Merge pull request #429 from 4sval/dev

Dev
This commit is contained in:
Valentin
2023-10-28 19:43:28 +02:00
committed by GitHub
78 changed files with 2275 additions and 3243 deletions

View File

@@ -41,6 +41,8 @@ public partial class App
{ {
UserSettings.Default = JsonConvert.DeserializeObject<UserSettings>( UserSettings.Default = JsonConvert.DeserializeObject<UserSettings>(
File.ReadAllText(UserSettings.FilePath), JsonNetSerializer.SerializerSettings); File.ReadAllText(UserSettings.FilePath), JsonNetSerializer.SerializerSettings);
/*if (UserSettings.Default.ShowChangelog) */MigrateV1Games();
} }
catch catch
{ {
@@ -141,6 +143,17 @@ public partial class App
e.Handled = true; e.Handled = true;
} }
private void MigrateV1Games()
{
foreach ((var gameDir, var setting) in UserSettings.Default.ManualGames)
{
if (!Directory.Exists(gameDir)) continue;
UserSettings.Default.PerDirectory[gameDir] =
DirectorySettings.Default(setting.GameName, setting.GameDirectory, true, setting.OverridedGame, setting.AesKeys?.MainKey);
}
UserSettings.Default.ManualGames.Clear();
}
private string GetOperatingSystemProductName() private string GetOperatingSystemProductName()
{ {
var productName = string.Empty; var productName = string.Empty;

View File

@@ -20,7 +20,7 @@ public static class Constants
public const string YELLOW = "#E5C07B"; public const string YELLOW = "#E5C07B";
public const string BLUE = "#528BCC"; public const string BLUE = "#528BCC";
public const string ISSUE_LINK = "https://github.com/iAmAsval/FModel/issues/new/choose"; public const string ISSUE_LINK = "https://github.com/4sval/FModel/discussions/categories/q-a";
public const string DONATE_LINK = "https://fmodel.app/donate"; public const string DONATE_LINK = "https://fmodel.app/donate";
public const string DISCORD_LINK = "https://fmodel.app/discord"; public const string DISCORD_LINK = "https://fmodel.app/discord";

View File

@@ -52,7 +52,7 @@ public class BaseIconStats : BaseIcon
foreach (var poi in challengeMapPoiData) foreach (var poi in challengeMapPoiData)
{ {
if (!poi.TryGetValue(out FStructFallback locationTag, "LocationTag") || !locationTag.TryGetValue(out FName tagName, "TagName") || if (!poi.TryGetValue(out FStructFallback locationTag, "LocationTag") || !locationTag.TryGetValue(out FName tagName, "TagName") ||
!tagName.Text.Equals(location.Text, StringComparison.OrdinalIgnoreCase) || !poi.TryGetValue(out FText text, "Text")) continue; tagName != location.TagName || !poi.TryGetValue(out FText text, "Text")) continue;
locationName = text.Text; locationName = text.Text;
break; break;
@@ -92,7 +92,11 @@ public class BaseIconStats : BaseIcon
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "35D04D1B45737BEA25B69686D9E085B9", "Damage"), dmgPb * multiplier, 200)); _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "35D04D1B45737BEA25B69686D9E085B9", "Damage"), dmgPb * multiplier, 200));
} }
if (weaponRowValue.TryGetValue(out float dmgCritical, "DamageZone_Critical")) if (weaponRowValue.TryGetValue(out float mdpc, "MaxDamagePerCartridge") && mdpc >= 0f)
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "0DEF2455463B008C4499FEA03D149EDF", "Headshot Damage"), mdpc, 200));
}
else if (weaponRowValue.TryGetValue(out float dmgCritical, "DamageZone_Critical"))
{ {
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "0DEF2455463B008C4499FEA03D149EDF", "Headshot Damage"), dmgPb * dmgCritical * multiplier, 200)); _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "0DEF2455463B008C4499FEA03D149EDF", "Headshot Damage"), dmgPb * dmgCritical * multiplier, 200));
} }

View File

@@ -160,9 +160,9 @@ public class CreatorPackage : IDisposable
case "FortChallengeBundleItemDefinition": case "FortChallengeBundleItemDefinition":
creator = new BaseBundle(_object, _style); creator = new BaseBundle(_object, _style);
return true; return true;
case "AthenaSeasonItemDefinition": // case "AthenaSeasonItemDefinition":
creator = new BaseSeason(_object, _style); // creator = new BaseSeason(_object, _style);
return true; // return true;
case "FortItemAccessTokenType": case "FortItemAccessTokenType":
creator = new BaseItemAccessToken(_object, _style); creator = new BaseItemAccessToken(_object, _style);
return true; return true;
@@ -203,69 +203,6 @@ public class CreatorPackage : IDisposable
case "QuestData": case "QuestData":
creator = new Bases.MV.BaseQuest(_object, _style); creator = new Bases.MV.BaseQuest(_object, _style);
return true; return true;
// Battle Breakers
case "WExpGenericAccountItemDefinition":
case "WExpGearAccountItemDefinition":
case "WExpHQWorkerLodgesDefinition":
case "WExpPersonalEventDefinition":
case "WExpUpgradePotionDefinition":
case "WExpAccountRewardDefinition":
case "WExpHQBlacksmithDefinition":
case "WExpHQSecretShopDefinition":
case "WExpHQMonsterPitDefinition":
case "WExpHQHeroTowerDefinition":
case "WExpVoucherItemDefinition":
case "WExpTreasureMapDefinition":
case "WExpHammerChestDefinition":
case "WExpHQWorkshopDefinition":
case "WExpUnlockableDefinition":
case "WExpHQSmelterDefinition":
case "WExpContainerDefinition":
case "WExpCharacterDefinition":
case "WExpHQMarketDefinition":
case "WExpGiftboxDefinition":
case "WExpStandInDefinition":
case "WExpRegionDefinition":
case "WExpHQMineDefinition":
case "WExpXpBookDefinition":
case "WExpTokenDefinition":
case "WExpItemDefinition":
case "WExpZoneDefinition":
creator = new BaseBreakersIcon(_object, EIconStyle.Default);
return true;
// Spellbreak
case "GTargetedTeleportActiveSkill":
case "GChronomasterV2ActiveSkill":
case "GShadowstepActiveSkill":
case "GGatewayActiveSkill":
case "GStealthActiveSkill":
case "GFeatherActiveSkill":
case "GCosmeticDropTrail":
case "GFlightActiveSkill":
case "GCosmeticRunTrail":
case "GCosmeticArtifact":
case "GCosmeticTriumph":
case "GWolfsbloodSkill":
case "GDashActiveSkill":
case "GCharacterPerk":
case "GCosmeticTitle":
case "GCosmeticBadge":
case "GRMTStoreOffer":
case "GCosmeticEmote":
case "GCosmeticCard":
case "GCosmeticSkin":
case "GStoreOffer":
case "GAccolade":
case "GRuneItem":
case "GQuest":
creator = new BaseSpellIcon(_object, EIconStyle.Default);
return true;
case "GLeagueTier":
creator = new BaseLeague(_object, EIconStyle.Default);
return true;
case "GLeagueDivision":
creator = new BaseDivision(_object, EIconStyle.Default);
return true;
default: default:
creator = null; creator = null;
return false; return false;

View File

@@ -21,7 +21,6 @@ public class Typefaces
private const string _BURBANK_BIG_REGULAR_BOLD = "BurbankBigRegular-Bold"; // official fortnite ig private const string _BURBANK_BIG_REGULAR_BOLD = "BurbankBigRegular-Bold"; // official fortnite ig
private const string _BURBANK_SMALL_MEDIUM = "BurbankSmall-Medium"; private const string _BURBANK_SMALL_MEDIUM = "BurbankSmall-Medium";
private const string _DROID_SANS_FORTNITE_SUBSET = "DroidSans-Fortnite-Subset"; 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_COLOR_EMOJI = "NotoColorEmoji";
private const string _NOTO_SANS_BOLD = "NotoSans-Bold"; private const string _NOTO_SANS_BOLD = "NotoSans-Bold";
private const string _NOTO_SANS_FORTNITE_BOLD = "NotoSans-Fortnite-Bold"; private const string _NOTO_SANS_FORTNITE_BOLD = "NotoSans-Fortnite-Bold";
@@ -32,7 +31,7 @@ public class Typefaces
private const string _NOTO_SANS_ARABIC_BLACK = "NotoSansArabic-Black"; // arabic fortnite 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_BOLD = "NotoSansArabic-Bold";
private const string _NOTO_SANS_ARABIC_REGULAR = "NotoSansArabic-Regular"; private const string _NOTO_SANS_ARABIC_REGULAR = "NotoSansArabic-Regular";
private const string _NOTO_SANS_JP_BOLD = "NotoSansJP-Bold"; private const string _NOTO_SANS_JP_BOLD = "NotoSansJP-Bold"; // japanese fortnite
private const string _NOTO_SANS_KR_REGULAR = "NotoSansKR-Regular"; private const string _NOTO_SANS_KR_REGULAR = "NotoSansKR-Regular";
private const string _NOTO_SANS_SC_BLACK = "NotoSansSC-Black"; // simplified chinese fortnite private const string _NOTO_SANS_SC_BLACK = "NotoSansSC-Black"; // simplified chinese fortnite
private const string _NOTO_SANS_SC_REGULAR = "NotoSansSC-Regular"; private const string _NOTO_SANS_SC_REGULAR = "NotoSansSC-Regular";
@@ -49,23 +48,6 @@ public class Typefaces
private const string _XIANGHEHEI_SC_PRO_BLACK = "XiangHeHei_SC/MXiangHeHeiSCPro-Black"; private const string _XIANGHEHEI_SC_PRO_BLACK = "XiangHeHei_SC/MXiangHeHeiSCPro-Black";
private const string _XIANGHEHEI_SC_PRO_HEAVY = "XiangHeHei_SC/MXiangHeHeiSCPro-Heavy"; private const string _XIANGHEHEI_SC_PRO_HEAVY = "XiangHeHei_SC/MXiangHeHeiSCPro-Heavy";
// Spellbreak
private const string _SPELLBREAK_BASE_PATH = "/Game/UI/Fonts/";
private const string _MONTSERRAT_SEMIBOLD = "Montserrat-Semibold";
private const string _MONTSERRAT_SEMIBOLD_ITALIC = "Montserrat-SemiBoldItalic";
private const string _NANUM_GOTHIC = "NanumGothic";
private const string _QUADRAT_BOLD = "Quadrat_Bold";
private const string _SEGOE_BOLD_ITALIC = "Segoe_Bold_Italic";
// WorldExplorers
private const string _BATTLE_BREAKERS_BASE_PATH = "/Game/UMG/Fonts/Faces/";
private const string _HEMIHEAD426 = "HemiHead426";
private const string _NOTO_SANS_JP_REGULAR = "NotoSansJP-Regular";
private const string _LATO_BLACK = "Lato-Black";
private const string _LATO_BLACK_ITALIC = "Lato-BlackItalic";
private const string _LATO_LIGHT = "Lato-Light";
private const string _LATO_MEDIUM = "Lato-Medium";
private readonly CUE4ParseViewModel _viewModel; private readonly CUE4ParseViewModel _viewModel;
public readonly SKTypeface Default; // used as a fallback font for all untranslated strings (item source, ...) public readonly SKTypeface Default; // used as a fallback font for all untranslated strings (item source, ...)
@@ -85,16 +67,16 @@ public class Typefaces
Default = SKTypeface.FromStream(Application.GetResourceStream(_BURBANK_BIG_CONDENSED_BOLD)?.Stream); Default = SKTypeface.FromStream(Application.GetResourceStream(_BURBANK_BIG_CONDENSED_BOLD)?.Stream);
switch (viewModel.Game) switch (viewModel.Provider.InternalGameName.ToUpperInvariant())
{ {
case FGame.FortniteGame: case "FORTNITEGAME":
{ {
DisplayName = OnTheFly(_FORTNITE_BASE_PATH + DisplayName = OnTheFly(_FORTNITE_BASE_PATH +
language switch language switch
{ {
ELanguage.Korean => _ASIA_ERINM, ELanguage.Korean => _ASIA_ERINM,
ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK, ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK,
ELanguage.Japanese => _NIS_JYAU, ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK, ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK, ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
ELanguage.Chinese => _NOTO_SANS_SC_BLACK, ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
@@ -130,7 +112,7 @@ public class Typefaces
{ {
ELanguage.Korean => _ASIA_ERINM, ELanguage.Korean => _ASIA_ERINM,
ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK, ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK,
ELanguage.Japanese => _NIS_JYAU, ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK, ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK, ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
ELanguage.Chinese => _NOTO_SANS_SC_BLACK, ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
@@ -142,7 +124,7 @@ public class Typefaces
{ {
ELanguage.Korean => _ASIA_ERINM, ELanguage.Korean => _ASIA_ERINM,
ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK, ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK,
ELanguage.Japanese => _NIS_JYAU, ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK, ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK, ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
ELanguage.Chinese => _NOTO_SANS_SC_BLACK, ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
@@ -153,7 +135,7 @@ public class Typefaces
language switch language switch
{ {
ELanguage.Korean => _ASIA_ERINM, ELanguage.Korean => _ASIA_ERINM,
ELanguage.Japanese => _NIS_JYAU, ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK, ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK, ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
ELanguage.Chinese => _NOTO_SANS_SC_BLACK, ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
@@ -164,7 +146,7 @@ public class Typefaces
language switch language switch
{ {
ELanguage.Korean => _ASIA_ERINM, ELanguage.Korean => _ASIA_ERINM,
ELanguage.Japanese => _NIS_JYAU, ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK, ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK, ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
ELanguage.Chinese => _NOTO_SANS_SC_BLACK, ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
@@ -172,36 +154,7 @@ public class Typefaces
} + _EXT); } + _EXT);
break; break;
} }
case FGame.WorldExplorers: case "MULTIVERSUS":
{
DisplayName = OnTheFly(_BATTLE_BREAKERS_BASE_PATH +
language switch
{
ELanguage.Korean => _NOTO_SANS_KR_REGULAR,
ELanguage.Russian => _LATO_BLACK,
ELanguage.Japanese => _NOTO_SANS_JP_REGULAR,
ELanguage.Chinese => _NOTO_SANS_SC_REGULAR,
_ => _HEMIHEAD426
} + _EXT);
Description = OnTheFly(_BATTLE_BREAKERS_BASE_PATH +
language switch
{
ELanguage.Korean => _NOTO_SANS_KR_REGULAR,
ELanguage.Russian => _LATO_BLACK,
ELanguage.Japanese => _NOTO_SANS_JP_REGULAR,
ELanguage.Chinese => _NOTO_SANS_SC_REGULAR,
_ => _HEMIHEAD426
} + _EXT);
break;
}
case FGame.g3:
{
DisplayName = OnTheFly(_SPELLBREAK_BASE_PATH + _QUADRAT_BOLD + _EXT);
Description = OnTheFly(_SPELLBREAK_BASE_PATH + _MONTSERRAT_SEMIBOLD + _EXT);
break;
}
case FGame.MultiVersus:
{ {
DisplayName = OnTheFly(_PANDAGAME_BASE_PATH + language switch DisplayName = OnTheFly(_PANDAGAME_BASE_PATH + language switch
{ {

View File

@@ -112,7 +112,7 @@ public static class Utils
public static SKBitmap GetB64Bitmap(string b64) => SKBitmap.Decode(new MemoryStream(Convert.FromBase64String(b64)) { Position = 0 }); public static SKBitmap GetB64Bitmap(string b64) => SKBitmap.Decode(new MemoryStream(Convert.FromBase64String(b64)) { Position = 0 });
public static SKBitmap GetBitmap(FSoftObjectPath softObjectPath) => GetBitmap(softObjectPath.AssetPathName.Text); public static SKBitmap GetBitmap(FSoftObjectPath softObjectPath) => GetBitmap(softObjectPath.AssetPathName.Text);
public static SKBitmap GetBitmap(string fullPath) => TryLoadObject(fullPath, out UTexture2D texture) ? GetBitmap(texture) : null; public static SKBitmap GetBitmap(string fullPath) => TryLoadObject(fullPath, out UTexture2D texture) ? GetBitmap(texture) : null;
public static SKBitmap GetBitmap(UTexture2D texture) => texture.IsVirtual ? null : texture.Decode(UserSettings.Default.OverridedPlatform); public static SKBitmap GetBitmap(UTexture2D texture) => texture.Decode(UserSettings.Default.CurrentDir.TexturePlatform);
public static SKBitmap GetBitmap(byte[] data) => SKBitmap.Decode(data); public static SKBitmap GetBitmap(byte[] data) => SKBitmap.Decode(data);
public static SKBitmap ResizeWithRatio(this SKBitmap me, double width, double height) public static SKBitmap ResizeWithRatio(this SKBitmap me, double width, double height)

View File

@@ -52,56 +52,6 @@ public enum EDiscordRpc
Never Never
} }
public enum FGame
{
[Description("Unknown")]
Unknown,
[Description("Fortnite")]
FortniteGame,
[Description("Valorant")]
ShooterGame,
[Description("Dead By Daylight")]
DeadByDaylight,
[Description("Borderlands 3")]
OakGame,
[Description("Minecraft Dungeons")]
Dungeons,
[Description("Battle Breakers")]
WorldExplorers,
[Description("Spellbreak")]
g3,
[Description("State Of Decay 2")]
StateOfDecay2,
[Description("The Cycle")]
Prospect,
[Description("The Outer Worlds")]
Indiana,
[Description("Rogue Company")]
RogueCompany,
[Description("Star Wars: Jedi Fallen Order")]
SwGame,
[Description("Core")]
Platform,
[Description("Days Gone")]
BendGame,
[Description("PLAYERUNKNOWN'S BATTLEGROUNDS")]
TslGame,
[Description("Splitgate")]
PortalWars,
[Description("GTA: The Trilogy - Definitive Edition")]
Gameface,
[Description("Sea of Thieves")]
Athena,
[Description("DEPRECATED")]
PandaGame,
[Description("MultiVersus")]
MultiVersus,
[Description("Tower of Fantasy")]
Hotta,
[Description("eFootball 2023")]
eFootball
}
public enum ELoadingMode public enum ELoadingMode
{ {
[Description("Single")] [Description("Single")]

View File

@@ -5,9 +5,9 @@
<TargetFramework>net6.0-windows</TargetFramework> <TargetFramework>net6.0-windows</TargetFramework>
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
<ApplicationIcon>FModel.ico</ApplicationIcon> <ApplicationIcon>FModel.ico</ApplicationIcon>
<Version>4.4.3.1</Version> <Version>4.4.3.4</Version>
<AssemblyVersion>4.4.3.1</AssemblyVersion> <AssemblyVersion>4.4.3.4</AssemblyVersion>
<FileVersion>4.4.3.1</FileVersion> <FileVersion>4.4.3.4</FileVersion>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<IsPublishable>true</IsPublishable> <IsPublishable>true</IsPublishable>
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <RuntimeIdentifier>win-x64</RuntimeIdentifier>
@@ -77,6 +77,7 @@
<None Remove="Resources\linux.png" /> <None Remove="Resources\linux.png" />
<None Remove="Resources\stateofdecay2.png" /> <None Remove="Resources\stateofdecay2.png" />
<None Remove="Resources\T_Placeholder_Item_Image.png" /> <None Remove="Resources\T_Placeholder_Item_Image.png" />
<None Remove="Resources\checker.png" />
<None Remove="Resources\T_ClipSize_Weapon_Stats.png" /> <None Remove="Resources\T_ClipSize_Weapon_Stats.png" />
<None Remove="Resources\T_DamagePerBullet_Weapon_Stats.png" /> <None Remove="Resources\T_DamagePerBullet_Weapon_Stats.png" />
<None Remove="Resources\T_ReloadTime_Weapon_Stats.png" /> <None Remove="Resources\T_ReloadTime_Weapon_Stats.png" />
@@ -107,6 +108,8 @@
<None Remove="Resources\picking.vert" /> <None Remove="Resources\picking.vert" />
<None Remove="Resources\light.frag" /> <None Remove="Resources\light.frag" />
<None Remove="Resources\light.vert" /> <None Remove="Resources\light.vert" />
<None Remove="Resources\bone.frag" />
<None Remove="Resources\bone.vert" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -130,24 +133,26 @@
<EmbeddedResource Include="Resources\picking.vert" /> <EmbeddedResource Include="Resources\picking.vert" />
<EmbeddedResource Include="Resources\light.frag" /> <EmbeddedResource Include="Resources\light.frag" />
<EmbeddedResource Include="Resources\light.vert" /> <EmbeddedResource Include="Resources\light.vert" />
<EmbeddedResource Include="Resources\bone.frag" />
<EmbeddedResource Include="Resources\bone.vert" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AdonisUI" Version="1.17.1" /> <PackageReference Include="AdonisUI" Version="1.17.1" />
<PackageReference Include="AdonisUI.ClassicTheme" Version="1.17.1" /> <PackageReference Include="AdonisUI.ClassicTheme" Version="1.17.1" />
<PackageReference Include="Autoupdater.NET.Official" Version="1.8.2" /> <PackageReference Include="Autoupdater.NET.Official" Version="1.8.3" />
<PackageReference Include="AvalonEdit" Version="6.3.0.90" /> <PackageReference Include="AvalonEdit" Version="6.3.0.90" />
<PackageReference Include="CSCore" Version="1.2.1.2" /> <PackageReference Include="CSCore" Version="1.2.1.2" />
<PackageReference Include="DiscordRichPresence" Version="1.1.3.18" /> <PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
<PackageReference Include="ImGui.NET" Version="1.89.5" /> <PackageReference Include="ImGui.NET" Version="1.89.7.1" />
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.3.5" /> <PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.3.5" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NVorbis" Version="0.10.5" /> <PackageReference Include="NVorbis" Version="0.10.5" />
<PackageReference Include="Oodle.NET" Version="1.0.1" /> <PackageReference Include="Oodle.NET" Version="1.0.1" />
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" /> <PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" />
<PackageReference Include="OpenTK" Version="4.7.7" /> <PackageReference Include="OpenTK" Version="4.8.0" />
<PackageReference Include="RestSharp" Version="110.2.0" /> <PackageReference Include="RestSharp" Version="110.2.0" />
<PackageReference Include="Serilog" Version="2.12.0" /> <PackageReference Include="Serilog" Version="3.0.1" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" /> <PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.0.1" /> <PackageReference Include="SixLabors.ImageSharp" Version="3.0.1" />
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.88.3" /> <PackageReference Include="SkiaSharp.HarfBuzz" Version="2.88.3" />
@@ -203,6 +208,7 @@
<Resource Include="Resources\linux.png" /> <Resource Include="Resources\linux.png" />
<Resource Include="Resources\stateofdecay2.png" /> <Resource Include="Resources\stateofdecay2.png" />
<Resource Include="Resources\T_Placeholder_Item_Image.png" /> <Resource Include="Resources\T_Placeholder_Item_Image.png" />
<Resource Include="Resources\checker.png" />
<Resource Include="Resources\T_ClipSize_Weapon_Stats.png" /> <Resource Include="Resources\T_ClipSize_Weapon_Stats.png" />
<Resource Include="Resources\T_DamagePerBullet_Weapon_Stats.png" /> <Resource Include="Resources\T_DamagePerBullet_Weapon_Stats.png" />
<Resource Include="Resources\T_ReloadTime_Weapon_Stats.png" /> <Resource Include="Resources\T_ReloadTime_Weapon_Stats.png" />

View File

@@ -94,7 +94,7 @@
</Viewbox> </Viewbox>
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Directories" ItemsSource="{Binding CustomDirectories.Directories}" IsEnabled="{Binding Status.IsReady}"> <MenuItem Header="Favorite Directories" ItemsSource="{Binding CustomDirectories.Directories}" IsEnabled="{Binding Status.IsReady}">
<MenuItem.Icon> <MenuItem.Icon>
<Viewbox Width="16" Height="16"> <Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24"> <Canvas Width="24" Height="24">
@@ -126,25 +126,6 @@
</Viewbox> </Viewbox>
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Map Viewer" Command="{Binding MenuCommand}" CommandParameter="Views_MapViewer" IsEnabled="{Binding Status.IsReady}">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource MapIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
<MenuItem.Style>
<Style TargetType="MenuItem" BasedOn="{StaticResource {x:Type MenuItem}}">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding CUE4Parse.Game}" Value="{x:Static local:FGame.FortniteGame}">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</MenuItem.Style>
</MenuItem>
<MenuItem Header="Image Merger" Command="{Binding MenuCommand}" CommandParameter="Views_ImageMerger"> <MenuItem Header="Image Merger" Command="{Binding MenuCommand}" CommandParameter="Views_ImageMerger">
<MenuItem.Icon> <MenuItem.Icon>
<Viewbox Width="16" Height="16"> <Viewbox Width="16" Height="16">
@@ -396,7 +377,7 @@
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<Separator /> <Separator />
<MenuItem Header="Save Directory" Click="OnSaveDirectoryClick"> <MenuItem Header="Favorite Directory" Click="OnFavoriteDirectoryClick">
<MenuItem.Icon> <MenuItem.Icon>
<Viewbox Width="16" Height="16"> <Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24"> <Canvas Width="24" Height="24">

View File

@@ -40,7 +40,6 @@ public partial class MainWindow
private void OnClosing(object sender, CancelEventArgs e) private void OnClosing(object sender, CancelEventArgs e)
{ {
_applicationView.CustomDirectories.Save();
_discordHandler.Dispose(); _discordHandler.Dispose();
} }
@@ -56,8 +55,8 @@ public partial class MainWindow
case EAesReload.Always: case EAesReload.Always:
await _applicationView.CUE4Parse.RefreshAes(); await _applicationView.CUE4Parse.RefreshAes();
break; break;
case EAesReload.OncePerDay when UserSettings.Default.LastAesReload != DateTime.Today: case EAesReload.OncePerDay when UserSettings.Default.CurrentDir.LastAesReload != DateTime.Today:
UserSettings.Default.LastAesReload = DateTime.Today; UserSettings.Default.CurrentDir.LastAesReload = DateTime.Today;
await _applicationView.CUE4Parse.RefreshAes(); await _applicationView.CUE4Parse.RefreshAes();
break; break;
} }
@@ -84,9 +83,12 @@ public partial class MainWindow
).ConfigureAwait(false); ).ConfigureAwait(false);
#if DEBUG #if DEBUG
// await _threadWorkerView.Begin(cancellationToken => await _threadWorkerView.Begin(cancellationToken =>
// _applicationView.CUE4Parse.Extract(cancellationToken, _applicationView.CUE4Parse.Extract(cancellationToken,
// "fortnitegame/Content/Characters/Player/Female/Medium/Bodies/F_MED_Ballerina/Meshes/F_MED_Ballerina.uasset")); "fortnitegame/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Meshes/F_Med_Soldier_01.uasset"));
await _threadWorkerView.Begin(cancellationToken =>
_applicationView.CUE4Parse.Extract(cancellationToken,
"fortnitegame/Content/Animation/Game/MainPlayer/Emotes/Cowbell/Cowbell_CMM_Loop_M.uasset"));
#endif #endif
} }
@@ -140,7 +142,7 @@ public partial class MainWindow
private async void OnMappingsReload(object sender, ExecutedRoutedEventArgs e) private async void OnMappingsReload(object sender, ExecutedRoutedEventArgs e)
{ {
await _applicationView.CUE4Parse.InitMappings(); await _applicationView.CUE4Parse.InitMappings(true);
} }
private void OnOpenAvalonFinder() private void OnOpenAvalonFinder()
@@ -228,13 +230,13 @@ public partial class MainWindow
} }
} }
private void OnSaveDirectoryClick(object sender, RoutedEventArgs e) private void OnFavoriteDirectoryClick(object sender, RoutedEventArgs e)
{ {
if (AssetsFolderName.SelectedItem is not TreeItem folder) return; if (AssetsFolderName.SelectedItem is not TreeItem folder) return;
_applicationView.CustomDirectories.Add(new CustomDirectory(folder.Header, folder.PathAtThisPoint)); _applicationView.CustomDirectories.Add(new CustomDirectory(folder.Header, folder.PathAtThisPoint));
FLogger.Append(ELog.Information, () => FLogger.Append(ELog.Information, () =>
FLogger.Text($"Successfully saved '{folder.PathAtThisPoint}' as a new custom directory", Constants.WHITE, true)); FLogger.Text($"Successfully saved '{folder.PathAtThisPoint}' as a new favorite directory", Constants.WHITE, true));
} }
private void OnCopyDirectoryPathClick(object sender, RoutedEventArgs e) private void OnCopyDirectoryPathClick(object sender, RoutedEventArgs e)

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

View File

@@ -228,7 +228,7 @@ void main()
} }
else if (bVertexColors[4]) else if (bVertexColors[4])
{ {
FragColor = vec4(fTexCoords, 0.0, 1.0); FragColor = SamplerToVector(uParameters.Diffuse[0].Sampler);
} }
else else
{ {

View File

@@ -16,10 +16,15 @@ layout(std430, binding = 1) buffer BoneMatrices
{ {
mat4 uFinalBonesMatrix[]; mat4 uFinalBonesMatrix[];
}; };
layout(std430, binding = 2) buffer RestBoneMatrices
{
mat4 uRestBonesMatrix[];
};
uniform mat4 uView; uniform mat4 uView;
uniform mat4 uProjection; uniform mat4 uProjection;
uniform float uMorphTime; uniform float uMorphTime;
uniform bool uIsAnimated;
out vec3 fPos; out vec3 fPos;
out vec3 fNormal; out vec3 fNormal;
@@ -37,14 +42,14 @@ void main()
vec4 finalPos = vec4(0.0); vec4 finalPos = vec4(0.0);
vec4 finalNormal = vec4(0.0); vec4 finalNormal = vec4(0.0);
vec4 finalTangent = vec4(0.0); vec4 finalTangent = vec4(0.0);
if (vBoneIds != vBoneWeights) if (uIsAnimated)
{ {
for(int i = 0 ; i < 4; i++) for(int i = 0 ; i < 4; i++)
{ {
int boneIndex = int(vBoneIds[i]); int boneIndex = int(vBoneIds[i]);
if(boneIndex < 0) break; if(boneIndex < 0) break;
mat4 boneMatrix = uFinalBonesMatrix[boneIndex]; mat4 boneMatrix = uFinalBonesMatrix[boneIndex] * inverse(uRestBonesMatrix[boneIndex]);
mat4 inverseBoneMatrix = transpose(inverse(boneMatrix)); mat4 inverseBoneMatrix = transpose(inverse(boneMatrix));
float weight = vBoneWeights[i]; float weight = vBoneWeights[i];

View File

@@ -11,11 +11,16 @@ layout(std430, binding = 1) buffer BoneMatrices
{ {
mat4 uFinalBonesMatrix[]; mat4 uFinalBonesMatrix[];
}; };
layout(std430, binding = 2) buffer RestBoneMatrices
{
mat4 uRestBonesMatrix[];
};
uniform mat4 uView; uniform mat4 uView;
uniform vec3 uViewPos; uniform vec3 uViewPos;
uniform mat4 uProjection; uniform mat4 uProjection;
uniform float uMorphTime; uniform float uMorphTime;
uniform bool uIsAnimated;
void main() void main()
{ {
@@ -24,14 +29,14 @@ void main()
vec4 finalPos = vec4(0.0); vec4 finalPos = vec4(0.0);
vec4 finalNormal = vec4(0.0); vec4 finalNormal = vec4(0.0);
if (vBoneIds != vBoneWeights) if (uIsAnimated)
{ {
for(int i = 0 ; i < 4; i++) for(int i = 0 ; i < 4; i++)
{ {
int boneIndex = int(vBoneIds[i]); int boneIndex = int(vBoneIds[i]);
if(boneIndex < 0) break; if(boneIndex < 0) break;
mat4 boneMatrix = uFinalBonesMatrix[boneIndex]; mat4 boneMatrix = uFinalBonesMatrix[boneIndex] * inverse(uRestBonesMatrix[boneIndex]);
float weight = vBoneWeights[i]; float weight = vBoneWeights[i];
finalPos += boneMatrix * bindPos * weight; finalPos += boneMatrix * bindPos * weight;

View File

@@ -10,24 +10,29 @@ layout(std430, binding = 1) buffer BoneMatrices
{ {
mat4 uFinalBonesMatrix[]; mat4 uFinalBonesMatrix[];
}; };
layout(std430, binding = 2) buffer RestBoneMatrices
{
mat4 uRestBonesMatrix[];
};
uniform mat4 uView; uniform mat4 uView;
uniform mat4 uProjection; uniform mat4 uProjection;
uniform float uMorphTime; uniform float uMorphTime;
uniform bool uIsAnimated;
void main() void main()
{ {
vec4 bindPos = vec4(mix(vPos, vMorphTargetPos, uMorphTime), 1.0); vec4 bindPos = vec4(mix(vPos, vMorphTargetPos, uMorphTime), 1.0);
vec4 finalPos = vec4(0.0); vec4 finalPos = vec4(0.0);
if (vBoneIds != vBoneWeights) if (uIsAnimated)
{ {
for(int i = 0 ; i < 4; i++) for(int i = 0 ; i < 4; i++)
{ {
int boneIndex = int(vBoneIds[i]); int boneIndex = int(vBoneIds[i]);
if(boneIndex < 0) break; if(boneIndex < 0) break;
finalPos += uFinalBonesMatrix[boneIndex] * bindPos * vBoneWeights[i]; finalPos += uFinalBonesMatrix[boneIndex] * inverse(uRestBonesMatrix[boneIndex]) * bindPos * vBoneWeights[i];
} }
} }
else finalPos = bindPos; else finalPos = bindPos;

View File

@@ -41,14 +41,14 @@ namespace FModel.Services
Details = $"{gameName} - Idling" Details = $"{gameName} - Idling"
}; };
_client.OnReady += (_, args) => Log.Information("{Username}#{Discriminator} ({UserId}) is now ready", args.User.Username, args.User.Discriminator, args.User.ID); _client.OnReady += (_, args) => Log.Information("@{Username} ({UserId}) is now ready", args.User.Username, args.User.ID);
_client.SetPresence(_currentPresence); _client.SetPresence(_currentPresence);
_client.Initialize(); _client.Initialize();
} }
public void UpdatePresence(CUE4ParseViewModel viewModel) => public void UpdatePresence(CUE4ParseViewModel viewModel) =>
UpdatePresence( UpdatePresence(
$"{viewModel.Provider.GameDisplayName ?? viewModel.Provider.GameName} - {viewModel.Provider.MountedVfs.Count}/{viewModel.Provider.MountedVfs.Count + viewModel.Provider.UnloadedVfs.Count} Packages", $"{viewModel.Provider.GameDisplayName ?? viewModel.Provider.InternalGameName} - {viewModel.Provider.MountedVfs.Count}/{viewModel.Provider.MountedVfs.Count + viewModel.Provider.UnloadedVfs.Count} Packages",
$"Mode: {UserSettings.Default.LoadingMode.GetDescription()} - {viewModel.SearchVm.ResultsCount:### ### ###} Loaded Assets".Trim()); $"Mode: {UserSettings.Default.LoadingMode.GetDescription()} - {viewModel.SearchVm.ResultsCount:### ### ###} Loaded Assets".Trim());
public void UpdatePresence(string details, string state) public void UpdatePresence(string details, string state)

View File

@@ -0,0 +1,65 @@
using System.Collections.Generic;
using FModel.Framework;
namespace FModel.Settings;
public class CustomDirectory : ViewModel
{
public static IList<CustomDirectory> Default(string gameName)
{
switch (gameName)
{
case "Fortnite":
case "Fortnite [LIVE]":
return new List<CustomDirectory>
{
new("Cosmetics", "FortniteGame/Content/Athena/Items/Cosmetics/"),
new("Emotes [AUDIO]", "FortniteGame/Content/Athena/Sounds/Emotes/"),
new("Music Packs [AUDIO]", "FortniteGame/Content/Athena/Sounds/MusicPacks/"),
new("Weapons", "FortniteGame/Content/Athena/Items/Weapons/"),
new("Strings", "FortniteGame/Content/Localization/")
};
case "VALORANT":
case "VALORANT [LIVE]":
return new List<CustomDirectory>
{
new("Audio", "ShooterGame/Content/WwiseAudio/Media/"),
new("Characters", "ShooterGame/Content/Characters/"),
new("Gun Buddies", "ShooterGame/Content/Equippables/Buddies/"),
new("Cards and Sprays", "ShooterGame/Content/Personalization/"),
new("Shop Backgrounds", "ShooterGame/Content/UI/OutOfGame/MainMenu/Store/Shared/Textures/"),
new("Weapon Renders", "ShooterGame/Content/UI/Screens/OutOfGame/MainMenu/Collection/Assets/Large/")
};
default:
return new List<CustomDirectory>();
}
}
private string _header;
public string Header
{
get => _header;
set => SetProperty(ref _header, value);
}
private string _directoryPath;
public string DirectoryPath
{
get => _directoryPath;
set => SetProperty(ref _directoryPath, value);
}
public CustomDirectory()
{
Header = string.Empty;
DirectoryPath = string.Empty;
}
public CustomDirectory(string header, string path)
{
Header = header;
DirectoryPath = path;
}
public override string ToString() => Header;
}

View File

@@ -0,0 +1,120 @@
using System;
using System.Collections.Generic;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Versions;
using FModel.Framework;
using FModel.ViewModels.ApiEndpoints.Models;
namespace FModel.Settings;
public class DirectorySettings : ViewModel, ICloneable
{
public static DirectorySettings Default(
string gameName, string gameDir, bool manual = false, EGame ue = EGame.GAME_UE4_LATEST, string aes = "")
{
UserSettings.Default.PerDirectory.TryGetValue(gameDir, out var old);
return new DirectorySettings
{
GameName = gameName,
GameDirectory = gameDir,
IsManual = manual,
UeVersion = old?.UeVersion ?? ue,
TexturePlatform = old?.TexturePlatform ?? ETexturePlatform.DesktopMobile,
Versioning = old?.Versioning ?? new VersioningSettings(),
Endpoints = old?.Endpoints ?? EndpointSettings.Default(gameName),
Directories = old?.Directories ?? CustomDirectory.Default(gameName),
AesKeys = old?.AesKeys ?? new AesResponse { MainKey = aes, DynamicKeys = null },
LastAesReload = old?.LastAesReload ?? DateTime.Today.AddDays(-1)
};
}
private string _gameName;
public string GameName
{
get => _gameName;
set => SetProperty(ref _gameName, value);
}
private string _gameDirectory;
public string GameDirectory
{
get => _gameDirectory;
set => SetProperty(ref _gameDirectory, value);
}
private bool _isManual;
public bool IsManual
{
get => _isManual;
set => SetProperty(ref _isManual, value);
}
private EGame _ueVersion;
public EGame UeVersion
{
get => _ueVersion;
set => SetProperty(ref _ueVersion, value);
}
private ETexturePlatform _texturePlatform;
public ETexturePlatform TexturePlatform
{
get => _texturePlatform;
set => SetProperty(ref _texturePlatform, value);
}
private VersioningSettings _versioning;
public VersioningSettings Versioning
{
get => _versioning;
set => SetProperty(ref _versioning, value);
}
private EndpointSettings[] _endpoints;
public EndpointSettings[] Endpoints
{
get => _endpoints;
set => SetProperty(ref _endpoints, value);
}
private IList<CustomDirectory> _directories;
public IList<CustomDirectory> Directories
{
get => _directories;
set => SetProperty(ref _directories, value);
}
private AesResponse _aesKeys;
public AesResponse AesKeys
{
get => _aesKeys;
set => SetProperty(ref _aesKeys, value);
}
private DateTime _lastAesReload;
public DateTime LastAesReload
{
get => _lastAesReload;
set => SetProperty(ref _lastAesReload, value);
}
private bool Equals(DirectorySettings other)
{
return GameDirectory == other.GameDirectory && UeVersion == other.UeVersion;
}
public override bool Equals(object obj)
{
return obj is DirectorySettings other && Equals(other);
}
public override int GetHashCode()
{
return HashCode.Combine(GameDirectory, (int) UeVersion);
}
public object Clone()
{
return this.MemberwiseClone();
}
}

View File

@@ -1,12 +1,29 @@
using System.Linq; using System.Linq;
using FModel.Framework;
using FModel.ViewModels.ApiEndpoints; using FModel.ViewModels.ApiEndpoints;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace FModel.Framework; namespace FModel.Settings;
public class FEndpoint : ViewModel public class EndpointSettings : ViewModel
{ {
public static EndpointSettings[] Default(string gameName)
{
switch (gameName)
{
case "Fortnite":
case "Fortnite [LIVE]":
return new EndpointSettings[]
{
new("https://fortnitecentral.genxgames.gg/api/v1/aes", "$.['mainKey','dynamicKeys']"),
new("https://fortnitecentral.genxgames.gg/api/v1/mappings", "$.[?(@.meta.compressionMethod=='Oodle')].['url','fileName']")
};
default:
return new EndpointSettings[] { new(), new() };
}
}
private string _url; private string _url;
public string Url public string Url
{ {
@@ -51,8 +68,8 @@ public class FEndpoint : ViewModel
"Your endpoint configuration is valid! Please, avoid any unnecessary modifications!" : "Your endpoint configuration is valid! Please, avoid any unnecessary modifications!" :
"Your endpoint configuration DOES NOT seem to be valid yet! Please, test it out!"; "Your endpoint configuration DOES NOT seem to be valid yet! Please, test it out!";
public FEndpoint() {} public EndpointSettings() {}
public FEndpoint(string url, string path) public EndpointSettings(string url, string path)
{ {
Url = url; Url = url;
Path = path; Path = path;

View File

@@ -3,8 +3,6 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Windows; using System.Windows;
using System.Windows.Input; using System.Windows.Input;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Objects.Core.Serialization;
using CUE4Parse.UE4.Versions; using CUE4Parse.UE4.Versions;
using CUE4Parse_Conversion.Meshes; using CUE4Parse_Conversion.Meshes;
using CUE4Parse_Conversion.Textures; using CUE4Parse_Conversion.Textures;
@@ -33,6 +31,8 @@ namespace FModel.Settings
public static void Save() public static void Save()
{ {
if (Default == null) return;
Default.PerDirectory[Default.CurrentDir.GameDirectory] = Default.CurrentDir;
File.WriteAllText(FilePath, JsonConvert.SerializeObject(Default, Formatting.Indented)); File.WriteAllText(FilePath, JsonConvert.SerializeObject(Default, Formatting.Indented));
} }
@@ -41,13 +41,9 @@ namespace FModel.Settings
if (File.Exists(FilePath)) File.Delete(FilePath); if (File.Exists(FilePath)) File.Delete(FilePath);
} }
public static bool IsEndpointValid(FGame game, EEndpointType type, out FEndpoint endpoint) public static bool IsEndpointValid(EEndpointType type, out EndpointSettings endpoint)
{ {
endpoint = null; endpoint = Default.CurrentDir.Endpoints[(int) type];
if (!Default.CustomEndpoints.TryGetValue(game, out var endpoints))
return false;
endpoint = endpoints[(int) type];
return endpoint.Overwrite || endpoint.IsValid; return endpoint.Overwrite || endpoint.IsValid;
} }
@@ -100,7 +96,7 @@ namespace FModel.Settings
set => SetProperty(ref _modelDirectory, value); set => SetProperty(ref _modelDirectory, value);
} }
private string _gameDirectory; private string _gameDirectory = string.Empty;
public string GameDirectory public string GameDirectory
{ {
get => _gameDirectory; get => _gameDirectory;
@@ -135,13 +131,6 @@ namespace FModel.Settings
set => SetProperty(ref _avalonImageSize, value); set => SetProperty(ref _avalonImageSize, value);
} }
private IDictionary<FGame, AesResponse> _aesKeys = new Dictionary<FGame, AesResponse>();
public IDictionary<FGame, AesResponse> AesKeys
{
get => _aesKeys;
set => SetProperty(ref _aesKeys, value);
}
private string _audioDeviceId; private string _audioDeviceId;
public string AudioDeviceId public string AudioDeviceId
{ {
@@ -156,7 +145,7 @@ namespace FModel.Settings
set => SetProperty(ref _audioPlayerVolume, value); set => SetProperty(ref _audioPlayerVolume, value);
} }
private ELoadingMode _loadingMode = ELoadingMode.Multiple; private ELoadingMode _loadingMode = ELoadingMode.All;
public ELoadingMode LoadingMode public ELoadingMode LoadingMode
{ {
get => _loadingMode; get => _loadingMode;
@@ -233,9 +222,19 @@ namespace FModel.Settings
set => SetProperty(ref _readScriptData, value); set => SetProperty(ref _readScriptData, value);
} }
// <gameDirectory as string, settings> private IDictionary<string, DirectorySettings> _perDirectory = new Dictionary<string, DirectorySettings>();
// can't refactor to use this data layout for everything public IDictionary<string, DirectorySettings> PerDirectory
// because it will wipe old user settings that relies on FGame {
get => _perDirectory;
set => SetProperty(ref _perDirectory, value);
}
[JsonIgnore]
public DirectorySettings CurrentDir { get; set; }
/// <summary>
/// TO DELETEEEEEEEEEEEEE
/// </summary>
private IDictionary<string, GameSelectorViewModel.DetectedGame> _manualGames = new Dictionary<string, GameSelectorViewModel.DetectedGame>(); private IDictionary<string, GameSelectorViewModel.DetectedGame> _manualGames = new Dictionary<string, GameSelectorViewModel.DetectedGame>();
public IDictionary<string, GameSelectorViewModel.DetectedGame> ManualGames public IDictionary<string, GameSelectorViewModel.DetectedGame> ManualGames
{ {
@@ -243,291 +242,6 @@ namespace FModel.Settings
set => SetProperty(ref _manualGames, value); set => SetProperty(ref _manualGames, value);
} }
private ETexturePlatform _overridedPlatform = ETexturePlatform.DesktopMobile;
public ETexturePlatform OverridedPlatform
{
get => _overridedPlatform;
set => SetProperty(ref _overridedPlatform, value);
}
private IDictionary<FGame, string> _presets = new Dictionary<FGame, string>
{
{FGame.Unknown, Constants._NO_PRESET_TRIGGER},
{FGame.FortniteGame, Constants._NO_PRESET_TRIGGER},
{FGame.ShooterGame, Constants._NO_PRESET_TRIGGER},
{FGame.DeadByDaylight, Constants._NO_PRESET_TRIGGER},
{FGame.OakGame, Constants._NO_PRESET_TRIGGER},
{FGame.Dungeons, Constants._NO_PRESET_TRIGGER},
{FGame.WorldExplorers, Constants._NO_PRESET_TRIGGER},
{FGame.g3, Constants._NO_PRESET_TRIGGER},
{FGame.StateOfDecay2, Constants._NO_PRESET_TRIGGER},
{FGame.Prospect, Constants._NO_PRESET_TRIGGER},
{FGame.Indiana, Constants._NO_PRESET_TRIGGER},
{FGame.RogueCompany, Constants._NO_PRESET_TRIGGER},
{FGame.SwGame, Constants._NO_PRESET_TRIGGER},
{FGame.Platform, Constants._NO_PRESET_TRIGGER},
{FGame.BendGame, Constants._NO_PRESET_TRIGGER},
{FGame.TslGame, Constants._NO_PRESET_TRIGGER},
{FGame.PortalWars, Constants._NO_PRESET_TRIGGER},
{FGame.Gameface, Constants._NO_PRESET_TRIGGER},
{FGame.Athena, Constants._NO_PRESET_TRIGGER},
{FGame.MultiVersus, Constants._NO_PRESET_TRIGGER},
{FGame.Hotta, Constants._NO_PRESET_TRIGGER},
{FGame.eFootball, Constants._NO_PRESET_TRIGGER}
};
public IDictionary<FGame, string> Presets
{
get => _presets;
set => SetProperty(ref _presets, value);
}
private IDictionary<FGame, EGame> _overridedGame = new Dictionary<FGame, EGame>
{
{FGame.Unknown, EGame.GAME_UE4_LATEST},
{FGame.FortniteGame, EGame.GAME_UE5_2},
{FGame.ShooterGame, EGame.GAME_Valorant},
{FGame.DeadByDaylight, EGame.GAME_UE4_27},
{FGame.OakGame, EGame.GAME_Borderlands3},
{FGame.Dungeons, EGame.GAME_UE4_22},
{FGame.WorldExplorers, EGame.GAME_UE4_24},
{FGame.g3, EGame.GAME_UE4_22},
{FGame.StateOfDecay2, EGame.GAME_StateOfDecay2},
{FGame.Prospect, EGame.GAME_Splitgate},
{FGame.Indiana, EGame.GAME_UE4_21},
{FGame.RogueCompany, EGame.GAME_RogueCompany},
{FGame.SwGame, EGame.GAME_UE4_LATEST},
{FGame.Platform, EGame.GAME_UE4_26},
{FGame.BendGame, EGame.GAME_UE4_11},
{FGame.TslGame, EGame.GAME_PlayerUnknownsBattlegrounds},
{FGame.PortalWars, EGame.GAME_UE4_27},
{FGame.Gameface, EGame.GAME_GTATheTrilogyDefinitiveEdition},
{FGame.Athena, EGame.GAME_SeaOfThieves},
{FGame.MultiVersus, EGame.GAME_UE4_26},
{FGame.Hotta, EGame.GAME_TowerOfFantasy},
{FGame.eFootball, EGame.GAME_UE4_26}
};
public IDictionary<FGame, EGame> OverridedGame
{
get => _overridedGame;
set => SetProperty(ref _overridedGame, value);
}
private IDictionary<FGame, List<FCustomVersion>> _overridedCustomVersions = new Dictionary<FGame, List<FCustomVersion>>
{
{FGame.Unknown, null},
{FGame.FortniteGame, null},
{FGame.ShooterGame, null},
{FGame.DeadByDaylight, null},
{FGame.OakGame, null},
{FGame.Dungeons, null},
{FGame.WorldExplorers, null},
{FGame.g3, null},
{FGame.StateOfDecay2, null},
{FGame.Prospect, null},
{FGame.Indiana, null},
{FGame.RogueCompany, null},
{FGame.SwGame, null},
{FGame.Platform, null},
{FGame.BendGame, null},
{FGame.TslGame, null},
{FGame.PortalWars, null},
{FGame.Gameface, null},
{FGame.Athena, null},
{FGame.MultiVersus, null},
{FGame.Hotta, null},
{FGame.eFootball, null}
};
public IDictionary<FGame, List<FCustomVersion>> OverridedCustomVersions
{
get => _overridedCustomVersions;
set => SetProperty(ref _overridedCustomVersions, value);
}
private IDictionary<FGame, Dictionary<string, bool>> _overridedOptions = new Dictionary<FGame, Dictionary<string, bool>>
{
{FGame.Unknown, null},
{FGame.FortniteGame, null},
{FGame.ShooterGame, null},
{FGame.DeadByDaylight, null},
{FGame.OakGame, null},
{FGame.Dungeons, null},
{FGame.WorldExplorers, null},
{FGame.g3, null},
{FGame.StateOfDecay2, null},
{FGame.Prospect, null},
{FGame.Indiana, null},
{FGame.RogueCompany, null},
{FGame.SwGame, null},
{FGame.Platform, null},
{FGame.BendGame, null},
{FGame.TslGame, null},
{FGame.PortalWars, null},
{FGame.Gameface, null},
{FGame.Athena, null},
{FGame.MultiVersus, null},
{FGame.Hotta, null},
{FGame.eFootball, null}
};
private IDictionary<FGame, Dictionary<string, KeyValuePair<string, string>>> _overridedMapStructTypes = new Dictionary<FGame, Dictionary<string, KeyValuePair<string, string>>>
{
{FGame.Unknown, null},
{FGame.FortniteGame, null},
{FGame.ShooterGame, null},
{FGame.DeadByDaylight, null},
{FGame.OakGame, null},
{FGame.Dungeons, null},
{FGame.WorldExplorers, null},
{FGame.g3, null},
{FGame.StateOfDecay2, null},
{FGame.Prospect, null},
{FGame.Indiana, null},
{FGame.RogueCompany, null},
{FGame.SwGame, null},
{FGame.Platform, null},
{FGame.BendGame, null},
{FGame.TslGame, null},
{FGame.PortalWars, null},
{FGame.Gameface, null},
{FGame.Athena, null},
{FGame.MultiVersus, null},
{FGame.Hotta, null},
{FGame.eFootball, null}
};
public IDictionary<FGame, Dictionary<string, bool>> OverridedOptions
{
get => _overridedOptions;
set => SetProperty(ref _overridedOptions, value);
}
public IDictionary<FGame, Dictionary<string, KeyValuePair<string, string>>> OverridedMapStructTypes
{
get => _overridedMapStructTypes;
set => SetProperty(ref _overridedMapStructTypes, value);
}
private IDictionary<FGame, FEndpoint[]> _customEndpoints = new Dictionary<FGame, FEndpoint[]>
{
{FGame.Unknown, new FEndpoint[]{new (), new ()}},
{
FGame.FortniteGame, new []
{
new FEndpoint("https://fortnitecentral.genxgames.gg/api/v1/aes", "$.['mainKey','dynamicKeys']"),
new FEndpoint("https://fortnitecentral.genxgames.gg/api/v1/mappings", "$.[?(@.meta.compressionMethod=='Oodle')].['url','fileName']") // && @.meta.platform=='Windows'
}
},
{FGame.ShooterGame, new FEndpoint[]{new (), new ()}},
{FGame.DeadByDaylight, new FEndpoint[]{new (), new ()}},
{FGame.OakGame, new FEndpoint[]{new (), new ()}},
{FGame.Dungeons, new FEndpoint[]{new (), new ()}},
{FGame.WorldExplorers, new FEndpoint[]{new (), new ()}},
{FGame.g3, new FEndpoint[]{new (), new ()}},
{FGame.StateOfDecay2, new FEndpoint[]{new (), new ()}},
{FGame.Prospect, new FEndpoint[]{new (), new ()}},
{FGame.Indiana, new FEndpoint[]{new (), new ()}},
{FGame.RogueCompany, new FEndpoint[]{new (), new ()}},
{FGame.SwGame, new FEndpoint[]{new (), new ()}},
{FGame.Platform, new FEndpoint[]{new (), new ()}},
{FGame.BendGame, new FEndpoint[]{new (), new ()}},
{FGame.TslGame, new FEndpoint[]{new (), new ()}},
{FGame.PortalWars, new FEndpoint[]{new (), new ()}},
{FGame.Gameface, new FEndpoint[]{new (), new ()}},
{FGame.Athena, new FEndpoint[]{new (), new ()}},
{FGame.MultiVersus, new FEndpoint[]{new (), new ()}},
{FGame.Hotta, new FEndpoint[]{new (), new ()}},
{FGame.eFootball, new FEndpoint[]{new (), new ()}}
};
public IDictionary<FGame, FEndpoint[]> CustomEndpoints
{
get => _customEndpoints;
set => SetProperty(ref _customEndpoints, value);
}
private IDictionary<FGame, IList<CustomDirectory>> _customDirectories = new Dictionary<FGame, IList<CustomDirectory>>
{
{FGame.Unknown, new List<CustomDirectory>()},
{
FGame.FortniteGame, new List<CustomDirectory>
{
new("Cosmetics", "FortniteGame/Content/Athena/Items/Cosmetics/"),
new("Emotes [AUDIO]", "FortniteGame/Content/Athena/Sounds/Emotes/"),
new("Music Packs [AUDIO]", "FortniteGame/Content/Athena/Sounds/MusicPacks/"),
new("Weapons", "FortniteGame/Content/Athena/Items/Weapons/"),
new("Strings", "FortniteGame/Content/Localization/")
}
},
{
FGame.ShooterGame, new List<CustomDirectory>
{
new("Audio", "ShooterGame/Content/WwiseAudio/Media/"),
new("Characters", "ShooterGame/Content/Characters/"),
new("Gun Buddies", "ShooterGame/Content/Equippables/Buddies/"),
new("Cards and Sprays", "ShooterGame/Content/Personalization/"),
new("Shop Backgrounds", "ShooterGame/Content/UI/OutOfGame/MainMenu/Store/Shared/Textures/"),
new("Weapon Renders", "ShooterGame/Content/UI/Screens/OutOfGame/MainMenu/Collection/Assets/Large/")
}
},
{
FGame.DeadByDaylight, new List<CustomDirectory>
{
new("Audio", "DeadByDaylight/Content/WwiseAudio/Windows/"),
new("Characters", "DeadByDaylight/Content/Characters/"),
new("Icons", "DeadByDaylight/Content/UI/UMGAssets/Icons/"),
new("Strings", "DeadByDaylight/Content/Localization/")
}
},
{FGame.OakGame, new List<CustomDirectory>()},
{
FGame.Dungeons, new List<CustomDirectory>
{
new("Levels", "Dungeons/Content/data/Lovika/Levels"),
new("Friendlies", "Dungeons/Content/Actor/Characters/Friendlies"),
new("Skins", "Dungeons/Content/Actor/Characters/Player/Master/Skins"),
new("Strings", "Dungeons/Content/Localization/")
}
},
{
FGame.WorldExplorers, new List<CustomDirectory>
{
new("Loot", "WorldExplorers/Content/Loot/"),
new("Strings", "WorldExplorers/Content/Localization/")
}
},
{
FGame.g3, new List<CustomDirectory>
{
new("Cosmetics", "g3/Content/Blueprints/Cosmetics/"),
new("Strings", "g3/Content/Localization/")
}
},
{FGame.StateOfDecay2, new List<CustomDirectory>()},
{FGame.Prospect, new List<CustomDirectory>()},
{FGame.Indiana, new List<CustomDirectory>()},
{FGame.RogueCompany, new List<CustomDirectory>()},
{FGame.SwGame, new List<CustomDirectory>()},
{FGame.Platform, new List<CustomDirectory>()},
{FGame.BendGame, new List<CustomDirectory>()},
{FGame.TslGame, new List<CustomDirectory>()},
{FGame.PortalWars, new List<CustomDirectory>()},
{FGame.Gameface, new List<CustomDirectory>()},
{FGame.Athena, new List<CustomDirectory>()},
{FGame.MultiVersus, new List<CustomDirectory>()},
{FGame.Hotta, new List<CustomDirectory>()},
{FGame.eFootball, new List<CustomDirectory>()}
};
public IDictionary<FGame, IList<CustomDirectory>> CustomDirectories
{
get => _customDirectories;
set => SetProperty(ref _customDirectories, value);
}
private DateTime _lastAesReload = DateTime.Today.AddDays(-1);
public DateTime LastAesReload
{
get => _lastAesReload;
set => SetProperty(ref _lastAesReload, value);
}
private AuthResponse _lastAuthResponse = new() {AccessToken = "", ExpiresAt = DateTime.Now}; private AuthResponse _lastAuthResponse = new() {AccessToken = "", ExpiresAt = DateTime.Now};
public AuthResponse LastAuthResponse public AuthResponse LastAuthResponse
{ {
@@ -710,6 +424,13 @@ namespace FModel.Settings
set => SetProperty(ref _saveMorphTargets, value); set => SetProperty(ref _saveMorphTargets, value);
} }
private bool _saveEmbeddedMaterials = true;
public bool SaveEmbeddedMaterials
{
get => _saveEmbeddedMaterials;
set => SetProperty(ref _saveEmbeddedMaterials, value);
}
private bool _saveSkeletonAsMesh; private bool _saveSkeletonAsMesh;
public bool SaveSkeletonAsMesh public bool SaveSkeletonAsMesh
{ {

View File

@@ -0,0 +1,31 @@
using System.Collections.Generic;
using CUE4Parse.UE4.Objects.Core.Serialization;
using FModel.Framework;
namespace FModel.Settings;
public class VersioningSettings : ViewModel
{
private IList<FCustomVersion> _customVersions;
public IList<FCustomVersion> CustomVersions
{
get => _customVersions;
set => SetProperty(ref _customVersions, value);
}
private IDictionary<string, bool> _options;
public IDictionary<string, bool> Options
{
get => _options;
set => SetProperty(ref _options, value);
}
private IDictionary<string, KeyValuePair<string, string>> _mapStructTypes;
public IDictionary<string, KeyValuePair<string, string>> MapStructTypes
{
get => _mapStructTypes;
set => SetProperty(ref _mapStructTypes, value);
}
public VersioningSettings() {}
}

View File

@@ -0,0 +1,70 @@
using System.Text;
using System.Threading.Tasks;
using FModel.Framework;
using FModel.Services;
using FModel.ViewModels.ApiEndpoints.Models;
namespace FModel.ViewModels;
public class AboutViewModel : ViewModel
{
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
private string _descriptionLabel;
public string DescriptionLabel
{
get => _descriptionLabel;
set => SetProperty(ref _descriptionLabel, value);
}
private string _contributorsLabel;
public string ContributorsLabel
{
get => _contributorsLabel;
set => SetProperty(ref _contributorsLabel, value);
}
private string _donatorsLabel;
public string DonatorsLabel
{
get => _donatorsLabel;
set => SetProperty(ref _donatorsLabel, value);
}
private string _referencesLabel;
public string ReferencesLabel
{
get => _referencesLabel;
set => SetProperty(ref _referencesLabel, value);
}
public AboutViewModel()
{
}
public async Task Initialize()
{
await Task.WhenAll(
Task.Run(() =>
{
DescriptionLabel = "FModel is an archive explorer for Unreal Engine games that uses CUE4Parse as its core parsing library, providing robust support for the latest UE4 and UE5 archive formats. It aims to deliver a modern and intuitive user interface, powerful features, and a comprehensive set of tools for previewing and converting game packages, empowering YOU to understand games' inner workings with ease.";
ContributorsLabel = $"FModel owes its continued existence to the passionate individuals who have generously contributed their time and expertise. Contributions from individuals such as {string.Join(", ", "GMatrixGames", "amr", "LongerWarrior", "MinshuG", "InTheShade", "Officer")}, and countless others, both in the past and those yet to come, ensure the continuous development and success of this project. If you are benefiting from FModel and would like to support its continued improvements, please consider making a donation.";
ReferencesLabel = string.Join(", ",
"Adonis UI", "AutoUpdater.NET", "AvalonEdit", "CSCore", "CUE4Parse", "DiscordRichPresence",
"EpicManifestParser", "ImGui.NET", "K4os.Compression.LZ4", "Newtonsoft.Json", "NVorbis", "Oodle.NET",
"Ookii.Dialogs.Wpf", "OpenTK", "RestSharp", "Serilog", "SixLabors.ImageSharp", "SkiaSharp");
}),
Task.Run(() =>
{
var donators = _apiEndpointView.FModelApi.GetDonators();
if (donators == null) return;
var sb = new StringBuilder();
sb.AppendJoin<Donator>(", ", donators);
sb.Append('.');
DonatorsLabel = sb.ToString();
})
).ConfigureAwait(false);
}
}

View File

@@ -35,22 +35,7 @@ public class AesManagerViewModel : ViewModel
{ {
await _threadWorkerView.Begin(_ => await _threadWorkerView.Begin(_ =>
{ {
if (_cue4Parse.Game == FGame.Unknown && _keysFromSettings = UserSettings.Default.CurrentDir.AesKeys;
UserSettings.Default.ManualGames.TryGetValue(UserSettings.Default.GameDirectory, out var settings))
{
_keysFromSettings = settings.AesKeys;
}
else
{
UserSettings.Default.AesKeys.TryGetValue(_cue4Parse.Game, out _keysFromSettings);
}
_keysFromSettings ??= new AesResponse
{
MainKey = string.Empty,
DynamicKeys = null
};
_mainKey.Key = Helper.FixKey(_keysFromSettings.MainKey); _mainKey.Key = Helper.FixKey(_keysFromSettings.MainKey);
AesKeys = new FullyObservableCollection<FileItem>(EnumerateAesKeys()); AesKeys = new FullyObservableCollection<FileItem>(EnumerateAesKeys());
AesKeys.ItemPropertyChanged += AesKeysOnItemPropertyChanged; AesKeys.ItemPropertyChanged += AesKeysOnItemPropertyChanged;
@@ -105,9 +90,7 @@ public class AesManagerViewModel : ViewModel
public void SetAesKeys() public void SetAesKeys()
{ {
if (_cue4Parse.Game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(UserSettings.Default.GameDirectory)) UserSettings.Default.CurrentDir.AesKeys = _keysFromSettings;
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].AesKeys = _keysFromSettings;
else UserSettings.Default.AesKeys[_cue4Parse.Game] = _keysFromSettings;
// Log.Information("{@Json}", UserSettings.Default); // Log.Information("{@Json}", UserSettings.Default);
} }

View File

@@ -24,6 +24,7 @@ public class FModelApiEndpoint : AbstractApiProvider
{ {
private News _news; private News _news;
private Info _infos; private Info _infos;
private Donator[] _donators;
private Backup[] _backups; private Backup[] _backups;
private Game _game; private Game _game;
private readonly IDictionary<string, CommunityDesign> _communityDesigns = new Dictionary<string, CommunityDesign>(); private readonly IDictionary<string, CommunityDesign> _communityDesigns = new Dictionary<string, CommunityDesign>();
@@ -58,6 +59,19 @@ public class FModelApiEndpoint : AbstractApiProvider
return _infos ?? GetInfosAsync(token, updateMode).GetAwaiter().GetResult(); return _infos ?? GetInfosAsync(token, updateMode).GetAwaiter().GetResult();
} }
public async Task<Donator[]> GetDonatorsAsync()
{
var request = new FRestRequest($"https://api.fmodel.app/v1/donations/donators");
var response = await _client.ExecuteAsync<Donator[]>(request).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, response.ResponseUri?.OriginalString);
return response.Data;
}
public Donator[] GetDonators()
{
return _donators ??= GetDonatorsAsync().GetAwaiter().GetResult();
}
public async Task<Backup[]> GetBackupsAsync(CancellationToken token, string gameName) public async Task<Backup[]> GetBackupsAsync(CancellationToken token, string gameName)
{ {
var request = new FRestRequest($"https://api.fmodel.app/v1/backups/{gameName}"); var request = new FRestRequest($"https://api.fmodel.app/v1/backups/{gameName}");

View File

@@ -25,6 +25,14 @@ public class Backup
[J] public long FileSize { get; private set; } [J] public long FileSize { get; private set; }
} }
public class Donator
{
[J] public string Username { get; private set; }
[J] public int Count { get; private set; }
public override string ToString() => $"{Username}{(Count > 5 ? " ❤️" : "")}";
}
[DebuggerDisplay("{" + nameof(DisplayName) + "}")] [DebuggerDisplay("{" + nameof(DisplayName) + "}")]
public class Game public class Game
{ {

View File

@@ -48,9 +48,7 @@ public class ApplicationViewModel : ViewModel
public string InitialWindowTitle => $"FModel {UserSettings.Default.UpdateMode}"; public string InitialWindowTitle => $"FModel {UserSettings.Default.UpdateMode}";
public string GameDisplayName => CUE4Parse.Provider.GameDisplayName ?? "Unknown"; public string GameDisplayName => CUE4Parse.Provider.GameDisplayName ?? "Unknown";
public string TitleExtra => public string TitleExtra => $"({UserSettings.Default.CurrentDir.UeVersion}){(Build != EBuildKind.Release ? $" ({Build})" : "")}";
$"({(CUE4Parse.Game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(UserSettings.Default.GameDirectory, out var settings) ? settings.OverridedGame : UserSettings.Default.OverridedGame[CUE4Parse.Game])})" +
$"{(Build != EBuildKind.Release ? $" ({Build})" : "")}";
public LoadingModesViewModel LoadingModes { get; } public LoadingModesViewModel LoadingModes { get; }
public CustomDirectoriesViewModel CustomDirectories { get; } public CustomDirectoriesViewModel CustomDirectories { get; }
@@ -58,7 +56,6 @@ public class ApplicationViewModel : ViewModel
public SettingsViewModel SettingsView { get; } public SettingsViewModel SettingsView { get; }
public AesManagerViewModel AesManager { get; } public AesManagerViewModel AesManager { get; }
public AudioPlayerViewModel AudioPlayer { get; } public AudioPlayerViewModel AudioPlayer { get; }
public MapViewerViewModel MapViewer { get; }
private OodleCompressor _oodle; private OodleCompressor _oodle;
public ApplicationViewModel() public ApplicationViewModel()
@@ -73,50 +70,41 @@ public class ApplicationViewModel : ViewModel
#endif #endif
LoadingModes = new LoadingModesViewModel(); LoadingModes = new LoadingModesViewModel();
AvoidEmptyGameDirectoryAndSetEGame(false); UserSettings.Default.CurrentDir = AvoidEmptyGameDirectory(false);
if (UserSettings.Default.GameDirectory is null) if (UserSettings.Default.CurrentDir is null)
{ {
//If no game is selected, many things will break before a shutdown request is processed in the normal way. //If no game is selected, many things will break before a shutdown request is processed in the normal way.
//A hard exit is preferable to an unhandled expection in this case //A hard exit is preferable to an unhandled expection in this case
Environment.Exit(0); Environment.Exit(0);
} }
CUE4Parse = new CUE4ParseViewModel(UserSettings.Default.GameDirectory); CUE4Parse = new CUE4ParseViewModel();
CustomDirectories = new CustomDirectoriesViewModel(CUE4Parse.Game, UserSettings.Default.GameDirectory); CustomDirectories = new CustomDirectoriesViewModel();
SettingsView = new SettingsViewModel(CUE4Parse.Game); SettingsView = new SettingsViewModel();
AesManager = new AesManagerViewModel(CUE4Parse); AesManager = new AesManagerViewModel(CUE4Parse);
MapViewer = new MapViewerViewModel(CUE4Parse);
AudioPlayer = new AudioPlayerViewModel(); AudioPlayer = new AudioPlayerViewModel();
Status.SetStatus(EStatusKind.Ready); Status.SetStatus(EStatusKind.Ready);
} }
public void AvoidEmptyGameDirectoryAndSetEGame(bool bAlreadyLaunched) public DirectorySettings AvoidEmptyGameDirectory(bool bAlreadyLaunched)
{ {
var gameDirectory = UserSettings.Default.GameDirectory; var gameDirectory = UserSettings.Default.GameDirectory;
if (!string.IsNullOrEmpty(gameDirectory) && !bAlreadyLaunched) return; if (!bAlreadyLaunched && UserSettings.Default.PerDirectory.TryGetValue(gameDirectory, out var currentDir))
return currentDir;
var gameLauncherViewModel = new GameSelectorViewModel(gameDirectory); var gameLauncherViewModel = new GameSelectorViewModel(gameDirectory);
var result = new DirectorySelector(gameLauncherViewModel).ShowDialog(); var result = new DirectorySelector(gameLauncherViewModel).ShowDialog();
if (!result.HasValue || !result.Value) return; if (!result.HasValue || !result.Value) return null;
UserSettings.Default.GameDirectory = gameLauncherViewModel.SelectedDetectedGame.GameDirectory; UserSettings.Default.GameDirectory = gameLauncherViewModel.SelectedDirectory.GameDirectory;
if (!bAlreadyLaunched || gameDirectory == gameLauncherViewModel.SelectedDetectedGame.GameDirectory) return; if (!bAlreadyLaunched || UserSettings.Default.CurrentDir.Equals(gameLauncherViewModel.SelectedDirectory))
return gameLauncherViewModel.SelectedDirectory;
// UserSettings.Save(); // ??? change key then change game, key saved correctly what?
UserSettings.Default.CurrentDir = gameLauncherViewModel.SelectedDirectory;
RestartWithWarning(); RestartWithWarning();
} return null;
public async Task UpdateProvider(bool isLaunch)
{
if (!isLaunch && !AesManager.HasChange) return;
CUE4Parse.ClearProvider();
await ApplicationService.ThreadWorkerView.Begin(cancellationToken =>
{
CUE4Parse.LoadVfs(cancellationToken, AesManager.AesKeys);
CUE4Parse.Provider.LoadIniConfigs();
AesManager.SetAesKeys();
});
RaisePropertyChanged(nameof(GameDisplayName));
} }
public void RestartWithWarning() public void RestartWithWarning()
@@ -161,6 +149,20 @@ public class ApplicationViewModel : ViewModel
Application.Current.Shutdown(); Application.Current.Shutdown();
} }
public async Task UpdateProvider(bool isLaunch)
{
if (!isLaunch && !AesManager.HasChange) return;
CUE4Parse.ClearProvider();
await ApplicationService.ThreadWorkerView.Begin(cancellationToken =>
{
CUE4Parse.LoadVfs(cancellationToken, AesManager.AesKeys);
CUE4Parse.Provider.LoadIniConfigs();
AesManager.SetAesKeys();
});
RaisePropertyChanged(nameof(GameDisplayName));
}
public async Task InitVgmStream() public async Task InitVgmStream()
{ {
var vgmZipFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "vgmstream-win.zip"); var vgmZipFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "vgmstream-win.zip");

View File

@@ -551,6 +551,7 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
case "adpcm": case "adpcm":
case "opus": case "opus":
case "wem": case "wem":
case "at9":
{ {
if (TryConvert(out var wavFilePath) && !string.IsNullOrEmpty(wavFilePath)) if (TryConvert(out var wavFilePath) && !string.IsNullOrEmpty(wavFilePath))
{ {

View File

@@ -62,11 +62,11 @@ public class CUE4ParseViewModel : ViewModel
private readonly Regex _fnLive = new(@"^FortniteGame(/|\\)Content(/|\\)Paks(/|\\)", private readonly Regex _fnLive = new(@"^FortniteGame(/|\\)Content(/|\\)Paks(/|\\)",
RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
private FGame _game; private string _internalGameName;
public FGame Game public string InternalGameName
{ {
get => _game; get => _internalGameName;
set => SetProperty(ref _game, value); set => SetProperty(ref _internalGameName, value);
} }
private bool _modelIsOverwritingMaterial; private bool _modelIsOverwritingMaterial;
@@ -96,7 +96,7 @@ public class CUE4ParseViewModel : ViewModel
var scale = ImGuiController.GetDpiScale(); var scale = ImGuiController.GetDpiScale();
var htz = Snooper.GetMaxRefreshFrequency(); var htz = Snooper.GetMaxRefreshFrequency();
return _snooper = new Snooper( return _snooper = new Snooper(
new GameWindowSettings { RenderFrequency = htz, UpdateFrequency = htz }, new GameWindowSettings { UpdateFrequency = htz },
new NativeWindowSettings new NativeWindowSettings
{ {
Size = new OpenTK.Mathematics.Vector2i( Size = new OpenTK.Mathematics.Vector2i(
@@ -123,80 +123,48 @@ public class CUE4ParseViewModel : ViewModel
public TabControlViewModel TabControl { get; } public TabControlViewModel TabControl { get; }
public ConfigIni BuildInfo { get; } public ConfigIni BuildInfo { get; }
public CUE4ParseViewModel(string gameDirectory) public CUE4ParseViewModel()
{ {
var currentDir = UserSettings.Default.CurrentDir;
var gameDirectory = currentDir.GameDirectory;
var versionContainer = new VersionContainer(
game: currentDir.UeVersion, platform: currentDir.TexturePlatform,
customVersions: new FCustomVersionContainer(currentDir.Versioning.CustomVersions),
optionOverrides: currentDir.Versioning.Options,
mapStructTypesOverrides: currentDir.Versioning.MapStructTypes);
switch (gameDirectory) switch (gameDirectory)
{ {
case Constants._FN_LIVE_TRIGGER: case Constants._FN_LIVE_TRIGGER:
{ {
Game = FGame.FortniteGame; InternalGameName = "FortniteGame";
Provider = new StreamedFileProvider("FortniteLive", true, Provider = new StreamedFileProvider("FortniteLive", true, versionContainer);
new VersionContainer(
UserSettings.Default.OverridedGame[Game], UserSettings.Default.OverridedPlatform,
customVersions: new FCustomVersionContainer(UserSettings.Default.OverridedCustomVersions[Game]),
optionOverrides: UserSettings.Default.OverridedOptions[Game]));
break; break;
} }
case Constants._VAL_LIVE_TRIGGER: case Constants._VAL_LIVE_TRIGGER:
{ {
Game = FGame.ShooterGame; InternalGameName = "ShooterGame";
Provider = new StreamedFileProvider("ValorantLive", true, Provider = new StreamedFileProvider("ValorantLive", true, versionContainer);
new VersionContainer(
UserSettings.Default.OverridedGame[Game], UserSettings.Default.OverridedPlatform,
customVersions: new FCustomVersionContainer(UserSettings.Default.OverridedCustomVersions[Game]),
optionOverrides: UserSettings.Default.OverridedOptions[Game]));
break; break;
} }
default: default:
{ {
var parent = gameDirectory.SubstringBeforeLast("\\Content").SubstringAfterLast("\\"); InternalGameName = gameDirectory.SubstringBeforeLast(gameDirectory.Contains("eFootball") ? "\\pak" : "\\Content").SubstringAfterLast("\\");
if (gameDirectory.Contains("eFootball")) parent = gameDirectory.SubstringBeforeLast("\\pak").SubstringAfterLast("\\"); Provider = InternalGameName switch
Game = parent.ToEnum(FGame.Unknown);
var versions = new VersionContainer(UserSettings.Default.OverridedGame[Game], UserSettings.Default.OverridedPlatform,
customVersions: new FCustomVersionContainer(UserSettings.Default.OverridedCustomVersions[Game]),
optionOverrides: UserSettings.Default.OverridedOptions[Game],
mapStructTypesOverrides: UserSettings.Default.OverridedMapStructTypes[Game]);
switch (Game)
{ {
case FGame.StateOfDecay2: "StateOfDecay2" => new DefaultFileProvider(new DirectoryInfo(gameDirectory),
{ new DirectoryInfo[]
Provider = new DefaultFileProvider(new DirectoryInfo(gameDirectory), new List<DirectoryInfo> {
{ new(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\StateOfDecay2\\Saved\\Paks"),
new(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\StateOfDecay2\\Saved\\Paks"), new(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\StateOfDecay2\\Saved\\DisabledPaks")
new(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\StateOfDecay2\\Saved\\DisabledPaks") }, SearchOption.AllDirectories, true, versionContainer),
}, "eFootball" => new DefaultFileProvider(new DirectoryInfo(gameDirectory),
SearchOption.AllDirectories, true, versions); new DirectoryInfo[]
break; {
} new(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + "\\KONAMI\\eFootball\\ST\\Download")
case FGame.FortniteGame: }, SearchOption.AllDirectories, true, versionContainer),
Provider = new DefaultFileProvider(new DirectoryInfo(gameDirectory), new List<DirectoryInfo> _ => new DefaultFileProvider(gameDirectory, SearchOption.AllDirectories, true, versionContainer)
{ };
new(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\FortniteGame\\Saved\\PersistentDownloadDir\\InstalledBundles"),
},
SearchOption.AllDirectories, true, versions);
break;
case FGame.eFootball:
Provider = new DefaultFileProvider(new DirectoryInfo(gameDirectory), new List<DirectoryInfo>
{
new(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + "\\KONAMI\\eFootball\\ST\\Download")
},
SearchOption.AllDirectories, true, versions);
break;
case FGame.Unknown when UserSettings.Default.ManualGames.TryGetValue(gameDirectory, out var settings):
{
versions = new VersionContainer(settings.OverridedGame, UserSettings.Default.OverridedPlatform,
customVersions: new FCustomVersionContainer(settings.OverridedCustomVersions),
optionOverrides: settings.OverridedOptions,
mapStructTypesOverrides: settings.OverridedMapStructTypes);
goto default;
}
default:
{
Provider = new DefaultFileProvider(gameDirectory, SearchOption.AllDirectories, true, versions);
break;
}
}
break; break;
} }
@@ -255,7 +223,7 @@ public class CUE4ParseViewModel : ViewModel
} }
if (!_fnLive.IsMatch(fileManifest.Name)) continue; if (!_fnLive.IsMatch(fileManifest.Name)) continue;
p.Initialize(fileManifest.Name, new Stream[] { fileManifest.GetStream() } p.RegisterVfs(fileManifest.Name, new Stream[] { fileManifest.GetStream() }
, it => new FStreamArchive(it, manifest.FileManifests.First(x => x.Name.Equals(it)).GetStream(), p.Versions)); , it => new FStreamArchive(it, manifest.FileManifests.First(x => x.Name.Equals(it)).GetStream(), p.Versions));
} }
@@ -273,7 +241,7 @@ public class CUE4ParseViewModel : ViewModel
for (var i = 0; i < manifestInfo.Paks.Length; i++) for (var i = 0; i < manifestInfo.Paks.Length; i++)
{ {
p.Initialize(manifestInfo.Paks[i].GetFullName(), new[] { manifestInfo.GetPakStream(i) }); p.RegisterVfs(manifestInfo.Paks[i].GetFullName(), new[] { manifestInfo.GetPakStream(i) });
} }
FLogger.Append(ELog.Information, () => FLogger.Append(ELog.Information, () =>
@@ -283,18 +251,18 @@ public class CUE4ParseViewModel : ViewModel
} }
break; break;
case DefaultFileProvider d: case DefaultFileProvider:
d.Initialize();
var buildInfoPath = Path.Combine(UserSettings.Default.GameDirectory, "..\\..\\..\\Cloud\\BuildInfo.ini"); var buildInfoPath = Path.Combine(UserSettings.Default.GameDirectory, "..\\..\\..\\Cloud\\BuildInfo.ini");
if (File.Exists(buildInfoPath)) BuildInfo.Read(new StringReader(File.ReadAllText(buildInfoPath))); if (File.Exists(buildInfoPath)) BuildInfo.Read(new StringReader(File.ReadAllText(buildInfoPath)));
break; break;
} }
Provider.Initialize();
foreach (var vfs in Provider.UnloadedVfs) // push files from the provider to the ui foreach (var vfs in Provider.UnloadedVfs) // push files from the provider to the ui
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
if (vfs.Length <= 365 || !_hiddenArchives.IsMatch(vfs.Name)) continue; if (!_hiddenArchives.IsMatch(vfs.Name)) continue;
GameDirectory.Add(vfs); GameDirectory.Add(vfs);
} }
@@ -336,7 +304,7 @@ public class CUE4ParseViewModel : ViewModel
file.FileCount = vfs.FileCount; file.FileCount = vfs.FileCount;
} }
Game = Provider.GameName.ToEnum(Game); InternalGameName = Provider.InternalGameName;
} }
public void ClearProvider() public void ClearProvider()
@@ -354,7 +322,7 @@ public class CUE4ParseViewModel : ViewModel
{ {
// game directory dependent, we don't have the provider game name yet since we don't have aes keys // game directory dependent, we don't have the provider game name yet since we don't have aes keys
// except when this comes from the AES Manager // except when this comes from the AES Manager
if (!UserSettings.IsEndpointValid(Game, EEndpointType.Aes, out var endpoint)) if (!UserSettings.IsEndpointValid(EEndpointType.Aes, out var endpoint))
return; return;
await _threadWorkerView.Begin(cancellationToken => await _threadWorkerView.Begin(cancellationToken =>
@@ -362,7 +330,7 @@ public class CUE4ParseViewModel : ViewModel
var aes = _apiEndpointView.DynamicApi.GetAesKeys(cancellationToken, endpoint.Url, endpoint.Path); var aes = _apiEndpointView.DynamicApi.GetAesKeys(cancellationToken, endpoint.Url, endpoint.Path);
if (aes is not { IsValid: true }) return; if (aes is not { IsValid: true }) return;
UserSettings.Default.AesKeys[Game] = aes; UserSettings.Default.CurrentDir.AesKeys = aes;
}); });
} }
@@ -370,7 +338,7 @@ public class CUE4ParseViewModel : ViewModel
{ {
await _threadWorkerView.Begin(cancellationToken => await _threadWorkerView.Begin(cancellationToken =>
{ {
var info = _apiEndpointView.FModelApi.GetNews(cancellationToken, Provider.GameName); var info = _apiEndpointView.FModelApi.GetNews(cancellationToken, Provider.InternalGameName);
if (info == null) return; if (info == null) return;
FLogger.Append(ELog.None, () => FLogger.Append(ELog.None, () =>
@@ -383,9 +351,9 @@ public class CUE4ParseViewModel : ViewModel
}); });
} }
public Task InitMappings() public Task InitMappings(bool force = false)
{ {
if (!UserSettings.IsEndpointValid(Game, EEndpointType.Mapping, out var endpoint)) if (!UserSettings.IsEndpointValid(EEndpointType.Mapping, out var endpoint))
{ {
Provider.MappingsContainer = null; Provider.MappingsContainer = null;
return Task.CompletedTask; return Task.CompletedTask;
@@ -393,11 +361,10 @@ public class CUE4ParseViewModel : ViewModel
return Task.Run(() => return Task.Run(() =>
{ {
var l = ELog.Information;
if (endpoint.Overwrite && File.Exists(endpoint.FilePath)) if (endpoint.Overwrite && File.Exists(endpoint.FilePath))
{ {
Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(endpoint.FilePath); Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(endpoint.FilePath);
FLogger.Append(ELog.Information, () =>
FLogger.Text($"Mappings pulled from '{endpoint.FilePath.SubstringAfterLast("\\")}'", Constants.WHITE, true));
} }
else if (endpoint.IsValid) else if (endpoint.IsValid)
{ {
@@ -410,14 +377,12 @@ public class CUE4ParseViewModel : ViewModel
if (!mapping.IsValid) continue; if (!mapping.IsValid) continue;
var mappingPath = Path.Combine(mappingsFolder, mapping.FileName); var mappingPath = Path.Combine(mappingsFolder, mapping.FileName);
if (!File.Exists(mappingPath)) if (force || !File.Exists(mappingPath))
{ {
_apiEndpointView.DownloadFile(mapping.Url, mappingPath); _apiEndpointView.DownloadFile(mapping.Url, mappingPath);
} }
Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(mappingPath); Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(mappingPath);
FLogger.Append(ELog.Information, () =>
FLogger.Text($"Mappings pulled from '{mapping.FileName}'", Constants.WHITE, true));
break; break;
} }
} }
@@ -429,10 +394,15 @@ public class CUE4ParseViewModel : ViewModel
var latestUsmapInfo = latestUsmaps.OrderBy(f => f.LastWriteTime).Last(); var latestUsmapInfo = latestUsmaps.OrderBy(f => f.LastWriteTime).Last();
Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(latestUsmapInfo.FullName); Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(latestUsmapInfo.FullName);
FLogger.Append(ELog.Warning, () => l = ELog.Warning;
FLogger.Text($"Mappings pulled from '{latestUsmapInfo.Name}'", Constants.WHITE, true));
} }
} }
if (Provider.MappingsContainer is FileUsmapTypeMappingsProvider m)
{
Log.Information($"Mappings pulled from '{m.FileName}'");
FLogger.Append(l, () => FLogger.Text($"Mappings pulled from '{m.FileName}'", Constants.WHITE, true));
}
}); });
} }
@@ -444,13 +414,24 @@ public class CUE4ParseViewModel : ViewModel
return Task.Run(() => return Task.Run(() =>
{ {
var inst = new List<InstructionToken>(); foreach (var token in Provider.DefaultEngine.Sections.FirstOrDefault(s => s.Name == "ConsoleVariables")?.Tokens ?? new List<IniToken>())
Provider.DefaultEngine.FindPropertyInstructions("ConsoleVariables", "a.StripAdditiveRefPose", inst);
if (inst.Count > 0 && inst[0].Value.Equals("1"))
{ {
FLogger.Append(ELog.Warning, () => if (token is not InstructionToken it) continue;
FLogger.Text("Additive animations have their reference pose stripped, which will lead to inaccurate preview and export", Constants.WHITE, true)); var boolValue = it.Value.Equals("1");
switch (it.Key)
{
case "a.StripAdditiveRefPose" when boolValue:
FLogger.Append(ELog.Warning, () =>
FLogger.Text("Additive animations have their reference pose stripped, which will lead to inaccurate preview and export", Constants.WHITE, true));
continue;
case "r.StaticMesh.KeepMobileMinLODSettingOnDesktop":
case "r.SkeletalMesh.KeepMobileMinLODSettingOnDesktop":
Provider.Versions[it.Key[2..]] = boolValue;
continue;
}
} }
_cvaVerifDone = true; _cvaVerifDone = true;
}); });
} }
@@ -472,7 +453,7 @@ public class CUE4ParseViewModel : ViewModel
public Task VerifyContentBuildManifest() public Task VerifyContentBuildManifest()
{ {
if (Provider is not DefaultFileProvider || !Provider.GameName.Equals("FortniteGame", StringComparison.OrdinalIgnoreCase)) if (Provider is not DefaultFileProvider || !Provider.InternalGameName.Equals("FortniteGame", StringComparison.OrdinalIgnoreCase))
return Task.CompletedTask; return Task.CompletedTask;
var persistentDownloadDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "FortniteGame/Saved/PersistentDownloadDir"); var persistentDownloadDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "FortniteGame/Saved/PersistentDownloadDir");
@@ -564,7 +545,7 @@ public class CUE4ParseViewModel : ViewModel
} }
private Task LoadHotfixedLocalizedResources() private Task LoadHotfixedLocalizedResources()
{ {
if (!Provider.GameName.Equals("fortnitegame", StringComparison.OrdinalIgnoreCase) || HotfixedResourcesDone) return Task.CompletedTask; if (!Provider.InternalGameName.Equals("fortnitegame", StringComparison.OrdinalIgnoreCase) || HotfixedResourcesDone) return Task.CompletedTask;
return Task.Run(() => return Task.Run(() =>
{ {
var hotfixes = ApplicationService.ApiEndpointView.CentralApi.GetHotfixes(default, Provider.GetLanguageCode(UserSettings.Default.AssetLanguage)); var hotfixes = ApplicationService.ApiEndpointView.CentralApi.GetHotfixes(default, Provider.GetLanguageCode(UserSettings.Default.AssetLanguage));
@@ -591,7 +572,7 @@ public class CUE4ParseViewModel : ViewModel
if (_virtualPathCount > 0) return Task.CompletedTask; if (_virtualPathCount > 0) return Task.CompletedTask;
return Task.Run(() => return Task.Run(() =>
{ {
_virtualPathCount = Provider.LoadVirtualPaths(UserSettings.Default.OverridedGame[Game].GetVersion()); _virtualPathCount = Provider.LoadVirtualPaths(UserSettings.Default.CurrentDir.UeVersion.GetVersion());
if (_virtualPathCount > 0) if (_virtualPathCount > 0)
{ {
FLogger.Append(ELog.Information, () => FLogger.Append(ELog.Information, () =>
@@ -609,7 +590,7 @@ public class CUE4ParseViewModel : ViewModel
{ {
foreach (var asset in assetItems) foreach (var asset in assetItems)
{ {
Thread.Sleep(10); Thread.Yield();
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
Extract(cancellationToken, asset.FullPath, TabControl.HasNoTabs); Extract(cancellationToken, asset.FullPath, TabControl.HasNoTabs);
} }
@@ -619,7 +600,7 @@ public class CUE4ParseViewModel : ViewModel
{ {
foreach (var asset in folder.AssetsList.Assets) foreach (var asset in folder.AssetsList.Assets)
{ {
Thread.Sleep(10); Thread.Yield();
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
try try
{ {
@@ -759,7 +740,7 @@ public class CUE4ParseViewModel : ViewModel
break; break;
} }
case "bin" when fileName.Contains("AssetRegistry"): case "bin" when fileName.Contains("AssetRegistry", StringComparison.OrdinalIgnoreCase):
{ {
if (Provider.TryCreateReader(fullPath, out var archive)) if (Provider.TryCreateReader(fullPath, out var archive))
{ {
@@ -892,7 +873,7 @@ public class CUE4ParseViewModel : ViewModel
TabControl.SelectedTab.SetDocumentText(verseDigest.ReadableCode, false, false); TabControl.SelectedTab.SetDocumentText(verseDigest.ReadableCode, false, false);
return true; return true;
} }
case UTexture2D { IsVirtual: false } texture when isNone: case UTexture texture when isNone || saveTextures:
{ {
TabControl.SelectedTab.AddImage(texture, saveTextures, updateUi); TabControl.SelectedTab.AddImage(texture, saveTextures, updateUi);
return false; return false;
@@ -902,8 +883,12 @@ public class CUE4ParseViewModel : ViewModel
{ {
var shouldDecompress = UserSettings.Default.CompressedAudioMode == ECompressedAudio.PlayDecompressed; var shouldDecompress = UserSettings.Default.CompressedAudioMode == ECompressedAudio.PlayDecompressed;
export.Decode(shouldDecompress, out var audioFormat, out var data); export.Decode(shouldDecompress, out var audioFormat, out var data);
if (data == null || string.IsNullOrEmpty(audioFormat) || export.Owner == null) var hasAf = !string.IsNullOrEmpty(audioFormat);
if (data == null || !hasAf || export.Owner == null)
{
if (hasAf) FLogger.Append(ELog.Warning, () => FLogger.Text($"Unsupported audio format '{audioFormat}'", Constants.WHITE, true));
return false; return false;
}
SaveAndPlaySound(Path.Combine(TabControl.SelectedTab.Directory, TabControl.SelectedTab.Header.SubstringBeforeLast('.')).Replace('\\', '/'), audioFormat, data); SaveAndPlaySound(Path.Combine(TabControl.SelectedTab.Directory, TabControl.SelectedTab.Header.SubstringBeforeLast('.')).Replace('\\', '/'), audioFormat, data);
return false; return false;
@@ -911,8 +896,10 @@ public class CUE4ParseViewModel : ViewModel
case UWorld when isNone && UserSettings.Default.PreviewWorlds: case UWorld when isNone && UserSettings.Default.PreviewWorlds:
case UStaticMesh when isNone && UserSettings.Default.PreviewStaticMeshes: case UStaticMesh when isNone && UserSettings.Default.PreviewStaticMeshes:
case USkeletalMesh when isNone && UserSettings.Default.PreviewSkeletalMeshes: case USkeletalMesh when isNone && UserSettings.Default.PreviewSkeletalMeshes:
case USkeleton when isNone && UserSettings.Default.SaveSkeletonAsMesh:
case UMaterialInstance when isNone && UserSettings.Default.PreviewMaterials && !ModelIsOverwritingMaterial && case UMaterialInstance when isNone && UserSettings.Default.PreviewMaterials && !ModelIsOverwritingMaterial &&
!(Game == FGame.FortniteGame && export.Owner != null && (export.Owner.Name.EndsWith($"/MI_OfferImages/{export.Name}", StringComparison.OrdinalIgnoreCase) || !(Provider.InternalGameName.Equals("FortniteGame", StringComparison.OrdinalIgnoreCase) && export.Owner != null &&
(export.Owner.Name.EndsWith($"/MI_OfferImages/{export.Name}", StringComparison.OrdinalIgnoreCase) ||
export.Owner.Name.EndsWith($"/RenderSwitch_Materials/{export.Name}", StringComparison.OrdinalIgnoreCase) || export.Owner.Name.EndsWith($"/RenderSwitch_Materials/{export.Name}", StringComparison.OrdinalIgnoreCase) ||
export.Owner.Name.EndsWith($"/MI_BPTile/{export.Name}", StringComparison.OrdinalIgnoreCase))): export.Owner.Name.EndsWith($"/MI_BPTile/{export.Name}", StringComparison.OrdinalIgnoreCase))):
{ {
@@ -997,8 +984,9 @@ public class CUE4ParseViewModel : ViewModel
MaterialFormat = UserSettings.Default.MaterialExportFormat, MaterialFormat = UserSettings.Default.MaterialExportFormat,
TextureFormat = UserSettings.Default.TextureExportFormat, TextureFormat = UserSettings.Default.TextureExportFormat,
SocketFormat = UserSettings.Default.SocketExportFormat, SocketFormat = UserSettings.Default.SocketExportFormat,
Platform = UserSettings.Default.OverridedPlatform, Platform = UserSettings.Default.CurrentDir.TexturePlatform,
ExportMorphTargets = UserSettings.Default.SaveMorphTargets ExportMorphTargets = UserSettings.Default.SaveMorphTargets,
ExportMaterials = UserSettings.Default.SaveEmbeddedMaterials
}; };
var toSave = new Exporter(export, exportOptions); var toSave = new Exporter(export, exportOptions);

View File

@@ -1,5 +1,6 @@
using AdonisUI.Controls; using AdonisUI.Controls;
using FModel.Framework; using FModel.Framework;
using FModel.Settings;
using FModel.Views; using FModel.Views;
namespace FModel.ViewModels.Commands; namespace FModel.ViewModels.Commands;
@@ -31,4 +32,4 @@ public class AddEditDirectoryCommand : ViewModelCommand<CustomDirectoriesViewMod
contextViewModel.Add(customDir); contextViewModel.Add(customDir);
}); });
} }
} }

View File

@@ -1,4 +1,5 @@
using FModel.Framework; using FModel.Framework;
using FModel.Settings;
namespace FModel.ViewModels.Commands; namespace FModel.ViewModels.Commands;
@@ -17,4 +18,4 @@ public class DeleteDirectoryCommand : ViewModelCommand<CustomDirectoriesViewMode
contextViewModel.Delete(index); contextViewModel.Delete(index);
} }
} }

View File

@@ -23,13 +23,13 @@ public class MenuCommand : ViewModelCommand<ApplicationViewModel>
switch (parameter) switch (parameter)
{ {
case "Directory_Selector": case "Directory_Selector":
contextViewModel.AvoidEmptyGameDirectoryAndSetEGame(true); contextViewModel.AvoidEmptyGameDirectory(true);
break; break;
case "Directory_AES": case "Directory_AES":
Helper.OpenWindow<AdonisWindow>("AES Manager", () => new AesManager().Show()); Helper.OpenWindow<AdonisWindow>("AES Manager", () => new AesManager().Show());
break; break;
case "Directory_Backup": case "Directory_Backup":
Helper.OpenWindow<AdonisWindow>("Backup Manager", () => new BackupManager(contextViewModel.CUE4Parse.Provider.GameName).Show()); Helper.OpenWindow<AdonisWindow>("Backup Manager", () => new BackupManager(contextViewModel.CUE4Parse.Provider.InternalGameName).Show());
break; break;
case "Directory_ArchivesInfo": case "Directory_ArchivesInfo":
contextViewModel.CUE4Parse.TabControl.AddTab("Archives Info"); contextViewModel.CUE4Parse.TabControl.AddTab("Archives Info");
@@ -42,19 +42,12 @@ public class MenuCommand : ViewModelCommand<ApplicationViewModel>
case "Views_AudioPlayer": case "Views_AudioPlayer":
Helper.OpenWindow<AdonisWindow>("Audio Player", () => new AudioPlayer().Show()); Helper.OpenWindow<AdonisWindow>("Audio Player", () => new AudioPlayer().Show());
break; break;
case "Views_MapViewer":
Helper.OpenWindow<AdonisWindow>("Map Viewer", () => new MapViewer().Show());
break;
case "Views_ImageMerger": case "Views_ImageMerger":
Helper.OpenWindow<AdonisWindow>("Image Merger", () => new ImageMerger().Show()); Helper.OpenWindow<AdonisWindow>("Image Merger", () => new ImageMerger().Show());
break; break;
case "Settings": case "Settings":
Helper.OpenWindow<AdonisWindow>("Settings", () => new SettingsView().Show()); Helper.OpenWindow<AdonisWindow>("Settings", () => new SettingsView().Show());
break; break;
case "ModelSettings":
UserSettings.Default.LastOpenedSettingTab = contextViewModel.CUE4Parse.Game == FGame.FortniteGame ? 2 : 1;
Helper.OpenWindow<AdonisWindow>("Settings", () => new SettingsView().Show());
break;
case "Help_About": case "Help_About":
Helper.OpenWindow<AdonisWindow>("About", () => new About().Show()); Helper.OpenWindow<AdonisWindow>("About", () => new About().Show());
break; break;
@@ -111,7 +104,7 @@ public class MenuCommand : ViewModelCommand<ApplicationViewModel>
if (!expand && folder.IsExpanded) if (!expand && folder.IsExpanded)
{ {
folder.IsExpanded = false; folder.IsExpanded = false;
Thread.Sleep(10); Thread.Yield();
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
} }
@@ -129,7 +122,7 @@ public class MenuCommand : ViewModelCommand<ApplicationViewModel>
for (var node = nodes.Last; node != null; node = node.Previous) for (var node = nodes.Last; node != null; node = node.Previous)
{ {
node.Value.IsExpanded = true; node.Value.IsExpanded = true;
Thread.Sleep(10); Thread.Yield();
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
} }
} }

View File

@@ -29,7 +29,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
case "Assets_Extract_New_Tab": case "Assets_Extract_New_Tab":
foreach (var asset in assetItems) foreach (var asset in assetItems)
{ {
Thread.Sleep(10); Thread.Yield();
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, true); contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, true);
} }
@@ -37,7 +37,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
case "Assets_Export_Data": case "Assets_Export_Data":
foreach (var asset in assetItems) foreach (var asset in assetItems)
{ {
Thread.Sleep(10); Thread.Yield();
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.ExportData(asset.FullPath); contextViewModel.CUE4Parse.ExportData(asset.FullPath);
} }
@@ -45,7 +45,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
case "Assets_Save_Properties": case "Assets_Save_Properties":
foreach (var asset in assetItems) foreach (var asset in assetItems)
{ {
Thread.Sleep(10); Thread.Yield();
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Properties); contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Properties);
} }
@@ -53,7 +53,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
case "Assets_Save_Textures": case "Assets_Save_Textures":
foreach (var asset in assetItems) foreach (var asset in assetItems)
{ {
Thread.Sleep(10); Thread.Yield();
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Textures); contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Textures);
} }
@@ -61,7 +61,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
case "Assets_Save_Models": case "Assets_Save_Models":
foreach (var asset in assetItems) foreach (var asset in assetItems)
{ {
Thread.Sleep(10); Thread.Yield();
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Meshes | EBulkType.Auto); contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Meshes | EBulkType.Auto);
} }
@@ -69,7 +69,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
case "Assets_Save_Animations": case "Assets_Save_Animations":
foreach (var asset in assetItems) foreach (var asset in assetItems)
{ {
Thread.Sleep(10); Thread.Yield();
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Animations | EBulkType.Auto); contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Animations | EBulkType.Auto);
} }

View File

@@ -11,37 +11,6 @@ using FModel.ViewModels.Commands;
namespace FModel.ViewModels; namespace FModel.ViewModels;
public class CustomDirectory : ViewModel
{
private string _header;
public string Header
{
get => _header;
set => SetProperty(ref _header, value);
}
private string _directoryPath;
public string DirectoryPath
{
get => _directoryPath;
set => SetProperty(ref _directoryPath, value);
}
public CustomDirectory()
{
Header = string.Empty;
DirectoryPath = string.Empty;
}
public CustomDirectory(string header, string path)
{
Header = header;
DirectoryPath = path;
}
public override string ToString() => Header;
}
public class CustomDirectoriesViewModel : ViewModel public class CustomDirectoriesViewModel : ViewModel
{ {
private GoToCommand _goToCommand; private GoToCommand _goToCommand;
@@ -54,13 +23,8 @@ public class CustomDirectoriesViewModel : ViewModel
private readonly ObservableCollection<Control> _directories; private readonly ObservableCollection<Control> _directories;
public ReadOnlyObservableCollection<Control> Directories { get; } public ReadOnlyObservableCollection<Control> Directories { get; }
private readonly FGame _game; public CustomDirectoriesViewModel()
private readonly string _gameDirectoryAtLaunch;
public CustomDirectoriesViewModel(FGame game, string directory)
{ {
_game = game;
_gameDirectoryAtLaunch = directory;
_directories = new ObservableCollection<Control>(EnumerateDirectories()); _directories = new ObservableCollection<Control>(EnumerateDirectories());
Directories = new ReadOnlyObservableCollection<Control>(_directories); Directories = new ReadOnlyObservableCollection<Control>(_directories);
} }
@@ -74,6 +38,7 @@ public class CustomDirectoriesViewModel : ViewModel
public void Add(CustomDirectory dir) public void Add(CustomDirectory dir)
{ {
_directories.Add(new MenuItem { Header = dir.Header, Tag = dir.DirectoryPath, ItemsSource = EnumerateCommands(dir) }); _directories.Add(new MenuItem { Header = dir.Header, Tag = dir.DirectoryPath, ItemsSource = EnumerateCommands(dir) });
Save();
} }
public void Edit(int index, CustomDirectory newDir) public void Edit(int index, CustomDirectory newDir)
@@ -82,25 +47,25 @@ public class CustomDirectoriesViewModel : ViewModel
dir.Header = newDir.Header; dir.Header = newDir.Header;
dir.Tag = newDir.DirectoryPath; dir.Tag = newDir.DirectoryPath;
Save();
} }
public void Delete(int index) public void Delete(int index)
{ {
_directories.RemoveAt(index); _directories.RemoveAt(index);
Save();
} }
public void Save() public void Save()
{ {
var cd = new List<CustomDirectory>(); var directories = new List<CustomDirectory>();
for (var i = 2; i < _directories.Count; i++) for (var i = 2; i < _directories.Count; i++)
{ {
if (_directories[i] is not MenuItem m) continue; if (_directories[i] is not MenuItem m) continue;
cd.Add(new CustomDirectory(m.Header.ToString(), m.Tag.ToString())); directories.Add(new CustomDirectory(m.Header.ToString(), m.Tag.ToString()));
} }
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(_gameDirectoryAtLaunch)) UserSettings.Default.CurrentDir.Directories = directories;
UserSettings.Default.ManualGames[_gameDirectoryAtLaunch].CustomDirectories = cd;
else UserSettings.Default.CustomDirectories[_game] = cd;
} }
private IEnumerable<Control> EnumerateDirectories() private IEnumerable<Control> EnumerateDirectories()
@@ -115,12 +80,7 @@ public class CustomDirectoriesViewModel : ViewModel
}; };
yield return new Separator(); yield return new Separator();
IList<CustomDirectory> cd; foreach (var setting in UserSettings.Default.CurrentDir.Directories)
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(_gameDirectoryAtLaunch, out var settings))
cd = settings.CustomDirectories;
else cd = UserSettings.Default.CustomDirectories[_game];
foreach (var setting in cd)
{ {
if (setting.DirectoryPath.EndsWith('/')) if (setting.DirectoryPath.EndsWith('/'))
setting.DirectoryPath = setting.DirectoryPath[..^1]; setting.DirectoryPath = setting.DirectoryPath[..^1];
@@ -167,4 +127,4 @@ public class CustomDirectoriesViewModel : ViewModel
CommandParameter = dir CommandParameter = dir
}; };
} }
} }

View File

@@ -22,114 +22,97 @@ public class GameSelectorViewModel : ViewModel
{ {
public string GameName { get; set; } public string GameName { get; set; }
public string GameDirectory { get; set; } public string GameDirectory { get; set; }
public EGame OverridedGame { get; set; }
public bool IsManual { get; set; } public bool IsManual { get; set; }
// the followings are only used when game is manually added // the followings are only used when game is manually added
public AesResponse AesKeys { get; set; } public AesResponse AesKeys { get; set; }
public EGame OverridedGame { get; set; }
public List<FCustomVersion> OverridedCustomVersions { get; set; } public List<FCustomVersion> OverridedCustomVersions { get; set; }
public Dictionary<string, bool> OverridedOptions { get; set; } public Dictionary<string, bool> OverridedOptions { get; set; }
public Dictionary<string, KeyValuePair<string, string>> OverridedMapStructTypes { get; set; } public Dictionary<string, KeyValuePair<string, string>> OverridedMapStructTypes { get; set; }
public IList<CustomDirectory> CustomDirectories { get; set; } public IList<CustomDirectory> CustomDirectories { get; set; }
} }
private DetectedGame _selectedDetectedGame; private DirectorySettings _selectedDirectory;
public DetectedGame SelectedDetectedGame public DirectorySettings SelectedDirectory
{ {
get => _selectedDetectedGame; get => _selectedDirectory;
set => SetProperty(ref _selectedDetectedGame, value); set => SetProperty(ref _selectedDirectory, value);
} }
private readonly ObservableCollection<DetectedGame> _autoDetectedGames; private readonly ObservableCollection<DirectorySettings> _detectedDirectories;
public ReadOnlyObservableCollection<DetectedGame> AutoDetectedGames { get; } public ReadOnlyObservableCollection<DirectorySettings> DetectedDirectories { get; }
public ReadOnlyObservableCollection<EGame> UeVersions { get; }
public GameSelectorViewModel(string gameDirectory) public GameSelectorViewModel(string gameDirectory)
{ {
_autoDetectedGames = new ObservableCollection<DetectedGame>(EnumerateDetectedGames().Where(x => x != null)); _detectedDirectories = new ObservableCollection<DirectorySettings>(EnumerateDetectedGames().Where(x => x != null));
foreach (var game in UserSettings.Default.ManualGames.Values) foreach (var dir in UserSettings.Default.PerDirectory.Values.Where(x => x.IsManual))
{ {
_autoDetectedGames.Add(game); _detectedDirectories.Add((DirectorySettings) dir.Clone());
} }
AutoDetectedGames = new ReadOnlyObservableCollection<DetectedGame>(_autoDetectedGames); DetectedDirectories = new ReadOnlyObservableCollection<DirectorySettings>(_detectedDirectories);
if (AutoDetectedGames.FirstOrDefault(x => x.GameDirectory == gameDirectory) is { } detectedGame) if (DetectedDirectories.FirstOrDefault(x => x.GameDirectory == gameDirectory) is { } detectedGame)
SelectedDetectedGame = detectedGame; SelectedDirectory = detectedGame;
else if (!string.IsNullOrEmpty(gameDirectory)) else if (!string.IsNullOrEmpty(gameDirectory))
AddUnknownGame(gameDirectory); AddUndetectedDir(gameDirectory);
else else
SelectedDetectedGame = AutoDetectedGames.FirstOrDefault(); SelectedDirectory = DetectedDirectories.FirstOrDefault();
UeVersions = new ReadOnlyObservableCollection<EGame>(new ObservableCollection<EGame>(EnumerateUeGames()));
} }
/// <summary> public void AddUndetectedDir(string gameDirectory) => AddUndetectedDir(gameDirectory.SubstringAfterLast('\\'), gameDirectory);
/// dedicated to manual games public void AddUndetectedDir(string gameName, string gameDirectory)
/// </summary>
public void AddUnknownGame(string gameName, string gameDirectory)
{ {
var game = new DetectedGame var setting = DirectorySettings.Default(gameName, gameDirectory, true);
{ UserSettings.Default.PerDirectory[gameDirectory] = setting;
GameName = gameName, _detectedDirectories.Add(setting);
GameDirectory = gameDirectory, SelectedDirectory = DetectedDirectories.Last();
IsManual = true,
AesKeys = null,
OverridedGame = EGame.GAME_UE4_LATEST,
OverridedCustomVersions = null,
OverridedOptions = null,
OverridedMapStructTypes = null,
CustomDirectories = new List<CustomDirectory>()
};
UserSettings.Default.ManualGames[gameDirectory] = game;
_autoDetectedGames.Add(game);
SelectedDetectedGame = AutoDetectedGames.Last();
}
public void AddUnknownGame(string gameDirectory)
{
_autoDetectedGames.Add(new DetectedGame { GameName = gameDirectory.SubstringAfterLast('\\'), GameDirectory = gameDirectory });
SelectedDetectedGame = AutoDetectedGames.Last();
} }
public void DeleteSelectedGame() public void DeleteSelectedGame()
{ {
UserSettings.Default.ManualGames.Remove(SelectedDetectedGame.GameDirectory); // should not be a problem UserSettings.Default.PerDirectory.Remove(SelectedDirectory.GameDirectory); // should not be a problem
_autoDetectedGames.Remove(SelectedDetectedGame); _detectedDirectories.Remove(SelectedDirectory);
SelectedDetectedGame = AutoDetectedGames.Last(); SelectedDirectory = DetectedDirectories.Last();
} }
private IEnumerable<DetectedGame> EnumerateDetectedGames() private IEnumerable<EGame> EnumerateUeGames()
=> Enum.GetValues<EGame>()
.GroupBy(value => (int)value)
.Select(group => group.First())
.OrderBy(value => (int)value == ((int)value & ~0xF));
private IEnumerable<DirectorySettings> EnumerateDetectedGames()
{ {
yield return GetUnrealEngineGame("Fortnite", "\\FortniteGame\\Content\\Paks"); yield return GetUnrealEngineGame("Fortnite", "\\FortniteGame\\Content\\Paks", EGame.GAME_UE5_3);
yield return new DetectedGame { GameName = "Fortnite [LIVE]", GameDirectory = Constants._FN_LIVE_TRIGGER }; yield return DirectorySettings.Default("Fortnite [LIVE]", Constants._FN_LIVE_TRIGGER, ue: EGame.GAME_UE5_3);
yield return GetUnrealEngineGame("Pewee", "\\RogueCompany\\Content\\Paks"); yield return GetUnrealEngineGame("Pewee", "\\RogueCompany\\Content\\Paks", EGame.GAME_RogueCompany);
yield return GetUnrealEngineGame("Rosemallow", "\\Indiana\\Content\\Paks"); yield return GetUnrealEngineGame("Rosemallow", "\\Indiana\\Content\\Paks", EGame.GAME_UE4_21);
yield return GetUnrealEngineGame("Catnip", "\\OakGame\\Content\\Paks"); yield return GetUnrealEngineGame("Catnip", "\\OakGame\\Content\\Paks", EGame.GAME_Borderlands3);
yield return GetUnrealEngineGame("AzaleaAlpha", "\\Prospect\\Content\\Paks"); yield return GetUnrealEngineGame("AzaleaAlpha", "\\Prospect\\Content\\Paks", EGame.GAME_UE4_27);
yield return GetUnrealEngineGame("WorldExplorersLive", "\\WorldExplorers\\Content\\Paks"); yield return GetUnrealEngineGame("shoebill", "\\SwGame\\Content\\Paks", EGame.GAME_StarWarsJediFallenOrder);
yield return GetUnrealEngineGame("Newt", "\\g3\\Content\\Paks"); yield return GetUnrealEngineGame("Snoek", "\\StateOfDecay2\\Content\\Paks", EGame.GAME_StateOfDecay2);
yield return GetUnrealEngineGame("shoebill", "\\SwGame\\Content\\Paks"); yield return GetUnrealEngineGame("711c5e95dc094ca58e5f16bd48e751d6", "\\MultiVersus\\Content\\Paks", EGame.GAME_UE4_26);
yield return GetUnrealEngineGame("Snoek", "\\StateOfDecay2\\Content\\Paks"); yield return GetUnrealEngineGame("9361c8c6d2f34b42b5f2f61093eedf48", "\\TslGame\\Content\\Paks", EGame.GAME_PlayerUnknownsBattlegrounds);
yield return GetUnrealEngineGame("a99769d95d8f400baad1f67ab5dfe508", "\\Core\\Platform\\Content\\Paks"); yield return GetRiotGame("VALORANT", "ShooterGame\\Content\\Paks", EGame.GAME_Valorant);
yield return GetUnrealEngineGame("Nebula", "\\BendGame\\Content"); yield return DirectorySettings.Default("VALORANT [LIVE]", Constants._VAL_LIVE_TRIGGER, ue: EGame.GAME_Valorant);
yield return GetUnrealEngineGame("711c5e95dc094ca58e5f16bd48e751d6", "\\MultiVersus\\Content\\Paks"); yield return GetSteamGame(381210, "\\DeadByDaylight\\Content\\Paks", EGame.GAME_UE4_27); // Dead By Daylight
yield return GetUnrealEngineGame("9361c8c6d2f34b42b5f2f61093eedf48", "\\TslGame\\Content\\Paks"); yield return GetSteamGame(578080, "\\TslGame\\Content\\Paks", EGame.GAME_PlayerUnknownsBattlegrounds); // PUBG
yield return GetRiotGame("VALORANT", "ShooterGame\\Content\\Paks"); yield return GetSteamGame(1172380, "\\SwGame\\Content\\Paks", EGame.GAME_StarWarsJediFallenOrder); // STAR WARS Jedi: Fallen Order™
yield return new DetectedGame { GameName = "Valorant [LIVE]", GameDirectory = Constants._VAL_LIVE_TRIGGER }; yield return GetSteamGame(677620, "\\PortalWars\\Content\\Paks", EGame.GAME_Splitgate); // Splitgate
yield return GetMojangGame("MinecraftDungeons", "\\dungeons\\dungeons\\Dungeons\\Content\\Paks"); yield return GetSteamGame(1172620, "\\Athena\\Content\\Paks", EGame.GAME_SeaOfThieves); // Sea of Thieves
yield return GetSteamGame(381210, "\\DeadByDaylight\\Content\\Paks"); // Dead By Daylight yield return GetSteamGame(1665460, "\\pak", EGame.GAME_UE4_26); // eFootball 2023
yield return GetSteamGame(578080, "\\TslGame\\Content\\Paks"); // PUBG yield return GetRockstarGamesGame("GTA III - Definitive Edition", "\\Gameface\\Content\\Paks", EGame.GAME_GTATheTrilogyDefinitiveEdition);
yield return GetSteamGame(1172380, "\\SwGame\\Content\\Paks"); // STAR WARS Jedi: Fallen Order™ yield return GetRockstarGamesGame("GTA San Andreas - Definitive Edition", "\\Gameface\\Content\\Paks", EGame.GAME_GTATheTrilogyDefinitiveEdition);
yield return GetSteamGame(677620, "\\PortalWars\\Content\\Paks"); // Splitgate yield return GetRockstarGamesGame("GTA Vice City - Definitive Edition", "\\Gameface\\Content\\Paks", EGame.GAME_GTATheTrilogyDefinitiveEdition);
yield return GetSteamGame(1172620, "\\Athena\\Content\\Paks"); // Sea of Thieves yield return GetLevelInfiniteGame("tof_launcher", "\\Hotta\\Content\\Paks", EGame.GAME_TowerOfFantasy);
yield return GetSteamGame(1665460, "\\pak"); // eFootball 2023
yield return GetRockstarGamesGame("GTA III - Definitive Edition", "\\Gameface\\Content\\Paks");
yield return GetRockstarGamesGame("GTA San Andreas - Definitive Edition", "\\Gameface\\Content\\Paks");
yield return GetRockstarGamesGame("GTA Vice City - Definitive Edition", "\\Gameface\\Content\\Paks");
yield return GetLevelInfiniteGame("tof_launcher", "\\Hotta\\Content\\Paks");
} }
private LauncherInstalled _launcherInstalled; private LauncherInstalled _launcherInstalled;
private DetectedGame GetUnrealEngineGame(string gameName, string pakDirectory) private DirectorySettings GetUnrealEngineGame(string gameName, string pakDirectory, EGame ueVersion)
{ {
_launcherInstalled ??= GetDriveLauncherInstalls<LauncherInstalled>("ProgramData\\Epic\\UnrealEngineLauncher\\LauncherInstalled.dat"); _launcherInstalled ??= GetDriveLauncherInstalls<LauncherInstalled>("ProgramData\\Epic\\UnrealEngineLauncher\\LauncherInstalled.dat");
if (_launcherInstalled?.InstallationList != null) if (_launcherInstalled?.InstallationList != null)
@@ -140,7 +123,7 @@ public class GameSelectorViewModel : ViewModel
if (installationList.AppName.Equals(gameName, StringComparison.OrdinalIgnoreCase) && Directory.Exists(gameDir)) if (installationList.AppName.Equals(gameName, StringComparison.OrdinalIgnoreCase) && Directory.Exists(gameDir))
{ {
Log.Debug("Found {GameName} in LauncherInstalled.dat", gameName); Log.Debug("Found {GameName} in LauncherInstalled.dat", gameName);
return new DetectedGame { GameName = installationList.AppName, GameDirectory = gameDir }; return DirectorySettings.Default(installationList.AppName, gameDir, ue: ueVersion);
} }
} }
} }
@@ -149,7 +132,7 @@ public class GameSelectorViewModel : ViewModel
} }
private RiotClientInstalls _riotClientInstalls; private RiotClientInstalls _riotClientInstalls;
private DetectedGame GetRiotGame(string gameName, string pakDirectory) private DirectorySettings GetRiotGame(string gameName, string pakDirectory, EGame ueVersion)
{ {
_riotClientInstalls ??= GetDriveLauncherInstalls<RiotClientInstalls>("ProgramData\\Riot Games\\RiotClientInstalls.json"); _riotClientInstalls ??= GetDriveLauncherInstalls<RiotClientInstalls>("ProgramData\\Riot Games\\RiotClientInstalls.json");
if (_riotClientInstalls is { AssociatedClient: { } }) if (_riotClientInstalls is { AssociatedClient: { } })
@@ -160,7 +143,7 @@ public class GameSelectorViewModel : ViewModel
if (key.Contains(gameName, StringComparison.OrdinalIgnoreCase) && Directory.Exists(gameDir)) if (key.Contains(gameName, StringComparison.OrdinalIgnoreCase) && Directory.Exists(gameDir))
{ {
Log.Debug("Found {GameName} in RiotClientInstalls.json", gameName); Log.Debug("Found {GameName} in RiotClientInstalls.json", gameName);
return new DetectedGame { GameName = gameName, GameDirectory = gameDir }; return DirectorySettings.Default(gameName, gameDir, ue: ueVersion);
} }
} }
} }
@@ -168,36 +151,19 @@ public class GameSelectorViewModel : ViewModel
return null; return null;
} }
private LauncherSettings _launcherSettings; private DirectorySettings GetSteamGame(int id, string pakDirectory, EGame ueVersion)
private DetectedGame GetMojangGame(string gameName, string pakDirectory)
{
_launcherSettings ??= GetDataLauncherInstalls<LauncherSettings>("\\.minecraft\\launcher_settings.json");
if (_launcherSettings is { ProductLibraryDir: { } })
{
var gameDir = $"{_launcherSettings.ProductLibraryDir}{pakDirectory}";
if (Directory.Exists(gameDir))
{
Log.Debug("Found {GameName} in launcher_settings.json", gameName);
return new DetectedGame { GameName = gameName, GameDirectory = gameDir };
}
}
return null;
}
private DetectedGame GetSteamGame(int id, string pakDirectory)
{ {
var steamInfo = SteamDetection.GetSteamGameById(id); var steamInfo = SteamDetection.GetSteamGameById(id);
if (steamInfo is not null) if (steamInfo is not null)
{ {
Log.Debug("Found {GameName} in steam manifests", steamInfo.Name); Log.Debug("Found {GameName} in steam manifests", steamInfo.Name);
return new DetectedGame { GameName = steamInfo.Name, GameDirectory = $"{steamInfo.GameRoot}{pakDirectory}" }; return DirectorySettings.Default(steamInfo.Name, $"{steamInfo.GameRoot}{pakDirectory}", ue: ueVersion);
} }
return null; return null;
} }
private DetectedGame GetRockstarGamesGame(string key, string pakDirectory) private DirectorySettings GetRockstarGamesGame(string key, string pakDirectory, EGame ueVersion)
{ {
var installLocation = string.Empty; var installLocation = string.Empty;
try try
@@ -213,13 +179,13 @@ public class GameSelectorViewModel : ViewModel
if (Directory.Exists(gameDir)) if (Directory.Exists(gameDir))
{ {
Log.Debug("Found {GameName} in the registry", key); Log.Debug("Found {GameName} in the registry", key);
return new DetectedGame { GameName = key, GameDirectory = gameDir }; return DirectorySettings.Default(key, gameDir, ue: ueVersion);
} }
return null; return null;
} }
private DetectedGame GetLevelInfiniteGame(string key, string pakDirectory) private DirectorySettings GetLevelInfiniteGame(string key, string pakDirectory, EGame ueVersion)
{ {
var installLocation = string.Empty; var installLocation = string.Empty;
var displayName = string.Empty; var displayName = string.Empty;
@@ -238,7 +204,7 @@ public class GameSelectorViewModel : ViewModel
if (Directory.Exists(gameDir)) if (Directory.Exists(gameDir))
{ {
Log.Debug("Found {GameName} in the registry", key); Log.Debug("Found {GameName} in the registry", key);
return new DetectedGame { GameName = displayName, GameDirectory = gameDir }; return DirectorySettings.Default(displayName, gameDir, ue: ueVersion);
} }
return null; return null;
@@ -258,19 +224,6 @@ public class GameSelectorViewModel : ViewModel
return default; return default;
} }
private T GetDataLauncherInstalls<T>(string jsonFile)
{
var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
var launcher = $"{appData}{jsonFile}";
if (File.Exists(launcher))
{
Log.Debug("\"{Launcher}\" found in \"{AppData}\"", launcher, appData);
return JsonConvert.DeserializeObject<T>(File.ReadAllText(launcher));
}
return default;
}
#pragma warning disable 649 #pragma warning disable 649
private class LauncherInstalled private class LauncherInstalled
{ {

View File

@@ -1,876 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Media.Imaging;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Engine;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.GameplayTags;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Creator;
using FModel.Extensions;
using FModel.Framework;
using FModel.Services;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.ViewModels;
public class MapLayer
{
public SKBitmap Layer;
public bool IsEnabled;
}
public enum EWaypointType
{
Parkour,
TimeTrials
}
public class MapViewerViewModel : ViewModel
{
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
private DiscordHandler _discordHandler => DiscordService.DiscordHandler;
#region BINDINGS
private bool _brPois;
public bool BrPois
{
get => _brPois;
set => SetProperty(ref _brPois, value, "ApolloGameplay_MapPois");
}
private bool _brLandmarks;
public bool BrLandmarks
{
get => _brLandmarks;
set => SetProperty(ref _brLandmarks, value, "ApolloGameplay_MapLandmarks");
}
private bool _brTagsLocation;
public bool BrTagsLocation
{
get => _brTagsLocation;
set => SetProperty(ref _brTagsLocation, value, "ApolloGameplay_TagsLocation");
}
private bool _brPatrolsPath;
public bool BrPatrolsPath
{
get => _brPatrolsPath;
set => SetProperty(ref _brPatrolsPath, value, "ApolloGameplay_PatrolsPath");
}
private bool _brUpgradeBenches;
public bool BrUpgradeBenches
{
get => _brUpgradeBenches;
set => SetProperty(ref _brUpgradeBenches, value, "ApolloGameplay_UpgradeBenches");
}
private bool _brPhonebooths;
public bool BrPhonebooths
{
get => _brPhonebooths;
set => SetProperty(ref _brPhonebooths, value, "ApolloGameplay_Phonebooths");
}
private bool _brVendingMachines;
public bool BrVendingMachines
{
get => _brVendingMachines;
set => SetProperty(ref _brVendingMachines, value, "ApolloGameplay_VendingMachines");
}
private bool _brBountyBoards;
public bool BrBountyBoards
{
get => _brBountyBoards;
set => SetProperty(ref _brBountyBoards, value, "ApolloGameplay_BountyBoards");
}
private bool _prLandmarks;
public bool PrLandmarks
{
get => _prLandmarks;
set => SetProperty(ref _prLandmarks, value, "PapayaGameplay_MapLandmarks");
}
private bool _prCannonball;
public bool PrCannonball
{
get => _prCannonball;
set => SetProperty(ref _prCannonball, value, "PapayaGameplay_CannonballGame");
}
private bool _prSkydive;
public bool PrSkydive
{
get => _prSkydive;
set => SetProperty(ref _prSkydive, value, "PapayaGameplay_SkydiveGame");
}
private bool _prShootingTargets;
public bool PrShootingTargets
{
get => _prShootingTargets;
set => SetProperty(ref _prShootingTargets, value, "PapayaGameplay_ShootingTargets");
}
private bool _prParkour;
public bool PrParkour
{
get => _prParkour;
set => SetProperty(ref _prParkour, value, "PapayaGameplay_ParkourGame");
}
private bool _prTimeTrials;
public bool PrTimeTrials
{
get => _prTimeTrials;
set => SetProperty(ref _prTimeTrials, value, "PapayaGameplay_TimeTrials");
}
private bool _prVendingMachines;
public bool PrVendingMachines
{
get => _prVendingMachines;
set => SetProperty(ref _prVendingMachines, value, "PapayaGameplay_VendingMachines");
}
private bool _prMusicBlocks;
public bool PrMusicBlocks
{
get => _prMusicBlocks;
set => SetProperty(ref _prMusicBlocks, value, "PapayaGameplay_MusicBlocks");
}
#endregion
#region BITMAP IMAGES
private BitmapImage _brMiniMapImage;
private BitmapImage _prMiniMapImage;
private BitmapImage _mapImage;
public BitmapImage MapImage
{
get => _mapImage;
set => SetProperty(ref _mapImage, value);
}
private BitmapImage _brLayerImage;
private BitmapImage _prLayerImage;
private BitmapImage _layerImage;
public BitmapImage LayerImage
{
get => _layerImage;
set => SetProperty(ref _layerImage, value);
}
private const int _widthHeight = 2048;
private const int _brRadius = 141000;
private const int _prRadius = 51000;
private int _mapIndex;
public int MapIndex // 0 is BR, 1 is PR
{
get => _mapIndex;
set
{
SetProperty(ref _mapIndex, value);
TriggerChange();
}
}
#endregion
private const string _FIRST_BITMAP = "MapCheck";
private readonly Dictionary<string, MapLayer>[] _bitmaps; // first bitmap is the displayed map, others are overlays of the map
private readonly CUE4ParseViewModel _cue4Parse;
public MapViewerViewModel(CUE4ParseViewModel cue4Parse)
{
_bitmaps = new[]
{
new Dictionary<string, MapLayer>(),
new Dictionary<string, MapLayer>()
};
_cue4Parse = cue4Parse;
}
public async void Initialize()
{
Utils.Typefaces ??= new Typefaces(_cue4Parse);
_textPaint.Typeface = _fillPaint.Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.DisplayName;
await LoadBrMiniMap();
await LoadPrMiniMap();
TriggerChange();
}
public BitmapImage GetImageToSave() => GetImageSource(GetLayerBitmap(true));
private SKBitmap GetLayerBitmap(bool withMap)
{
var ret = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
foreach (var (key, value) in _bitmaps[MapIndex])
{
if (!value.IsEnabled || !withMap && key == _FIRST_BITMAP)
continue;
c.DrawBitmap(value.Layer, new SKRect(0, 0, _widthHeight, _widthHeight));
}
return ret;
}
protected override bool SetProperty<T>(ref T storage, T value, string propertyName = null) // don't delete, else nothing will update for some reason
{
var ret = base.SetProperty(ref storage, value, propertyName);
if (bool.TryParse(value.ToString(), out var b)) GenericToggle(propertyName, b);
return ret;
}
private async void GenericToggle(string key, bool enabled)
{
if (_bitmaps[MapIndex].TryGetValue(key, out var layer) && layer.Layer != null)
{
layer.IsEnabled = enabled;
}
else if (enabled) // load layer
{
switch (key)
{
case "ApolloGameplay_MapPois":
case "ApolloGameplay_MapLandmarks":
case "PapayaGameplay_MapLandmarks":
await LoadQuestIndicatorData();
break;
case "ApolloGameplay_TagsLocation":
await LoadTagsLocation();
break;
case "ApolloGameplay_PatrolsPath":
await LoadPatrolsPath();
break;
case "ApolloGameplay_UpgradeBenches":
await LoadUpgradeBenches();
break;
case "ApolloGameplay_Phonebooths":
await LoadPhonebooths();
break;
case "ApolloGameplay_VendingMachines":
await LoadBrVendingMachines();
break;
case "ApolloGameplay_BountyBoards":
await LoadBountyBoards();
break;
case "PapayaGameplay_CannonballGame":
await LoadCannonballGame();
break;
case "PapayaGameplay_SkydiveGame":
await LoadSkydiveGame();
break;
case "PapayaGameplay_ShootingTargets":
await LoadShootingTargets();
break;
case "PapayaGameplay_ParkourGame":
await LoadWaypoint(EWaypointType.Parkour);
break;
case "PapayaGameplay_TimeTrials":
await LoadWaypoint(EWaypointType.TimeTrials);
break;
case "PapayaGameplay_VendingMachines":
await LoadPrVendingMachines();
break;
case "PapayaGameplay_MusicBlocks":
await LoadMusicBlocks();
break;
}
_bitmaps[MapIndex][key].IsEnabled = true;
}
switch (MapIndex)
{
case 0:
_brLayerImage = GetImageSource(GetLayerBitmap(false));
break;
case 1:
_prLayerImage = GetImageSource(GetLayerBitmap(false));
break;
}
TriggerChange();
}
private BitmapImage GetImageSource(SKBitmap bitmap)
{
if (bitmap == null) return null;
using var data = bitmap.Encode(SKEncodedImageFormat.Png, 100);
using var stream = data.AsStream();
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = stream;
image.EndInit();
image.Freeze();
return image;
}
private void TriggerChange()
{
var layerCount = _bitmaps[_mapIndex].Count(x => x.Value.IsEnabled);
var layerString = $"{layerCount} Layer{(layerCount > 1 ? "s" : "")}";
switch (_mapIndex)
{
case 0:
_discordHandler.UpdateButDontSavePresence(null, $"Map Viewer: Battle Royale ({layerString})");
_mapImage = _brMiniMapImage;
_layerImage = _brLayerImage;
break;
case 1:
_discordHandler.UpdateButDontSavePresence(null, $"Map Viewer: Party Royale ({layerString})");
_mapImage = _prMiniMapImage;
_layerImage = _prLayerImage;
break;
}
RaisePropertyChanged(nameof(MapImage));
RaisePropertyChanged(nameof(LayerImage));
}
private readonly SKPaint _textPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Color = SKColors.White, TextAlign = SKTextAlign.Center, TextSize = 26
};
private readonly SKPaint _fillPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
IsStroke = true, Color = SKColors.Black, TextSize = 26,
TextAlign = SKTextAlign.Center
};
private readonly SKPaint _pathPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High, IsStroke = true,
Style = SKPaintStyle.Stroke, StrokeWidth = 5, Color = SKColors.Red,
ImageFilter = SKImageFilter.CreateDropShadow(4, 4, 8, 8, SKColors.Black)
};
private FVector2D GetMapPosition(FVector vector, int mapRadius)
{
const int wh = 2048 + 128 + 32;
var nx = (vector.Y + mapRadius) / (mapRadius * 2) * wh;
var ny = (1 - (vector.X + mapRadius) / (mapRadius * 2)) * wh;
return new FVector2D(nx, ny);
}
private async Task LoadBrMiniMap()
{
if (_bitmaps[0].TryGetValue(_FIRST_BITMAP, out var brMap) && brMap.Layer != null)
return; // if map already loaded
await _threadWorkerView.Begin(_ =>
{
if (!Utils.TryLoadObject("FortniteGame/Content/UI/IngameMap/UIMapManagerBR.Default__UIMapManagerBR_C", out UObject mapManager) ||
!mapManager.TryGetValue(out UMaterial mapMaterial, "MapMaterial")) return;
var midTex = mapMaterial.GetFirstTexture();
if ((midTex?.Name ?? string.Empty).Contains("Mask"))
midTex = mapMaterial.GetTextureAtIndex(1);
if (midTex is not UTexture2D tex) return;
_bitmaps[0][_FIRST_BITMAP] = new MapLayer { Layer = Utils.GetBitmap(tex), IsEnabled = true };
_brMiniMapImage = GetImageSource(_bitmaps[0][_FIRST_BITMAP].Layer);
});
}
private async Task LoadPrMiniMap()
{
if (_bitmaps[1].TryGetValue(_FIRST_BITMAP, out var prMap) && prMap.Layer != null)
return; // if map already loaded
await _threadWorkerView.Begin(_ =>
{
if (!Utils.TryLoadObject("FortniteGame/Content/UI/IngameMap/UIMapManagerPapaya.Default__UIMapManagerPapaya_C", out UObject mapManager) ||
!mapManager.TryGetValue(out UMaterial mapMaterial, "MapMaterial") || mapMaterial.GetFirstTexture() is not UTexture2D tex) return;
_bitmaps[1][_FIRST_BITMAP] = new MapLayer { Layer = Utils.GetBitmap(tex), IsEnabled = true };
_prMiniMapImage = GetImageSource(_bitmaps[1][_FIRST_BITMAP].Layer);
});
}
private async Task LoadQuestIndicatorData()
{
await _threadWorkerView.Begin(_ =>
{
var poisBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
var brLandmarksBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
var prLandmarksBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
using var pois = new SKCanvas(poisBitmap);
using var brLandmarks = new SKCanvas(brLandmarksBitmap);
using var prLandmarks = new SKCanvas(prLandmarksBitmap);
if (Utils.TryLoadObject("FortniteGame/Content/Quests/QuestIndicatorData", out UObject indicatorData) &&
indicatorData.TryGetValue(out FStructFallback[] challengeMapPoiData, "ChallengeMapPoiData"))
{
foreach (var poiData in challengeMapPoiData)
{
if (!poiData.TryGetValue(out FSoftObjectPath discoveryQuest, "DiscoveryQuest") ||
!poiData.TryGetValue(out FText text, "Text") || string.IsNullOrEmpty(text.Text) ||
!poiData.TryGetValue(out FVector worldLocation, "WorldLocation") ||
!poiData.TryGetValue(out FName discoverBackend, "DiscoverObjectiveBackendName")) continue;
var shaper = new CustomSKShaper(_textPaint.Typeface);
if (discoverBackend.Text.Contains("papaya", StringComparison.OrdinalIgnoreCase))
{
_fillPaint.StrokeWidth = 5;
var vector = GetMapPosition(worldLocation, _prRadius);
prLandmarks.DrawPoint(vector.X, vector.Y, _pathPaint);
prLandmarks.DrawShapedText(shaper, text.Text, vector.X, vector.Y - 12.5F, _fillPaint);
prLandmarks.DrawShapedText(shaper, text.Text, vector.X, vector.Y - 12.5F, _textPaint);
}
else if (discoveryQuest.AssetPathName.Text.Contains("landmarks", StringComparison.OrdinalIgnoreCase))
{
_fillPaint.StrokeWidth = 5;
var vector = GetMapPosition(worldLocation, _brRadius);
brLandmarks.DrawPoint(vector.X, vector.Y, _pathPaint);
brLandmarks.DrawShapedText(shaper, text.Text, vector.X, vector.Y - 12.5F, _fillPaint);
brLandmarks.DrawShapedText(shaper, text.Text, vector.X, vector.Y - 12.5F, _textPaint);
}
else
{
_fillPaint.StrokeWidth = 10;
var vector = GetMapPosition(worldLocation, _brRadius);
pois.DrawShapedText(shaper, text.Text.ToUpperInvariant(), vector.X, vector.Y, _fillPaint);
pois.DrawShapedText(shaper, text.Text.ToUpperInvariant(), vector.X, vector.Y, _textPaint);
}
}
}
_bitmaps[0]["ApolloGameplay_MapPois"] = new MapLayer { Layer = poisBitmap, IsEnabled = false };
_bitmaps[0]["ApolloGameplay_MapLandmarks"] = new MapLayer { Layer = brLandmarksBitmap, IsEnabled = false };
_bitmaps[1]["PapayaGameplay_MapLandmarks"] = new MapLayer { Layer = prLandmarksBitmap, IsEnabled = false };
});
}
private async Task LoadPatrolsPath()
{
await _threadWorkerView.Begin(_ =>
{
_fillPaint.StrokeWidth = 5;
var patrolsPathBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(patrolsPathBitmap);
var exports = Utils.LoadExports("/NPCLibrary/LevelOverlays/Artemis_Overlay_S22_NPCLibrary");
foreach (var export in exports)
{
if (!export.ExportType.Equals("FortAthenaPatrolPath", StringComparison.OrdinalIgnoreCase) ||
!export.TryGetValue(out FPackageIndex[] patrolPoints, "PatrolPoints")) continue;
var displayName = export.Name["FortAthenaPatrolPath_".Length..];
if (export.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags") && gameplayTags.GameplayTags.Length > 0)
displayName = gameplayTags.GameplayTags[0].Text["Athena.AI.SpawnLocation.Tandem.".Length..];
if (!Utils.TryGetPackageIndexExport(patrolPoints[0], out UObject uObject) ||
!uObject.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
!Utils.TryGetPackageIndexExport(rootComponent, out uObject) ||
!uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
var path = new SKPath();
var vector = GetMapPosition(relativeLocation, _brRadius);
path.MoveTo(vector.X, vector.Y);
for (var i = 1; i < patrolPoints.Length; i++)
{
if (!Utils.TryGetPackageIndexExport(patrolPoints[i], out uObject) ||
!uObject.TryGetValue(out rootComponent, "RootComponent") ||
!Utils.TryGetPackageIndexExport(rootComponent, out uObject) ||
!uObject.TryGetValue(out relativeLocation, "RelativeLocation")) continue;
vector = GetMapPosition(relativeLocation, _brRadius);
path.LineTo(vector.X, vector.Y);
}
c.DrawPath(path, _pathPaint);
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint);
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint);
}
_bitmaps[0]["ApolloGameplay_PatrolsPath"] = new MapLayer { Layer = patrolsPathBitmap, IsEnabled = false };
});
}
private async Task LoadCannonballGame()
{
await _threadWorkerView.Begin(_ =>
{
_fillPaint.StrokeWidth = 5;
var cannonballBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(cannonballBitmap);
var exports = Utils.LoadExports("/PapayaGameplay/LevelOverlays/PapayaGameplay_CannonballGame");
foreach (var export in exports)
{
if (!export.ExportType.Equals("BP_CannonballGame_Target_C", StringComparison.OrdinalIgnoreCase) &&
!export.ExportType.Equals("CannonballGame_VehicleSpawner_C", StringComparison.OrdinalIgnoreCase)) continue;
if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
!Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) ||
!uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
var displayName = Utils.GetLocalizedResource("", "D998BEF44F051E0885C6C58565934BEA", "Cannonball");
var vector = GetMapPosition(relativeLocation, _prRadius);
c.DrawPoint(vector.X, vector.Y, _pathPaint);
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint);
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint);
}
_bitmaps[1]["PapayaGameplay_CannonballGame"] = new MapLayer { Layer = cannonballBitmap, IsEnabled = false };
});
}
private async Task LoadSkydiveGame()
{
await _threadWorkerView.Begin(_ =>
{
_fillPaint.StrokeWidth = 5;
var skydiveBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(skydiveBitmap);
var exports = Utils.LoadExports("/PapayaGameplay/LevelOverlays/PapayaGameplay_SkydiveGame");
foreach (var export in exports)
{
if (!export.ExportType.Equals("BP_Waypoint_Papaya_Skydive_Start_C", StringComparison.OrdinalIgnoreCase)) continue;
if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
!export.TryGetValue(out FText minigameActivityName, "MinigameActivityName") ||
!Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) ||
!uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
var vector = GetMapPosition(relativeLocation, _prRadius);
c.DrawPoint(vector.X, vector.Y, _pathPaint);
c.DrawText(minigameActivityName.Text, vector.X, vector.Y - 12.5F, _fillPaint);
c.DrawText(minigameActivityName.Text, vector.X, vector.Y - 12.5F, _textPaint);
}
_bitmaps[1]["PapayaGameplay_SkydiveGame"] = new MapLayer { Layer = skydiveBitmap, IsEnabled = false };
});
}
private async Task LoadShootingTargets()
{
await _threadWorkerView.Begin(_ =>
{
_fillPaint.StrokeWidth = 5;
var shootingTargetsBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(shootingTargetsBitmap);
var bDone = false;
var exports = Utils.LoadExports("/PapayaGameplay/LevelOverlays/PapayaGameplay_ShootingTargets");
foreach (var export in exports)
{
if (!export.ExportType.Equals("PapayaShootingTarget_C", StringComparison.OrdinalIgnoreCase)) continue;
if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
!Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) ||
!uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
var vector = GetMapPosition(relativeLocation, _prRadius);
c.DrawPoint(vector.X, vector.Y, _pathPaint);
if (bDone) continue;
bDone = true;
c.DrawText("Shooting Target", vector.X, vector.Y - 12.5F, _fillPaint);
c.DrawText("Shooting Target", vector.X, vector.Y - 12.5F, _textPaint);
}
_bitmaps[1]["PapayaGameplay_ShootingTargets"] = new MapLayer { Layer = shootingTargetsBitmap, IsEnabled = false };
});
}
private async Task LoadWaypoint(EWaypointType type)
{
await _threadWorkerView.Begin(_ =>
{
_fillPaint.StrokeWidth = 5;
var waypointBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(waypointBitmap);
string file;
string name;
switch (type)
{
case EWaypointType.Parkour:
file = "PapayaGameplay_ParkourGame";
name = "Parkour";
break;
case EWaypointType.TimeTrials:
file = "PapayaGameplay_TimeTrials";
name = "Time Trials";
break;
default:
throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
var path = new SKPath();
var exports = Utils.LoadExports($"/PapayaGameplay/LevelOverlays/{file}");
foreach (var export in exports)
{
if (!export.ExportType.Equals("BP_Waypoint_Parent_Papaya_C", StringComparison.OrdinalIgnoreCase)) continue;
if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
!Utils.TryGetPackageIndexExport(rootComponent, out UObject root) ||
!root.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
var vector = GetMapPosition(relativeLocation, _prRadius);
if (path.IsEmpty || export.TryGetValue(out bool startsTrial, "StartsTrial") && startsTrial)
{
path.MoveTo(vector.X, vector.Y);
c.DrawText(name, vector.X, vector.Y - 12.5F, _fillPaint);
c.DrawText(name, vector.X, vector.Y - 12.5F, _textPaint);
}
else if (export.TryGetValue(out bool endsTrial, "EndsTrial") && endsTrial)
{
path.LineTo(vector.X, vector.Y);
c.DrawPath(path, _pathPaint);
path = new SKPath();
}
else path.LineTo(vector.X, vector.Y);
}
_bitmaps[1][file] = new MapLayer { Layer = waypointBitmap, IsEnabled = false };
});
}
private async Task LoadPrVendingMachines()
{
await _threadWorkerView.Begin(_ =>
{
_fillPaint.StrokeWidth = 5;
var set = new HashSet<string>();
var timeTrialsBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(timeTrialsBitmap);
var exports = Utils.LoadExports("/PapayaGameplay/LevelOverlays/PapayaGameplay_VendingMachines");
foreach (var export in exports)
{
if (!export.ExportType.Equals("B_Papaya_VendingMachine_Boat_C", StringComparison.OrdinalIgnoreCase) &&
!export.ExportType.Equals("B_Papaya_VendingMachine_BoogieBomb_C", StringComparison.OrdinalIgnoreCase) &&
!export.ExportType.Equals("B_Papaya_VendingMachine_Burger_C", StringComparison.OrdinalIgnoreCase) &&
!export.ExportType.Equals("B_Papaya_VendingMachine_CrashPad_C", StringComparison.OrdinalIgnoreCase) &&
!export.ExportType.Equals("B_Papaya_VendingMachine_FishingPole_C", StringComparison.OrdinalIgnoreCase) &&
!export.ExportType.Equals("B_Papaya_VendingMachine_Grappler_C", StringComparison.OrdinalIgnoreCase) &&
!export.ExportType.Equals("B_Papaya_VendingMachine_Jetpack_C", StringComparison.OrdinalIgnoreCase) &&
!export.ExportType.Equals("B_Papaya_VendingMachine_PaintGrenade_Blue_C", StringComparison.OrdinalIgnoreCase) &&
!export.ExportType.Equals("B_Papaya_VendingMachine_PaintGrenade_Red_C", StringComparison.OrdinalIgnoreCase) &&
!export.ExportType.Equals("B_Papaya_VendingMachine_PaintLauncher_Blue_C", StringComparison.OrdinalIgnoreCase) &&
!export.ExportType.Equals("B_Papaya_VendingMachine_PaintLauncher_Red_C", StringComparison.OrdinalIgnoreCase) &&
!export.ExportType.Equals("B_Papaya_VendingMachine_PlungerBow_C", StringComparison.OrdinalIgnoreCase) &&
!export.ExportType.Equals("B_Papaya_VendingMachine_Quad_C", StringComparison.OrdinalIgnoreCase) &&
!export.ExportType.Equals("B_Papaya_VendingMachine_Tomato_C", StringComparison.OrdinalIgnoreCase)) continue;
if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
!Utils.TryGetPackageIndexExport(rootComponent, out UObject root) ||
!root.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
var name = export.ExportType.SubstringAfter("B_Papaya_VendingMachine_").SubstringBeforeLast("_C");
var vector = GetMapPosition(relativeLocation, _prRadius);
c.DrawPoint(vector.X, vector.Y, _pathPaint);
if (!set.Add(name)) continue;
c.DrawText(name, vector.X, vector.Y - 12.5F, _fillPaint);
c.DrawText(name, vector.X, vector.Y - 12.5F, _textPaint);
}
_bitmaps[1]["PapayaGameplay_VendingMachines"] = new MapLayer { Layer = timeTrialsBitmap, IsEnabled = false };
});
}
private async Task LoadMusicBlocks()
{
await _threadWorkerView.Begin(_ =>
{
_fillPaint.StrokeWidth = 5;
var shootingTargetsBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(shootingTargetsBitmap);
var bDone = false;
var exports = Utils.LoadExports("/PapayaGameplay/LevelOverlays/PapayaGameplay_MusicBlocks");
foreach (var export in exports)
{
if (!export.ExportType.Equals("MusicBlock_Piano3_Papaya_C", StringComparison.OrdinalIgnoreCase)) continue;
if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
!Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) ||
!uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
var vector = GetMapPosition(relativeLocation, _prRadius);
c.DrawPoint(vector.X, vector.Y, _pathPaint);
if (bDone) continue;
bDone = true;
c.DrawText("Music Blocks", vector.X, vector.Y - 12.5F, _fillPaint);
c.DrawText("Music Blocks", vector.X, vector.Y - 12.5F, _textPaint);
}
_bitmaps[1]["PapayaGameplay_MusicBlocks"] = new MapLayer { Layer = shootingTargetsBitmap, IsEnabled = false };
});
}
private async Task LoadUpgradeBenches()
{
await _threadWorkerView.Begin(_ =>
{
_fillPaint.StrokeWidth = 5;
var upgradeBenchesBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(upgradeBenchesBitmap);
var exports = Utils.LoadExports("/NPCLibrary/LevelOverlays/Artemis_Overlay_S19_UpgradeBenches");
foreach (var export in exports)
{
if (!export.ExportType.Equals("B_Athena_Spawner_UpgradeStation_C", StringComparison.OrdinalIgnoreCase)) continue;
var displayName = export.Name["B_Athena_Spawner_".Length..];
if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
!Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) ||
!uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
var vector = GetMapPosition(relativeLocation, _brRadius);
c.DrawPoint(vector.X, vector.Y, _pathPaint);
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint);
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint);
}
_bitmaps[0]["ApolloGameplay_UpgradeBenches"] = new MapLayer { Layer = upgradeBenchesBitmap, IsEnabled = false };
});
}
private async Task LoadPhonebooths()
{
await _threadWorkerView.Begin(_ =>
{
_fillPaint.StrokeWidth = 5;
var phoneboothsBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(phoneboothsBitmap);
var exports = Utils.LoadExports("/NPCLibrary/LevelOverlays/Apollo_Terrain_NPCLibrary_Stations_Phonebooths");
foreach (var export in exports)
{
if (!export.ExportType.Equals("B_Athena_Spawner_Payphone_C", StringComparison.OrdinalIgnoreCase)) continue;
var displayName = export.Name["B_Athena_Spawner_".Length..];
if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
!Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) ||
!uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
var vector = GetMapPosition(relativeLocation, _brRadius);
c.DrawPoint(vector.X, vector.Y, _pathPaint);
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint);
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint);
}
_bitmaps[0]["ApolloGameplay_Phonebooths"] = new MapLayer { Layer = phoneboothsBitmap, IsEnabled = false };
});
}
private async Task LoadBrVendingMachines()
{
await _threadWorkerView.Begin(_ =>
{
_fillPaint.StrokeWidth = 5;
var vendingMachinesBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(vendingMachinesBitmap);
var exports = Utils.LoadExports("/NPCLibrary/LevelOverlays/Artemis_Overlay_S19_ServiceStations");
foreach (var export in exports)
{
if (!export.ExportType.Equals("B_Athena_Spawner_VendingMachine_MendingOnly_C", StringComparison.OrdinalIgnoreCase) &&
!export.ExportType.Equals("B_Athena_Spawner_VendingMachine_Random_C", StringComparison.OrdinalIgnoreCase)) continue;
var displayName = $"{(export.ExportType.Contains("Mending") ? "MM" : "WOM")}_{export.Name["B_Athena_Spawner_VendingMachine_Random".Length..]}";
if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
!Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) ||
!uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
var vector = GetMapPosition(relativeLocation, _brRadius);
c.DrawPoint(vector.X, vector.Y, _pathPaint);
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint);
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint);
}
_bitmaps[0]["ApolloGameplay_VendingMachines"] = new MapLayer { Layer = vendingMachinesBitmap, IsEnabled = false };
});
}
private async Task LoadBountyBoards()
{
await _threadWorkerView.Begin(_ =>
{
_fillPaint.StrokeWidth = 5;
var bountyBoardsBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(bountyBoardsBitmap);
var exports = Utils.LoadExports("/Bounties/Maps/BB_Overlay_ServiceStations");
foreach (var export in exports)
{
if (!export.ExportType.Equals("B_Bounties_Spawner_BountyBoard_C", StringComparison.OrdinalIgnoreCase)) continue;
var displayName = $"BountyBoard_{export.Name["BP_BountyBoard_C_".Length..]}";
if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
!Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) ||
!uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
var vector = GetMapPosition(relativeLocation, _brRadius);
c.DrawPoint(vector.X, vector.Y, _pathPaint);
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint);
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint);
}
_bitmaps[0]["ApolloGameplay_BountyBoards"] = new MapLayer { Layer = bountyBoardsBitmap, IsEnabled = false };
});
}
private async Task LoadTagsLocation()
{
await _threadWorkerView.Begin(_ =>
{
_fillPaint.StrokeWidth = 5;
if (!Utils.TryLoadObject("FortniteGame/Content/Quests/QuestTagToLocationDataRows.QuestTagToLocationDataRows", out UDataTable locationData))
return;
var tagsLocationBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(tagsLocationBitmap);
foreach (var (key, uObject) in locationData.RowMap)
{
if (key.Text.StartsWith("Athena.Location.POI", StringComparison.OrdinalIgnoreCase) ||
key.Text.StartsWith("Athena.Location.Unnamed", StringComparison.OrdinalIgnoreCase) ||
key.Text.Contains(".Tandem.", StringComparison.OrdinalIgnoreCase) ||
!uObject.TryGetValue(out FVector worldLocation, "WorldLocation")) continue;
var parts = key.Text.Split('.');
var displayName = parts[^2];
if (!int.TryParse(parts[^1], out var _))
displayName += " " + parts[^1];
var vector = GetMapPosition(worldLocation, _brRadius);
c.DrawPoint(vector.X, vector.Y, _pathPaint);
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint);
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint);
}
_bitmaps[0]["ApolloGameplay_TagsLocation"] = new MapLayer { Layer = tagsLocationBitmap, IsEnabled = false };
});
}
}

View File

@@ -1,26 +1,21 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Threading.Tasks; using System.Linq;
using CUE4Parse.UE4.Assets.Exports.Texture; using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Objects.Core.Misc;
using CUE4Parse.UE4.Objects.Core.Serialization; using CUE4Parse.UE4.Objects.Core.Serialization;
using CUE4Parse.UE4.Versions; using CUE4Parse.UE4.Versions;
using CUE4Parse_Conversion.Meshes; using CUE4Parse_Conversion.Meshes;
using CUE4Parse_Conversion.Textures; using CUE4Parse_Conversion.Textures;
using CUE4Parse.UE4.Assets.Exports.Material; using CUE4Parse.UE4.Assets.Exports.Material;
using FModel.Extensions;
using FModel.Framework; using FModel.Framework;
using FModel.Services; using FModel.Services;
using FModel.Settings; using FModel.Settings;
using FModel.ViewModels.ApiEndpoints.Models;
namespace FModel.ViewModels; namespace FModel.ViewModels;
public class SettingsViewModel : ViewModel public class SettingsViewModel : ViewModel
{ {
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
private readonly DiscordHandler _discordHandler = DiscordService.DiscordHandler; private readonly DiscordHandler _discordHandler = DiscordService.DiscordHandler;
private bool _useCustomOutputFolders; private bool _useCustomOutputFolders;
@@ -37,17 +32,6 @@ public class SettingsViewModel : ViewModel
set => SetProperty(ref _selectedUpdateMode, value); set => SetProperty(ref _selectedUpdateMode, value);
} }
private string _selectedPreset;
public string SelectedPreset
{
get => _selectedPreset;
set
{
SetProperty(ref _selectedPreset, value);
RaisePropertyChanged("EnableElements");
}
}
private ETexturePlatform _selectedUePlatform; private ETexturePlatform _selectedUePlatform;
public ETexturePlatform SelectedUePlatform public ETexturePlatform SelectedUePlatform
{ {
@@ -62,36 +46,36 @@ public class SettingsViewModel : ViewModel
set => SetProperty(ref _selectedUeGame, value); set => SetProperty(ref _selectedUeGame, value);
} }
private List<FCustomVersion> _selectedCustomVersions; private IList<FCustomVersion> _selectedCustomVersions;
public List<FCustomVersion> SelectedCustomVersions public IList<FCustomVersion> SelectedCustomVersions
{ {
get => _selectedCustomVersions; get => _selectedCustomVersions;
set => SetProperty(ref _selectedCustomVersions, value); set => SetProperty(ref _selectedCustomVersions, value);
} }
private Dictionary<string, bool> _selectedOptions; private IDictionary<string, bool> _selectedOptions;
public Dictionary<string, bool> SelectedOptions public IDictionary<string, bool> SelectedOptions
{ {
get => _selectedOptions; get => _selectedOptions;
set => SetProperty(ref _selectedOptions, value); set => SetProperty(ref _selectedOptions, value);
} }
private Dictionary<string, KeyValuePair<string, string>> _selectedMapStructTypes; private IDictionary<string, KeyValuePair<string, string>> _selectedMapStructTypes;
public Dictionary<string, KeyValuePair<string, string>> SelectedMapStructTypes public IDictionary<string, KeyValuePair<string, string>> SelectedMapStructTypes
{ {
get => _selectedMapStructTypes; get => _selectedMapStructTypes;
set => SetProperty(ref _selectedMapStructTypes, value); set => SetProperty(ref _selectedMapStructTypes, value);
} }
private FEndpoint _aesEndpoint; private EndpointSettings _aesEndpoint;
public FEndpoint AesEndpoint public EndpointSettings AesEndpoint
{ {
get => _aesEndpoint; get => _aesEndpoint;
set => SetProperty(ref _aesEndpoint, value); set => SetProperty(ref _aesEndpoint, value);
} }
private FEndpoint _mappingEndpoint; private EndpointSettings _mappingEndpoint;
public FEndpoint MappingEndpoint public EndpointSettings MappingEndpoint
{ {
get => _mappingEndpoint; get => _mappingEndpoint;
set => SetProperty(ref _mappingEndpoint, value); set => SetProperty(ref _mappingEndpoint, value);
@@ -168,7 +152,6 @@ public class SettingsViewModel : ViewModel
} }
public ReadOnlyObservableCollection<EUpdateMode> UpdateModes { get; private set; } public ReadOnlyObservableCollection<EUpdateMode> UpdateModes { get; private set; }
public ObservableCollection<string> Presets { get; private set; }
public ReadOnlyObservableCollection<EGame> UeGames { get; private set; } public ReadOnlyObservableCollection<EGame> UeGames { get; private set; }
public ReadOnlyObservableCollection<ELanguage> AssetLanguages { get; private set; } public ReadOnlyObservableCollection<ELanguage> AssetLanguages { get; private set; }
public ReadOnlyObservableCollection<EAesReload> AesReloads { get; private set; } public ReadOnlyObservableCollection<EAesReload> AesReloads { get; private set; }
@@ -182,10 +165,6 @@ public class SettingsViewModel : ViewModel
public ReadOnlyObservableCollection<ETextureFormat> TextureExportFormats { get; private set; } public ReadOnlyObservableCollection<ETextureFormat> TextureExportFormats { get; private set; }
public ReadOnlyObservableCollection<ETexturePlatform> Platforms { get; private set; } public ReadOnlyObservableCollection<ETexturePlatform> Platforms { get; private set; }
public bool EnableElements => SelectedPreset == Constants._NO_PRESET_TRIGGER;
private readonly FGame _game;
private Game _gamePreset;
private string _outputSnapshot; private string _outputSnapshot;
private string _rawDataSnapshot; private string _rawDataSnapshot;
private string _propertiesSnapshot; private string _propertiesSnapshot;
@@ -194,12 +173,11 @@ public class SettingsViewModel : ViewModel
private string _modelSnapshot; private string _modelSnapshot;
private string _gameSnapshot; private string _gameSnapshot;
private EUpdateMode _updateModeSnapshot; private EUpdateMode _updateModeSnapshot;
private string _presetSnapshot;
private ETexturePlatform _uePlatformSnapshot; private ETexturePlatform _uePlatformSnapshot;
private EGame _ueGameSnapshot; private EGame _ueGameSnapshot;
private List<FCustomVersion> _customVersionsSnapshot; private IList<FCustomVersion> _customVersionsSnapshot;
private Dictionary<string, bool> _optionsSnapshot; private IDictionary<string, bool> _optionsSnapshot;
private Dictionary<string, KeyValuePair<string, string>> _mapStructTypesSnapshot; private IDictionary<string, KeyValuePair<string, string>> _mapStructTypesSnapshot;
private ELanguage _assetLanguageSnapshot; private ELanguage _assetLanguageSnapshot;
private ECompressedAudio _compressedAudioSnapshot; private ECompressedAudio _compressedAudioSnapshot;
private EIconStyle _cosmeticStyleSnapshot; private EIconStyle _cosmeticStyleSnapshot;
@@ -211,9 +189,9 @@ public class SettingsViewModel : ViewModel
private bool _mappingsUpdate = false; private bool _mappingsUpdate = false;
public SettingsViewModel(FGame game) public SettingsViewModel()
{ {
_game = game;
} }
public void Initialize() public void Initialize()
@@ -226,33 +204,19 @@ public class SettingsViewModel : ViewModel
_modelSnapshot = UserSettings.Default.ModelDirectory; _modelSnapshot = UserSettings.Default.ModelDirectory;
_gameSnapshot = UserSettings.Default.GameDirectory; _gameSnapshot = UserSettings.Default.GameDirectory;
_updateModeSnapshot = UserSettings.Default.UpdateMode; _updateModeSnapshot = UserSettings.Default.UpdateMode;
_presetSnapshot = UserSettings.Default.Presets[_game]; _uePlatformSnapshot = UserSettings.Default.CurrentDir.TexturePlatform;
_uePlatformSnapshot = UserSettings.Default.OverridedPlatform; _ueGameSnapshot = UserSettings.Default.CurrentDir.UeVersion;
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(_gameSnapshot, out var settings)) _customVersionsSnapshot = UserSettings.Default.CurrentDir.Versioning.CustomVersions;
{ _optionsSnapshot = UserSettings.Default.CurrentDir.Versioning.Options;
_ueGameSnapshot = settings.OverridedGame; _mapStructTypesSnapshot = UserSettings.Default.CurrentDir.Versioning.MapStructTypes;
_customVersionsSnapshot = settings.OverridedCustomVersions;
_optionsSnapshot = settings.OverridedOptions;
_mapStructTypesSnapshot = settings.OverridedMapStructTypes;
}
else
{
_ueGameSnapshot = UserSettings.Default.OverridedGame[_game];
_customVersionsSnapshot = UserSettings.Default.OverridedCustomVersions[_game];
_optionsSnapshot = UserSettings.Default.OverridedOptions[_game];
_mapStructTypesSnapshot = UserSettings.Default.OverridedMapStructTypes[_game];
}
if (UserSettings.Default.CustomEndpoints.TryGetValue(_game, out var endpoints)) AesEndpoint = UserSettings.Default.CurrentDir.Endpoints[0];
MappingEndpoint = UserSettings.Default.CurrentDir.Endpoints[1];
MappingEndpoint.PropertyChanged += (_, args) =>
{ {
AesEndpoint = endpoints[0]; if (!_mappingsUpdate)
MappingEndpoint = endpoints[1]; _mappingsUpdate = args.PropertyName is "Overwrite" or "FilePath";
MappingEndpoint.PropertyChanged += (_, args) => };
{
if (!_mappingsUpdate)
_mappingsUpdate = args.PropertyName is "Overwrite" or "FilePath";
};
}
_assetLanguageSnapshot = UserSettings.Default.AssetLanguage; _assetLanguageSnapshot = UserSettings.Default.AssetLanguage;
_compressedAudioSnapshot = UserSettings.Default.CompressedAudioMode; _compressedAudioSnapshot = UserSettings.Default.CompressedAudioMode;
@@ -264,7 +228,6 @@ public class SettingsViewModel : ViewModel
_textureExportFormatSnapshot = UserSettings.Default.TextureExportFormat; _textureExportFormatSnapshot = UserSettings.Default.TextureExportFormat;
SelectedUpdateMode = _updateModeSnapshot; SelectedUpdateMode = _updateModeSnapshot;
SelectedPreset = _presetSnapshot;
SelectedUePlatform = _uePlatformSnapshot; SelectedUePlatform = _uePlatformSnapshot;
SelectedUeGame = _ueGameSnapshot; SelectedUeGame = _ueGameSnapshot;
SelectedCustomVersions = _customVersionsSnapshot; SelectedCustomVersions = _customVersionsSnapshot;
@@ -282,7 +245,6 @@ public class SettingsViewModel : ViewModel
SelectedDiscordRpc = UserSettings.Default.DiscordRpc; SelectedDiscordRpc = UserSettings.Default.DiscordRpc;
UpdateModes = new ReadOnlyObservableCollection<EUpdateMode>(new ObservableCollection<EUpdateMode>(EnumerateUpdateModes())); UpdateModes = new ReadOnlyObservableCollection<EUpdateMode>(new ObservableCollection<EUpdateMode>(EnumerateUpdateModes()));
Presets = new ObservableCollection<string>(EnumeratePresets());
UeGames = new ReadOnlyObservableCollection<EGame>(new ObservableCollection<EGame>(EnumerateUeGames())); UeGames = new ReadOnlyObservableCollection<EGame>(new ObservableCollection<EGame>(EnumerateUeGames()));
AssetLanguages = new ReadOnlyObservableCollection<ELanguage>(new ObservableCollection<ELanguage>(EnumerateAssetLanguages())); AssetLanguages = new ReadOnlyObservableCollection<ELanguage>(new ObservableCollection<ELanguage>(EnumerateAssetLanguages()));
AesReloads = new ReadOnlyObservableCollection<EAesReload>(new ObservableCollection<EAesReload>(EnumerateAesReloads())); AesReloads = new ReadOnlyObservableCollection<EAesReload>(new ObservableCollection<EAesReload>(EnumerateAesReloads()));
@@ -297,53 +259,6 @@ public class SettingsViewModel : ViewModel
Platforms = new ReadOnlyObservableCollection<ETexturePlatform>(new ObservableCollection<ETexturePlatform>(EnumerateUePlatforms())); Platforms = new ReadOnlyObservableCollection<ETexturePlatform>(new ObservableCollection<ETexturePlatform>(EnumerateUePlatforms()));
} }
public async Task InitPresets(string gameName)
{
await _threadWorkerView.Begin(cancellationToken =>
{
if (string.IsNullOrEmpty(gameName)) return;
_gamePreset = _apiEndpointView.FModelApi.GetGames(cancellationToken, gameName);
});
if (_gamePreset?.Versions == null) return;
foreach (var version in _gamePreset.Versions.Keys)
{
Presets.Add(version);
}
}
public void SwitchPreset(string key)
{
if (_gamePreset?.Versions == null || !_gamePreset.Versions.TryGetValue(key, out var version)) return;
SelectedUeGame = version.GameEnum.ToEnum(EGame.GAME_UE4_LATEST);
SelectedCustomVersions = new List<FCustomVersion>();
foreach (var (guid, v) in version.CustomVersions)
{
SelectedCustomVersions.Add(new FCustomVersion { Key = new FGuid(guid), Version = v });
}
SelectedOptions = new Dictionary<string, bool>();
foreach (var (k, v) in version.Options)
{
SelectedOptions[k] = v;
}
SelectedMapStructTypes = new Dictionary<string, KeyValuePair<string, string>>();
foreach (var (k, v) in version.MapStructTypes)
{
SelectedMapStructTypes[k] = v;
}
}
public void ResetPreset()
{
SelectedUeGame = _ueGameSnapshot;
SelectedCustomVersions = _customVersionsSnapshot;
SelectedOptions = _optionsSnapshot;
SelectedMapStructTypes = _mapStructTypesSnapshot;
}
public bool Save(out List<SettingsOut> whatShouldIDo) public bool Save(out List<SettingsOut> whatShouldIDo)
{ {
var restart = false; var restart = false;
@@ -369,22 +284,11 @@ public class SettingsViewModel : ViewModel
restart = true; restart = true;
UserSettings.Default.UpdateMode = SelectedUpdateMode; UserSettings.Default.UpdateMode = SelectedUpdateMode;
UserSettings.Default.Presets[_game] = SelectedPreset; UserSettings.Default.CurrentDir.UeVersion = SelectedUeGame;
UserSettings.Default.OverridedPlatform = SelectedUePlatform; UserSettings.Default.CurrentDir.TexturePlatform = SelectedUePlatform;
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(UserSettings.Default.GameDirectory)) UserSettings.Default.CurrentDir.Versioning.CustomVersions = SelectedCustomVersions;
{ UserSettings.Default.CurrentDir.Versioning.Options = SelectedOptions;
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedGame = SelectedUeGame; UserSettings.Default.CurrentDir.Versioning.MapStructTypes = SelectedMapStructTypes;
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedCustomVersions = SelectedCustomVersions;
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedOptions = SelectedOptions;
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedMapStructTypes = SelectedMapStructTypes;
}
else
{
UserSettings.Default.OverridedGame[_game] = SelectedUeGame;
UserSettings.Default.OverridedCustomVersions[_game] = SelectedCustomVersions;
UserSettings.Default.OverridedOptions[_game] = SelectedOptions;
UserSettings.Default.OverridedMapStructTypes[_game] = SelectedMapStructTypes;
}
UserSettings.Default.AssetLanguage = SelectedAssetLanguage; UserSettings.Default.AssetLanguage = SelectedAssetLanguage;
UserSettings.Default.CompressedAudioMode = SelectedCompressedAudio; UserSettings.Default.CompressedAudioMode = SelectedCompressedAudio;
@@ -404,11 +308,11 @@ public class SettingsViewModel : ViewModel
} }
private IEnumerable<EUpdateMode> EnumerateUpdateModes() => Enum.GetValues<EUpdateMode>(); private IEnumerable<EUpdateMode> EnumerateUpdateModes() => Enum.GetValues<EUpdateMode>();
private IEnumerable<string> EnumeratePresets() private IEnumerable<EGame> EnumerateUeGames()
{ => Enum.GetValues<EGame>()
yield return Constants._NO_PRESET_TRIGGER; .GroupBy(value => (int)value)
} .Select(group => group.First())
private IEnumerable<EGame> EnumerateUeGames() => Enum.GetValues<EGame>(); .OrderBy(value => (int)value == ((int)value & ~0xF));
private IEnumerable<ELanguage> EnumerateAssetLanguages() => Enum.GetValues<ELanguage>(); private IEnumerable<ELanguage> EnumerateAssetLanguages() => Enum.GetValues<ELanguage>();
private IEnumerable<EAesReload> EnumerateAesReloads() => Enum.GetValues<EAesReload>(); private IEnumerable<EAesReload> EnumerateAesReloads() => Enum.GetValues<EAesReload>();
private IEnumerable<EDiscordRpc> EnumerateDiscordRpcs() => Enum.GetValues<EDiscordRpc>(); private IEnumerable<EDiscordRpc> EnumerateDiscordRpcs() => Enum.GetValues<EDiscordRpc>();

View File

@@ -228,8 +228,8 @@ public class TabItem : ViewModel
}); });
} }
public void AddImage(UTexture2D texture, bool save, bool updateUi) public void AddImage(UTexture texture, bool save, bool updateUi)
=> AddImage(texture.Name, texture.bRenderNearestNeighbor, texture.Decode(UserSettings.Default.OverridedPlatform), save, updateUi); => AddImage(texture.Name, texture.RenderNearestNeighbor, texture.Decode(UserSettings.Default.CurrentDir.TexturePlatform), save, updateUi);
public void AddImage(string name, bool rnn, SKBitmap[] img, bool save, bool updateUi) public void AddImage(string name, bool rnn, SKBitmap[] img, bool save, bool updateUi)
{ {

View File

@@ -4,7 +4,8 @@
xmlns:local="clr-namespace:FModel" xmlns:local="clr-namespace:FModel"
xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI" xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI"
WindowStartupLocation="CenterScreen" ResizeMode="NoResize" WindowStartupLocation="CenterScreen" ResizeMode="NoResize"
IconVisibility="Collapsed" Width="500" SizeToContent="Height"> IconVisibility="Collapsed" Width="500" SizeToContent="Height"
Loaded="OnLoaded">
<adonisControls:AdonisWindow.Style> <adonisControls:AdonisWindow.Style>
<Style TargetType="adonisControls:AdonisWindow" BasedOn="{StaticResource {x:Type adonisControls:AdonisWindow}}" > <Style TargetType="adonisControls:AdonisWindow" BasedOn="{StaticResource {x:Type adonisControls:AdonisWindow}}" >
<Setter Property="Title" Value="About" /> <Setter Property="Title" Value="About" />
@@ -24,31 +25,27 @@
</StackPanel> </StackPanel>
<StackPanel HorizontalAlignment="Center"> <StackPanel HorizontalAlignment="Center">
<TextBlock Text="History" FontSize="15" FontWeight="700" Foreground="#9DA3DD" FontStretch="Expanded" /> <TextBlock Text="Description" FontSize="15" FontWeight="700" Foreground="#9DA3DD" FontStretch="Expanded" />
<TextBlock Text="&#09;&#09;&#09;&#09;" FontSize="25" FontWeight="700" Height="2" Foreground="Transparent" HorizontalAlignment="Center" /> <TextBlock Text="&#09;&#09;&#09;&#09;" FontSize="25" FontWeight="700" Height="2" Foreground="Transparent" HorizontalAlignment="Center" />
</StackPanel> </StackPanel>
<TextBlock FontSize="12" Foreground="#727272" TextWrapping="Wrap" Margin="0 0 0 30" <TextBlock FontSize="12" Foreground="#727272" TextWrapping="Wrap" Margin="0 0 0 30" Text="{Binding DescriptionLabel}" />
Text="Since the release in March 2019, we've continuously added new features and improved old ones. It started as a simple UE4 file explorer to parse packages and, as things progressed, became really popular to data-mine games and create items icons. FModel 4 is now the most complete and well-made version so far. It features dozens of settings for you to use to make FModel your own." />
<StackPanel HorizontalAlignment="Center"> <StackPanel HorizontalAlignment="Center">
<TextBlock Text="Contributors" FontSize="15" FontWeight="700" Foreground="#9DA3DD" FontStretch="Expanded" /> <TextBlock Text="Contributors" FontSize="15" FontWeight="700" Foreground="#9DA3DD" FontStretch="Expanded" />
<TextBlock Text="&#09;&#09;&#09;&#09;" FontSize="25" FontWeight="700" Height="2" Foreground="Transparent" HorizontalAlignment="Center" /> <TextBlock Text="&#09;&#09;&#09;&#09;" FontSize="25" FontWeight="700" Height="2" Foreground="Transparent" HorizontalAlignment="Center" />
</StackPanel> </StackPanel>
<TextBlock FontSize="12" Foreground="#727272" TextWrapping="Wrap" Margin="0 0 0 30" <TextBlock FontSize="12" Foreground="#727272" TextWrapping="Wrap" Margin="0 0 0 30" Text="{Binding ContributorsLabel}" />
Text="With the help of Waddlesworth, Fabian, Maiky, GMatrix, Amr, Officer, Tiger, Mang0e, and a lot more, this project continues to exist. If you're making money off of FModel consider donating to sustain FModel's continued improvements." />
<StackPanel HorizontalAlignment="Center"> <StackPanel HorizontalAlignment="Center">
<TextBlock Text="Donators" FontSize="15" FontWeight="700" Foreground="#9DA3DD" FontStretch="Expanded" /> <TextBlock Text="Donators" FontSize="15" FontWeight="700" Foreground="#9DA3DD" FontStretch="Expanded" />
<TextBlock Text="&#09;&#09;&#09;&#09;" FontSize="25" FontWeight="700" Height="2" Foreground="Transparent" HorizontalAlignment="Center" /> <TextBlock Text="&#09;&#09;&#09;&#09;" FontSize="25" FontWeight="700" Height="2" Foreground="Transparent" HorizontalAlignment="Center" />
</StackPanel> </StackPanel>
<TextBlock FontSize="12" Foreground="#727272" TextWrapping="Wrap" Margin="0 0 0 30" <TextBlock FontSize="12" Foreground="#727272" TextWrapping="Wrap" Margin="0 0 0 30" Text="{Binding DonatorsLabel}" />
Text="TheGameVlog &#x2665;, Quentin &#x2665;, Maiky &#x2665;, HYPEX &#x2665;, AnimatedAspect, Evan, VenomLeaks, Fortnite.GG, JayKey, Fevers, Netu, Laggy, s0ll, RazTracker, Mikey, kyle, Yanteh, Shiina, SexyNutella, Alexander, Jinx, koba, Tector, imatrix, LamZykoss, Frenzy Leaks, LlamaLeaks, xplore, XTigerHyperX, FunGames, WeLoveFortnite." />
<StackPanel HorizontalAlignment="Center"> <StackPanel HorizontalAlignment="Center">
<TextBlock Text="Powered by" FontSize="15" FontWeight="700" Foreground="#9DA3DD" FontStretch="Expanded" /> <TextBlock Text="Powered by" FontSize="15" FontWeight="700" Foreground="#9DA3DD" FontStretch="Expanded" />
<TextBlock Text="&#09;&#09;&#09;&#09;" FontSize="25" FontWeight="700" Height="2" Foreground="Transparent" HorizontalAlignment="Center" /> <TextBlock Text="&#09;&#09;&#09;&#09;" FontSize="25" FontWeight="700" Height="2" Foreground="Transparent" HorizontalAlignment="Center" />
</StackPanel> </StackPanel>
<TextBlock FontSize="12" Foreground="#727272" TextWrapping="Wrap" Margin="0 0 0 30" <TextBlock FontSize="12" Foreground="#727272" TextWrapping="Wrap" Margin="0 0 0 30" Text="{Binding ReferencesLabel}" />
Text="CUE4Parse, BenBot, Fortnite-Api, AdonisUI, AvalonEdit, CSCore, NVorbis, VgmStream, RestSharp, Serilog, Discord, K4os.Compression.LZ4, Ookii.Dialogs, Newtonsoft.Json, ..." />
</StackPanel> </StackPanel>
</adonisControls:AdonisWindow> </adonisControls:AdonisWindow>

View File

@@ -1,9 +1,20 @@
namespace FModel.Views; using System.Windows;
using FModel.ViewModels;
namespace FModel.Views;
public partial class About public partial class About
{ {
private readonly AboutViewModel _viewModel;
public About() public About()
{ {
DataContext = _viewModel = new AboutViewModel();
InitializeComponent(); InitializeComponent();
} }
}
private async void OnLoaded(object sender, RoutedEventArgs e)
{
await _viewModel.Initialize();
}
}

View File

@@ -80,14 +80,8 @@
<Button Grid.Column="1" MinWidth="78" Margin="0 0 12 0" IsDefault="True" IsCancel="False" <Button Grid.Column="1" MinWidth="78" Margin="0 0 12 0" IsDefault="True" IsCancel="False"
HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="OK" Click="OnClick" /> HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="OK" Click="OnClick" />
<Button Grid.Column="2" MinWidth="78" Margin="0 0 12 0" IsDefault="False" IsCancel="False" <Button Grid.Column="2" MinWidth="78" Margin="0 0 12 0" IsDefault="False" IsCancel="False"
HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="Refresh" Click="OnRefreshAes"> HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="Refresh" Click="OnRefreshAes"
<Button.Visibility> Visibility="{Binding Converter={x:Static converters:EndpointToTypeConverter.Instance}, ConverterParameter={x:Static local:EEndpointType.Aes}}">
<!-- if aes custom endpoint is enabled, make this visible -->
<MultiBinding Converter="{x:Static converters:EndpointToTypeConverter.Instance}">
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type local:Views.AesManager}}" Path="DataContext" />
<Binding Source="{x:Static local:EEndpointType.Aes}" />
</MultiBinding>
</Button.Visibility>
</Button> </Button>
</Grid> </Grid>
</Border> </Border>

View File

@@ -1,5 +1,5 @@
using System.Windows; using System.Windows;
using FModel.ViewModels; using FModel.Settings;
namespace FModel.Views; namespace FModel.Views;
@@ -9,7 +9,7 @@ public partial class CustomDir
{ {
DataContext = customDir; DataContext = customDir;
InitializeComponent(); InitializeComponent();
Activate(); Activate();
WpfSuckMyDick.Focus(); WpfSuckMyDick.Focus();
WpfSuckMyDick.SelectAll(); WpfSuckMyDick.SelectAll();
@@ -20,4 +20,4 @@ public partial class CustomDir
DialogResult = true; DialogResult = true;
Close(); Close();
} }
} }

View File

@@ -36,37 +36,46 @@
<GroupBox Grid.Row="1" adonisExtensions:LayerExtension.Layer="2" Margin="10 10 10 18" <GroupBox Grid.Row="1" adonisExtensions:LayerExtension.Layer="2" Margin="10 10 10 18"
Padding="{adonisUi:Space 0}" Background="Transparent"> Padding="{adonisUi:Space 0}" Background="Transparent">
<StackPanel> <StackPanel>
<Grid Margin="0 5"> <Grid x:Name="Hello" Margin="0 5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="10" /> <ColumnDefinition Width="10" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="Detected Game" VerticalAlignment="Center" /> <TextBlock Grid.Row="0" Grid.Column="0" Text="Detected Game" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Column="2" ItemsSource="{Binding AutoDetectedGames}" <ComboBox Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding DetectedDirectories}" Margin="0 0 0 5"
VerticalAlignment="Center" SelectedItem="{Binding SelectedDetectedGame, Mode=TwoWay}"> VerticalAlignment="Center" SelectedItem="{Binding SelectedDirectory, Mode=TwoWay}">
<ComboBox.ItemTemplate> <ComboBox.ItemTemplate>
<DataTemplate> <DataTemplate>
<TextBlock Text="{Binding GameName, Converter={x:Static converters:StringToGameConverter.Instance}}" /> <TextBlock Text="{Binding GameName, Converter={x:Static converters:StringToGameConverter.Instance}}" />
</DataTemplate> </DataTemplate>
</ComboBox.ItemTemplate> </ComboBox.ItemTemplate>
</ComboBox> </ComboBox>
</Grid>
<Grid x:Name="Hello" Margin="0 0 0 5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding SelectedDetectedGame.GameDirectory, Mode=TwoWay}" /> <TextBlock Grid.Row="1" Grid.Column="0" Text="UE Versions" VerticalAlignment="Center" Margin="0 0 0 5" />
<Button Grid.Column="2" Content="..." HorizontalAlignment="Right" Click="OnBrowseDirectories" /> <ComboBox Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding UeVersions}" Margin="0 0 0 5"
<Button Grid.Column="3" Style="{DynamicResource {x:Static adonisUi:Styles.AccentButton}}" Padding="0" VerticalAlignment="Center" SelectedItem="{Binding SelectedDirectory.UeVersion, Mode=TwoWay}">
Click="OnDeleteDirectory" Width="{Binding ActualWidth, ElementName=OkGuysButWhoFuckingAsked}" <ComboBox.ItemTemplate>
Visibility="{Binding SelectedDetectedGame.IsManual, Converter={StaticResource BoolToVisibilityConverter}}" <DataTemplate>
ToolTip="Delete Game" Margin="5 0 0 0"> <TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="2" Grid.Column="0" Text="Directory" VerticalAlignment="Center" />
<TextBox Grid.Row="2" Grid.Column="2" Text="{Binding SelectedDirectory.GameDirectory, Mode=TwoWay}" />
<Button Grid.Row="2" Grid.Column="4" Content="..." HorizontalAlignment="Right" Click="OnBrowseDirectories" />
<Button Grid.Row="2" Grid.Column="4" Style="{DynamicResource {x:Static adonisUi:Styles.AccentButton}}" Padding="0"
Click="OnDeleteDirectory" Width="{Binding ActualWidth, ElementName=OkGuysButWhoFuckingAsked}" ToolTip="Delete Game"
Visibility="{Binding SelectedDirectory.IsManual, Converter={StaticResource BoolToVisibilityConverter}}">
<Viewbox Width="16" Height="16" HorizontalAlignment="Center"> <Viewbox Width="16" Height="16" HorizontalAlignment="Center">
<Canvas Width="24" Height="24"> <Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource RemoveIcon}" /> <Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource RemoveIcon}" />

View File

@@ -29,7 +29,7 @@ public partial class DirectorySelector
var folderBrowser = new VistaFolderBrowserDialog {ShowNewFolderButton = false}; var folderBrowser = new VistaFolderBrowserDialog {ShowNewFolderButton = false};
if (folderBrowser.ShowDialog() == true) if (folderBrowser.ShowDialog() == true)
{ {
gameLauncherViewModel.AddUnknownGame(folderBrowser.SelectedPath); gameLauncherViewModel.AddUndetectedDir(folderBrowser.SelectedPath);
} }
} }
@@ -44,12 +44,12 @@ public partial class DirectorySelector
private void OnAddDirectory(object sender, RoutedEventArgs e) private void OnAddDirectory(object sender, RoutedEventArgs e)
{ {
if (DataContext is not GameSelectorViewModel gameLauncherViewModel|| if (DataContext is not GameSelectorViewModel gameLauncherViewModel ||
string.IsNullOrEmpty(HelloMyNameIsGame.Text) || string.IsNullOrEmpty(HelloMyNameIsGame.Text) ||
string.IsNullOrEmpty(HelloGameMyNameIsDirectory.Text)) string.IsNullOrEmpty(HelloGameMyNameIsDirectory.Text))
return; return;
gameLauncherViewModel.AddUnknownGame(HelloMyNameIsGame.Text, HelloGameMyNameIsDirectory.Text); gameLauncherViewModel.AddUndetectedDir(HelloMyNameIsGame.Text, HelloGameMyNameIsDirectory.Text);
HelloMyNameIsGame.Clear(); HelloMyNameIsGame.Clear();
HelloGameMyNameIsDirectory.Clear(); HelloGameMyNameIsDirectory.Clear();
} }
@@ -61,4 +61,4 @@ public partial class DirectorySelector
gameLauncherViewModel.DeleteSelectedGame(); gameLauncherViewModel.DeleteSelectedGame();
} }
} }

View File

@@ -1,126 +0,0 @@
<adonisControls:AdonisWindow x:Class="FModel.Views.MapViewer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:FModel"
xmlns:controls="clr-namespace:FModel.Views.Resources.Controls"
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI"
WindowStartupLocation="CenterScreen" IconVisibility="Collapsed" SizeToContent="Width" ResizeMode="CanMinimize" Closing="OnClosing"
Height="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenHeight}, Converter={converters:RatioConverter}, ConverterParameter='0.75'}"
MinWidth="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.50'}">
<adonisControls:AdonisWindow.Style>
<Style TargetType="adonisControls:AdonisWindow" BasedOn="{StaticResource {x:Type adonisControls:AdonisWindow}}" >
<Setter Property="Title" Value="Map Viewer" />
</Style>
</adonisControls:AdonisWindow.Style>
<adonisControls:AdonisWindow.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Resources.xaml"/>
</ResourceDictionary.MergedDictionaries>
<controls:OnTagDataTemplateSelector x:Key="TagTemplateSelector" />
<DataTemplate x:Key="BrTemplate">
<StackPanel VerticalAlignment="Center" Margin="25 0">
<CheckBox Content="Points Of Interest" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.BrPois}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" />
<CheckBox Content="Landmarks" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.BrLandmarks}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" />
<!-- <CheckBox Content="Tags Location" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.BrTagsLocation}" -->
<!-- DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" /> -->
<!-- <CheckBox Content="Patrols Path" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.BrPatrolsPath}" -->
<!-- DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" /> -->
<!-- <CheckBox Content="Upgrade Benches" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.BrUpgradeBenches}" -->
<!-- DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" /> -->
<!-- <CheckBox Content="Phonebooths" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.BrPhonebooths}" -->
<!-- DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" /> -->
<!-- <CheckBox Content="Weapon-o-matic/Mending Machines" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.BrVendingMachines}" -->
<!-- DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" /> -->
<!-- <CheckBox Content="Bounty Boards" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.BrBountyBoards}" -->
<!-- DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" /> -->
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="PrTemplate">
<StackPanel VerticalAlignment="Center" Margin="25 0">
<CheckBox Content="Landmarks" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.PrLandmarks}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" />
<CheckBox Content="Cannonball" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.PrCannonball}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" />
<CheckBox Content="Skydive" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.PrSkydive}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" />
<CheckBox Content="Shooting Targets" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.PrShootingTargets}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" />
<CheckBox Content="Parkour" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.PrParkour}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" />
<CheckBox Content="Time Trials" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.PrTimeTrials}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" />
<CheckBox Content="Vending Machines" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.PrVendingMachines}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" />
<!-- <CheckBox Content="Music Blocks" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.PrMusicBlocks}" -->
<!-- DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" /> -->
</StackPanel>
</DataTemplate>
</ResourceDictionary>
</adonisControls:AdonisWindow.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="300" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TreeView Grid.Row="0" Grid.RowSpan="3" x:Name="MapTree" SelectedItemChanged="OnSelectedItemChanged"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer3BackgroundBrush}}">
<TreeViewItem Tag="BrTemplate" IsSelected="True">
<TreeViewItem.Header>
<StackPanel Orientation="Horizontal">
<Viewbox Width="16" Height="16" HorizontalAlignment="Center" Margin="-20 4 7.5 4">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource GliderIcon}" />
</Canvas>
</Viewbox>
<TextBlock Text="Battle Royale" HorizontalAlignment="Left" VerticalAlignment="Center" />
</StackPanel>
</TreeViewItem.Header>
</TreeViewItem>
<TreeViewItem Tag="PrTemplate">
<TreeViewItem.Header>
<StackPanel Orientation="Horizontal">
<Viewbox Width="16" Height="16" HorizontalAlignment="Center" Margin="-20 4 7.5 4">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource AnchorIcon}" />
</Canvas>
</Viewbox>
<TextBlock Text="Party Royale" HorizontalAlignment="Left" VerticalAlignment="Center" />
</StackPanel>
</TreeViewItem.Header>
</TreeViewItem>
</TreeView>
<Grid Grid.Row="1" HorizontalAlignment="Stretch">
<ContentControl ContentTemplateSelector="{StaticResource TagTemplateSelector}" Content="{Binding SelectedItem.Tag, ElementName=MapTree}" />
</Grid>
<Button Grid.Row="2" Content="Save Image" Margin="5" IsEnabled="{Binding Status.IsReady}" VerticalAlignment="Bottom" Click="OnClick" />
</Grid>
<Grid Grid.Column="1" HorizontalAlignment="Stretch">
<controls:MagnifierManager.Magnifier>
<controls:Magnifier Radius="200" ZoomFactor=".4" BorderBrush="{DynamicResource {x:Static adonisUi:Brushes.AccentBrush}}" BorderThickness="1" />
</controls:MagnifierManager.Magnifier>
<TextBlock Text="Minimap is loading, please wait..." HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Image UseLayoutRounding="True" Source="{Binding MapViewer.MapImage}" HorizontalAlignment="Right" VerticalAlignment="Center"
Visibility="{Binding Status.IsReady, Converter={StaticResource BoolToVisibilityConverter}}" />
<Image UseLayoutRounding="True" Source="{Binding MapViewer.LayerImage}" HorizontalAlignment="Right" VerticalAlignment="Center"
Visibility="{Binding Status.IsReady, Converter={StaticResource BoolToVisibilityConverter}}" />
</Grid>
</Grid>
</adonisControls:AdonisWindow>

View File

@@ -1,78 +0,0 @@
using System.ComponentModel;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
using FModel.Extensions;
using FModel.Services;
using FModel.Settings;
using FModel.ViewModels;
using FModel.Views.Resources.Controls;
using Microsoft.Win32;
using Serilog;
namespace FModel.Views;
public partial class MapViewer
{
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
public MapViewer()
{
DataContext = _applicationView;
_applicationView.MapViewer.Initialize();
InitializeComponent();
}
private void OnClosing(object sender, CancelEventArgs e) => DiscordService.DiscordHandler.UpdateToSavedPresence();
private void OnClick(object sender, RoutedEventArgs e)
{
if (_applicationView.MapViewer.MapImage == null) return;
var path = Path.Combine(UserSettings.Default.TextureDirectory, "MiniMap.png");
var saveFileDialog = new SaveFileDialog
{
Title = "Save MiniMap",
FileName = "MiniMap.png",
InitialDirectory = path.SubstringBeforeLast('\\'),
Filter = "PNG Files (*.png)|*.png|All Files (*.*)|*.*"
};
if (!saveFileDialog.ShowDialog().GetValueOrDefault()) return;
path = saveFileDialog.FileName;
using var fileStream = new FileStream(path, FileMode.Create);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(_applicationView.MapViewer.GetImageToSave()));
encoder.Save(fileStream);
if (File.Exists(path))
{
Log.Information("MiniMap.png successfully saved");
FLogger.Append(ELog.Information, () => FLogger.Text("Successfully saved 'MiniMap.png'", Constants.WHITE, true));
}
else
{
Log.Error("MiniMap.png could not be saved");
FLogger.Append(ELog.Error, () => FLogger.Text("Could not save 'MiniMap.png'", Constants.WHITE, true));
}
}
private void OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
var i = 0;
foreach (var item in MapTree.Items)
{
if (item is not TreeViewItem { IsSelected: true })
{
i++;
continue;
}
_applicationView.MapViewer.MapIndex = i;
break;
}
}
}

View File

@@ -23,7 +23,7 @@
</Grid.RowDefinitions> </Grid.RowDefinitions>
<avalonEdit:TextEditor x:Name="MyAvalonEditor" Grid.Row="0" Background="{DynamicResource {x:Static adonisUi:Brushes.Layer3BackgroundBrush}}" <avalonEdit:TextEditor x:Name="MyAvalonEditor" Grid.Row="0" Background="{DynamicResource {x:Static adonisUi:Brushes.Layer3BackgroundBrush}}"
FontFamily="Consolas" FontSize="8pt" IsReadOnly="True" ShowLineNumbers="True" Foreground="#DAE5F2" /> FontFamily="Consolas" FontSize="8pt" ShowLineNumbers="True" Foreground="#DAE5F2" />
<Border Grid.Row="1" <Border Grid.Row="1"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer1BackgroundBrush}}" Background="{DynamicResource {x:Static adonisUi:Brushes.Layer1BackgroundBrush}}"

View File

@@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Windows; using System.Windows;
using System.Windows.Media; using System.Windows.Media;
using CUE4Parse.UE4.Objects.Core.Misc; using CUE4Parse.UE4.Objects.Core.Misc;
@@ -13,7 +12,6 @@ namespace FModel.Views.Resources.Controls;
public partial class DictionaryEditor public partial class DictionaryEditor
{ {
private readonly bool _enableElements;
private readonly List<FCustomVersion> _defaultCustomVersions; private readonly List<FCustomVersion> _defaultCustomVersions;
private readonly Dictionary<string, bool> _defaultOptions; private readonly Dictionary<string, bool> _defaultOptions;
private readonly Dictionary<string, KeyValuePair<string, string>> _defaultMapStructTypes; private readonly Dictionary<string, KeyValuePair<string, string>> _defaultMapStructTypes;
@@ -22,9 +20,8 @@ public partial class DictionaryEditor
public Dictionary<string, bool> Options { get; private set; } public Dictionary<string, bool> Options { get; private set; }
public Dictionary<string, KeyValuePair<string, string>> MapStructTypes { get; private set; } public Dictionary<string, KeyValuePair<string, string>> MapStructTypes { get; private set; }
public DictionaryEditor(string title, bool enableElements) public DictionaryEditor(string title)
{ {
_enableElements = enableElements;
_defaultCustomVersions = new List<FCustomVersion> { new() { Key = new FGuid(), Version = 0 } }; _defaultCustomVersions = new List<FCustomVersion> { new() { Key = new FGuid(), Version = 0 } };
_defaultOptions = new Dictionary<string, bool> { { "key1", true }, { "key2", false } }; _defaultOptions = new Dictionary<string, bool> { { "key1", true }, { "key2", false } };
_defaultMapStructTypes = new Dictionary<string, KeyValuePair<string, string>> { { "MapName", new KeyValuePair<string, string>("KeyType", "ValueType") } }; _defaultMapStructTypes = new Dictionary<string, KeyValuePair<string, string>> { { "MapName", new KeyValuePair<string, string>("KeyType", "ValueType") } };
@@ -32,11 +29,10 @@ public partial class DictionaryEditor
InitializeComponent(); InitializeComponent();
Title = title; Title = title;
MyAvalonEditor.IsReadOnly = !_enableElements;
MyAvalonEditor.SyntaxHighlighting = AvalonExtensions.HighlighterSelector(""); MyAvalonEditor.SyntaxHighlighting = AvalonExtensions.HighlighterSelector("");
} }
public DictionaryEditor(List<FCustomVersion> customVersions, string title, bool enableElements) : this(title, enableElements) public DictionaryEditor(IList<FCustomVersion> customVersions, string title) : this(title)
{ {
MyAvalonEditor.Document = new TextDocument MyAvalonEditor.Document = new TextDocument
{ {
@@ -44,7 +40,7 @@ public partial class DictionaryEditor
}; };
} }
public DictionaryEditor(Dictionary<string, bool> options, string title, bool enableElements) : this(title, enableElements) public DictionaryEditor(IDictionary<string, bool> options, string title) : this(title)
{ {
MyAvalonEditor.Document = new TextDocument MyAvalonEditor.Document = new TextDocument
{ {
@@ -52,7 +48,7 @@ public partial class DictionaryEditor
}; };
} }
public DictionaryEditor(Dictionary<string, KeyValuePair<string, string>> options, string title, bool enableElements) : this(title, enableElements) public DictionaryEditor(IDictionary<string, KeyValuePair<string, string>> options, string title) : this(title)
{ {
MyAvalonEditor.Document = new TextDocument MyAvalonEditor.Document = new TextDocument
{ {
@@ -62,13 +58,6 @@ public partial class DictionaryEditor
private void OnClick(object sender, RoutedEventArgs e) private void OnClick(object sender, RoutedEventArgs e)
{ {
if (!_enableElements)
{
DialogResult = false;
Close();
return;
}
try try
{ {
switch (Title) switch (Title)
@@ -85,7 +74,7 @@ public partial class DictionaryEditor
DialogResult = true; DialogResult = true;
Close(); Close();
break; break;
case "MapStructTypes": case "Versioning Configuration (MapStructTypes)":
MapStructTypes = JsonConvert.DeserializeObject<Dictionary<string, KeyValuePair<string, string>>>(MyAvalonEditor.Document.Text); MapStructTypes = JsonConvert.DeserializeObject<Dictionary<string, KeyValuePair<string, string>>>(MyAvalonEditor.Document.Text);
// DialogResult = !Options.SequenceEqual(_defaultOptions); // DialogResult = !Options.SequenceEqual(_defaultOptions);
DialogResult = true; DialogResult = true;
@@ -104,9 +93,6 @@ public partial class DictionaryEditor
private void OnReset(object sender, RoutedEventArgs e) private void OnReset(object sender, RoutedEventArgs e)
{ {
if (!_enableElements)
return;
MyAvalonEditor.Document = Title switch MyAvalonEditor.Document = Title switch
{ {
"Versioning Configuration (Custom Versions)" => new TextDocument "Versioning Configuration (Custom Versions)" => new TextDocument
@@ -117,7 +103,7 @@ public partial class DictionaryEditor
{ {
Text = JsonConvert.SerializeObject(_defaultOptions, Formatting.Indented) Text = JsonConvert.SerializeObject(_defaultOptions, Formatting.Indented)
}, },
"MapStructTypes" => new TextDocument "Versioning Configuration (MapStructTypes)" => new TextDocument
{ {
Text = JsonConvert.SerializeObject(_defaultMapStructTypes, Formatting.Indented) Text = JsonConvert.SerializeObject(_defaultMapStructTypes, Formatting.Indented)
}, },

View File

@@ -2,12 +2,10 @@
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using FModel.Extensions; using FModel.Extensions;
using FModel.Framework;
using FModel.Services; using FModel.Services;
using FModel.Settings;
using ICSharpCode.AvalonEdit.Document; using ICSharpCode.AvalonEdit.Document;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using RestSharp;
namespace FModel.Views.Resources.Controls; namespace FModel.Views.Resources.Controls;
@@ -16,7 +14,7 @@ public partial class EndpointEditor
private readonly EEndpointType _type; private readonly EEndpointType _type;
private bool _isTested; private bool _isTested;
public EndpointEditor(FEndpoint endpoint, string title, EEndpointType type) public EndpointEditor(EndpointSettings endpoint, string title, EEndpointType type)
{ {
DataContext = endpoint; DataContext = endpoint;
_type = type; _type = type;
@@ -52,13 +50,13 @@ public partial class EndpointEditor
private void OnClick(object sender, RoutedEventArgs e) private void OnClick(object sender, RoutedEventArgs e)
{ {
DialogResult = _isTested && DataContext is FEndpoint { IsValid: true }; DialogResult = _isTested && DataContext is EndpointSettings { IsValid: true };
Close(); Close();
} }
private async void OnSend(object sender, RoutedEventArgs e) private async void OnSend(object sender, RoutedEventArgs e)
{ {
if (DataContext is not FEndpoint endpoint) return; if (DataContext is not EndpointSettings endpoint) return;
var body = await ApplicationService.ApiEndpointView.DynamicApi.GetRequestBody(default, endpoint.Url).ConfigureAwait(false); var body = await ApplicationService.ApiEndpointView.DynamicApi.GetRequestBody(default, endpoint.Url).ConfigureAwait(false);
Application.Current.Dispatcher.Invoke(delegate Application.Current.Dispatcher.Invoke(delegate
@@ -70,7 +68,7 @@ public partial class EndpointEditor
private void OnTest(object sender, RoutedEventArgs e) private void OnTest(object sender, RoutedEventArgs e)
{ {
if (DataContext is not FEndpoint endpoint) return; if (DataContext is not EndpointSettings endpoint) return;
endpoint.TryValidate(ApplicationService.ApiEndpointView.DynamicApi, _type, out var response); endpoint.TryValidate(ApplicationService.ApiEndpointView.DynamicApi, _type, out var response);
_isTested = true; _isTested = true;
@@ -82,7 +80,7 @@ public partial class EndpointEditor
private void OnTextChanged(object sender, TextChangedEventArgs e) private void OnTextChanged(object sender, TextChangedEventArgs e)
{ {
if (sender is not TextBox { IsLoaded: true } || if (sender is not TextBox { IsLoaded: true } ||
DataContext is not FEndpoint endpoint) return; DataContext is not EndpointSettings endpoint) return;
endpoint.IsValid = false; endpoint.IsValid = false;
} }

View File

@@ -3,21 +3,18 @@ using System.Globalization;
using System.Windows; using System.Windows;
using System.Windows.Data; using System.Windows.Data;
using FModel.Settings; using FModel.Settings;
using FModel.ViewModels;
namespace FModel.Views.Resources.Converters; namespace FModel.Views.Resources.Converters;
public class EndpointToTypeConverter : IMultiValueConverter public class EndpointToTypeConverter : IValueConverter
{ {
public static readonly EndpointToTypeConverter Instance = new(); public static readonly EndpointToTypeConverter Instance = new();
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{ {
if (values[0] is not ApplicationViewModel viewModel || if (parameter is not EEndpointType type) throw new NotImplementedException();
values[1] is not EEndpointType type)
return false;
var isValid = UserSettings.IsEndpointValid(viewModel.CUE4Parse.Game, type, out _); var isValid = UserSettings.IsEndpointValid(type, out _);
return targetType switch return targetType switch
{ {
not null when targetType == typeof(Visibility) => isValid ? Visibility.Visible : Visibility.Collapsed, not null when targetType == typeof(Visibility) => isValid ? Visibility.Visible : Visibility.Collapsed,
@@ -25,7 +22,7 @@ public class EndpointToTypeConverter : IMultiValueConverter
}; };
} }
public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture) public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }

View File

@@ -11,31 +11,24 @@ public class StringToGameConverter : IValueConverter
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{ {
var ret = value switch return value switch
{ {
"Newt" => FGame.g3, "Pewee" => "Rogue Company",
"Nebula" => FGame.BendGame, "Rosemallow" => "The Outer Worlds",
"Fortnite" => FGame.FortniteGame, "Catnip" => "Borderlands 3",
"VALORANT" => FGame.ShooterGame, "AzaleaAlpha" => "The Cycle",
"Pewee" => FGame.RogueCompany, "shoebill" => "Star Wars: Jedi Fallen Order",
"Catnip" => FGame.OakGame, "Snoek" => "State Of Decay 2",
"AzaleaAlpha" => FGame.Prospect, "711c5e95dc094ca58e5f16bd48e751d6" => "MultiVersus",
"Snoek" => FGame.StateOfDecay2, "9361c8c6d2f34b42b5f2f61093eedf48" => "PLAYERUNKNOWN'S BATTLEGROUNDS",
"Rosemallow" => FGame.Indiana, 381210 => "Dead By Daylight",
"WorldExplorersLive" => FGame.WorldExplorers, 578080 => "PLAYERUNKNOWN'S BATTLEGROUNDS",
"MinecraftDungeons" => FGame.Dungeons, 1172380 => "Star Wars: Jedi Fallen Order",
"shoebill" => FGame.SwGame, 677620 => "Splitgate",
"a99769d95d8f400baad1f67ab5dfe508" => FGame.Platform, 1172620 => "Sea of Thieves",
"711c5e95dc094ca58e5f16bd48e751d6" => FGame.MultiVersus, 1665460 => "eFootball 2023",
"9361c8c6d2f34b42b5f2f61093eedf48" => FGame.TslGame, _ => value
381210 => FGame.DeadByDaylight,
578080 => FGame.TslGame,
677620 => FGame.PortalWars,
1172620 => FGame.Athena,
1665460 => FGame.eFootball,
_ => FGame.Unknown
}; };
return ret == FGame.Unknown ? value : ret.GetDescription();
} }
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)

View File

@@ -655,47 +655,50 @@
<GridSplitter Grid.Column="1" Width="4" VerticalAlignment="Stretch" ResizeDirection="Columns" KeyboardIncrement="0" <GridSplitter Grid.Column="1" Width="4" VerticalAlignment="Stretch" ResizeDirection="Columns" KeyboardIncrement="0"
Visibility="{Binding SelectedItem.HasImage, ElementName=TabControlName, Converter={StaticResource BoolToVisibilityConverter}}" Visibility="{Binding SelectedItem.HasImage, ElementName=TabControlName, Converter={StaticResource BoolToVisibilityConverter}}"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer3BackgroundBrush}}" /> Background="{DynamicResource {x:Static adonisUi:Brushes.Layer3BackgroundBrush}}" />
<Image Grid.Column="2" VerticalAlignment="Center" HorizontalAlignment="Center" Stretch="Uniform" <Border Grid.Column="2" BorderBrush="#3b3d4a" BorderThickness="1" HorizontalAlignment="Center" VerticalAlignment="Center"
Visibility="{Binding SelectedItem.HasImage, ElementName=TabControlName, Converter={StaticResource BoolToVisibilityConverter}}" Visibility="{Binding SelectedItem.HasImage, ElementName=TabControlName, Converter={StaticResource BoolToVisibilityConverter}}">
Source="{Binding SelectedItem.SelectedImage.Image, ElementName=TabControlName}" RenderOptions.BitmapScalingMode="{Binding SelectedItem.SelectedImage.RenderNearestNeighbor, ElementName=TabControlName, Converter={x:Static converters:BoolToRenderModeConverter.Instance}}"> <Image VerticalAlignment="Center" HorizontalAlignment="Center" Stretch="Uniform"
<Image.InputBindings> Visibility="{Binding SelectedItem.HasImage, ElementName=TabControlName, Converter={StaticResource BoolToVisibilityConverter}}"
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding SelectedItem.ImageCommand, ElementName=TabControlName}" CommandParameter="Open"/> Source="{Binding SelectedItem.SelectedImage.Image, ElementName=TabControlName}" RenderOptions.BitmapScalingMode="{Binding SelectedItem.SelectedImage.RenderNearestNeighbor, ElementName=TabControlName, Converter={x:Static converters:BoolToRenderModeConverter.Instance}}">
</Image.InputBindings> <Image.InputBindings>
<Image.ContextMenu> <MouseBinding MouseAction="LeftDoubleClick" Command="{Binding SelectedItem.ImageCommand, ElementName=TabControlName}" CommandParameter="Open"/>
<ContextMenu DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}"> </Image.InputBindings>
<MenuItem Header="Disable Alpha Channel" IsCheckable="True" IsChecked="{Binding DataContext.SelectedImage.NoAlpha}" /> <Image.ContextMenu>
<MenuItem Header="Open Image" Command="{Binding DataContext.ImageCommand}" CommandParameter="Open"> <ContextMenu DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}">
<MenuItem.Icon> <MenuItem Header="Disable Alpha Channel" IsCheckable="True" IsChecked="{Binding DataContext.SelectedImage.NoAlpha}" />
<Viewbox Width="16" Height="16"> <MenuItem Header="Open Image" Command="{Binding DataContext.ImageCommand}" CommandParameter="Open">
<Canvas Width="24" Height="24"> <MenuItem.Icon>
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M20,4H4C2.9,4,2,4.9,2,6v12c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V6C22,4.9,21.1,4,20,4z M20,18H4V6h16V18z" /> <Viewbox Width="16" Height="16">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M6,12c0.55,0,1-0.45,1-1V9h2c0.55,0,1-0.45,1-1S9.55,7,9,7H6C5.45,7,5,7.45,5,8v3C5,11.55,5.45,12,6,12z" /> <Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M15,17h3c0.55,0,1-0.45,1-1v-3c0-0.55-0.45-1-1-1s-1,0.45-1,1v2h-2c-0.55,0-1,0.45-1,1S14.45,17,15,17z" /> <Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M20,4H4C2.9,4,2,4.9,2,6v12c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V6C22,4.9,21.1,4,20,4z M20,18H4V6h16V18z" />
</Canvas> <Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M6,12c0.55,0,1-0.45,1-1V9h2c0.55,0,1-0.45,1-1S9.55,7,9,7H6C5.45,7,5,7.45,5,8v3C5,11.55,5.45,12,6,12z" />
</Viewbox> <Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M15,17h3c0.55,0,1-0.45,1-1v-3c0-0.55-0.45-1-1-1s-1,0.45-1,1v2h-2c-0.55,0-1,0.45-1,1S14.45,17,15,17z" />
</MenuItem.Icon> </Canvas>
</MenuItem> </Viewbox>
<MenuItem Header="Copy Image" Command="{Binding DataContext.ImageCommand}" CommandParameter="Copy"> </MenuItem.Icon>
<MenuItem.Icon> </MenuItem>
<Viewbox Width="16" Height="16"> <MenuItem Header="Copy Image" Command="{Binding DataContext.ImageCommand}" CommandParameter="Copy">
<Canvas Width="24" Height="24"> <MenuItem.Icon>
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource CopyIcon}" /> <Viewbox Width="16" Height="16">
</Canvas> <Canvas Width="24" Height="24">
</Viewbox> <Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource CopyIcon}" />
</MenuItem.Icon> </Canvas>
</MenuItem> </Viewbox>
<MenuItem Header="Save Image" Command="{Binding DataContext.ImageCommand}" CommandParameter="Save"> </MenuItem.Icon>
<MenuItem.Icon> </MenuItem>
<Viewbox Width="16" Height="16"> <MenuItem Header="Save Image" Command="{Binding DataContext.ImageCommand}" CommandParameter="Save">
<Canvas Width="24" Height="24"> <MenuItem.Icon>
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SaveIcon}" /> <Viewbox Width="16" Height="16">
</Canvas> <Canvas Width="24" Height="24">
</Viewbox> <Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SaveIcon}" />
</MenuItem.Icon> </Canvas>
</MenuItem> </Viewbox>
</ContextMenu> </MenuItem.Icon>
</Image.ContextMenu> </MenuItem>
</Image> </ContextMenu>
</Image.ContextMenu>
</Image>
</Border>
<TextBlock Grid.Column="2" VerticalAlignment="Bottom" HorizontalAlignment="Center" <TextBlock Grid.Column="2" VerticalAlignment="Bottom" HorizontalAlignment="Center"
Visibility="{Binding SelectedItem.HasMultipleImages, ElementName=TabControlName, Converter={StaticResource BoolToVisibilityConverter}}" Visibility="{Binding SelectedItem.HasMultipleImages, ElementName=TabControlName, Converter={StaticResource BoolToVisibilityConverter}}"
Text="{Binding SelectedItem.Page, ElementName=TabControlName}" /> Text="{Binding SelectedItem.Page, ElementName=TabControlName}" />

View File

@@ -7,7 +7,7 @@
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI" xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI" xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI"
xmlns:adonisExtensions="clr-namespace:AdonisUI.Extensions;assembly=AdonisUI" xmlns:adonisExtensions="clr-namespace:AdonisUI.Extensions;assembly=AdonisUI"
WindowStartupLocation="CenterScreen" ResizeMode="NoResize" IconVisibility="Collapsed" SizeToContent="Height" Loaded="OnLoaded" WindowStartupLocation="CenterScreen" ResizeMode="NoResize" IconVisibility="Collapsed" SizeToContent="Height"
MinHeight="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenHeight}, Converter={converters:RatioConverter}, ConverterParameter='0.10'}" MinHeight="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenHeight}, Converter={converters:RatioConverter}, ConverterParameter='0.10'}"
Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.35'}"> Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.35'}">
<adonisControls:AdonisWindow.Style> <adonisControls:AdonisWindow.Style>
@@ -55,7 +55,7 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Output Directory *" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Directory where log files, backups and other do-not-delete files will be put in." /> <TextBlock Grid.Row="0" Grid.Column="0" Text="Output Directory *" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Directory where log files, backups and other do-not-delete files will be put in." />
<TextBox x:Name="ImStockBro" Grid.Row="0" Grid.Column="2" Text="{Binding OutputDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0 0 0 5" /> <TextBox x:Name="ImJackedBro" Grid.Row="0" Grid.Column="2" Text="{Binding OutputDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0 0 0 5" />
<Button Grid.Row="0" Grid.Column="4" Content="..." HorizontalAlignment="Right" Click="OnBrowseOutput" Margin="0 0 0 5" /> <Button Grid.Row="0" Grid.Column="4" Content="..." HorizontalAlignment="Right" Click="OnBrowseOutput" Margin="0 0 0 5" />
<CheckBox Grid.Row="0" Grid.Column="6" Margin="5 0 0 5" ToolTip="Customize the directory of more output folders" <CheckBox Grid.Row="0" Grid.Column="6" Margin="5 0 0 5" ToolTip="Customize the directory of more output folders"
@@ -73,7 +73,7 @@
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="10" /> <ColumnDefinition Width="10" />
<ColumnDefinition Width="{Binding ActualWidth, ElementName=ImStockBro}" /> <ColumnDefinition Width="{Binding ActualWidth, ElementName=ImJackedBro}" />
<ColumnDefinition Width="5" /> <ColumnDefinition Width="5" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
@@ -95,12 +95,8 @@
<Button Grid.Row="3" Grid.Column="4" Content="..." HorizontalAlignment="Right" Click="OnBrowseAudio" Margin="0 0 0 5" /> <Button Grid.Row="3" Grid.Column="4" Content="..." HorizontalAlignment="Right" Click="OnBrowseAudio" Margin="0 0 0 5" />
</Grid> </Grid>
<TextBlock Grid.Row="2" Grid.Column="0" Text="Game's Archive Directory *" VerticalAlignment="Center" Margin="0 0 0 5" /> <TextBlock Grid.Row="2" Grid.Column="0" Text="Update Mode" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Receive updates each time a new release is pushed to GitHub&#10;Receive updates each time a new commit is pushed to GitHub" />
<TextBox Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="3" Text="{Binding GameDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0 0 0 5" /> <ComboBox Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.UpdateModes}" SelectedItem="{Binding SettingsView.SelectedUpdateMode, Mode=TwoWay}"
<Button Grid.Row="2" Grid.Column="6" Content="..." HorizontalAlignment="Right" Click="OnBrowseDirectories" Margin="0 0 0 5" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="Update Mode" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Receive updates each time a new release is pushed to GitHub&#10;Receive updates each time a new commit is pushed to GitHub" />
<ComboBox Grid.Row="3" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.UpdateModes}" SelectedItem="{Binding SettingsView.SelectedUpdateMode, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5"> DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
<ComboBox.ItemTemplate> <ComboBox.ItemTemplate>
<DataTemplate> <DataTemplate>
@@ -109,18 +105,8 @@
</ComboBox.ItemTemplate> </ComboBox.ItemTemplate>
</ComboBox> </ComboBox>
<TextBlock Grid.Row="4" Grid.Column="0" Text="Packages Language" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Language used and shown when parsing packages" /> <TextBlock Grid.Row="3" Grid.Column="0" Text="Discord Rich Presence" VerticalAlignment="Center" />
<ComboBox Grid.Row="4" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.AssetLanguages}" SelectedItem="{Binding SettingsView.SelectedAssetLanguage, Mode=TwoWay}" <ComboBox Grid.Row="3" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.DiscordRpcs}" SelectedItem="{Binding SettingsView.SelectedDiscordRpc, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="5" Grid.Column="0" Text="Discord Rich Presence" VerticalAlignment="Center" />
<ComboBox Grid.Row="5" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.DiscordRpcs}" SelectedItem="{Binding SettingsView.SelectedDiscordRpc, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"> DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}">
<ComboBox.ItemTemplate> <ComboBox.ItemTemplate>
<DataTemplate> <DataTemplate>
@@ -129,16 +115,15 @@
</ComboBox.ItemTemplate> </ComboBox.ItemTemplate>
</ComboBox> </ComboBox>
<Separator Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="7" Style="{StaticResource CustomSeparator}" Tag="ADVANCED"></Separator> <Separator Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="7" Style="{StaticResource CustomSeparator}" Tag="GAME"></Separator>
<TextBlock Grid.Row="7" Grid.Column="0" Text="Presets" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Use a fine tuned preset for the game you're trying to load and its version" /> <TextBlock Grid.Row="5" Grid.Column="0" Text="Archive Directory *" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="7" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.Presets}" SelectedItem="{Binding SettingsView.SelectedPreset, Mode=TwoWay}" <TextBox Grid.Row="5" Grid.Column="2" Grid.ColumnSpan="3" Text="{Binding GameDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0 0 0 5" />
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" SelectionChanged="OnSelectionChanged" Margin="0 0 0 5"> <Button Grid.Row="5" Grid.Column="6" Content="..." HorizontalAlignment="Right" Click="OnBrowseDirectories" Margin="0 0 0 5" />
</ComboBox>
<TextBlock Grid.Row="8" Grid.Column="0" Text="UE Versions *" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Override the UE version to use when parsing packages" /> <TextBlock Grid.Row="6" Grid.Column="0" Text="UE Versions *" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Override the UE version to use when parsing packages" />
<ComboBox Grid.Row="8" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.UeGames}" SelectedItem="{Binding SettingsView.SelectedUeGame, Mode=TwoWay}" <ComboBox Grid.Row="6" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.UeGames}" SelectedItem="{Binding SettingsView.SelectedUeGame, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" IsEnabled="{Binding SettingsView.EnableElements}" DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
Margin="0 0 0 5"> Margin="0 0 0 5">
<ComboBox.ItemTemplate> <ComboBox.ItemTemplate>
<DataTemplate> <DataTemplate>
@@ -147,8 +132,45 @@
</ComboBox.ItemTemplate> </ComboBox.ItemTemplate>
</ComboBox> </ComboBox>
<TextBlock Grid.Row="9" Grid.Column="0" Text="Versioning Configuration *" VerticalAlignment="Center" Margin="0 0 0 5" /> <TextBlock Grid.Row="7" Grid.Column="0" Text="Texture Platform *" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Override the game's platform to ensure texture compatibility" />
<Grid Grid.Row="9" Grid.Column="2" Grid.ColumnSpan="5" Margin="0 0 0 5"> <ComboBox Grid.Row="7" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.Platforms}" SelectedItem="{Binding SettingsView.SelectedUePlatform, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
Margin="0 0 0 5">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="8" Grid.Column="0" Text="Compressed Audio" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="What to do when encountering a compressed audio file" />
<ComboBox Grid.Row="8" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.CompressedAudios}" SelectedItem="{Binding SettingsView.SelectedCompressedAudio, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="9" Grid.Column="0" Text="Packages Language" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Language used and shown when parsing packages" />
<ComboBox Grid.Row="9" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.AssetLanguages}" SelectedItem="{Binding SettingsView.SelectedAssetLanguage, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="10" Grid.Column="0" Text="Keep Directory Structure" VerticalAlignment="Center" Margin="0 5 0 5" ToolTip="Auto-save packages following their game directory" />
<CheckBox Grid.Row="10" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding KeepDirectoryStructure, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 0"/>
<Separator Grid.Row="11" Grid.Column="0" Grid.ColumnSpan="7" Style="{StaticResource CustomSeparator}" Tag="ADVANCED"></Separator>
<TextBlock Grid.Row="12" Grid.Column="0" Text="Versioning Configuration *" VerticalAlignment="Center" Margin="0 0 0 5" />
<Grid Grid.Row="12" Grid.Column="2" Grid.ColumnSpan="5" Margin="0 0 0 5">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="5" /> <ColumnDefinition Width="5" />
@@ -162,40 +184,6 @@
<Button Grid.Column="4" Content="MapStructTypes" Click="OpenMapStructTypes" /> <Button Grid.Column="4" Content="MapStructTypes" Click="OpenMapStructTypes" />
</Grid> </Grid>
<TextBlock Grid.Row="10" Grid.Column="0" Text="Texture Platform *" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Override the game's platform to ensure texture compatibility" />
<ComboBox Grid.Row="10" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.Platforms}" SelectedItem="{Binding SettingsView.SelectedUePlatform, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" IsEnabled="{Binding SettingsView.EnableElements}"
Margin="0 0 0 5">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="11" Grid.Column="0" Text="Compressed Audio" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="What to do when encountering a compressed audio file" />
<ComboBox Grid.Row="11" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.CompressedAudios}" SelectedItem="{Binding SettingsView.SelectedCompressedAudio, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="12" Grid.Column="0" Text="Endpoint Configuration" VerticalAlignment="Center" Margin="0 0 0 5" />
<Grid Grid.Row="12" Grid.Column="2" Grid.ColumnSpan="5" Margin="0 0 0 5"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Content="AES" Click="OpenAesEndpoint" />
<Button Grid.Column="2" Content="Mapping" Click="OpenMappingEndpoint" />
</Grid>
<TextBlock Grid.Row="13" Grid.Column="0" Text="AES Reload at Launch" VerticalAlignment="Center" Margin="0 0 0 5" <TextBlock Grid.Row="13" Grid.Column="0" Text="AES Reload at Launch" VerticalAlignment="Center" Margin="0 0 0 5"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
Visibility="{Binding SettingsView.AesEndpoint.IsValid, Converter={StaticResource BoolToVisibilityConverter}}" /> Visibility="{Binding SettingsView.AesEndpoint.IsValid, Converter={StaticResource BoolToVisibilityConverter}}" />
@@ -210,29 +198,38 @@
</ComboBox.ItemTemplate> </ComboBox.ItemTemplate>
</ComboBox> </ComboBox>
<TextBlock Grid.Row="14" Grid.Column="0" Text="Serialize Script Bytecode" VerticalAlignment="Center" Margin="0 0 0 5" /> <TextBlock Grid.Row="14" Grid.Column="0" Text="Endpoint Configuration" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="14" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}" <Grid Grid.Row="14" Grid.Column="2" Grid.ColumnSpan="5" Margin="0 0 0 5"
IsChecked="{Binding ReadScriptData, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10"/> DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="15" Grid.Column="0" Text="Keep Directory Structure" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Auto-save packages following their game directory" /> <Button Grid.Column="0" Content="AES" Click="OpenAesEndpoint" />
<CheckBox Grid.Row="15" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}" <Button Grid.Column="2" Content="Mapping" Click="OpenMappingEndpoint" />
IsChecked="{Binding KeepDirectoryStructure, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10"/> </Grid>
<TextBlock Grid.Row="16" Grid.Column="0" Text="Local Mapping File" VerticalAlignment="Center" Margin="0 0 0 5" /> <TextBlock Grid.Row="15" Grid.Column="0" Text="Local Mapping File" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="16" Grid.Column="2" Margin="0 5 0 10" <CheckBox Grid.Row="15" Grid.Column="2" Margin="0 5 0 10"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding SettingsView.MappingEndpoint.Overwrite, Mode=TwoWay}" /> IsChecked="{Binding SettingsView.MappingEndpoint.Overwrite, Mode=TwoWay}" />
<TextBlock Grid.Row="17" Grid.Column="0" Text="Mapping File Path" VerticalAlignment="Center" Margin="0 0 0 5" <TextBlock Grid.Row="16" Grid.Column="0" Text="Mapping File Path" VerticalAlignment="Center" Margin="0 0 0 5"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
Visibility="{Binding SettingsView.MappingEndpoint.Overwrite, Converter={StaticResource BoolToVisibilityConverter}}" /> Visibility="{Binding SettingsView.MappingEndpoint.Overwrite, Converter={StaticResource BoolToVisibilityConverter}}" />
<TextBox Grid.Row="17" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 0 0 5" Text="{Binding SettingsView.MappingEndpoint.FilePath, Mode=TwoWay}" <TextBox Grid.Row="16" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 0 0 5" Text="{Binding SettingsView.MappingEndpoint.FilePath, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
Visibility="{Binding SettingsView.MappingEndpoint.Overwrite, Converter={StaticResource BoolToVisibilityConverter}}" /> Visibility="{Binding SettingsView.MappingEndpoint.Overwrite, Converter={StaticResource BoolToVisibilityConverter}}" />
<Button Grid.Row="17" Grid.Column="6" Content="..." HorizontalAlignment="Right" Click="OnBrowseMappings" Margin="0 0 0 5" <Button Grid.Row="16" Grid.Column="6" Content="..." HorizontalAlignment="Right" Click="OnBrowseMappings" Margin="0 0 0 5"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
Visibility="{Binding SettingsView.MappingEndpoint.Overwrite, Converter={StaticResource BoolToVisibilityConverter}}" /> Visibility="{Binding SettingsView.MappingEndpoint.Overwrite, Converter={StaticResource BoolToVisibilityConverter}}" />
<TextBlock Grid.Row="17" Grid.Column="0" Text="Serialize Script Bytecode" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="17" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding ReadScriptData, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10"/>
</Grid> </Grid>
</DataTemplate> </DataTemplate>
<DataTemplate x:Key="CreatorTemplate"> <DataTemplate x:Key="CreatorTemplate">
@@ -318,6 +315,7 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
@@ -389,20 +387,25 @@
IsChecked="{Binding PreviewWorlds, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" IsChecked="{Binding PreviewWorlds, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/> Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
<TextBlock Grid.Row="10" Grid.Column="0" Text="Save Morph Targets in Meshes" VerticalAlignment="Center" Margin="0 0 0 5" /> <TextBlock Grid.Row="10" Grid.Column="0" Text="Save Materials Embedded within Meshes" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="10" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}" <CheckBox Grid.Row="10" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding SaveEmbeddedMaterials, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
<TextBlock Grid.Row="11" Grid.Column="0" Text="Save Morph Targets in Meshes" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="11" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding SaveMorphTargets, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" IsChecked="{Binding SaveMorphTargets, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/> Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
<TextBlock Grid.Row="11" Grid.Column="0" Text="Handle Skeletons as Empty Meshes" VerticalAlignment="Center" /> <TextBlock Grid.Row="12" Grid.Column="0" Text="Handle Skeletons as Empty Meshes" VerticalAlignment="Center" />
<CheckBox Grid.Row="11" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}" <CheckBox Grid.Row="12" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding SaveSkeletonAsMesh, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" IsChecked="{Binding SaveSkeletonAsMesh, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 0"/> Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 0"/>
<Separator Grid.Row="12" Grid.Column="0" Grid.ColumnSpan="5" Style="{StaticResource CustomSeparator}" /> <Separator Grid.Row="13" Grid.Column="0" Grid.ColumnSpan="5" Style="{StaticResource CustomSeparator}" />
<TextBlock Grid.Row="13" Grid.Column="0" Text="Material Format" VerticalAlignment="Center" Margin="0 0 0 5" /> <TextBlock Grid.Row="14" Grid.Column="0" Text="Material Format" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="13" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.MaterialExportFormats}" SelectedItem="{Binding SettingsView.SelectedMaterialExportFormat, Mode=TwoWay}" <ComboBox Grid.Row="14" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.MaterialExportFormats}" SelectedItem="{Binding SettingsView.SelectedMaterialExportFormat, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5"> DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
<ComboBox.ItemTemplate> <ComboBox.ItemTemplate>
<DataTemplate> <DataTemplate>
@@ -411,8 +414,8 @@
</ComboBox.ItemTemplate> </ComboBox.ItemTemplate>
</ComboBox> </ComboBox>
<TextBlock Grid.Row="14" Grid.Column="0" Text="Texture Format" VerticalAlignment="Center" Margin="0 0 0 5" /> <TextBlock Grid.Row="15" Grid.Column="0" Text="Texture Format" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="14" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.TextureExportFormats}" SelectedItem="{Binding SettingsView.SelectedTextureExportFormat, Mode=TwoWay}" <ComboBox Grid.Row="15" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.TextureExportFormats}" SelectedItem="{Binding SettingsView.SelectedTextureExportFormat, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5"> DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
<ComboBox.ItemTemplate> <ComboBox.ItemTemplate>
<DataTemplate> <DataTemplate>
@@ -524,7 +527,7 @@
<Style TargetType="TreeViewItem" BasedOn="{StaticResource TreeViewItemStyle}"> <Style TargetType="TreeViewItem" BasedOn="{StaticResource TreeViewItemStyle}">
<Setter Property="Visibility" Value="Collapsed"/> <Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers> <Style.Triggers>
<DataTrigger Binding="{Binding CUE4Parse.Game}" Value="{x:Static local:FGame.FortniteGame}"> <DataTrigger Binding="{Binding CUE4Parse.InternalGameName, Converter={x:Static converters:CaseInsensitiveStringEqualsConverter.Instance}, ConverterParameter='FortniteGame'}" Value="True">
<Setter Property="Visibility" Value="Visible" /> <Setter Property="Visibility" Value="Visible" />
</DataTrigger> </DataTrigger>
</Style.Triggers> </Style.Triggers>

View File

@@ -30,11 +30,6 @@ public partial class SettingsView
} }
} }
private async void OnLoaded(object sender, RoutedEventArgs e)
{
await _applicationView.SettingsView.InitPresets(_applicationView.CUE4Parse.Provider.GameName);
}
private async void OnClick(object sender, RoutedEventArgs e) private async void OnClick(object sender, RoutedEventArgs e)
{ {
var restart = _applicationView.SettingsView.Save(out var whatShouldIDo); var restart = _applicationView.SettingsView.Save(out var whatShouldIDo);
@@ -154,19 +149,9 @@ public partial class SettingsView
} }
} }
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (sender is not ComboBox { SelectedItem: string s }) return;
if (s == Constants._NO_PRESET_TRIGGER) _applicationView.SettingsView.ResetPreset();
else _applicationView.SettingsView.SwitchPreset(s);
}
private void OpenCustomVersions(object sender, RoutedEventArgs e) private void OpenCustomVersions(object sender, RoutedEventArgs e)
{ {
var editor = new DictionaryEditor( var editor = new DictionaryEditor(_applicationView.SettingsView.SelectedCustomVersions, "Versioning Configuration (Custom Versions)");
_applicationView.SettingsView.SelectedCustomVersions,
"Versioning Configuration (Custom Versions)",
_applicationView.SettingsView.EnableElements);
var result = editor.ShowDialog(); var result = editor.ShowDialog();
if (!result.HasValue || !result.Value) if (!result.HasValue || !result.Value)
return; return;
@@ -176,10 +161,7 @@ public partial class SettingsView
private void OpenOptions(object sender, RoutedEventArgs e) private void OpenOptions(object sender, RoutedEventArgs e)
{ {
var editor = new DictionaryEditor( var editor = new DictionaryEditor(_applicationView.SettingsView.SelectedOptions, "Versioning Configuration (Options)");
_applicationView.SettingsView.SelectedOptions,
"Versioning Configuration (Options)",
_applicationView.SettingsView.EnableElements);
var result = editor.ShowDialog(); var result = editor.ShowDialog();
if (!result.HasValue || !result.Value) if (!result.HasValue || !result.Value)
return; return;
@@ -189,10 +171,7 @@ public partial class SettingsView
private void OpenMapStructTypes(object sender, RoutedEventArgs e) private void OpenMapStructTypes(object sender, RoutedEventArgs e)
{ {
var editor = new DictionaryEditor( var editor = new DictionaryEditor(_applicationView.SettingsView.SelectedMapStructTypes, "Versioning Configuration (MapStructTypes)");
_applicationView.SettingsView.SelectedMapStructTypes,
"MapStructTypes",
_applicationView.SettingsView.EnableElements);
var result = editor.ShowDialog(); var result = editor.ShowDialog();
if (!result.HasValue || !result.Value) if (!result.HasValue || !result.Value)
return; return;

View File

@@ -1,10 +1,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Numerics; using System.Numerics;
using CUE4Parse_Conversion;
using CUE4Parse_Conversion.Animations.PSA; using CUE4Parse_Conversion.Animations.PSA;
using CUE4Parse.UE4.Assets.Exports; using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Objects.Core.Misc; using CUE4Parse.UE4.Objects.Core.Misc;
using CUE4Parse.Utils; using FModel.Settings;
using FModel.Views.Snooper.Models;
using ImGuiNET; using ImGuiNET;
namespace FModel.Views.Snooper.Animations; namespace FModel.Views.Snooper.Animations;
@@ -12,21 +15,16 @@ namespace FModel.Views.Snooper.Animations;
public class Animation : IDisposable public class Animation : IDisposable
{ {
private readonly UObject _export; private readonly UObject _export;
private readonly CAnimSet _animSet;
public readonly CAnimSet UnrealAnim;
public readonly string Path; public readonly string Path;
public readonly string Name; public readonly string Name;
public readonly Sequence[] Sequences; public readonly Sequence[] Sequences;
public readonly float StartTime; // Animation Start Time public readonly float StartTime; // Animation Start Time
public readonly float EndTime; // Animation End Time public readonly float EndTime; // Animation End Time
public readonly float TotalElapsedTime; // Animation Max Time public readonly float TotalElapsedTime; // Animation Max Time
public readonly string TargetSkeleton; public readonly Dictionary<int, float> Framing;
public int CurrentSequence;
public int FrameInSequence; // Current Sequence's Frame to Display
public string Label =>
$"Retarget: {TargetSkeleton}\nSequences: {CurrentSequence + 1}/{Sequences.Length}\nFrames: {FrameInSequence}/{Sequences[CurrentSequence].EndFrame}";
public bool IsActive; public bool IsActive;
public bool IsSelected; public bool IsSelected;
@@ -38,23 +36,22 @@ public class Animation : IDisposable
Path = _export.GetPathName(); Path = _export.GetPathName();
Name = _export.Name; Name = _export.Name;
Sequences = Array.Empty<Sequence>(); Sequences = Array.Empty<Sequence>();
Framing = new Dictionary<int, float>();
AttachedModels = new List<FGuid>(); AttachedModels = new List<FGuid>();
} }
public Animation(UObject export, CAnimSet animSet) : this(export) public Animation(UObject export, CAnimSet animSet) : this(export)
{ {
_animSet = animSet; UnrealAnim = animSet;
TargetSkeleton = _animSet.Skeleton.Name;
Sequences = new Sequence[_animSet.Sequences.Count]; Sequences = new Sequence[UnrealAnim.Sequences.Count];
for (int i = 0; i < Sequences.Length; i++) for (int i = 0; i < Sequences.Length; i++)
{ {
Sequences[i] = new Sequence(_animSet.Sequences[i]); Sequences[i] = new Sequence(UnrealAnim.Sequences[i]);
EndTime = Sequences[i].EndTime; EndTime = Sequences[i].EndTime;
TotalElapsedTime += _animSet.Sequences[i].NumFrames * Sequences[i].TimePerFrame;
} }
TotalElapsedTime = animSet.TotalAnimTime;
if (Sequences.Length > 0) if (Sequences.Length > 0)
StartTime = Sequences[0].StartTime; StartTime = Sequences[0].StartTime;
} }
@@ -68,25 +65,16 @@ public class Animation : IDisposable
{ {
for (int i = 0; i < Sequences.Length; i++) for (int i = 0; i < Sequences.Length; i++)
{ {
if (elapsedTime < Sequences[i].EndTime && elapsedTime >= Sequences[i].StartTime) var sequence = Sequences[i];
if (elapsedTime <= sequence.EndTime && elapsedTime >= sequence.StartTime)
{ {
CurrentSequence = i; Framing[i] = (elapsedTime - sequence.StartTime) / sequence.TimePerFrame;
break;
} }
else Framing.Remove(i);
} }
if (elapsedTime >= TotalElapsedTime) Reset();
var lastEndTime = 0.0f; if (elapsedTime >= TotalElapsedTime)
for (int s = 0; s < CurrentSequence; s++) Framing.Clear();
lastEndTime = Sequences[s].EndTime;
FrameInSequence = Math.Min(((elapsedTime - lastEndTime) / Sequences[CurrentSequence].TimePerFrame).FloorToInt(), Sequences[CurrentSequence].EndFrame);
}
private void Reset()
{
FrameInSequence = 0;
CurrentSequence = 0;
} }
public void Dispose() public void Dispose()
@@ -134,11 +122,11 @@ public class Animation : IDisposable
foreach ((var guid, var model) in s.Renderer.Options.Models) foreach ((var guid, var model) in s.Renderer.Options.Models)
{ {
var selected = AttachedModels.Contains(guid); var selected = AttachedModels.Contains(guid);
if (ImGui.MenuItem(model.Name, null, selected, (model.HasSkeleton && !model.Skeleton.IsAnimated) || selected)) if (model is SkeletalModel skeletalModel && ImGui.MenuItem(model.Name, null, selected, !skeletalModel.Skeleton.IsAnimated || selected))
{ {
if (selected) AttachedModels.Remove(guid); else AttachedModels.Add(guid); if (selected) AttachedModels.Remove(guid); else AttachedModels.Add(guid);
model.Skeleton.ResetAnimatedData(true); skeletalModel.Skeleton.ResetAnimatedData(true);
if (!selected) model.Skeleton.Animate(_animSet, s.Renderer.AnimateWithRotationOnly); if (!selected) skeletalModel.Skeleton.Animate(UnrealAnim);
} }
} }
ImGui.EndMenu(); ImGui.EndMenu();
@@ -146,7 +134,7 @@ public class Animation : IDisposable
if (ImGui.MenuItem("Save")) if (ImGui.MenuItem("Save"))
{ {
s.WindowShouldFreeze(true); s.WindowShouldFreeze(true);
saver.Value = s.Renderer.Options.TrySave(_export, out saver.Label, out saver.Path); saver.Value = new Exporter(_export).TryWriteToDir(new DirectoryInfo(UserSettings.Default.ModelDirectory), out saver.Label, out saver.Path);
s.WindowShouldFreeze(false); s.WindowShouldFreeze(false);
} }
ImGui.Separator(); ImGui.Separator();

View File

@@ -1,25 +1,37 @@
namespace FModel.Views.Snooper.Animations; using System.Collections.Generic;
namespace FModel.Views.Snooper.Animations;
public class Bone public class Bone
{ {
public readonly int Index; public readonly int Index;
public readonly int ParentIndex; public int ParentIndex;
public readonly Transform Rest; public readonly Transform Rest;
public readonly bool IsVirtual;
public string LoweredParentName; public string LoweredParentName;
public List<string> LoweredChildNames;
public int SkeletonIndex = -1; public int SkeletonIndex = -1;
public bool IsAnimated; public readonly List<int> AnimatedBySequences;
public Bone(int i, int p, Transform t) public Bone(int i, int p, Transform t, bool isVirtual = false)
{ {
Index = i; Index = i;
ParentIndex = p; ParentIndex = p;
Rest = t; Rest = t;
IsVirtual = isVirtual;
LoweredChildNames = new List<string>();
AnimatedBySequences = new List<int>();
} }
public bool IsRoot => Index == 0 && ParentIndex == -1 && string.IsNullOrEmpty(LoweredParentName); public bool IsRoot => Index == 0 && ParentIndex == -1 && string.IsNullOrEmpty(LoweredParentName);
public bool IsDaron => LoweredChildNames.Count > 0;
public bool IsMapped => SkeletonIndex > -1; public bool IsMapped => SkeletonIndex > -1;
public bool IsAnimated => AnimatedBySequences.Count > 0;
public bool IsNative => Index == SkeletonIndex; public bool IsNative => Index == SkeletonIndex;
public uint Color => !IsMapped || !IsAnimated ? 0xFFA0A0A0 : 0xFF48B048;
public override string ToString() => $"Mesh Ref '{Index}' is Skel Ref '{SkeletonIndex}' ({IsAnimated})"; public override string ToString() => $"Mesh Ref '{Index}' is Skel Ref '{SkeletonIndex}'";
} }

View File

@@ -1,10 +1,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Numerics; using System.Numerics;
using CUE4Parse_Conversion.Animations.PSA; using CUE4Parse_Conversion.Animations.PSA;
using CUE4Parse.UE4.Assets.Exports.Animation; using CUE4Parse.UE4.Assets.Exports.Animation;
using CUE4Parse.UE4.Objects.Core.Math;
using FModel.Views.Snooper.Buffers; using FModel.Views.Snooper.Buffers;
using FModel.Views.Snooper.Shading;
using ImGuiNET;
using OpenTK.Graphics.OpenGL4; using OpenTK.Graphics.OpenGL4;
using Serilog; using Serilog;
@@ -13,169 +15,148 @@ namespace FModel.Views.Snooper.Animations;
public class Skeleton : IDisposable public class Skeleton : IDisposable
{ {
private int _handle; private int _handle;
private BufferObject<Matrix4x4> _rest;
private BufferObject<Matrix4x4> _ssbo; private BufferObject<Matrix4x4> _ssbo;
private Matrix4x4[] _boneMatriceAtFrame;
private readonly List<string> _breadcrumb;
public string Name; public string Name;
public readonly Dictionary<string, Bone> BonesByLoweredName; public readonly Dictionary<string, Bone> BonesByLoweredName;
private int _previousAnimationSequence; public readonly int BoneCount;
private int _previousSequenceFrame; private int _additionalBoneCount;
private Transform[][][] _animatedBonesTransform; // [sequence][bone][frame] private int TotalBoneCount => BoneCount + _additionalBoneCount;
private readonly Matrix4x4[] _invertedBonesMatrix;
public int BoneCount => _invertedBonesMatrix.Length; public bool IsAnimated { get; private set; }
public bool IsAnimated => _animatedBonesTransform.Length > 0; public string SelectedBone;
private const int _vertexSize = 12;
private BufferObject<float> _vbo;
private int _vaoHandle;
public Skeleton() public Skeleton()
{ {
BonesByLoweredName = new Dictionary<string, Bone>(); BonesByLoweredName = new Dictionary<string, Bone>();
_animatedBonesTransform = Array.Empty<Transform[][]>(); _breadcrumb = new List<string>();
_invertedBonesMatrix = Array.Empty<Matrix4x4>();
} }
public Skeleton(FReferenceSkeleton referenceSkeleton) : this() public Skeleton(FReferenceSkeleton referenceSkeleton) : this()
{ {
_invertedBonesMatrix = new Matrix4x4[referenceSkeleton.FinalRefBoneInfo.Length]; BoneCount = referenceSkeleton.FinalRefBoneInfo.Length;
for (int boneIndex = 0; boneIndex < _invertedBonesMatrix.Length; boneIndex++) for (int boneIndex = 0; boneIndex < BoneCount; boneIndex++)
{ {
var info = referenceSkeleton.FinalRefBoneInfo[boneIndex]; var info = referenceSkeleton.FinalRefBoneInfo[boneIndex];
var boneTransform = new Transform var boneName = info.Name.Text.ToLower();
var bone = new Bone(boneIndex, info.ParentIndex, new Transform
{ {
Rotation = referenceSkeleton.FinalRefBonePose[boneIndex].Rotation, Rotation = referenceSkeleton.FinalRefBonePose[boneIndex].Rotation,
Position = referenceSkeleton.FinalRefBonePose[boneIndex].Translation * Constants.SCALE_DOWN_RATIO, Position = referenceSkeleton.FinalRefBonePose[boneIndex].Translation * Constants.SCALE_DOWN_RATIO,
Scale = referenceSkeleton.FinalRefBonePose[boneIndex].Scale3D Scale = referenceSkeleton.FinalRefBonePose[boneIndex].Scale3D
}; });
var bone = new Bone(boneIndex, info.ParentIndex, boneTransform);
if (!bone.IsRoot) if (!bone.IsRoot)
{ {
bone.LoweredParentName = bone.LoweredParentName = referenceSkeleton.FinalRefBoneInfo[bone.ParentIndex].Name.Text.ToLower();
referenceSkeleton.FinalRefBoneInfo[bone.ParentIndex].Name.Text.ToLower(); var parentBone = BonesByLoweredName[bone.LoweredParentName];
bone.Rest.Relation = BonesByLoweredName[bone.LoweredParentName].Rest.Matrix;
bone.Rest.Relation = parentBone.Rest.Matrix;
parentBone.LoweredChildNames.Add(boneName);
} }
BonesByLoweredName[info.Name.Text.ToLower()] = bone; if (boneIndex == 0) SelectedBone = boneName;
BonesByLoweredName[boneName] = bone;
Matrix4x4.Invert(boneTransform.Matrix, out var inverted);
_invertedBonesMatrix[bone.Index] = inverted;
} }
_breadcrumb.Add(SelectedBone);
_boneMatriceAtFrame = new Matrix4x4[BoneCount];
} }
public void Animate(CAnimSet anim, bool rotationOnly) public void Merge(FReferenceSkeleton referenceSkeleton)
{ {
MapSkeleton(anim); for (int boneIndex = 0; boneIndex < referenceSkeleton.FinalRefBoneInfo.Length; boneIndex++)
_animatedBonesTransform = new Transform[anim.Sequences.Count][][];
for (int s = 0; s < _animatedBonesTransform.Length; s++)
{ {
var sequence = anim.Sequences[s]; var info = referenceSkeleton.FinalRefBoneInfo[boneIndex];
_animatedBonesTransform[s] = new Transform[BoneCount][]; var boneName = info.Name.Text.ToLower();
foreach (var bone in BonesByLoweredName.Values)
if (!BonesByLoweredName.TryGetValue(boneName, out var bone))
{ {
_animatedBonesTransform[s][bone.Index] = new Transform[sequence.NumFrames]; bone = new Bone(BoneCount + _additionalBoneCount, -1, new Transform
var skeletonBoneIndex = bone.SkeletonIndex;
if (sequence.OriginalSequence.FindTrackForBoneIndex(skeletonBoneIndex) < 0)
{ {
bone.IsAnimated |= false; Rotation = referenceSkeleton.FinalRefBonePose[boneIndex].Rotation,
for (int frame = 0; frame < _animatedBonesTransform[s][bone.Index].Length; frame++) Position = referenceSkeleton.FinalRefBonePose[boneIndex].Translation * Constants.SCALE_DOWN_RATIO,
{ Scale = referenceSkeleton.FinalRefBonePose[boneIndex].Scale3D
_animatedBonesTransform[s][bone.Index][frame] = new Transform }, true);
{
Relation = bone.IsRoot ? bone.Rest.Relation : if (!bone.IsRoot)
bone.Rest.LocalMatrix * _animatedBonesTransform[s][bone.ParentIndex][frame].Matrix
};
}
}
else
{ {
bone.IsAnimated |= true; bone.LoweredParentName = referenceSkeleton.FinalRefBoneInfo[info.ParentIndex].Name.Text.ToLower();
for (int frame = 0; frame < _animatedBonesTransform[s][bone.Index].Length; frame++) var parentBone = BonesByLoweredName[bone.LoweredParentName];
{
var boneOrientation = bone.Rest.Rotation;
var bonePosition = bone.Rest.Position;
var boneScale = bone.Rest.Scale;
sequence.Tracks[skeletonBoneIndex].GetBoneTransform(frame, sequence.NumFrames, ref boneOrientation, ref bonePosition, ref boneScale); bone.ParentIndex = parentBone.Index;
bone.Rest.Relation = parentBone.Rest.Matrix;
switch (anim.Skeleton.BoneTree[skeletonBoneIndex]) parentBone.LoweredChildNames.Add(boneName);
{
case EBoneTranslationRetargetingMode.Skeleton when !rotationOnly:
{
var targetTransform = sequence.RetargetBasePose?[skeletonBoneIndex] ?? anim.Skeleton.ReferenceSkeleton.FinalRefBonePose[skeletonBoneIndex];
bonePosition = targetTransform.Translation;
break;
}
case EBoneTranslationRetargetingMode.AnimationScaled when !rotationOnly:
{
var sourceTranslationLength = (bone.Rest.Position / Constants.SCALE_DOWN_RATIO).Size();
if (sourceTranslationLength > UnrealMath.KindaSmallNumber)
{
var targetTranslationLength = sequence.RetargetBasePose?[skeletonBoneIndex].Translation.Size() ?? anim.Skeleton.ReferenceSkeleton.FinalRefBonePose[skeletonBoneIndex].Translation.Size();
bonePosition.Scale(targetTranslationLength / sourceTranslationLength);
}
break;
}
case EBoneTranslationRetargetingMode.AnimationRelative when !rotationOnly:
{
// can't tell if it's working or not
var sourceSkelTrans = bone.Rest.Position / Constants.SCALE_DOWN_RATIO;
var refPoseTransform = sequence.RetargetBasePose?[skeletonBoneIndex] ?? anim.Skeleton.ReferenceSkeleton.FinalRefBonePose[skeletonBoneIndex];
boneOrientation = boneOrientation * FQuat.Conjugate(bone.Rest.Rotation) * refPoseTransform.Rotation;
bonePosition += refPoseTransform.Translation - sourceSkelTrans;
boneScale *= refPoseTransform.Scale3D * bone.Rest.Scale;
boneOrientation.Normalize();
break;
}
case EBoneTranslationRetargetingMode.OrientAndScale when !rotationOnly:
{
var sourceSkelTrans = bone.Rest.Position / Constants.SCALE_DOWN_RATIO;
var targetSkelTrans = sequence.RetargetBasePose?[skeletonBoneIndex].Translation ?? anim.Skeleton.ReferenceSkeleton.FinalRefBonePose[skeletonBoneIndex].Translation;
if (!sourceSkelTrans.Equals(targetSkelTrans))
{
var sourceSkelTransLength = sourceSkelTrans.Size();
var targetSkelTransLength = targetSkelTrans.Size();
if (!UnrealMath.IsNearlyZero(sourceSkelTransLength * targetSkelTransLength))
{
var sourceSkelTransDir = sourceSkelTrans / sourceSkelTransLength;
var targetSkelTransDir = targetSkelTrans / targetSkelTransLength;
var deltaRotation = FQuat.FindBetweenNormals(sourceSkelTransDir, targetSkelTransDir);
var scale = targetSkelTransLength / sourceSkelTransLength;
bonePosition = deltaRotation.RotateVector(bonePosition) * scale;
}
}
break;
}
}
_animatedBonesTransform[s][bone.Index][frame] = new Transform
{
Relation = bone.IsRoot ? bone.Rest.Relation : _animatedBonesTransform[s][bone.ParentIndex][frame].Matrix,
Rotation = boneOrientation,
Position = rotationOnly ? bone.Rest.Position : bonePosition * Constants.SCALE_DOWN_RATIO,
Scale = boneScale
};
}
} }
BonesByLoweredName[boneName] = bone;
_additionalBoneCount++;
} }
} }
_boneMatriceAtFrame = new Matrix4x4[TotalBoneCount];
} }
private void MapSkeleton(CAnimSet anim) public void Setup()
{ {
_handle = GL.CreateProgram();
_vaoHandle = GL.GenVertexArray();
GL.BindVertexArray(_vaoHandle);
_vbo = new BufferObject<float>(_vertexSize * BoneCount, BufferTarget.ArrayBuffer);
var sf = sizeof(float);
var half = _vertexSize / 2;
GL.EnableVertexAttribArray(0);
GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, sf * half, sf * 0);
GL.EnableVertexAttribArray(1);
GL.VertexAttribPointer(1, 3, VertexAttribPointerType.Float, false, sf * half, sf * 3);
GL.BindVertexArray(0);
_rest = new BufferObject<Matrix4x4>(BoneCount, BufferTarget.ShaderStorageBuffer);
foreach (var bone in BonesByLoweredName.Values)
{
if (bone.IsVirtual) break;
_rest.Update(bone.Index, bone.Rest.Matrix);
}
_rest.Unbind();
_ssbo = new BufferObject<Matrix4x4>(TotalBoneCount, BufferTarget.ShaderStorageBuffer);
_ssbo.UpdateRange(Matrix4x4.Identity);
}
public void Animate(CAnimSet animation)
{
IsAnimated = true;
ResetAnimatedData(); ResetAnimatedData();
// map bones // map bones
for (int boneIndex = 0; boneIndex < anim.Skeleton.BoneCount; boneIndex++) for (int boneIndex = 0; boneIndex < animation.Skeleton.BoneCount; boneIndex++)
{ {
var info = anim.Skeleton.ReferenceSkeleton.FinalRefBoneInfo[boneIndex]; var info = animation.Skeleton.ReferenceSkeleton.FinalRefBoneInfo[boneIndex];
if (!BonesByLoweredName.TryGetValue(info.Name.Text.ToLower(), out var bone)) if (!BonesByLoweredName.TryGetValue(info.Name.Text.ToLower(), out var bone))
continue; continue;
bone.SkeletonIndex = boneIndex; bone.SkeletonIndex = boneIndex;
bone.IsAnimated = false; }
// find playable sequences
for (int s = 0; s < animation.Sequences.Count; s++)
{
var sequence = animation.Sequences[s];
foreach (var bone in BonesByLoweredName.Values.Where(bone => sequence.OriginalSequence.FindTrackForBoneIndex(bone.SkeletonIndex) >= 0))
{
bone.AnimatedBySequences.Add(s);
}
sequence.RetargetTracks(animation.Skeleton);
} }
#if DEBUG #if DEBUG
@@ -194,52 +175,213 @@ public class Skeleton : IDisposable
foreach (var bone in BonesByLoweredName.Values) foreach (var bone in BonesByLoweredName.Values)
{ {
bone.SkeletonIndex = -1; bone.SkeletonIndex = -1;
bone.IsAnimated = false; bone.AnimatedBySequences.Clear();
} }
if (!full) return; if (!full) return;
_animatedBonesTransform = Array.Empty<Transform[][]>(); IsAnimated = false;
_ssbo.UpdateRange(BoneCount, Matrix4x4.Identity); _ssbo.UpdateRange(Matrix4x4.Identity);
} }
public void Setup() public void UpdateAnimationMatrices(Animation animation, bool rotationOnly)
{
_handle = GL.CreateProgram();
_ssbo = new BufferObject<Matrix4x4>(BoneCount, BufferTarget.ShaderStorageBuffer);
_ssbo.UpdateRange(BoneCount, Matrix4x4.Identity);
}
public void UpdateAnimationMatrices(int currentSequence, int frameInSequence)
{ {
if (!IsAnimated) return; if (!IsAnimated) return;
_previousAnimationSequence = currentSequence;
if (_previousSequenceFrame == frameInSequence) return;
_previousSequenceFrame = frameInSequence;
_ssbo.Bind(); _ssbo.Bind();
for (int boneIndex = 0; boneIndex < BoneCount; boneIndex++) // interpolate here foreach (var bone in BonesByLoweredName.Values)
_ssbo.Update(boneIndex, _invertedBonesMatrix[boneIndex] * _animatedBonesTransform[_previousAnimationSequence][boneIndex][_previousSequenceFrame].Matrix); {
var boneMatrix = bone.IsRoot ? bone.Rest.Relation : bone.Rest.LocalMatrix * _boneMatriceAtFrame[bone.ParentIndex];
if (bone.IsAnimated)
{
var (s, f) = GetBoneFrameData(bone, animation);
var sequence = animation.UnrealAnim.Sequences[s];
var boneOrientation = bone.Rest.Rotation;
var bonePosition = bone.Rest.Position;
var boneScale = bone.Rest.Scale;
sequence.Tracks[bone.SkeletonIndex].GetBoneTransform(f, sequence.NumFrames, ref boneOrientation, ref bonePosition, ref boneScale);
if (!bone.IsRoot) boneMatrix = _boneMatriceAtFrame[bone.ParentIndex];
bonePosition = rotationOnly ? bone.Rest.Position : bonePosition * Constants.SCALE_DOWN_RATIO;
boneMatrix = new Transform
{
Relation = boneMatrix,
Rotation = boneOrientation,
Position = bonePosition,
Scale = boneScale
}.Matrix;
}
_ssbo.Update(bone.Index, boneMatrix);
_boneMatriceAtFrame[bone.Index] = boneMatrix;
}
_ssbo.Unbind(); _ssbo.Unbind();
} }
public Matrix4x4 GetBoneMatrix(Bone bone) public void UpdateVertices()
{ {
return IsAnimated _vbo.Bind();
? _animatedBonesTransform[_previousAnimationSequence][bone.Index][_previousSequenceFrame].Matrix foreach (var (boneName, bone) in BonesByLoweredName)
: bone.Rest.Matrix; {
var boneMatrix = IsAnimated ? _boneMatriceAtFrame[bone.Index] : bone.Rest.Matrix;
var parentBoneMatrix = bone.IsRoot ? boneMatrix :
IsAnimated ? _boneMatriceAtFrame[bone.ParentIndex] :
BonesByLoweredName[bone.LoweredParentName].Rest.Matrix;
var count = 0;
var baseIndex = bone.Index * _vertexSize;
_vbo.Update(baseIndex + count++, boneMatrix.Translation.X);
_vbo.Update(baseIndex + count++, boneMatrix.Translation.Y);
_vbo.Update(baseIndex + count++, boneMatrix.Translation.Z);
_vbo.Update(baseIndex + count++, 1.0f);
_vbo.Update(baseIndex + count++, boneName == SelectedBone ? 0.0f : 1.0f);
_vbo.Update(baseIndex + count++, boneName == SelectedBone ? 0.0f : 1.0f);
_vbo.Update(baseIndex + count++, parentBoneMatrix.Translation.X);
_vbo.Update(baseIndex + count++, parentBoneMatrix.Translation.Y);
_vbo.Update(baseIndex + count++, parentBoneMatrix.Translation.Z);
_vbo.Update(baseIndex + count++, 1.0f);
_vbo.Update(baseIndex + count++, bone.LoweredParentName == SelectedBone ? 0.0f : 1.0f);
_vbo.Update(baseIndex + count++, bone.LoweredParentName == SelectedBone ? 0.0f : 1.0f);
}
_vbo.Unbind();
} }
public void Render() private (int, float) GetBoneFrameData(Bone bone, Animation animation)
{ {
int s = -1;
float f = 0.0f;
void Get(Bone b)
{
foreach (var i in b.AnimatedBySequences)
{
s = i;
if (animation.Framing.TryGetValue(s, out f))
break;
}
}
Get(bone);
if (s == -1)
{
var parent = BonesByLoweredName[bone.LoweredParentName];
while (!parent.IsAnimated)
{
parent = BonesByLoweredName[parent.LoweredParentName];
}
Get(parent);
}
return (s, f);
}
public Matrix4x4 GetBoneMatrix(Bone bone) => IsAnimated ? _boneMatriceAtFrame[bone.Index] : bone.Rest.Matrix;
public void Render(Shader shader)
{
shader.SetUniform("uIsAnimated", IsAnimated);
_ssbo.BindBufferBase(1); _ssbo.BindBufferBase(1);
_rest.BindBufferBase(2);
}
public void RenderBones()
{
GL.Disable(EnableCap.DepthTest);
GL.BindVertexArray(_vaoHandle);
GL.DrawArrays(PrimitiveType.Lines, 0, _vbo.Size);
GL.DrawArrays(PrimitiveType.Points, 0, _vbo.Size);
GL.BindVertexArray(0);
GL.Enable(EnableCap.DepthTest);
}
public void ImGuiBoneBreadcrumb()
{
var p1 = ImGui.GetCursorScreenPos();
var canvasSize = ImGui.GetContentRegionAvail() with { Y = 20 };
var p2 = p1 + canvasSize;
ImGui.BeginChild("skeleton_breadcrumb", canvasSize);
var drawList = ImGui.GetWindowDrawList();
drawList.AddRectFilled(p1, p2, 0xFF242424);
var x = p1.X;
var y = p1.Y + (p2.Y - p1.Y) / 2;
for (int i = Math.Min(_breadcrumb.Count - 1, 5); i >= 0; i--)
{
var boneName = _breadcrumb[i];
var size = ImGui.CalcTextSize(boneName);
var position = new Vector2(x + 5, y - size.Y / 2f);
ImGui.SetCursorScreenPos(position);
if (ImGui.InvisibleButton($"breakfast_{boneName}", size, ImGuiButtonFlags.MouseButtonLeft))
{
SelectedBone = boneName;
_breadcrumb.RemoveRange(0, i);
break;
}
drawList.AddText(position, i == 0 || ImGui.IsItemHovered() ? 0xFFFFFFFF : 0xA0FFFFFF, boneName);
x += size.X + 7.5f;
drawList.AddText(position with { X = x }, 0xA0FFFFFF, ">");
x += 7.5f;
}
ImGui.EndChild();
}
public void ImGuiBoneHierarchy()
{
foreach (var name in BonesByLoweredName[SelectedBone].LoweredChildNames)
{
DrawBoneTree(name, BonesByLoweredName[name]);
}
}
private void DrawBoneTree(string boneName, Bone bone)
{
ImGui.PushID(bone.Index);
ImGui.TableNextRow();
ImGui.TableNextColumn();
var flags = ImGuiTreeNodeFlags.OpenOnArrow | ImGuiTreeNodeFlags.SpanFullWidth;
if (bone.IsVirtual) flags |= ImGuiTreeNodeFlags.Leaf;
else if (!bone.IsDaron) flags |= ImGuiTreeNodeFlags.Bullet;
ImGui.SetNextItemOpen(bone.LoweredChildNames.Count <= 1, ImGuiCond.Appearing);
var open = ImGui.TreeNodeEx(boneName, flags);
if (ImGui.IsItemClicked() && !ImGui.IsItemToggledOpen() && bone.IsDaron)
{
SelectedBone = boneName;
_breadcrumb.Clear();
do
{
_breadcrumb.Add(boneName);
boneName = BonesByLoweredName[boneName].LoweredParentName;
} while (boneName != null);
}
ImGui.TableNextColumn();
ImGui.TextColored(ImGui.ColorConvertU32ToFloat4(bone.Color), bone.SkeletonIndex.ToString());
if (open)
{
foreach (var name in bone.LoweredChildNames)
{
DrawBoneTree(name, BonesByLoweredName[name]);
}
ImGui.TreePop();
}
ImGui.PopID();
} }
public void Dispose() public void Dispose()
{ {
BonesByLoweredName.Clear(); BonesByLoweredName.Clear();
_rest?.Dispose();
_ssbo?.Dispose(); _ssbo?.Dispose();
GL.DeleteProgram(_handle); GL.DeleteProgram(_handle);
} }

View File

@@ -6,26 +6,33 @@ namespace FModel.Views.Snooper.Buffers;
public class BufferObject<TDataType> : IDisposable where TDataType : unmanaged public class BufferObject<TDataType> : IDisposable where TDataType : unmanaged
{ {
private readonly int _handle; private readonly int _handle;
private readonly int _sizeOf;
private readonly BufferTarget _bufferTarget; private readonly BufferTarget _bufferTarget;
private BufferObject(BufferTarget bufferTarget) public readonly int Size;
private unsafe BufferObject(BufferTarget bufferTarget)
{ {
_bufferTarget = bufferTarget; _bufferTarget = bufferTarget;
_handle = GL.GenBuffer(); _handle = GL.GenBuffer();
_sizeOf = sizeof(TDataType);
Bind(); Bind();
} }
public unsafe BufferObject(TDataType[] data, BufferTarget bufferTarget) : this(bufferTarget) public BufferObject(TDataType[] data, BufferTarget bufferTarget) : this(bufferTarget)
{ {
GL.BufferData(bufferTarget, data.Length * sizeof(TDataType), data, BufferUsageHint.StaticDraw); Size = data.Length;
GL.BufferData(bufferTarget, Size * _sizeOf, data, BufferUsageHint.StaticDraw);
} }
public unsafe BufferObject(int length, BufferTarget bufferTarget) : this(bufferTarget) public BufferObject(int length, BufferTarget bufferTarget) : this(bufferTarget)
{ {
GL.BufferData(bufferTarget, length * sizeof(TDataType), IntPtr.Zero, BufferUsageHint.DynamicDraw); Size = length;
GL.BufferData(bufferTarget, Size * _sizeOf, IntPtr.Zero, BufferUsageHint.DynamicDraw);
} }
public void UpdateRange(TDataType data) => UpdateRange(Size, data);
public void UpdateRange(int count, TDataType data) public void UpdateRange(int count, TDataType data)
{ {
Bind(); Bind();
@@ -33,14 +40,23 @@ public class BufferObject<TDataType> : IDisposable where TDataType : unmanaged
Unbind(); Unbind();
} }
public unsafe void Update(int offset, TDataType data) public void Update(int offset, TDataType data)
{ {
GL.BufferSubData(_bufferTarget, (IntPtr) (offset * sizeof(TDataType)), sizeof(TDataType), ref data); GL.BufferSubData(_bufferTarget, (IntPtr) (offset * _sizeOf), _sizeOf, ref data);
} }
public unsafe void Update(TDataType[] data) public void Update(TDataType[] data)
{ {
GL.BufferSubData(_bufferTarget, IntPtr.Zero, data.Length * sizeof(TDataType), data); Bind();
GL.BufferSubData(_bufferTarget, IntPtr.Zero, data.Length * _sizeOf, data);
Unbind();
}
public TDataType Get(int offset)
{
TDataType data = default;
GL.GetBufferSubData(_bufferTarget, (IntPtr) (offset * _sizeOf), _sizeOf, ref data);
return data;
} }
public void Bind() public void Bind()

View File

@@ -54,20 +54,20 @@ public class PickingTexture : IDisposable
Bind(0); Bind(0);
} }
public void Render(Matrix4x4 viewMatrix, Matrix4x4 projMatrix, IDictionary<FGuid,Model> models) public void Render(Matrix4x4 viewMatrix, Matrix4x4 projMatrix, IDictionary<FGuid, UModel> models)
{ {
Bind(); Bind();
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
_shader.Render(viewMatrix, projMatrix); _shader.Render(viewMatrix, projMatrix);
foreach ((FGuid guid, Model model) in models) foreach ((var guid, var model) in models)
{ {
_shader.SetUniform("uA", guid.A); _shader.SetUniform("uA", guid.A);
_shader.SetUniform("uB", guid.B); _shader.SetUniform("uB", guid.B);
_shader.SetUniform("uC", guid.C); _shader.SetUniform("uC", guid.C);
_shader.SetUniform("uD", guid.D); _shader.SetUniform("uD", guid.D);
if (!model.Show) continue; if (!model.IsVisible) continue;
model.PickingRender(_shader); model.PickingRender(_shader);
} }

View File

@@ -7,25 +7,29 @@ namespace FModel.Views.Snooper.Buffers;
public class VertexArrayObject<TVertexType, TIndexType> : IDisposable where TVertexType : unmanaged where TIndexType : unmanaged public class VertexArrayObject<TVertexType, TIndexType> : IDisposable where TVertexType : unmanaged where TIndexType : unmanaged
{ {
private readonly int _handle; private readonly int _handle;
private readonly int _sizeOfVertex;
private readonly int _sizeOfIndex;
public VertexArrayObject(BufferObject<TVertexType> vbo, BufferObject<TIndexType> ebo) public unsafe VertexArrayObject(BufferObject<TVertexType> vbo, BufferObject<TIndexType> ebo)
{ {
_handle = GL.GenVertexArray(); _handle = GL.GenVertexArray();
_sizeOfVertex = sizeof(TVertexType);
_sizeOfIndex = sizeof(TIndexType);
Bind(); Bind();
vbo.Bind(); vbo.Bind();
ebo.Bind(); ebo.Bind();
} }
public unsafe void VertexAttributePointer(uint index, int count, VertexAttribPointerType type, int vertexSize, int offset) public void VertexAttributePointer(uint index, int count, VertexAttribPointerType type, int vertexSize, int offset)
{ {
switch (type) switch (type)
{ {
case VertexAttribPointerType.Int: case VertexAttribPointerType.Int:
GL.VertexAttribIPointer(index, count, VertexAttribIntegerType.Int, vertexSize * sizeof(TVertexType), (IntPtr) (offset * sizeof(TVertexType))); GL.VertexAttribIPointer(index, count, VertexAttribIntegerType.Int, vertexSize * _sizeOfVertex, (IntPtr) (offset * _sizeOfVertex));
break; break;
default: default:
GL.VertexAttribPointer(index, count, type, false, vertexSize * sizeof(TVertexType), offset * sizeof(TVertexType)); GL.VertexAttribPointer(index, count, type, false, vertexSize * _sizeOfVertex, offset * _sizeOfVertex);
break; break;
} }
GL.EnableVertexAttribArray(index); GL.EnableVertexAttribArray(index);

View File

@@ -0,0 +1,57 @@
using System.Collections.Generic;
using System.Numerics;
using CUE4Parse.UE4.Objects.Core.Math;
namespace FModel.Views.Snooper.Models;
public class Attachment
{
private string _modelName;
private string _attachedTo;
private readonly List<string> _attachedFor;
private Matrix4x4 _oldRelation;
public bool IsAttached => _attachedTo.Length > 0;
public bool IsAttachment => _attachedFor.Count > 0;
public string Icon => IsAttachment ? "link_has" : IsAttached ? "link_on" : "link_off";
public string Tooltip => IsAttachment ? $"Is Attachment For:\n{string.Join("\n", _attachedFor)}" : IsAttached ? $"Is Attached To {_attachedTo}" : "Not Attached To Any Socket Nor Attachment For Any Model";
public Attachment(string modelName)
{
_modelName = modelName;
_attachedTo = string.Empty;
_attachedFor = new List<string>();
}
public void Attach(UModel attachedTo, Transform transform, Socket socket, SocketAttachementInfo info)
{
socket.AttachedModels.Add(info);
_attachedTo = $"'{socket.Name}' from '{attachedTo.Name}'{(!socket.BoneName.IsNone ? $" at '{socket.BoneName}'" : "")}";
attachedTo.Attachments.AddAttachment(_modelName);
// reset PRS to 0 so it's attached to the actual position (can be transformed relative to the socket later by the user)
_oldRelation = transform.Relation;
transform.Position = FVector.ZeroVector;
transform.Rotation = FQuat.Identity;
transform.Scale = FVector.OneVector;
}
public void Detach(UModel attachedTo, Transform transform, Socket socket, SocketAttachementInfo info)
{
socket.AttachedModels.Remove(info);
SafeDetach(attachedTo, transform);
}
public void SafeDetach(UModel attachedTo, Transform transform)
{
_attachedTo = string.Empty;
attachedTo.Attachments.RemoveAttachment(_modelName);
transform.Relation = _oldRelation;
}
public void AddAttachment(string modelName) => _attachedFor.Add($"'{modelName}'");
public void RemoveAttachment(string modelName) => _attachedFor.Remove($"'{modelName}'");
}

View File

@@ -0,0 +1,14 @@
namespace FModel.Views.Snooper.Models;
public enum EAttribute
{
Index,
Position,
Normals,
Tangent,
UVs,
Layer,
Colors,
BonesId,
BonesWeight
}

View File

@@ -45,16 +45,17 @@ public class Grid : IDisposable
{ {
GL.Disable(EnableCap.CullFace); GL.Disable(EnableCap.CullFace);
GL.Disable(EnableCap.DepthTest); GL.Disable(EnableCap.DepthTest);
_vao.Bind(); _vao.Bind();
_shader.Use(); _shader.Use();
_shader.SetUniform("view", viewMatrix); _shader.SetUniform("view", viewMatrix);
_shader.SetUniform("proj", projMatrix); _shader.SetUniform("proj", projMatrix);
_shader.SetUniform("uNear", near); _shader.SetUniform("uNear", near);
_shader.SetUniform("uFar", far); _shader.SetUniform("uFar", far);
GL.DrawArrays(PrimitiveType.Triangles, 0, Indices.Length); GL.DrawArrays(PrimitiveType.Triangles, 0, Indices.Length);
GL.Enable(EnableCap.DepthTest); GL.Enable(EnableCap.DepthTest);
GL.Enable(EnableCap.CullFace); GL.Enable(EnableCap.CullFace);
} }

View File

@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using FModel.Views.Snooper.Buffers;
using FModel.Views.Snooper.Shading;
namespace FModel.Views.Snooper.Models;
public interface IRenderableModel : IDisposable
{
protected int Handle { get; set; }
protected BufferObject<uint> Ebo { get; set; }
protected BufferObject<float> Vbo { get; set; }
protected BufferObject<Matrix4x4> MatrixVbo { get; set; }
protected VertexArrayObject<float, uint> Vao { get; set; }
public string Path { get; }
public string Name { get; }
public string Type { get; }
public int UvCount { get; }
public uint[] Indices { get; protected set; }
public float[] Vertices { get; protected set; }
public Section[] Sections { get; protected set; }
public List<Transform> Transforms { get; }
public Attachment Attachments { get; }
public bool IsSetup { get; set; }
public bool IsVisible { get; set; }
public bool IsSelected { get; set; }
public bool ShowWireframe { get; set; }
public void Setup(Options options);
public void SetupInstances();
public void Render(Shader shader, Texture checker = null, bool outline = false);
public void PickingRender(Shader shader);
public void Update(Options options);
public void AddInstance(Transform transform);
}

View File

@@ -1,462 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using CUE4Parse.UE4.Assets.Exports.Animation;
using CUE4Parse.UE4.Objects.UObject;
using CUE4Parse_Conversion.Meshes.PSK;
using CUE4Parse.UE4.Assets;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
using CUE4Parse.UE4.Objects.Core.Math;
using FModel.Extensions;
using FModel.Views.Snooper.Animations;
using FModel.Views.Snooper.Buffers;
using FModel.Views.Snooper.Shading;
using OpenTK.Graphics.OpenGL4;
namespace FModel.Views.Snooper.Models;
public class VertexAttribute
{
public int Size;
public bool Enabled;
}
public enum EAttribute
{
Index,
Position,
Normals,
Tangent,
UVs,
Layer,
Colors,
BonesId,
BonesWeight
}
public class Model : IDisposable
{
private int _handle;
private const int _LOD_INDEX = 0;
private BufferObject<uint> _ebo;
private BufferObject<float> _vbo;
private BufferObject<float> _morphVbo;
private BufferObject<Matrix4x4> _matrixVbo;
private VertexArrayObject<float, uint> _vao;
private readonly List<VertexAttribute> _vertexAttributes = new()
{
new VertexAttribute { Size = 1, Enabled = true }, // VertexIndex
new VertexAttribute { Size = 3, Enabled = true }, // Position
new VertexAttribute { Size = 3, Enabled = true }, // Normal
new VertexAttribute { Size = 3, Enabled = true }, // Tangent
new VertexAttribute { Size = 2, Enabled = true }, // UV
new VertexAttribute { Size = 1, Enabled = true }, // TextureLayer
new VertexAttribute { Size = 4, Enabled = false }, // Colors
new VertexAttribute { Size = 4, Enabled = false }, // BoneIds
new VertexAttribute { Size = 4, Enabled = false } // BoneWeights
};
public int VertexSize => _vertexAttributes.Where(x => x.Enabled).Sum(x => x.Size);
public bool HasVertexColors => _vertexAttributes[(int) EAttribute.Colors].Enabled;
private const int _faceSize = 3;
public readonly UObject Export;
public readonly string Path;
public readonly string Name;
public readonly string Type;
public readonly int UvCount;
public readonly FBox Box;
public uint[] Indices;
public float[] Vertices;
public Section[] Sections;
public Material[] Materials;
public bool TwoSided;
public bool IsAnimatedProp;
public bool HasSkeleton => Skeleton != null;
public readonly Skeleton Skeleton;
public bool HasSockets => Sockets.Count > 0;
public readonly List<Socket> Sockets;
public bool HasMorphTargets => Morphs.Count > 0;
public readonly List<Morph> Morphs;
private string _attachedTo = string.Empty;
private readonly List<string> _attachedFor = new ();
public bool IsAttached => _attachedTo.Length > 0;
public bool IsAttachment => _attachedFor.Count > 0;
public string AttachIcon => IsAttachment ? "link_has" : IsAttached ? "link_on" : "link_off";
public string AttachTooltip => IsAttachment ? $"Is Attachment For:\n{string.Join("\n", _attachedFor)}" : IsAttached ? $"Is Attached To {_attachedTo}" : "Not Attached To Any Socket Nor Attachment For Any Model";
public int TransformsCount;
public readonly List<Transform> Transforms;
private Matrix4x4 _previousMatrix;
public bool Show;
public bool Wireframe;
public bool IsSetup { get; private set; }
public bool IsSelected;
public int SelectedInstance;
public float MorphTime;
protected Model(UObject export)
{
Export = export;
Path = Export.GetPathName();
Name = Path.SubstringAfterLast('/').SubstringBefore('.');
Type = export.ExportType;
UvCount = 1;
Box = new FBox(new FVector(-2f), new FVector(2f));
Sockets = new List<Socket>();
Morphs = new List<Morph>();
Transforms = new List<Transform>();
}
public Model(UStaticMesh export, CStaticMesh staticMesh) : this(export, staticMesh, Transform.Identity) {}
public Model(UStaticMesh export, CStaticMesh staticMesh, Transform transform) : this(export, export.Materials, staticMesh.LODs, transform)
{
Box = staticMesh.BoundingBox * Constants.SCALE_DOWN_RATIO;
for (int i = 0; i < export.Sockets.Length; i++)
{
if (export.Sockets[i].Load<UStaticMeshSocket>() is not { } socket) continue;
Sockets.Add(new Socket(socket));
}
}
public Model(USkeletalMesh export, CSkeletalMesh skeletalMesh) : this(export, skeletalMesh, Transform.Identity) {}
public Model(USkeletalMesh export, CSkeletalMesh skeletalMesh, Transform transform) : this(export, export.Materials, skeletalMesh.LODs, transform)
{
Box = skeletalMesh.BoundingBox * Constants.SCALE_DOWN_RATIO;
Skeleton = new Skeleton(export.ReferenceSkeleton);
var sockets = new List<FPackageIndex>();
sockets.AddRange(export.Sockets);
if (HasSkeleton && export.Skeleton.TryLoad(out USkeleton skeleton))
{
Skeleton.Name = skeleton.Name;
sockets.AddRange(skeleton.Sockets);
}
for (int i = 0; i < sockets.Count; i++)
{
if (sockets[i].Load<USkeletalMeshSocket>() is not { } socket) continue;
Sockets.Add(new Socket(socket));
}
for (var i = 0; i < export.MorphTargets.Length; i++)
{
if (!export.MorphTargets[i].TryLoad(out UMorphTarget morphTarget) ||
morphTarget.MorphLODModels.Length < 1 || morphTarget.MorphLODModels[0].Vertices.Length < 1)
continue;
Morphs.Add(new Morph(Vertices, VertexSize, morphTarget));
}
}
private Model(UObject export, IReadOnlyList<ResolvedObject> materials, IReadOnlyList<CStaticMeshLod> lods, Transform transform = null)
: this(export, materials, lods[_LOD_INDEX], lods[_LOD_INDEX].Verts, lods.Count, transform) {}
private Model(UObject export, IReadOnlyList<ResolvedObject> materials, IReadOnlyList<CSkelMeshLod> lods, Transform transform = null)
: this(export, materials, lods[_LOD_INDEX], lods[_LOD_INDEX].Verts, lods.Count, transform) {}
private Model(UObject export, IReadOnlyList<ResolvedObject> materials, CBaseMeshLod lod, IReadOnlyList<CMeshVertex> vertices, int numLods, Transform transform = null) : this(export)
{
var hasCustomUvs = lod.ExtraUV.IsValueCreated;
UvCount = hasCustomUvs ? Math.Max(lod.NumTexCoords, numLods) : lod.NumTexCoords;
TwoSided = lod.IsTwoSided;
Materials = new Material[materials.Count];
for (int m = 0; m < Materials.Length; m++)
{
if ((materials[m]?.TryLoad(out var material) ?? false) && material is UMaterialInterface unrealMaterial)
Materials[m] = new Material(unrealMaterial); else Materials[m] = new Material();
}
_vertexAttributes[(int) EAttribute.Colors].Enabled = lod.VertexColors is { Length: > 0};
_vertexAttributes[(int) EAttribute.BonesId].Enabled =
_vertexAttributes[(int) EAttribute.BonesWeight].Enabled = vertices is CSkelMeshVertex[];
Indices = new uint[lod.Indices.Value.Length];
for (int i = 0; i < Indices.Length; i++)
{
Indices[i] = (uint) lod.Indices.Value[i];
}
Vertices = new float[lod.NumVerts * VertexSize];
for (int i = 0; i < vertices.Count; i++)
{
var count = 0;
var baseIndex = i * VertexSize;
var vert = vertices[i];
Vertices[baseIndex + count++] = i;
Vertices[baseIndex + count++] = vert.Position.X * Constants.SCALE_DOWN_RATIO;
Vertices[baseIndex + count++] = vert.Position.Z * Constants.SCALE_DOWN_RATIO;
Vertices[baseIndex + count++] = vert.Position.Y * Constants.SCALE_DOWN_RATIO;
Vertices[baseIndex + count++] = vert.Normal.X;
Vertices[baseIndex + count++] = vert.Normal.Z;
Vertices[baseIndex + count++] = vert.Normal.Y;
Vertices[baseIndex + count++] = vert.Tangent.X;
Vertices[baseIndex + count++] = vert.Tangent.Z;
Vertices[baseIndex + count++] = vert.Tangent.Y;
Vertices[baseIndex + count++] = vert.UV.U;
Vertices[baseIndex + count++] = vert.UV.V;
Vertices[baseIndex + count++] = hasCustomUvs ? lod.ExtraUV.Value[0][i].U : .5f;
if (HasVertexColors)
{
var color = lod.VertexColors[i];
Vertices[baseIndex + count++] = color.R;
Vertices[baseIndex + count++] = color.G;
Vertices[baseIndex + count++] = color.B;
Vertices[baseIndex + count++] = color.A;
}
if (vert is CSkelMeshVertex skelVert)
{
var weightsHash = skelVert.UnpackWeights();
Vertices[baseIndex + count++] = skelVert.Bone[0];
Vertices[baseIndex + count++] = skelVert.Bone[1];
Vertices[baseIndex + count++] = skelVert.Bone[2];
Vertices[baseIndex + count++] = skelVert.Bone[3];
Vertices[baseIndex + count++] = weightsHash[0];
Vertices[baseIndex + count++] = weightsHash[1];
Vertices[baseIndex + count++] = weightsHash[2];
Vertices[baseIndex + count++] = weightsHash[3];
}
}
Sections = new Section[lod.Sections.Value.Length];
for (var s = 0; s < Sections.Length; s++)
{
var section = lod.Sections.Value[s];
Sections[s] = new Section(section.MaterialIndex, section.NumFaces * _faceSize, section.FirstIndex);
if (section.IsValid) Sections[s].SetupMaterial(Materials[section.MaterialIndex]);
}
var t = transform ?? Transform.Identity;
_previousMatrix = t.Matrix;
AddInstance(t);
}
public void AddInstance(Transform transform)
{
SelectedInstance = TransformsCount;
TransformsCount++;
Transforms.Add(transform);
}
public void UpdateMatrices(Options options)
{
var worldMatrix = UpdateMatrices();
foreach (var socket in Sockets)
{
var boneMatrix = Matrix4x4.Identity;
if (HasSkeleton && Skeleton.BonesByLoweredName.TryGetValue(socket.BoneName.Text.ToLower(), out var bone))
boneMatrix = Skeleton.GetBoneMatrix(bone);
var socketRelation = boneMatrix * worldMatrix;
foreach (var info in socket.AttachedModels)
{
if (!options.TryGetModel(info.Guid, out var attachedModel))
continue;
attachedModel.Transforms[info.Instance].Relation = socket.Transform.LocalMatrix * socketRelation;
attachedModel.UpdateMatrices(options);
}
}
}
private Matrix4x4 UpdateMatrices()
{
_matrixVbo.Bind();
for (int instance = 0; instance < TransformsCount; instance++)
{
var matrix = Transforms[instance].Matrix;
_matrixVbo.Update(instance, matrix);
_previousMatrix = matrix;
}
_matrixVbo.Unbind();
return _previousMatrix;
}
public void UpdateMorph(int index)
{
_morphVbo.Bind();
_morphVbo.Update(Morphs[index].Vertices);
_morphVbo.Unbind();
}
public void AttachModel(Model attachedTo, Socket socket, SocketAttachementInfo info)
{
socket.AttachedModels.Add(info);
_attachedTo = $"'{socket.Name}' from '{attachedTo.Name}'{(!socket.BoneName.IsNone ? $" at '{socket.BoneName}'" : "")}";
attachedTo._attachedFor.Add($"'{Name}'");
// reset PRS to 0 so it's attached to the actual position (can be transformed relative to the socket later by the user)
Transforms[SelectedInstance].Position = FVector.ZeroVector;
Transforms[SelectedInstance].Rotation = FQuat.Identity;
Transforms[SelectedInstance].Scale = FVector.OneVector;
}
public void DetachModel(Model attachedTo, Socket socket, SocketAttachementInfo info)
{
socket.AttachedModels.Remove(info);
SafeDetachModel(attachedTo);
}
public void SafeDetachModel(Model attachedTo)
{
_attachedTo = string.Empty;
attachedTo._attachedFor.Remove($"'{Name}'");
Transforms[SelectedInstance].Relation = _previousMatrix;
}
public void SetupInstances()
{
var instanceMatrix = new Matrix4x4[TransformsCount];
for (var i = 0; i < instanceMatrix.Length; i++)
instanceMatrix[i] = Transforms[i].Matrix;
_matrixVbo = new BufferObject<Matrix4x4>(instanceMatrix, BufferTarget.ArrayBuffer);
_vao.BindInstancing(); // VertexAttributePointer
}
public void Setup(Options options)
{
_handle = GL.CreateProgram();
var broken = GL.GetInteger(GetPName.MaxTextureCoords) == 0;
_ebo = new BufferObject<uint>(Indices, BufferTarget.ElementArrayBuffer);
_vbo = new BufferObject<float>(Vertices, BufferTarget.ArrayBuffer);
_vao = new VertexArrayObject<float, uint>(_vbo, _ebo);
var offset = 0;
for (int i = 0; i < _vertexAttributes.Count; i++)
{
var attribute = _vertexAttributes[i];
if (!attribute.Enabled) continue;
if (i != 5 || !broken)
{
_vao.VertexAttributePointer((uint) i, attribute.Size, i == 0 ? VertexAttribPointerType.Int : VertexAttribPointerType.Float, VertexSize, offset);
}
offset += attribute.Size;
}
SetupInstances(); // instanced models transform
// setup all used materials for use in different UV channels
for (var i = 0; i < Materials.Length; i++)
{
if (!Materials[i].IsUsed) continue;
Materials[i].Setup(options, broken ? 1 : UvCount);
}
if (HasSkeleton) Skeleton.Setup();
if (HasMorphTargets)
{
for (int morph = 0; morph < Morphs.Count; morph++)
{
Morphs[morph].Setup();
if (morph == 0)
_morphVbo = new BufferObject<float>(Morphs[morph].Vertices, BufferTarget.ArrayBuffer);
}
_vao.Bind();
_vao.VertexAttributePointer(13, 3, VertexAttribPointerType.Float, Morph.VertexSize, 0); // morph position
_vao.VertexAttributePointer(14, 3, VertexAttribPointerType.Float, Morph.VertexSize, 0); // morph tangent
_vao.Unbind();
}
for (int section = 0; section < Sections.Length; section++)
{
if (!Show) Show = Sections[section].Show;
}
IsSetup = true;
}
public void Render(Shader shader, bool outline = false)
{
if (outline) GL.Disable(EnableCap.DepthTest);
if (TwoSided) GL.Disable(EnableCap.CullFace);
if (IsSelected)
{
GL.Enable(EnableCap.StencilTest);
GL.StencilFunc(outline ? StencilFunction.Notequal : StencilFunction.Always, 1, 0xFF);
}
_vao.Bind();
shader.SetUniform("uMorphTime", MorphTime);
if (HasSkeleton) Skeleton.Render();
if (!outline)
{
shader.SetUniform("uUvCount", UvCount);
shader.SetUniform("uHasVertexColors", HasVertexColors);
}
GL.PolygonMode(MaterialFace.FrontAndBack, Wireframe ? PolygonMode.Line : PolygonMode.Fill);
foreach (var section in Sections)
{
if (!section.Show) continue;
if (!outline)
{
shader.SetUniform("uSectionColor", section.Color);
Materials[section.MaterialIndex].Render(shader);
}
GL.DrawElementsInstanced(PrimitiveType.Triangles, section.FacesCount, DrawElementsType.UnsignedInt, section.FirstFaceIndexPtr, TransformsCount);
}
_vao.Unbind();
if (IsSelected)
{
GL.StencilFunc(StencilFunction.Always, 0, 0xFF);
GL.Disable(EnableCap.StencilTest);
}
if (TwoSided) GL.Enable(EnableCap.CullFace);
if (outline) GL.Enable(EnableCap.DepthTest);
}
public void PickingRender(Shader shader)
{
if (TwoSided) GL.Disable(EnableCap.CullFace);
_vao.Bind();
shader.SetUniform("uMorphTime", MorphTime);
if (HasSkeleton) Skeleton.Render();
foreach (var section in Sections)
{
if (!section.Show) continue;
GL.DrawElementsInstanced(PrimitiveType.Triangles, section.FacesCount, DrawElementsType.UnsignedInt, section.FirstFaceIndexPtr, TransformsCount);
}
_vao.Unbind();
if (TwoSided) GL.Enable(EnableCap.CullFace);
}
public void Dispose()
{
_ebo.Dispose();
_vbo.Dispose();
_matrixVbo.Dispose();
_vao.Dispose();
Skeleton?.Dispose();
for (int socket = 0; socket < Sockets.Count; socket++)
{
Sockets[socket]?.Dispose();
}
Sockets.Clear();
if (HasMorphTargets) _morphVbo.Dispose();
for (var morph = 0; morph < Morphs.Count; morph++)
{
Morphs[morph]?.Dispose();
}
Morphs.Clear();
GL.DeleteProgram(_handle);
}
}

View File

@@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
using CUE4Parse_Conversion.Meshes.PSK;
using CUE4Parse.UE4.Assets.Exports.Animation;
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Views.Snooper.Animations;
using FModel.Views.Snooper.Buffers;
using FModel.Views.Snooper.Shading;
using OpenTK.Graphics.OpenGL4;
namespace FModel.Views.Snooper.Models;
public class SkeletalModel : UModel
{
private BufferObject<float> _morphVbo;
public readonly Skeleton Skeleton;
public readonly List<Morph> Morphs;
public bool HasMorphTargets => Morphs.Count > 0;
public float MorphTime;
public SkeletalModel(USkeletalMesh export, CSkeletalMesh skeletalMesh, Transform transform = null)
: base(export, skeletalMesh.LODs[LodLevel], export.Materials, skeletalMesh.LODs[LodLevel].Verts, skeletalMesh.LODs.Count, transform)
{
Box = skeletalMesh.BoundingBox * Constants.SCALE_DOWN_RATIO;
Skeleton = new Skeleton(export.ReferenceSkeleton);
var sockets = new List<FPackageIndex>();
sockets.AddRange(export.Sockets);
if (export.Skeleton.TryLoad(out USkeleton skeleton))
{
Skeleton.Name = skeleton.Name;
// Skeleton.Merge(skeleton.ReferenceSkeleton);
sockets.AddRange(skeleton.Sockets);
}
for (int i = 0; i < sockets.Count; i++)
{
if (sockets[i].Load<USkeletalMeshSocket>() is not { } socket) continue;
Sockets.Add(new Socket(socket));
}
Morphs = new List<Morph>();
for (var i = 0; i < export.MorphTargets.Length; i++)
{
if (!export.MorphTargets[i].TryLoad(out UMorphTarget morphTarget) ||
morphTarget.MorphLODModels.Length < 1 || morphTarget.MorphLODModels[0].Vertices.Length < 1)
continue;
Morphs.Add(new Morph(Vertices, VertexSize, morphTarget));
}
}
public SkeletalModel(USkeleton export, FBox box) : base(export)
{
Indices = Array.Empty<uint>();
Materials = Array.Empty<Material>();
Vertices = Array.Empty<float>();
Sections = Array.Empty<Section>();
AddInstance(Transform.Identity);
Box = box * Constants.SCALE_DOWN_RATIO;
Morphs = new List<Morph>();
Skeleton = new Skeleton(export.ReferenceSkeleton);
Skeleton.Name = export.Name;
for (int i = 0; i < export.Sockets.Length; i++)
{
if (export.Sockets[i].Load<USkeletalMeshSocket>() is not { } socket) continue;
Sockets.Add(new Socket(socket));
}
}
public override void Setup(Options options)
{
base.Setup(options);
Skeleton.Setup();
if (!HasMorphTargets) return;
for (int morph = 0; morph < Morphs.Count; morph++)
{
Morphs[morph].Setup();
if (morph == 0)
_morphVbo = new BufferObject<float>(Morphs[morph].Vertices, BufferTarget.ArrayBuffer);
}
Vao.Bind();
Vao.VertexAttributePointer(13, 3, VertexAttribPointerType.Float, Morph.VertexSize, 0); // morph position
Vao.VertexAttributePointer(14, 3, VertexAttribPointerType.Float, Morph.VertexSize, 0); // morph tangent
Vao.Unbind();
}
public void Render(Shader shader)
{
shader.SetUniform("uMorphTime", MorphTime);
Skeleton.Render(shader);
}
public void RenderBones(Shader shader)
{
shader.SetUniform("uInstanceMatrix", GetTransform().Matrix);
Skeleton.RenderBones();
}
public void UpdateMorph(int index)
{
_morphVbo.Update(Morphs[index].Vertices);
}
public override void Dispose()
{
Skeleton?.Dispose();
if (HasMorphTargets) _morphVbo.Dispose();
foreach (var morph in Morphs)
{
morph?.Dispose();
}
Morphs.Clear();
base.Dispose();
}
}

View File

@@ -87,8 +87,6 @@ public class Skybox : IDisposable
GL.DepthFunc(DepthFunction.Lequal); GL.DepthFunc(DepthFunction.Lequal);
_vao.Bind(); _vao.Bind();
_cubeMap.Bind(TextureUnit.Texture0);
_shader.Use(); _shader.Use();
viewMatrix.M41 = 0; viewMatrix.M41 = 0;
@@ -97,6 +95,7 @@ public class Skybox : IDisposable
_shader.SetUniform("uView", viewMatrix); _shader.SetUniform("uView", viewMatrix);
_shader.SetUniform("uProjection", projMatrix); _shader.SetUniform("uProjection", projMatrix);
_cubeMap.Bind(TextureUnit.Texture0);
_shader.SetUniform("cubemap", 0); _shader.SetUniform("cubemap", 0);
GL.DrawArrays(PrimitiveType.Triangles, 0, 36); GL.DrawArrays(PrimitiveType.Triangles, 0, 36);

View File

@@ -21,6 +21,7 @@ public class Socket : IDisposable
public readonly bool IsVirtual; public readonly bool IsVirtual;
public readonly List<SocketAttachementInfo> AttachedModels; public readonly List<SocketAttachementInfo> AttachedModels;
public bool IsDaron => AttachedModels.Count > 0;
private Socket() private Socket()
{ {

View File

@@ -1,14 +1,15 @@
using CUE4Parse_Conversion.Meshes.PSK; using CUE4Parse_Conversion.Meshes.PSK;
using CUE4Parse.UE4.Assets.Exports.Material; using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
using FModel.Views.Snooper.Shading; using FModel.Views.Snooper.Shading;
namespace FModel.Views.Snooper.Models; namespace FModel.Views.Snooper.Models;
public class Cube : Model public class StaticModel : UModel
{ {
public Cube(CStaticMesh mesh, UMaterialInterface unrealMaterial) : base(unrealMaterial) public StaticModel(UMaterialInterface unrealMaterial, CStaticMesh staticMesh) : base(unrealMaterial)
{ {
var lod = mesh.LODs[0]; var lod = staticMesh.LODs[LodLevel];
Indices = new uint[lod.Indices.Value.Length]; Indices = new uint[lod.Indices.Value.Length];
for (int i = 0; i < Indices.Length; i++) for (int i = 0; i < Indices.Length; i++)
@@ -44,5 +45,18 @@ public class Cube : Model
Sections[0] = new Section(0, Indices.Length, 0); Sections[0] = new Section(0, Indices.Length, 0);
AddInstance(Transform.Identity); AddInstance(Transform.Identity);
Box = staticMesh.BoundingBox * 1.5f * Constants.SCALE_DOWN_RATIO;
}
public StaticModel(UStaticMesh export, CStaticMesh staticMesh, Transform transform = null)
: base(export, staticMesh.LODs[LodLevel], export.Materials, staticMesh.LODs[LodLevel].Verts, staticMesh.LODs.Count, transform)
{
Box = staticMesh.BoundingBox * Constants.SCALE_DOWN_RATIO;
for (int i = 0; i < export.Sockets.Length; i++)
{
if (export.Sockets[i].Load<UStaticMeshSocket>() is not { } socket) continue;
Sockets.Add(new Socket(socket));
}
} }
} }

View File

@@ -0,0 +1,389 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Numerics;
using CUE4Parse_Conversion;
using CUE4Parse_Conversion.Meshes.PSK;
using CUE4Parse.UE4.Assets;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.Utils;
using FModel.Settings;
using FModel.Views.Snooper.Buffers;
using FModel.Views.Snooper.Shading;
using OpenTK.Graphics.OpenGL4;
namespace FModel.Views.Snooper.Models;
public class VertexAttribute
{
public int Size;
public bool Enabled;
}
public abstract class UModel : IRenderableModel
{
protected const int LodLevel = 0;
private readonly UObject _export;
private readonly List<VertexAttribute> _vertexAttributes = new()
{
new VertexAttribute { Size = 1, Enabled = false }, // VertexIndex
new VertexAttribute { Size = 3, Enabled = true }, // Position
new VertexAttribute { Size = 3, Enabled = false }, // Normal
new VertexAttribute { Size = 3, Enabled = false }, // Tangent
new VertexAttribute { Size = 2, Enabled = false }, // UV
new VertexAttribute { Size = 1, Enabled = false }, // TextureLayer
new VertexAttribute { Size = 4, Enabled = false }, // Colors
new VertexAttribute { Size = 4, Enabled = false }, // BoneIds
new VertexAttribute { Size = 4, Enabled = false } // BoneWeights
};
public int Handle { get; set; }
public BufferObject<uint> Ebo { get; set; }
public BufferObject<float> Vbo { get; set; }
public BufferObject<Matrix4x4> MatrixVbo { get; set; }
public VertexArrayObject<float, uint> Vao { get; set; }
public string Path { get; }
public string Name { get; }
public string Type { get; }
public int UvCount { get; }
public uint[] Indices { get; set; }
public float[] Vertices { get; set; }
public Section[] Sections { get; set; }
public List<Transform> Transforms { get; }
public Attachment Attachments { get; }
public FBox Box;
public readonly List<Socket> Sockets;
public Material[] Materials;
public bool IsTwoSided;
public bool IsProp;
public int VertexSize => _vertexAttributes.Where(x => x.Enabled).Sum(x => x.Size);
public bool HasVertexColors => _vertexAttributes[(int) EAttribute.Colors].Enabled;
public bool HasSockets => Sockets.Count > 0;
public int TransformsCount => Transforms.Count;
public bool IsSetup { get; set; }
public bool IsVisible { get; set; }
public bool IsSelected { get; set; }
public bool ShowWireframe { get; set; }
public int SelectedInstance;
protected UModel()
{
_export = null;
UvCount = 1;
Box = new FBox(new FVector(-2f), new FVector(2f));
Sockets = new List<Socket>();
Transforms = new List<Transform>();
}
protected UModel(UObject export)
{
_export = export;
Path = _export.GetPathName();
Name = Path.SubstringAfterLast('/').SubstringBefore('.');
Type = export.ExportType;
UvCount = 1;
Box = new FBox(new FVector(-2f), new FVector(2f));
Sockets = new List<Socket>();
Transforms = new List<Transform>();
Attachments = new Attachment(Name);
_vertexAttributes[(int) EAttribute.Index].Enabled =
_vertexAttributes[(int) EAttribute.Normals].Enabled =
_vertexAttributes[(int) EAttribute.Tangent].Enabled =
_vertexAttributes[(int) EAttribute.UVs].Enabled =
_vertexAttributes[(int) EAttribute.Layer].Enabled = true;
}
protected UModel(UObject export, CBaseMeshLod lod, IReadOnlyList<ResolvedObject> materials, IReadOnlyList<CMeshVertex> vertices, int numLods, Transform transform = null) : this(export)
{
var hasCustomUvs = lod.ExtraUV.IsValueCreated;
UvCount = hasCustomUvs ? Math.Max(lod.NumTexCoords, numLods) : lod.NumTexCoords;
IsTwoSided = lod.IsTwoSided;
Indices = new uint[lod.Indices.Value.Length];
for (int i = 0; i < Indices.Length; i++)
{
Indices[i] = (uint) lod.Indices.Value[i];
}
Materials = new Material[materials.Count];
for (int m = 0; m < Materials.Length; m++)
{
if ((materials[m]?.TryLoad(out var material) ?? false) && material is UMaterialInterface unrealMaterial)
Materials[m] = new Material(unrealMaterial); else Materials[m] = new Material();
}
_vertexAttributes[(int) EAttribute.Colors].Enabled = lod.VertexColors is { Length: > 0};
_vertexAttributes[(int) EAttribute.BonesId].Enabled =
_vertexAttributes[(int) EAttribute.BonesWeight].Enabled = vertices is CSkelMeshVertex[];
Vertices = new float[lod.NumVerts * VertexSize];
for (int i = 0; i < vertices.Count; i++)
{
var count = 0;
var baseIndex = i * VertexSize;
var vert = vertices[i];
Vertices[baseIndex + count++] = i;
Vertices[baseIndex + count++] = vert.Position.X * Constants.SCALE_DOWN_RATIO;
Vertices[baseIndex + count++] = vert.Position.Z * Constants.SCALE_DOWN_RATIO;
Vertices[baseIndex + count++] = vert.Position.Y * Constants.SCALE_DOWN_RATIO;
Vertices[baseIndex + count++] = vert.Normal.X;
Vertices[baseIndex + count++] = vert.Normal.Z;
Vertices[baseIndex + count++] = vert.Normal.Y;
Vertices[baseIndex + count++] = vert.Tangent.X;
Vertices[baseIndex + count++] = vert.Tangent.Z;
Vertices[baseIndex + count++] = vert.Tangent.Y;
Vertices[baseIndex + count++] = vert.UV.U;
Vertices[baseIndex + count++] = vert.UV.V;
Vertices[baseIndex + count++] = hasCustomUvs ? lod.ExtraUV.Value[0][i].U : .5f;
if (HasVertexColors)
{
var color = lod.VertexColors[i];
Vertices[baseIndex + count++] = color.R;
Vertices[baseIndex + count++] = color.G;
Vertices[baseIndex + count++] = color.B;
Vertices[baseIndex + count++] = color.A;
}
if (vert is CSkelMeshVertex skelVert)
{
var weightsHash = skelVert.UnpackWeights();
Vertices[baseIndex + count++] = skelVert.Bone[0];
Vertices[baseIndex + count++] = skelVert.Bone[1];
Vertices[baseIndex + count++] = skelVert.Bone[2];
Vertices[baseIndex + count++] = skelVert.Bone[3];
Vertices[baseIndex + count++] = weightsHash[0];
Vertices[baseIndex + count++] = weightsHash[1];
Vertices[baseIndex + count++] = weightsHash[2];
Vertices[baseIndex + count++] = weightsHash[3];
}
}
Sections = new Section[lod.Sections.Value.Length];
for (var s = 0; s < Sections.Length; s++)
{
var section = lod.Sections.Value[s];
Sections[s] = new Section(section.MaterialIndex, section.NumFaces * 3, section.FirstIndex);
if (section.IsValid) Sections[s].SetupMaterial(Materials[section.MaterialIndex]);
}
AddInstance(transform ?? Transform.Identity);
}
public virtual void Setup(Options options)
{
Handle = GL.CreateProgram();
Ebo = new BufferObject<uint>(Indices, BufferTarget.ElementArrayBuffer);
Vbo = new BufferObject<float>(Vertices, BufferTarget.ArrayBuffer);
Vao = new VertexArrayObject<float, uint>(Vbo, Ebo);
var offset = 0;
var broken = GL.GetInteger(GetPName.MaxTextureCoords) == 0;
for (int i = 0; i < _vertexAttributes.Count; i++)
{
var attribute = _vertexAttributes[i];
if (!attribute.Enabled) continue;
if (i != 5 || !broken)
{
Vao.VertexAttributePointer((uint) i, attribute.Size, i == 0 ? VertexAttribPointerType.Int : VertexAttribPointerType.Float, VertexSize, offset);
}
offset += attribute.Size;
}
SetupInstances(); // instanced models transform
// setup all used materials for use in different UV channels
for (var i = 0; i < Materials.Length; i++)
{
if (!Materials[i].IsUsed) continue;
Materials[i].Setup(options, broken ? 1 : UvCount);
}
if (options.Models.Count == 1 && Sections.All(x => !x.Show))
{
IsVisible = true;
foreach (var section in Sections)
{
section.Show = true;
}
}
else foreach (var section in Sections)
{
if (!IsVisible) IsVisible = section.Show;
}
IsSetup = true;
}
public virtual void Render(Shader shader, Texture checker = null, bool outline = false)
{
if (outline) GL.Disable(EnableCap.DepthTest);
if (IsTwoSided) GL.Disable(EnableCap.CullFace);
if (IsSelected)
{
GL.Enable(EnableCap.StencilTest);
GL.StencilFunc(outline ? StencilFunction.Notequal : StencilFunction.Always, 1, 0xFF);
}
if (this is SkeletalModel skeletalModel) skeletalModel.Render(shader);
else shader.SetUniform("uIsAnimated", false);
if (!outline)
{
shader.SetUniform("uUvCount", UvCount);
shader.SetUniform("uHasVertexColors", HasVertexColors);
}
Vao.Bind();
GL.PolygonMode(MaterialFace.FrontAndBack, ShowWireframe ? PolygonMode.Line : PolygonMode.Fill);
foreach (var section in Sections)
{
if (!section.Show) continue;
if (!outline)
{
if (checker != null)
{
shader.SetUniform("uParameters.Diffuse[0].Sampler", 0);
checker.Bind(TextureUnit.Texture0);
}
else
{
shader.SetUniform("uSectionColor", section.Color);
Materials[section.MaterialIndex].Render(shader);
}
}
GL.DrawElementsInstanced(PrimitiveType.Triangles, section.FacesCount, DrawElementsType.UnsignedInt, section.FirstFaceIndexPtr, TransformsCount);
}
GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill);
Vao.Unbind();
if (IsSelected)
{
GL.StencilFunc(StencilFunction.Always, 0, 0xFF);
GL.Disable(EnableCap.StencilTest);
}
if (IsTwoSided) GL.Enable(EnableCap.CullFace);
if (outline) GL.Enable(EnableCap.DepthTest);
}
public void PickingRender(Shader shader)
{
if (IsTwoSided) GL.Disable(EnableCap.CullFace);
if (this is SkeletalModel skeletalModel)
skeletalModel.Render(shader);
else shader.SetUniform("uIsAnimated", false);
Vao.Bind();
foreach (var section in Sections)
{
if (!section.Show) continue;
GL.DrawElementsInstanced(PrimitiveType.Triangles, section.FacesCount, DrawElementsType.UnsignedInt, section.FirstFaceIndexPtr, TransformsCount);
}
Vao.Unbind();
if (IsTwoSided) GL.Enable(EnableCap.CullFace);
}
public void Update(Options options)
{
MatrixVbo.Bind();
for (int instance = 0; instance < TransformsCount; instance++)
{
MatrixVbo.Update(instance, Transforms[instance].Matrix);
}
MatrixVbo.Unbind();
var worldMatrix = GetTransform().Matrix;
foreach (var socket in Sockets)
{
if (!socket.IsDaron) continue;
var boneMatrix = Matrix4x4.Identity;
if (this is SkeletalModel skeletalModel && skeletalModel.Skeleton.BonesByLoweredName.TryGetValue(socket.BoneName.Text.ToLower(), out var bone))
boneMatrix = skeletalModel.Skeleton.GetBoneMatrix(bone);
var socketRelation = boneMatrix * worldMatrix;
foreach (var info in socket.AttachedModels)
{
if (!options.TryGetModel(info.Guid, out var attachedModel))
continue;
attachedModel.Transforms[info.Instance].Relation = socket.Transform.LocalMatrix * socketRelation;
attachedModel.Update(options);
}
}
}
public void AddInstance(Transform transform)
{
SelectedInstance = TransformsCount;
Transforms.Add(transform);
}
public void SetupInstances()
{
MatrixVbo = new BufferObject<Matrix4x4>(TransformsCount, BufferTarget.ArrayBuffer);
for (int instance = 0; instance < TransformsCount; instance++)
{
MatrixVbo.Update(instance, Transforms[instance].Matrix);
}
Vao.BindInstancing(); // VertexAttributePointer
}
public Transform GetTransform() => Transforms[SelectedInstance];
public Matrix4x4 GetSocketTransform(int index)
{
var socket = Sockets[index];
var worldMatrix = GetTransform().Matrix;
var boneMatrix = Matrix4x4.Identity;
if (this is SkeletalModel skeletalModel && skeletalModel.Skeleton.BonesByLoweredName.TryGetValue(socket.BoneName.Text.ToLower(), out var bone))
boneMatrix = skeletalModel.Skeleton.GetBoneMatrix(bone);
var socketRelation = boneMatrix * worldMatrix;
return socket.Transform.LocalMatrix * socketRelation;
}
public bool Save(out string label, out string savedFilePath)
{
var exportOptions = new ExporterOptions
{
LodFormat = UserSettings.Default.LodExportFormat,
MeshFormat = UserSettings.Default.MeshExportFormat,
MaterialFormat = UserSettings.Default.MaterialExportFormat,
TextureFormat = UserSettings.Default.TextureExportFormat,
SocketFormat = UserSettings.Default.SocketExportFormat,
Platform = UserSettings.Default.CurrentDir.TexturePlatform,
ExportMorphTargets = UserSettings.Default.SaveMorphTargets,
ExportMaterials = UserSettings.Default.SaveEmbeddedMaterials
};
var toSave = new Exporter(_export, exportOptions);
return toSave.TryWriteToDir(new DirectoryInfo(UserSettings.Default.ModelDirectory), out label, out savedFilePath);
}
public virtual void Dispose()
{
Ebo?.Dispose();
Vbo?.Dispose();
MatrixVbo?.Dispose();
Vao?.Dispose();
foreach (var socket in Sockets)
{
socket?.Dispose();
}
Sockets.Clear();
GL.DeleteProgram(Handle);
}
}

View File

@@ -1,10 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using CUE4Parse_Conversion;
using CUE4Parse_Conversion.Textures; using CUE4Parse_Conversion.Textures;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Texture; using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Objects.Core.Misc; using CUE4Parse.UE4.Objects.Core.Misc;
using FModel.Settings; using FModel.Settings;
@@ -22,7 +19,7 @@ public class Options
public int SelectedMorph { get; private set; } public int SelectedMorph { get; private set; }
public int SelectedAnimation{ get; private set; } public int SelectedAnimation{ get; private set; }
public readonly Dictionary<FGuid, Model> Models; public readonly Dictionary<FGuid, UModel> Models;
public readonly Dictionary<FGuid, Texture> Textures; public readonly Dictionary<FGuid, Texture> Textures;
public readonly List<Light> Lights; public readonly List<Light> Lights;
@@ -31,12 +28,11 @@ public class Options
public readonly Dictionary<string, Texture> Icons; public readonly Dictionary<string, Texture> Icons;
private readonly ETexturePlatform _platform;
private readonly string _game; private readonly string _game;
public Options() public Options()
{ {
Models = new Dictionary<FGuid, Model>(); Models = new Dictionary<FGuid, UModel>();
Textures = new Dictionary<FGuid, Texture>(); Textures = new Dictionary<FGuid, Texture>();
Lights = new List<Light>(); Lights = new List<Light>();
@@ -47,6 +43,7 @@ public class Options
{ {
["material"] = new ("materialicon"), ["material"] = new ("materialicon"),
["noimage"] = new ("T_Placeholder_Item_Image"), ["noimage"] = new ("T_Placeholder_Item_Image"),
["checker"] = new ("checker"),
["pointlight"] = new ("pointlight"), ["pointlight"] = new ("pointlight"),
["spotlight"] = new ("spotlight"), ["spotlight"] = new ("spotlight"),
["link_on"] = new ("link_on"), ["link_on"] = new ("link_on"),
@@ -60,8 +57,7 @@ public class Options
["tl_next"] = new ("tl_next"), ["tl_next"] = new ("tl_next"),
}; };
_platform = UserSettings.Default.OverridedPlatform; _game = Services.ApplicationService.ApplicationView.CUE4Parse.Provider.InternalGameName.ToUpper();
_game = Services.ApplicationService.ApplicationView.CUE4Parse.Provider.GameName.ToUpper();
SelectModel(Guid.Empty); SelectModel(Guid.Empty);
} }
@@ -107,27 +103,30 @@ public class Options
public void RemoveModel(FGuid guid) public void RemoveModel(FGuid guid)
{ {
if (!TryGetModel(guid, out var model)) return; if (!TryGetModel(guid, out var m) || m is not UModel model)
return;
DetachAndRemoveModels(model, true); DetachAndRemoveModels(model, true);
model.Dispose(); model.Dispose();
Models.Remove(guid); Models.Remove(guid);
} }
private void DetachAndRemoveModels(Model model, bool detach) private void DetachAndRemoveModels(UModel model, bool detach)
{ {
foreach (var socket in model.Sockets.ToList()) foreach (var socket in model.Sockets.ToList())
{ {
foreach (var info in socket.AttachedModels) foreach (var info in socket.AttachedModels)
{ {
if (!TryGetModel(info.Guid, out var attachedModel)) continue; if (!TryGetModel(info.Guid, out var m) || m is not UModel attachedModel)
continue;
if (attachedModel.IsAnimatedProp) var t = attachedModel.GetTransform();
if (attachedModel.IsProp)
{ {
attachedModel.SafeDetachModel(model); attachedModel.Attachments.SafeDetach(model, t);
RemoveModel(info.Guid); RemoveModel(info.Guid);
} }
else if (detach) attachedModel.SafeDetachModel(model); else if (detach) attachedModel.Attachments.SafeDetach(model, t);
} }
if (socket.IsVirtual) if (socket.IsVirtual)
@@ -152,17 +151,20 @@ public class Options
{ {
foreach (var guid in animation.AttachedModels) foreach (var guid in animation.AttachedModels)
{ {
if (!TryGetModel(guid, out var animatedModel)) continue; if (!TryGetModel(guid, out var model) || model is not SkeletalModel animatedModel)
continue;
animatedModel.Skeleton.ResetAnimatedData(true); animatedModel.Skeleton.ResetAnimatedData(true);
DetachAndRemoveModels(animatedModel, false); DetachAndRemoveModels(animatedModel, false);
} }
animation.Dispose(); animation.Dispose();
} }
foreach (var kvp in Models.ToList().Where(kvp => kvp.Value.IsAnimatedProp))
{ foreach (var kvp in Models)
RemoveModel(kvp.Key); if (kvp.Value.IsProp)
} RemoveModel(kvp.Key);
Animations.Clear(); Animations.Clear();
} }
@@ -171,7 +173,7 @@ public class Options
SelectedSection = index; SelectedSection = index;
} }
public void SelectMorph(int index, Model model) public void SelectMorph(int index, SkeletalModel model)
{ {
SelectedMorph = index; SelectedMorph = index;
model.UpdateMorph(SelectedMorph); model.UpdateMorph(SelectedMorph);
@@ -180,19 +182,19 @@ public class Options
public bool TryGetTexture(UTexture2D o, bool fix, out Texture texture) public bool TryGetTexture(UTexture2D o, bool fix, out Texture texture)
{ {
var guid = o.LightingGuid; var guid = o.LightingGuid;
if (!Textures.TryGetValue(guid, out texture) && o.GetMipByMaxSize(UserSettings.Default.PreviewMaxTextureSize) is { } mip) if (!Textures.TryGetValue(guid, out texture) &&
o.Decode(UserSettings.Default.PreviewMaxTextureSize, UserSettings.Default.CurrentDir.TexturePlatform) is { } bitmap)
{ {
TextureDecoder.DecodeTexture(mip, o.Format, o.isNormalMap, _platform, out var data, out _); texture = new Texture(bitmap, o);
texture = new Texture(data, mip.SizeX, mip.SizeY, o);
if (fix) TextureHelper.FixChannels(_game, texture); if (fix) TextureHelper.FixChannels(_game, texture);
Textures[guid] = texture; Textures[guid] = texture;
bitmap.Dispose();
} }
return texture != null; return texture != null;
} }
public bool TryGetModel(out Model model) => Models.TryGetValue(SelectedModel, out model); public bool TryGetModel(out UModel model) => Models.TryGetValue(SelectedModel, out model);
public bool TryGetModel(FGuid guid, out Model model) => Models.TryGetValue(guid, out model); public bool TryGetModel(FGuid guid, out UModel model) => Models.TryGetValue(guid, out model);
public bool TryGetSection(out Section section) => TryGetSection(SelectedModel, out section); public bool TryGetSection(out Section section) => TryGetSection(SelectedModel, out section);
public bool TryGetSection(FGuid guid, out Section section) public bool TryGetSection(FGuid guid, out Section section)
@@ -205,7 +207,7 @@ public class Options
section = null; section = null;
return false; return false;
} }
public bool TryGetSection(Model model, out Section section) public bool TryGetSection(UModel model, out Section section)
{ {
if (SelectedSection >= 0 && SelectedSection < model.Sections.Length) if (SelectedSection >= 0 && SelectedSection < model.Sections.Length)
section = model.Sections[SelectedSection]; else section = null; section = model.Sections[SelectedSection]; else section = null;
@@ -222,22 +224,6 @@ public class Options
Services.ApplicationService.ApplicationView.CUE4Parse.ModelIsWaitingAnimation = value; Services.ApplicationService.ApplicationView.CUE4Parse.ModelIsWaitingAnimation = value;
} }
public bool TrySave(UObject export, out string label, out string savedFilePath)
{
var exportOptions = new ExporterOptions
{
LodFormat = UserSettings.Default.LodExportFormat,
MeshFormat = UserSettings.Default.MeshExportFormat,
MaterialFormat = UserSettings.Default.MaterialExportFormat,
TextureFormat = UserSettings.Default.TextureExportFormat,
SocketFormat = UserSettings.Default.SocketExportFormat,
Platform = UserSettings.Default.OverridedPlatform,
ExportMorphTargets = UserSettings.Default.SaveMorphTargets
};
var toSave = new Exporter(export, exportOptions);
return toSave.TryWriteToDir(new DirectoryInfo(UserSettings.Default.ModelDirectory), out label, out savedFilePath);
}
public void ResetModelsLightsAnimations() public void ResetModelsLightsAnimations()
{ {
foreach (var model in Models.Values) foreach (var model in Models.Values)

View File

@@ -24,6 +24,8 @@ using FModel.Views.Snooper.Buffers;
using FModel.Views.Snooper.Lights; using FModel.Views.Snooper.Lights;
using FModel.Views.Snooper.Models; using FModel.Views.Snooper.Models;
using FModel.Views.Snooper.Shading; using FModel.Views.Snooper.Shading;
using OpenTK.Windowing.GraphicsLibraryFramework;
using UModel = FModel.Views.Snooper.Models.UModel;
namespace FModel.Views.Snooper; namespace FModel.Views.Snooper;
@@ -43,12 +45,14 @@ public class Renderer : IDisposable
private Shader _shader; private Shader _shader;
private Shader _outline; private Shader _outline;
private Shader _light; private Shader _light;
private Shader _bone;
private bool _saveCameraMode; private bool _saveCameraMode;
public bool ShowSkybox; public bool ShowSkybox;
public bool ShowGrid; public bool ShowGrid;
public bool ShowLights; public bool ShowLights;
public bool AnimateWithRotationOnly; public bool AnimateWithRotationOnly;
public bool IsSkeletonTreeOpen;
public VertexColor Color; public VertexColor Color;
public Camera CameraOp { get; } public Camera CameraOp { get; }
@@ -83,6 +87,9 @@ public class Renderer : IDisposable
case USkeletalMesh sk: case USkeletalMesh sk:
LoadSkeletalMesh(sk); LoadSkeletalMesh(sk);
break; break;
case USkeleton skel:
LoadSkeleton(skel);
break;
case UMaterialInstance mi: case UMaterialInstance mi:
LoadMaterialInstance(mi); LoadMaterialInstance(mi);
break; break;
@@ -90,6 +97,7 @@ public class Renderer : IDisposable
LoadWorld(cancellationToken, wd, Transform.Identity); LoadWorld(cancellationToken, wd, Transform.Identity);
break; break;
} }
SetupCamera();
} }
public void Swap(UMaterialInstance unrealMaterial) public void Swap(UMaterialInstance unrealMaterial)
@@ -103,7 +111,7 @@ public class Renderer : IDisposable
public void Animate(UObject anim) => Animate(anim, Options.SelectedModel); public void Animate(UObject anim) => Animate(anim, Options.SelectedModel);
private void Animate(UObject anim, FGuid guid) private void Animate(UObject anim, FGuid guid)
{ {
if (!Options.TryGetModel(guid, out var model) || !model.HasSkeleton) if (!Options.TryGetModel(guid, out var m) || m is not SkeletalModel model)
return; return;
float maxElapsedTime; float maxElapsedTime;
@@ -114,7 +122,7 @@ public class Renderer : IDisposable
var animSet = skeleton.ConvertAnims(animSequence); var animSet = skeleton.ConvertAnims(animSequence);
var animation = new Animation(animSequence, animSet, guid); var animation = new Animation(animSequence, animSet, guid);
maxElapsedTime = animation.TotalElapsedTime; maxElapsedTime = animation.TotalElapsedTime;
model.Skeleton.Animate(animSet, AnimateWithRotationOnly); model.Skeleton.Animate(animSet);
Options.AddAnimation(animation); Options.AddAnimation(animation);
break; break;
} }
@@ -123,7 +131,7 @@ public class Renderer : IDisposable
var animSet = skeleton.ConvertAnims(animMontage); var animSet = skeleton.ConvertAnims(animMontage);
var animation = new Animation(animMontage, animSet, guid); var animation = new Animation(animMontage, animSet, guid);
maxElapsedTime = animation.TotalElapsedTime; maxElapsedTime = animation.TotalElapsedTime;
model.Skeleton.Animate(animSet, AnimateWithRotationOnly); model.Skeleton.Animate(animSet);
Options.AddAnimation(animation); Options.AddAnimation(animation);
foreach (var notifyEvent in animMontage.Notifies) foreach (var notifyEvent in animMontage.Notifies)
@@ -140,32 +148,39 @@ public class Renderer : IDisposable
t.Scale = offset.Scale3D; t.Scale = offset.Scale3D;
} }
UModel addedModel = null;
switch (export) switch (export)
{ {
case UStaticMesh st: case UStaticMesh st:
{ {
guid = st.LightingGuid; guid = st.LightingGuid;
if (Options.TryGetModel(guid, out var instancedModel)) if (Options.TryGetModel(guid, out addedModel))
instancedModel.AddInstance(t); {
addedModel.AddInstance(t);
}
else if (st.TryConvert(out var mesh)) else if (st.TryConvert(out var mesh))
Options.Models[guid] = new Model(st, mesh, t); {
addedModel = new StaticModel(st, mesh, t);
Options.Models[guid] = addedModel;
}
break; break;
} }
case USkeletalMesh sk: case USkeletalMesh sk:
{ {
guid = Guid.NewGuid(); guid = Guid.NewGuid();
if (!Options.Models.ContainsKey(guid) && sk.TryConvert(out var mesh)) if (!Options.Models.ContainsKey(guid) && sk.TryConvert(out var mesh))
Options.Models[guid] = new Model(sk, mesh, t); {
addedModel = new SkeletalModel(sk, mesh, t);
Options.Models[guid] = addedModel;
}
break; break;
} }
default:
throw new ArgumentException();
} }
if (!Options.TryGetModel(guid, out var addedModel)) if (addedModel == null)
continue; throw new ArgumentException("Unknown model type");
addedModel.IsAnimatedProp = true; addedModel.IsProp = true;
if (notifyClass.TryGetValue(out UObject skeletalMeshPropAnimation, "SkeletalMeshPropAnimation", "Animation")) if (notifyClass.TryGetValue(out UObject skeletalMeshPropAnimation, "SkeletalMeshPropAnimation", "Animation"))
Animate(skeletalMeshPropAnimation, guid); Animate(skeletalMeshPropAnimation, guid);
if (notifyClass.TryGetValue(out FName socketName, "SocketName")) if (notifyClass.TryGetValue(out FName socketName, "SocketName"))
@@ -178,9 +193,10 @@ public class Renderer : IDisposable
if (notifyClass.TryGetValue(out FVector scale, "Scale")) if (notifyClass.TryGetValue(out FVector scale, "Scale"))
t.Scale = scale; t.Scale = scale;
var s = new Socket($"TL_{addedModel.Name}", socketName, t, true); var s = new Socket($"ANIM_{addedModel.Name}", socketName, t, true);
model.Sockets.Add(s); model.Sockets.Add(s);
addedModel.AttachModel(model, s, new SocketAttachementInfo { Guid = guid, Instance = addedModel.SelectedInstance }); addedModel.Attachments.Attach(model, addedModel.GetTransform(), s,
new SocketAttachementInfo { Guid = guid, Instance = addedModel.SelectedInstance });
} }
} }
break; break;
@@ -190,7 +206,7 @@ public class Renderer : IDisposable
var animSet = skeleton.ConvertAnims(animComposite); var animSet = skeleton.ConvertAnims(animComposite);
var animation = new Animation(animComposite, animSet, guid); var animation = new Animation(animComposite, animSet, guid);
maxElapsedTime = animation.TotalElapsedTime; maxElapsedTime = animation.TotalElapsedTime;
model.Skeleton.Animate(animSet, AnimateWithRotationOnly); model.Skeleton.Animate(animSet);
Options.AddAnimation(animation); Options.AddAnimation(animation);
break; break;
} }
@@ -210,12 +226,13 @@ public class Renderer : IDisposable
_shader = new Shader(); _shader = new Shader();
_outline = new Shader("outline"); _outline = new Shader("outline");
_light = new Shader("light"); _light = new Shader("light");
_bone = new Shader("bone");
Picking.Setup(); Picking.Setup();
Options.SetupModelsAndLights(); Options.SetupModelsAndLights();
} }
public void Render(float deltaSeconds) public void Render()
{ {
var viewMatrix = CameraOp.GetViewMatrix(); var viewMatrix = CameraOp.GetViewMatrix();
var projMatrix = CameraOp.GetProjectionMatrix(); var projMatrix = CameraOp.GetProjectionMatrix();
@@ -227,23 +244,11 @@ public class Renderer : IDisposable
for (int i = 0; i < 5; i++) for (int i = 0; i < 5; i++)
_shader.SetUniform($"bVertexColors[{i}]", i == (int) Color); _shader.SetUniform($"bVertexColors[{i}]", i == (int) Color);
// update animations
if (Options.Animations.Count > 0) Options.Tracker.Update(deltaSeconds);
foreach (var animation in Options.Animations)
{
animation.TimeCalculation(Options.Tracker.ElapsedTime);
foreach (var guid in animation.AttachedModels.Where(guid => Options.Models[guid].HasSkeleton))
{
Options.Models[guid].Skeleton.UpdateAnimationMatrices(animation.CurrentSequence, animation.FrameInSequence);
}
}
// render model pass // render model pass
foreach (var model in Options.Models.Values) foreach (var model in Options.Models.Values)
{ {
model.UpdateMatrices(Options); if (!model.IsVisible) continue;
if (!model.Show) continue; model.Render(_shader, Color == VertexColor.TextureCoordinates ? Options.Icons["checker"] : null);
model.Render(_shader);
} }
{ // light pass { // light pass
@@ -259,17 +264,67 @@ public class Renderer : IDisposable
Options.Lights[i].Render(_light); Options.Lights[i].Render(_light);
} }
// outline pass // debug + outline pass
if (Options.TryGetModel(out var selected) && selected.Show) if (Options.TryGetModel(out var selected) && selected.IsVisible)
{ {
if (IsSkeletonTreeOpen && selected is SkeletalModel skeletalModel)
{
_bone.Render(viewMatrix, projMatrix);
skeletalModel.RenderBones(_bone);
}
_outline.Render(viewMatrix, CameraOp.Position, projMatrix); _outline.Render(viewMatrix, CameraOp.Position, projMatrix);
selected.Render(_outline, true); selected.Render(_outline, Color == VertexColor.TextureCoordinates ? Options.Icons["checker"] : null, true);
} }
// picking pass (dedicated FBO, binding to 0 afterward) // picking pass (dedicated FBO, binding to 0 afterward)
Picking.Render(viewMatrix, projMatrix, Options.Models); Picking.Render(viewMatrix, projMatrix, Options.Models);
} }
public void Update(Snooper wnd, float deltaSeconds)
{
if (Options.Animations.Count > 0) Options.Tracker.Update(deltaSeconds);
foreach (var animation in Options.Animations)
{
animation.TimeCalculation(Options.Tracker.ElapsedTime);
foreach (var guid in animation.AttachedModels)
{
if (Options.Models[guid] is not SkeletalModel skeletalModel) continue;
skeletalModel.Skeleton.UpdateAnimationMatrices(animation, AnimateWithRotationOnly);
}
}
{
foreach (var model in Options.Models.Values)
{
model.Update(Options);
}
if (IsSkeletonTreeOpen && Options.TryGetModel(out var selected) && selected is SkeletalModel { IsVisible: true } skeletalModel)
{
skeletalModel.Skeleton.UpdateVertices();
}
}
CameraOp.Modify(wnd.KeyboardState, deltaSeconds);
if (wnd.KeyboardState.IsKeyPressed(Keys.Z) &&
Options.TryGetModel(out var selectedModel) &&
selectedModel is SkeletalModel)
{
Options.RemoveAnimations();
Options.AnimateMesh(true);
wnd.WindowShouldClose(true, false);
}
if (wnd.KeyboardState.IsKeyPressed(Keys.Space))
Options.Tracker.IsPaused = !Options.Tracker.IsPaused;
if (wnd.KeyboardState.IsKeyPressed(Keys.Delete))
Options.RemoveModel(Options.SelectedModel);
if (wnd.KeyboardState.IsKeyPressed(Keys.H))
wnd.WindowShouldClose(true, false);
if (wnd.KeyboardState.IsKeyPressed(Keys.Escape))
wnd.WindowShouldClose(true, true);
}
private void LoadStaticMesh(UStaticMesh original) private void LoadStaticMesh(UStaticMesh original)
{ {
var guid = original.LightingGuid; var guid = original.LightingGuid;
@@ -283,9 +338,8 @@ public class Renderer : IDisposable
if (!original.TryConvert(out var mesh)) if (!original.TryConvert(out var mesh))
return; return;
Options.Models[guid] = new Model(original, mesh); Options.Models[guid] = new StaticModel(original, mesh);
Options.SelectModel(guid); Options.SelectModel(guid);
SetupCamera(Options.Models[guid].Box);
} }
private void LoadSkeletalMesh(USkeletalMesh original) private void LoadSkeletalMesh(USkeletalMesh original)
@@ -293,14 +347,25 @@ public class Renderer : IDisposable
var guid = new FGuid((uint) original.GetFullName().GetHashCode()); var guid = new FGuid((uint) original.GetFullName().GetHashCode());
if (Options.Models.ContainsKey(guid) || !original.TryConvert(out var mesh)) return; if (Options.Models.ContainsKey(guid) || !original.TryConvert(out var mesh)) return;
Options.Models[guid] = new Model(original, mesh); var skeletalModel = new SkeletalModel(original, mesh);
Options.Models[guid] = skeletalModel;
Options.SelectModel(guid); Options.SelectModel(guid);
SetupCamera(Options.Models[guid].Box); }
private void LoadSkeleton(USkeleton original)
{
var guid = original.Guid;
if (Options.Models.ContainsKey(guid) || !original.TryConvert(out _, out var box)) return;
var fakeSkeletalModel = new SkeletalModel(original, box);
Options.Models[guid] = fakeSkeletalModel;
Options.SelectModel(guid);
IsSkeletonTreeOpen = true;
} }
private void LoadMaterialInstance(UMaterialInstance original) private void LoadMaterialInstance(UMaterialInstance original)
{ {
if (!Utils.TryLoadObject("Engine/Content/EditorMeshes/EditorCube.EditorCube", out UStaticMesh editorCube)) if (!Utils.TryLoadObject("Engine/Content/BasicShapes/Cube.Cube", out UStaticMesh editorCube))
return; return;
var guid = editorCube.LightingGuid; var guid = editorCube.LightingGuid;
@@ -314,12 +379,15 @@ public class Renderer : IDisposable
if (!editorCube.TryConvert(out var mesh)) if (!editorCube.TryConvert(out var mesh))
return; return;
Options.Models[guid] = new Cube(mesh, original); Options.Models[guid] = new StaticModel(original, mesh);
Options.SelectModel(guid); Options.SelectModel(guid);
SetupCamera(Options.Models[guid].Box);
} }
private void SetupCamera(FBox box) => CameraOp.Setup(box); private void SetupCamera()
{
if (Options.TryGetModel(out var model))
CameraOp.Setup(model.Box);
}
private void LoadWorld(CancellationToken cancellationToken, UWorld original, Transform transform) private void LoadWorld(CancellationToken cancellationToken, UWorld original, Transform transform)
{ {
@@ -400,14 +468,15 @@ public class Renderer : IDisposable
Scale = staticMeshComp.GetOrDefault("RelativeScale3D", FVector.OneVector) Scale = staticMeshComp.GetOrDefault("RelativeScale3D", FVector.OneVector)
}; };
OverrideVertexColors(staticMeshComp, m);
if (Options.TryGetModel(guid, out var model)) if (Options.TryGetModel(guid, out var model))
{ {
model.AddInstance(t); model.AddInstance(t);
} }
else if (m.TryConvert(out var mesh)) else if (m.TryConvert(out var mesh))
{ {
model = new Model(m, mesh, t); model = new StaticModel(m, mesh, t);
model.TwoSided = actor.GetOrDefault("bMirrored", staticMeshComp.GetOrDefault("bDisallowMeshPaintPerInstance", model.TwoSided)); model.IsTwoSided = actor.GetOrDefault("bMirrored", staticMeshComp.GetOrDefault("bDisallowMeshPaintPerInstance", model.IsTwoSided));
if (actor.TryGetValue(out FPackageIndex baseMaterial, "BaseMaterial") && if (actor.TryGetValue(out FPackageIndex baseMaterial, "BaseMaterial") &&
actor.TryGetAllValues(out FPackageIndex[] textureData, "TextureData")) actor.TryGetAllValues(out FPackageIndex[] textureData, "TextureData"))
@@ -473,6 +542,20 @@ public class Renderer : IDisposable
} }
} }
private void OverrideVertexColors(UStaticMeshComponent staticMeshComp, UStaticMesh staticMesh)
{
if (staticMeshComp.LODData is not { Length: > 0 } || staticMesh.RenderData is not { LODs.Length: > 0 })
return;
for (var lod = 0; lod < staticMeshComp.LODData.Length; lod++)
{
var vertexColors = staticMeshComp.LODData[lod].OverrideVertexColors;
if (vertexColors == null) continue;
staticMesh.RenderData.LODs[lod].ColorVertexBuffer = vertexColors;
}
}
private void WorldTextureData(Material material, UObject textureData, string name, string key) private void WorldTextureData(Material material, UObject textureData, string name, string key)
{ {
if (textureData.TryGetValue(out FPackageIndex package, name) && package.Load() is UTexture2D texture) if (textureData.TryGetValue(out FPackageIndex package, name) && package.Load() is UTexture2D texture)
@@ -524,6 +607,7 @@ public class Renderer : IDisposable
_shader?.Dispose(); _shader?.Dispose();
_outline?.Dispose(); _outline?.Dispose();
_light?.Dispose(); _light?.Dispose();
_bone?.Dispose();
Picking?.Dispose(); Picking?.Dispose();
Options?.Dispose(); Options?.Dispose();
} }

View File

@@ -76,11 +76,11 @@ public class Material : IDisposable
if (uvCount < 1 || Parameters.IsNull) if (uvCount < 1 || Parameters.IsNull)
{ {
Diffuse = new[] { new Texture(new FLinearColor(1f, 0f, 0f, 1f)) }; Diffuse = new[] { options.Icons["checker"] };
Normals = new[] { new Texture(new FLinearColor(0.498f, 0.498f, 0.996f, 1f)) }; Normals = new[] { new Texture(new FLinearColor(0.498f, 0.498f, 0.996f, 1f)) };
SpecularMasks = new [] { new Texture(new FLinearColor(1f, 0.5f, 0.5f, 1f)) }; SpecularMasks = new [] { new Texture(new FLinearColor(1f, 0.5f, 0.5f, 1f)) };
Emissive = new Texture[1]; Emissive = new Texture[1];
DiffuseColor = new[] { new Vector4(0.5f) }; DiffuseColor = new[] { Vector4.One };
EmissiveColor = new[] { Vector4.One }; EmissiveColor = new[] { Vector4.One };
} }
else else
@@ -98,7 +98,7 @@ public class Material : IDisposable
} }
{ // ambient occlusion + color boost { // ambient occlusion + color boost
if (Parameters.TryGetTexture2d(out var original, "M", "AEM", "AO") && if (Parameters.TryGetTexture2d(out var original, "M", "AEM", "AO", "Mask") &&
!original.Name.Equals("T_BlackMask") && options.TryGetTexture(original, false, out var transformed)) !original.Name.Equals("T_BlackMask") && options.TryGetTexture(original, false, out var transformed))
{ {
HasAo = true; HasAo = true;
@@ -132,6 +132,22 @@ public class Material : IDisposable
"Emissive 2 UV Positioning (RG)UpperLeft (BA)LowerRight", "Emissive 2 UV Positioning (RG)UpperLeft (BA)LowerRight",
"EmissiveUVPositioning (RG)UpperLeft (BA)LowerRight")) "EmissiveUVPositioning (RG)UpperLeft (BA)LowerRight"))
EmissiveRegion = new Vector4(EmissiveUVs.R, EmissiveUVs.G, EmissiveUVs.B, EmissiveUVs.A); EmissiveRegion = new Vector4(EmissiveUVs.R, EmissiveUVs.G, EmissiveUVs.B, EmissiveUVs.A);
if ((Parameters.TryGetSwitch(out var swizzleRoughnessToGreen, "SwizzleRoughnessToGreen") && swizzleRoughnessToGreen) ||
Parameters.Textures.ContainsKey("SRM"))
{
foreach (var specMask in SpecularMasks)
{
specMask.SwizzleMask = new []
{
(int) PixelFormat.Red,
(int) PixelFormat.Blue,
(int) PixelFormat.Green,
(int) PixelFormat.Alpha
};
specMask.Swizzle();
}
}
} }
} }
} }
@@ -301,7 +317,7 @@ public class Material : IDisposable
} }
} }
public bool ImGuiTextures(Dictionary<string, Texture> icons, Model model) public bool ImGuiTextures(Dictionary<string, Texture> icons, UModel model)
{ {
if (ImGui.BeginTable("material_textures", 2)) if (ImGui.BeginTable("material_textures", 2))
{ {

View File

@@ -8,6 +8,7 @@ using ImGuiNET;
using OpenTK.Graphics.OpenGL4; using OpenTK.Graphics.OpenGL4;
using SixLabors.ImageSharp; using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SkiaSharp;
namespace FModel.Views.Snooper.Shading; namespace FModel.Views.Snooper.Shading;
@@ -78,7 +79,7 @@ public class Texture : IDisposable
GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, _target, _handle, 0); GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, _target, _handle, 0);
} }
public Texture(byte[] data, int width, int height, UTexture2D texture2D) : this(TextureType.Normal) public Texture(SKBitmap bitmap, UTexture2D texture2D) : this(TextureType.Normal)
{ {
Type = texture2D.ExportType; Type = texture2D.ExportType;
Guid = texture2D.LightingGuid; Guid = texture2D.LightingGuid;
@@ -87,11 +88,11 @@ public class Texture : IDisposable
Format = texture2D.Format; Format = texture2D.Format;
ImportedWidth = texture2D.ImportedSize.X; ImportedWidth = texture2D.ImportedSize.X;
ImportedHeight = texture2D.ImportedSize.Y; ImportedHeight = texture2D.ImportedSize.Y;
Width = width; Width = bitmap.Width;
Height = height; Height = bitmap.Height;
Bind(TextureUnit.Texture0); Bind(TextureUnit.Texture0);
GL.TexImage2D(_target, 0, texture2D.SRGB ? PixelInternalFormat.Srgb : PixelInternalFormat.Rgb, Width, Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, data); GL.TexImage2D(_target, 0, texture2D.SRGB ? PixelInternalFormat.Srgb : PixelInternalFormat.Rgb, Width, Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, bitmap.Bytes);
GL.TexParameter(_target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.LinearMipmapLinear); GL.TexParameter(_target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.LinearMipmapLinear);
GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear); GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear);
GL.TexParameter(_target, TextureParameterName.TextureBaseLevel, 0); GL.TexParameter(_target, TextureParameterName.TextureBaseLevel, 0);

View File

@@ -66,7 +66,7 @@ public class SnimGui
private readonly float _tableWidth; private readonly float _tableWidth;
private Vector2 _outlinerSize; private Vector2 _outlinerSize;
private bool _ti_open; private bool _tiOpen;
private bool _viewportFocus; private bool _viewportFocus;
private readonly Vector4 _accentColor = new (0.125f, 0.42f, 0.831f, 1.0f); private readonly Vector4 _accentColor = new (0.125f, 0.42f, 0.831f, 1.0f);
@@ -89,7 +89,7 @@ public class SnimGui
public void Render(Snooper s) public void Render(Snooper s)
{ {
Controller.SemiBold(); Controller.SemiBold();
DrawDockSpace(s.Size); DrawDockSpace(s.ClientSize);
SectionWindow("Material Inspector", s.Renderer, DrawMaterialInspector, false); SectionWindow("Material Inspector", s.Renderer, DrawMaterialInspector, false);
AnimationWindow("Timeline", s.Renderer, (icons, tracker, animations) => AnimationWindow("Timeline", s.Renderer, (icons, tracker, animations) =>
@@ -103,9 +103,11 @@ public class SnimGui
Draw3DViewport(s); Draw3DViewport(s);
DrawNavbar(); DrawNavbar();
DrawTextureInspector(s);
DrawSkeletonTree(s);
DrawModals(s); DrawModals(s);
if (_ti_open) DrawTextureInspector(s);
Controller.Render(); Controller.Render();
} }
@@ -181,7 +183,7 @@ public class SnimGui
{ {
foreach (var model in s.Renderer.Options.Models.Values) foreach (var model in s.Renderer.Options.Models.Values)
{ {
b |= s.Renderer.Options.TrySave(model.Export, out _, out _); b |= model.Save(out _, out _);
} }
} }
}); });
@@ -210,52 +212,43 @@ public class SnimGui
ImGui.EndTable(); ImGui.EndTable();
} }
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); ImGui.SeparatorText("Editor");
if (ImGui.CollapsingHeader("Editor")) if (ImGui.BeginTable("world_editor", 2))
{ {
if (ImGui.BeginTable("world_editor", 2)) Layout("Skybox");ImGui.PushID(1);
{ ImGui.Checkbox("", ref s.Renderer.ShowSkybox);
Layout("Skybox");ImGui.PushID(1); ImGui.PopID();Layout("Grid");ImGui.PushID(2);
ImGui.Checkbox("", ref s.Renderer.ShowSkybox); ImGui.Checkbox("", ref s.Renderer.ShowGrid);
ImGui.PopID();Layout("Grid");ImGui.PushID(2); ImGui.PopID();Layout("Lights");ImGui.PushID(3);
ImGui.Checkbox("", ref s.Renderer.ShowGrid); ImGui.Checkbox("", ref s.Renderer.ShowLights);
ImGui.PopID();Layout("Lights");ImGui.PushID(3); ImGui.PopID();Layout("Animate With Rotation Only");ImGui.PushID(4);
ImGui.Checkbox("", ref s.Renderer.ShowLights); ImGui.Checkbox("", ref s.Renderer.AnimateWithRotationOnly);
ImGui.PopID();Layout("Animate With Rotation Only");ImGui.PushID(4); ImGui.PopID();Layout("Vertex Colors");ImGui.PushID(5);
ImGui.Checkbox("", ref s.Renderer.AnimateWithRotationOnly); var c = (int) s.Renderer.Color;
ImGui.PopID();Layout("Vertex Colors");ImGui.PushID(5); ImGui.Combo("vertex_colors", ref c,
var c = (int) s.Renderer.Color; "Default\0Sections\0Colors\0Normals\0Texture Coordinates\0");
ImGui.Combo("vertex_colors", ref c, s.Renderer.Color = (VertexColor) c;
"Default\0Sections\0Colors\0Normals\0Texture Coordinates\0"); ImGui.PopID();
s.Renderer.Color = (VertexColor) c;
ImGui.PopID();
ImGui.EndTable();
}
ImGui.SeparatorText("Camera");
s.Renderer.CameraOp.ImGuiCamera();
ImGui.SeparatorText("Lights");
for (int i = 0; i < s.Renderer.Options.Lights.Count; i++)
{
var light = s.Renderer.Options.Lights[i];
var id = s.Renderer.Options.TryGetModel(light.Model, out var lightModel) ? lightModel.Name : "None";
id += $"##{i}";
if (ImGui.TreeNode(id) && ImGui.BeginTable(id, 2))
{
s.Renderer.Options.SelectModel(light.Model);
light.ImGuiLight();
ImGui.EndTable(); ImGui.EndTable();
} ImGui.TreePop();
}
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.CollapsingHeader("Camera"))
{
s.Renderer.CameraOp.ImGuiCamera();
}
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.CollapsingHeader("Lights"))
{
for (int i = 0; i < s.Renderer.Options.Lights.Count; i++)
{
var light = s.Renderer.Options.Lights[i];
var id = s.Renderer.Options.TryGetModel(light.Model, out var lightModel) ? lightModel.Name : "None";
id += $"##{i}";
if (ImGui.TreeNode(id) && ImGui.BeginTable(id, 2))
{
s.Renderer.Options.SelectModel(light.Model);
light.ImGuiLight();
ImGui.EndTable();
ImGui.TreePop();
}
} }
} }
} }
@@ -409,16 +402,16 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
ImGui.TableHeadersRow(); ImGui.TableHeadersRow();
var i = 0; var i = 0;
foreach ((FGuid guid, Model model) in s.Renderer.Options.Models) foreach ((var guid, var model) in s.Renderer.Options.Models)
{ {
ImGui.PushID(i); ImGui.PushID(i);
ImGui.TableNextRow(); ImGui.TableNextRow();
ImGui.TableNextColumn(); ImGui.TableNextColumn();
if (!model.Show) if (!model.IsVisible)
ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, ImGui.GetColorU32(new Vector4(1, 0, 0, .5f))); ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, ImGui.GetColorU32(new Vector4(1, 0, 0, .5f)));
else if (model.IsAttachment) else if (model.Attachments.IsAttachment)
ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, ImGui.GetColorU32(new Vector4(0, .75f, 0, .5f))); ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, ImGui.GetColorU32(new Vector4(0, .75f, 0, .5f)));
else if (model.IsAttached) else if (model.Attachments.IsAttached)
ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, ImGui.GetColorU32(new Vector4(1, 1, 0, .5f))); ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, ImGui.GetColorU32(new Vector4(1, 1, 0, .5f)));
ImGui.Text(model.TransformsCount.ToString("D")); ImGui.Text(model.TransformsCount.ToString("D"));
@@ -432,16 +425,16 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
Popup(() => Popup(() =>
{ {
s.Renderer.Options.SelectModel(guid); s.Renderer.Options.SelectModel(guid);
if (ImGui.MenuItem("Show", null, model.Show)) model.Show = !model.Show; if (ImGui.MenuItem("Show", null, model.IsVisible)) model.IsVisible = !model.IsVisible;
if (ImGui.MenuItem("Wireframe", null, model.Wireframe)) model.Wireframe = !model.Wireframe; if (ImGui.MenuItem("Wireframe", null, model.ShowWireframe)) model.ShowWireframe = !model.ShowWireframe;
ImGui.Separator(); ImGui.Separator();
if (ImGui.MenuItem("Save")) if (ImGui.MenuItem("Save"))
{ {
s.WindowShouldFreeze(true); s.WindowShouldFreeze(true);
_saver.Value = s.Renderer.Options.TrySave(model.Export, out _saver.Label, out _saver.Path); _saver.Value = model.Save(out _saver.Label, out _saver.Path);
s.WindowShouldFreeze(false); s.WindowShouldFreeze(false);
} }
if (ImGui.MenuItem("Animate", model.HasSkeleton)) if (ImGui.MenuItem("Animate", model is SkeletalModel))
{ {
if (_swapper.IsAware) if (_swapper.IsAware)
{ {
@@ -461,10 +454,14 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
_swapper.Value = true; _swapper.Value = true;
} }
} }
if (ImGui.MenuItem("Skeleton Tree", model is SkeletalModel))
{
s.Renderer.IsSkeletonTreeOpen = true;
ImGui.SetWindowFocus("Skeleton Tree");
}
if (ImGui.MenuItem("Teleport To")) if (ImGui.MenuItem("Teleport To"))
{ {
var instancePos = model.Transforms[model.SelectedInstance].Matrix.Translation; s.Renderer.CameraOp.Teleport(model.GetTransform().Matrix.Translation, model.Box);
s.Renderer.CameraOp.Teleport(instancePos, model.Box);
} }
if (ImGui.MenuItem("Delete")) s.Renderer.Options.RemoveModel(guid); if (ImGui.MenuItem("Delete")) s.Renderer.Options.RemoveModel(guid);
@@ -474,8 +471,8 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
}); });
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.Image(s.Renderer.Options.Icons[model.AttachIcon].GetPointer(), new Vector2(_tableWidth)); ImGui.Image(s.Renderer.Options.Icons[model.Attachments.Icon].GetPointer(), new Vector2(_tableWidth));
TooltipCopy(model.AttachTooltip); TooltipCopy(model.Attachments.Tooltip);
ImGui.PopID(); ImGui.PopID();
i++; i++;
@@ -502,14 +499,14 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
{ {
var isAttached = socket.AttachedModels.Contains(info); var isAttached = socket.AttachedModels.Contains(info);
ImGui.PushID(i); ImGui.PushID(i);
ImGui.BeginDisabled(selectedModel.IsAttached && !isAttached); ImGui.BeginDisabled(selectedModel.Attachments.IsAttached && !isAttached);
switch (isAttached) switch (isAttached)
{ {
case false when ImGui.Button($"Attach to '{socket.Name}'"): case false when ImGui.Button($"Attach to '{socket.Name}'"):
selectedModel.AttachModel(model, socket, info); selectedModel.Attachments.Attach(model, selectedModel.GetTransform(), socket, info);
break; break;
case true when ImGui.Button($"Detach from '{socket.Name}'"): case true when ImGui.Button($"Detach from '{socket.Name}'"):
selectedModel.DetachModel(model, socket, info); selectedModel.Attachments.Detach(model, selectedModel.GetTransform(), socket, info);
break; break;
} }
ImGui.EndDisabled(); ImGui.EndDisabled();
@@ -533,14 +530,14 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
{ {
Layout("Entity");ImGui.Text($" : ({model.Type}) {model.Name}"); Layout("Entity");ImGui.Text($" : ({model.Type}) {model.Name}");
Layout("Guid");ImGui.Text($" : {s.Renderer.Options.SelectedModel.ToString(EGuidFormats.UniqueObjectGuid)}"); Layout("Guid");ImGui.Text($" : {s.Renderer.Options.SelectedModel.ToString(EGuidFormats.UniqueObjectGuid)}");
if (model.HasSkeleton) if (model is SkeletalModel skeletalModel)
{ {
Layout("Skeleton");ImGui.Text($" : {model.Skeleton.Name}"); Layout("Skeleton");ImGui.Text($" : {skeletalModel.Skeleton.Name}");
Layout("Bones");ImGui.Text($" : x{model.Skeleton.BoneCount}"); Layout("Bones");ImGui.Text($" : x{skeletalModel.Skeleton.BoneCount}");
} }
else else
{ {
Layout("Two Sided");ImGui.Text($" : {model.TwoSided}"); Layout("Two Sided");ImGui.Text($" : {model.IsTwoSided}");
} }
Layout("Sockets");ImGui.Text($" : x{model.Sockets.Count}"); Layout("Sockets");ImGui.Text($" : x{model.Sockets.Count}");
@@ -621,7 +618,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
if (ImGui.BeginTabItem("Morph Targets")) if (ImGui.BeginTabItem("Morph Targets"))
{ {
if (model.HasMorphTargets) if (model is SkeletalModel { HasMorphTargets: true } skeletalModel)
{ {
const float width = 10; const float width = 10;
var region = ImGui.GetContentRegionAvail(); var region = ImGui.GetContentRegionAvail();
@@ -629,12 +626,12 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
if (ImGui.BeginListBox("", box)) if (ImGui.BeginListBox("", box))
{ {
for (int i = 0; i < model.Morphs.Count; i++) for (int i = 0; i < skeletalModel.Morphs.Count; i++)
{ {
ImGui.PushID(i); ImGui.PushID(i);
if (ImGui.Selectable(model.Morphs[i].Name, s.Renderer.Options.SelectedMorph == i)) if (ImGui.Selectable(skeletalModel.Morphs[i].Name, s.Renderer.Options.SelectedMorph == i))
{ {
s.Renderer.Options.SelectMorph(i, model); s.Renderer.Options.SelectMorph(i, skeletalModel);
} }
ImGui.PopID(); ImGui.PopID();
} }
@@ -642,10 +639,10 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(2f, 0f)); ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(2f, 0f));
ImGui.SameLine(); ImGui.PushID(99); ImGui.SameLine(); ImGui.PushID(99);
ImGui.VSliderFloat("", box with { X = width }, ref model.MorphTime, 0.0f, 1.0f, "", ImGuiSliderFlags.AlwaysClamp); ImGui.VSliderFloat("", box with { X = width }, ref skeletalModel.MorphTime, 0.0f, 1.0f, "", ImGuiSliderFlags.AlwaysClamp);
ImGui.PopID(); ImGui.PopStyleVar(); ImGui.PopID(); ImGui.PopStyleVar();
ImGui.Spacing(); ImGui.Spacing();
ImGui.Text($"Time: {model.MorphTime:P}%"); ImGui.Text($"Time: {skeletalModel.MorphTime:P}%");
} }
} }
else CenteredTextColored(_errorColor, "Selected Mesh Has No Morph Targets"); else CenteredTextColored(_errorColor, "Selected Mesh Has No Morph Targets");
@@ -656,7 +653,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
ImGui.PopStyleVar(); ImGui.PopStyleVar();
} }
private void DrawMaterialInspector(Dictionary<string, Texture> icons, Model model, Section section) private void DrawMaterialInspector(Dictionary<string, Texture> icons, UModel model, Section section)
{ {
var material = model.Materials[section.MaterialIndex]; var material = model.Materials[section.MaterialIndex];
@@ -665,61 +662,56 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
ImGui.SameLine(); ImGui.AlignTextToFramePadding(); ImGui.Text(material.Name); ImGui.SameLine(); ImGui.AlignTextToFramePadding(); ImGui.Text(material.Name);
ImGui.Spacing(); ImGui.Spacing();
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); ImGui.SeparatorText("Parameters");
if (ImGui.CollapsingHeader("Parameters")) material.ImGuiParameters();
{
material.ImGuiParameters();
}
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); ImGui.SeparatorText("Textures");
if (ImGui.CollapsingHeader("Textures") && material.ImGuiTextures(icons, model)) if (material.ImGuiTextures(icons, model))
{ {
_ti_open = true; _tiOpen = true;
ImGui.SetWindowFocus("Texture Inspector"); ImGui.SetWindowFocus("Texture Inspector");
} }
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); ImGui.SeparatorText("Properties");
if (ImGui.CollapsingHeader("Properties")) NoFramePaddingOnY(() =>
{ {
NoFramePaddingOnY(() => ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.TreeNode("Base"))
{ {
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); material.ImGuiBaseProperties("base");
if (ImGui.TreeNode("Base")) ImGui.TreePop();
{ }
material.ImGuiBaseProperties("base");
ImGui.TreePop();
}
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.TreeNode("Scalars")) if (ImGui.TreeNode("Scalars"))
{ {
material.ImGuiDictionaries("scalars", material.Parameters.Scalars, true); material.ImGuiDictionaries("scalars", material.Parameters.Scalars, true);
ImGui.TreePop(); ImGui.TreePop();
} }
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.TreeNode("Switches")) if (ImGui.TreeNode("Switches"))
{ {
material.ImGuiDictionaries("switches", material.Parameters.Switches, true); material.ImGuiDictionaries("switches", material.Parameters.Switches, true);
ImGui.TreePop(); ImGui.TreePop();
} }
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.TreeNode("Colors")) if (ImGui.TreeNode("Colors"))
{ {
material.ImGuiColors(material.Parameters.Colors); material.ImGuiColors(material.Parameters.Colors);
ImGui.TreePop(); ImGui.TreePop();
} }
if (ImGui.TreeNode("All Textures")) if (ImGui.TreeNode("All Textures"))
{ {
material.ImGuiDictionaries("textures", material.Parameters.Textures); material.ImGuiDictionaries("textures", material.Parameters.Textures);
ImGui.TreePop(); ImGui.TreePop();
} }
}); });
}
} }
private void DrawTextureInspector(Snooper s) private void DrawTextureInspector(Snooper s)
{ {
if (ImGui.Begin("Texture Inspector", ref _ti_open, ImGuiWindowFlags.NoScrollbar) && if (!_tiOpen) return;
if (ImGui.Begin("Texture Inspector", ref _tiOpen, ImGuiWindowFlags.NoScrollbar) &&
s.Renderer.Options.TryGetModel(out var model) && s.Renderer.Options.TryGetModel(out var model) &&
s.Renderer.Options.TryGetSection(model, out var section)) s.Renderer.Options.TryGetSection(model, out var section))
{ {
@@ -728,6 +720,27 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
ImGui.End(); // if window is collapsed ImGui.End(); // if window is collapsed
} }
private void DrawSkeletonTree(Snooper s)
{
if (!s.Renderer.IsSkeletonTreeOpen) return;
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
if (ImGui.Begin("Skeleton Tree", ref s.Renderer.IsSkeletonTreeOpen, ImGuiWindowFlags.NoScrollbar) &&
s.Renderer.Options.TryGetModel(out var model) && model is SkeletalModel skeletalModel)
{
skeletalModel.Skeleton.ImGuiBoneBreadcrumb();
if (ImGui.BeginTable("skeleton_tree", 2, ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.RowBg, ImGui.GetContentRegionAvail(), ImGui.GetWindowWidth()))
{
ImGui.TableSetupColumn("Bone", ImGuiTableColumnFlags.WidthStretch);
ImGui.TableSetupColumn("", ImGuiTableColumnFlags.NoHeaderWidth | ImGuiTableColumnFlags.WidthFixed, _tableWidth);
skeletalModel.Skeleton.ImGuiBoneHierarchy();
ImGui.EndTable();
}
}
ImGui.End(); // if window is collapsed
ImGui.PopStyleVar();
}
private void Draw3DViewport(Snooper s) private void Draw3DViewport(Snooper s)
{ {
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero); ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
@@ -818,7 +831,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
} }
} }
private void MeshWindow(string name, Renderer renderer, Action<Dictionary<string, Texture>, Model> content, bool styled = true) private void MeshWindow(string name, Renderer renderer, Action<Dictionary<string, Texture>, UModel> content, bool styled = true)
{ {
Window(name, () => Window(name, () =>
{ {
@@ -827,7 +840,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
}, styled); }, styled);
} }
private void SectionWindow(string name, Renderer renderer, Action<Dictionary<string, Texture>, Model, Section> content, bool styled = true) private void SectionWindow(string name, Renderer renderer, Action<Dictionary<string, Texture>, UModel, Section> content, bool styled = true)
{ {
MeshWindow(name, renderer, (icons, model) => MeshWindow(name, renderer, (icons, model) =>
{ {

View File

@@ -5,7 +5,6 @@ using System.Threading;
using System.Windows.Forms; using System.Windows.Forms;
using CUE4Parse.UE4.Assets.Exports; using CUE4Parse.UE4.Assets.Exports;
using FModel.Views.Snooper.Buffers; using FModel.Views.Snooper.Buffers;
using ImGuiNET;
using OpenTK.Graphics.OpenGL4; using OpenTK.Graphics.OpenGL4;
using OpenTK.Windowing.Common; using OpenTK.Windowing.Common;
using OpenTK.Windowing.Common.Input; using OpenTK.Windowing.Common.Input;
@@ -14,7 +13,6 @@ using OpenTK.Windowing.GraphicsLibraryFramework;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using Application = System.Windows.Application; using Application = System.Windows.Application;
using Keys = OpenTK.Windowing.GraphicsLibraryFramework.Keys;
namespace FModel.Views.Snooper; namespace FModel.Views.Snooper;
@@ -104,6 +102,7 @@ public class Snooper : GameWindow
GL.Enable(EnableCap.CullFace); GL.Enable(EnableCap.CullFace);
GL.Enable(EnableCap.DepthTest); GL.Enable(EnableCap.DepthTest);
GL.Enable(EnableCap.Multisample); GL.Enable(EnableCap.Multisample);
GL.Enable(EnableCap.VertexProgramPointSize);
GL.StencilOp(StencilOp.Keep, StencilOp.Replace, StencilOp.Replace); GL.StencilOp(StencilOp.Keep, StencilOp.Replace, StencilOp.Replace);
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha); GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
@@ -112,33 +111,40 @@ public class Snooper : GameWindow
_init = true; _init = true;
} }
private void ClearWhatHasBeenDrawn()
{
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit);
}
protected override void OnRenderFrame(FrameEventArgs args) protected override void OnRenderFrame(FrameEventArgs args)
{ {
base.OnRenderFrame(args); base.OnRenderFrame(args);
if (!IsVisible) if (!IsVisible)
return; return;
var delta = (float) args.Time;
ClearWhatHasBeenDrawn(); // clear window background ClearWhatHasBeenDrawn(); // clear window background
_gui.Controller.Update(this, delta);
_gui.Render(this);
Framebuffer.Bind(); // switch to viewport background Framebuffer.Bind(); // switch to viewport background
ClearWhatHasBeenDrawn(); // clear viewport background ClearWhatHasBeenDrawn(); // clear viewport background
Renderer.Render(delta); Renderer.Render(); // render everything
Framebuffer.BindMsaa(); Framebuffer.BindMsaa();
Framebuffer.Bind(0); // switch to window background Framebuffer.Bind(0); // switch to window background
_gui.Render(this); // render UI
SwapBuffers(); SwapBuffers();
} }
private void ClearWhatHasBeenDrawn() protected override void OnUpdateFrame(FrameEventArgs e)
{ {
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit); base.OnUpdateFrame(e);
GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill); if (!IsVisible)
return;
var delta = (float) e.Time;
_gui.Controller.Update(this, delta);
Renderer.Update(this, delta);
} }
protected override void OnTextInput(TextInputEventArgs e) protected override void OnTextInput(TextInputEventArgs e)
@@ -150,33 +156,6 @@ public class Snooper : GameWindow
_gui.Controller.PressChar((char) e.Unicode); _gui.Controller.PressChar((char) e.Unicode);
} }
protected override void OnUpdateFrame(FrameEventArgs e)
{
base.OnUpdateFrame(e);
if (!IsVisible || ImGui.GetIO().WantTextInput)
return;
var delta = (float) e.Time;
Renderer.CameraOp.Modify(KeyboardState, delta);
if (KeyboardState.IsKeyPressed(Keys.Z) &&
Renderer.Options.TryGetModel(out var selectedModel) &&
selectedModel.HasSkeleton)
{
Renderer.Options.RemoveAnimations();
Renderer.Options.AnimateMesh(true);
WindowShouldClose(true, false);
}
if (KeyboardState.IsKeyPressed(Keys.Space))
Renderer.Options.Tracker.IsPaused = !Renderer.Options.Tracker.IsPaused;
if (KeyboardState.IsKeyPressed(Keys.Delete))
Renderer.Options.RemoveModel(Renderer.Options.SelectedModel);
if (KeyboardState.IsKeyPressed(Keys.H))
WindowShouldClose(true, false);
if (KeyboardState.IsKeyPressed(Keys.Escape))
WindowShouldClose(true, true);
}
protected override void OnResize(ResizeEventArgs e) protected override void OnResize(ResizeEventArgs e)
{ {
base.OnResize(e); base.OnResize(e);