mirror of
https://github.com/4sval/FModel.git
synced 2026-03-22 01:34:37 -05:00
commit
a83de461d4
|
|
@ -1 +1 @@
|
|||
Subproject commit 2cdf9d61e66253f746f6a6e5b23b57979fbaab69
|
||||
Subproject commit d816fe61ac8e5798d1584ea2f9871acfca0ca429
|
||||
|
|
@ -41,6 +41,8 @@ public partial class App
|
|||
{
|
||||
UserSettings.Default = JsonConvert.DeserializeObject<UserSettings>(
|
||||
File.ReadAllText(UserSettings.FilePath), JsonNetSerializer.SerializerSettings);
|
||||
|
||||
/*if (UserSettings.Default.ShowChangelog) */MigrateV1Games();
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
|
@ -141,6 +143,17 @@ public partial class App
|
|||
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()
|
||||
{
|
||||
var productName = string.Empty;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ public static class Constants
|
|||
public const string YELLOW = "#E5C07B";
|
||||
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 DISCORD_LINK = "https://fmodel.app/discord";
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ public class BaseIconStats : BaseIcon
|
|||
foreach (var poi in challengeMapPoiData)
|
||||
{
|
||||
if (!poi.TryGetValue(out FStructFallback locationTag, "LocationTag") || !locationTag.TryGetValue(out FName tagName, "TagName") ||
|
||||
!tagName.Text.Equals(location.Text, StringComparison.OrdinalIgnoreCase) || !poi.TryGetValue(out FText text, "Text")) continue;
|
||||
tagName != location.TagName || !poi.TryGetValue(out FText text, "Text")) continue;
|
||||
|
||||
locationName = text.Text;
|
||||
break;
|
||||
|
|
@ -92,7 +92,11 @@ public class BaseIconStats : BaseIcon
|
|||
_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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -160,9 +160,9 @@ public class CreatorPackage : IDisposable
|
|||
case "FortChallengeBundleItemDefinition":
|
||||
creator = new BaseBundle(_object, _style);
|
||||
return true;
|
||||
case "AthenaSeasonItemDefinition":
|
||||
creator = new BaseSeason(_object, _style);
|
||||
return true;
|
||||
// case "AthenaSeasonItemDefinition":
|
||||
// creator = new BaseSeason(_object, _style);
|
||||
// return true;
|
||||
case "FortItemAccessTokenType":
|
||||
creator = new BaseItemAccessToken(_object, _style);
|
||||
return true;
|
||||
|
|
@ -203,69 +203,6 @@ public class CreatorPackage : IDisposable
|
|||
case "QuestData":
|
||||
creator = new Bases.MV.BaseQuest(_object, _style);
|
||||
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:
|
||||
creator = null;
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ public class Typefaces
|
|||
private const string _BURBANK_BIG_REGULAR_BOLD = "BurbankBigRegular-Bold"; // official fortnite ig
|
||||
private const string _BURBANK_SMALL_MEDIUM = "BurbankSmall-Medium";
|
||||
private const string _DROID_SANS_FORTNITE_SUBSET = "DroidSans-Fortnite-Subset";
|
||||
private const string _NIS_JYAU = "NIS_JYAU"; // japanese fortnite
|
||||
private const string _NOTO_COLOR_EMOJI = "NotoColorEmoji";
|
||||
private const string _NOTO_SANS_BOLD = "NotoSans-Bold";
|
||||
private const string _NOTO_SANS_FORTNITE_BOLD = "NotoSans-Fortnite-Bold";
|
||||
|
|
@ -32,7 +31,7 @@ public class Typefaces
|
|||
private const string _NOTO_SANS_ARABIC_BLACK = "NotoSansArabic-Black"; // arabic fortnite
|
||||
private const string _NOTO_SANS_ARABIC_BOLD = "NotoSansArabic-Bold";
|
||||
private const string _NOTO_SANS_ARABIC_REGULAR = "NotoSansArabic-Regular";
|
||||
private const string _NOTO_SANS_JP_BOLD = "NotoSansJP-Bold";
|
||||
private const string _NOTO_SANS_JP_BOLD = "NotoSansJP-Bold"; // japanese fortnite
|
||||
private const string _NOTO_SANS_KR_REGULAR = "NotoSansKR-Regular";
|
||||
private const string _NOTO_SANS_SC_BLACK = "NotoSansSC-Black"; // simplified chinese fortnite
|
||||
private const string _NOTO_SANS_SC_REGULAR = "NotoSansSC-Regular";
|
||||
|
|
@ -49,23 +48,6 @@ public class Typefaces
|
|||
private const string _XIANGHEHEI_SC_PRO_BLACK = "XiangHeHei_SC/MXiangHeHeiSCPro-Black";
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
switch (viewModel.Game)
|
||||
switch (viewModel.Provider.InternalGameName.ToUpperInvariant())
|
||||
{
|
||||
case FGame.FortniteGame:
|
||||
case "FORTNITEGAME":
|
||||
{
|
||||
DisplayName = OnTheFly(_FORTNITE_BASE_PATH +
|
||||
language switch
|
||||
{
|
||||
ELanguage.Korean => _ASIA_ERINM,
|
||||
ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK,
|
||||
ELanguage.Japanese => _NIS_JYAU,
|
||||
ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
|
||||
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
|
||||
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
|
||||
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
|
||||
|
|
@ -130,7 +112,7 @@ public class Typefaces
|
|||
{
|
||||
ELanguage.Korean => _ASIA_ERINM,
|
||||
ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK,
|
||||
ELanguage.Japanese => _NIS_JYAU,
|
||||
ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
|
||||
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
|
||||
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
|
||||
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
|
||||
|
|
@ -142,7 +124,7 @@ public class Typefaces
|
|||
{
|
||||
ELanguage.Korean => _ASIA_ERINM,
|
||||
ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK,
|
||||
ELanguage.Japanese => _NIS_JYAU,
|
||||
ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
|
||||
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
|
||||
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
|
||||
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
|
||||
|
|
@ -153,7 +135,7 @@ public class Typefaces
|
|||
language switch
|
||||
{
|
||||
ELanguage.Korean => _ASIA_ERINM,
|
||||
ELanguage.Japanese => _NIS_JYAU,
|
||||
ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
|
||||
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
|
||||
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
|
||||
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
|
||||
|
|
@ -164,7 +146,7 @@ public class Typefaces
|
|||
language switch
|
||||
{
|
||||
ELanguage.Korean => _ASIA_ERINM,
|
||||
ELanguage.Japanese => _NIS_JYAU,
|
||||
ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
|
||||
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
|
||||
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
|
||||
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
|
||||
|
|
@ -172,36 +154,7 @@ public class Typefaces
|
|||
} + _EXT);
|
||||
break;
|
||||
}
|
||||
case FGame.WorldExplorers:
|
||||
{
|
||||
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:
|
||||
case "MULTIVERSUS":
|
||||
{
|
||||
DisplayName = OnTheFly(_PANDAGAME_BASE_PATH + language switch
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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 GetBitmap(FSoftObjectPath softObjectPath) => GetBitmap(softObjectPath.AssetPathName.Text);
|
||||
public static SKBitmap GetBitmap(string fullPath) => TryLoadObject(fullPath, out UTexture2D texture) ? GetBitmap(texture) : null;
|
||||
public static SKBitmap GetBitmap(UTexture2D texture) => texture.IsVirtual ? null : 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 ResizeWithRatio(this SKBitmap me, double width, double height)
|
||||
|
|
|
|||
|
|
@ -52,56 +52,6 @@ public enum EDiscordRpc
|
|||
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
|
||||
{
|
||||
[Description("Single")]
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@
|
|||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ApplicationIcon>FModel.ico</ApplicationIcon>
|
||||
<Version>4.4.3.1</Version>
|
||||
<AssemblyVersion>4.4.3.1</AssemblyVersion>
|
||||
<FileVersion>4.4.3.1</FileVersion>
|
||||
<Version>4.4.3.4</Version>
|
||||
<AssemblyVersion>4.4.3.4</AssemblyVersion>
|
||||
<FileVersion>4.4.3.4</FileVersion>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsPublishable>true</IsPublishable>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
|
|
@ -77,6 +77,7 @@
|
|||
<None Remove="Resources\linux.png" />
|
||||
<None Remove="Resources\stateofdecay2.png" />
|
||||
<None Remove="Resources\T_Placeholder_Item_Image.png" />
|
||||
<None Remove="Resources\checker.png" />
|
||||
<None Remove="Resources\T_ClipSize_Weapon_Stats.png" />
|
||||
<None Remove="Resources\T_DamagePerBullet_Weapon_Stats.png" />
|
||||
<None Remove="Resources\T_ReloadTime_Weapon_Stats.png" />
|
||||
|
|
@ -107,6 +108,8 @@
|
|||
<None Remove="Resources\picking.vert" />
|
||||
<None Remove="Resources\light.frag" />
|
||||
<None Remove="Resources\light.vert" />
|
||||
<None Remove="Resources\bone.frag" />
|
||||
<None Remove="Resources\bone.vert" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -130,24 +133,26 @@
|
|||
<EmbeddedResource Include="Resources\picking.vert" />
|
||||
<EmbeddedResource Include="Resources\light.frag" />
|
||||
<EmbeddedResource Include="Resources\light.vert" />
|
||||
<EmbeddedResource Include="Resources\bone.frag" />
|
||||
<EmbeddedResource Include="Resources\bone.vert" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AdonisUI" 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="CSCore" Version="1.2.1.2" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.1.3.18" />
|
||||
<PackageReference Include="ImGui.NET" Version="1.89.5" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||
<PackageReference Include="ImGui.NET" Version="1.89.7.1" />
|
||||
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.3.5" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="NVorbis" Version="0.10.5" />
|
||||
<PackageReference Include="Oodle.NET" Version="1.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="Serilog" Version="2.12.0" />
|
||||
<PackageReference Include="Serilog" Version="3.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.0.1" />
|
||||
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.88.3" />
|
||||
|
|
@ -203,6 +208,7 @@
|
|||
<Resource Include="Resources\linux.png" />
|
||||
<Resource Include="Resources\stateofdecay2.png" />
|
||||
<Resource Include="Resources\T_Placeholder_Item_Image.png" />
|
||||
<Resource Include="Resources\checker.png" />
|
||||
<Resource Include="Resources\T_ClipSize_Weapon_Stats.png" />
|
||||
<Resource Include="Resources\T_DamagePerBullet_Weapon_Stats.png" />
|
||||
<Resource Include="Resources\T_ReloadTime_Weapon_Stats.png" />
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</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>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
|
|
@ -126,25 +126,6 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</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.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
|
|
@ -396,7 +377,7 @@
|
|||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Header="Save Directory" Click="OnSaveDirectoryClick">
|
||||
<MenuItem Header="Favorite Directory" Click="OnFavoriteDirectoryClick">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ public partial class MainWindow
|
|||
|
||||
private void OnClosing(object sender, CancelEventArgs e)
|
||||
{
|
||||
_applicationView.CustomDirectories.Save();
|
||||
_discordHandler.Dispose();
|
||||
}
|
||||
|
||||
|
|
@ -56,8 +55,8 @@ public partial class MainWindow
|
|||
case EAesReload.Always:
|
||||
await _applicationView.CUE4Parse.RefreshAes();
|
||||
break;
|
||||
case EAesReload.OncePerDay when UserSettings.Default.LastAesReload != DateTime.Today:
|
||||
UserSettings.Default.LastAesReload = DateTime.Today;
|
||||
case EAesReload.OncePerDay when UserSettings.Default.CurrentDir.LastAesReload != DateTime.Today:
|
||||
UserSettings.Default.CurrentDir.LastAesReload = DateTime.Today;
|
||||
await _applicationView.CUE4Parse.RefreshAes();
|
||||
break;
|
||||
}
|
||||
|
|
@ -84,9 +83,12 @@ public partial class MainWindow
|
|||
).ConfigureAwait(false);
|
||||
|
||||
#if DEBUG
|
||||
// await _threadWorkerView.Begin(cancellationToken =>
|
||||
// _applicationView.CUE4Parse.Extract(cancellationToken,
|
||||
// "fortnitegame/Content/Characters/Player/Female/Medium/Bodies/F_MED_Ballerina/Meshes/F_MED_Ballerina.uasset"));
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
_applicationView.CUE4Parse.Extract(cancellationToken,
|
||||
"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
|
||||
}
|
||||
|
||||
|
|
@ -140,7 +142,7 @@ public partial class MainWindow
|
|||
|
||||
private async void OnMappingsReload(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
await _applicationView.CUE4Parse.InitMappings();
|
||||
await _applicationView.CUE4Parse.InitMappings(true);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
_applicationView.CustomDirectories.Add(new CustomDirectory(folder.Header, folder.PathAtThisPoint));
|
||||
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)
|
||||
|
|
|
|||
11
FModel/Resources/bone.frag
Normal file
11
FModel/Resources/bone.frag
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#version 460 core
|
||||
|
||||
in vec3 fPos;
|
||||
in vec3 fColor;
|
||||
|
||||
out vec4 FragColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
FragColor = vec4(fColor, 1.0);
|
||||
}
|
||||
19
FModel/Resources/bone.vert
Normal file
19
FModel/Resources/bone.vert
Normal 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;
|
||||
}
|
||||
BIN
FModel/Resources/checker.png
Normal file
BIN
FModel/Resources/checker.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 125 KiB |
|
|
@ -228,7 +228,7 @@ void main()
|
|||
}
|
||||
else if (bVertexColors[4])
|
||||
{
|
||||
FragColor = vec4(fTexCoords, 0.0, 1.0);
|
||||
FragColor = SamplerToVector(uParameters.Diffuse[0].Sampler);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -16,10 +16,15 @@ layout(std430, binding = 1) buffer BoneMatrices
|
|||
{
|
||||
mat4 uFinalBonesMatrix[];
|
||||
};
|
||||
layout(std430, binding = 2) buffer RestBoneMatrices
|
||||
{
|
||||
mat4 uRestBonesMatrix[];
|
||||
};
|
||||
|
||||
uniform mat4 uView;
|
||||
uniform mat4 uProjection;
|
||||
uniform float uMorphTime;
|
||||
uniform bool uIsAnimated;
|
||||
|
||||
out vec3 fPos;
|
||||
out vec3 fNormal;
|
||||
|
|
@ -37,14 +42,14 @@ void main()
|
|||
vec4 finalPos = vec4(0.0);
|
||||
vec4 finalNormal = vec4(0.0);
|
||||
vec4 finalTangent = vec4(0.0);
|
||||
if (vBoneIds != vBoneWeights)
|
||||
if (uIsAnimated)
|
||||
{
|
||||
for(int i = 0 ; i < 4; i++)
|
||||
{
|
||||
int boneIndex = int(vBoneIds[i]);
|
||||
if(boneIndex < 0) break;
|
||||
|
||||
mat4 boneMatrix = uFinalBonesMatrix[boneIndex];
|
||||
mat4 boneMatrix = uFinalBonesMatrix[boneIndex] * inverse(uRestBonesMatrix[boneIndex]);
|
||||
mat4 inverseBoneMatrix = transpose(inverse(boneMatrix));
|
||||
float weight = vBoneWeights[i];
|
||||
|
||||
|
|
|
|||
|
|
@ -11,11 +11,16 @@ layout(std430, binding = 1) buffer BoneMatrices
|
|||
{
|
||||
mat4 uFinalBonesMatrix[];
|
||||
};
|
||||
layout(std430, binding = 2) buffer RestBoneMatrices
|
||||
{
|
||||
mat4 uRestBonesMatrix[];
|
||||
};
|
||||
|
||||
uniform mat4 uView;
|
||||
uniform vec3 uViewPos;
|
||||
uniform mat4 uProjection;
|
||||
uniform float uMorphTime;
|
||||
uniform bool uIsAnimated;
|
||||
|
||||
void main()
|
||||
{
|
||||
|
|
@ -24,14 +29,14 @@ void main()
|
|||
|
||||
vec4 finalPos = vec4(0.0);
|
||||
vec4 finalNormal = vec4(0.0);
|
||||
if (vBoneIds != vBoneWeights)
|
||||
if (uIsAnimated)
|
||||
{
|
||||
for(int i = 0 ; i < 4; i++)
|
||||
{
|
||||
int boneIndex = int(vBoneIds[i]);
|
||||
if(boneIndex < 0) break;
|
||||
|
||||
mat4 boneMatrix = uFinalBonesMatrix[boneIndex];
|
||||
mat4 boneMatrix = uFinalBonesMatrix[boneIndex] * inverse(uRestBonesMatrix[boneIndex]);
|
||||
float weight = vBoneWeights[i];
|
||||
|
||||
finalPos += boneMatrix * bindPos * weight;
|
||||
|
|
|
|||
|
|
@ -10,24 +10,29 @@ layout(std430, binding = 1) buffer BoneMatrices
|
|||
{
|
||||
mat4 uFinalBonesMatrix[];
|
||||
};
|
||||
layout(std430, binding = 2) buffer RestBoneMatrices
|
||||
{
|
||||
mat4 uRestBonesMatrix[];
|
||||
};
|
||||
|
||||
uniform mat4 uView;
|
||||
uniform mat4 uProjection;
|
||||
uniform float uMorphTime;
|
||||
uniform bool uIsAnimated;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 bindPos = vec4(mix(vPos, vMorphTargetPos, uMorphTime), 1.0);
|
||||
|
||||
vec4 finalPos = vec4(0.0);
|
||||
if (vBoneIds != vBoneWeights)
|
||||
if (uIsAnimated)
|
||||
{
|
||||
for(int i = 0 ; i < 4; i++)
|
||||
{
|
||||
int boneIndex = int(vBoneIds[i]);
|
||||
if(boneIndex < 0) break;
|
||||
|
||||
finalPos += uFinalBonesMatrix[boneIndex] * bindPos * vBoneWeights[i];
|
||||
finalPos += uFinalBonesMatrix[boneIndex] * inverse(uRestBonesMatrix[boneIndex]) * bindPos * vBoneWeights[i];
|
||||
}
|
||||
}
|
||||
else finalPos = bindPos;
|
||||
|
|
|
|||
|
|
@ -41,14 +41,14 @@ namespace FModel.Services
|
|||
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.Initialize();
|
||||
}
|
||||
|
||||
public void UpdatePresence(CUE4ParseViewModel viewModel) =>
|
||||
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());
|
||||
|
||||
public void UpdatePresence(string details, string state)
|
||||
|
|
|
|||
65
FModel/Settings/CustomDirectory.cs
Normal file
65
FModel/Settings/CustomDirectory.cs
Normal 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;
|
||||
}
|
||||
120
FModel/Settings/DirectorySettings.cs
Normal file
120
FModel/Settings/DirectorySettings.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,29 @@
|
|||
using System.Linq;
|
||||
using FModel.Framework;
|
||||
using FModel.ViewModels.ApiEndpoints;
|
||||
using Newtonsoft.Json;
|
||||
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;
|
||||
public string Url
|
||||
{
|
||||
|
|
@ -51,8 +68,8 @@ public class FEndpoint : ViewModel
|
|||
"Your endpoint configuration is valid! Please, avoid any unnecessary modifications!" :
|
||||
"Your endpoint configuration DOES NOT seem to be valid yet! Please, test it out!";
|
||||
|
||||
public FEndpoint() {}
|
||||
public FEndpoint(string url, string path)
|
||||
public EndpointSettings() {}
|
||||
public EndpointSettings(string url, string path)
|
||||
{
|
||||
Url = url;
|
||||
Path = path;
|
||||
|
|
@ -3,8 +3,6 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Objects.Core.Serialization;
|
||||
using CUE4Parse.UE4.Versions;
|
||||
using CUE4Parse_Conversion.Meshes;
|
||||
using CUE4Parse_Conversion.Textures;
|
||||
|
|
@ -33,6 +31,8 @@ namespace FModel.Settings
|
|||
|
||||
public static void Save()
|
||||
{
|
||||
if (Default == null) return;
|
||||
Default.PerDirectory[Default.CurrentDir.GameDirectory] = Default.CurrentDir;
|
||||
File.WriteAllText(FilePath, JsonConvert.SerializeObject(Default, Formatting.Indented));
|
||||
}
|
||||
|
||||
|
|
@ -41,13 +41,9 @@ namespace FModel.Settings
|
|||
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;
|
||||
if (!Default.CustomEndpoints.TryGetValue(game, out var endpoints))
|
||||
return false;
|
||||
|
||||
endpoint = endpoints[(int) type];
|
||||
endpoint = Default.CurrentDir.Endpoints[(int) type];
|
||||
return endpoint.Overwrite || endpoint.IsValid;
|
||||
}
|
||||
|
||||
|
|
@ -100,7 +96,7 @@ namespace FModel.Settings
|
|||
set => SetProperty(ref _modelDirectory, value);
|
||||
}
|
||||
|
||||
private string _gameDirectory;
|
||||
private string _gameDirectory = string.Empty;
|
||||
public string GameDirectory
|
||||
{
|
||||
get => _gameDirectory;
|
||||
|
|
@ -135,13 +131,6 @@ namespace FModel.Settings
|
|||
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;
|
||||
public string AudioDeviceId
|
||||
{
|
||||
|
|
@ -156,7 +145,7 @@ namespace FModel.Settings
|
|||
set => SetProperty(ref _audioPlayerVolume, value);
|
||||
}
|
||||
|
||||
private ELoadingMode _loadingMode = ELoadingMode.Multiple;
|
||||
private ELoadingMode _loadingMode = ELoadingMode.All;
|
||||
public ELoadingMode LoadingMode
|
||||
{
|
||||
get => _loadingMode;
|
||||
|
|
@ -233,9 +222,19 @@ namespace FModel.Settings
|
|||
set => SetProperty(ref _readScriptData, value);
|
||||
}
|
||||
|
||||
// <gameDirectory as string, settings>
|
||||
// can't refactor to use this data layout for everything
|
||||
// because it will wipe old user settings that relies on FGame
|
||||
private IDictionary<string, DirectorySettings> _perDirectory = new Dictionary<string, DirectorySettings>();
|
||||
public IDictionary<string, DirectorySettings> PerDirectory
|
||||
{
|
||||
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>();
|
||||
public IDictionary<string, GameSelectorViewModel.DetectedGame> ManualGames
|
||||
{
|
||||
|
|
@ -243,291 +242,6 @@ namespace FModel.Settings
|
|||
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};
|
||||
public AuthResponse LastAuthResponse
|
||||
{
|
||||
|
|
@ -710,6 +424,13 @@ namespace FModel.Settings
|
|||
set => SetProperty(ref _saveMorphTargets, value);
|
||||
}
|
||||
|
||||
private bool _saveEmbeddedMaterials = true;
|
||||
public bool SaveEmbeddedMaterials
|
||||
{
|
||||
get => _saveEmbeddedMaterials;
|
||||
set => SetProperty(ref _saveEmbeddedMaterials, value);
|
||||
}
|
||||
|
||||
private bool _saveSkeletonAsMesh;
|
||||
public bool SaveSkeletonAsMesh
|
||||
{
|
||||
|
|
|
|||
31
FModel/Settings/VersioningSettings.cs
Normal file
31
FModel/Settings/VersioningSettings.cs
Normal 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() {}
|
||||
}
|
||||
70
FModel/ViewModels/AboutViewModel.cs
Normal file
70
FModel/ViewModels/AboutViewModel.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -35,22 +35,7 @@ public class AesManagerViewModel : ViewModel
|
|||
{
|
||||
await _threadWorkerView.Begin(_ =>
|
||||
{
|
||||
if (_cue4Parse.Game == FGame.Unknown &&
|
||||
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
|
||||
};
|
||||
|
||||
_keysFromSettings = UserSettings.Default.CurrentDir.AesKeys;
|
||||
_mainKey.Key = Helper.FixKey(_keysFromSettings.MainKey);
|
||||
AesKeys = new FullyObservableCollection<FileItem>(EnumerateAesKeys());
|
||||
AesKeys.ItemPropertyChanged += AesKeysOnItemPropertyChanged;
|
||||
|
|
@ -105,9 +90,7 @@ public class AesManagerViewModel : ViewModel
|
|||
|
||||
public void SetAesKeys()
|
||||
{
|
||||
if (_cue4Parse.Game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(UserSettings.Default.GameDirectory))
|
||||
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].AesKeys = _keysFromSettings;
|
||||
else UserSettings.Default.AesKeys[_cue4Parse.Game] = _keysFromSettings;
|
||||
UserSettings.Default.CurrentDir.AesKeys = _keysFromSettings;
|
||||
// Log.Information("{@Json}", UserSettings.Default);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ public class FModelApiEndpoint : AbstractApiProvider
|
|||
{
|
||||
private News _news;
|
||||
private Info _infos;
|
||||
private Donator[] _donators;
|
||||
private Backup[] _backups;
|
||||
private Game _game;
|
||||
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();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
var request = new FRestRequest($"https://api.fmodel.app/v1/backups/{gameName}");
|
||||
|
|
|
|||
|
|
@ -25,6 +25,14 @@ public class Backup
|
|||
[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) + "}")]
|
||||
public class Game
|
||||
{
|
||||
|
|
|
|||
|
|
@ -48,9 +48,7 @@ public class ApplicationViewModel : ViewModel
|
|||
|
||||
public string InitialWindowTitle => $"FModel {UserSettings.Default.UpdateMode}";
|
||||
public string GameDisplayName => CUE4Parse.Provider.GameDisplayName ?? "Unknown";
|
||||
public string TitleExtra =>
|
||||
$"({(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 string TitleExtra => $"({UserSettings.Default.CurrentDir.UeVersion}){(Build != EBuildKind.Release ? $" ({Build})" : "")}";
|
||||
|
||||
public LoadingModesViewModel LoadingModes { get; }
|
||||
public CustomDirectoriesViewModel CustomDirectories { get; }
|
||||
|
|
@ -58,7 +56,6 @@ public class ApplicationViewModel : ViewModel
|
|||
public SettingsViewModel SettingsView { get; }
|
||||
public AesManagerViewModel AesManager { get; }
|
||||
public AudioPlayerViewModel AudioPlayer { get; }
|
||||
public MapViewerViewModel MapViewer { get; }
|
||||
private OodleCompressor _oodle;
|
||||
|
||||
public ApplicationViewModel()
|
||||
|
|
@ -73,50 +70,41 @@ public class ApplicationViewModel : ViewModel
|
|||
#endif
|
||||
LoadingModes = new LoadingModesViewModel();
|
||||
|
||||
AvoidEmptyGameDirectoryAndSetEGame(false);
|
||||
if (UserSettings.Default.GameDirectory is null)
|
||||
UserSettings.Default.CurrentDir = AvoidEmptyGameDirectory(false);
|
||||
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.
|
||||
//A hard exit is preferable to an unhandled expection in this case
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
CUE4Parse = new CUE4ParseViewModel(UserSettings.Default.GameDirectory);
|
||||
CustomDirectories = new CustomDirectoriesViewModel(CUE4Parse.Game, UserSettings.Default.GameDirectory);
|
||||
SettingsView = new SettingsViewModel(CUE4Parse.Game);
|
||||
CUE4Parse = new CUE4ParseViewModel();
|
||||
CustomDirectories = new CustomDirectoriesViewModel();
|
||||
SettingsView = new SettingsViewModel();
|
||||
AesManager = new AesManagerViewModel(CUE4Parse);
|
||||
MapViewer = new MapViewerViewModel(CUE4Parse);
|
||||
AudioPlayer = new AudioPlayerViewModel();
|
||||
|
||||
Status.SetStatus(EStatusKind.Ready);
|
||||
}
|
||||
|
||||
public void AvoidEmptyGameDirectoryAndSetEGame(bool bAlreadyLaunched)
|
||||
public DirectorySettings AvoidEmptyGameDirectory(bool bAlreadyLaunched)
|
||||
{
|
||||
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 result = new DirectorySelector(gameLauncherViewModel).ShowDialog();
|
||||
if (!result.HasValue || !result.Value) return;
|
||||
if (!result.HasValue || !result.Value) return null;
|
||||
|
||||
UserSettings.Default.GameDirectory = gameLauncherViewModel.SelectedDetectedGame.GameDirectory;
|
||||
if (!bAlreadyLaunched || gameDirectory == gameLauncherViewModel.SelectedDetectedGame.GameDirectory) return;
|
||||
UserSettings.Default.GameDirectory = gameLauncherViewModel.SelectedDirectory.GameDirectory;
|
||||
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();
|
||||
}
|
||||
|
||||
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));
|
||||
return null;
|
||||
}
|
||||
|
||||
public void RestartWithWarning()
|
||||
|
|
@ -161,6 +149,20 @@ public class ApplicationViewModel : ViewModel
|
|||
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()
|
||||
{
|
||||
var vgmZipFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "vgmstream-win.zip");
|
||||
|
|
|
|||
|
|
@ -551,6 +551,7 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
|
|||
case "adpcm":
|
||||
case "opus":
|
||||
case "wem":
|
||||
case "at9":
|
||||
{
|
||||
if (TryConvert(out var wavFilePath) && !string.IsNullOrEmpty(wavFilePath))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -62,11 +62,11 @@ public class CUE4ParseViewModel : ViewModel
|
|||
private readonly Regex _fnLive = new(@"^FortniteGame(/|\\)Content(/|\\)Paks(/|\\)",
|
||||
RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
|
||||
private FGame _game;
|
||||
public FGame Game
|
||||
private string _internalGameName;
|
||||
public string InternalGameName
|
||||
{
|
||||
get => _game;
|
||||
set => SetProperty(ref _game, value);
|
||||
get => _internalGameName;
|
||||
set => SetProperty(ref _internalGameName, value);
|
||||
}
|
||||
|
||||
private bool _modelIsOverwritingMaterial;
|
||||
|
|
@ -96,7 +96,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
var scale = ImGuiController.GetDpiScale();
|
||||
var htz = Snooper.GetMaxRefreshFrequency();
|
||||
return _snooper = new Snooper(
|
||||
new GameWindowSettings { RenderFrequency = htz, UpdateFrequency = htz },
|
||||
new GameWindowSettings { UpdateFrequency = htz },
|
||||
new NativeWindowSettings
|
||||
{
|
||||
Size = new OpenTK.Mathematics.Vector2i(
|
||||
|
|
@ -123,80 +123,48 @@ public class CUE4ParseViewModel : ViewModel
|
|||
public TabControlViewModel TabControl { 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)
|
||||
{
|
||||
case Constants._FN_LIVE_TRIGGER:
|
||||
{
|
||||
Game = FGame.FortniteGame;
|
||||
Provider = new StreamedFileProvider("FortniteLive", true,
|
||||
new VersionContainer(
|
||||
UserSettings.Default.OverridedGame[Game], UserSettings.Default.OverridedPlatform,
|
||||
customVersions: new FCustomVersionContainer(UserSettings.Default.OverridedCustomVersions[Game]),
|
||||
optionOverrides: UserSettings.Default.OverridedOptions[Game]));
|
||||
InternalGameName = "FortniteGame";
|
||||
Provider = new StreamedFileProvider("FortniteLive", true, versionContainer);
|
||||
break;
|
||||
}
|
||||
case Constants._VAL_LIVE_TRIGGER:
|
||||
{
|
||||
Game = FGame.ShooterGame;
|
||||
Provider = new StreamedFileProvider("ValorantLive", true,
|
||||
new VersionContainer(
|
||||
UserSettings.Default.OverridedGame[Game], UserSettings.Default.OverridedPlatform,
|
||||
customVersions: new FCustomVersionContainer(UserSettings.Default.OverridedCustomVersions[Game]),
|
||||
optionOverrides: UserSettings.Default.OverridedOptions[Game]));
|
||||
InternalGameName = "ShooterGame";
|
||||
Provider = new StreamedFileProvider("ValorantLive", true, versionContainer);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
var parent = gameDirectory.SubstringBeforeLast("\\Content").SubstringAfterLast("\\");
|
||||
if (gameDirectory.Contains("eFootball")) parent = gameDirectory.SubstringBeforeLast("\\pak").SubstringAfterLast("\\");
|
||||
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)
|
||||
InternalGameName = gameDirectory.SubstringBeforeLast(gameDirectory.Contains("eFootball") ? "\\pak" : "\\Content").SubstringAfterLast("\\");
|
||||
Provider = InternalGameName switch
|
||||
{
|
||||
case FGame.StateOfDecay2:
|
||||
{
|
||||
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\\DisabledPaks")
|
||||
},
|
||||
SearchOption.AllDirectories, true, versions);
|
||||
break;
|
||||
}
|
||||
case FGame.FortniteGame:
|
||||
Provider = new DefaultFileProvider(new DirectoryInfo(gameDirectory), new List<DirectoryInfo>
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
"StateOfDecay2" => new DefaultFileProvider(new DirectoryInfo(gameDirectory),
|
||||
new DirectoryInfo[]
|
||||
{
|
||||
new(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\StateOfDecay2\\Saved\\Paks"),
|
||||
new(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\StateOfDecay2\\Saved\\DisabledPaks")
|
||||
}, SearchOption.AllDirectories, true, versionContainer),
|
||||
"eFootball" => new DefaultFileProvider(new DirectoryInfo(gameDirectory),
|
||||
new DirectoryInfo[]
|
||||
{
|
||||
new(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + "\\KONAMI\\eFootball\\ST\\Download")
|
||||
}, SearchOption.AllDirectories, true, versionContainer),
|
||||
_ => new DefaultFileProvider(gameDirectory, SearchOption.AllDirectories, true, versionContainer)
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
@ -255,7 +223,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
}
|
||||
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));
|
||||
}
|
||||
|
||||
|
|
@ -273,7 +241,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
|
||||
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, () =>
|
||||
|
|
@ -283,18 +251,18 @@ public class CUE4ParseViewModel : ViewModel
|
|||
}
|
||||
|
||||
break;
|
||||
case DefaultFileProvider d:
|
||||
d.Initialize();
|
||||
|
||||
case DefaultFileProvider:
|
||||
var buildInfoPath = Path.Combine(UserSettings.Default.GameDirectory, "..\\..\\..\\Cloud\\BuildInfo.ini");
|
||||
if (File.Exists(buildInfoPath)) BuildInfo.Read(new StringReader(File.ReadAllText(buildInfoPath)));
|
||||
break;
|
||||
}
|
||||
|
||||
Provider.Initialize();
|
||||
|
||||
foreach (var vfs in Provider.UnloadedVfs) // push files from the provider to the ui
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (vfs.Length <= 365 || !_hiddenArchives.IsMatch(vfs.Name)) continue;
|
||||
if (!_hiddenArchives.IsMatch(vfs.Name)) continue;
|
||||
|
||||
GameDirectory.Add(vfs);
|
||||
}
|
||||
|
|
@ -336,7 +304,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
file.FileCount = vfs.FileCount;
|
||||
}
|
||||
|
||||
Game = Provider.GameName.ToEnum(Game);
|
||||
InternalGameName = Provider.InternalGameName;
|
||||
}
|
||||
|
||||
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
|
||||
// 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;
|
||||
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
|
|
@ -362,7 +330,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
var aes = _apiEndpointView.DynamicApi.GetAesKeys(cancellationToken, endpoint.Url, endpoint.Path);
|
||||
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 =>
|
||||
{
|
||||
var info = _apiEndpointView.FModelApi.GetNews(cancellationToken, Provider.GameName);
|
||||
var info = _apiEndpointView.FModelApi.GetNews(cancellationToken, Provider.InternalGameName);
|
||||
if (info == null) return;
|
||||
|
||||
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;
|
||||
return Task.CompletedTask;
|
||||
|
|
@ -393,11 +361,10 @@ public class CUE4ParseViewModel : ViewModel
|
|||
|
||||
return Task.Run(() =>
|
||||
{
|
||||
var l = ELog.Information;
|
||||
if (endpoint.Overwrite && File.Exists(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)
|
||||
{
|
||||
|
|
@ -410,14 +377,12 @@ public class CUE4ParseViewModel : ViewModel
|
|||
if (!mapping.IsValid) continue;
|
||||
|
||||
var mappingPath = Path.Combine(mappingsFolder, mapping.FileName);
|
||||
if (!File.Exists(mappingPath))
|
||||
if (force || !File.Exists(mappingPath))
|
||||
{
|
||||
_apiEndpointView.DownloadFile(mapping.Url, mappingPath);
|
||||
}
|
||||
|
||||
Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(mappingPath);
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
FLogger.Text($"Mappings pulled from '{mapping.FileName}'", Constants.WHITE, true));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -429,10 +394,15 @@ public class CUE4ParseViewModel : ViewModel
|
|||
|
||||
var latestUsmapInfo = latestUsmaps.OrderBy(f => f.LastWriteTime).Last();
|
||||
Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(latestUsmapInfo.FullName);
|
||||
FLogger.Append(ELog.Warning, () =>
|
||||
FLogger.Text($"Mappings pulled from '{latestUsmapInfo.Name}'", Constants.WHITE, true));
|
||||
l = ELog.Warning;
|
||||
}
|
||||
}
|
||||
|
||||
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(() =>
|
||||
{
|
||||
var inst = new List<InstructionToken>();
|
||||
Provider.DefaultEngine.FindPropertyInstructions("ConsoleVariables", "a.StripAdditiveRefPose", inst);
|
||||
if (inst.Count > 0 && inst[0].Value.Equals("1"))
|
||||
foreach (var token in Provider.DefaultEngine.Sections.FirstOrDefault(s => s.Name == "ConsoleVariables")?.Tokens ?? new List<IniToken>())
|
||||
{
|
||||
FLogger.Append(ELog.Warning, () =>
|
||||
FLogger.Text("Additive animations have their reference pose stripped, which will lead to inaccurate preview and export", Constants.WHITE, true));
|
||||
if (token is not InstructionToken it) continue;
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
|
@ -472,7 +453,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
|
||||
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;
|
||||
|
||||
var persistentDownloadDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "FortniteGame/Saved/PersistentDownloadDir");
|
||||
|
|
@ -564,7 +545,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
}
|
||||
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(() =>
|
||||
{
|
||||
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;
|
||||
return Task.Run(() =>
|
||||
{
|
||||
_virtualPathCount = Provider.LoadVirtualPaths(UserSettings.Default.OverridedGame[Game].GetVersion());
|
||||
_virtualPathCount = Provider.LoadVirtualPaths(UserSettings.Default.CurrentDir.UeVersion.GetVersion());
|
||||
if (_virtualPathCount > 0)
|
||||
{
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
|
|
@ -609,7 +590,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
{
|
||||
foreach (var asset in assetItems)
|
||||
{
|
||||
Thread.Sleep(10);
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
Extract(cancellationToken, asset.FullPath, TabControl.HasNoTabs);
|
||||
}
|
||||
|
|
@ -619,7 +600,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
{
|
||||
foreach (var asset in folder.AssetsList.Assets)
|
||||
{
|
||||
Thread.Sleep(10);
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
try
|
||||
{
|
||||
|
|
@ -759,7 +740,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
|
||||
break;
|
||||
}
|
||||
case "bin" when fileName.Contains("AssetRegistry"):
|
||||
case "bin" when fileName.Contains("AssetRegistry", StringComparison.OrdinalIgnoreCase):
|
||||
{
|
||||
if (Provider.TryCreateReader(fullPath, out var archive))
|
||||
{
|
||||
|
|
@ -892,7 +873,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
TabControl.SelectedTab.SetDocumentText(verseDigest.ReadableCode, false, false);
|
||||
return true;
|
||||
}
|
||||
case UTexture2D { IsVirtual: false } texture when isNone:
|
||||
case UTexture texture when isNone || saveTextures:
|
||||
{
|
||||
TabControl.SelectedTab.AddImage(texture, saveTextures, updateUi);
|
||||
return false;
|
||||
|
|
@ -902,8 +883,12 @@ public class CUE4ParseViewModel : ViewModel
|
|||
{
|
||||
var shouldDecompress = UserSettings.Default.CompressedAudioMode == ECompressedAudio.PlayDecompressed;
|
||||
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;
|
||||
}
|
||||
|
||||
SaveAndPlaySound(Path.Combine(TabControl.SelectedTab.Directory, TabControl.SelectedTab.Header.SubstringBeforeLast('.')).Replace('\\', '/'), audioFormat, data);
|
||||
return false;
|
||||
|
|
@ -911,8 +896,10 @@ public class CUE4ParseViewModel : ViewModel
|
|||
case UWorld when isNone && UserSettings.Default.PreviewWorlds:
|
||||
case UStaticMesh when isNone && UserSettings.Default.PreviewStaticMeshes:
|
||||
case USkeletalMesh when isNone && UserSettings.Default.PreviewSkeletalMeshes:
|
||||
case USkeleton when isNone && UserSettings.Default.SaveSkeletonAsMesh:
|
||||
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($"/MI_BPTile/{export.Name}", StringComparison.OrdinalIgnoreCase))):
|
||||
{
|
||||
|
|
@ -997,8 +984,9 @@ public class CUE4ParseViewModel : ViewModel
|
|||
MaterialFormat = UserSettings.Default.MaterialExportFormat,
|
||||
TextureFormat = UserSettings.Default.TextureExportFormat,
|
||||
SocketFormat = UserSettings.Default.SocketExportFormat,
|
||||
Platform = UserSettings.Default.OverridedPlatform,
|
||||
ExportMorphTargets = UserSettings.Default.SaveMorphTargets
|
||||
Platform = UserSettings.Default.CurrentDir.TexturePlatform,
|
||||
ExportMorphTargets = UserSettings.Default.SaveMorphTargets,
|
||||
ExportMaterials = UserSettings.Default.SaveEmbeddedMaterials
|
||||
};
|
||||
var toSave = new Exporter(export, exportOptions);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using AdonisUI.Controls;
|
||||
using FModel.Framework;
|
||||
using FModel.Settings;
|
||||
using FModel.Views;
|
||||
|
||||
namespace FModel.ViewModels.Commands;
|
||||
|
|
@ -31,4 +32,4 @@ public class AddEditDirectoryCommand : ViewModelCommand<CustomDirectoriesViewMod
|
|||
contextViewModel.Add(customDir);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using FModel.Framework;
|
||||
using FModel.Settings;
|
||||
|
||||
namespace FModel.ViewModels.Commands;
|
||||
|
||||
|
|
@ -17,4 +18,4 @@ public class DeleteDirectoryCommand : ViewModelCommand<CustomDirectoriesViewMode
|
|||
|
||||
contextViewModel.Delete(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,13 +23,13 @@ public class MenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
switch (parameter)
|
||||
{
|
||||
case "Directory_Selector":
|
||||
contextViewModel.AvoidEmptyGameDirectoryAndSetEGame(true);
|
||||
contextViewModel.AvoidEmptyGameDirectory(true);
|
||||
break;
|
||||
case "Directory_AES":
|
||||
Helper.OpenWindow<AdonisWindow>("AES Manager", () => new AesManager().Show());
|
||||
break;
|
||||
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;
|
||||
case "Directory_ArchivesInfo":
|
||||
contextViewModel.CUE4Parse.TabControl.AddTab("Archives Info");
|
||||
|
|
@ -42,19 +42,12 @@ public class MenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
case "Views_AudioPlayer":
|
||||
Helper.OpenWindow<AdonisWindow>("Audio Player", () => new AudioPlayer().Show());
|
||||
break;
|
||||
case "Views_MapViewer":
|
||||
Helper.OpenWindow<AdonisWindow>("Map Viewer", () => new MapViewer().Show());
|
||||
break;
|
||||
case "Views_ImageMerger":
|
||||
Helper.OpenWindow<AdonisWindow>("Image Merger", () => new ImageMerger().Show());
|
||||
break;
|
||||
case "Settings":
|
||||
Helper.OpenWindow<AdonisWindow>("Settings", () => new SettingsView().Show());
|
||||
break;
|
||||
case "ModelSettings":
|
||||
UserSettings.Default.LastOpenedSettingTab = contextViewModel.CUE4Parse.Game == FGame.FortniteGame ? 2 : 1;
|
||||
Helper.OpenWindow<AdonisWindow>("Settings", () => new SettingsView().Show());
|
||||
break;
|
||||
case "Help_About":
|
||||
Helper.OpenWindow<AdonisWindow>("About", () => new About().Show());
|
||||
break;
|
||||
|
|
@ -111,7 +104,7 @@ public class MenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
if (!expand && folder.IsExpanded)
|
||||
{
|
||||
folder.IsExpanded = false;
|
||||
Thread.Sleep(10);
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
|
|
@ -129,7 +122,7 @@ public class MenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
for (var node = nodes.Last; node != null; node = node.Previous)
|
||||
{
|
||||
node.Value.IsExpanded = true;
|
||||
Thread.Sleep(10);
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
case "Assets_Extract_New_Tab":
|
||||
foreach (var asset in assetItems)
|
||||
{
|
||||
Thread.Sleep(10);
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, true);
|
||||
}
|
||||
|
|
@ -37,7 +37,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
case "Assets_Export_Data":
|
||||
foreach (var asset in assetItems)
|
||||
{
|
||||
Thread.Sleep(10);
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.ExportData(asset.FullPath);
|
||||
}
|
||||
|
|
@ -45,7 +45,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
case "Assets_Save_Properties":
|
||||
foreach (var asset in assetItems)
|
||||
{
|
||||
Thread.Sleep(10);
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Properties);
|
||||
}
|
||||
|
|
@ -53,7 +53,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
case "Assets_Save_Textures":
|
||||
foreach (var asset in assetItems)
|
||||
{
|
||||
Thread.Sleep(10);
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Textures);
|
||||
}
|
||||
|
|
@ -61,7 +61,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
case "Assets_Save_Models":
|
||||
foreach (var asset in assetItems)
|
||||
{
|
||||
Thread.Sleep(10);
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Meshes | EBulkType.Auto);
|
||||
}
|
||||
|
|
@ -69,7 +69,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
case "Assets_Save_Animations":
|
||||
foreach (var asset in assetItems)
|
||||
{
|
||||
Thread.Sleep(10);
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Animations | EBulkType.Auto);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,37 +11,6 @@ using FModel.ViewModels.Commands;
|
|||
|
||||
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
|
||||
{
|
||||
private GoToCommand _goToCommand;
|
||||
|
|
@ -54,13 +23,8 @@ public class CustomDirectoriesViewModel : ViewModel
|
|||
private readonly ObservableCollection<Control> _directories;
|
||||
public ReadOnlyObservableCollection<Control> Directories { get; }
|
||||
|
||||
private readonly FGame _game;
|
||||
private readonly string _gameDirectoryAtLaunch;
|
||||
|
||||
public CustomDirectoriesViewModel(FGame game, string directory)
|
||||
public CustomDirectoriesViewModel()
|
||||
{
|
||||
_game = game;
|
||||
_gameDirectoryAtLaunch = directory;
|
||||
_directories = new ObservableCollection<Control>(EnumerateDirectories());
|
||||
Directories = new ReadOnlyObservableCollection<Control>(_directories);
|
||||
}
|
||||
|
|
@ -74,6 +38,7 @@ public class CustomDirectoriesViewModel : ViewModel
|
|||
public void Add(CustomDirectory dir)
|
||||
{
|
||||
_directories.Add(new MenuItem { Header = dir.Header, Tag = dir.DirectoryPath, ItemsSource = EnumerateCommands(dir) });
|
||||
Save();
|
||||
}
|
||||
|
||||
public void Edit(int index, CustomDirectory newDir)
|
||||
|
|
@ -82,25 +47,25 @@ public class CustomDirectoriesViewModel : ViewModel
|
|||
|
||||
dir.Header = newDir.Header;
|
||||
dir.Tag = newDir.DirectoryPath;
|
||||
Save();
|
||||
}
|
||||
|
||||
public void Delete(int index)
|
||||
{
|
||||
_directories.RemoveAt(index);
|
||||
Save();
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
var cd = new List<CustomDirectory>();
|
||||
var directories = new List<CustomDirectory>();
|
||||
for (var i = 2; i < _directories.Count; i++)
|
||||
{
|
||||
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.ManualGames[_gameDirectoryAtLaunch].CustomDirectories = cd;
|
||||
else UserSettings.Default.CustomDirectories[_game] = cd;
|
||||
UserSettings.Default.CurrentDir.Directories = directories;
|
||||
}
|
||||
|
||||
private IEnumerable<Control> EnumerateDirectories()
|
||||
|
|
@ -115,12 +80,7 @@ public class CustomDirectoriesViewModel : ViewModel
|
|||
};
|
||||
yield return new Separator();
|
||||
|
||||
IList<CustomDirectory> cd;
|
||||
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)
|
||||
foreach (var setting in UserSettings.Default.CurrentDir.Directories)
|
||||
{
|
||||
if (setting.DirectoryPath.EndsWith('/'))
|
||||
setting.DirectoryPath = setting.DirectoryPath[..^1];
|
||||
|
|
@ -167,4 +127,4 @@ public class CustomDirectoriesViewModel : ViewModel
|
|||
CommandParameter = dir
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,114 +22,97 @@ public class GameSelectorViewModel : ViewModel
|
|||
{
|
||||
public string GameName { get; set; }
|
||||
public string GameDirectory { get; set; }
|
||||
public EGame OverridedGame { get; set; }
|
||||
public bool IsManual { get; set; }
|
||||
|
||||
// the followings are only used when game is manually added
|
||||
public AesResponse AesKeys { get; set; }
|
||||
public EGame OverridedGame { get; set; }
|
||||
public List<FCustomVersion> OverridedCustomVersions { get; set; }
|
||||
public Dictionary<string, bool> OverridedOptions { get; set; }
|
||||
public Dictionary<string, KeyValuePair<string, string>> OverridedMapStructTypes { get; set; }
|
||||
public IList<CustomDirectory> CustomDirectories { get; set; }
|
||||
}
|
||||
|
||||
private DetectedGame _selectedDetectedGame;
|
||||
public DetectedGame SelectedDetectedGame
|
||||
private DirectorySettings _selectedDirectory;
|
||||
public DirectorySettings SelectedDirectory
|
||||
{
|
||||
get => _selectedDetectedGame;
|
||||
set => SetProperty(ref _selectedDetectedGame, value);
|
||||
get => _selectedDirectory;
|
||||
set => SetProperty(ref _selectedDirectory, value);
|
||||
}
|
||||
|
||||
private readonly ObservableCollection<DetectedGame> _autoDetectedGames;
|
||||
public ReadOnlyObservableCollection<DetectedGame> AutoDetectedGames { get; }
|
||||
private readonly ObservableCollection<DirectorySettings> _detectedDirectories;
|
||||
public ReadOnlyObservableCollection<DirectorySettings> DetectedDirectories { get; }
|
||||
public ReadOnlyObservableCollection<EGame> UeVersions { get; }
|
||||
|
||||
public GameSelectorViewModel(string gameDirectory)
|
||||
{
|
||||
_autoDetectedGames = new ObservableCollection<DetectedGame>(EnumerateDetectedGames().Where(x => x != null));
|
||||
foreach (var game in UserSettings.Default.ManualGames.Values)
|
||||
_detectedDirectories = new ObservableCollection<DirectorySettings>(EnumerateDetectedGames().Where(x => x != null));
|
||||
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)
|
||||
SelectedDetectedGame = detectedGame;
|
||||
if (DetectedDirectories.FirstOrDefault(x => x.GameDirectory == gameDirectory) is { } detectedGame)
|
||||
SelectedDirectory = detectedGame;
|
||||
else if (!string.IsNullOrEmpty(gameDirectory))
|
||||
AddUnknownGame(gameDirectory);
|
||||
AddUndetectedDir(gameDirectory);
|
||||
else
|
||||
SelectedDetectedGame = AutoDetectedGames.FirstOrDefault();
|
||||
SelectedDirectory = DetectedDirectories.FirstOrDefault();
|
||||
|
||||
UeVersions = new ReadOnlyObservableCollection<EGame>(new ObservableCollection<EGame>(EnumerateUeGames()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// dedicated to manual games
|
||||
/// </summary>
|
||||
public void AddUnknownGame(string gameName, string gameDirectory)
|
||||
public void AddUndetectedDir(string gameDirectory) => AddUndetectedDir(gameDirectory.SubstringAfterLast('\\'), gameDirectory);
|
||||
public void AddUndetectedDir(string gameName, string gameDirectory)
|
||||
{
|
||||
var game = new DetectedGame
|
||||
{
|
||||
GameName = gameName,
|
||||
GameDirectory = gameDirectory,
|
||||
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();
|
||||
var setting = DirectorySettings.Default(gameName, gameDirectory, true);
|
||||
UserSettings.Default.PerDirectory[gameDirectory] = setting;
|
||||
_detectedDirectories.Add(setting);
|
||||
SelectedDirectory = DetectedDirectories.Last();
|
||||
}
|
||||
|
||||
public void DeleteSelectedGame()
|
||||
{
|
||||
UserSettings.Default.ManualGames.Remove(SelectedDetectedGame.GameDirectory); // should not be a problem
|
||||
_autoDetectedGames.Remove(SelectedDetectedGame);
|
||||
SelectedDetectedGame = AutoDetectedGames.Last();
|
||||
UserSettings.Default.PerDirectory.Remove(SelectedDirectory.GameDirectory); // should not be a problem
|
||||
_detectedDirectories.Remove(SelectedDirectory);
|
||||
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 new DetectedGame { GameName = "Fortnite [LIVE]", GameDirectory = Constants._FN_LIVE_TRIGGER };
|
||||
yield return GetUnrealEngineGame("Pewee", "\\RogueCompany\\Content\\Paks");
|
||||
yield return GetUnrealEngineGame("Rosemallow", "\\Indiana\\Content\\Paks");
|
||||
yield return GetUnrealEngineGame("Catnip", "\\OakGame\\Content\\Paks");
|
||||
yield return GetUnrealEngineGame("AzaleaAlpha", "\\Prospect\\Content\\Paks");
|
||||
yield return GetUnrealEngineGame("WorldExplorersLive", "\\WorldExplorers\\Content\\Paks");
|
||||
yield return GetUnrealEngineGame("Newt", "\\g3\\Content\\Paks");
|
||||
yield return GetUnrealEngineGame("shoebill", "\\SwGame\\Content\\Paks");
|
||||
yield return GetUnrealEngineGame("Snoek", "\\StateOfDecay2\\Content\\Paks");
|
||||
yield return GetUnrealEngineGame("a99769d95d8f400baad1f67ab5dfe508", "\\Core\\Platform\\Content\\Paks");
|
||||
yield return GetUnrealEngineGame("Nebula", "\\BendGame\\Content");
|
||||
yield return GetUnrealEngineGame("711c5e95dc094ca58e5f16bd48e751d6", "\\MultiVersus\\Content\\Paks");
|
||||
yield return GetUnrealEngineGame("9361c8c6d2f34b42b5f2f61093eedf48", "\\TslGame\\Content\\Paks");
|
||||
yield return GetRiotGame("VALORANT", "ShooterGame\\Content\\Paks");
|
||||
yield return new DetectedGame { GameName = "Valorant [LIVE]", GameDirectory = Constants._VAL_LIVE_TRIGGER };
|
||||
yield return GetMojangGame("MinecraftDungeons", "\\dungeons\\dungeons\\Dungeons\\Content\\Paks");
|
||||
yield return GetSteamGame(381210, "\\DeadByDaylight\\Content\\Paks"); // Dead By Daylight
|
||||
yield return GetSteamGame(578080, "\\TslGame\\Content\\Paks"); // PUBG
|
||||
yield return GetSteamGame(1172380, "\\SwGame\\Content\\Paks"); // STAR WARS Jedi: Fallen Order™
|
||||
yield return GetSteamGame(677620, "\\PortalWars\\Content\\Paks"); // Splitgate
|
||||
yield return GetSteamGame(1172620, "\\Athena\\Content\\Paks"); // Sea of Thieves
|
||||
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");
|
||||
yield return GetUnrealEngineGame("Fortnite", "\\FortniteGame\\Content\\Paks", EGame.GAME_UE5_3);
|
||||
yield return DirectorySettings.Default("Fortnite [LIVE]", Constants._FN_LIVE_TRIGGER, ue: EGame.GAME_UE5_3);
|
||||
yield return GetUnrealEngineGame("Pewee", "\\RogueCompany\\Content\\Paks", EGame.GAME_RogueCompany);
|
||||
yield return GetUnrealEngineGame("Rosemallow", "\\Indiana\\Content\\Paks", EGame.GAME_UE4_21);
|
||||
yield return GetUnrealEngineGame("Catnip", "\\OakGame\\Content\\Paks", EGame.GAME_Borderlands3);
|
||||
yield return GetUnrealEngineGame("AzaleaAlpha", "\\Prospect\\Content\\Paks", EGame.GAME_UE4_27);
|
||||
yield return GetUnrealEngineGame("shoebill", "\\SwGame\\Content\\Paks", EGame.GAME_StarWarsJediFallenOrder);
|
||||
yield return GetUnrealEngineGame("Snoek", "\\StateOfDecay2\\Content\\Paks", EGame.GAME_StateOfDecay2);
|
||||
yield return GetUnrealEngineGame("711c5e95dc094ca58e5f16bd48e751d6", "\\MultiVersus\\Content\\Paks", EGame.GAME_UE4_26);
|
||||
yield return GetUnrealEngineGame("9361c8c6d2f34b42b5f2f61093eedf48", "\\TslGame\\Content\\Paks", EGame.GAME_PlayerUnknownsBattlegrounds);
|
||||
yield return GetRiotGame("VALORANT", "ShooterGame\\Content\\Paks", EGame.GAME_Valorant);
|
||||
yield return DirectorySettings.Default("VALORANT [LIVE]", Constants._VAL_LIVE_TRIGGER, ue: EGame.GAME_Valorant);
|
||||
yield return GetSteamGame(381210, "\\DeadByDaylight\\Content\\Paks", EGame.GAME_UE4_27); // Dead By Daylight
|
||||
yield return GetSteamGame(578080, "\\TslGame\\Content\\Paks", EGame.GAME_PlayerUnknownsBattlegrounds); // PUBG
|
||||
yield return GetSteamGame(1172380, "\\SwGame\\Content\\Paks", EGame.GAME_StarWarsJediFallenOrder); // STAR WARS Jedi: Fallen Order™
|
||||
yield return GetSteamGame(677620, "\\PortalWars\\Content\\Paks", EGame.GAME_Splitgate); // Splitgate
|
||||
yield return GetSteamGame(1172620, "\\Athena\\Content\\Paks", EGame.GAME_SeaOfThieves); // Sea of Thieves
|
||||
yield return GetSteamGame(1665460, "\\pak", EGame.GAME_UE4_26); // eFootball 2023
|
||||
yield return GetRockstarGamesGame("GTA III - Definitive Edition", "\\Gameface\\Content\\Paks", EGame.GAME_GTATheTrilogyDefinitiveEdition);
|
||||
yield return GetRockstarGamesGame("GTA San Andreas - Definitive Edition", "\\Gameface\\Content\\Paks", EGame.GAME_GTATheTrilogyDefinitiveEdition);
|
||||
yield return GetRockstarGamesGame("GTA Vice City - Definitive Edition", "\\Gameface\\Content\\Paks", EGame.GAME_GTATheTrilogyDefinitiveEdition);
|
||||
yield return GetLevelInfiniteGame("tof_launcher", "\\Hotta\\Content\\Paks", EGame.GAME_TowerOfFantasy);
|
||||
}
|
||||
|
||||
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");
|
||||
if (_launcherInstalled?.InstallationList != null)
|
||||
|
|
@ -140,7 +123,7 @@ public class GameSelectorViewModel : ViewModel
|
|||
if (installationList.AppName.Equals(gameName, StringComparison.OrdinalIgnoreCase) && Directory.Exists(gameDir))
|
||||
{
|
||||
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 DetectedGame GetRiotGame(string gameName, string pakDirectory)
|
||||
private DirectorySettings GetRiotGame(string gameName, string pakDirectory, EGame ueVersion)
|
||||
{
|
||||
_riotClientInstalls ??= GetDriveLauncherInstalls<RiotClientInstalls>("ProgramData\\Riot Games\\RiotClientInstalls.json");
|
||||
if (_riotClientInstalls is { AssociatedClient: { } })
|
||||
|
|
@ -160,7 +143,7 @@ public class GameSelectorViewModel : ViewModel
|
|||
if (key.Contains(gameName, StringComparison.OrdinalIgnoreCase) && Directory.Exists(gameDir))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
private LauncherSettings _launcherSettings;
|
||||
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)
|
||||
private DirectorySettings GetSteamGame(int id, string pakDirectory, EGame ueVersion)
|
||||
{
|
||||
var steamInfo = SteamDetection.GetSteamGameById(id);
|
||||
if (steamInfo is not null)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
private DetectedGame GetRockstarGamesGame(string key, string pakDirectory)
|
||||
private DirectorySettings GetRockstarGamesGame(string key, string pakDirectory, EGame ueVersion)
|
||||
{
|
||||
var installLocation = string.Empty;
|
||||
try
|
||||
|
|
@ -213,13 +179,13 @@ public class GameSelectorViewModel : ViewModel
|
|||
if (Directory.Exists(gameDir))
|
||||
{
|
||||
Log.Debug("Found {GameName} in the registry", key);
|
||||
return new DetectedGame { GameName = key, GameDirectory = gameDir };
|
||||
return DirectorySettings.Default(key, gameDir, ue: ueVersion);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private DetectedGame GetLevelInfiniteGame(string key, string pakDirectory)
|
||||
private DirectorySettings GetLevelInfiniteGame(string key, string pakDirectory, EGame ueVersion)
|
||||
{
|
||||
var installLocation = string.Empty;
|
||||
var displayName = string.Empty;
|
||||
|
|
@ -238,7 +204,7 @@ public class GameSelectorViewModel : ViewModel
|
|||
if (Directory.Exists(gameDir))
|
||||
{
|
||||
Log.Debug("Found {GameName} in the registry", key);
|
||||
return new DetectedGame { GameName = displayName, GameDirectory = gameDir };
|
||||
return DirectorySettings.Default(displayName, gameDir, ue: ueVersion);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
@ -258,19 +224,6 @@ public class GameSelectorViewModel : ViewModel
|
|||
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
|
||||
private class LauncherInstalled
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +1,21 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using CUE4Parse.UE4.Objects.Core.Serialization;
|
||||
using CUE4Parse.UE4.Versions;
|
||||
using CUE4Parse_Conversion.Meshes;
|
||||
using CUE4Parse_Conversion.Textures;
|
||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using FModel.Extensions;
|
||||
using FModel.Framework;
|
||||
using FModel.Services;
|
||||
using FModel.Settings;
|
||||
using FModel.ViewModels.ApiEndpoints.Models;
|
||||
|
||||
namespace FModel.ViewModels;
|
||||
|
||||
public class SettingsViewModel : ViewModel
|
||||
{
|
||||
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
|
||||
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
|
||||
private readonly DiscordHandler _discordHandler = DiscordService.DiscordHandler;
|
||||
|
||||
private bool _useCustomOutputFolders;
|
||||
|
|
@ -37,17 +32,6 @@ public class SettingsViewModel : ViewModel
|
|||
set => SetProperty(ref _selectedUpdateMode, value);
|
||||
}
|
||||
|
||||
private string _selectedPreset;
|
||||
public string SelectedPreset
|
||||
{
|
||||
get => _selectedPreset;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _selectedPreset, value);
|
||||
RaisePropertyChanged("EnableElements");
|
||||
}
|
||||
}
|
||||
|
||||
private ETexturePlatform _selectedUePlatform;
|
||||
public ETexturePlatform SelectedUePlatform
|
||||
{
|
||||
|
|
@ -62,36 +46,36 @@ public class SettingsViewModel : ViewModel
|
|||
set => SetProperty(ref _selectedUeGame, value);
|
||||
}
|
||||
|
||||
private List<FCustomVersion> _selectedCustomVersions;
|
||||
public List<FCustomVersion> SelectedCustomVersions
|
||||
private IList<FCustomVersion> _selectedCustomVersions;
|
||||
public IList<FCustomVersion> SelectedCustomVersions
|
||||
{
|
||||
get => _selectedCustomVersions;
|
||||
set => SetProperty(ref _selectedCustomVersions, value);
|
||||
}
|
||||
|
||||
private Dictionary<string, bool> _selectedOptions;
|
||||
public Dictionary<string, bool> SelectedOptions
|
||||
private IDictionary<string, bool> _selectedOptions;
|
||||
public IDictionary<string, bool> SelectedOptions
|
||||
{
|
||||
get => _selectedOptions;
|
||||
set => SetProperty(ref _selectedOptions, value);
|
||||
}
|
||||
|
||||
private Dictionary<string, KeyValuePair<string, string>> _selectedMapStructTypes;
|
||||
public Dictionary<string, KeyValuePair<string, string>> SelectedMapStructTypes
|
||||
private IDictionary<string, KeyValuePair<string, string>> _selectedMapStructTypes;
|
||||
public IDictionary<string, KeyValuePair<string, string>> SelectedMapStructTypes
|
||||
{
|
||||
get => _selectedMapStructTypes;
|
||||
set => SetProperty(ref _selectedMapStructTypes, value);
|
||||
}
|
||||
|
||||
private FEndpoint _aesEndpoint;
|
||||
public FEndpoint AesEndpoint
|
||||
private EndpointSettings _aesEndpoint;
|
||||
public EndpointSettings AesEndpoint
|
||||
{
|
||||
get => _aesEndpoint;
|
||||
set => SetProperty(ref _aesEndpoint, value);
|
||||
}
|
||||
|
||||
private FEndpoint _mappingEndpoint;
|
||||
public FEndpoint MappingEndpoint
|
||||
private EndpointSettings _mappingEndpoint;
|
||||
public EndpointSettings MappingEndpoint
|
||||
{
|
||||
get => _mappingEndpoint;
|
||||
set => SetProperty(ref _mappingEndpoint, value);
|
||||
|
|
@ -168,7 +152,6 @@ public class SettingsViewModel : ViewModel
|
|||
}
|
||||
|
||||
public ReadOnlyObservableCollection<EUpdateMode> UpdateModes { get; private set; }
|
||||
public ObservableCollection<string> Presets { get; private set; }
|
||||
public ReadOnlyObservableCollection<EGame> UeGames { get; private set; }
|
||||
public ReadOnlyObservableCollection<ELanguage> AssetLanguages { 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<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 _rawDataSnapshot;
|
||||
private string _propertiesSnapshot;
|
||||
|
|
@ -194,12 +173,11 @@ public class SettingsViewModel : ViewModel
|
|||
private string _modelSnapshot;
|
||||
private string _gameSnapshot;
|
||||
private EUpdateMode _updateModeSnapshot;
|
||||
private string _presetSnapshot;
|
||||
private ETexturePlatform _uePlatformSnapshot;
|
||||
private EGame _ueGameSnapshot;
|
||||
private List<FCustomVersion> _customVersionsSnapshot;
|
||||
private Dictionary<string, bool> _optionsSnapshot;
|
||||
private Dictionary<string, KeyValuePair<string, string>> _mapStructTypesSnapshot;
|
||||
private IList<FCustomVersion> _customVersionsSnapshot;
|
||||
private IDictionary<string, bool> _optionsSnapshot;
|
||||
private IDictionary<string, KeyValuePair<string, string>> _mapStructTypesSnapshot;
|
||||
private ELanguage _assetLanguageSnapshot;
|
||||
private ECompressedAudio _compressedAudioSnapshot;
|
||||
private EIconStyle _cosmeticStyleSnapshot;
|
||||
|
|
@ -211,9 +189,9 @@ public class SettingsViewModel : ViewModel
|
|||
|
||||
private bool _mappingsUpdate = false;
|
||||
|
||||
public SettingsViewModel(FGame game)
|
||||
public SettingsViewModel()
|
||||
{
|
||||
_game = game;
|
||||
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
|
|
@ -226,33 +204,19 @@ public class SettingsViewModel : ViewModel
|
|||
_modelSnapshot = UserSettings.Default.ModelDirectory;
|
||||
_gameSnapshot = UserSettings.Default.GameDirectory;
|
||||
_updateModeSnapshot = UserSettings.Default.UpdateMode;
|
||||
_presetSnapshot = UserSettings.Default.Presets[_game];
|
||||
_uePlatformSnapshot = UserSettings.Default.OverridedPlatform;
|
||||
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(_gameSnapshot, out var settings))
|
||||
{
|
||||
_ueGameSnapshot = settings.OverridedGame;
|
||||
_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];
|
||||
}
|
||||
_uePlatformSnapshot = UserSettings.Default.CurrentDir.TexturePlatform;
|
||||
_ueGameSnapshot = UserSettings.Default.CurrentDir.UeVersion;
|
||||
_customVersionsSnapshot = UserSettings.Default.CurrentDir.Versioning.CustomVersions;
|
||||
_optionsSnapshot = UserSettings.Default.CurrentDir.Versioning.Options;
|
||||
_mapStructTypesSnapshot = UserSettings.Default.CurrentDir.Versioning.MapStructTypes;
|
||||
|
||||
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];
|
||||
MappingEndpoint = endpoints[1];
|
||||
MappingEndpoint.PropertyChanged += (_, args) =>
|
||||
{
|
||||
if (!_mappingsUpdate)
|
||||
_mappingsUpdate = args.PropertyName is "Overwrite" or "FilePath";
|
||||
};
|
||||
}
|
||||
if (!_mappingsUpdate)
|
||||
_mappingsUpdate = args.PropertyName is "Overwrite" or "FilePath";
|
||||
};
|
||||
|
||||
_assetLanguageSnapshot = UserSettings.Default.AssetLanguage;
|
||||
_compressedAudioSnapshot = UserSettings.Default.CompressedAudioMode;
|
||||
|
|
@ -264,7 +228,6 @@ public class SettingsViewModel : ViewModel
|
|||
_textureExportFormatSnapshot = UserSettings.Default.TextureExportFormat;
|
||||
|
||||
SelectedUpdateMode = _updateModeSnapshot;
|
||||
SelectedPreset = _presetSnapshot;
|
||||
SelectedUePlatform = _uePlatformSnapshot;
|
||||
SelectedUeGame = _ueGameSnapshot;
|
||||
SelectedCustomVersions = _customVersionsSnapshot;
|
||||
|
|
@ -282,7 +245,6 @@ public class SettingsViewModel : ViewModel
|
|||
SelectedDiscordRpc = UserSettings.Default.DiscordRpc;
|
||||
|
||||
UpdateModes = new ReadOnlyObservableCollection<EUpdateMode>(new ObservableCollection<EUpdateMode>(EnumerateUpdateModes()));
|
||||
Presets = new ObservableCollection<string>(EnumeratePresets());
|
||||
UeGames = new ReadOnlyObservableCollection<EGame>(new ObservableCollection<EGame>(EnumerateUeGames()));
|
||||
AssetLanguages = new ReadOnlyObservableCollection<ELanguage>(new ObservableCollection<ELanguage>(EnumerateAssetLanguages()));
|
||||
AesReloads = new ReadOnlyObservableCollection<EAesReload>(new ObservableCollection<EAesReload>(EnumerateAesReloads()));
|
||||
|
|
@ -297,53 +259,6 @@ public class SettingsViewModel : ViewModel
|
|||
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)
|
||||
{
|
||||
var restart = false;
|
||||
|
|
@ -369,22 +284,11 @@ public class SettingsViewModel : ViewModel
|
|||
restart = true;
|
||||
|
||||
UserSettings.Default.UpdateMode = SelectedUpdateMode;
|
||||
UserSettings.Default.Presets[_game] = SelectedPreset;
|
||||
UserSettings.Default.OverridedPlatform = SelectedUePlatform;
|
||||
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(UserSettings.Default.GameDirectory))
|
||||
{
|
||||
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedGame = SelectedUeGame;
|
||||
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.CurrentDir.UeVersion = SelectedUeGame;
|
||||
UserSettings.Default.CurrentDir.TexturePlatform = SelectedUePlatform;
|
||||
UserSettings.Default.CurrentDir.Versioning.CustomVersions = SelectedCustomVersions;
|
||||
UserSettings.Default.CurrentDir.Versioning.Options = SelectedOptions;
|
||||
UserSettings.Default.CurrentDir.Versioning.MapStructTypes = SelectedMapStructTypes;
|
||||
|
||||
UserSettings.Default.AssetLanguage = SelectedAssetLanguage;
|
||||
UserSettings.Default.CompressedAudioMode = SelectedCompressedAudio;
|
||||
|
|
@ -404,11 +308,11 @@ public class SettingsViewModel : ViewModel
|
|||
}
|
||||
|
||||
private IEnumerable<EUpdateMode> EnumerateUpdateModes() => Enum.GetValues<EUpdateMode>();
|
||||
private IEnumerable<string> EnumeratePresets()
|
||||
{
|
||||
yield return Constants._NO_PRESET_TRIGGER;
|
||||
}
|
||||
private IEnumerable<EGame> EnumerateUeGames() => Enum.GetValues<EGame>();
|
||||
private IEnumerable<EGame> EnumerateUeGames()
|
||||
=> Enum.GetValues<EGame>()
|
||||
.GroupBy(value => (int)value)
|
||||
.Select(group => group.First())
|
||||
.OrderBy(value => (int)value == ((int)value & ~0xF));
|
||||
private IEnumerable<ELanguage> EnumerateAssetLanguages() => Enum.GetValues<ELanguage>();
|
||||
private IEnumerable<EAesReload> EnumerateAesReloads() => Enum.GetValues<EAesReload>();
|
||||
private IEnumerable<EDiscordRpc> EnumerateDiscordRpcs() => Enum.GetValues<EDiscordRpc>();
|
||||
|
|
|
|||
|
|
@ -228,8 +228,8 @@ public class TabItem : ViewModel
|
|||
});
|
||||
}
|
||||
|
||||
public void AddImage(UTexture2D texture, bool save, bool updateUi)
|
||||
=> AddImage(texture.Name, texture.bRenderNearestNeighbor, texture.Decode(UserSettings.Default.OverridedPlatform), save, updateUi);
|
||||
public void AddImage(UTexture texture, bool save, bool 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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@
|
|||
xmlns:local="clr-namespace:FModel"
|
||||
xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI"
|
||||
WindowStartupLocation="CenterScreen" ResizeMode="NoResize"
|
||||
IconVisibility="Collapsed" Width="500" SizeToContent="Height">
|
||||
IconVisibility="Collapsed" Width="500" SizeToContent="Height"
|
||||
Loaded="OnLoaded">
|
||||
<adonisControls:AdonisWindow.Style>
|
||||
<Style TargetType="adonisControls:AdonisWindow" BasedOn="{StaticResource {x:Type adonisControls:AdonisWindow}}" >
|
||||
<Setter Property="Title" Value="About" />
|
||||
|
|
@ -24,31 +25,27 @@
|
|||
</StackPanel>
|
||||
|
||||
<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="				" FontSize="25" FontWeight="700" Height="2" Foreground="Transparent" HorizontalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<TextBlock FontSize="12" Foreground="#727272" TextWrapping="Wrap" Margin="0 0 0 30"
|
||||
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." />
|
||||
<TextBlock FontSize="12" Foreground="#727272" TextWrapping="Wrap" Margin="0 0 0 30" Text="{Binding DescriptionLabel}" />
|
||||
|
||||
<StackPanel HorizontalAlignment="Center">
|
||||
<TextBlock Text="Contributors" FontSize="15" FontWeight="700" Foreground="#9DA3DD" FontStretch="Expanded" />
|
||||
<TextBlock Text="				" FontSize="25" FontWeight="700" Height="2" Foreground="Transparent" HorizontalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<TextBlock FontSize="12" Foreground="#727272" TextWrapping="Wrap" Margin="0 0 0 30"
|
||||
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." />
|
||||
<TextBlock FontSize="12" Foreground="#727272" TextWrapping="Wrap" Margin="0 0 0 30" Text="{Binding ContributorsLabel}" />
|
||||
|
||||
<StackPanel HorizontalAlignment="Center">
|
||||
<TextBlock Text="Donators" FontSize="15" FontWeight="700" Foreground="#9DA3DD" FontStretch="Expanded" />
|
||||
<TextBlock Text="				" FontSize="25" FontWeight="700" Height="2" Foreground="Transparent" HorizontalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<TextBlock FontSize="12" Foreground="#727272" TextWrapping="Wrap" Margin="0 0 0 30"
|
||||
Text="TheGameVlog ♥, Quentin ♥, Maiky ♥, HYPEX ♥, 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." />
|
||||
<TextBlock FontSize="12" Foreground="#727272" TextWrapping="Wrap" Margin="0 0 0 30" Text="{Binding DonatorsLabel}" />
|
||||
|
||||
<StackPanel HorizontalAlignment="Center">
|
||||
<TextBlock Text="Powered by" FontSize="15" FontWeight="700" Foreground="#9DA3DD" FontStretch="Expanded" />
|
||||
<TextBlock Text="				" FontSize="25" FontWeight="700" Height="2" Foreground="Transparent" HorizontalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<TextBlock FontSize="12" Foreground="#727272" TextWrapping="Wrap" Margin="0 0 0 30"
|
||||
Text="CUE4Parse, BenBot, Fortnite-Api, AdonisUI, AvalonEdit, CSCore, NVorbis, VgmStream, RestSharp, Serilog, Discord, K4os.Compression.LZ4, Ookii.Dialogs, Newtonsoft.Json, ..." />
|
||||
<TextBlock FontSize="12" Foreground="#727272" TextWrapping="Wrap" Margin="0 0 0 30" Text="{Binding ReferencesLabel}" />
|
||||
</StackPanel>
|
||||
</adonisControls:AdonisWindow>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,20 @@
|
|||
namespace FModel.Views;
|
||||
using System.Windows;
|
||||
using FModel.ViewModels;
|
||||
|
||||
namespace FModel.Views;
|
||||
|
||||
public partial class About
|
||||
{
|
||||
private readonly AboutViewModel _viewModel;
|
||||
|
||||
public About()
|
||||
{
|
||||
DataContext = _viewModel = new AboutViewModel();
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await _viewModel.Initialize();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,14 +80,8 @@
|
|||
<Button Grid.Column="1" MinWidth="78" Margin="0 0 12 0" IsDefault="True" IsCancel="False"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="OK" Click="OnClick" />
|
||||
<Button Grid.Column="2" MinWidth="78" Margin="0 0 12 0" IsDefault="False" IsCancel="False"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="Refresh" Click="OnRefreshAes">
|
||||
<Button.Visibility>
|
||||
<!-- 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>
|
||||
HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="Refresh" Click="OnRefreshAes"
|
||||
Visibility="{Binding Converter={x:Static converters:EndpointToTypeConverter.Instance}, ConverterParameter={x:Static local:EEndpointType.Aes}}">
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
using System.Windows;
|
||||
using FModel.ViewModels;
|
||||
using FModel.Settings;
|
||||
|
||||
namespace FModel.Views;
|
||||
|
||||
|
|
@ -9,7 +9,7 @@ public partial class CustomDir
|
|||
{
|
||||
DataContext = customDir;
|
||||
InitializeComponent();
|
||||
|
||||
|
||||
Activate();
|
||||
WpfSuckMyDick.Focus();
|
||||
WpfSuckMyDick.SelectAll();
|
||||
|
|
@ -20,4 +20,4 @@ public partial class CustomDir
|
|||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,37 +36,46 @@
|
|||
<GroupBox Grid.Row="1" adonisExtensions:LayerExtension.Layer="2" Margin="10 10 10 18"
|
||||
Padding="{adonisUi:Space 0}" Background="Transparent">
|
||||
<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>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="10" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="5" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Grid.Column="0" Text="Detected Game" VerticalAlignment="Center" />
|
||||
<ComboBox Grid.Column="2" ItemsSource="{Binding AutoDetectedGames}"
|
||||
VerticalAlignment="Center" SelectedItem="{Binding SelectedDetectedGame, Mode=TwoWay}">
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="Detected Game" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<ComboBox Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding DetectedDirectories}" Margin="0 0 0 5"
|
||||
VerticalAlignment="Center" SelectedItem="{Binding SelectedDirectory, Mode=TwoWay}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding GameName, Converter={x:Static converters:StringToGameConverter.Instance}}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</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}" />
|
||||
<Button Grid.Column="2" Content="..." HorizontalAlignment="Right" Click="OnBrowseDirectories" />
|
||||
<Button Grid.Column="3" Style="{DynamicResource {x:Static adonisUi:Styles.AccentButton}}" Padding="0"
|
||||
Click="OnDeleteDirectory" Width="{Binding ActualWidth, ElementName=OkGuysButWhoFuckingAsked}"
|
||||
Visibility="{Binding SelectedDetectedGame.IsManual, Converter={StaticResource BoolToVisibilityConverter}}"
|
||||
ToolTip="Delete Game" Margin="5 0 0 0">
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Text="UE Versions" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<ComboBox Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding UeVersions}" Margin="0 0 0 5"
|
||||
VerticalAlignment="Center" SelectedItem="{Binding SelectedDirectory.UeVersion, Mode=TwoWay}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<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">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource RemoveIcon}" />
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ public partial class DirectorySelector
|
|||
var folderBrowser = new VistaFolderBrowserDialog {ShowNewFolderButton = false};
|
||||
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)
|
||||
{
|
||||
if (DataContext is not GameSelectorViewModel gameLauncherViewModel||
|
||||
if (DataContext is not GameSelectorViewModel gameLauncherViewModel ||
|
||||
string.IsNullOrEmpty(HelloMyNameIsGame.Text) ||
|
||||
string.IsNullOrEmpty(HelloGameMyNameIsDirectory.Text))
|
||||
return;
|
||||
|
||||
gameLauncherViewModel.AddUnknownGame(HelloMyNameIsGame.Text, HelloGameMyNameIsDirectory.Text);
|
||||
gameLauncherViewModel.AddUndetectedDir(HelloMyNameIsGame.Text, HelloGameMyNameIsDirectory.Text);
|
||||
HelloMyNameIsGame.Clear();
|
||||
HelloGameMyNameIsDirectory.Clear();
|
||||
}
|
||||
|
|
@ -61,4 +61,4 @@ public partial class DirectorySelector
|
|||
|
||||
gameLauncherViewModel.DeleteSelectedGame();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
</Grid.RowDefinitions>
|
||||
|
||||
<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"
|
||||
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer1BackgroundBrush}}"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
|
|
@ -13,7 +12,6 @@ namespace FModel.Views.Resources.Controls;
|
|||
|
||||
public partial class DictionaryEditor
|
||||
{
|
||||
private readonly bool _enableElements;
|
||||
private readonly List<FCustomVersion> _defaultCustomVersions;
|
||||
private readonly Dictionary<string, bool> _defaultOptions;
|
||||
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, 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 } };
|
||||
_defaultOptions = new Dictionary<string, bool> { { "key1", true }, { "key2", false } };
|
||||
_defaultMapStructTypes = new Dictionary<string, KeyValuePair<string, string>> { { "MapName", new KeyValuePair<string, string>("KeyType", "ValueType") } };
|
||||
|
|
@ -32,11 +29,10 @@ public partial class DictionaryEditor
|
|||
InitializeComponent();
|
||||
|
||||
Title = title;
|
||||
MyAvalonEditor.IsReadOnly = !_enableElements;
|
||||
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
|
||||
{
|
||||
|
|
@ -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
|
||||
{
|
||||
|
|
@ -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
|
||||
{
|
||||
|
|
@ -62,13 +58,6 @@ public partial class DictionaryEditor
|
|||
|
||||
private void OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!_enableElements)
|
||||
{
|
||||
DialogResult = false;
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
switch (Title)
|
||||
|
|
@ -85,7 +74,7 @@ public partial class DictionaryEditor
|
|||
DialogResult = true;
|
||||
Close();
|
||||
break;
|
||||
case "MapStructTypes":
|
||||
case "Versioning Configuration (MapStructTypes)":
|
||||
MapStructTypes = JsonConvert.DeserializeObject<Dictionary<string, KeyValuePair<string, string>>>(MyAvalonEditor.Document.Text);
|
||||
// DialogResult = !Options.SequenceEqual(_defaultOptions);
|
||||
DialogResult = true;
|
||||
|
|
@ -104,9 +93,6 @@ public partial class DictionaryEditor
|
|||
|
||||
private void OnReset(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!_enableElements)
|
||||
return;
|
||||
|
||||
MyAvalonEditor.Document = Title switch
|
||||
{
|
||||
"Versioning Configuration (Custom Versions)" => new TextDocument
|
||||
|
|
@ -117,7 +103,7 @@ public partial class DictionaryEditor
|
|||
{
|
||||
Text = JsonConvert.SerializeObject(_defaultOptions, Formatting.Indented)
|
||||
},
|
||||
"MapStructTypes" => new TextDocument
|
||||
"Versioning Configuration (MapStructTypes)" => new TextDocument
|
||||
{
|
||||
Text = JsonConvert.SerializeObject(_defaultMapStructTypes, Formatting.Indented)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,12 +2,10 @@
|
|||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using FModel.Extensions;
|
||||
using FModel.Framework;
|
||||
using FModel.Services;
|
||||
using FModel.Settings;
|
||||
using ICSharpCode.AvalonEdit.Document;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using RestSharp;
|
||||
|
||||
namespace FModel.Views.Resources.Controls;
|
||||
|
||||
|
|
@ -16,7 +14,7 @@ public partial class EndpointEditor
|
|||
private readonly EEndpointType _type;
|
||||
private bool _isTested;
|
||||
|
||||
public EndpointEditor(FEndpoint endpoint, string title, EEndpointType type)
|
||||
public EndpointEditor(EndpointSettings endpoint, string title, EEndpointType type)
|
||||
{
|
||||
DataContext = endpoint;
|
||||
_type = type;
|
||||
|
|
@ -52,13 +50,13 @@ public partial class EndpointEditor
|
|||
|
||||
private void OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = _isTested && DataContext is FEndpoint { IsValid: true };
|
||||
DialogResult = _isTested && DataContext is EndpointSettings { IsValid: true };
|
||||
Close();
|
||||
}
|
||||
|
||||
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);
|
||||
Application.Current.Dispatcher.Invoke(delegate
|
||||
|
|
@ -70,7 +68,7 @@ public partial class EndpointEditor
|
|||
|
||||
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);
|
||||
_isTested = true;
|
||||
|
|
@ -82,7 +80,7 @@ public partial class EndpointEditor
|
|||
private void OnTextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (sender is not TextBox { IsLoaded: true } ||
|
||||
DataContext is not FEndpoint endpoint) return;
|
||||
DataContext is not EndpointSettings endpoint) return;
|
||||
endpoint.IsValid = false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,21 +3,18 @@ using System.Globalization;
|
|||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using FModel.Settings;
|
||||
using FModel.ViewModels;
|
||||
|
||||
namespace FModel.Views.Resources.Converters;
|
||||
|
||||
public class EndpointToTypeConverter : IMultiValueConverter
|
||||
public class EndpointToTypeConverter : IValueConverter
|
||||
{
|
||||
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 ||
|
||||
values[1] is not EEndpointType type)
|
||||
return false;
|
||||
if (parameter is not EEndpointType type) throw new NotImplementedException();
|
||||
|
||||
var isValid = UserSettings.IsEndpointValid(viewModel.CUE4Parse.Game, type, out _);
|
||||
var isValid = UserSettings.IsEndpointValid(type, out _);
|
||||
return targetType switch
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,31 +11,24 @@ public class StringToGameConverter : IValueConverter
|
|||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
var ret = value switch
|
||||
return value switch
|
||||
{
|
||||
"Newt" => FGame.g3,
|
||||
"Nebula" => FGame.BendGame,
|
||||
"Fortnite" => FGame.FortniteGame,
|
||||
"VALORANT" => FGame.ShooterGame,
|
||||
"Pewee" => FGame.RogueCompany,
|
||||
"Catnip" => FGame.OakGame,
|
||||
"AzaleaAlpha" => FGame.Prospect,
|
||||
"Snoek" => FGame.StateOfDecay2,
|
||||
"Rosemallow" => FGame.Indiana,
|
||||
"WorldExplorersLive" => FGame.WorldExplorers,
|
||||
"MinecraftDungeons" => FGame.Dungeons,
|
||||
"shoebill" => FGame.SwGame,
|
||||
"a99769d95d8f400baad1f67ab5dfe508" => FGame.Platform,
|
||||
"711c5e95dc094ca58e5f16bd48e751d6" => FGame.MultiVersus,
|
||||
"9361c8c6d2f34b42b5f2f61093eedf48" => FGame.TslGame,
|
||||
381210 => FGame.DeadByDaylight,
|
||||
578080 => FGame.TslGame,
|
||||
677620 => FGame.PortalWars,
|
||||
1172620 => FGame.Athena,
|
||||
1665460 => FGame.eFootball,
|
||||
_ => FGame.Unknown
|
||||
"Pewee" => "Rogue Company",
|
||||
"Rosemallow" => "The Outer Worlds",
|
||||
"Catnip" => "Borderlands 3",
|
||||
"AzaleaAlpha" => "The Cycle",
|
||||
"shoebill" => "Star Wars: Jedi Fallen Order",
|
||||
"Snoek" => "State Of Decay 2",
|
||||
"711c5e95dc094ca58e5f16bd48e751d6" => "MultiVersus",
|
||||
"9361c8c6d2f34b42b5f2f61093eedf48" => "PLAYERUNKNOWN'S BATTLEGROUNDS",
|
||||
381210 => "Dead By Daylight",
|
||||
578080 => "PLAYERUNKNOWN'S BATTLEGROUNDS",
|
||||
1172380 => "Star Wars: Jedi Fallen Order",
|
||||
677620 => "Splitgate",
|
||||
1172620 => "Sea of Thieves",
|
||||
1665460 => "eFootball 2023",
|
||||
_ => value
|
||||
};
|
||||
return ret == FGame.Unknown ? value : ret.GetDescription();
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
|
|
|
|||
|
|
@ -655,47 +655,50 @@
|
|||
<GridSplitter Grid.Column="1" Width="4" VerticalAlignment="Stretch" ResizeDirection="Columns" KeyboardIncrement="0"
|
||||
Visibility="{Binding SelectedItem.HasImage, ElementName=TabControlName, Converter={StaticResource BoolToVisibilityConverter}}"
|
||||
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer3BackgroundBrush}}" />
|
||||
<Image Grid.Column="2" VerticalAlignment="Center" HorizontalAlignment="Center" Stretch="Uniform"
|
||||
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.InputBindings>
|
||||
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding SelectedItem.ImageCommand, ElementName=TabControlName}" CommandParameter="Open"/>
|
||||
</Image.InputBindings>
|
||||
<Image.ContextMenu>
|
||||
<ContextMenu DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}">
|
||||
<MenuItem Header="Disable Alpha Channel" IsCheckable="True" IsChecked="{Binding DataContext.SelectedImage.NoAlpha}" />
|
||||
<MenuItem Header="Open Image" Command="{Binding DataContext.ImageCommand}" CommandParameter="Open">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
<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" />
|
||||
<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" />
|
||||
<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" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Copy Image" Command="{Binding DataContext.ImageCommand}" CommandParameter="Copy">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource CopyIcon}" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Image" Command="{Binding DataContext.ImageCommand}" CommandParameter="Save">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SaveIcon}" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</ContextMenu>
|
||||
</Image.ContextMenu>
|
||||
</Image>
|
||||
<Border Grid.Column="2" BorderBrush="#3b3d4a" BorderThickness="1" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Visibility="{Binding SelectedItem.HasImage, ElementName=TabControlName, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<Image VerticalAlignment="Center" HorizontalAlignment="Center" Stretch="Uniform"
|
||||
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.InputBindings>
|
||||
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding SelectedItem.ImageCommand, ElementName=TabControlName}" CommandParameter="Open"/>
|
||||
</Image.InputBindings>
|
||||
<Image.ContextMenu>
|
||||
<ContextMenu DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}">
|
||||
<MenuItem Header="Disable Alpha Channel" IsCheckable="True" IsChecked="{Binding DataContext.SelectedImage.NoAlpha}" />
|
||||
<MenuItem Header="Open Image" Command="{Binding DataContext.ImageCommand}" CommandParameter="Open">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
<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" />
|
||||
<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" />
|
||||
<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" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Copy Image" Command="{Binding DataContext.ImageCommand}" CommandParameter="Copy">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource CopyIcon}" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Image" Command="{Binding DataContext.ImageCommand}" CommandParameter="Save">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SaveIcon}" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</ContextMenu>
|
||||
</Image.ContextMenu>
|
||||
</Image>
|
||||
</Border>
|
||||
<TextBlock Grid.Column="2" VerticalAlignment="Bottom" HorizontalAlignment="Center"
|
||||
Visibility="{Binding SelectedItem.HasMultipleImages, ElementName=TabControlName, Converter={StaticResource BoolToVisibilityConverter}}"
|
||||
Text="{Binding SelectedItem.Page, ElementName=TabControlName}" />
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
|
||||
xmlns:adonisControls="clr-namespace:AdonisUI.Controls;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'}"
|
||||
Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.35'}">
|
||||
<adonisControls:AdonisWindow.Style>
|
||||
|
|
@ -55,7 +55,7 @@
|
|||
</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." />
|
||||
<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" />
|
||||
|
||||
<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>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="10" />
|
||||
<ColumnDefinition Width="{Binding ActualWidth, ElementName=ImStockBro}" />
|
||||
<ColumnDefinition Width="{Binding ActualWidth, ElementName=ImJackedBro}" />
|
||||
<ColumnDefinition Width="5" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
|
@ -95,12 +95,8 @@
|
|||
<Button Grid.Row="3" Grid.Column="4" Content="..." HorizontalAlignment="Right" Click="OnBrowseAudio" Margin="0 0 0 5" />
|
||||
</Grid>
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" Text="Game's Archive Directory *" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<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" />
|
||||
<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 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}"
|
||||
<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 Receive updates each time a new commit is pushed to GitHub" />
|
||||
<ComboBox Grid.Row="2" 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">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
|
|
@ -109,18 +105,8 @@
|
|||
</ComboBox.ItemTemplate>
|
||||
</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" />
|
||||
<ComboBox Grid.Row="4" 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="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}"
|
||||
<TextBlock Grid.Row="3" Grid.Column="0" Text="Discord Rich Presence" VerticalAlignment="Center" />
|
||||
<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}}}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
|
|
@ -129,16 +115,15 @@
|
|||
</ComboBox.ItemTemplate>
|
||||
</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" />
|
||||
<ComboBox Grid.Row="7" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.Presets}" SelectedItem="{Binding SettingsView.SelectedPreset, Mode=TwoWay}"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" SelectionChanged="OnSelectionChanged" Margin="0 0 0 5">
|
||||
</ComboBox>
|
||||
<TextBlock Grid.Row="5" Grid.Column="0" Text="Archive Directory *" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<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" />
|
||||
<Button Grid.Row="5" Grid.Column="6" Content="..." HorizontalAlignment="Right" Click="OnBrowseDirectories" Margin="0 0 0 5" />
|
||||
|
||||
<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" />
|
||||
<ComboBox Grid.Row="8" 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}"
|
||||
<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="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}}}"
|
||||
Margin="0 0 0 5">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
|
|
@ -147,8 +132,45 @@
|
|||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<TextBlock Grid.Row="9" Grid.Column="0" Text="Versioning Configuration *" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<Grid Grid.Row="9" Grid.Column="2" Grid.ColumnSpan="5" 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" />
|
||||
<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>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="5" />
|
||||
|
|
@ -162,40 +184,6 @@
|
|||
<Button Grid.Column="4" Content="MapStructTypes" Click="OpenMapStructTypes" />
|
||||
</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"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
|
||||
Visibility="{Binding SettingsView.AesEndpoint.IsValid, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
|
|
@ -210,29 +198,38 @@
|
|||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<TextBlock Grid.Row="14" Grid.Column="0" Text="Serialize Script Bytecode" 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}}"
|
||||
IsChecked="{Binding ReadScriptData, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10"/>
|
||||
<TextBlock Grid.Row="14" Grid.Column="0" Text="Endpoint Configuration" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<Grid Grid.Row="14" 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>
|
||||
|
||||
<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" />
|
||||
<CheckBox Grid.Row="15" 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 10"/>
|
||||
<Button Grid.Column="0" Content="AES" Click="OpenAesEndpoint" />
|
||||
<Button Grid.Column="2" Content="Mapping" Click="OpenMappingEndpoint" />
|
||||
</Grid>
|
||||
|
||||
<TextBlock Grid.Row="16" 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"
|
||||
<TextBlock Grid.Row="15" Grid.Column="0" Text="Local Mapping File" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<CheckBox Grid.Row="15" Grid.Column="2" Margin="0 5 0 10"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
|
||||
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
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}}}"
|
||||
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}}}"
|
||||
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}}}"
|
||||
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>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="CreatorTemplate">
|
||||
|
|
@ -318,6 +315,7 @@
|
|||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
|
|
@ -389,20 +387,25 @@
|
|||
IsChecked="{Binding PreviewWorlds, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
|
||||
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}}"
|
||||
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}"
|
||||
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" />
|
||||
<CheckBox Grid.Row="11" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
<TextBlock Grid.Row="12" Grid.Column="0" Text="Handle Skeletons as Empty Meshes" VerticalAlignment="Center" />
|
||||
<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}"
|
||||
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" />
|
||||
<ComboBox Grid.Row="13" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.MaterialExportFormats}" SelectedItem="{Binding SettingsView.SelectedMaterialExportFormat, Mode=TwoWay}"
|
||||
<TextBlock Grid.Row="14" Grid.Column="0" Text="Material Format" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<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">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
|
|
@ -411,8 +414,8 @@
|
|||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<TextBlock Grid.Row="14" 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}"
|
||||
<TextBlock Grid.Row="15" Grid.Column="0" Text="Texture Format" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<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">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
|
|
@ -524,7 +527,7 @@
|
|||
<Style TargetType="TreeViewItem" BasedOn="{StaticResource TreeViewItemStyle}">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
<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" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
var editor = new DictionaryEditor(
|
||||
_applicationView.SettingsView.SelectedCustomVersions,
|
||||
"Versioning Configuration (Custom Versions)",
|
||||
_applicationView.SettingsView.EnableElements);
|
||||
var editor = new DictionaryEditor(_applicationView.SettingsView.SelectedCustomVersions, "Versioning Configuration (Custom Versions)");
|
||||
var result = editor.ShowDialog();
|
||||
if (!result.HasValue || !result.Value)
|
||||
return;
|
||||
|
|
@ -176,10 +161,7 @@ public partial class SettingsView
|
|||
|
||||
private void OpenOptions(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var editor = new DictionaryEditor(
|
||||
_applicationView.SettingsView.SelectedOptions,
|
||||
"Versioning Configuration (Options)",
|
||||
_applicationView.SettingsView.EnableElements);
|
||||
var editor = new DictionaryEditor(_applicationView.SettingsView.SelectedOptions, "Versioning Configuration (Options)");
|
||||
var result = editor.ShowDialog();
|
||||
if (!result.HasValue || !result.Value)
|
||||
return;
|
||||
|
|
@ -189,10 +171,7 @@ public partial class SettingsView
|
|||
|
||||
private void OpenMapStructTypes(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var editor = new DictionaryEditor(
|
||||
_applicationView.SettingsView.SelectedMapStructTypes,
|
||||
"MapStructTypes",
|
||||
_applicationView.SettingsView.EnableElements);
|
||||
var editor = new DictionaryEditor(_applicationView.SettingsView.SelectedMapStructTypes, "Versioning Configuration (MapStructTypes)");
|
||||
var result = editor.ShowDialog();
|
||||
if (!result.HasValue || !result.Value)
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using CUE4Parse_Conversion;
|
||||
using CUE4Parse_Conversion.Animations.PSA;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using CUE4Parse.Utils;
|
||||
using FModel.Settings;
|
||||
using FModel.Views.Snooper.Models;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace FModel.Views.Snooper.Animations;
|
||||
|
|
@ -12,21 +15,16 @@ namespace FModel.Views.Snooper.Animations;
|
|||
public class Animation : IDisposable
|
||||
{
|
||||
private readonly UObject _export;
|
||||
private readonly CAnimSet _animSet;
|
||||
|
||||
public readonly CAnimSet UnrealAnim;
|
||||
public readonly string Path;
|
||||
public readonly string Name;
|
||||
public readonly Sequence[] Sequences;
|
||||
public readonly float StartTime; // Animation Start Time
|
||||
public readonly float EndTime; // Animation End 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 IsSelected;
|
||||
|
||||
|
|
@ -38,23 +36,22 @@ public class Animation : IDisposable
|
|||
Path = _export.GetPathName();
|
||||
Name = _export.Name;
|
||||
Sequences = Array.Empty<Sequence>();
|
||||
Framing = new Dictionary<int, float>();
|
||||
AttachedModels = new List<FGuid>();
|
||||
}
|
||||
|
||||
public Animation(UObject export, CAnimSet animSet) : this(export)
|
||||
{
|
||||
_animSet = animSet;
|
||||
TargetSkeleton = _animSet.Skeleton.Name;
|
||||
UnrealAnim = animSet;
|
||||
|
||||
Sequences = new Sequence[_animSet.Sequences.Count];
|
||||
Sequences = new Sequence[UnrealAnim.Sequences.Count];
|
||||
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;
|
||||
TotalElapsedTime += _animSet.Sequences[i].NumFrames * Sequences[i].TimePerFrame;
|
||||
}
|
||||
|
||||
TotalElapsedTime = animSet.TotalAnimTime;
|
||||
if (Sequences.Length > 0)
|
||||
StartTime = Sequences[0].StartTime;
|
||||
}
|
||||
|
|
@ -68,25 +65,16 @@ public class Animation : IDisposable
|
|||
{
|
||||
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;
|
||||
break;
|
||||
Framing[i] = (elapsedTime - sequence.StartTime) / sequence.TimePerFrame;
|
||||
}
|
||||
else Framing.Remove(i);
|
||||
}
|
||||
if (elapsedTime >= TotalElapsedTime) Reset();
|
||||
|
||||
var lastEndTime = 0.0f;
|
||||
for (int s = 0; s < CurrentSequence; s++)
|
||||
lastEndTime = Sequences[s].EndTime;
|
||||
|
||||
FrameInSequence = Math.Min(((elapsedTime - lastEndTime) / Sequences[CurrentSequence].TimePerFrame).FloorToInt(), Sequences[CurrentSequence].EndFrame);
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
FrameInSequence = 0;
|
||||
CurrentSequence = 0;
|
||||
if (elapsedTime >= TotalElapsedTime)
|
||||
Framing.Clear();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
@ -134,11 +122,11 @@ public class Animation : IDisposable
|
|||
foreach ((var guid, var model) in s.Renderer.Options.Models)
|
||||
{
|
||||
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);
|
||||
model.Skeleton.ResetAnimatedData(true);
|
||||
if (!selected) model.Skeleton.Animate(_animSet, s.Renderer.AnimateWithRotationOnly);
|
||||
skeletalModel.Skeleton.ResetAnimatedData(true);
|
||||
if (!selected) skeletalModel.Skeleton.Animate(UnrealAnim);
|
||||
}
|
||||
}
|
||||
ImGui.EndMenu();
|
||||
|
|
@ -146,7 +134,7 @@ public class Animation : IDisposable
|
|||
if (ImGui.MenuItem("Save"))
|
||||
{
|
||||
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);
|
||||
}
|
||||
ImGui.Separator();
|
||||
|
|
|
|||
|
|
@ -1,25 +1,37 @@
|
|||
namespace FModel.Views.Snooper.Animations;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FModel.Views.Snooper.Animations;
|
||||
|
||||
public class Bone
|
||||
{
|
||||
public readonly int Index;
|
||||
public readonly int ParentIndex;
|
||||
public int ParentIndex;
|
||||
public readonly Transform Rest;
|
||||
public readonly bool IsVirtual;
|
||||
|
||||
public string LoweredParentName;
|
||||
public List<string> LoweredChildNames;
|
||||
|
||||
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;
|
||||
ParentIndex = p;
|
||||
Rest = t;
|
||||
IsVirtual = isVirtual;
|
||||
|
||||
LoweredChildNames = new List<string>();
|
||||
AnimatedBySequences = new List<int>();
|
||||
}
|
||||
|
||||
public bool IsRoot => Index == 0 && ParentIndex == -1 && string.IsNullOrEmpty(LoweredParentName);
|
||||
public bool IsDaron => LoweredChildNames.Count > 0;
|
||||
public bool IsMapped => SkeletonIndex > -1;
|
||||
public bool IsAnimated => AnimatedBySequences.Count > 0;
|
||||
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}'";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using CUE4Parse_Conversion.Animations.PSA;
|
||||
using CUE4Parse.UE4.Assets.Exports.Animation;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using FModel.Views.Snooper.Buffers;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
using ImGuiNET;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using Serilog;
|
||||
|
||||
|
|
@ -13,169 +15,148 @@ namespace FModel.Views.Snooper.Animations;
|
|||
public class Skeleton : IDisposable
|
||||
{
|
||||
private int _handle;
|
||||
private BufferObject<Matrix4x4> _rest;
|
||||
private BufferObject<Matrix4x4> _ssbo;
|
||||
private Matrix4x4[] _boneMatriceAtFrame;
|
||||
private readonly List<string> _breadcrumb;
|
||||
|
||||
public string Name;
|
||||
public readonly Dictionary<string, Bone> BonesByLoweredName;
|
||||
|
||||
private int _previousAnimationSequence;
|
||||
private int _previousSequenceFrame;
|
||||
private Transform[][][] _animatedBonesTransform; // [sequence][bone][frame]
|
||||
private readonly Matrix4x4[] _invertedBonesMatrix;
|
||||
public int BoneCount => _invertedBonesMatrix.Length;
|
||||
public bool IsAnimated => _animatedBonesTransform.Length > 0;
|
||||
public readonly int BoneCount;
|
||||
private int _additionalBoneCount;
|
||||
private int TotalBoneCount => BoneCount + _additionalBoneCount;
|
||||
|
||||
public bool IsAnimated { get; private set; }
|
||||
public string SelectedBone;
|
||||
|
||||
private const int _vertexSize = 12;
|
||||
private BufferObject<float> _vbo;
|
||||
private int _vaoHandle;
|
||||
|
||||
public Skeleton()
|
||||
{
|
||||
BonesByLoweredName = new Dictionary<string, Bone>();
|
||||
_animatedBonesTransform = Array.Empty<Transform[][]>();
|
||||
_invertedBonesMatrix = Array.Empty<Matrix4x4>();
|
||||
_breadcrumb = new List<string>();
|
||||
}
|
||||
|
||||
public Skeleton(FReferenceSkeleton referenceSkeleton) : this()
|
||||
{
|
||||
_invertedBonesMatrix = new Matrix4x4[referenceSkeleton.FinalRefBoneInfo.Length];
|
||||
for (int boneIndex = 0; boneIndex < _invertedBonesMatrix.Length; boneIndex++)
|
||||
BoneCount = referenceSkeleton.FinalRefBoneInfo.Length;
|
||||
for (int boneIndex = 0; boneIndex < BoneCount; 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,
|
||||
Position = referenceSkeleton.FinalRefBonePose[boneIndex].Translation * Constants.SCALE_DOWN_RATIO,
|
||||
Scale = referenceSkeleton.FinalRefBonePose[boneIndex].Scale3D
|
||||
};
|
||||
});
|
||||
|
||||
var bone = new Bone(boneIndex, info.ParentIndex, boneTransform);
|
||||
if (!bone.IsRoot)
|
||||
{
|
||||
bone.LoweredParentName =
|
||||
referenceSkeleton.FinalRefBoneInfo[bone.ParentIndex].Name.Text.ToLower();
|
||||
bone.Rest.Relation = BonesByLoweredName[bone.LoweredParentName].Rest.Matrix;
|
||||
bone.LoweredParentName = referenceSkeleton.FinalRefBoneInfo[bone.ParentIndex].Name.Text.ToLower();
|
||||
var parentBone = BonesByLoweredName[bone.LoweredParentName];
|
||||
|
||||
bone.Rest.Relation = parentBone.Rest.Matrix;
|
||||
parentBone.LoweredChildNames.Add(boneName);
|
||||
}
|
||||
|
||||
BonesByLoweredName[info.Name.Text.ToLower()] = bone;
|
||||
|
||||
Matrix4x4.Invert(boneTransform.Matrix, out var inverted);
|
||||
_invertedBonesMatrix[bone.Index] = inverted;
|
||||
if (boneIndex == 0) SelectedBone = boneName;
|
||||
BonesByLoweredName[boneName] = bone;
|
||||
}
|
||||
_breadcrumb.Add(SelectedBone);
|
||||
_boneMatriceAtFrame = new Matrix4x4[BoneCount];
|
||||
}
|
||||
|
||||
public void Animate(CAnimSet anim, bool rotationOnly)
|
||||
public void Merge(FReferenceSkeleton referenceSkeleton)
|
||||
{
|
||||
MapSkeleton(anim);
|
||||
|
||||
_animatedBonesTransform = new Transform[anim.Sequences.Count][][];
|
||||
for (int s = 0; s < _animatedBonesTransform.Length; s++)
|
||||
for (int boneIndex = 0; boneIndex < referenceSkeleton.FinalRefBoneInfo.Length; boneIndex++)
|
||||
{
|
||||
var sequence = anim.Sequences[s];
|
||||
_animatedBonesTransform[s] = new Transform[BoneCount][];
|
||||
foreach (var bone in BonesByLoweredName.Values)
|
||||
var info = referenceSkeleton.FinalRefBoneInfo[boneIndex];
|
||||
var boneName = info.Name.Text.ToLower();
|
||||
|
||||
if (!BonesByLoweredName.TryGetValue(boneName, out var bone))
|
||||
{
|
||||
_animatedBonesTransform[s][bone.Index] = new Transform[sequence.NumFrames];
|
||||
|
||||
var skeletonBoneIndex = bone.SkeletonIndex;
|
||||
if (sequence.OriginalSequence.FindTrackForBoneIndex(skeletonBoneIndex) < 0)
|
||||
bone = new Bone(BoneCount + _additionalBoneCount, -1, new Transform
|
||||
{
|
||||
bone.IsAnimated |= false;
|
||||
for (int frame = 0; frame < _animatedBonesTransform[s][bone.Index].Length; frame++)
|
||||
{
|
||||
_animatedBonesTransform[s][bone.Index][frame] = new Transform
|
||||
{
|
||||
Relation = bone.IsRoot ? bone.Rest.Relation :
|
||||
bone.Rest.LocalMatrix * _animatedBonesTransform[s][bone.ParentIndex][frame].Matrix
|
||||
};
|
||||
}
|
||||
}
|
||||
else
|
||||
Rotation = referenceSkeleton.FinalRefBonePose[boneIndex].Rotation,
|
||||
Position = referenceSkeleton.FinalRefBonePose[boneIndex].Translation * Constants.SCALE_DOWN_RATIO,
|
||||
Scale = referenceSkeleton.FinalRefBonePose[boneIndex].Scale3D
|
||||
}, true);
|
||||
|
||||
if (!bone.IsRoot)
|
||||
{
|
||||
bone.IsAnimated |= true;
|
||||
for (int frame = 0; frame < _animatedBonesTransform[s][bone.Index].Length; frame++)
|
||||
{
|
||||
var boneOrientation = bone.Rest.Rotation;
|
||||
var bonePosition = bone.Rest.Position;
|
||||
var boneScale = bone.Rest.Scale;
|
||||
bone.LoweredParentName = referenceSkeleton.FinalRefBoneInfo[info.ParentIndex].Name.Text.ToLower();
|
||||
var parentBone = BonesByLoweredName[bone.LoweredParentName];
|
||||
|
||||
sequence.Tracks[skeletonBoneIndex].GetBoneTransform(frame, sequence.NumFrames, ref boneOrientation, ref bonePosition, ref boneScale);
|
||||
|
||||
switch (anim.Skeleton.BoneTree[skeletonBoneIndex])
|
||||
{
|
||||
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
|
||||
};
|
||||
}
|
||||
bone.ParentIndex = parentBone.Index;
|
||||
bone.Rest.Relation = parentBone.Rest.Matrix;
|
||||
parentBone.LoweredChildNames.Add(boneName);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
// 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))
|
||||
continue;
|
||||
|
||||
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
|
||||
|
|
@ -194,52 +175,213 @@ public class Skeleton : IDisposable
|
|||
foreach (var bone in BonesByLoweredName.Values)
|
||||
{
|
||||
bone.SkeletonIndex = -1;
|
||||
bone.IsAnimated = false;
|
||||
bone.AnimatedBySequences.Clear();
|
||||
}
|
||||
|
||||
if (!full) return;
|
||||
_animatedBonesTransform = Array.Empty<Transform[][]>();
|
||||
_ssbo.UpdateRange(BoneCount, Matrix4x4.Identity);
|
||||
IsAnimated = false;
|
||||
_ssbo.UpdateRange(Matrix4x4.Identity);
|
||||
}
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
_handle = GL.CreateProgram();
|
||||
|
||||
_ssbo = new BufferObject<Matrix4x4>(BoneCount, BufferTarget.ShaderStorageBuffer);
|
||||
_ssbo.UpdateRange(BoneCount, Matrix4x4.Identity);
|
||||
}
|
||||
|
||||
public void UpdateAnimationMatrices(int currentSequence, int frameInSequence)
|
||||
public void UpdateAnimationMatrices(Animation animation, bool rotationOnly)
|
||||
{
|
||||
if (!IsAnimated) return;
|
||||
|
||||
_previousAnimationSequence = currentSequence;
|
||||
if (_previousSequenceFrame == frameInSequence) return;
|
||||
_previousSequenceFrame = frameInSequence;
|
||||
|
||||
_ssbo.Bind();
|
||||
for (int boneIndex = 0; boneIndex < BoneCount; boneIndex++) // interpolate here
|
||||
_ssbo.Update(boneIndex, _invertedBonesMatrix[boneIndex] * _animatedBonesTransform[_previousAnimationSequence][boneIndex][_previousSequenceFrame].Matrix);
|
||||
foreach (var bone in BonesByLoweredName.Values)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
public Matrix4x4 GetBoneMatrix(Bone bone)
|
||||
public void UpdateVertices()
|
||||
{
|
||||
return IsAnimated
|
||||
? _animatedBonesTransform[_previousAnimationSequence][bone.Index][_previousSequenceFrame].Matrix
|
||||
: bone.Rest.Matrix;
|
||||
_vbo.Bind();
|
||||
foreach (var (boneName, bone) in BonesByLoweredName)
|
||||
{
|
||||
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);
|
||||
_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()
|
||||
{
|
||||
BonesByLoweredName.Clear();
|
||||
|
||||
_rest?.Dispose();
|
||||
_ssbo?.Dispose();
|
||||
GL.DeleteProgram(_handle);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,26 +6,33 @@ namespace FModel.Views.Snooper.Buffers;
|
|||
public class BufferObject<TDataType> : IDisposable where TDataType : unmanaged
|
||||
{
|
||||
private readonly int _handle;
|
||||
private readonly int _sizeOf;
|
||||
private readonly BufferTarget _bufferTarget;
|
||||
|
||||
private BufferObject(BufferTarget bufferTarget)
|
||||
public readonly int Size;
|
||||
|
||||
private unsafe BufferObject(BufferTarget bufferTarget)
|
||||
{
|
||||
_bufferTarget = bufferTarget;
|
||||
_handle = GL.GenBuffer();
|
||||
_sizeOf = sizeof(TDataType);
|
||||
|
||||
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)
|
||||
{
|
||||
Bind();
|
||||
|
|
@ -33,14 +40,23 @@ public class BufferObject<TDataType> : IDisposable where TDataType : unmanaged
|
|||
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()
|
||||
|
|
|
|||
|
|
@ -54,20 +54,20 @@ public class PickingTexture : IDisposable
|
|||
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();
|
||||
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
|
||||
|
||||
_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("uB", guid.B);
|
||||
_shader.SetUniform("uC", guid.C);
|
||||
_shader.SetUniform("uD", guid.D);
|
||||
|
||||
if (!model.Show) continue;
|
||||
if (!model.IsVisible) continue;
|
||||
model.PickingRender(_shader);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,25 +7,29 @@ namespace FModel.Views.Snooper.Buffers;
|
|||
public class VertexArrayObject<TVertexType, TIndexType> : IDisposable where TVertexType : unmanaged where TIndexType : unmanaged
|
||||
{
|
||||
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();
|
||||
_sizeOfVertex = sizeof(TVertexType);
|
||||
_sizeOfIndex = sizeof(TIndexType);
|
||||
|
||||
Bind();
|
||||
vbo.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)
|
||||
{
|
||||
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;
|
||||
default:
|
||||
GL.VertexAttribPointer(index, count, type, false, vertexSize * sizeof(TVertexType), offset * sizeof(TVertexType));
|
||||
GL.VertexAttribPointer(index, count, type, false, vertexSize * _sizeOfVertex, offset * _sizeOfVertex);
|
||||
break;
|
||||
}
|
||||
GL.EnableVertexAttribArray(index);
|
||||
|
|
|
|||
57
FModel/Views/Snooper/Models/Attachment.cs
Normal file
57
FModel/Views/Snooper/Models/Attachment.cs
Normal 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}'");
|
||||
}
|
||||
14
FModel/Views/Snooper/Models/EAttribute.cs
Normal file
14
FModel/Views/Snooper/Models/EAttribute.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
namespace FModel.Views.Snooper.Models;
|
||||
|
||||
public enum EAttribute
|
||||
{
|
||||
Index,
|
||||
Position,
|
||||
Normals,
|
||||
Tangent,
|
||||
UVs,
|
||||
Layer,
|
||||
Colors,
|
||||
BonesId,
|
||||
BonesWeight
|
||||
}
|
||||
|
|
@ -45,16 +45,17 @@ public class Grid : IDisposable
|
|||
{
|
||||
GL.Disable(EnableCap.CullFace);
|
||||
GL.Disable(EnableCap.DepthTest);
|
||||
|
||||
_vao.Bind();
|
||||
|
||||
_shader.Use();
|
||||
|
||||
_shader.SetUniform("view", viewMatrix);
|
||||
_shader.SetUniform("proj", projMatrix);
|
||||
_shader.SetUniform("uNear", near);
|
||||
_shader.SetUniform("uFar", far);
|
||||
|
||||
GL.DrawArrays(PrimitiveType.Triangles, 0, Indices.Length);
|
||||
|
||||
GL.Enable(EnableCap.DepthTest);
|
||||
GL.Enable(EnableCap.CullFace);
|
||||
}
|
||||
|
|
|
|||
38
FModel/Views/Snooper/Models/IRenderableModel.cs
Normal file
38
FModel/Views/Snooper/Models/IRenderableModel.cs
Normal 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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
127
FModel/Views/Snooper/Models/SkeletalModel.cs
Normal file
127
FModel/Views/Snooper/Models/SkeletalModel.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -87,8 +87,6 @@ public class Skybox : IDisposable
|
|||
GL.DepthFunc(DepthFunction.Lequal);
|
||||
|
||||
_vao.Bind();
|
||||
|
||||
_cubeMap.Bind(TextureUnit.Texture0);
|
||||
_shader.Use();
|
||||
|
||||
viewMatrix.M41 = 0;
|
||||
|
|
@ -97,6 +95,7 @@ public class Skybox : IDisposable
|
|||
_shader.SetUniform("uView", viewMatrix);
|
||||
_shader.SetUniform("uProjection", projMatrix);
|
||||
|
||||
_cubeMap.Bind(TextureUnit.Texture0);
|
||||
_shader.SetUniform("cubemap", 0);
|
||||
|
||||
GL.DrawArrays(PrimitiveType.Triangles, 0, 36);
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ public class Socket : IDisposable
|
|||
public readonly bool IsVirtual;
|
||||
|
||||
public readonly List<SocketAttachementInfo> AttachedModels;
|
||||
public bool IsDaron => AttachedModels.Count > 0;
|
||||
|
||||
private Socket()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
using CUE4Parse_Conversion.Meshes.PSK;
|
||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
|
||||
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];
|
||||
for (int i = 0; i < Indices.Length; i++)
|
||||
|
|
@ -44,5 +45,18 @@ public class Cube : Model
|
|||
Sections[0] = new Section(0, Indices.Length, 0);
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
389
FModel/Views/Snooper/Models/UModel.cs
Normal file
389
FModel/Views/Snooper/Models/UModel.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using CUE4Parse_Conversion;
|
||||
using CUE4Parse_Conversion.Textures;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using FModel.Settings;
|
||||
|
|
@ -22,7 +19,7 @@ public class Options
|
|||
public int SelectedMorph { 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 List<Light> Lights;
|
||||
|
||||
|
|
@ -31,12 +28,11 @@ public class Options
|
|||
|
||||
public readonly Dictionary<string, Texture> Icons;
|
||||
|
||||
private readonly ETexturePlatform _platform;
|
||||
private readonly string _game;
|
||||
|
||||
public Options()
|
||||
{
|
||||
Models = new Dictionary<FGuid, Model>();
|
||||
Models = new Dictionary<FGuid, UModel>();
|
||||
Textures = new Dictionary<FGuid, Texture>();
|
||||
Lights = new List<Light>();
|
||||
|
||||
|
|
@ -47,6 +43,7 @@ public class Options
|
|||
{
|
||||
["material"] = new ("materialicon"),
|
||||
["noimage"] = new ("T_Placeholder_Item_Image"),
|
||||
["checker"] = new ("checker"),
|
||||
["pointlight"] = new ("pointlight"),
|
||||
["spotlight"] = new ("spotlight"),
|
||||
["link_on"] = new ("link_on"),
|
||||
|
|
@ -60,8 +57,7 @@ public class Options
|
|||
["tl_next"] = new ("tl_next"),
|
||||
};
|
||||
|
||||
_platform = UserSettings.Default.OverridedPlatform;
|
||||
_game = Services.ApplicationService.ApplicationView.CUE4Parse.Provider.GameName.ToUpper();
|
||||
_game = Services.ApplicationService.ApplicationView.CUE4Parse.Provider.InternalGameName.ToUpper();
|
||||
|
||||
SelectModel(Guid.Empty);
|
||||
}
|
||||
|
|
@ -107,27 +103,30 @@ public class Options
|
|||
|
||||
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);
|
||||
model.Dispose();
|
||||
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 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);
|
||||
}
|
||||
else if (detach) attachedModel.SafeDetachModel(model);
|
||||
else if (detach) attachedModel.Attachments.SafeDetach(model, t);
|
||||
}
|
||||
|
||||
if (socket.IsVirtual)
|
||||
|
|
@ -152,17 +151,20 @@ public class Options
|
|||
{
|
||||
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);
|
||||
DetachAndRemoveModels(animatedModel, false);
|
||||
}
|
||||
|
||||
animation.Dispose();
|
||||
}
|
||||
foreach (var kvp in Models.ToList().Where(kvp => kvp.Value.IsAnimatedProp))
|
||||
{
|
||||
RemoveModel(kvp.Key);
|
||||
}
|
||||
|
||||
foreach (var kvp in Models)
|
||||
if (kvp.Value.IsProp)
|
||||
RemoveModel(kvp.Key);
|
||||
|
||||
Animations.Clear();
|
||||
}
|
||||
|
||||
|
|
@ -171,7 +173,7 @@ public class Options
|
|||
SelectedSection = index;
|
||||
}
|
||||
|
||||
public void SelectMorph(int index, Model model)
|
||||
public void SelectMorph(int index, SkeletalModel model)
|
||||
{
|
||||
SelectedMorph = index;
|
||||
model.UpdateMorph(SelectedMorph);
|
||||
|
|
@ -180,19 +182,19 @@ public class Options
|
|||
public bool TryGetTexture(UTexture2D o, bool fix, out Texture texture)
|
||||
{
|
||||
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(data, mip.SizeX, mip.SizeY, o);
|
||||
texture = new Texture(bitmap, o);
|
||||
if (fix) TextureHelper.FixChannels(_game, texture);
|
||||
Textures[guid] = texture;
|
||||
bitmap.Dispose();
|
||||
}
|
||||
return texture != null;
|
||||
}
|
||||
|
||||
public bool TryGetModel(out Model model) => Models.TryGetValue(SelectedModel, out model);
|
||||
public bool TryGetModel(FGuid guid, out Model model) => Models.TryGetValue(guid, out model);
|
||||
public bool TryGetModel(out UModel model) => Models.TryGetValue(SelectedModel, 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(FGuid guid, out Section section)
|
||||
|
|
@ -205,7 +207,7 @@ public class Options
|
|||
section = null;
|
||||
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)
|
||||
section = model.Sections[SelectedSection]; else section = null;
|
||||
|
|
@ -222,22 +224,6 @@ public class Options
|
|||
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()
|
||||
{
|
||||
foreach (var model in Models.Values)
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ using FModel.Views.Snooper.Buffers;
|
|||
using FModel.Views.Snooper.Lights;
|
||||
using FModel.Views.Snooper.Models;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
using OpenTK.Windowing.GraphicsLibraryFramework;
|
||||
using UModel = FModel.Views.Snooper.Models.UModel;
|
||||
|
||||
namespace FModel.Views.Snooper;
|
||||
|
||||
|
|
@ -43,12 +45,14 @@ public class Renderer : IDisposable
|
|||
private Shader _shader;
|
||||
private Shader _outline;
|
||||
private Shader _light;
|
||||
private Shader _bone;
|
||||
private bool _saveCameraMode;
|
||||
|
||||
public bool ShowSkybox;
|
||||
public bool ShowGrid;
|
||||
public bool ShowLights;
|
||||
public bool AnimateWithRotationOnly;
|
||||
public bool IsSkeletonTreeOpen;
|
||||
public VertexColor Color;
|
||||
|
||||
public Camera CameraOp { get; }
|
||||
|
|
@ -83,6 +87,9 @@ public class Renderer : IDisposable
|
|||
case USkeletalMesh sk:
|
||||
LoadSkeletalMesh(sk);
|
||||
break;
|
||||
case USkeleton skel:
|
||||
LoadSkeleton(skel);
|
||||
break;
|
||||
case UMaterialInstance mi:
|
||||
LoadMaterialInstance(mi);
|
||||
break;
|
||||
|
|
@ -90,6 +97,7 @@ public class Renderer : IDisposable
|
|||
LoadWorld(cancellationToken, wd, Transform.Identity);
|
||||
break;
|
||||
}
|
||||
SetupCamera();
|
||||
}
|
||||
|
||||
public void Swap(UMaterialInstance unrealMaterial)
|
||||
|
|
@ -103,7 +111,7 @@ public class Renderer : IDisposable
|
|||
public void Animate(UObject anim) => Animate(anim, Options.SelectedModel);
|
||||
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;
|
||||
|
||||
float maxElapsedTime;
|
||||
|
|
@ -114,7 +122,7 @@ public class Renderer : IDisposable
|
|||
var animSet = skeleton.ConvertAnims(animSequence);
|
||||
var animation = new Animation(animSequence, animSet, guid);
|
||||
maxElapsedTime = animation.TotalElapsedTime;
|
||||
model.Skeleton.Animate(animSet, AnimateWithRotationOnly);
|
||||
model.Skeleton.Animate(animSet);
|
||||
Options.AddAnimation(animation);
|
||||
break;
|
||||
}
|
||||
|
|
@ -123,7 +131,7 @@ public class Renderer : IDisposable
|
|||
var animSet = skeleton.ConvertAnims(animMontage);
|
||||
var animation = new Animation(animMontage, animSet, guid);
|
||||
maxElapsedTime = animation.TotalElapsedTime;
|
||||
model.Skeleton.Animate(animSet, AnimateWithRotationOnly);
|
||||
model.Skeleton.Animate(animSet);
|
||||
Options.AddAnimation(animation);
|
||||
|
||||
foreach (var notifyEvent in animMontage.Notifies)
|
||||
|
|
@ -140,32 +148,39 @@ public class Renderer : IDisposable
|
|||
t.Scale = offset.Scale3D;
|
||||
}
|
||||
|
||||
UModel addedModel = null;
|
||||
switch (export)
|
||||
{
|
||||
case UStaticMesh st:
|
||||
{
|
||||
guid = st.LightingGuid;
|
||||
if (Options.TryGetModel(guid, out var instancedModel))
|
||||
instancedModel.AddInstance(t);
|
||||
if (Options.TryGetModel(guid, out addedModel))
|
||||
{
|
||||
addedModel.AddInstance(t);
|
||||
}
|
||||
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;
|
||||
}
|
||||
case USkeletalMesh sk:
|
||||
{
|
||||
guid = Guid.NewGuid();
|
||||
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;
|
||||
}
|
||||
default:
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
if (!Options.TryGetModel(guid, out var addedModel))
|
||||
continue;
|
||||
if (addedModel == null)
|
||||
throw new ArgumentException("Unknown model type");
|
||||
|
||||
addedModel.IsAnimatedProp = true;
|
||||
addedModel.IsProp = true;
|
||||
if (notifyClass.TryGetValue(out UObject skeletalMeshPropAnimation, "SkeletalMeshPropAnimation", "Animation"))
|
||||
Animate(skeletalMeshPropAnimation, guid);
|
||||
if (notifyClass.TryGetValue(out FName socketName, "SocketName"))
|
||||
|
|
@ -178,9 +193,10 @@ public class Renderer : IDisposable
|
|||
if (notifyClass.TryGetValue(out FVector 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);
|
||||
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;
|
||||
|
|
@ -190,7 +206,7 @@ public class Renderer : IDisposable
|
|||
var animSet = skeleton.ConvertAnims(animComposite);
|
||||
var animation = new Animation(animComposite, animSet, guid);
|
||||
maxElapsedTime = animation.TotalElapsedTime;
|
||||
model.Skeleton.Animate(animSet, AnimateWithRotationOnly);
|
||||
model.Skeleton.Animate(animSet);
|
||||
Options.AddAnimation(animation);
|
||||
break;
|
||||
}
|
||||
|
|
@ -210,12 +226,13 @@ public class Renderer : IDisposable
|
|||
_shader = new Shader();
|
||||
_outline = new Shader("outline");
|
||||
_light = new Shader("light");
|
||||
_bone = new Shader("bone");
|
||||
|
||||
Picking.Setup();
|
||||
Options.SetupModelsAndLights();
|
||||
}
|
||||
|
||||
public void Render(float deltaSeconds)
|
||||
public void Render()
|
||||
{
|
||||
var viewMatrix = CameraOp.GetViewMatrix();
|
||||
var projMatrix = CameraOp.GetProjectionMatrix();
|
||||
|
|
@ -227,23 +244,11 @@ public class Renderer : IDisposable
|
|||
for (int i = 0; i < 5; i++)
|
||||
_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
|
||||
foreach (var model in Options.Models.Values)
|
||||
{
|
||||
model.UpdateMatrices(Options);
|
||||
if (!model.Show) continue;
|
||||
model.Render(_shader);
|
||||
if (!model.IsVisible) continue;
|
||||
model.Render(_shader, Color == VertexColor.TextureCoordinates ? Options.Icons["checker"] : null);
|
||||
}
|
||||
|
||||
{ // light pass
|
||||
|
|
@ -259,17 +264,67 @@ public class Renderer : IDisposable
|
|||
Options.Lights[i].Render(_light);
|
||||
}
|
||||
|
||||
// outline pass
|
||||
if (Options.TryGetModel(out var selected) && selected.Show)
|
||||
// debug + outline pass
|
||||
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);
|
||||
selected.Render(_outline, true);
|
||||
selected.Render(_outline, Color == VertexColor.TextureCoordinates ? Options.Icons["checker"] : null, true);
|
||||
}
|
||||
|
||||
// picking pass (dedicated FBO, binding to 0 afterward)
|
||||
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)
|
||||
{
|
||||
var guid = original.LightingGuid;
|
||||
|
|
@ -283,9 +338,8 @@ public class Renderer : IDisposable
|
|||
if (!original.TryConvert(out var mesh))
|
||||
return;
|
||||
|
||||
Options.Models[guid] = new Model(original, mesh);
|
||||
Options.Models[guid] = new StaticModel(original, mesh);
|
||||
Options.SelectModel(guid);
|
||||
SetupCamera(Options.Models[guid].Box);
|
||||
}
|
||||
|
||||
private void LoadSkeletalMesh(USkeletalMesh original)
|
||||
|
|
@ -293,14 +347,25 @@ public class Renderer : IDisposable
|
|||
var guid = new FGuid((uint) original.GetFullName().GetHashCode());
|
||||
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);
|
||||
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)
|
||||
{
|
||||
if (!Utils.TryLoadObject("Engine/Content/EditorMeshes/EditorCube.EditorCube", out UStaticMesh editorCube))
|
||||
if (!Utils.TryLoadObject("Engine/Content/BasicShapes/Cube.Cube", out UStaticMesh editorCube))
|
||||
return;
|
||||
|
||||
var guid = editorCube.LightingGuid;
|
||||
|
|
@ -314,12 +379,15 @@ public class Renderer : IDisposable
|
|||
if (!editorCube.TryConvert(out var mesh))
|
||||
return;
|
||||
|
||||
Options.Models[guid] = new Cube(mesh, original);
|
||||
Options.Models[guid] = new StaticModel(original, mesh);
|
||||
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)
|
||||
{
|
||||
|
|
@ -400,14 +468,15 @@ public class Renderer : IDisposable
|
|||
Scale = staticMeshComp.GetOrDefault("RelativeScale3D", FVector.OneVector)
|
||||
};
|
||||
|
||||
OverrideVertexColors(staticMeshComp, m);
|
||||
if (Options.TryGetModel(guid, out var model))
|
||||
{
|
||||
model.AddInstance(t);
|
||||
}
|
||||
else if (m.TryConvert(out var mesh))
|
||||
{
|
||||
model = new Model(m, mesh, t);
|
||||
model.TwoSided = actor.GetOrDefault("bMirrored", staticMeshComp.GetOrDefault("bDisallowMeshPaintPerInstance", model.TwoSided));
|
||||
model = new StaticModel(m, mesh, t);
|
||||
model.IsTwoSided = actor.GetOrDefault("bMirrored", staticMeshComp.GetOrDefault("bDisallowMeshPaintPerInstance", model.IsTwoSided));
|
||||
|
||||
if (actor.TryGetValue(out FPackageIndex baseMaterial, "BaseMaterial") &&
|
||||
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)
|
||||
{
|
||||
if (textureData.TryGetValue(out FPackageIndex package, name) && package.Load() is UTexture2D texture)
|
||||
|
|
@ -524,6 +607,7 @@ public class Renderer : IDisposable
|
|||
_shader?.Dispose();
|
||||
_outline?.Dispose();
|
||||
_light?.Dispose();
|
||||
_bone?.Dispose();
|
||||
Picking?.Dispose();
|
||||
Options?.Dispose();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,11 +76,11 @@ public class Material : IDisposable
|
|||
|
||||
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)) };
|
||||
SpecularMasks = new [] { new Texture(new FLinearColor(1f, 0.5f, 0.5f, 1f)) };
|
||||
Emissive = new Texture[1];
|
||||
DiffuseColor = new[] { new Vector4(0.5f) };
|
||||
DiffuseColor = new[] { Vector4.One };
|
||||
EmissiveColor = new[] { Vector4.One };
|
||||
}
|
||||
else
|
||||
|
|
@ -98,7 +98,7 @@ public class Material : IDisposable
|
|||
}
|
||||
|
||||
{ // 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))
|
||||
{
|
||||
HasAo = true;
|
||||
|
|
@ -132,6 +132,22 @@ public class Material : IDisposable
|
|||
"Emissive 2 UV Positioning (RG)UpperLeft (BA)LowerRight",
|
||||
"EmissiveUVPositioning (RG)UpperLeft (BA)LowerRight"))
|
||||
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))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using ImGuiNET;
|
|||
using OpenTK.Graphics.OpenGL4;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Views.Snooper.Shading;
|
||||
|
||||
|
|
@ -78,7 +79,7 @@ public class Texture : IDisposable
|
|||
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;
|
||||
Guid = texture2D.LightingGuid;
|
||||
|
|
@ -87,11 +88,11 @@ public class Texture : IDisposable
|
|||
Format = texture2D.Format;
|
||||
ImportedWidth = texture2D.ImportedSize.X;
|
||||
ImportedHeight = texture2D.ImportedSize.Y;
|
||||
Width = width;
|
||||
Height = height;
|
||||
Width = bitmap.Width;
|
||||
Height = bitmap.Height;
|
||||
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.TextureMagFilter, (int) TextureMagFilter.Linear);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureBaseLevel, 0);
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ public class SnimGui
|
|||
private readonly float _tableWidth;
|
||||
|
||||
private Vector2 _outlinerSize;
|
||||
private bool _ti_open;
|
||||
private bool _tiOpen;
|
||||
private bool _viewportFocus;
|
||||
|
||||
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)
|
||||
{
|
||||
Controller.SemiBold();
|
||||
DrawDockSpace(s.Size);
|
||||
DrawDockSpace(s.ClientSize);
|
||||
|
||||
SectionWindow("Material Inspector", s.Renderer, DrawMaterialInspector, false);
|
||||
AnimationWindow("Timeline", s.Renderer, (icons, tracker, animations) =>
|
||||
|
|
@ -103,9 +103,11 @@ public class SnimGui
|
|||
Draw3DViewport(s);
|
||||
DrawNavbar();
|
||||
|
||||
DrawTextureInspector(s);
|
||||
DrawSkeletonTree(s);
|
||||
|
||||
DrawModals(s);
|
||||
|
||||
if (_ti_open) DrawTextureInspector(s);
|
||||
Controller.Render();
|
||||
}
|
||||
|
||||
|
|
@ -181,7 +183,7 @@ public class SnimGui
|
|||
{
|
||||
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.SetNextItemOpen(true, ImGuiCond.Appearing);
|
||||
if (ImGui.CollapsingHeader("Editor"))
|
||||
ImGui.SeparatorText("Editor");
|
||||
if (ImGui.BeginTable("world_editor", 2))
|
||||
{
|
||||
if (ImGui.BeginTable("world_editor", 2))
|
||||
{
|
||||
Layout("Skybox");ImGui.PushID(1);
|
||||
ImGui.Checkbox("", ref s.Renderer.ShowSkybox);
|
||||
ImGui.PopID();Layout("Grid");ImGui.PushID(2);
|
||||
ImGui.Checkbox("", ref s.Renderer.ShowGrid);
|
||||
ImGui.PopID();Layout("Lights");ImGui.PushID(3);
|
||||
ImGui.Checkbox("", ref s.Renderer.ShowLights);
|
||||
ImGui.PopID();Layout("Animate With Rotation Only");ImGui.PushID(4);
|
||||
ImGui.Checkbox("", ref s.Renderer.AnimateWithRotationOnly);
|
||||
ImGui.PopID();Layout("Vertex Colors");ImGui.PushID(5);
|
||||
var c = (int) s.Renderer.Color;
|
||||
ImGui.Combo("vertex_colors", ref c,
|
||||
"Default\0Sections\0Colors\0Normals\0Texture Coordinates\0");
|
||||
s.Renderer.Color = (VertexColor) c;
|
||||
ImGui.PopID();
|
||||
Layout("Skybox");ImGui.PushID(1);
|
||||
ImGui.Checkbox("", ref s.Renderer.ShowSkybox);
|
||||
ImGui.PopID();Layout("Grid");ImGui.PushID(2);
|
||||
ImGui.Checkbox("", ref s.Renderer.ShowGrid);
|
||||
ImGui.PopID();Layout("Lights");ImGui.PushID(3);
|
||||
ImGui.Checkbox("", ref s.Renderer.ShowLights);
|
||||
ImGui.PopID();Layout("Animate With Rotation Only");ImGui.PushID(4);
|
||||
ImGui.Checkbox("", ref s.Renderer.AnimateWithRotationOnly);
|
||||
ImGui.PopID();Layout("Vertex Colors");ImGui.PushID(5);
|
||||
var c = (int) s.Renderer.Color;
|
||||
ImGui.Combo("vertex_colors", ref c,
|
||||
"Default\0Sections\0Colors\0Normals\0Texture Coordinates\0");
|
||||
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.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();
|
||||
}
|
||||
ImGui.TreePop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -409,16 +402,16 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
ImGui.TableHeadersRow();
|
||||
|
||||
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.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
if (!model.Show)
|
||||
if (!model.IsVisible)
|
||||
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)));
|
||||
else if (model.IsAttached)
|
||||
else if (model.Attachments.IsAttached)
|
||||
ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, ImGui.GetColorU32(new Vector4(1, 1, 0, .5f)));
|
||||
|
||||
ImGui.Text(model.TransformsCount.ToString("D"));
|
||||
|
|
@ -432,16 +425,16 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
Popup(() =>
|
||||
{
|
||||
s.Renderer.Options.SelectModel(guid);
|
||||
if (ImGui.MenuItem("Show", null, model.Show)) model.Show = !model.Show;
|
||||
if (ImGui.MenuItem("Wireframe", null, model.Wireframe)) model.Wireframe = !model.Wireframe;
|
||||
if (ImGui.MenuItem("Show", null, model.IsVisible)) model.IsVisible = !model.IsVisible;
|
||||
if (ImGui.MenuItem("Wireframe", null, model.ShowWireframe)) model.ShowWireframe = !model.ShowWireframe;
|
||||
ImGui.Separator();
|
||||
if (ImGui.MenuItem("Save"))
|
||||
{
|
||||
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);
|
||||
}
|
||||
if (ImGui.MenuItem("Animate", model.HasSkeleton))
|
||||
if (ImGui.MenuItem("Animate", model is SkeletalModel))
|
||||
{
|
||||
if (_swapper.IsAware)
|
||||
{
|
||||
|
|
@ -461,10 +454,14 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
_swapper.Value = true;
|
||||
}
|
||||
}
|
||||
if (ImGui.MenuItem("Skeleton Tree", model is SkeletalModel))
|
||||
{
|
||||
s.Renderer.IsSkeletonTreeOpen = true;
|
||||
ImGui.SetWindowFocus("Skeleton Tree");
|
||||
}
|
||||
if (ImGui.MenuItem("Teleport To"))
|
||||
{
|
||||
var instancePos = model.Transforms[model.SelectedInstance].Matrix.Translation;
|
||||
s.Renderer.CameraOp.Teleport(instancePos, model.Box);
|
||||
s.Renderer.CameraOp.Teleport(model.GetTransform().Matrix.Translation, model.Box);
|
||||
}
|
||||
|
||||
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.Image(s.Renderer.Options.Icons[model.AttachIcon].GetPointer(), new Vector2(_tableWidth));
|
||||
TooltipCopy(model.AttachTooltip);
|
||||
ImGui.Image(s.Renderer.Options.Icons[model.Attachments.Icon].GetPointer(), new Vector2(_tableWidth));
|
||||
TooltipCopy(model.Attachments.Tooltip);
|
||||
|
||||
ImGui.PopID();
|
||||
i++;
|
||||
|
|
@ -502,14 +499,14 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
{
|
||||
var isAttached = socket.AttachedModels.Contains(info);
|
||||
ImGui.PushID(i);
|
||||
ImGui.BeginDisabled(selectedModel.IsAttached && !isAttached);
|
||||
ImGui.BeginDisabled(selectedModel.Attachments.IsAttached && !isAttached);
|
||||
switch (isAttached)
|
||||
{
|
||||
case false when ImGui.Button($"Attach to '{socket.Name}'"):
|
||||
selectedModel.AttachModel(model, socket, info);
|
||||
selectedModel.Attachments.Attach(model, selectedModel.GetTransform(), socket, info);
|
||||
break;
|
||||
case true when ImGui.Button($"Detach from '{socket.Name}'"):
|
||||
selectedModel.DetachModel(model, socket, info);
|
||||
selectedModel.Attachments.Detach(model, selectedModel.GetTransform(), socket, info);
|
||||
break;
|
||||
}
|
||||
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("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("Bones");ImGui.Text($" : x{model.Skeleton.BoneCount}");
|
||||
Layout("Skeleton");ImGui.Text($" : {skeletalModel.Skeleton.Name}");
|
||||
Layout("Bones");ImGui.Text($" : x{skeletalModel.Skeleton.BoneCount}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Layout("Two Sided");ImGui.Text($" : {model.TwoSided}");
|
||||
Layout("Two Sided");ImGui.Text($" : {model.IsTwoSided}");
|
||||
}
|
||||
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 (model.HasMorphTargets)
|
||||
if (model is SkeletalModel { HasMorphTargets: true } skeletalModel)
|
||||
{
|
||||
const float width = 10;
|
||||
var region = ImGui.GetContentRegionAvail();
|
||||
|
|
@ -629,12 +626,12 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
|
||||
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);
|
||||
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();
|
||||
}
|
||||
|
|
@ -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.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.Spacing();
|
||||
ImGui.Text($"Time: {model.MorphTime:P}%");
|
||||
ImGui.Text($"Time: {skeletalModel.MorphTime:P}%");
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
|
|
@ -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.Spacing();
|
||||
|
||||
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
|
||||
if (ImGui.CollapsingHeader("Parameters"))
|
||||
{
|
||||
material.ImGuiParameters();
|
||||
}
|
||||
ImGui.SeparatorText("Parameters");
|
||||
material.ImGuiParameters();
|
||||
|
||||
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
|
||||
if (ImGui.CollapsingHeader("Textures") && material.ImGuiTextures(icons, model))
|
||||
ImGui.SeparatorText("Textures");
|
||||
if (material.ImGuiTextures(icons, model))
|
||||
{
|
||||
_ti_open = true;
|
||||
_tiOpen = true;
|
||||
ImGui.SetWindowFocus("Texture Inspector");
|
||||
}
|
||||
|
||||
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
|
||||
if (ImGui.CollapsingHeader("Properties"))
|
||||
ImGui.SeparatorText("Properties");
|
||||
NoFramePaddingOnY(() =>
|
||||
{
|
||||
NoFramePaddingOnY(() =>
|
||||
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
|
||||
if (ImGui.TreeNode("Base"))
|
||||
{
|
||||
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
|
||||
if (ImGui.TreeNode("Base"))
|
||||
{
|
||||
material.ImGuiBaseProperties("base");
|
||||
ImGui.TreePop();
|
||||
}
|
||||
material.ImGuiBaseProperties("base");
|
||||
ImGui.TreePop();
|
||||
}
|
||||
|
||||
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
|
||||
if (ImGui.TreeNode("Scalars"))
|
||||
{
|
||||
material.ImGuiDictionaries("scalars", material.Parameters.Scalars, true);
|
||||
ImGui.TreePop();
|
||||
}
|
||||
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
|
||||
if (ImGui.TreeNode("Switches"))
|
||||
{
|
||||
material.ImGuiDictionaries("switches", material.Parameters.Switches, true);
|
||||
ImGui.TreePop();
|
||||
}
|
||||
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
|
||||
if (ImGui.TreeNode("Colors"))
|
||||
{
|
||||
material.ImGuiColors(material.Parameters.Colors);
|
||||
ImGui.TreePop();
|
||||
}
|
||||
if (ImGui.TreeNode("All Textures"))
|
||||
{
|
||||
material.ImGuiDictionaries("textures", material.Parameters.Textures);
|
||||
ImGui.TreePop();
|
||||
}
|
||||
});
|
||||
}
|
||||
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
|
||||
if (ImGui.TreeNode("Scalars"))
|
||||
{
|
||||
material.ImGuiDictionaries("scalars", material.Parameters.Scalars, true);
|
||||
ImGui.TreePop();
|
||||
}
|
||||
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
|
||||
if (ImGui.TreeNode("Switches"))
|
||||
{
|
||||
material.ImGuiDictionaries("switches", material.Parameters.Switches, true);
|
||||
ImGui.TreePop();
|
||||
}
|
||||
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
|
||||
if (ImGui.TreeNode("Colors"))
|
||||
{
|
||||
material.ImGuiColors(material.Parameters.Colors);
|
||||
ImGui.TreePop();
|
||||
}
|
||||
if (ImGui.TreeNode("All Textures"))
|
||||
{
|
||||
material.ImGuiDictionaries("textures", material.Parameters.Textures);
|
||||
ImGui.TreePop();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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.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
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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, () =>
|
||||
{
|
||||
|
|
@ -827,7 +840,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
}, 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) =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ using System.Threading;
|
|||
using System.Windows.Forms;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using FModel.Views.Snooper.Buffers;
|
||||
using ImGuiNET;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using OpenTK.Windowing.Common;
|
||||
using OpenTK.Windowing.Common.Input;
|
||||
|
|
@ -14,7 +13,6 @@ using OpenTK.Windowing.GraphicsLibraryFramework;
|
|||
using SixLabors.ImageSharp.Advanced;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Application = System.Windows.Application;
|
||||
using Keys = OpenTK.Windowing.GraphicsLibraryFramework.Keys;
|
||||
|
||||
namespace FModel.Views.Snooper;
|
||||
|
||||
|
|
@ -104,6 +102,7 @@ public class Snooper : GameWindow
|
|||
GL.Enable(EnableCap.CullFace);
|
||||
GL.Enable(EnableCap.DepthTest);
|
||||
GL.Enable(EnableCap.Multisample);
|
||||
GL.Enable(EnableCap.VertexProgramPointSize);
|
||||
GL.StencilOp(StencilOp.Keep, StencilOp.Replace, StencilOp.Replace);
|
||||
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
||||
|
||||
|
|
@ -112,33 +111,40 @@ public class Snooper : GameWindow
|
|||
_init = true;
|
||||
}
|
||||
|
||||
private void ClearWhatHasBeenDrawn()
|
||||
{
|
||||
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit);
|
||||
}
|
||||
|
||||
protected override void OnRenderFrame(FrameEventArgs args)
|
||||
{
|
||||
base.OnRenderFrame(args);
|
||||
if (!IsVisible)
|
||||
return;
|
||||
|
||||
var delta = (float) args.Time;
|
||||
|
||||
ClearWhatHasBeenDrawn(); // clear window background
|
||||
_gui.Controller.Update(this, delta);
|
||||
_gui.Render(this);
|
||||
|
||||
Framebuffer.Bind(); // switch to viewport background
|
||||
ClearWhatHasBeenDrawn(); // clear viewport background
|
||||
|
||||
Renderer.Render(delta);
|
||||
Renderer.Render(); // render everything
|
||||
|
||||
Framebuffer.BindMsaa();
|
||||
Framebuffer.Bind(0); // switch to window background
|
||||
|
||||
_gui.Render(this); // render UI
|
||||
SwapBuffers();
|
||||
}
|
||||
|
||||
private void ClearWhatHasBeenDrawn()
|
||||
protected override void OnUpdateFrame(FrameEventArgs e)
|
||||
{
|
||||
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit);
|
||||
GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill);
|
||||
base.OnUpdateFrame(e);
|
||||
if (!IsVisible)
|
||||
return;
|
||||
|
||||
var delta = (float) e.Time;
|
||||
|
||||
_gui.Controller.Update(this, delta);
|
||||
Renderer.Update(this, delta);
|
||||
}
|
||||
|
||||
protected override void OnTextInput(TextInputEventArgs e)
|
||||
|
|
@ -150,33 +156,6 @@ public class Snooper : GameWindow
|
|||
_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)
|
||||
{
|
||||
base.OnResize(e);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user