Compare commits

..

1004 Commits

Author SHA1 Message Date
Valentin
d132bc46e2
Merge pull request #510 from 4sval/dev
Dev
2024-10-23 09:14:58 +02:00
Asval
c6c8cf201e FModel v4.4.4
Some checks failed
FModel QA Builder / build (push) Has been cancelled
2024-10-20 21:11:17 +02:00
GMatrixGames
f266cd4c34
Bump for build
Some checks failed
FModel QA Builder / build (push) Has been cancelled
2024-10-14 18:55:07 -04:00
Marlon
18ed85b17b
netease & gameforpeace fixes 2024-10-14 21:55:42 +02:00
LongerWarrior
d1ca498643 CommitControl update
Some checks are pending
FModel QA Builder / build (push) Waiting to run
2024-10-14 19:05:23 +03:00
Marlon
07d26f7235
nuget updates
Some checks failed
FModel QA Builder / build (push) Has been cancelled
2024-10-12 10:07:01 +02:00
Asval
0f459624c0 bump
Some checks are pending
FModel QA Builder / build (push) Waiting to run
2024-10-12 01:15:55 +02:00
Asval
45996b730e my dumbass
Some checks are pending
FModel QA Builder / build (push) Waiting to run
2024-10-11 18:31:05 +02:00
Asval
905ab7cf98 actually removed update modes + uemodel texture export 2024-10-11 18:20:35 +02:00
LongerWarrior
3c614775c9 bump
Some checks are pending
FModel QA Builder / build (push) Waiting to run
2024-10-11 00:10:23 +03:00
LongerWarrior
65118dbc72 bump
Some checks failed
FModel QA Builder / build (push) Has been cancelled
2024-10-07 23:16:05 +03:00
Valentin
7fda6bc3b8
Merge pull request #503 from 4sval/no-update-modes
Some checks failed
FModel QA Builder / build (push) Has been cancelled
No update modes
2024-10-06 16:58:36 +02:00
Asval
774525ed50 actually implementing dotnet features 2024-10-06 15:31:04 +02:00
Asval
784704b645 add past 20 commits releases just in case 2024-10-06 03:13:03 +02:00
Asval
0060635e66 done? 2024-10-06 02:37:04 +02:00
LongerWarrior
e668c53947 bump
Some checks failed
FModel QA Builder / build (push) Has been cancelled
2024-10-04 01:32:57 +03:00
Asval
2d001ff21f more of the logic 2024-10-02 21:08:41 +02:00
Marlon
cd3a41d18c
make use of RandomAccess archives
Some checks failed
FModel QA Builder / build (push) Has been cancelled
2024-09-30 21:16:44 +02:00
LongerWarrior
bde2ce54ed GlobalShaderCache and TonyHawkProSkater support
Some checks are pending
FModel QA Builder / build (push) Waiting to run
2024-09-30 15:43:56 +03:00
Marlon
d5e7aba05c
Merge pull request #502 from Krowe-moh/patch-2
Some checks are pending
FModel QA Builder / build (push) Waiting to run
2024-09-29 23:03:13 +02:00
Krowe Moh
54ced30f51
support for more types 2024-09-30 06:54:41 +10:00
Asval
1edfab594d bump
Some checks are pending
FModel QA Builder / build (push) Waiting to run
2024-09-29 10:16:15 +02:00
Marlon
0df8501650
cue4p sync
Some checks are pending
FModel QA Builder / build (push) Waiting to run
2024-09-29 02:43:06 +02:00
Marlon
09b694c33c
nuget updates 2024-09-29 02:42:28 +02:00
Asval
ddee7c1479 current/latest badges 2024-09-29 01:52:41 +02:00
Asval
fdf3deed76 poc 2024-09-28 20:19:15 +02:00
Asval
7e35306b01 history ui done 2024-09-28 20:10:08 +02:00
Asval
51575d1025 Merge remote-tracking branch 'origin/no-update-modes' into no-update-modes 2024-09-25 18:42:56 +02:00
Asval
6a527c3a2f fix 2024-09-25 18:42:09 +02:00
Asval
6d4c76f2a4 ctrl s 2024-09-25 18:38:01 +02:00
Asval
bbb495b485 versioned backups
Some checks failed
FModel QA Builder / build (push) Has been cancelled
2024-09-25 18:32:23 +02:00
Asval
a54e0056c2 ctrl s 2024-09-22 20:23:59 +02:00
LongerWarrior
4b82eb37b0 MonsterJam+Rennsport+FunkoFusion support
Some checks failed
FModel QA Builder / build (push) Has been cancelled
2024-09-16 20:43:16 +03:00
Valentin
b536f10b14
Merge pull request #497 from GhostScissors/TGA
Some checks failed
FModel QA Builder / build (push) Has been cancelled
Texture export format based on user settings
2024-09-09 12:09:21 +02:00
GhostScissors
581db18c7b Update CUE4Parse and remove .png 2024-09-09 15:37:54 +05:30
GhostScissors
9c3eeaf9cc Remove GetBitmap since it is not being used anywhere 2024-09-09 15:28:00 +05:30
GhostScissors
4bce892c79 Texture export based on texture export settings
Asval is goofy
2024-09-09 15:24:01 +05:30
GhostScissors
3ebc245cf3 Revert 2024-09-09 13:33:34 +05:30
GhostScissors
1a62062289 Comment this out 2024-09-09 13:15:05 +05:30
GhostScissors
da62476639 Export textures as TGA 2024-09-09 13:13:30 +05:30
LongerWarrior
b6d67741e7 bump and improving MorphTarget data generation for Snooper
Some checks are pending
FModel QA Builder / build (push) Waiting to run
2024-09-08 21:10:18 +03:00
Asval
aa41224d57 slipping this in
Some checks failed
FModel QA Builder / build (push) Has been cancelled
2024-08-30 17:51:38 +02:00
LongerWarrior
0ae8386316 Updating MorphTarget 2024-08-30 18:48:01 +03:00
Asval
3df3e802d4 filterable ue combobox
Some checks failed
FModel QA Builder / build (push) Has been cancelled
2024-08-28 17:31:02 +02:00
Marlon
fbbf3c9c57
load preview from datalist first
Some checks failed
FModel QA Builder / build (push) Has been cancelled
2024-08-19 19:51:39 +02:00
Asval
693484f8e8 bump
Some checks are pending
FModel QA Builder / build (push) Waiting to run
2024-08-18 12:51:12 +02:00
Asval
3af527770b dotless i?
Some checks failed
FModel QA Builder / build (push) Has been cancelled
2024-08-15 21:56:09 +02:00
LongerWarrior
bf97c0002f Delta Force Hawk Ops support
Some checks are pending
FModel QA Builder / build (push) Waiting to run
2024-08-15 01:08:13 +03:00
Asval
1983e6f8bd bump 2024-08-14 19:11:47 +02:00
GMatrixGames
148c2c7db9
Bump for MR 2
Some checks failed
FModel QA Builder / build (push) Has been cancelled
2024-08-07 18:03:45 -04:00
GMatrixGames
f78903f1d2
Bump for MR 2024-08-07 16:19:53 -04:00
LongerWarrior
8d646b077b bump for VisionsofMana
Some checks failed
FModel QA Builder / build (push) Has been cancelled
2024-08-04 00:49:43 +03:00
LongerWarrior
a2f2117602 bump for WildAssault
Some checks are pending
FModel QA Builder / build (push) Waiting to run
2024-08-03 01:27:45 +03:00
Asval
fabc064a46 update ui
Some checks failed
FModel QA Builder / build (push) Has been cancelled
2024-07-29 12:48:35 +02:00
Marlon
19c2eb9e78
sync cue4p
Some checks failed
FModel QA Builder / build (push) Has been cancelled
2024-07-23 10:54:04 +02:00
GMatrixGames
5f9cda1016
Merge branch 'dev' of https://github.com/4sval/FModel into dev
Some checks are pending
FModel QA Builder / build (push) Waiting to run
2024-07-22 11:03:51 -04:00
GMatrixGames
7a06ac967e
parse 2024-07-22 11:03:46 -04:00
Asval
5494c6b259 bump 2024-07-17 22:19:04 +02:00
Minshu
ac835497da little better game name detection ig 2024-07-09 00:07:26 +05:30
Asval
c6383a1d22 close #479 2024-07-02 20:08:58 +02:00
GMatrixGames
9c50e2b7d7
Up parse 2024-06-30 14:42:09 -04:00
Marlon
6132f120c2
sync cue4p 2024-06-14 01:43:04 +02:00
Asval
2cf21c69ba fixed series border color 2024-06-13 22:33:56 +02:00
GMatrixGames
3e200bd5d0
Fix some issues 2024-06-13 13:57:48 -04:00
GMatrixGames
3aa1d60901
Merge branch 'dev' of https://github.com/4sval/FModel into dev 2024-06-13 13:35:34 -04:00
GMatrixGames
9774737e67
Fix icons on //Fortnite/Release-30.10 2024-06-13 13:35:22 -04:00
Asval
bd378594b1 fixed #476 2024-06-13 15:18:50 +02:00
Marlon
86ee2b98f3
updated EpicManifestParser 2024-06-11 02:11:51 +02:00
Marlon
1ccb2aa83b
nuget updates 2024-06-11 02:11:39 +02:00
Marlon
3fa30b5979
fix 2024-06-11 02:10:18 +02:00
Marlon
5d991e27b1
fix 2024-06-11 02:09:53 +02:00
GMatrixGames
631530dbf7
Outline Shader Drifting 2024-05-29 14:39:59 -04:00
Valentin
bcbda59523
Merge pull request #472 from 4sval/dev
Dev
2024-05-26 19:26:31 +02:00
Valentin
eb3f302552
Merge branch 'master' into dev 2024-05-26 19:25:23 +02:00
Asval
244462cc77 FModel v4.4.3.6 2024-05-26 19:18:42 +02:00
Asval
11bad8687e event triggered game directory 2024-05-25 19:51:43 +02:00
GMatrixGames
ed575429b1
Epic decided this was a good idea 2024-05-25 00:38:21 -04:00
Asval
e87e75acd4 removed atom model 2024-05-25 02:42:54 +02:00
Asval
9781446aef juno building preview 2024-05-25 00:19:47 +02:00
Asval
eb49b3c853 HighlightedCheckBox 2024-05-24 16:42:15 +02:00
GMatrixGames
ba31c63073
Fix exception when deleting saved games. 2024-05-21 16:34:33 -04:00
Asval
608e6fd42d forgot this 2024-05-19 21:17:41 +02:00
Asval
e94f558d6f split EGames 2024-05-19 20:31:36 +02:00
Asval
d7dac033cb bump 2024-05-18 21:09:11 +02:00
Asval
0bef5967c4 improved shader 2024-05-18 05:48:57 +02:00
LongerWarrior
c1c8adf122 3on3FreestyleRebound support 2024-05-13 22:01:12 +03:00
Asval
b2e7de5a47 time multiplier 2024-05-13 17:03:01 +02:00
GMatrixGames
ad665a83b9
Bump for Farlight 84 2024-05-12 15:48:41 -04:00
GMatrixGames
8867f4bf0c
Merge branch 'dev' of https://github.com/4sval/FModel into dev 2024-05-12 14:23:26 -04:00
GMatrixGames
b9a9a1e60e
Bump for QA 2024-05-12 14:23:08 -04:00
Asval
a8a278c989 more collisions 2024-05-12 07:25:23 +02:00
GMatrixGames
ade8da87a9
Marvel Rivals 2024-05-10 20:15:24 -04:00
LongerWarrior
330ab0a5b2 bump for PaxDei support 2024-05-09 19:22:10 +03:00
Asval
0b1c4bd4c9 bump 2024-05-08 13:56:02 +02:00
Asval
967ffefbe0 point clouds for now + reduced opacity 2024-05-06 23:22:59 +02:00
Asval
7e69adf978 crash fix 2024-05-04 23:48:32 +02:00
Asval
ca95df2dd4 convex collision preview 2024-05-04 23:25:01 +02:00
GMatrixGames
d3ce4bedd7
Cherry pick 0b11888ed2 2024-05-04 00:02:14 -04:00
Garrett Koleda
0b11888ed2
Change default mapping settings for Fortnite and Fortnite [LIVE]
Modifies the RegEx to display the first value in the array rather than only Oodle. Having this restriction prevents FModel from automatically loading mappings of other compression types. FModel and CUE4Parse both natively support all upstream compression types of the Usmap format, so it's unnecessary.
2024-05-03 23:56:48 -04:00
halfuwu
66ed2ce62a remove model/anim extension strings 2024-05-03 22:36:40 -04:00
LongerWarrior
2a733b9d9a compression settings 2024-05-03 22:36:40 -04:00
LongerWarrior
9a9eb02f05 DbD custom encryption handling 2024-05-03 23:40:00 +03:00
GMatrixGames
2df24242ac
Undawn custom encryption handling 2024-05-02 14:30:54 -04:00
Marlon
c3e4e7d404
obsolete fix? 2024-04-30 10:46:33 +02:00
Marlon
3714f1e125
cue4p sync 2024-04-30 10:46:23 +02:00
Asval
071d32b2da 8 bone influence 2024-04-30 02:54:40 +02:00
Asval
21cfa0781f bye bye 2024-04-27 12:35:13 +02:00
Marlon
0f20b543ca
use zip 2024-04-27 12:33:58 +02:00
Marlon
547dadbfc2
nuget updates 2024-04-27 12:33:47 +02:00
Marlon
81be2dacec
7z 2024-04-27 12:14:28 +02:00
Marlon
2d87dcf83c
grr 2024-04-27 12:10:24 +02:00
Marlon
5965cb7e7f
fix 2024-04-27 12:09:30 +02:00
Marlon
049a4434c9
updated qa qorkflow 2024-04-27 12:07:00 +02:00
Marlon
c52df06e7a
sync cue4parse 2024-04-27 11:22:59 +02:00
Marlon
82ce5e74aa
Merge pull request #463 from 4sval/manifestparser-rework 2024-04-27 11:19:21 +02:00
Marlon
a6eb13c0ab
updated faulty epicmanifestparser 2024-04-10 10:27:50 +02:00
Asval
eb9983bb18 improvements
- custom imgui settings directory
- viewport icons (not finished)
- can read ue version project name, number of archives, number of aes from logs
- bug fixes
2024-04-06 20:38:45 +02:00
Marlon
1852eb1b7d
removed submodule 2024-04-05 06:39:34 +02:00
Marlon
bc15757db0
nuget updates 2024-04-04 14:36:24 +02:00
Marlon
158bf9d6c7
minor changes 2024-04-04 14:36:12 +02:00
Marlon
4be7d4c3fb
small rework
removed DotNetZip
make use of Oodle.NET & Zlib-ng.NET
updated EpicManifestParser
minor optimizations
2024-04-04 06:18:49 +02:00
Asval
eabe4690aa bump 2024-03-08 16:57:08 +01:00
Lyndsey Winter
f64151b0ee
add support for ComponentContainer (#454) 2024-02-22 16:18:01 -05:00
FireMonkey
53bb6e1f90
LEGO Fortnite JIDO Emote Support (#455)
* LEGO Fortnite JIDO Emote Support

* LEGO Fortnite JIDO Emote Support

Might be a better way of doing this but if it fits it sits.
2024-02-22 16:06:30 -05:00
GMatrixGames
5e6765f4b0
version bump so you dont have to pull 2024-02-22 05:09:25 -05:00
GMatrixGames
8cbb9503fd
hm 2024-02-15 19:38:44 -05:00
GMatrixGames
3e8b155d4a
parse 2024-02-10 15:50:44 -05:00
Marlon
e22703c272
Merge pull request #450 from GhostScissors/dev 2024-02-08 14:56:47 +01:00
GhostScissors
033009ef82
Update BaseJuno.cs
Hmm not doing this anymore
2024-02-08 19:23:37 +05:30
GhostScissors
44f704258c
Update BaseJuno.cs
Some JIDO use "LowDetailsAssembledMeshSchema" proeprty instead of "AssembledMeshSchema"
2024-02-08 16:29:08 +05:30
Marlon
cd138b4037
Merge pull request #449 from djlorenzouasset/dev 2024-02-07 20:03:20 +01:00
ᴅᴊʟᴏʀ3xᴢᴏ
bb0741af38 QuestsBundle property change (DisplayName->ItemName) 2024-02-07 16:14:42 +01:00
GMatrixGames
ea279b6785
28.20 property name change 2024-02-07 09:50:22 -05:00
Asval
eb546602ea quest fix + juno icons 2024-01-28 15:53:32 +01:00
GMatrixGames
b2f6223958
parse update 2024-01-25 18:24:29 -05:00
Asval
69b18039e6 bump 2024-01-09 23:47:07 +01:00
Asval
fa11884936 bump acl 2024-01-09 17:26:27 +01:00
Asval
0160abf630 FModel v4.4.3.5 2024-01-07 13:18:23 +01:00
Asval
3721d7cd17 fixes 2023-12-07 18:05:47 +01:00
GMatrixGames
1461a6e525
Force QA build 2023-12-04 15:56:33 -05:00
Asval
a80379c3b9 bind pose stripped anim export + cubemap to panorama
Co-authored-by: Zain / Kaiser M <106357974+KaiserM21@users.noreply.github.com>
2023-11-25 05:36:08 +01:00
Asval
0551bc3731 .NET 8 2023-11-17 23:08:34 +01:00
Asval
8a790321dd if you have, you have 2023-11-16 22:50:41 +01:00
Asval
d6af826097 instanced SMs in umap 2023-11-16 20:39:01 +01:00
Asval
b525796f79 acl track skipping workaround 2023-11-09 20:34:58 +01:00
Asval
88adcd03be load ondemand archives 2023-11-08 22:15:04 +01:00
Valentin
5ef205c142
Update qa.yml 2023-11-04 20:23:37 +01:00
Valentin
b5a3b1c655
Update qa.yml 2023-11-04 20:18:11 +01:00
Valentin
e81259b298
Update qa.yml 2023-11-04 20:14:18 +01:00
Valentin
f510ca0a0c
Update qa.yml 2023-11-04 20:10:15 +01:00
Valentin
26a0dfcde8
Update qa.yml 2023-11-04 20:08:53 +01:00
Valentin
1e9f7e7355
Update qa.yml 2023-11-04 20:03:55 +01:00
Valentin
0f7bd1833a
Update qa.yml 2023-11-04 19:59:12 +01:00
Valentin
88e3ab1a71
Update qa.yml 2023-11-04 19:51:21 +01:00
Valentin
3b2a0fb044
Update qa.yml 2023-11-04 19:42:41 +01:00
Valentin
33e311c457
Update qa.yml 2023-11-04 19:05:58 +01:00
Valentin
3afb34018d
Update qa.yml 2023-11-04 19:04:10 +01:00
Valentin
5f2fea6828
Update qa.yml 2023-11-04 18:54:02 +01:00
Valentin
3cab5e6a27
Create qa.yml 2023-11-04 18:47:02 +01:00
Asval
af10998e31 hello 2023-11-04 18:40:26 +01:00
Asval
ef94d35ca2 qa testing test 2023-11-04 18:09:07 +01:00
GMatrixGames
5e5051628d
CUE4Parse-up 2023-11-02 20:52:27 -04:00
Valentin
9031dd40a3
Merge pull request #430 from KaiserM21/dev
Fix Overwrite Material Index
2023-10-30 20:51:13 +01:00
Zain / Kaiser M
422f892af8 Fix Overwrite Material Index 2023-10-30 18:52:40 +01:00
Asval
b478d43987 yes 2023-10-28 19:52:07 +02:00
Valentin
a83de461d4
Merge pull request #429 from 4sval/dev
Dev
2023-10-28 19:43:28 +02:00
Asval
db07574a61 FModel v.4.4.3.4 2023-10-28 19:40:36 +02:00
Asval
73e027c867 preview skeletons + save mesh materials switch 2023-10-28 18:17:16 +02:00
Marlon
4c5f5a213b
cue4p update 2023-10-20 06:11:34 +02:00
Asval
1f8f080c20 fixes 2023-09-18 18:10:55 +02:00
Asval
cf512a3d3d default checker texture instead of red + same vertex color texture 2023-08-30 18:47:18 +02:00
Asval
6b6403e9c1 better 2023-08-29 00:33:10 +02:00
Asval
6796577b9d whoops 2023-08-28 23:57:56 +02:00
Asval
b1be4da9b5 better skeleton tree 2023-08-28 23:50:09 +02:00
Asval
3c2795cded draw skeleton POC 2023-08-27 23:59:29 +02:00
Asval
a1494bf2e1 FModel v4.4.3.3 2023-08-27 19:03:46 +02:00
Asval
53bfbedefa fixes 2023-08-26 21:48:18 +02:00
Asval
ee9f3feb09 fixes 2023-08-25 22:30:11 +02:00
Asval
841f40e32b skeleton tree + fixed skeleton bones incorrect relation 2023-08-20 03:54:39 +02:00
Asval
1aa45b1b88 mock-up bone hierarchy 2023-08-17 01:06:36 +02:00
Asval
7d698ea8dd trying to merge 2 skeletons, doesn't fix anything 2023-08-13 08:10:29 +02:00
Asval
51516cd7d7 GetBufferSubData doesn't like being called too much, too bad 2023-08-12 19:09:48 +02:00
Asval
d97f570328 GetBoneTransform at runtime but it's flickering now, for later me 2023-08-12 06:34:12 +02:00
Asval
250b199b2a settings layout 2023-08-10 00:28:33 +02:00
Asval
223dd8fc3d multiple anims on same model 2023-08-09 23:56:20 +02:00
Asval
946d38c87b it is about time the update thread gets fixed 2023-08-06 20:02:21 +02:00
Asval
5dc90e6ec8 virtual textures preview 2023-08-03 04:39:15 +02:00
Asval
6ed335d483 POC frame interpolation 2023-08-01 23:06:16 +02:00
Asval
993726c681 ouin ouin ouin cue4parse errors 2023-07-30 13:06:50 +02:00
GMatrixGames
13c74f1506
AT9, forgot about this when recommiting 2023-07-11 11:00:44 -04:00
GMatrixGames
e92a66ac63
Recommit "AT9 Support" 2023-07-09 16:46:53 -04:00
GMatrixGames
a7fd4674b1
Revert "AT9 Support"
This reverts commit 8a72f29489.
2023-07-09 16:45:22 -04:00
GMatrixGames
8a72f29489
AT9 Support 2023-07-09 16:44:53 -04:00
Asval
487e40a64e #403 done 2023-07-09 04:03:13 +02:00
Asval
cb347adb52 ouin ouin ouin there's two 5.3 2023-06-24 00:00:57 +02:00
Valentin
b337ab7abd
Merge pull request #401 from 4sval/feature/settingsv2
feature/settingsv2
2023-06-23 00:15:21 +02:00
Asval
1829660256 valorant dirs 2023-06-23 00:08:27 +02:00
4sval
8fa027737b c'est ciao 2023-06-18 22:21:29 +02:00
4sval
d891c3e8da migrate main key + fix custom dirs dup + fix manual games add by ref 2023-06-18 04:51:36 +02:00
4sval
2a265eae60 usable 2023-06-18 03:26:50 +02:00
4sval
25c10e8638 that's the way 2023-06-18 02:34:54 +02:00
4sval
011046aa22 and this is where problems begin 2023-06-14 21:39:27 +02:00
4sval
a184a258ab explicit ue version 2023-06-13 23:54:09 +02:00
GMatrixGames
3435897e99
Handle KeepMobileMinLODSettingOnDesktop 2023-06-11 08:51:24 -04:00
4sval
6f11d870c3 FModel v4.4.3.2 2023-06-09 21:49:21 +02:00
4sval
d4e2bf187f SwizzleRoughnessToGreen 2023-06-09 21:19:37 +02:00
GMatrixGames
84f5d4d605
Fixes for texture refactor 2023-06-05 17:39:52 -04:00
4sval
6dce30b33f you back 2023-06-04 20:09:48 +02:00
4sval
cfa8eeea6c build 2023-06-04 18:18:37 +02:00
Valentin
3f9b1197d9
Merge pull request #391 from 4sval/dev
Dev
2023-06-04 18:12:16 +02:00
4sval
664054e4b4 FModel v4.4.3.1 2023-06-04 18:10:44 +02:00
GMatrixGames
2ddfdb4fcd
Custom version container 2023-05-28 13:55:26 -04:00
4sval
a9b25f5c0f folder 2023-05-27 21:35:09 +02:00
4sval
fa25b362cc problems 2023-05-23 00:54:56 +02:00
4sval
be061df238 packages bump 2023-05-23 00:38:31 +02:00
Valentin
6cc0cbbf72
Delete autoclose.yml 2023-05-21 22:30:13 +02:00
Valentin
36123c4cb2
Merge pull request #389 from 4sval/dev
Dev
2023-05-21 22:28:12 +02:00
4sval
91eb123cc7 FModel v4.4.3 2023-05-21 22:26:15 +02:00
4sval
b5e79ba71c vfc = local | cbm = local if not on demand | live = none 2023-05-20 18:55:13 +02:00
4sval
df66382cf7 vfc = local | cbm = live 2023-05-17 17:42:03 +02:00
4sval
ce4d96bd4a dead game nobody cares 2023-05-16 23:40:23 +02:00
4sval
d774fb7664 cleaned some stuff 2023-05-16 22:41:23 +02:00
GMatrixGames
870e7adeb6
Merge branch 'Kismet' into dev 2023-05-15 21:42:35 -04:00
4sval
b4f29562cd fn live hd texture download 2023-05-10 16:29:18 +02:00
4sval
42ad5ddb7a fortnite hd texture download 2023-05-09 14:27:11 +02:00
4sval
2bf6c245d9 hmm 2023-05-07 03:31:16 +02:00
4sval
2c099efd2b do as much as possible simultaneously without soft locking the UI 2023-05-06 22:45:58 +02:00
4sval
42116766d5 little warning 2023-05-06 17:25:59 +02:00
4sval
303775e848 yeeted 2023-05-06 16:34:13 +02:00
4sval
550e2b17cb cached fortnite hd textures 2023-05-06 16:27:11 +02:00
GMatrixGames
178184bcee
Forgot 2023-05-05 12:51:22 -04:00
GMatrixGames
c4f8e3025a
CUE4Parse pull 2023-05-05 12:43:20 -04:00
GMatrixGames
4fe00c9a1b
Merge remote-tracking branch 'fc/dev' 2023-05-05 12:33:03 -04:00
4sval
8453692bfe local clean 2023-04-15 14:33:34 +02:00
LongerWarrior
2aaf54c661 Adding Kismet 2023-04-07 22:20:38 +03:00
4sval
0f5aeaad8d vgmstream-cli.exe 2023-03-30 00:00:10 +02:00
4sval
f261dd9043 workaround #372 2023-03-29 23:47:17 +02:00
MountainFlash
7bd3f7da58 Fallen Order 2023-03-29 13:32:11 +05:30
4sval
c530275d84 game display name 2023-03-21 01:00:14 +01:00
Valentin
928834fd4b
Merge pull request #372 from GICodeWarrior/patch-1
Improve TreeView and Export performance
2023-03-18 22:34:49 +01:00
4sval
fdfaddd627 . 2023-03-17 17:08:00 +01:00
Rusty Burchfield
3e20a32bce Fix Visual Studio complaints about Nested Types
https://developercommunity.visualstudio.com/t/xaml-nested-types-are-not-supported/516142
2023-03-16 13:06:26 -07:00
Rusty Burchfield
f9f3eb9f6a Optimize Expand/Collapse for quicker layout 2023-03-16 12:46:00 -07:00
Rusty Burchfield
b0db727426 Enable TreeView UI Virtualization 2023-03-16 11:56:40 -07:00
4sval
26e92238ba cleaned skeleton and bones 2023-03-14 00:51:23 +01:00
Rusty Burchfield
d9e6c1f951
Sleep -> Yield 2023-03-12 18:30:06 -07:00
Rusty Burchfield
84bb78558e
Sleep -> Yield 2023-03-12 18:29:15 -07:00
4sval
39f5855b12 improved CustomRichTextBox + updateUi parameter to speed up savings 2023-03-12 00:11:59 +01:00
4sval
3f57ce6276 #367 2023-03-11 00:50:20 +01:00
4sval
43ff431782 additive 2023-03-08 22:54:27 +01:00
4sval
18f0d3a772 additive ref pose type 2023-03-07 02:48:03 +01:00
4sval
0d6f05c0f9 build 2023-03-05 17:28:28 +01:00
4sval
ec026400fc FModel v4.4.2 2023-03-05 17:20:13 +01:00
4sval
a5cbc5b9f5 keep bones in rest pos if there's no track for them 2023-03-04 16:44:11 +01:00
4sval
3f6e46ef82 timeline is dpi scaled 2023-03-04 13:03:57 +01:00
MountainFlash
e44a6a032f dpi scaling 2023-03-04 15:59:06 +05:30
MountainFlash
0343ee3577 better fps limiter 2023-03-04 09:05:11 +05:30
4sval
aca754a517 bug fixes 2023-03-03 03:40:38 +01:00
4sval
edd3a47353 section colors + no empty morph target 2023-02-26 16:39:04 +01:00
4sval
ab13eff151 animation relative retargeting? + covering fmodel's ass 2023-02-26 06:14:05 +01:00
4sval
412c5dd786 proper texture swizzling 2023-02-26 04:35:53 +01:00
4sval
ec2e25153e quality of use 2023-02-25 21:32:01 +01:00
4sval
94c5bba770 Preview Max Texture Size 2023-02-25 17:11:24 +01:00
4sval
2e1809793b load mat layers from settings 2023-02-25 02:43:35 +01:00
Valentin
5d730f0bc1
Merge pull request #361 from SirCxyrtyx/startup-crash
Exit gracefully if no game is selected during first-time setup
2023-02-22 10:10:38 +01:00
SirCxyrtyx
1b244e6317 exit gracefully if no game is selected during first-time setup 2023-02-21 21:17:54 -08:00
4sval
4247dec633 timeline 2023-02-21 23:21:14 +01:00
4sval
591a2d2336 animate loaded model 2023-02-21 18:06:37 +01:00
4sval
d78df4a953 working mess 2023-02-21 12:38:26 +01:00
4sval
cdb52d096f removed guid from models + bone delta matrix + timeline scroller 2023-02-20 20:06:43 +01:00
4sval
0ed26e1a7d fixed non virtual sockets being deleted on anim change 2023-02-17 16:23:15 +01:00
4sval
4797a4b338 bug fixes 2023-02-17 06:55:23 +01:00
4sval
0db81b8456 socket support for instanced models 2023-02-17 05:52:18 +01:00
4sval
4eb220168e new timeline part 1 2023-02-17 04:58:29 +01:00
4sval
e49216a844 separators 2023-02-16 20:09:04 +01:00
4sval
5b5dd8be53 timeline is world relative - part1 2023-02-16 19:47:19 +01:00
4sval
8438591839 saving this before refactoring 2023-02-16 16:55:48 +01:00
4sval
ad7dc46a0a fully fixed untracked bones parent 2023-02-16 12:57:43 +01:00
4sval
3b3fe6cb95 fixed outliner messing up vertex normal scale 2023-02-15 00:54:12 +01:00
4sval
ffd871b755 fixed outliner + reverse matrix per model + update ssbo only if needed 2023-02-14 23:34:32 +01:00
4sval
a01ef4a792 this is a problem for later me 2023-02-14 19:23:37 +01:00
4sval
a219b5bc7d animation retarget, kinda 2023-02-14 18:57:03 +01:00
4sval
62e619deef everything is now time based 2023-02-14 01:30:45 +01:00
4sval
144cf0eaf2 calculation of frame, frame in sequence, based on actual elapsed time 2023-02-13 20:35:23 +01:00
4sval
9b4c83931b timeline 2023-02-13 01:05:54 +01:00
4sval
c7d532fff9 timeline poc 2023-02-12 00:13:27 +01:00
4sval
a77d76ff34 using canvas in texture inspector 2023-02-11 23:57:42 +01:00
GMatrixGames
995cd25bc4
Fix soft crash on "blank" shop assets 2023-02-09 17:54:49 -05:00
4sval
f3c1103b0c nothing important 2023-02-09 20:06:58 +01:00
4sval
2a6c42d15f fixed outliner 2023-02-08 23:29:28 +01:00
4sval
9da407ea39 using SSBOs for unlimited bones 2023-02-08 22:18:19 +01:00
4sval
1ca18c3958 play anim sequences consecutively 2023-02-08 21:23:55 +01:00
4sval
70d4791b5b only parse bone tracks + anim montage test 2023-02-08 01:58:04 +01:00
4sval
af2fceb9e5 removed any kind of popup asking for a directory
it was more annoying than useful now that you can just click to open the file location
2023-02-07 21:14:00 +01:00
4sval
0b7ed2cf7f rotation only animation 2023-02-06 00:12:46 +01:00
4sval
f288791b71 fix picking texture + static meshes 2023-02-05 03:15:15 +01:00
4sval
a636c1ff84 play anim at the right pace + fixed outliner 2023-02-05 01:03:50 +01:00
4sval
f36a7b79cd some animations work, others do not, awaiting improvements 2023-02-04 01:31:37 +01:00
4sval
bbda1c5c0d close snooper on extract + moonman (marsman?) specular 2023-02-01 18:29:34 +01:00
GMatrixGames
535ffd45ec
vmodule and verse file types 2023-02-01 11:45:36 -05:00
4sval
79399e5db2 usable again 2023-01-27 23:45:55 +01:00
Valentin
35a804cbc8
Merge pull request #356 from LongerWarrior/MapPropertyStructTypes
MapStructTypes dictionary UI
2023-01-27 13:02:53 +01:00
LongerWarrior
b88dda3341 Fixed MapStructTypes button location 2023-01-27 12:47:35 +02:00
LongerWarrior
58d941c91c MapStructTypes dictionary UI 2023-01-27 12:40:01 +02:00
4sval
e96a3b6d4d fu 2023-01-25 00:04:53 +01:00
Marlon
abab1e427e
Update CUE4Parse 2023-01-20 23:45:39 +01:00
4sval
09ab40ad37 ctrl s, don't use this commit for tomorrow 2023-01-16 23:16:53 +01:00
4sval
f98c8b34da fixes 2023-01-14 03:09:26 +01:00
4sval
481d9d3032 more world lights 2023-01-14 00:37:51 +01:00
4sval
59e4c7c521 so there's 2 skeletons ok why not 2023-01-12 20:15:14 +01:00
4sval
ed5258895f sockets absolute rotation and scale 2023-01-12 15:53:38 +01:00
4sval
65d80158db don't save umap camera 2023-01-10 00:10:45 +01:00
4sval
c596f85c55 teleport at computed position 2023-01-09 20:08:12 +01:00
4sval
835d5f9d40 IsAttachment 2023-01-09 19:53:28 +01:00
4sval
8d986c4dc6 socket frontend 2023-01-09 19:06:50 +01:00
4sval
bf171b28cd socket backend 2023-01-09 13:11:35 +01:00
GMatrixGames
ac0e11ea39
Merge remote-tracking branch 'origin/dev' into dev 2023-01-07 23:26:18 -05:00
GMatrixGames
ac3a0e344e
naming 2023-01-07 23:26:14 -05:00
4sval
8f9a6927fd normalize quaternions 2023-01-08 03:03:26 +01:00
GMatrixGames
134f72293a
genxgames 2023-01-06 18:15:23 -05:00
4sval
51d334cb60 compute all bones transform 2023-01-06 16:48:39 +01:00
4sval
0221405758 I should have persevered with quaternions 2023-01-06 15:14:58 +01:00
4sval
eebaa19178 thread lock 2023-01-06 05:35:06 +01:00
Minshu Gurjar
41ebad422d
fix missing meshes (umap) 2023-01-05 18:19:42 +05:30
4sval
eecf5f16ce don't you dare tell me you don't know where things go now 2023-01-04 01:35:01 +01:00
4sval
d02272b82d fixes 2023-01-03 20:58:09 +01:00
4sval
174401ec42 morph tangent + cull facing 2023-01-02 20:36:38 +01:00
4sval
76a9f88eee cleaned camera + generated .umap position 2023-01-01 20:09:46 +01:00
4sval
26f9b5b9ce camera mode 2023-01-01 03:49:25 +01:00
4sval
9a0e6aa6c6 improved generic roughness 2022-12-31 04:52:58 +01:00
4sval
9cf6c32817 speed up raw data bulk export 2022-12-29 21:47:58 +01:00
Valentin
8e2363d114
Merge pull request #349 from 4sval/dev 2022-12-29 10:51:06 +01:00
4sval
944f77c70b FModel vStable 2022-12-29 10:29:26 +01:00
4sval
41adc2412a FModel v4.4.1 2022-12-28 16:18:55 +01:00
4sval
b068e446d0 kinda cleaned the big mess 2022-12-27 19:26:41 +01:00
GMatrixGames
16dd4236eb
Fix game version defaults 2022-12-22 23:04:08 -05:00
4sval
e1321a8258 full fix #344 2022-12-22 19:49:41 +01:00
4sval
2fee3c6ffa increased texture count for custom uv-ed models 2022-12-22 02:18:12 +01:00
4sval
857de890e9 whoops 2022-12-21 22:53:17 +01:00
4sval
a7bc3d0b60 removed auto export XYZ + added right click bulk commands 2022-12-21 18:33:56 +01:00
4sval
2c4c1faeaa bump ogl to 4.6 2022-12-20 23:46:39 +01:00
4sval
35f3ce38fb Emissive Region 2022-12-20 18:31:28 +01:00
4sval
e5b66bb8d9 improved maths for point & spot lights to make them look more realistic 2022-12-18 02:54:28 +01:00
4sval
b5aecebaf1 reduced models memory by 3 2022-12-17 18:06:38 +01:00
4sval
a2ae5da47f final fantasy emissive intensity 2022-12-16 22:41:20 +01:00
Marlon
cdc987b984
Merge branch 'dev' of https://github.com/4sval/FModel into dev 2022-12-16 19:54:04 +01:00
Marlon
352386d1fa
ui nullref fix 2022-12-16 19:53:34 +01:00
4sval
24512b0d1c fixed TextureData 2022-12-16 17:03:16 +01:00
4sval
fd709d08b7 texture viewer 2022-12-16 01:14:15 +01:00
4sval
21f39cc472 close #338 2022-12-15 19:06:37 +01:00
4sval
0327af1bd5 workaround #344 ? 2022-12-13 17:51:55 +01:00
GMatrixGames
2a0dd1eb8a
fix for invalid access token 2022-12-12 20:26:22 -05:00
4sval
91858ca5ca valorant specular 2022-12-11 23:00:00 +01:00
4sval
8ad48736f0 improved shader light reflection 2022-12-11 01:29:57 +01:00
4sval
b6872dda6e no description 2022-12-10 00:52:53 +01:00
4sval
2a4f91cc65 FortPlayerAugmentItemDefinition + no more auto save 2022-12-10 00:47:18 +01:00
GMatrixGames
fc82540388
pubg egl 2022-12-08 12:10:20 -05:00
4sval
1d22fa6cf6 fixed vertex colors + TextureData incorrect mapping 2022-12-06 22:54:31 +01:00
4sval
327cbfdd39 GetInternalSID #340 2022-12-06 13:43:37 +01:00
4sval
31e2a867fa fixed negative uv layer 2022-12-06 00:41:45 +01:00
Marlon
2fea609a63
exception fix 2022-12-05 04:44:43 +01:00
Valentin
794403afbf
Merge pull request #337 from 4sval/snooper-tk
snooper
2022-12-04 23:58:05 +01:00
Valentin
07ecd3273e Merge branch 'dev' into snooper-tk 2022-12-04 23:54:41 +01:00
4sval
69592ad46d FModel v4.4 2022-12-04 23:35:16 +01:00
4sval
9754ae4a73 releasable 2022-12-04 03:42:38 +01:00
4sval
617dfad22e fixed 0 padding + ao pixel color fighting 2022-12-04 02:15:42 +01:00
4sval
7a9556e957 save mesh 2022-12-03 02:02:29 +01:00
4sval
88fa0dfd82 default layout + switches + no more scroll flicker 2022-12-02 01:51:14 +01:00
4sval
1ae75d36d2 fixed clamp + meow roughness + ui stuff 2022-11-30 02:03:07 +01:00
4sval
fd7b1226b5 merged options and cache + imgui lights + roughness parameter 2022-11-29 21:58:42 +01:00
4sval
9926d0de7c meow lights 2022-11-27 22:19:39 +01:00
GMatrixGames
2173ac01fb
Core's engine version is wrong 2022-11-26 09:17:45 -05:00
GMatrixGames
932f295fd3
efootball game detection for extra path 2022-11-26 00:11:13 -05:00
4sval
53dd500e03 System.Numerics + cached uniforms location 2022-11-24 19:09:15 +01:00
4sval
e7ff5c41a6 well, I guess spots always points to the ground 2022-11-24 03:34:49 +01:00
4sval
8b072e39fc umap lights 2022-11-24 02:44:01 +01:00
4sval
ce529c41ec ui stuff 2022-11-17 23:53:40 +01:00
4sval
38b6371a81 ui stuff 2022-11-17 01:27:26 +01:00
4sval
d0963bd018 imgui icons + material inspector v.final +/- 2022-11-16 01:35:30 +01:00
GMatrixGames
9d7e491bf3
Improve IsExpired with proper auth check 2022-11-15 11:18:20 -05:00
GMatrixGames
ede6628a2d
Update cosmetic season parsing. (Manual update each chapter)
Feel free to update the initial implementation, I'm not dealing with math rn 🤷
2022-11-14 21:48:01 -05:00
4sval
ad6c4b9474 fuck you bones position
that's a problem for future me
2022-11-13 22:10:22 +01:00
4sval
25de50818d pbr lights + gamma correction 2022-11-11 04:45:38 +01:00
4sval
65d20fbde8 :blobglareannoyed: 2022-11-09 02:14:44 +01:00
4sval
f14da04c05 fixed some keys not being forwarded to imgui 2022-11-09 01:51:41 +01:00
4sval
0a5862a0ca code clean up 2022-11-07 18:47:33 +01:00
4sval
86c3eba85f refactored & improved shaders and materials 2022-11-07 02:10:41 +01:00
4sval
b8d731c3ae relative to the parent 2022-11-05 01:39:34 +01:00
4sval
c2bb0ead88 additional worlds + hierarchical transformation matrix 2022-11-05 01:03:21 +01:00
4sval
556a27577d socket format 2022-11-04 11:35:11 +01:00
4sval
481078cbca fixed EULER rotation 2022-11-04 11:18:07 +01:00
4sval
b4ea36cb68 socket format 2022-11-04 08:13:49 +01:00
4sval
53d9ca97e6 FModel v4.3.2.1 2022-11-03 16:58:37 +01:00
4sval
2f3b076aa4 fix texture misalignment + negative far plane 2022-11-03 00:42:05 +01:00
4sval
cc89becb4c picking texture, not pixel perfect but good enough 2022-11-02 23:16:38 +01:00
4sval
a267c6233f ui 2022-11-02 02:16:44 +01:00
4sval
216e8b069c morphing is back 2022-11-02 00:03:04 +01:00
4sval
3e41fba8b0 bump 2022-11-01 14:09:06 +01:00
4sval
9dc553500d bump 2022-11-01 13:54:14 +01:00
4sval
525718d35c gui 2022-11-01 05:23:53 +01:00
4sval
321b82b458 material improvements 2022-11-01 03:56:40 +01:00
4sval
b4df69fbeb junky cam movements 2022-10-31 01:56:29 +01:00
4sval
4e321d74c9 docking gets saved now wtf 2022-10-30 04:05:33 +01:00
4sval
c6dcd921d8 slowly getting better 2022-10-29 23:55:57 +02:00
4sval
59dd045a42 not perfect but better 2022-10-28 03:16:39 +02:00
4sval
86bd205878 I guess I can't get away so easily for now 2022-10-27 21:10:34 +02:00
4sval
3d1d125e40 per used material render call 2022-10-27 20:44:03 +02:00
4sval
12eba5e730 good enough for now 2022-10-27 15:50:15 +02:00
4sval
dfea705c5e nop 2022-10-25 22:53:51 +02:00
4sval
edfa3276cf submodule 2022-10-25 22:13:59 +02:00
GMatrixGames
70c2754bd1
Fix Fortnite [LIVE] 2022-10-25 15:00:57 -04:00
4sval
29f556dc2a uv map poc 2022-10-25 00:45:21 +02:00
4sval
023b68ffae fixed gap between grid and skybox 2022-10-22 00:17:56 +02:00
4sval
b32d77601e cached textures 2022-10-21 23:13:20 +02:00
4sval
4da6b2d775 somewhat usable 2022-10-19 23:54:38 +02:00
4sval
59973f95d6 only setup the needed one 2022-10-19 00:12:34 +02:00
4sval
41319ad2a3 fmodel { -viewer: save !important; } 2022-10-18 23:54:05 +02:00
4sval
c8f0d9f014 POC 2022-10-14 23:13:28 +02:00
4sval
e3e5689fce ctrl s 2022-10-12 02:18:54 +02:00
4sval
d047b56978 stuff 2022-10-09 23:25:29 +02:00
GMatrixGames
a664f34bec
CUE4Parse changes 2022-10-09 13:36:17 -04:00
4sval
b57585a0c6 implemented loading label 2022-10-06 22:59:58 +02:00
4sval
41d93177b5 swap before vacation 2022-09-21 20:21:28 +02:00
4sval
c98634df80 preparation 2022-09-21 01:17:32 +02:00
4sval
656e1908f2 do not convert mesh if instanced 2022-09-19 06:39:19 +02:00
4sval
7cff932a81 less restriction on emission 2022-09-18 20:26:12 +02:00
4sval
db384fbc7d morph 2022-09-18 19:51:53 +02:00
4sval
98a40fc2f1 sync with dev 2022-09-18 19:19:21 +02:00
4sval
d8032572c1 FModel v4.3.2 2022-09-18 19:09:03 +02:00
Valentin
c7f9187b02
Merge pull request #323 from shimizu-izumi/dev
Auto Detection for Tower of Fantasy
2022-09-18 18:13:52 +02:00
4sval
9196a96981 snooper fix 2022-09-18 18:06:15 +02:00
4sval
cd73bdd70f no more useless data in buffer 2022-09-17 23:51:35 +02:00
4sval
40500d925f properly working 2022-09-17 13:04:21 +02:00
4sval
2d73375392 one at a time, take it or leave it. 2022-09-16 06:55:44 +02:00
4sval
24e3d549f5 no fuck you 2022-09-16 00:14:38 +02:00
Shimizu Izumi
0010b4cedf Removed the custom endpoint for Tower of Fantasy because it completely broke the settings (you weren't able to open them). 2022-09-15 19:33:51 +02:00
4sval
569558640a poc 2022-09-15 00:39:54 +02:00
Shimizu Izumi
a7dc54b48d Added auto detection for the standalone launcher version of Tower of Fantasy 2022-09-14 14:48:24 +02:00
4sval
926cbec7eb blinded by the lights 2022-09-13 22:56:45 +02:00
4sval
0295d47eba mesh morphing POC 2022-09-13 22:29:29 +02:00
4sval
dd963c7cce ui stuff 2022-09-08 23:22:47 +02:00
4sval
29983c23e3 finally what I was looking for 2022-09-07 22:44:42 +02:00
4sval
807029d211 better 2022-09-07 00:39:56 +02:00
4sval
a45ad49414 object outlining v0.annoying 2022-09-07 00:20:49 +02:00
4sval
a7885b1dbc instancing + uworld + fixed black normal maps 2022-09-05 23:31:40 +02:00
4sval
c63c1d8434 I thought it was working but no 2022-09-04 18:26:50 +02:00
4sval
355b7f0c3d don't go faster than the underlying platform can manage 2022-09-04 11:20:42 +02:00
4sval
4710d3afbc append mode shortcut 2022-09-04 10:18:15 +02:00
4sval
872b01c7ef imgui stuff 2022-09-03 23:50:02 +02:00
4sval
97b03ed0c7 ok i'll wait the official 2022-09-03 14:30:01 +02:00
4sval
eabf6d9bcf switch to ImTool 2022-09-03 13:03:21 +02:00
4sval
ac124e8f08 fixed layout 2022-09-03 09:57:11 +02:00
4sval
129f128f90 transforms 2022-09-02 20:38:27 +02:00
4sval
1684dfdfcc no more game check 2022-09-01 21:04:43 +02:00
4sval
2533166f5d bump 2022-09-01 21:00:41 +02:00
4sval
4194052eed ctrl s 2022-09-01 20:53:05 +02:00
4sval
bb7eaf3e15 frozen events workaround 2022-08-31 23:47:28 +02:00
4sval
fb330e33da msaa enabled 2022-08-31 23:04:15 +02:00
4sval
5b08fc400d basically just a non msaa framebuffer 2022-08-31 20:04:59 +02:00
4sval
628570e84d even less 2022-08-30 21:46:47 +02:00
4sval
6ba553e890 less shader call 2022-08-30 21:11:39 +02:00
4sval
37b5989378 display vertex colors 2022-08-30 19:40:27 +02:00
4sval
009ad9dc55 fixed light without normal map 2022-08-30 17:53:30 +02:00
4sval
b947ac3e96 improved shader 2022-08-30 17:14:32 +02:00
4sval
7b9e53c931 get out 2022-08-30 01:03:56 +02:00
4sval
cfc1a31fc7 there are a lot of new problems
it crashes because of Avalon sometimes, ImGui isn't stable sometimes, icon display name is not centered anymore, still mouse and keyboard somtime freeze
2022-08-30 00:58:07 +02:00
4sval
9b5bbd976d skybox 2022-08-29 23:06:47 +02:00
4sval
0c5ede60af fuck you skybox 2022-08-29 19:48:31 +02:00
4sval
b2002121ab fixed material index + wrong gl + draw distance + grid fading 2022-08-29 02:07:56 +02:00
4sval
8597e491e0 get back here 2022-08-29 01:20:28 +02:00
4sval
0b01400493 hmm 2022-08-29 01:10:57 +02:00
4sval
f7c1a764f7 basic layout 2022-08-28 23:43:20 +02:00
4sval
9f3d9b3341 hello imgui 2022-08-28 18:15:15 +02:00
4sval
80e8f3f02d infinite grid skid but it was pain in the ass 2022-08-28 17:48:05 +02:00
4sval
dde7688f2e really bad shader 2022-08-28 00:04:28 +02:00
4sval
4edfdabcd0 dynamic camera speed 2022-08-27 18:37:33 +02:00
4sval
2cf40a022c I have no idea how I made multiple materials work 2022-08-27 17:50:05 +02:00
4sval
0014b6f4ed good cam position + fixed depth mip mapping + trying to understand shit 2022-08-27 00:42:42 +02:00
4sval
7d775a8dd4 something's wrong 2022-08-26 16:10:42 +02:00
4sval
2c0fee3fd3 proof of concept 2022-08-25 23:38:34 +02:00
4sval
9b967341c2 just ctrl s 2022-08-25 20:27:35 +02:00
4sval
0f120f26ea prout 2022-08-25 20:23:20 +02:00
4sval
4a6b12ade1 f 2022-08-21 11:21:11 +02:00
4sval
6d5b3f801e fallback 2022-08-18 14:20:34 +02:00
4sval
83f1f3b1e7 bunch of stuff 2022-08-16 20:43:09 +02:00
4sval
77903bad20 hmmm odd chance 🤔 2022-08-15 19:23:43 +02:00
4sval
a108efbddf new link color + VisualLineTexts are weird + hide audio spectrum 2022-08-14 00:02:38 +02:00
4sval
0be44a37b2 update stuff that needs to be updated 2022-08-13 02:51:11 +02:00
4sval
f8e929b347 reworked how exceptions were displayed 2022-08-13 00:39:52 +02:00
4sval
61c1c67733 random stuff 2022-08-09 19:59:38 +02:00
4sval
4c195134bc allow null main + dynamic keys 2022-08-07 23:01:53 +02:00
4sval
527d48f7c2 FModel v4.3.1 2022-08-07 18:50:15 +02:00
4sval
3e89b784e5 panda missions 2022-08-07 18:16:25 +02:00
4sval
4931313636 should not throw exceptions anymore 2022-08-07 15:26:46 +02:00
4sval
4a9327633f no auto test 2022-08-07 14:57:45 +02:00
4sval
73151eaf38 instruction 2022-08-07 14:40:18 +02:00
4sval
947d5011c2 not bullet proof but good enough 2022-08-07 04:50:39 +02:00
4sval
a8c60ac3a9 xml 1 - json 0 2022-08-06 23:29:17 +02:00
4sval
e2a5a6af60 sleeping makes you clever 2022-08-06 12:08:17 +02:00
4sval
516fa33f93 endpoint configuration template 2022-08-06 02:38:28 +02:00
4sval
746292892d uh 2022-08-03 22:55:56 +02:00
4sval
ef8e08e4c7 few fixes 2022-08-03 00:05:35 +02:00
4sval
bc276e171b eheh 2022-08-02 20:10:26 +02:00
4sval
4e6e5448e5 prout 2022-08-02 19:57:53 +02:00
4sval
263c213dcc rip fonts 2022-08-02 18:43:48 +02:00
4sval
8725cdadf5 ctrl s 2022-08-02 00:31:43 +02:00
4sval
27791e0202 done 2022-07-31 16:07:03 +02:00
4sval
d046e6254f more stuff 2022-07-31 14:29:32 +02:00
4sval
ad2f275d5e don't mind me, simple CTRL S 2022-07-31 03:41:41 +02:00
Valentin
5330da94c8
Merge pull request #312 from OutTheShade/dev
Some more Multiversus support
2022-07-30 22:02:50 +02:00
shade
f429e7c911 multiversus 2022-07-29 15:31:52 -04:00
GMatrixGames
56dd9633a3
Move hotfixes too 2022-07-29 08:57:24 -04:00
GMatrixGames
88383c30f0
Move mappings and AES to Fortnite Central 2022-07-27 22:15:55 -04:00
4sval
ffedb2be90 I feel a little scammed 2022-07-26 18:47:12 +02:00
Valentin
918339c56b
sigh 2022-07-25 22:15:29 +02:00
4sval
3bc1d10988 grrrr 2022-07-24 20:55:53 +02:00
Valentin
c014478abc
Merge pull request #308 from 4sval/dev
Dev
2022-07-24 20:50:20 +02:00
4sval
7faa18fc3b FModel v4.3 2022-07-24 20:46:13 +02:00
4sval
f4cce4a60c settings 2022-07-23 22:22:42 +02:00
4sval
73f143e915 Specialis Revelio!!!! 2022-07-20 13:56:46 +02:00
4sval
57ca9dd813 news per game 2022-07-13 23:12:48 +02:00
4sval
8d80c3f8ac updated verse highlighter + #289 v1 2022-07-06 11:17:07 +02:00
4sval
97b4b00766 reworked verse highlighter 2022-06-22 00:06:13 +02:00
TSG
6bc02bfb13
Allow game forcing (#289)
* Allow game forcing

Allow users to over-write the auto-detected game set by FModel.

* Fix game forcing for net7

Fix the game forcing feature for the net7 update

* update to match latest fmodel source tree

* Revert "update to match latest fmodel source tree"

This reverts commit f7dfe654fa.
2022-06-17 12:21:31 -04:00
iAmAsval
c8b96b54fb fixed invisible main aes 2022-06-14 19:19:06 +02:00
iAmAsval
d570e45cb2 upgrade 2022-06-13 12:35:11 +02:00
GMatrixGames
5a31f9662c Set back to net 6.0 until another solution is done 2022-06-12 16:30:33 -04:00
iAmAsval
710414e995 ulang reader 2022-06-12 04:24:17 +02:00
GMatrixGames
e21a3be55b
Update/net7 (#290)
* file-scoped namespace & net7.0

* Workflow
2022-06-11 20:07:59 -04:00
iAmAsval
85158296a9 prout 2022-06-05 11:12:11 +02:00
GMatrixGames
75f8681043 Adjust windows version name detection
Fixes Windows 11 being improperly identified as Windows 10
2022-05-19 23:05:22 -04:00
MountainFlash
b88f288a11
Revert "check if InstalledBundles dir exists"
This reverts commit c3027d2102.
2022-05-19 15:52:23 +05:30
MountainFlash
c3027d2102
check if InstalledBundles dir exists 2022-05-19 15:37:03 +05:30
iAmAsval
8c706646ed FModel v4.2.2.1 2022-05-07 01:06:21 +02:00
MountainFlash
b319d9d89e
Fortnite InstalledBundles 2022-04-30 16:26:58 +05:30
iAmAsval
e2523b4a80 prout 2022-04-19 12:14:31 +02:00
GMatrixGames
ff5712860f
Fix Fortnite BR map viewer showing mask instead of map 2022-03-26 22:00:48 -04:00
XTigerHyperX
3a85fcbc05
quest fix 2022-03-23 14:58:03 +01:00
iAmAsval
7dff317981 FModel v4.2.2 2022-03-20 13:40:08 +01:00
iAmAsval
a594f53037 fixed fortnite 2022-03-20 11:03:44 +01:00
Officer
ee9fa6f0a3
fixed fortnite live 2022-03-20 10:11:26 +01:00
Ricky Owens
905c1a61ef
CRD_Melee WID Support (#252)
The following WIDs listed below used a new type of "FortCreativeWeaponMeleeItemDefinition" so I decided to throw in a quick fix to allow those icons to be generated.

WID_Creative_Melee_Sword_Ninja
WID_Creative_Melee_Spear_Basic
WID_Creative_Melee_Hammer_SledgeWrapped
2022-03-07 11:45:30 -05:00
GMatrixGames
c0a66fa60f
Manually merge e73f209e26 2022-02-28 09:59:43 -05:00
GMatrixGames
bb0af2d790
Update CUE4Parse submodule 2022-02-25 17:00:27 -05:00
MountainFlash
64edf00864
Chinese, Korean and Japanese text wrap 2022-02-18 13:40:35 +05:30
GMatrixGames
130e5867e0
typo 2022-01-20 12:19:56 -05:00
GMatrixGames
59a952cf54
per-platform texture support 2022-01-20 12:13:58 -05:00
GMatrixGames
d8627ebac2
Fix preview being overly large for certain Tandem images 2022-01-18 09:47:16 -05:00
GMatrixGames
9dfb828b16
fix 2022-01-18 04:01:27 -05:00
iAmAsval
3ad638dd67 proper fix 2022-01-18 01:23:28 +01:00
iAmAsval
a6adddf8fe can draw and show multiple icons on one extract 2022-01-17 22:21:23 +01:00
Not Officer
082917d3cb skbitmap migration 2022-01-14 17:08:47 +01:00
Valentin
10de896db9
Merge pull request #241 from OutTheShade/dav2-offer-images
display all offerimages in a dav2
2022-01-14 15:57:56 +01:00
GMatrixGames
62d9fd8fb7
Fix CTRL+A in text area cycling tabs
There's probably a different fix for this, but it works for now.
2022-01-11 14:05:03 -05:00
iAmAsval
1724039c5d implemented #242 2022-01-10 16:42:23 +01:00
shade
20602edee5 display all offerimages in a dav2 2022-01-08 23:15:22 -05:00
iAmAsval
1603295b16 user defined image size 2022-01-07 09:53:58 +01:00
iAmAsval
0b22a5b2a3 closed #239 2022-01-07 08:10:46 +01:00
GMatrixGames
fb33536c26
New NPC design, minimal compared to original 2022-01-06 17:49:27 -05:00
MountainFlash
9c2a899c35
fix copy material name and better handling mesh section when material
is unavailable
2021-12-27 19:13:27 +05:30
iAmAsval
fee89ebca8 FModel v4.2.1 2021-12-26 23:05:50 +01:00
iAmAsval
17c3fcea8c so it was crashing the whole time and nobody noticed 2021-12-23 23:03:04 +01:00
iAmAsval
05208deac7 support for multiple images in one package 2021-12-23 22:29:34 +01:00
iAmAsval
dabe13d495 don't forget to thank extract folder users if you find it laggy now 2021-12-23 13:25:05 +01:00
iAmAsval
f0caf246b5 just getting rid of some output folders 2021-12-22 18:21:57 +01:00
iAmAsval
fdfaddaa66 extract from search window 2021-12-21 21:34:36 +01:00
iAmAsval
7dc62d2989 fix 2021-12-21 11:36:12 +01:00
iAmAsval
ec71fc4491 expander 2021-12-21 00:01:03 +01:00
iAmAsval
1d25732fbf manual detected games 2021-12-20 20:15:08 +01:00
iAmAsval
227dafae53 materials + skeleton fix 2021-12-18 04:01:23 +01:00
Valentin
5db52e720c
Merge pull request #225 from iAmAsval/dev
Dev
2021-12-16 23:43:59 +01:00
iAmAsval
609e3e6e33 FModel v4.2 2021-12-16 23:43:23 +01:00
iAmAsval
7978d58e71 box 2021-12-16 10:38:57 +01:00
iAmAsval
4cfcbd0dd7 inner package navigation should be more obvious now 2021-12-15 22:05:39 +01:00
iAmAsval
501d216594 I can already hear people being lost because of a label change 2021-12-15 21:46:39 +01:00
XTigerHyperX
249757454f
Fortnite Funny 2021-12-12 23:23:10 +01:00
XTigerHyperX
7eb6d8090e
Easier to look at imo 2021-12-12 12:11:12 +01:00
iAmAsval
9aedd9fcc5 fixed overwriting wrong export 2021-12-11 15:33:26 +01:00
iAmAsval
40715b4c12 fixed rare overwrite material index error 2021-12-11 14:49:30 +01:00
iAmAsval
98b4da93a5 overwrite material in preview and in the mesh
so saving the mesh will also save the new material instead of the overwritten one
2021-12-10 22:56:48 +01:00
iAmAsval
061a0de29b fix material color overlapping material name 2021-12-09 23:49:33 +01:00
iAmAsval
45f4e9a476 material name before file name 2021-12-09 23:22:00 +01:00
MountainFlash
13ebe99862
Merge branch 'dev' of https://github.com/iAmAsval/FModel into dev 2021-12-09 23:46:47 +05:30
MountainFlash
6d0cc1b87a
made cubemap darker 2021-12-09 23:44:19 +05:30
iAmAsval
5f1ddce5ed you don't need that anyway 2021-12-09 10:39:23 +01:00
Not Officer
a6d62d91bf Update CUE4Parse 2021-12-08 08:30:46 +01:00
iAmAsval
84160d0a3a FModel v4.1.0.2 2021-12-07 21:59:12 +01:00
iAmAsval
2471874027 overwrite mapping file 2021-12-07 13:32:54 +01:00
Not Officer
b3955379a6 enum cleanups 2021-12-07 02:22:37 +01:00
iAmAsval
354401a24a bounty boards 2021-12-06 23:03:25 +01:00
iAmAsval
9d2181b260 dedicated model directory 2021-12-06 21:35:55 +01:00
iAmAsval
b3d7930fc0 fixed margins 2021-12-06 18:06:06 +01:00
MountainFlash
845542a6bf
save models as scene
Ctrl + S -> Save
Ctrl + Shift + S -> Save as Scene
2021-12-06 22:32:27 +05:30
iAmAsval
7f14a5ca05 improved model settings close #222 2021-12-06 13:48:50 +01:00
iAmAsval
66c8672ace map viewer is back 2021-12-06 00:05:50 +01:00
iAmAsval
878b41b055 ce n'est qu'un aurevoir 2021-12-05 15:25:21 +01:00
iAmAsval
e62f7fdf4f prout 2021-12-05 15:12:21 +01:00
iAmAsval
9581ca7a12 lol I didn't commit CUE4Parse last time 2021-12-04 00:13:49 +01:00
iAmAsval
e085b09bb9 emissive color support 2021-12-03 22:18:52 +01:00
iAmAsval
7033e57d8d colored gta map 2021-11-30 00:36:49 +01:00
iAmAsval
347b28cb52 better material color handling 2021-11-26 19:53:11 +01:00
GMatrixGames
60dae604b1
Remove State of Decay 2 icon creator
It's currently unused, and not useful to include
2021-11-25 19:53:16 -05:00
iAmAsval
998d8581ff and there was light 2021-11-25 21:27:27 +01:00
iAmAsval
eb987195d6 hello you 2021-11-25 20:58:45 +01:00
iAmAsval
1b9bd2c56a MaterialColorToggle 2021-11-25 02:11:34 +01:00
Valentin
212d8a12f8
Merge pull request #216 from BrandonItaly/dev
Add Dead by Daylight detection
2021-11-24 14:18:15 +01:00
Marlon
2d6d3aabac
Merge pull request #215 from floxay/dev 2021-11-24 14:11:06 +01:00
floxay
dce3d1fa17 Brackets 2021-11-24 11:05:10 +01:00
Brandon
4d3fd786c8 game name converter 2021-11-23 21:04:24 -08:00
Brandon
39062e02fb Add Dead by Daylight detection 2021-11-23 17:53:07 -08:00
floxay
3da075096f Valorant MRAE MRAS MRA MRS 2021-11-24 02:18:10 +01:00
GMatrixGames
ba017a4efa
Steam detection causes issues 2021-11-23 08:16:27 -05:00
iAmAsval
2b41943c01 open material in viewer choice 2021-11-22 23:18:29 +01:00
iAmAsval
2dc8ae98fb save loaded and appended models 2021-11-22 22:46:26 +01:00
GMatrixGames
214f0a519d
Update CUE4Parse 2021-11-21 15:18:44 -05:00
iAmAsval
f2115b5bcc frustration? 2021-11-21 18:34:10 +01:00
iAmAsval
19bf7403f9 prout 2021-11-21 18:21:51 +01:00
Valentin
7b56359ba2
6 2021-11-21 18:08:42 +01:00
Valentin
bc2285adf0
Merge pull request #213 from iAmAsval/dev
Dev
2021-11-21 18:04:59 +01:00
Valentin
26e376609b
Merge branch 'master' into dev 2021-11-21 18:04:15 +01:00
iAmAsval
9224207aeb FModel v4.1 2021-11-21 18:02:12 +01:00
iAmAsval
d846750ac6 revert d37c5a922f 2021-11-21 16:35:14 +01:00
Not Officer
235f663ea5 vs 2022 2021-11-21 15:32:40 +01:00
Not Officer
d37c5a922f fix? 2021-11-21 15:32:31 +01:00
Not Officer
94e2ad3cc6 nuget update 2021-11-21 15:32:02 +01:00
MountainFlash
0669572d5d
shortcuts to recenter and focus selected mesh
Shift+C: Recenter
Decimal: Focus selected
2021-11-21 12:48:37 +05:30
iAmAsval
c62d3f691e gn 2021-11-21 02:02:00 +01:00
iAmAsval
c12a393f22 model viewer kinda done 2021-11-21 01:53:18 +01:00
iAmAsval
8a0a51867e refactored model viewer 2021-11-20 22:17:00 +01:00
GMatrixGames
22d1a32651
Fix shop MIs trying to be loaded in Material viewer 2021-11-19 19:06:59 -05:00
iAmAsval
4aed95019c MIRROR_MESH 2021-11-19 21:38:34 +01:00
iAmAsval
3e199836e4 valorant specular 2021-11-19 19:17:23 +01:00
iAmAsval
78db897510 gta specular 2021-11-19 18:34:37 +01:00
MountainFlash
dc797ade45
UMaterialInstance viewer 2021-11-19 22:39:12 +05:30
MountainFlash
7d12023618
fixed auto open meshes 2021-11-19 11:39:54 +05:30
iAmAsval
97eefb25af and i forget this again 2021-11-19 00:57:07 +01:00
iAmAsval
0ce3b47ad3 refactored model viewer to allow cool things 2021-11-19 00:44:55 +01:00
iAmAsval
5742f713e3 fixed fucked up camera 2021-11-18 20:18:34 +01:00
MountainFlash
411c0a135c
auto open mesh viewer 2021-11-18 19:12:22 +05:30
MountainFlash
81fbe018d4
Merge branch 'dev' of https://github.com/iAmAsval/FModel into dev 2021-11-18 17:25:19 +05:30
MountainFlash
eaf38495c7
env map not rendering fix 2021-11-18 17:24:18 +05:30
GMatrixGames
a75207487d
It doesn't like material names with dashes
https://cdn.discordapp.com/attachments/813073594388054047/910591518329409557/unknown.png
2021-11-17 13:10:08 -05:00
MountainFlash
86f0a5b96c
Merge branch 'dev' of https://github.com/iAmAsval/FModel into dev 2021-11-17 23:38:35 +05:30
MountainFlash
ae4aebd5ce
handle null stream path 2021-11-17 23:38:27 +05:30
GMatrixGames
73104a4656
Fix quest icons when NPCs have mic as icon 2021-11-17 13:03:31 -05:00
MountainFlash
c1d7d4ee35
SpecularMap support for fortnite 2021-11-17 20:15:34 +05:30
iAmAsval
577b82e86f emissive has a problem with some skins 2021-11-16 23:39:23 +01:00
iAmAsval
cdf629e1e7 red 2021-11-16 20:14:01 +01:00
MountainFlash
a3781e728d
no flat shading please 2021-11-17 00:39:37 +05:30
MountainFlash
e0f8fec74c
pbr mats, wireframe and diffuse only view 2021-11-17 00:34:20 +05:30
iAmAsval
5f6b70a742 starting to look like something usable 2021-11-16 16:53:13 +01:00
iAmAsval
8bd848219d normal map texture + hide material 2021-11-16 12:05:22 +01:00
iAmAsval
8cafa960e1 fixed crash if model viewer was closed 2021-11-15 19:35:50 +01:00
iAmAsval
177ae1dc0a multiple material support in viewer 2021-11-15 15:48:06 +01:00
iAmAsval
6c5a83409b support for "GTA: The Trilogy - Definitive Edition" 2021-11-13 16:03:33 +01:00
iAmAsval
8b23abd271 GTA extensions 2021-11-11 21:56:24 +01:00
XTigerHyperX
d7f11f3976
Merge branch 'dev' of https://github.com/iAmAsval/FModel into dev 2021-11-08 21:15:20 +01:00
XTigerHyperX
de123e1fcf
safe margin 2021-11-08 21:14:45 +01:00
Marlon
ce722a2371
hello .net 6² 2021-11-08 20:04:56 +01:00
Tiger
83b5c9a9c7
fixed publish for net 6 2021-11-08 20:04:44 +01:00
XTigerHyperX
1bd1e4dff2
hello .net 6 2021-11-08 19:51:15 +01:00
Not Officer
9bb0d1d40b fix 2021-11-03 22:24:26 +01:00
MountainFlash
d244717ce6
pass meshformat to Exporter 2021-10-29 13:39:51 +05:30
Marlon
2124e4742f
updated runtime download 2021-10-18 18:51:56 +02:00
MountainFlash
41a0cf0950
fixed OnTabClose 2021-10-18 10:52:01 +05:30
MountainFlash
24a7efccf4
remember caret offset for all open tabs
i hope this works fine.
2021-10-18 10:21:46 +05:30
GMatrixGames
d65fd5159f
update editor config to have space next to casts 2021-10-14 11:40:28 -04:00
iAmAsval
6f78a9c24e close #208 2021-10-13 23:01:46 +02:00
iAmAsval
722110dbc6 ui changes 2021-10-13 22:29:35 +02:00
GMatrixGames
0687958dab
Work with CUE4Parse update 2021-10-09 17:01:55 -04:00
iAmAsval
930a49b84b fix #207 2021-10-08 20:50:04 +02:00
MountainFlash
7c118a43c6
go to last cursor location 2021-10-06 21:24:43 +05:30
GMatrixGames
e73e1c7de9
Steam game detection (Splitgate and PUBG supported rn) 2021-10-03 18:05:53 -04:00
GMatrixGames
8f4f501138
Add hotfix translations 2021-10-02 21:04:28 -04:00
iAmAsval
f87fc8baeb bug fixes 2021-09-29 22:09:07 +02:00
Not Officer
fa9f64b060 added simple editorconfig 2021-09-23 14:10:37 +02:00
Not Officer
743303b4db use official adonis ui 2021-09-23 14:10:21 +02:00
Marlon
81ff427c7f
Merge pull request #203 from floxay/dev 2021-09-23 12:12:15 +02:00
floxay
a2f7098063 Fix normals in model viewer 2021-09-23 12:08:04 +02:00
iAmAsval
2d8a1322e1 prout 2021-09-22 19:47:12 +02:00
iAmAsval
16411edf9c i'd say better than nothing 2021-09-22 15:59:23 +02:00
GMatrixGames
e81a081c5f
Merge remote-tracking branch 'origin/master' into dev 2021-09-20 19:13:15 -04:00
GMatrixGames
3768ad2fdd
No need for FName "Rarity" anymore. 2021-09-20 19:13:03 -04:00
Marlon
adeb290799
updated runtime download 2021-09-20 07:40:35 +02:00
GMatrixGames
8bceb10f44
Force quest bundles to use 512x512 image, and Tandem images based on display asset settings 2021-09-19 22:24:12 -04:00
Valentin
6afd2090ef
Merge pull request #201 from iAmAsval/dev
Dev
2021-09-19 22:35:39 +02:00
iAmAsval
f2f6fc01d2 FModel v4.0.2 2021-09-19 22:35:04 +02:00
iAmAsval
17a1b0b69b more precise cube movements 2021-09-19 01:34:49 +02:00
GMatrixGames
805e5d8f5e
Merge remote-tracking branch 'origin/dev' into dev 2021-09-17 10:48:46 -04:00
GMatrixGames
619a7d018f
Add another creative control option type. 2021-09-17 10:48:27 -04:00
iAmAsval
35ce351fbd Cube Movements 2021-09-17 00:00:53 +02:00
iAmAsval
c27d7425c5 Corruption Zones 2021-09-16 18:59:54 +02:00
MountainFlash
07282faf8c
updated action 2021-09-15 20:46:30 +05:30
iAmAsval
8414fd5760 updated map viewer 2021-09-13 15:37:57 +02:00
iAmAsval
0bfd3b5b7f fixed outdated bindings 2021-09-13 10:13:03 +02:00
iAmAsval
34bca798c2 Auto Save Animations 2021-09-13 00:04:53 +02:00
GMatrixGames
794c749f0d
Fix dynamic pak asset registry detection. 2021-09-11 21:43:34 -04:00
Not Officer
6ac5fae132 reworked oodle usage 2021-09-11 00:40:15 +02:00
iAmAsval
4824aa01aa ResizeWithRatio 2021-09-10 23:39:49 +02:00
Not Officer
a741131a37 fixed syntax 2021-09-10 23:18:35 +02:00
Valentin
bf79ca5f12
Merge pull request #198 from sirvibegodlol/master
Fixes Minecraft Dungeons detection and adds more Quick Directories.
2021-09-08 09:28:07 +02:00
Nick
97c313b773
Update UserSettings.cs 2021-09-06 12:43:58 -04:00
iAmAsval
32da5d409b better 2021-09-06 18:39:40 +02:00
iAmAsval
664ddfcb7f because human input can be trusted 2021-09-06 18:21:44 +02:00
Nick
5a6e823099
Update GameSelectorViewModel.cs 2021-09-06 12:15:40 -04:00
Marlon
3a458d6d8d updated runtime download 2021-09-02 19:57:23 +02:00
iAmAsval
45f46015e8 zbeub 2021-09-01 18:07:40 +02:00
iAmAsval
14cc7e20c1 reworked right click commands 2021-08-30 20:49:54 +02:00
iAmAsval
35a8e4c71d added search for playlist 2021-08-30 20:11:17 +02:00
GMatrixGames
2915f831d0
Add SVG support to image viewer 2021-08-29 13:31:01 -04:00
iAmAsval
e15fa646d6 Expand_All / Collapse_All / Bring_To_View + Shift Enter 2021-08-24 20:21:07 +02:00
Not Officer
5abde2d7a4 disabled ready-to-run compile feature 2021-08-17 02:35:10 +02:00
GMatrixGames
a737e9e347
Update vgmstream release zip name 2021-08-16 10:30:45 -04:00
GMatrixGames
93789ccd42
4.0.1.1 2021-08-16 08:48:06 -04:00
GMatrixGames
5cc77f58d9
CUE4Parse update 2021-08-16 08:37:50 -04:00
GMatrixGames
3947a4a5d8
Virtual path stuff 2021-08-15 17:42:48 -04:00
Amrsatrio
80c9a27e10 Fix TryGetPackageIndexExport 2021-08-16 01:59:52 +07:00
GMatrixGames
2275587735
Add otf and ttf file warnings 2021-08-12 14:11:04 -04:00
iAmAsval
63ffdf0493 cue4parse update 2021-08-12 00:59:37 +02:00
iAmAsval
724c95aae1 Versioning Configuration 2021-08-09 21:10:19 +02:00
iAmAsval
f462b183ee fn live + version presets 2021-08-06 16:57:01 +02:00
GMatrixGames
c082463722
Update to be inline with CUE4Parse changes 2021-07-31 15:40:09 -04:00
iAmAsval
6672c9f10a converter 2021-07-31 17:30:23 +02:00
iAmAsval
d949bc5fb1 fixed mirrored textures 2021-07-30 22:07:54 +02:00
iAmAsval
9ee9241c63 proper uv mapping 2021-07-30 21:27:08 +02:00
GMatrixGames
810863dbb4
Inline with CUE4Parse update 2021-07-28 16:47:40 -04:00
iAmAsval
ec5e84a73e disable alpha channel 2021-07-28 18:10:33 +02:00
Not Officer
7ee52508ea keep image alpha in clipboard 2021-07-24 23:49:43 +02:00
Not Officer
1894641554 refactoring 2021-07-24 23:49:06 +02:00
Not Officer
6bdc7e74fa faster conversion w/o skia 2021-07-24 23:47:49 +02:00
Not Officer
1568ff5897 keep image alpha in clipboard 2021-07-24 21:29:29 +02:00
Not Officer
7c816ef824 Update CUE4Parse 2021-07-24 19:53:06 +02:00
Amrsatrio
adafa5721c Oof didn't see that 2021-07-22 21:22:19 +07:00
Amrsatrio
316ccc3e60 Submodule update 2021-07-22 21:22:19 +07:00
Amrsatrio
580fcba740 Use VersionContainer and fixed AssetRegistry file detection 2021-07-22 21:22:19 +07:00
XTigerHyperX
13adae9a98
Weapons Stat fixes for throwables 2021-07-22 15:18:47 +01:00
GMatrixGames
c0f43f7f84
Fix material instance path checks 2021-07-21 17:03:58 -04:00
iAmAsval
fa6324c8cf CVE-2021-27293 2021-07-20 17:04:35 +02:00
GMatrixGames
423a4be164
Merge remote-tracking branch 'origin/dev' into dev 2021-07-18 08:43:44 -04:00
GMatrixGames
d3f1924690
Better handing of unsupported file types 2021-07-18 08:43:27 -04:00
GMatrixGames
ae9e16e6c7
Add FortWeaponModItemDefinition to icon gen
This is pretty much just future proofing tbh
2021-07-17 16:39:01 -04:00
GMatrixGames
d39dd12389
Change Rogue Company game to custom + update CUE4Parse 2021-07-16 17:25:44 -04:00
iAmAsval
feee89f2e2 documentation? non-existent, pain? 100%, choose something else? probably 2021-07-15 18:15:20 +02:00
iAmAsval
db7fb35bd2 fixed the fucked up camera and meshes wrongly axed on Y 2021-07-13 22:50:56 +02:00
iAmAsval
983d9da845 scuffed but working model viewer 2021-07-13 16:55:26 +02:00
iAmAsval
08050aee78 release notes 2021-07-13 01:17:21 +02:00
GMatrixGames
dd932aed97
CUE4Parse update 2021-07-11 11:58:51 -04:00
GMatrixGames
39689ce2b1
Merge branch 'master' of https://github.com/iAmAsval/FModel into dev 2021-07-11 11:31:46 -04:00
GMatrixGames
c72b4ecf1a
line split fix for FN Alpha assets. 2021-07-11 11:29:19 -04:00
Valentin
714384458c
Merge pull request #187 from iAmAsval/dev
new map viewer, mesh support, other stuff
2021-07-11 13:54:05 +02:00
iAmAsval
d66b832b89 FModel v4.0.1 2021-07-11 13:51:54 +02:00
GMatrixGames
e878d2b2ee
Core is 4.25, not 4.24 2021-07-10 22:01:56 -04:00
iAmAsval
277bee902e full mesh support 2021-07-10 14:46:46 +02:00
iAmAsval
f4e7eeb6ae mesh export settings 2021-07-10 01:59:46 +02:00
GMatrixGames
8d263b6f84
AshtonBoardwalk MI fix 2021-07-07 19:50:46 -04:00
iAmAsval
73e2211cca generic exporter 2021-07-06 19:55:57 +02:00
iAmAsval
d03dc3e4e2 mesh export poc 2021-07-04 17:31:51 +02:00
iAmAsval
586d221573 console is back 2021-07-03 12:14:39 +02:00
iAmAsval
3a16131954 Fold Toggle At Level 2021-06-27 02:47:24 +02:00
iAmAsval
721f543d99 kinda useful folding shortcuts 2021-06-27 02:37:58 +02:00
iAmAsval
ed5dfe0a0a added Tags Location 2021-06-27 00:12:06 +02:00
iAmAsval
40c16c1c5c added Upgrade Benches, Phonebooths, and Alien Artifacts 2021-06-26 18:25:48 +02:00
iAmAsval
648f7c728a code folding doesn't like tabs 2021-06-24 17:11:21 +02:00
iAmAsval
37503ec012 code folding 2021-06-24 16:04:17 +02:00
GMatrixGames
e32136d5b2
Fix most* of the NPC patrol paths
Requires more testing on other GFD we need, but this will resolve a majority of them.
2021-06-22 12:56:15 -04:00
iAmAsval
79cb09df77 click 2021-06-22 00:03:59 +02:00
iAmAsval
1d4fc38c20 breadcrumb no click 2021-06-21 19:28:06 +02:00
GMatrixGames
6061730fee
Only use MIs for display assets 2021-06-21 08:45:14 -04:00
iAmAsval
b3e36708d6 go upper folder 2021-06-14 13:43:53 +02:00
iAmAsval
9ca5dbd1c8 advanced settings 2021-06-12 23:07:28 +02:00
Valentin
7cd4f32c71 just testing remote repositories 2021-06-12 23:04:55 +02:00
XTigerHyperX
7f18d0e80f
i hope they understand man 2021-06-11 23:32:42 +01:00
iAmAsval
be257d02cb really important update 2021-06-10 16:52:36 +02:00
iAmAsval
e98a113d13 improved mapviewer readability 2021-06-09 12:46:02 +02:00
iAmAsval
d74f7b1372 fixed a few aes problems 2021-06-09 09:41:23 +02:00
iAmAsval
2bde70ff2d binded BitmapScalingMode 2021-06-09 00:06:05 +02:00
iAmAsval
93866684f0 fixed season icons but will change 2021-06-08 16:18:32 +02:00
Valentin
3398c8a151
Merge pull request #182 from alextusinean/dev
change update messagebox
2021-06-08 10:09:37 +02:00
Alex Tusinean
95d013e95e move import 2021-06-08 11:03:30 +03:00
Alex Tusinean
e9470242eb this wasn't really needed but 2021-06-08 11:00:46 +03:00
iAmAsval
5eaa6c5c92 just testing 2021-06-08 01:05:36 +02:00
iAmAsval
60e23373f3 more discord rpc 2021-06-07 17:12:09 +02:00
iAmAsval
3ce2fb1430 fixed party royale time trials path 2021-06-05 08:49:51 +02:00
iAmAsval
a42a3fc6fd should fix some issues with the settings at launch 2021-06-05 08:06:44 +02:00
iAmAsval
aba44d5ea5 cue4parse update 2021-06-05 06:54:01 +02:00
iAmAsval
9a1501db4b map viewer v2 done kinda 2021-06-05 05:26:13 +02:00
GMatrixGames
36fa636dab
Days Gone and Battle Breakers won't search recursive folders 2021-06-04 22:50:31 -04:00
iAmAsval
588baef479 cannonball + skydive games 2021-06-05 04:15:19 +02:00
GMatrixGames
a58b212bec
Resolve error because I didn't set a default UE version 2021-06-04 22:13:10 -04:00
GMatrixGames
e9f308d028
Add Days Gone as UnrealEngine game. 2021-06-04 21:51:55 -04:00
iAmAsval
8dc9f21b94 back to a working map viewer 2021-06-05 03:25:32 +02:00
iAmAsval
a80052ef77 saving this 2021-06-05 00:50:55 +02:00
GMatrixGames
af72bd00fa
Handle background, defo can be improved 2021-06-03 20:42:43 -04:00
iAmAsval
d4e8e5abc4 finally it worked out 2021-06-04 00:03:07 +02:00
iAmAsval
23fc8c6c8f test again 2021-06-03 23:58:54 +02:00
GMatrixGames
2d1c20df3a
Try to figure out this TryLoadObject issue 2021-06-03 17:56:33 -04:00
Not Officer
df807b0421 fixed lz4 decompressing 2021-06-03 22:59:37 +02:00
GMatrixGames
384222bb96
Initial export type gathering for State of Decay 2 icons 2021-06-03 16:03:41 -04:00
Not Officer
b198774991 Update CUE4Parse 2021-06-03 20:56:14 +02:00
Not Officer
aeda17978d Update CUE4Parse 2021-06-03 20:37:03 +02:00
Not Officer
af0eae58d3 Merge branch 'dev' of https://github.com/iAmAsval/FModel into dev 2021-06-03 20:34:37 +02:00
iAmAsval
836d80f779 i give up now 2021-06-03 18:38:37 +02:00
iAmAsval
e52054695d test 2021-06-03 18:34:40 +02:00
iAmAsval
fca9856c85 i'm dumb 2021-06-03 17:22:23 +02:00
iAmAsval
2388246805 submodule update 2021-06-02 15:12:17 +02:00
GMatrixGames
004d5d8aea
submodule update 2021-06-01 16:55:04 -04:00
GMatrixGames
ff450a62d5
Merge remote-tracking branch 'origin/dev' into dev 2021-05-31 19:41:10 -04:00
GMatrixGames
459d8a523f
Fix '+' being drawn on icons that don't have UserFacingFlags 2021-05-31 19:40:45 -04:00
iAmAsval
e575b5fb3b base map viewer v2 2021-06-01 00:10:30 +02:00
GMatrixGames
9a79bd608d
Fix certain items not displaying on quests. 2021-05-31 16:43:06 -04:00
Not Officer
6f63d53152 fixed pak filepath concatination 2021-05-31 20:20:35 +02:00
Not Officer
d613ade489 fixed indexing root files (yes) 2021-05-31 15:16:36 +02:00
Not Officer
61d8efbaaa fixed indexing root files 2021-05-31 15:14:34 +02:00
Not Officer
fc80857ea9 Update CUE4Parse 2021-05-31 14:52:25 +02:00
Not Officer
1989a0f386 Update CUE4Parse 2021-05-31 14:46:53 +02:00
Not Officer
4f062fb58d another upscale case 2021-05-30 00:53:02 +02:00
Not Officer
5e334d82ca fixed bad statement 2021-05-29 23:53:18 +02:00
Not Officer
8349d2bb93 upscale linear images 2021-05-29 23:46:29 +02:00
GMatrixGames
bfe648f06c
Battle Breakers multiple directory support 2021-05-29 11:00:14 -04:00
iAmAsval
d5e33f1224 fixed jump to asset folder 2021-05-27 21:51:57 +02:00
GMatrixGames
554361bea5
Remove Party Royale landmarks from map viewer 2021-05-25 14:37:11 -04:00
Valentin
e5d7315d41
Merge pull request #178 from iAmAsval/dev
4.0.0.1
2021-05-25 19:12:23 +02:00
iAmAsval
a86de258b3 FModel v4.0.0.1 2021-05-25 19:10:55 +02:00
GMatrixGames
eecc708cba
Merge remote-tracking branch 'origin/dev' into dev 2021-05-24 16:15:11 -04:00
GMatrixGames
aeaa350d29
Add preview for ability kits 2021-05-24 16:15:02 -04:00
GMatrixGames
39b8f02f16
Revert preview drawing 2021-05-24 12:46:09 -04:00
XTigerHyperX
56596fb229
slider update by click 2021-05-24 13:16:17 +01:00
GMatrixGames
dd605933a4
GameModeInfo (currently not generating) 2021-05-23 22:32:22 -04:00
Ricky Owens
603b7a8a9f
[Spellbreak] Mo' Icons, Mo'... Icons! (#177)
* [Spellbreak] Mo' Icons, Mo'... Icons!

- Added support for Real Money Purchases, Active Skills, Store Offers.

- Fixed the wave of cases

- Fixed Cosmetic Cards not exporting properly.
2021-05-23 21:59:36 -04:00
GMatrixGames
9774d8b7bf
STW quest data 2021-05-23 21:15:39 -04:00
Ricky Owens
3460654898
[Battle Breakers] Massive Icon Support Added (#176)
FModel 4 now supports icons in Battle Breakers such as... well... All of it?
2021-05-23 20:53:48 -04:00
GMatrixGames
ccb5814024
Spellbreak fonts 2021-05-23 20:20:24 -04:00
Valentin
4f69509d6d fixed custom dir 2021-05-24 01:33:47 +02:00
GMatrixGames
395e2cb1dd
Re-add .udic support 2021-05-23 13:41:52 -04:00
Valentin
dd1457b580 pak double click + fixed export 2021-05-23 12:13:39 +02:00
XTigerHyperX
df27d650a8
fix shotgun dmg value / ticket 145 2021-05-23 07:47:56 +01:00
Valentin
3a6b166cf6 added valorant languages support + fixed backups not created 2021-05-23 01:16:47 +02:00
XTigerHyperX
829916e444
fix ticket 141 2021-05-22 23:06:26 +01:00
Not Officer
9413e68b5b fixed & re-added valorant live 2021-05-22 23:51:30 +02:00
Valentin
d536df54ef fixed ticket 129 2021-05-22 22:35:44 +02:00
GMatrixGames
2af6557fc8
Re-add GPL-3 license 2021-05-22 16:33:08 -04:00
Valentin
e37a09b42d
Update main.yml 2021-05-22 22:20:18 +02:00
Valentin
c403453cc3 FModel v4.0 2021-05-22 22:10:08 +02:00
iAmAsval
d7751d6f95 pre shot 2021-04-12 22:57:12 +02:00
XTigerHyperX
543ce7faeb
don't load _oo local mapping kthx 2021-03-30 11:47:25 +01:00
XTigerHyperX
3cb83faa8f
jpg file extension fix ( should fix SMarketing files ) 2021-03-25 04:18:08 +01:00
iAmAsval
7199bd3eba fixed #165 2021-03-18 16:41:18 +01:00
iAmAsval
af48c4393a fixed #169 2021-03-17 18:49:53 +01:00
Not Officer
5d02d81ba1 minor fixes 2021-02-21 02:38:22 +01:00
Not Officer
d3402c3430 fixed fortnite version message 2021-02-18 20:02:09 +01:00
Not Officer
d63b5c0e40 added manifest version console logs 2021-02-18 19:58:04 +01:00
Not Officer
9ea3ada561 implemented valorant v2 manifest 2021-02-18 12:44:43 +01:00
Not Officer
5680634acc updated packages 2021-02-18 12:44:25 +01:00
iAmAsval
2331b2de71 AssetObjectProperty != SoftObjectProperty 2021-02-10 20:25:46 +01:00
iAmAsval
9194d2bd1d Valorant ? 2021-02-02 23:23:05 +01:00
MaikyM
941f60ca97 Fixed Item shop icons. 2021-01-19 15:25:02 -06:00
iAmAsval
70901c9078 fixed crash for dead game mode folder 2021-01-14 16:59:38 +01:00
iAmAsval
ed63dc9d07 mapping fallback 2021-01-14 16:15:44 +01:00
XTigerHyperX
3659d2dfd8 epic being nice again 2021-01-13 23:59:42 +01:00
Not Officer
c8669237ca FIoStoreTocHeader fix 2021-01-13 21:39:11 +01:00
Not Officer
4ab5c95d2c proper FIoStoreTocHeader implementation 2021-01-13 21:26:40 +01:00
Not Officer
67d7032ea3 updated nuget packages 2021-01-13 21:16:59 +01:00
Not Officer
c060047d31 IoStoreToc PartitionSize fix 2021-01-13 21:10:40 +01:00
iAmAsval
dfa291e293 ticket 19 2021-01-11 16:39:52 +01:00
XTigerHyperX
c65c66cedc i'm so smart guys 2021-01-10 18:31:16 +01:00
XTigerHyperX
961031df8e we love epic 2021-01-10 18:18:55 +01:00
iAmAsval
5162e7f481 ok 2021-01-10 15:16:24 +01:00
iAmAsval
dc963f00fa hypex was here 2021-01-09 23:34:57 +01:00
iAmAsval
efc5b598db FModel 3.1.2 2021-01-09 23:06:09 +01:00
iAmAsval
37131d1885 fixed a few things 2021-01-08 20:08:19 +01:00
GMatrixGames
ce42936a9c
Fix QuestBundle_Styles_SpaceBunny 2021-01-04 14:50:19 -05:00
iAmAsval
f653e004cc ok vs 2021-01-04 17:00:18 +01:00
iAmAsval
92fca5278d fixed infinite loop 2021-01-04 14:31:14 +01:00
iAmAsval
841e76d2d7 implemented usmap 2021-01-04 14:12:08 +01:00
iAmAsval
d05bed14a4 Chic was here 2021-01-03 15:04:22 +01:00
Not Officer
68368265ec fixed exporttype "parsing" ^2 2020-12-30 15:50:47 +01:00
Not Officer
afc6d783ec fixed exporttype "parsing" 2020-12-30 15:05:46 +01:00
Not Officer
16bf9de35e fixed enumproperty parsing 2020-12-30 14:51:14 +01:00
Not Officer
06c4ebd539 fixed unversioned objects with empty headers 2020-12-30 14:42:01 +01:00
Not Officer
ae6b2b336d fixed fieldpathproperty parsing 2020-12-30 14:41:37 +01:00
Valentin
7cc9c376a5 Update issue templates again 2020-12-30 01:21:27 +01:00
iAmAsval
bb28447159 fixed black textures bug 2020-12-30 00:44:44 +01:00
Valentin
f77dcddb60 Update issue templates 2020-12-30 00:30:02 +01:00
Valentin
f86f47c366 Update issue templates 2020-12-30 00:28:36 +01:00
Valentin
e2b841787d Added issue templates 2020-12-30 00:21:27 +01:00
iAmAsval
09be7a9ea6 updated FByteBulkData so close #147 2020-12-23 17:46:11 +01:00
iAmAsval
b736325b6a fixed plugined assets 2020-12-23 15:31:53 +01:00
iAmAsval
d233349171 new item shop icon support 2020-12-21 01:40:50 +01:00
iAmAsval
1e01f39917 curve table fix 2020-12-18 19:22:38 +01:00
iAmAsval
1a3b2935f0 actual reward has priority over xp reward 2020-12-15 20:50:45 +01:00
iAmAsval
127af65b82 always put the export types 2020-12-15 00:10:38 +01:00
Fabian
c6e06d958e fixed not working cid's
basically "fixed" all Cannot read beyond end of stream
fixed more Cannot receive last fragment in Skip()
2020-12-08 20:45:45 +01:00
Fabian
3aad0fc919 fixed "Cannot receive last fragment in Skip()" 2020-12-07 20:14:04 +01:00
MaikyM
c2b11f9544 Fixed some paths (Fixed S15 Challenges Rewards) 2020-12-05 14:38:40 -06:00
iAmAsval
b75f7b6705 fixed bulk export + FortTandemCharacterData 2020-12-03 21:56:01 +01:00
GMatrixGames
aff147e0b2
Fix weekly challenge generation (missing EXP) 2020-12-03 12:57:09 -05:00
MaikyM
bbf7b6d9c5 Fixed some errors (QuestBundles S15 now works) 2020-12-03 10:14:14 -06:00
iAmAsval
e256486b98 my bad 2020-11-21 21:59:15 +01:00
iAmAsval
7c9b820a8d some other stuff 2020-11-21 21:54:30 +01:00
GMatrixGames
ded4c7a749
Implement fix for Blueprints causing crashes
Blueprints can't be mapped out, so I have disabled the parsing of them, so they return empty json information.
2020-11-21 15:49:11 -05:00
Maiky M
b5c6137fda
Update appveyor.yml 2020-11-15 16:50:37 -06:00
Valentin
5d086d855e
Update appveyor.yml 2020-11-14 22:14:09 +01:00
Valentin
749a2d6b30
Merge pull request #139 from iAmAsval/imgbot
[ImgBot] Optimize images
2020-11-14 22:01:53 +01:00
GMatrixGames
023fc1f758
Darn it git 2020-11-14 16:00:45 -05:00
ImgBotApp
036463c821
[ImgBot] Optimize images
*Total -- 66.87kb -> 45.47kb (32%)

/FModel/Resources/roguecompany.png -- 9.11kb -> 4.52kb (50.36%)
/FModel/Resources/battle-breakers-item-background.png -- 25.66kb -> 15.69kb (38.85%)
/FModel/Resources/theouterworlds.png -- 32.10kb -> 25.26kb (21.31%)

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>
2020-11-14 20:59:18 +00:00
GMatrixGames
0d11af9188
Implement new games and 1 lang
Games: Rogue Company & The Outer Worlds | Languauges: Zulu
2020-11-14 15:58:58 -05:00
Valentin
4e5df3ee50
Merge pull request #138 from GMatrixGames/master
Add default extension to files without one
2020-11-14 21:07:02 +01:00
GMatrixGames
d8e5ed900d
Add default extension to files without one 2020-11-14 15:04:12 -05:00
iAmAsval
d0afb22bee improvements 2020-11-14 12:48:32 +01:00
iAmAsval
712602b767 enabled fortnite live thanks to @NotOfficer 2020-11-11 13:09:50 +01:00
iAmAsval
67cc87f8f8 disabled fortnite live + reset settings in message box 2020-11-10 20:20:13 +01:00
GMatrixGames
3c90458749
Add UIDisplayDescription to description detection 2020-11-09 10:28:17 -05:00
Valentin
c23d403e1e
Merge pull request #137 from MinshuG/master
.csv and OGG10025600-1-1-1-1-1
2020-11-09 02:28:04 +01:00
MountainFlash
fd03471ff1
corrected .csv 2020-11-08 22:20:03 +05:30
MountainFlash
c48b1b93d5 .csv and OGG10025600-1-1-1-1-1 2020-11-08 15:47:32 +05:30
GMatrixGames
da6c03d17d
Revert 293a6e4137 2020-11-05 16:07:43 -05:00
Valentin
91fffaeb4a
Update README.md 2020-11-05 21:28:19 +01:00
GMatrixGames
099d409390
Add FortEventCurrencyItemDefinitionRedir to image gen 2020-11-05 15:28:17 -05:00
iAmAsval
49c8a40bca Discord RPC pak count fix + challenges fix v1 2020-11-05 20:43:55 +01:00
GMatrixGames
b10dfcc2a6
For 1H pickaxes 2020-11-03 15:20:47 -05:00
XTigerHyperX
293a6e4137 updated mapping repo 2020-11-03 21:20:14 +01:00
GMatrixGames
eb8bf4fc8d
Fix some weapon assets
Fix item defs that have FortWeaponAdditionalData_AudioVisualizerData before the actual definition in the exports
2020-11-01 17:55:13 -05:00
Maiky M
c8a6ef2731
Generate appveyor with .NET too 2020-10-31 12:35:15 -06:00
MaikyM
aefaf97f8e Added AppVeyor 2020-10-31 11:52:11 -06:00
iAmAsval
120d07fbdf added Io CurveTable support 2020-10-31 17:21:50 +01:00
MountainFlash
af5241b82d
Fixed Folder Extract and Export options (#133) 2020-10-31 10:08:32 -06:00
MaikyM
ac6a6e024c debug message 2020-10-31 08:23:18 -06:00
iAmAsval
5e6b949ddc added EnumType 2020-10-31 13:32:36 +01:00
MaikyM
1073daeede Fixed some errors 2020-10-30 23:58:11 -06:00
iAmAsval
4bf19551f4 fixed index error on some asset onClick function 2020-10-30 12:23:40 +01:00
iAmAsval
319a3d52b3 removed empty paks + fixed backups and loading modes + improved speed 2020-10-30 11:51:13 +01:00
Fabian
220ed024cd added some missing structs 2020-10-28 21:57:37 +01:00
Fabian
1b2f6c636f Uhm ok so I guess that's the big commit
- Added utoc/ucas (io store) loading support
- Added new unversioned asset parsing support ONLY for asset we have mappings yet
- Integrated that new parsing as good as possible into fmodel's old structure (which certainly wasn't prepared for such a change lol)
- ONLY for new package format disabled caching of files in debug mode (was annoying)
- Type and enum mappings while by default be loaded from a github repo BUT in debug mode it will first attempt to load them from local files (relative to the exe, also for debugging conveniance) but fallback to the ones from the repo
- F12 hotkey for reloading current type mappings (Mainly made for debug but also works in release)
2020-10-28 21:41:53 +01:00
Fabian
3835717dfc io store reading 2020-10-21 22:59:33 +02:00
GMatrixGames
8dd348638d
Merge remote-tracking branch 'origin/master' into master 2020-10-17 19:09:26 -04:00
GMatrixGames
5741372139
Add a working background to battle breakers item definitions
I tried using the texture from the files, but it kept being null
2020-10-17 19:08:59 -04:00
Valentin
a9b8928404 fixed a few things 2020-10-17 23:30:26 +02:00
GMatrixGames
fef39af079
WExpGenericAccountItemDefinition support 2020-10-17 17:05:43 -04:00
GMatrixGames
7b71a43eab
The Cycle added a language 🤦 2020-10-16 09:36:56 -04:00
GMatrixGames
cc6a3dce45
The Cycle is no longer early access despite the install folder saying so 2020-10-16 09:31:39 -04:00
GMatrixGames
3f3a9f8551
Merge remote-tracking branch 'origin/master' into master
# Conflicts:
#	FModel/MainWindow.xaml.cs
2020-10-16 09:27:51 -04:00
GMatrixGames
98c8894724
Fix commit 7e75fa0309 2020-10-16 09:27:32 -04:00
XTigerHyperX
7e75fa0309 Small gamechange issue fix 2020-10-16 12:16:21 +01:00
Fabian
30a50549c8 added support for BC6H, BC7, ETC1, ETC2_RGB and ETC2_RGBA textures 2020-10-15 18:42:29 +02:00
iAmAsval
c8a5ba0889 fixed languages index so users don't get their language change 2020-10-13 23:24:54 +02:00
iAmAsval
c8422a005e Added support for Valorant new sound structure 2020-10-13 23:04:18 +02:00
XTigerHyperX
dab19587eb uhh more translations ig 2020-10-13 00:45:51 +01:00
GMatrixGames
182c05bc8e
Finished implementing localized game selection names
- Example: https://up.gmatrixgames.ga/u4VqZl.png
2020-10-12 18:48:58 -04:00
GMatrixGames
231ffeb0f7
Add more game languages and make game names translatable
Game languages added:
- Spanish (Mexico)
- British English
- Portuguese (Portugal)
- Swedish
- Thai
- Indonesian
- Vietnamese (Vietnam)
2020-10-12 18:42:15 -04:00
iAmAsval
db1f5a225f added Reset Settings and fixed a few things 2020-10-12 20:45:41 +02:00
Valentin
e0171afec8
Merge pull request #125 from Mang00e/master
Portuguese
2020-10-12 20:35:28 +02:00
Mang0e
13c766f31c Portuguese 2020-10-12 11:17:07 -07:00
Mang0e
645dd79342 Portuguese Support
Added support for a portuguese language option.
2020-10-12 11:13:50 -07:00
iAmAsval
e754e14a03 reverted unsafe readfstring 2020-10-11 23:48:41 +02:00
iAmAsval
aeced01cce just saving before i break my VS 2020-10-11 22:46:50 +02:00
Ricky Owens
1ed59c448f
[Spellbreak] Quest Icon Support 2020-10-11 16:43:49 -04:00
Ricky Owens
16347b0982
Merge pull request #124 from iFireMonkey/master
Spellbreak Icon Fixes + Additions
2020-10-11 15:04:36 -04:00
Ricky Owens
9a4919f9de
Changes GCosmeticCard sizing [Spellbreak]
Also added a note regarding the stretch done to the Spellbreak Cards
2020-10-11 14:58:42 -04:00
Ricky Owens
fb34ebd488
Spellbreak Icon Support & Some Fixes
Added GCharacterPerk Support, Fixed GCosmeticCard to show Names & Descriptions, however it's not perfect yet.
2020-10-11 14:57:45 -04:00
XTigerHyperX
cc0b094ad2 i stg if smth goes wrong 2020-10-11 18:34:10 +01:00
Valentin
68d4636907
Merge pull request #123 from MinshuG/master
more icon generation support
2020-10-11 18:08:00 +02:00
minshug
7115e54458 Merge branch 'master' of https://github.com/iAmAsval/FModel into master 2020-10-11 21:23:38 +05:30
minshug
a9839efe09 more spellbreak icon generation support 2020-10-11 21:20:56 +05:30
minshug
279482425c more icon generation support (Fortnite) 2020-10-11 20:24:03 +05:30
XTigerHyperX
cf2e2e8f5b pain (temp fix) 2020-10-11 15:04:04 +01:00
XTigerHyperX
55edc0c9ab Revert "Improved and better Arabic Language support"
This reverts commit 4977fdffbd.
2020-10-11 12:26:42 +01:00
XTigerHyperX
4977fdffbd Improved and better Arabic Language support 2020-10-11 12:21:56 +01:00
XTigerHyperX
0e388ca8e5 Improved and better Arabic support 2020-10-11 12:19:46 +01:00
Officer
88cd1721c6
Added Valorant [LIVE] & some fixes (#122)
* some fixes

* added valorant live support
2020-10-10 22:06:38 -04:00
XTigerHyperX
45353c0c11 added new arabic words 2020-10-11 01:18:39 +01:00
Valentin
06becf043b
Merge pull request #121 from NotOfficer/master
Some (big) improvements
2020-10-10 22:56:44 +02:00
Not Officer
2e9534b7d1 some (big) improvements 2020-10-10 22:54:25 +02:00
iAmAsval
f4e8e34ce2 FModel 3.1.1.3 2020-10-10 22:06:50 +02:00
XTigerHyperX
b6a628f3b0
Merge pull request #119 from XTigerHyperX/master
smol description error fix
2020-10-10 21:00:15 +01:00
XTigerHyperX
18660b2428 smol description error fix 2020-10-10 20:55:15 +01:00
Valentin
54d2ccf090
Merge pull request #118 from NotOfficer/master
Playlist images support
2020-10-10 20:36:29 +02:00
Not Officer
f5fa499905 added playlist images support 2020-10-10 20:32:47 +02:00
iAmAsval
b18b38255b added Arabic fix everywhere 2020-10-10 14:49:25 +02:00
Valentin
1b93086307
close #102
Arabic icon drawing support
2020-10-10 13:52:36 +02:00
Not Officer
62d989c961 added arabic icon drawing support 2020-10-10 13:46:43 +02:00
iAmAsval
db65a03c8d Hide The Pain Harold 2020-10-10 00:34:35 +02:00
Ricky Owens
7678e06ec8
Fixed up Contributors Table
Looked pretty wonky on Windows 10 so I made a second row, allowing Profile Pictures to not get as squished.
2020-10-09 18:17:23 -04:00
iAmAsval
b7c48c12c4 added battle pass icon generation 2020-10-09 22:58:04 +02:00
GMatrixGames
23413616f0
Add FortCodeTokenItemDefinition
Although this remains unused currently, they may use it again in the future
2020-10-08 10:45:30 -04:00
iAmAsval
1c1a3edc9c added DrawStats + Flat Spellbreak icons, fixed DBD textures + challenges 2020-10-07 23:53:00 +02:00
GMatrixGames
78d49f9d06
Add missing contracts link in readme 2020-10-07 11:29:08 -04:00
GMatrixGames
52fba4e50c
Disable all UWP functions due to crashing issue 2020-10-03 14:32:38 -04:00
Valentin
1f464f6393
Merge pull request #111 from realhypex/master
added LTMs description & subname
2020-10-02 10:36:49 +02:00
HYPEX
039681e897
added LTMs description & subname 2020-10-02 07:03:30 +01:00
iAmAsval
7925834202 fixed the wave 2020-10-02 03:59:04 +02:00
iAmAsval
7b40730823 added access_item in challenges 2020-10-02 03:45:41 +02:00
Valentin
3406fdc37c
Merge pull request #110 from iAmAsval/better-uwp
Proper UWP game support
2020-10-02 03:44:05 +02:00
GMatrixGames
afa9b7a29d
Unsaved solution? 2020-10-01 21:21:30 -04:00
GMatrixGames
3f6d634c00
Proper UWP support from LukeFZ's fork 2020-10-01 21:19:38 -04:00
GMatrixGames
5ce188dc27
Following Ricky's update, some other icon gen IDs 2020-10-01 20:59:25 -04:00
Ricky Owens
b208624a5a
Added More Icon Generation Support
Icon Generation Support added: Creative Plot, Homebase Banner, Venture Modifier, & Playlists
2020-10-01 19:43:57 -04:00
iAmAsval
4ad0e9f039 filtering what consumable does player damage before generating its stats 2020-09-29 12:43:23 +02:00
iAmAsval
0d2c489360 yes 2020-09-26 19:58:51 +02:00
iAmAsval
5aea683c8f Added @NotOfficer fork locked to latest manifest 2020-09-26 19:46:24 +02:00
iAmAsval
643a02d879 added basic Spellbreak icon creation 2020-09-20 21:13:03 +02:00
Valentin
e591cb0818
Merge pull request #109 from iAmAsval/imgbot
[ImgBot] Optimize images
2020-09-19 17:59:52 +02:00
ImgBotApp
177747f845
[ImgBot] Optimize images
*Total -- 479.64kb -> 357.81kb (25.4%)

/FModel/Resources/ColorPickerTwo.png -- 6.73kb -> 4.27kb (36.56%)
/FModel/Resources/EIconDesign_NoText.png -- 31.61kb -> 20.45kb (35.3%)
/FModel/Resources/EIconDesign_Mini.png -- 36.18kb -> 24.14kb (33.28%)
/FModel/Resources/EIconDesign_Default.png -- 38.77kb -> 26.02kb (32.88%)
/FModel/Resources/fmodel.png -- 277.39kb -> 207.92kb (25.04%)
/FModel/Resources/alpha-a-box.png -- 0.62kb -> 0.47kb (23.85%)
/FModel/Resources/content-copy.png -- 0.58kb -> 0.44kb (23.44%)
/FModel/Resources/T-Icon-Pets-64.png -- 1.95kb -> 1.50kb (22.91%)
/FModel/Resources/T_DamagePerBullet_Weapon_Stats.png -- 1.35kb -> 1.06kb (21.72%)
/FModel/Resources/gamepad-variant.png -- 0.83kb -> 0.65kb (21.64%)
/FModel/Resources/EIconDesign_Flat.png -- 21.95kb -> 17.32kb (21.1%)
/FModel/Resources/image-plus.png -- 0.74kb -> 0.59kb (20.37%)
/FModel/Resources/trello.png -- 0.66kb -> 0.52kb (20.21%)
/FModel/Resources/image-move.png -- 0.88kb -> 0.71kb (20.13%)
/FModel/Resources/image-remove.png -- 0.86kb -> 0.69kb (19.13%)
/FModel/Resources/cast-audio.png -- 1.19kb -> 0.96kb (19.11%)
/FModel/Resources/T_Placeholder_Challenge_Image.png -- 16.93kb -> 13.92kb (17.74%)
/FModel/Resources/sign-direction-remove.png -- 0.50kb -> 0.41kb (17.7%)
/FModel/Resources/T-Icon-Quests-64.png -- 1.56kb -> 1.29kb (16.99%)
/FModel/Resources/open-in-new.png -- 0.64kb -> 0.54kb (16.84%)
/FModel/Resources/sign-direction-plus.png -- 0.52kb -> 0.43kb (16.7%)
/FModel/Resources/sign-direction.png -- 0.51kb -> 0.42kb (16.22%)
/FModel/Resources/discord.png -- 0.68kb -> 0.58kb (15.37%)
/FModel/Resources/delete-forever.png -- 0.66kb -> 0.55kb (15.35%)
/FModel/Resources/settings.png -- 0.64kb -> 0.55kb (14.92%)
/FModel/Resources/github-circle.png -- 0.72kb -> 0.62kb (14.73%)
/FModel/Resources/folder-download.png -- 0.48kb -> 0.41kb (14.29%)
/FModel/Resources/T_ReloadTime_Weapon_Stats.png -- 2.15kb -> 1.85kb (13.79%)
/FModel/Resources/volume-plus.png -- 0.49kb -> 0.43kb (13.04%)
/FModel/Resources/information.png -- 0.56kb -> 0.49kb (13.02%)
/FModel/Resources/paypal.png -- 0.66kb -> 0.57kb (12.89%)
/FModel/Resources/file-restore.png -- 0.57kb -> 0.50kb (12.24%)
/FModel/Resources/content-save.png -- 0.48kb -> 0.42kb (12.11%)
/FModel/Resources/folder-open.png -- 0.50kb -> 0.44kb (12.01%)
/FModel/Resources/playlist-plus.png -- 0.42kb -> 0.37kb (11.95%)
/FModel/Resources/magnify.png -- 0.55kb -> 0.48kb (11.92%)
/FModel/Resources/bug.png -- 0.52kb -> 0.46kb (11.91%)
/FModel/Resources/progress-download.png -- 0.72kb -> 0.64kb (11.67%)
/FModel/Resources/backup-restore.png -- 0.63kb -> 0.56kb (11.28%)
/FModel/Resources/share-all.png -- 0.71kb -> 0.63kb (11.2%)
/FModel/Resources/play.png -- 0.57kb -> 0.50kb (11.03%)
/FModel/Resources/power.png -- 0.59kb -> 0.52kb (10.95%)
/FModel/Resources/file-export.png -- 0.51kb -> 0.46kb (10.63%)
/FModel/Resources/zip-box.png -- 0.42kb -> 0.38kb (10.6%)
/FModel/Resources/volume-minus.png -- 0.48kb -> 0.43kb (10.59%)
/FModel/Resources/image-filter-black-white.png -- 0.48kb -> 0.43kb (9.8%)
/FModel/Resources/file-multiple.png -- 0.45kb -> 0.41kb (9.76%)
/FModel/Resources/view-dashboard.png -- 0.39kb -> 0.35kb (9.6%)
/FModel/Resources/volume-mute.png -- 0.60kb -> 0.55kb (9.09%)
/FModel/Resources/refresh.png -- 0.57kb -> 0.52kb (8.95%)
/FModel/Resources/pencil.png -- 0.46kb -> 0.42kb (8.86%)
/FModel/Resources/pause.png -- 0.39kb -> 0.36kb (8.04%)
/FModel/Resources/EIconDesign_NoBackground.png -- 8.44kb -> 7.77kb (7.92%)
/FModel/Resources/T_Placeholder_Item_Image.png -- 8.44kb -> 7.77kb (7.92%)
/FModel/Resources/stop.png -- 0.38kb -> 0.35kb (7.42%)
/FModel/Resources/share.png -- 0.49kb -> 0.46kb (6.96%)
/FModel/Resources/key.png -- 0.49kb -> 0.45kb (6.83%)
/FModel/Resources/file.png -- 0.42kb -> 0.40kb (5.14%)

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>
2020-09-19 15:59:10 +00:00
Fabian
f2d990e2ff automatic downloading of oodle dll if needed 2020-09-13 21:32:48 +02:00
iAmAsval
aeded745cf added FortShopOfferDisplayData icon support (sort of) 2020-09-11 00:16:52 +02:00
776 changed files with 32574 additions and 43879 deletions

164
.editorconfig Normal file
View File

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

1
.github/FUNDING.yml vendored Normal file
View File

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

45
.github/ISSUE_TEMPLATE/bug.yml vendored Normal file
View File

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

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

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

22
.github/ISSUE_TEMPLATE/feature.yml vendored Normal file
View File

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

48
.github/workflows/main.yml vendored Normal file
View File

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

65
.github/workflows/qa.yml vendored Normal file
View File

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

55
.gitignore vendored
View File

@ -4,6 +4,7 @@
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
@ -12,6 +13,9 @@
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
@ -19,13 +23,17 @@
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
.idea/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
@ -36,9 +44,10 @@ Generated\ Files/
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
@ -52,7 +61,6 @@ BenchmarkDotNet.Artifacts/
project.lock.json
project.fragment.lock.json
artifacts/
**/Properties/launchSettings.json
# StyleCop
StyleCopReport.xml
@ -60,7 +68,7 @@ StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_i.h
*_h.h
*.ilk
*.meta
*.obj
@ -77,6 +85,7 @@ StyleCopReport.xml
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
@ -119,9 +128,6 @@ _ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
@ -179,6 +185,8 @@ PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
@ -203,12 +211,14 @@ BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
!?*.[Cc]ache/
# Others
ClientBin/
@ -221,7 +231,7 @@ ClientBin/
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
@ -236,7 +246,7 @@ Generated_Code/
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
#Backup*/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
@ -252,6 +262,9 @@ ServiceFabricBackup/
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
@ -287,12 +300,8 @@ paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush
.cr/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
@ -317,7 +326,7 @@ __pycache__/
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
@ -326,5 +335,17 @@ ASALocalRun/
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "CUE4Parse"]
path = CUE4Parse
url = https://github.com/FabianFG/CUE4Parse

1
CUE4Parse Submodule

@ -0,0 +1 @@
Subproject commit 455b72e5e38bfe9476b5823bc642fc8ef488347f

View File

@ -1,25 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29806.167
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FModel", "FModel\FModel.csproj", "{A42F8737-D056-4FA5-BEB5-AA96E1639F8A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A42F8737-D056-4FA5-BEB5-AA96E1639F8A}.Debug|x64.ActiveCfg = Debug|x64
{A42F8737-D056-4FA5-BEB5-AA96E1639F8A}.Debug|x64.Build.0 = Debug|x64
{A42F8737-D056-4FA5-BEB5-AA96E1639F8A}.Release|x64.ActiveCfg = Release|x64
{A42F8737-D056-4FA5-BEB5-AA96E1639F8A}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FE6EA91D-BBB8-4FBC-875C-25AD92EDB519}
EndGlobalSection
EndGlobal

View File

@ -1,14 +1,19 @@
<Application x:Class="FModel.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
StartupUri="MainWindow.xaml" ShutdownMode="OnMainWindowClose"
DispatcherUnhandledException="OnDispatcherUnhandledException">
Exit="AppExit" DispatcherUnhandledException="OnUnhandledException">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Theme/Style.xaml" />
<ResourceDictionary Source="pack://application:,,,/ToastNotifications.Messages;component/Themes/Default.xaml" />
<ResourceDictionary Source="pack://application:,,,/AdonisUI;component/ColorSchemes/Dark.xaml"/>
<ResourceDictionary Source="pack://application:,,,/AdonisUI.ClassicTheme;component/Resources.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Color x:Key="{x:Static adonisUi:Colors.AccentColor}">#206BD4</Color>
<Color x:Key="{x:Static adonisUi:Colors.AlertColor}">#D49220</Color>
<Color x:Key="{x:Static adonisUi:Colors.ErrorColor}">#C22B2B</Color>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@ -1,80 +1,178 @@
using FModel.Logger;
using FModel.Utils;
using FModel.ViewModels.ComboBox;
using FModel.ViewModels.StatusBar;
using FModel.Windows.DarkMessageBox;
using AdonisUI.Controls;
using Microsoft.Win32;
using Serilog;
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
using FModel.Framework;
using FModel.Services;
using FModel.Settings;
using Newtonsoft.Json;
using Serilog.Sinks.SystemConsole.Themes;
using MessageBox = AdonisUI.Controls.MessageBox;
using MessageBoxImage = AdonisUI.Controls.MessageBoxImage;
using MessageBoxResult = AdonisUI.Controls.MessageBoxResult;
namespace FModel
namespace FModel;
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
[DllImport("kernel32.dll")]
private static extern bool AttachConsole(int dwProcessId);
[DllImport("winbrand.dll", CharSet = CharSet.Unicode)]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
static extern string BrandingFormatString(string format);
protected override void OnStartup(StartupEventArgs e)
{
internal static Stopwatch StartTimer { get; private set; }
static bool framerateSet = false;
#if DEBUG
AttachConsole(-1);
#endif
base.OnStartup(e);
protected override void OnStartup(StartupEventArgs e)
try
{
StartTimer = Stopwatch.StartNew();
DebugHelper.Init(LogsFilePath); // get old settings too
Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(ProgramLang.GetProgramLang());
DebugHelper.WriteLine("{0} {1}", "[FModel]", "");
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[Version]", Assembly.GetExecutingAssembly().GetName().Version.ToString());
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[Build]", Globals.Build);
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[OS]", Logger.Logger.GetOperatingSystemProductName(true));
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[Runtime]", RuntimeInformation.FrameworkDescription);
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[Culture]", Thread.CurrentThread.CurrentUICulture);
StatusBarVm.statusBarViewModel.Set(FModel.Properties.Resources.Initializing, FModel.Properties.Resources.Loading);
base.OnStartup(e);
UserSettings.Default = JsonConvert.DeserializeObject<UserSettings>(
File.ReadAllText(UserSettings.FilePath), JsonNetSerializer.SerializerSettings);
}
catch
{
UserSettings.Default = new UserSettings();
}
public static string LogsFilePath
var createMe = false;
if (!Directory.Exists(UserSettings.Default.OutputDirectory))
{
get
var currentDir = Directory.GetCurrentDirectory();
var dirInfo = new DirectoryInfo(currentDir);
if (dirInfo.Attributes.HasFlag(FileAttributes.Archive))
throw new Exception("FModel cannot be run from an archive file. Please extract it and try again.");
if (dirInfo.Attributes.HasFlag(FileAttributes.ReadOnly))
throw new Exception("FModel cannot be run from a read-only directory. Please move it to a writable location.");
UserSettings.Default.OutputDirectory = Path.Combine(currentDir, "Output");
}
if (!Directory.Exists(UserSettings.Default.RawDataDirectory))
{
createMe = true;
UserSettings.Default.RawDataDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
}
if (!Directory.Exists(UserSettings.Default.PropertiesDirectory))
{
createMe = true;
UserSettings.Default.PropertiesDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
}
if (!Directory.Exists(UserSettings.Default.TextureDirectory))
{
createMe = true;
UserSettings.Default.TextureDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
}
if (!Directory.Exists(UserSettings.Default.AudioDirectory))
{
createMe = true;
UserSettings.Default.AudioDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
}
if (!Directory.Exists(UserSettings.Default.ModelDirectory))
{
createMe = true;
UserSettings.Default.ModelDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
}
Directory.CreateDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FModel"));
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Backups"));
if (createMe) Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Exports"));
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Logs"));
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data"));
#if DEBUG
Log.Logger = new LoggerConfiguration().WriteTo.Console(theme: AnsiConsoleTheme.Literate).WriteTo.File(
Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Debug-Log-{DateTime.Now:yyyy-MM-dd}.txt"),
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [FModel] [{Level:u3}] {Message:lj}{NewLine}{Exception}").CreateLogger();
#else
Log.Logger = new LoggerConfiguration().WriteTo.Console(theme: AnsiConsoleTheme.Literate).WriteTo.File(
Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Log-{DateTime.Now:yyyy-MM-dd}.txt"),
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [FModel] [{Level:u3}] {Message:lj}{NewLine}{Exception}").CreateLogger();
#endif
Log.Information("Version {Version} ({CommitId})", Constants.APP_VERSION, Constants.APP_COMMIT_ID);
Log.Information("{OS}", GetOperatingSystemProductName());
Log.Information("{RuntimeVer}", RuntimeInformation.FrameworkDescription);
Log.Information("Culture {SysLang}", CultureInfo.CurrentCulture);
}
private void AppExit(object sender, ExitEventArgs e)
{
Log.Information("");
Log.CloseAndFlush();
UserSettings.Save();
Environment.Exit(0);
}
private void OnUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
Log.Error("{Exception}", e.Exception);
var messageBox = new MessageBoxModel
{
Text = $"An unhandled exception occurred: {e.Exception.Message}",
Caption = "Fatal Error",
Icon = MessageBoxImage.Error,
Buttons = new[]
{
string filename = string.Format("FModel-Log-{0:yyyy-MM-dd}.txt", DateTime.Now);
MessageBoxButtons.Custom("Reset Settings", EErrorKind.ResetSettings),
MessageBoxButtons.Custom("Restart", EErrorKind.Restart),
MessageBoxButtons.Custom("OK", EErrorKind.Ignore)
},
IsSoundEnabled = false
};
// Copy user settings from previous application version if necessary
if (FModel.Properties.Settings.Default.UpdateSettings)
FModel.Properties.Settings.Default.Upgrade();
Folders.LoadFolders();
return Path.Combine(FModel.Properties.Settings.Default.OutputPath + "\\Logs", filename);
}
}
private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
MessageBox.Show(messageBox);
if (messageBox.Result == MessageBoxResult.Custom && (EErrorKind) messageBox.ButtonPressed.Id != EErrorKind.Ignore)
{
string errorMessage = string.Format(FModel.Properties.Resources.UnhandledExceptionOccured, e.Exception.Message);
DebugHelper.WriteException(e.Exception, "thrown in App.xaml.cs by OnDispatcherUnhandledException");
DarkMessageBoxHelper.Show(errorMessage, FModel.Properties.Resources.Error, MessageBoxButton.OK, MessageBoxImage.Error);
e.Handled = true;
if ((EErrorKind) messageBox.ButtonPressed.Id == EErrorKind.ResetSettings)
UserSettings.Delete();
ApplicationService.ApplicationView.Restart();
}
internal static void SetFramerate()
e.Handled = true;
}
private string GetOperatingSystemProductName()
{
var productName = string.Empty;
try
{
if (!framerateSet)
{
System.Windows.Media.Animation.Timeline.DesiredFrameRateProperty.OverrideMetadata(
typeof(System.Windows.Media.Animation.Timeline),
new FrameworkPropertyMetadata { DefaultValue = 10 });
framerateSet = true;
}
productName = BrandingFormatString("%WINDOWS_LONG%");
}
catch
{
// ignored
}
if (string.IsNullOrEmpty(productName))
productName = Environment.OSVersion.VersionString;
return $"{productName} ({(Environment.Is64BitOperatingSystem ? "64" : "32")}-bit)";
}
public static string GetRegistryValue(string path, string name = null, RegistryHive root = RegistryHive.CurrentUser)
{
using var rk = RegistryKey.OpenBaseKey(root, RegistryView.Default).OpenSubKey(path);
if (rk != null)
return rk.GetValue(name, null) as string;
return string.Empty;
}
}

57
FModel/Constants.cs Normal file
View File

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

View File

@ -0,0 +1,42 @@
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Creator.Bases.FN;
using SkiaSharp;
namespace FModel.Creator.Bases.BB;
public class BaseBreakersIcon : BaseIcon
{
public BaseBreakersIcon(UObject uObject, EIconStyle style) : base(uObject, style)
{
SeriesBackground = Utils.GetBitmap("WorldExplorers/Content/UMG/Materials/t_TextGradient.t_TextGradient");
Background = new[] { SKColor.Parse("D0D0D0"), SKColor.Parse("636363") };
Border = new[] { SKColor.Parse("D0D0D0"), SKColor.Parse("FFFFFF") };
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FSoftObjectPath iconTextureAssetData, "IconTextureAssetData", "UnlockPortraitGuideImage"))
Preview = Utils.GetBitmap(iconTextureAssetData);
if (Object.TryGetValue(out FText displayName, "DisplayName", "RegionDisplayName", "ZoneName"))
DisplayName = displayName.Text;
if (Object.TryGetValue(out FText description, "Description", "RegionShortName", "ZoneDescription"))
Description = description.Text;
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
DrawBackground(c);
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
DrawDescription(c);
return new[] { ret };
}
}

View File

@ -1,102 +0,0 @@
using FModel.Creator.Bundles;
using FModel.Creator.Texts;
using PakReader.Pak;
using PakReader.Parsers.Class;
using PakReader.Parsers.PropertyTagData;
using System.Collections.Generic;
namespace FModel.Creator.Bases
{
public class BaseBundle
{
public Header DisplayStyle;
public string DisplayName;
public string FolderName;
public string Watermark;
public int Width = 1024;
public int HeaderHeight = 261; // height is the header basically
public int AdditionalSize = 50; // must be increased depending on the number of quests to draw
public bool IsDisplayNameShifted;
public List<Quest> Quests;
public List<CompletionReward> CompletionRewards;
public BaseBundle()
{
DisplayStyle = new Header();
DisplayName = "";
FolderName = "";
Watermark = Properties.Settings.Default.ChallengeBannerWatermark;
Quests = new List<Quest>();
CompletionRewards = new List<CompletionReward>();
}
/// <summary>
/// used for the settings
/// </summary>
public BaseBundle(string watermark) : this()
{
DisplayName = "{DisplayName}";
FolderName = "{FolderName}";
Watermark = watermark;
Quests.Add(new Quest { Description = "", Count = 999, Reward = null });
AdditionalSize += 89;
}
public BaseBundle(IUExport export, string assetFolder) : this()
{
if (export.GetExport<StructProperty>("DisplayStyle") is StructProperty displayStyle)
DisplayStyle = new Header(displayStyle, assetFolder);
if (export.GetExport<TextProperty>("DisplayName") is TextProperty displayName)
DisplayName = Text.GetTextPropertyBase(displayName);
if (export.GetExport<ArrayProperty>("CareerQuestBitShifts") is ArrayProperty careerQuestBitShifts)
{
foreach (SoftObjectProperty questPath in careerQuestBitShifts.Value)
{
PakPackage p = Utils.GetPropertyPakPackage(questPath.Value.AssetPathName.String);
if (p.HasExport() && !p.Equals(default))
{
var obj = p.GetExport<UObject>();
if (obj != null)
Quests.Add(new Quest(obj));
}
}
}
if (export.GetExport<ArrayProperty>("BundleCompletionRewards") is ArrayProperty bundleCompletionRewards)
{
foreach (StructProperty completionReward in bundleCompletionRewards.Value)
{
if (completionReward.Value is UObject reward &&
reward.TryGetValue("CompletionCount", out var c) && c is IntProperty completionCount &&
reward.TryGetValue("Rewards", out var r) && r is ArrayProperty rewards)
{
foreach (StructProperty rew in rewards.Value)
{
if (rew.Value is UObject re &&
re.TryGetValue("Quantity", out var q) && q is IntProperty quantity &&
re.TryGetValue("TemplateId", out var t) && t is StrProperty templateId &&
re.TryGetValue("ItemDefinition", out var d) && d is SoftObjectProperty itemDefinition)
{
if (!itemDefinition.Value.AssetPathName.IsNone &&
!itemDefinition.Value.AssetPathName.String.StartsWith("/Game/Items/Tokens/") &&
!itemDefinition.Value.AssetPathName.String.StartsWith("/Game/Athena/Items/Quests"))
{
CompletionRewards.Add(new CompletionReward(completionCount, quantity, itemDefinition));
}
else if (!string.IsNullOrEmpty(templateId.Value))
{
CompletionRewards.Add(new CompletionReward(completionCount, quantity, templateId.Value));
}
}
}
}
}
}
FolderName = assetFolder;
AdditionalSize += 95 * Quests.Count;
if (CompletionRewards.Count > 0) AdditionalSize += 50 + (95 * CompletionRewards.Count);
}
}
}

View File

@ -1,130 +0,0 @@
using FModel.Creator.Icons;
using FModel.Creator.Rarities;
using FModel.Creator.Stats;
using FModel.Creator.Texts;
using FModel.Utils;
using PakReader.Parsers.Class;
using PakReader.Parsers.PropertyTagData;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Windows;
namespace FModel.Creator.Bases
{
public class BaseIcon
{
public SKBitmap FallbackImage;
public SKBitmap IconImage;
public SKBitmap RarityBackgroundImage;
public SKBitmap[] UserFacingFlags;
public SKColor[] RarityBackgroundColors;
public SKColor[] RarityBorderColor;
public string DisplayName;
public string Description;
public string ShortDescription;
public string CosmeticSource;
public int Size = 512; // keep it 512 (or a multiple of 512) if you don't want blurry icons
public int AdditionalSize = 0; // must be increased if there are weapon stats, hero abilities or more to draw/show
public int Margin = 2;
public List<Statistic> Stats;
public BaseIcon()
{
FallbackImage = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_Placeholder_Item_Image.png")).Stream);
IconImage = FallbackImage;
RarityBackgroundImage = null;
UserFacingFlags = null;
RarityBackgroundColors = new SKColor[2] { SKColor.Parse("5EBC36"), SKColor.Parse("305C15") };
RarityBorderColor = new SKColor[2] { SKColor.Parse("74EF52"), SKColor.Parse("74EF52") };
DisplayName = "";
Description = "";
ShortDescription = "";
CosmeticSource = "";
Stats = new List<Statistic>();
}
public BaseIcon(IUExport export, string assetName, bool forceHR) : this()
{
if (export.GetExport<ObjectProperty>("Series") is ObjectProperty series)
Serie.GetRarity(this, series);
else if (Properties.Settings.Default.UseGameColors) // override default green
Rarity.GetInGameRarity(this, export.GetExport<EnumProperty>("Rarity")); // uncommon will be triggered by Rarity being null
else if (export.GetExport<EnumProperty>("Rarity") is EnumProperty rarity)
Rarity.GetHardCodedRarity(this, rarity);
if (export.GetExport<ObjectProperty>("HeroDefinition", "WeaponDefinition") is ObjectProperty itemDef)
LargeSmallImage.GetPreviewImage(this, itemDef, assetName, forceHR);
else if (export.GetExport<SoftObjectProperty>(forceHR ? "LargePreviewImage" : "SmallPreviewImage", forceHR ? "ItemDisplayAsset" : "SmallImage") is SoftObjectProperty previewImage)
LargeSmallImage.GetPreviewImage(this, previewImage);
}
/// <summary>
/// Order:
/// 1. Rarity
/// 2. Image
/// 3. Text
/// 1. DisplayName
/// 2. Description
/// 3. Misc
/// 4. GameplayTags
/// 1. order doesn't matter
/// 2. the importance here is to get the description before gameplay tags
/// </summary>
public BaseIcon(IUExport export, string exportType, ref string assetName) : this()
{
// rarity
if (export.GetExport<ObjectProperty>("Series") is ObjectProperty series)
Serie.GetRarity(this, series);
else if (Properties.Settings.Default.UseGameColors) // override default green
Rarity.GetInGameRarity(this, export.GetExport<EnumProperty>("Rarity")); // uncommon will be triggered by Rarity being null
else if (export.GetExport<EnumProperty>("Rarity") is EnumProperty rarity)
Rarity.GetHardCodedRarity(this, rarity);
// image
if (Properties.Settings.Default.UseItemShopIcon &&
DisplayAssetImage.GetDisplayAssetImage(this, export.GetExport<SoftObjectProperty>("DisplayAssetPath"), ref assetName))
{ } // ^^^^ will return false if image not found, if so, we try to get the normal icon
else if (export.GetExport<ObjectProperty>("HeroDefinition", "WeaponDefinition") is ObjectProperty itemDef)
LargeSmallImage.GetPreviewImage(this, itemDef, assetName);
else if (export.GetExport<SoftObjectProperty>("LargePreviewImage", "SmallPreviewImage", "ItemDisplayAsset") is SoftObjectProperty previewImage)
LargeSmallImage.GetPreviewImage(this, previewImage);
else if (export.GetExport<ObjectProperty>("SmallPreviewImage") is ObjectProperty smallPreviewImage)
this.IconImage = Utils.GetObjectTexture(smallPreviewImage);
else if (export.GetExport<StructProperty>("IconBrush") is StructProperty iconBrush) // abilities
LargeSmallImage.GetPreviewImage(this, iconBrush);
// text
if (export.GetExport<TextProperty>("DisplayName", "DefaultHeaderText", "UIDisplayName") is TextProperty displayName)
DisplayName = Text.GetTextPropertyBase(displayName);
if (export.GetExport<TextProperty>("Description", "DefaultBodyText") is TextProperty description)
Description = Text.GetTextPropertyBase(description);
else if (export.GetExport<ArrayProperty>("Description") is ArrayProperty arrayDescription) // abilities
Description = Text.GetTextPropertyBase(arrayDescription);
if (export.GetExport<StructProperty>("MaxStackSize") is StructProperty maxStackSize)
ShortDescription = Text.GetMaxStackSize(maxStackSize);
else if (export.GetExport<TextProperty>("ShortDescription") is TextProperty shortDescription)
ShortDescription = Text.GetTextPropertyBase(shortDescription);
else if (exportType.Equals("AthenaItemWrapDefinition")) // if no ShortDescription it's most likely a wrap
ShortDescription = Localizations.GetLocalization("Fort.Cosmetics", "ItemWrapShortDescription", "Wrap");
// gameplaytags
if (export.GetExport<StructProperty>("GameplayTags") is StructProperty gameplayTags)
GameplayTag.GetGameplayTags(this, gameplayTags, exportType);
else if (export.GetExport<ObjectProperty>("cosmetic_item") is ObjectProperty cosmeticItem) // variants
CosmeticSource = cosmeticItem.Value.Resource.ObjectName.String;
if (export.GetExport<SoftObjectProperty>("AmmoData") is SoftObjectProperty ammoData)
Statistics.GetAmmoData(this, ammoData);
if (export.GetExport<StructProperty>("WeaponStatHandle") is StructProperty weaponStatHandle)
Statistics.GetWeaponStats(this, weaponStatHandle);
if (export.GetExport<ObjectProperty>("HeroGameplayDefinition") is ObjectProperty heroGameplayDefinition)
Statistics.GetHeroStats(this, heroGameplayDefinition);
/* Please do not add Schematics support because it takes way too much memory */
/* Thank the STW Dev Team for using a 5,69Mb file to get... Oh nvm, they all left */
AdditionalSize = 48 * Stats.Count;
}
}
}

View File

@ -1,112 +0,0 @@
using FModel.Creator.Rarities;
using FModel.Creator.Texts;
using PakReader.Pak;
using PakReader.Parsers.Class;
using PakReader.Parsers.PropertyTagData;
using SkiaSharp;
namespace FModel.Creator.Bases
{
public class BaseItemAccess
{
private readonly SKPaint descriptionPaint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Typeface = Text.TypeFaces.DescriptionTypeface,
TextSize = 13,
Color = SKColors.White,
};
public BaseIcon Item;
public string SItem;
public SKBitmap Lock;
public SKBitmap Unlock;
public string DisplayName;
public string Description;
public string UnlockDescription;
public int Size = 512; // keep it 512 (or a multiple of 512) if you don't want blurry icons
public BaseItemAccess()
{
Item = new BaseIcon();
SItem = "";
Lock = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Locks/T-Icon-Lock-128").Resize(24, 24);
Unlock = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Locks/T-Icon-Unlocked-128").Resize(24, 24);
DisplayName = "";
Description = "";
UnlockDescription = "";
}
public BaseItemAccess(IUExport export) : this()
{
if (export.GetExport<ObjectProperty>("access_item") is ObjectProperty accessItem)
{
SItem = accessItem.Value.Resource.ObjectName.String;
PakPackage p = Utils.GetPropertyPakPackage(accessItem.Value.Resource.OuterIndex.Resource.ObjectName.String);
if (p.HasExport() && !p.Equals(default))
{
var d = p.GetExport<UObject>();
if (d != null)
{
Item = new BaseIcon(d, SItem + ".uasset", true);
}
}
}
if (export.GetExport<TextProperty>("DisplayName") is TextProperty displayName)
DisplayName = Text.GetTextPropertyBase(displayName);
if (export.GetExport<TextProperty>("Description") is TextProperty description)
Description = Text.GetTextPropertyBase(description);
if (export.GetExport<TextProperty>("UnlockDescription") is TextProperty unlockDescription)
UnlockDescription = Text.GetTextPropertyBase(unlockDescription);
}
public void Draw(SKCanvas c)
{
Rarity.DrawRarity(c, Item);
int size = 45;
int left = Size / 2;
SKPaint namePaint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Typeface = Text.TypeFaces.DisplayNameTypeface,
TextSize = size,
Color = SKColors.White,
TextAlign = SKTextAlign.Center,
};
while (namePaint.MeasureText(DisplayName) > (Size - (Item.Margin * 2)))
{
namePaint.TextSize = size -= 2;
}
c.DrawText(DisplayName, left, Item.Margin * 8 + size, namePaint);
int topBase = Item.Margin + size * 2;
c.DrawBitmap(Lock, new SKRect(50, topBase, 50 + Lock.Width, topBase + Lock.Height),
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
Helper.DrawMultilineText(c, UnlockDescription, Size, Item.Margin, ETextSide.Left,
new SKRect(70 + Lock.Width, topBase + 10, Size - 50, 256), descriptionPaint, out topBase);
c.DrawBitmap(Unlock, new SKRect(50, topBase, 50 + Unlock.Width, topBase + Unlock.Height),
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
Helper.DrawMultilineText(c, Description, Size, Item.Margin, ETextSide.Left,
new SKRect(70 + Unlock.Width, topBase + 10, Size - 50, 256), descriptionPaint, out topBase);
int h = Size - Item.Margin - topBase;
c.DrawBitmap(Item.IconImage ?? Item.FallbackImage, new SKRect(left - h / 2, topBase, left + h / 2, Size - Item.Margin),
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
c.DrawText(SItem, Size - (Item.Margin * 2.5f), Size - (Item.Margin * 2.5f), new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Typeface = Text.TypeFaces.DefaultTypeface,
TextSize = 15,
Color = SKColors.White,
TextAlign = SKTextAlign.Right,
});
}
}
}

View File

@ -1,96 +0,0 @@
using FModel.Creator.Texts;
using PakReader.Parsers.Class;
using PakReader.Parsers.PropertyTagData;
using SkiaSharp;
namespace FModel.Creator.Bases
{
public class BaseMapUIData
{
public SKBitmap Splash;
public SKBitmap VLogo;
public string DisplayName;
public string Description;
public string Coordinates;
public int Width = 1920;
public int Height = 1080;
public BaseMapUIData()
{
Splash = null;
VLogo = null;
DisplayName = "";
Description = "";
Coordinates = "";
}
public BaseMapUIData(IUExport export) : this()
{
if (export.GetExport<TextProperty>("DisplayName") is TextProperty displayName)
DisplayName = Text.GetTextPropertyBase(displayName) ?? "";
if (export.GetExport<TextProperty>("Description") is TextProperty description)
Description = Text.GetTextPropertyBase(description) ?? "";
if (export.GetExport<TextProperty>("Coordinates") is TextProperty coordinates)
Coordinates = Text.GetTextPropertyBase(coordinates) ?? "";
if (export.GetExport<ObjectProperty>("Splash") is ObjectProperty icon)
Splash = Utils.GetObjectTexture(icon);
VLogo = Utils.GetTexture("/Game/UI/Shared/Icons/Valorant_logo_cutout").Resize(48, 48);
if (Splash != null)
{
Width = Splash.Width;
Height = Splash.Height;
}
}
public void Draw(SKCanvas c)
{
int paddingLR = 80;
int paddingTB = 35;
int nameSize = 200;
int descriptionSize = 30;
using var namePaint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Typeface = Text.TypeFaces.DisplayNameTypeface,
TextSize = nameSize,
TextAlign = SKTextAlign.Left,
Color = SKColor.Parse("FFFBFA")
};
while (namePaint.MeasureText(DisplayName) > Width - (paddingLR * 2))
{
namePaint.TextSize = nameSize -= 2;
}
using var descriptionPaint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Typeface = Text.TypeFaces.DescriptionTypeface,
TextSize = descriptionSize,
TextAlign = SKTextAlign.Left,
Color = SKColor.Parse("FFFBFA")
};
while (descriptionPaint.MeasureText(Description) > Width - (paddingLR * 2))
{
descriptionPaint.TextSize = descriptionSize -= 2;
}
c.DrawBitmap(Splash, new SKRect(0, 0, Width, Height), new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
c.DrawText(DisplayName.ToUpper(), paddingLR, paddingTB + namePaint.TextSize, namePaint);
c.DrawRect(new SKRect(paddingLR + 2.5f, paddingTB + 25 + namePaint.TextSize, paddingLR + 202.5f, paddingTB + 27.5f + namePaint.TextSize), new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true, Color = SKColor.Parse("5AFFFBFA") });
c.DrawText(Description, paddingLR + 2.5f, paddingTB + 40 + namePaint.TextSize + descriptionPaint.TextSize, descriptionPaint);
descriptionPaint.Typeface = Text.TypeFaces.BundleDefaultTypeface;
c.DrawText(Coordinates.ToUpper(), paddingLR, Height - paddingTB - descriptionPaint.TextSize, descriptionPaint);
if (VLogo != null)
{
c.DrawBitmap(VLogo, new SKRect(Width - VLogo.Width - paddingLR, paddingLR, Width - paddingLR, paddingLR + VLogo.Height), new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
c.DrawRect(new SKRect(Width - VLogo.Width - paddingLR, paddingLR + VLogo.Height + 5, Width - paddingLR, paddingLR + VLogo.Height + 7.5f), new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true, Color = SKColor.Parse("FFFBFA") });
}
}
}
}

View File

@ -1,93 +0,0 @@
using PakReader.Parsers.Class;
using PakReader.Parsers.Objects;
using PakReader.Parsers.PropertyTagData;
using SkiaSharp;
using System;
using System.Windows;
namespace FModel.Creator.Bases
{
public class BaseOffer
{
public SKBitmap FallbackImage;
public SKBitmap IconImage;
public SKColor[] RarityBackgroundColors;
public SKColor RarityBorderColor;
public int Size = 512;
public int Margin = 2;
public BaseOffer()
{
FallbackImage = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_Placeholder_Item_Image.png")).Stream);
IconImage = FallbackImage;
RarityBackgroundColors = new SKColor[2] { SKColor.Parse("4F4F69"), SKColor.Parse("4F4F69") };
RarityBorderColor = SKColor.Parse("9092AB");
}
public BaseOffer(IUExport export) : this()
{
if (export.GetExport<StructProperty>("DetailsImage", "TileImage") is StructProperty typeImage)
{
if (typeImage.Value is UObject t && t.TryGetValue("ResourceObject", out var v) && v is ObjectProperty resourceObject)
{
IconImage = Utils.GetObjectTexture(resourceObject);
}
}
if (export.GetExport<StructProperty>("Gradient") is StructProperty gradient)
{
if (gradient.Value is UObject g &&
g.TryGetValue("Start", out var s1) && s1 is StructProperty t1 && t1.Value is FLinearColor start &&
g.TryGetValue("Stop", out var s2) && s2 is StructProperty t2 && t2.Value is FLinearColor stop)
{
RarityBackgroundColors = new SKColor[2] { SKColor.Parse(start.Hex), SKColor.Parse(stop.Hex) };
}
}
if (export.GetExport<StructProperty>("Background") is StructProperty background)
{
if (background.Value is FLinearColor b)
{
RarityBorderColor = SKColor.Parse(b.Hex);
}
}
}
public void DrawBackground(SKCanvas c)
{
if (RarityBackgroundColors[0] == RarityBackgroundColors[1])
RarityBackgroundColors[0] = RarityBorderColor;
RarityBackgroundColors[0].ToHsl(out var _, out var _, out var l1);
RarityBackgroundColors[1].ToHsl(out var _, out var _, out var l2);
bool reverse = l1 > l2;
// border
c.DrawRect(new SKRect(0, 0, Size, Size),
new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Color = RarityBorderColor
});
c.DrawRect(new SKRect(Margin, Margin, Size - Margin, Size - Margin),
new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Shader = SKShader.CreateRadialGradient(
new SKPoint(Size / 2, Size / 2),
Size / 5 * 4,
new SKColor[2] { reverse ? RarityBackgroundColors[0] : RarityBackgroundColors[1], reverse ? RarityBackgroundColors[1] : RarityBackgroundColors[0] },
SKShaderTileMode.Clamp)
});
}
public void DrawImage(SKCanvas c)
{
c.DrawBitmap(IconImage ?? FallbackImage, new SKRect(Margin, Margin, Size - Margin, Size - Margin),
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
}
}
}

View File

@ -1,177 +0,0 @@
using FModel.Creator.Stats;
using FModel.Creator.Texts;
using PakReader.Parsers.Class;
using PakReader.Parsers.PropertyTagData;
using SkiaSharp;
using System.Collections.Generic;
namespace FModel.Creator.Bases
{
public class BaseUIData
{
private readonly SKPaint descriptionPaint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Typeface = Text.TypeFaces.DescriptionTypeface,
TextSize = 19.5f,
Color = SKColor.Parse("939498"),
};
public SKBitmap IconImage;
public string DisplayName;
public string Description;
public List<Statistic> Abilities;
public int Width = 768; // keep it 512 (or a multiple of 512) if you don't want blurry icons
public int AdditionalWidth = 0;
public int Height = 96;
public int Margin = 3;
public BaseUIData()
{
IconImage = null;
DisplayName = "";
Description = "";
Abilities = new List<Statistic>();
}
public BaseUIData(IUExport[] exports, int baseIndex) : this()
{
if (exports[baseIndex].GetExport<TextProperty>("DisplayName") is TextProperty displayName)
DisplayName = Text.GetTextPropertyBase(displayName) ?? "";
if (exports[baseIndex].GetExport<TextProperty>("Description") is TextProperty description)
{
Description = Text.GetTextPropertyBase(description) ?? "";
if (Description.Equals(DisplayName)) Description = string.Empty;
if (!string.IsNullOrEmpty(Description))
{
Height += (int)descriptionPaint.TextSize * Helper.SplitLines(Description, descriptionPaint, Width - Margin).Length;
Height += (int)descriptionPaint.TextSize;
}
}
if (exports[baseIndex].GetExport<ObjectProperty>("StoreFeaturedImage", "FullRender", "VerticalPromoImage", "LargeIcon", "DisplayIcon2", "DisplayIcon") is ObjectProperty icon)
{
SKBitmap raw = Utils.GetObjectTexture(icon);
if (raw != null)
{
float coef = (float)Width / (float)raw.Width;
int sizeX = (int)(raw.Width * coef);
int sizeY = (int)(raw.Height * coef);
Height += sizeY;
IconImage = raw.Resize(sizeX, sizeY);
}
}
if (exports[baseIndex].GetExport<MapProperty>("Abilities") is MapProperty abilities)
{
AdditionalWidth = 768;
foreach (var (_, value) in abilities.Value)
{
if (value is ObjectProperty o && o.Value.Resource == null && o.Value.Index > 0)
{
Statistic s = new Statistic();
if (exports[o.Value.Index - 1].GetExport<TextProperty>("DisplayName") is TextProperty aDisplayName)
s.DisplayName = Text.GetTextPropertyBase(aDisplayName) ?? "";
if (exports[o.Value.Index - 1].GetExport<TextProperty>("Description") is TextProperty aDescription)
{
s.Description = Text.GetTextPropertyBase(aDescription) ?? "";
if (!string.IsNullOrEmpty(Description))
{
s.Height += (int)descriptionPaint.TextSize * Helper.SplitLines(s.Description, descriptionPaint, Width - Margin).Length;
s.Height += (int)descriptionPaint.TextSize * 3;
}
}
if (exports[o.Value.Index - 1].GetExport<ObjectProperty>("DisplayIcon") is ObjectProperty displayIcon)
{
SKBitmap raw = Utils.GetObjectTexture(displayIcon);
if (raw != null) s.Icon = raw.Resize(128, 128);
}
Abilities.Add(s);
}
}
}
}
public void Draw(SKCanvas c)
{
DrawCenteredTitle(c, DisplayName, 67.5f, out var textSize);
Helper.DrawMultilineText(c, Description, Width, Margin, ETextSide.Center,
new SKRect(Margin, textSize + 56.25f, Width - Margin, Height - 37.5f), descriptionPaint, out var yPos);
if (IconImage != null)
c.DrawBitmap(IconImage, new SKRect(0, yPos, Width, Height),
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
int yaPos = 0;
foreach (Statistic ability in Abilities)
{
int xToAdd = ability.Icon != null ? ability.Icon.Width : 0;
textSize = 42.5f;
var namePaint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Typeface = Text.TypeFaces.DisplayNameTypeface,
TextSize = textSize,
Color = SKColors.White,
TextAlign = SKTextAlign.Left,
};
// resize if too long
while (namePaint.MeasureText(ability.DisplayName) > Width - 128)
{
namePaint.TextSize = textSize -= 2;
}
c.DrawText(ability.DisplayName, Width + Margin + xToAdd + 10, yaPos + Margin + textSize, namePaint);
Helper.DrawMultilineText(c, ability.Description, Width, Width + Margin + xToAdd + 10, ETextSide.Left,
new SKRect(Width + Margin + xToAdd + 10, textSize + yaPos + 27.5f, Width + AdditionalWidth - Margin, Height - 27.5f), descriptionPaint, out var _);
if (ability.Icon != null)
c.DrawBitmap(ability.Icon, new SKRect(Width + Margin, yaPos, Width + Margin + ability.Icon.Width, yaPos + ability.Icon.Height),
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
yaPos += ability.Height + 48 + (Margin * 2);
}
}
private void DrawCenteredTitle(SKCanvas c, string title, float textSize, out float outTextSize)
{
SKPaint namePaint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Typeface = Text.TypeFaces.DisplayNameTypeface,
TextSize = textSize,
Color = SKColors.White,
TextAlign = SKTextAlign.Center,
};
float textWidth = namePaint.MeasureText(title);
while (textWidth > Width) // resize if too long
{
namePaint.TextSize = textSize -= 2;
textWidth = namePaint.MeasureText(title);
}
outTextSize = textSize;
float x1 = (Width / 2 - (textWidth / 2)) - 20;
float x2 = (x1 + textWidth) + 40;
float y1 = Margin + 5;
float y2 = Margin + namePaint.TextSize + 10;
c.DrawLine(new SKPoint(30, y1 + 5 + (namePaint.TextSize / 2)), new SKPoint(x1 - 30, y1 + 5 + (namePaint.TextSize / 2)), new SKPaint { Color = SKColor.Parse("E2E8E6") });
c.DrawLine(new SKPoint(x2 + 30, y1 + 5 + (namePaint.TextSize / 2)), new SKPoint(Width - 30, y1 + 5 + (namePaint.TextSize / 2)), new SKPaint { Color = SKColor.Parse("E2E8E6") });
c.DrawLine(new SKPoint(x1, y1), new SKPoint(x2, y1), new SKPaint { Color = SKColor.Parse("E2E8E6") }); // top
c.DrawLine(new SKPoint(x1, y2 + 5), new SKPoint(x2, y2 + 5), new SKPaint { Color = SKColor.Parse("E2E8E6") }); // bottom
c.DrawLine(new SKPoint(x1, y1), new SKPoint(x1, y2 + 5), new SKPaint { Color = SKColor.Parse("E2E8E6") }); // left
c.DrawLine(new SKPoint(x2, y1), new SKPoint(x2, y2 + 5), new SKPaint { Color = SKColor.Parse("E2E8E6") }); // right
c.DrawRect(new SKRect(x1 + 5, y1 + 5, x2 - 5, y2), new SKPaint { Color = SKColor.Parse("949598") });
c.DrawText(title, Width / 2, Margin + namePaint.TextSize, namePaint);
}
}
}

View File

@ -1,169 +0,0 @@
using FModel.Creator.Texts;
using PakReader.Parsers.Class;
using PakReader.Parsers.Objects;
using PakReader.Parsers.PropertyTagData;
using SkiaSharp;
using System.Collections.Generic;
namespace FModel.Creator.Bases
{
public class Options
{
public string Option;
public SKColor Color = SKColor.Parse("55C5FC").WithAlpha(150);
}
public class BaseUserOption
{
private readonly SKPaint descriptionPaint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Typeface = Text.TypeFaces.DisplayNameTypeface,
TextSize = 25,
Color = SKColor.Parse("88DBFF"),
};
public string OptionDisplayName;
public string OptionDescription;
public List<Options> OptionValues = new List<Options>();
public int Width = 512;
public int Height = 128;
public int Margin = 32;
public BaseUserOption(IUExport export)
{
if (export.GetExport<TextProperty>("OptionDisplayName") is TextProperty optionDisplayName)
OptionDisplayName = Text.GetTextPropertyBase(optionDisplayName).ToUpperInvariant();
if (export.GetExport<TextProperty>("OptionDescription") is TextProperty optionDescription)
{
OptionDescription = Text.GetTextPropertyBase(optionDescription);
if (!string.IsNullOrEmpty(OptionDescription))
{
Height += (int)descriptionPaint.TextSize * Helper.SplitLines(OptionDescription, descriptionPaint, Width - Margin).Length;
Height += (int)descriptionPaint.TextSize;
}
}
if (export.GetExport<ArrayProperty>("OptionValues") is ArrayProperty optionValues)
{
OptionValues = new List<Options>(optionValues.Value.Length);
for (int i = 0; i < OptionValues.Capacity; i++)
{
if (optionValues.Value[i] is StructProperty s && s.Value is UObject option)
{
if (option.TryGetValue("DisplayName", out var v1) && v1 is TextProperty displayName)
{
var opt = new Options { Option = Text.GetTextPropertyBase(displayName).ToUpperInvariant() };
if (option.TryGetValue("Value", out var v) && v is StructProperty value && value.Value is FLinearColor color)
opt.Color = SKColor.Parse(color.Hex).WithAlpha(150);
OptionValues.Add(opt);
}
else if (option.TryGetValue("PrimaryAssetName", out var v2) && v2 is NameProperty primaryAssetName)
OptionValues.Add(new Options { Option = primaryAssetName.Value.String });
}
}
}
if (export.GetExport<TextProperty>("OptionOnText") is TextProperty optionOnText)
OptionValues.Add(new Options { Option = Text.GetTextPropertyBase(optionOnText).ToUpperInvariant() });
if (export.GetExport<TextProperty>("OptionOffText") is TextProperty optionOffText)
OptionValues.Add(new Options { Option = Text.GetTextPropertyBase(optionOffText).ToUpperInvariant() });
if (export.GetExport<IntProperty>("Min", "DefaultValue") is IntProperty iMin &&
export.GetExport<IntProperty>("Max") is IntProperty iMax)
{
int increment = iMin.Value;
if (export.GetExport<IntProperty>("IncrementValue") is IntProperty incrementValue)
increment = incrementValue.Value;
for (int i = iMin.Value; i <= iMax.Value; i += increment)
{
OptionValues.Add(new Options { Option = i.ToString() });
}
}
if (export.GetExport<FloatProperty>("Min") is FloatProperty fMin &&
export.GetExport<FloatProperty>("Max") is FloatProperty fMax)
{
float increment = fMin.Value;
if (export.GetExport<FloatProperty>("IncrementValue") is FloatProperty incrementValue)
increment = incrementValue.Value;
for (float i = fMin.Value; i <= fMax.Value; i += increment)
{
OptionValues.Add(new Options { Option = i.ToString() });
}
}
Height += Margin;
Height += 35 * OptionValues.Count;
}
public void Draw(SKCanvas c)
{
c.DrawRect(new SKRect(0, 0, Width, Height),
new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Shader = SKShader.CreateLinearGradient(
new SKPoint(Width / 2, Height),
new SKPoint(Width, Height / 4),
new SKColor[2] { SKColor.Parse("01369C"), SKColor.Parse("1273C8") },
SKShaderTileMode.Clamp)
});
int textSize = 45;
SKPaint namePaint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Typeface = Text.TypeFaces.DisplayNameTypeface,
TextSize = textSize,
Color = SKColors.White,
TextAlign = SKTextAlign.Left
};
// resize if too long
while (namePaint.MeasureText(OptionDisplayName) > (Width - (Margin * 2)))
{
namePaint.TextSize = textSize -= 2;
}
int y = Margin + textSize;
c.DrawText(OptionDisplayName, Margin, y, namePaint);
y += (int)descriptionPaint.TextSize + (Margin / 2);
// wrap if too long
Helper.DrawMultilineText(c, OptionDescription, Width, Margin, ETextSide.Left,
new SKRect(Margin, y, Width - Margin, 256), descriptionPaint, out int top);
int height = 30;
int space = 5;
foreach (Options option in OptionValues)
{
c.DrawRect(new SKRect(Margin, top, Width - Margin, top + height),
new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Color = option.Color
});
int ts = 20;
c.DrawText(option.Option, Margin + (space * 2), top + (ts * 1.1f),
new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Typeface = Text.TypeFaces.DisplayNameTypeface,
TextSize = ts,
Color = SKColor.Parse("EEFFFF"),
TextAlign = SKTextAlign.Left
});
top += height + space;
}
}
}
}

View File

@ -0,0 +1,139 @@
using System.Collections.Generic;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Extensions;
using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN;
public class BaseBundle : UCreator
{
private IList<BaseQuest> _quests;
private const int _headerHeight = 100;
public BaseBundle(UObject uObject, EIconStyle style) : base(uObject, style)
{
Width = 1024;
Height = _headerHeight;
Margin = 0;
}
public override void ParseForInfo()
{
_quests = new List<BaseQuest>();
if (Object.TryGetValue(out FText displayName, "DisplayName", "ItemName"))
DisplayName = displayName.Text.ToUpperInvariant();
if (Object.TryGetValue(out FStructFallback[] quests, "QuestInfos")) // prout :)
{
foreach (var quest in quests)
{
if (!quest.TryGetValue(out FSoftObjectPath questDefinition, "QuestDefinition")) continue;
BaseQuest q;
var path = questDefinition.AssetPathName.Text;
do
{
if (!Utils.TryLoadObject(path, out UObject uObject)) break;
q = new BaseQuest(uObject, Style);
q.ParseForInfo();
_quests.Add(q);
path = path.SubstringBeforeWithLast('/') + q.NextQuestName + "." + q.NextQuestName;
} while (!string.IsNullOrEmpty(q.NextQuestName));
}
}
if (Object.TryGetValue(out FStructFallback[] completionRewards, "BundleCompletionRewards"))
{
foreach (var completionReward in completionRewards)
{
if (!completionReward.TryGetValue(out int completionCount, "CompletionCount") ||
!completionReward.TryGetValue(out FStructFallback[] rewards, "Rewards")) continue;
foreach (var reward in rewards)
{
if (!reward.TryGetValue(out int quantity, "Quantity") ||
!reward.TryGetValue(out string templateId, "TemplateId") ||
!reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition")) continue;
if (!itemDefinition.AssetPathName.IsNone &&
!itemDefinition.AssetPathName.Text.Contains("/Items/Tokens/") &&
!itemDefinition.AssetPathName.Text.Contains("/Items/Quests"))
{
_quests.Add(new BaseQuest(completionCount, itemDefinition, Style));
}
else if (!string.IsNullOrWhiteSpace(templateId))
{
_quests.Add(new BaseQuest(completionCount, quantity, templateId, Style));
}
}
}
}
Height += 256 * _quests.Count;
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
DrawHeader(c);
DrawDisplayName(c);
DrawQuests(c);
return new[] { ret };
}
private readonly SKPaint _headerPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Typeface = Utils.Typefaces.Bundle, TextSize = 50,
TextAlign = SKTextAlign.Center, Color = SKColor.Parse("#262630")
};
private void DrawHeader(SKCanvas c)
{
c.DrawRect(new SKRect(0, 0, Width, _headerHeight), _headerPaint);
var background = _quests.Count > 0 ? _quests[0].Background : Background;
_headerPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4,
new[] { background[0].WithAlpha(50), background[1].WithAlpha(50) }, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(0, 0, Width, _headerHeight), _headerPaint);
_headerPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, _headerHeight), new SKPoint(Width / 2, 75),
new[] { SKColors.Black.WithAlpha(25), background[1].WithAlpha(0) }, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(0, 75, Width, _headerHeight), _headerPaint);
}
private new void DrawDisplayName(SKCanvas c)
{
if (string.IsNullOrEmpty(DisplayName)) return;
_headerPaint.Shader = null;
_headerPaint.Color = SKColors.White;
while (_headerPaint.MeasureText(DisplayName) > Width)
{
_headerPaint.TextSize -= 1;
}
var shaper = new CustomSKShaper(_headerPaint.Typeface);
c.DrawShapedText(shaper, DisplayName, Width / 2f, _headerHeight / 2f + _headerPaint.TextSize / 2 - 10, _headerPaint);
}
private void DrawQuests(SKCanvas c)
{
var y = _headerHeight;
foreach (var quest in _quests)
{
quest.DrawQuest(c, y);
y += quest.Height;
}
}
}

View File

@ -0,0 +1,278 @@
using System.Linq;
using CUE4Parse.GameTypes.FN.Enums;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.GameplayTags;
using CUE4Parse.UE4.Objects.UObject;
using CUE4Parse.UE4.Versions;
using FModel.Extensions;
using FModel.Framework;
using FModel.Services;
using FModel.Settings;
using FModel.ViewModels.ApiEndpoints.Models;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN;
public class BaseCommunity : BaseIcon
{
private readonly CommunityDesign _design;
private string _rarityName;
private string _source;
private string _season;
private bool _lowerDrawn;
public BaseCommunity(UObject uObject, EIconStyle style, string designName) : base(uObject, style)
{
Margin = 0;
_lowerDrawn = false;
_design = ApplicationService.ApiEndpointView.FModelApi.GetDesign(designName);
}
public override void ParseForInfo()
{
ParseForReward(UserSettings.Default.CosmeticDisplayAsset);
if (Object.TryGetValue(out FPackageIndex series, "Series"))
{
_rarityName = series.Name;
}
else if (Object.TryGetValue(out FInstancedStruct[] dataList, "DataList") &&
dataList.FirstOrDefault(d => d.NonConstStruct?.TryGetValue(out FPackageIndex _, "Series") == true) is { } dl)
{
_rarityName = dl.NonConstStruct?.Get<FPackageIndex>("Series").Name;
}
else if (Object.TryGetValue(out FStructFallback componentContainer, "ComponentContainer") &&
componentContainer.TryGetValue(out FPackageIndex[] components, "Components") &&
components.FirstOrDefault(c => c.Name.Contains("Series")) is { } seriesDef &&
seriesDef.TryLoad(out var seriesDefObj) && seriesDefObj is not null &&
seriesDefObj.TryGetValue(out series, "Series"))
{
_rarityName = series.Name;
}
else
{
_rarityName = Object.GetOrDefault("Rarity", EFortRarity.Uncommon).GetDescription();
}
if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags"))
CheckGameplayTags(gameplayTags);
if (Object.TryGetValue(out FPackageIndex cosmeticItem, "cosmetic_item"))
CosmeticSource = cosmeticItem.Name.ToUpper();
DisplayName = DisplayName.ToUpper();
Description = Description.ToUpper();
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
if (_design == null)
{
base.Draw(c);
}
else
{
DrawBackground(c);
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
DrawDescription(c);
if (_design.DrawSeason && _design.Fonts.TryGetValue("Season", out var font))
DrawToBottom(c, font, _season);
if (_design.DrawSource && _design.Fonts.TryGetValue("Source", out font))
DrawToBottom(c, font, _source);
DrawUserFacingFlags(c, _design.GameplayTags.DrawCustomOnly);
}
return new[] { ret };
}
private void CheckGameplayTags(FGameplayTagContainer gameplayTags)
{
if (_design == null) return;
if (_design.DrawSource)
{
if (gameplayTags.TryGetGameplayTag("Cosmetics.Source.", out var source))
_source = source.Text["Cosmetics.Source.".Length..].ToUpper();
else if (gameplayTags.TryGetGameplayTag("Athena.ItemAction.", out var action))
_source = action.Text["Athena.ItemAction.".Length..].ToUpper();
}
if (_design.DrawSet && gameplayTags.TryGetGameplayTag("Cosmetics.Set.", out var set))
Description += GetCosmeticSet(set.Text, _design.DrawSetShort);
if (_design.DrawSeason && gameplayTags.TryGetGameplayTag("Cosmetics.Filter.Season.", out var season))
_season = GetCosmeticSeason(season.Text, _design.DrawSeasonShort);
var triggers = _design.GameplayTags.DrawCustomOnly ? new[] { "Cosmetics.UserFacingFlags." } : new[] { "Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender." };
GetUserFacingFlags(gameplayTags.GetAllGameplayTags(triggers));
}
private string GetCosmeticSet(string setName, bool bShort)
{
return bShort ? setName["Cosmetics.Set.".Length..] : base.GetCosmeticSet(setName);
}
private string GetCosmeticSeason(string seasonNumber, bool bShort)
{
if (!bShort) return base.GetCosmeticSeason(seasonNumber);
var s = seasonNumber["Cosmetics.Filter.Season.".Length..];
(int chapterIdx, int seasonIdx) = GetInternalSID(int.Parse(s));
return $"C{chapterIdx} S{seasonIdx}";
}
private new void DrawBackground(SKCanvas c)
{
if (_design.Rarities.TryGetValue(_rarityName, out var rarity))
{
c.DrawBitmap(rarity.Background, 0, 0, ImagePaint);
c.DrawBitmap(rarity.Upper, 0, 0, ImagePaint);
}
else
{
base.DrawBackground(c);
}
}
private new void DrawTextBackground(SKCanvas c)
{
if (!_lowerDrawn && string.IsNullOrEmpty(DisplayName) && string.IsNullOrEmpty(Description)) return;
_lowerDrawn = true;
if (_design.Rarities.TryGetValue(_rarityName, out var rarity))
{
c.DrawBitmap(rarity.Lower, 0, 0, ImagePaint);
}
else
{
base.DrawTextBackground(c);
}
}
private new void DrawDisplayName(SKCanvas c)
{
if (string.IsNullOrEmpty(DisplayName)) return;
if (_design.Fonts.TryGetValue(nameof(DisplayName), out var font))
{
DisplayNamePaint.TextSize = font.FontSize;
DisplayNamePaint.TextScaleX = font.FontScale;
DisplayNamePaint.Color = font.FontColor;
DisplayNamePaint.TextSkewX = font.SkewValue;
DisplayNamePaint.TextAlign = font.Alignment;
if (font.ShadowValue > 0)
DisplayNamePaint.ImageFilter = SKImageFilter.CreateDropShadow(2, 2, 4, 4, new SKColor(0, 0, 0, font.ShadowValue));
if (font.Typeface.TryGetValue(UserSettings.Default.AssetLanguage, out var path) ||
font.Typeface.TryGetValue(ELanguage.English, out path))
DisplayNamePaint.Typeface = Utils.Typefaces.OnTheFly(path);
while (DisplayNamePaint.MeasureText(DisplayName) > Width - Margin * 2)
{
DisplayNamePaint.TextSize -= 1;
}
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
var x = font.Alignment switch
{
SKTextAlign.Center => Width / 2f,
_ => font.X
};
c.DrawShapedText(shaper, DisplayName, x, font.Y, DisplayNamePaint);
}
else
{
base.DrawDisplayName(c);
}
}
private new void DrawDescription(SKCanvas c)
{
if (string.IsNullOrEmpty(Description)) return;
if (_design.Fonts.TryGetValue(nameof(Description), out var font))
{
DescriptionPaint.TextSize = font.FontSize;
DescriptionPaint.TextScaleX = font.FontScale;
DescriptionPaint.Color = font.FontColor;
DescriptionPaint.TextSkewX = font.SkewValue;
DescriptionPaint.TextAlign = font.Alignment;
if (font.ShadowValue > 0)
DescriptionPaint.ImageFilter = SKImageFilter.CreateDropShadow(2, 2, 4, 4, new SKColor(0, 0, 0, font.ShadowValue));
if (font.Typeface.TryGetValue(UserSettings.Default.AssetLanguage, out var path) ||
font.Typeface.TryGetValue(ELanguage.English, out path))
DescriptionPaint.Typeface = Utils.Typefaces.OnTheFly(path);
while (DescriptionPaint.MeasureText(Description) > Width - Margin * 2)
{
DescriptionPaint.TextSize -= 1;
}
var shaper = new CustomSKShaper(DescriptionPaint.Typeface);
var x = font.Alignment switch
{
SKTextAlign.Center => Width / 2f,
_ => font.X
};
if (font.MaxLineCount < 2)
{
c.DrawShapedText(shaper, Description, x, font.Y, DescriptionPaint);
}
else
{
Utils.DrawMultilineText(c, Description, Width - Margin * 2, Margin, DescriptionPaint.TextAlign,
new SKRect(Margin, font.Y, Width - Margin, Height), DescriptionPaint, out _);
}
}
else
{
base.DrawDescription(c);
}
}
private void DrawToBottom(SKCanvas c, FontDesign font, string text)
{
if (string.IsNullOrEmpty(text)) return;
if (!_lowerDrawn)
{
_lowerDrawn = true;
DrawTextBackground(c);
}
DisplayNamePaint.TextSize = font.FontSize;
DisplayNamePaint.TextScaleX = font.FontScale;
DisplayNamePaint.Color = font.FontColor;
DisplayNamePaint.TextSkewX = font.SkewValue;
DisplayNamePaint.TextAlign = font.Alignment;
if (font.ShadowValue > 0)
DisplayNamePaint.ImageFilter = SKImageFilter.CreateDropShadow(2, 2, 4, 4, new SKColor(0, 0, 0, font.ShadowValue));
if (font.Typeface.TryGetValue(UserSettings.Default.AssetLanguage, out var path) ||
font.Typeface.TryGetValue(ELanguage.English, out path))
DisplayNamePaint.Typeface = Utils.Typefaces.OnTheFly(path);
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
var x = font.Alignment switch
{
SKTextAlign.Center => Width / 2f,
_ => font.X
};
c.DrawShapedText(shaper, text, x, font.Y, DisplayNamePaint);
}
private void DrawUserFacingFlags(SKCanvas c, bool customOnly)
{
if (UserFacingFlags == null || UserFacingFlags.Count < 1) return;
if (customOnly)
{
c.DrawBitmap(_design.GameplayTags.Custom, 0, 0, ImagePaint);
}
else
{
// add size to api
// draw
}
}
}

View File

@ -0,0 +1,325 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using CUE4Parse.GameTypes.FN.Enums;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Engine;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.GameplayTags;
using CUE4Parse.UE4.Objects.UObject;
using CUE4Parse_Conversion.Textures;
using FModel.Settings;
using SkiaSharp;
namespace FModel.Creator.Bases.FN;
public class BaseIcon : UCreator
{
public SKBitmap SeriesBackground { get; protected set; }
protected string ShortDescription { get; set; }
protected string CosmeticSource { get; set; }
protected Dictionary<string, SKBitmap> UserFacingFlags { get; set; }
public BaseIcon(UObject uObject, EIconStyle style) : base(uObject, style) { }
public void ParseForReward(bool isUsingDisplayAsset)
{
// rarity
if (Object.TryGetValue(out FPackageIndex series, "Series")) GetSeries(series);
else if (Object.TryGetValue(out FStructFallback componentContainer, "ComponentContainer")) GetSeries(componentContainer);
else GetRarity(Object.GetOrDefault("Rarity", EFortRarity.Uncommon)); // default is uncommon
if (Object.TryGetValue(out FInstancedStruct[] dataList, "DataList"))
{
GetSeries(dataList);
Preview = Utils.GetBitmap(dataList);
}
// preview
if (Preview is null)
{
if (isUsingDisplayAsset && Utils.TryGetDisplayAsset(Object, out var preview))
Preview = preview;
else if (Object.TryGetValue(out FPackageIndex itemDefinition, "HeroDefinition", "WeaponDefinition"))
Preview = Utils.GetBitmap(itemDefinition);
else if (Object.TryGetValue(out FSoftObjectPath largePreview, "LargePreviewImage", "EntryListIcon", "SmallPreviewImage", "BundleImage", "ItemDisplayAsset", "LargeIcon", "ToastIcon", "SmallIcon"))
Preview = Utils.GetBitmap(largePreview);
else if (Object.TryGetValue(out string s, "LargePreviewImage") && !string.IsNullOrEmpty(s))
Preview = Utils.GetBitmap(s);
else if (Object.TryGetValue(out FPackageIndex otherPreview, "SmallPreviewImage", "ToastIcon", "access_item"))
Preview = Utils.GetBitmap(otherPreview);
else if (Object.TryGetValue(out UMaterialInstanceConstant materialInstancePreview, "EventCalloutImage"))
Preview = Utils.GetBitmap(materialInstancePreview);
else if (Object.TryGetValue(out FStructFallback brush, "IconBrush") && brush.TryGetValue(out UTexture2D res, "ResourceObject"))
Preview = Utils.GetBitmap(res);
}
// text
if (Object.TryGetValue(out FText displayName, "DisplayName", "ItemName", "BundleName", "DefaultHeaderText", "UIDisplayName", "EntryName", "EventCalloutTitle"))
DisplayName = displayName.Text;
if (Object.TryGetValue(out FText description, "Description", "ItemDescription", "BundleDescription", "GeneralDescription", "DefaultBodyText", "UIDescription", "UIDisplayDescription", "EntryDescription", "EventCalloutDescription"))
Description = description.Text;
else if (Object.TryGetValue(out FText[] descriptions, "Description"))
Description = string.Join('\n', descriptions.Select(x => x.Text));
if (Object.TryGetValue(out FText shortDescription, "ShortDescription", "UIDisplaySubName"))
ShortDescription = shortDescription.Text;
else if (Object.ExportType.Equals("AthenaItemWrapDefinition", StringComparison.OrdinalIgnoreCase))
ShortDescription = Utils.GetLocalizedResource("Fort.Cosmetics", "ItemWrapShortDescription", "Wrap");
// Only works on non-cataba designs
if (Object.TryGetValue(out FStructFallback eventArrowColor, "EventArrowColor") &&
eventArrowColor.TryGetValue(out FLinearColor specifiedArrowColor, "SpecifiedColor") &&
Object.TryGetValue(out FStructFallback eventArrowShadowColor, "EventArrowShadowColor") &&
eventArrowShadowColor.TryGetValue(out FLinearColor specifiedShadowColor, "SpecifiedColor"))
{
Background = new[] { SKColor.Parse(specifiedArrowColor.Hex), SKColor.Parse(specifiedShadowColor.Hex) };
Border = new[] { SKColor.Parse(specifiedShadowColor.Hex), SKColor.Parse(specifiedArrowColor.Hex) };
}
Description = Utils.RemoveHtmlTags(Description);
}
public override void ParseForInfo()
{
ParseForReward(UserSettings.Default.CosmeticDisplayAsset);
if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags"))
CheckGameplayTags(gameplayTags);
if (Object.TryGetValue(out FPackageIndex cosmeticItem, "cosmetic_item"))
CosmeticSource = cosmeticItem.Name;
}
protected void Draw(SKCanvas c)
{
switch (Style)
{
case EIconStyle.NoBackground:
DrawPreview(c);
break;
case EIconStyle.NoText:
DrawBackground(c);
DrawPreview(c);
DrawUserFacingFlags(c);
break;
default:
DrawBackground(c);
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
DrawDescription(c);
DrawToBottom(c, SKTextAlign.Right, CosmeticSource);
if (Description != ShortDescription)
DrawToBottom(c, SKTextAlign.Left, ShortDescription);
DrawUserFacingFlags(c);
break;
}
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
Draw(c);
return new[] { ret };
}
private void GetSeries(FPackageIndex s)
{
if (!Utils.TryGetPackageIndexExport(s, out UObject export)) return;
GetSeries(export);
}
private void GetSeries(FInstancedStruct[] s)
{
if (s.FirstOrDefault(d => d.NonConstStruct?.TryGetValue(out FPackageIndex _, "Series") == true) is { } dl)
GetSeries(dl.NonConstStruct?.Get<FPackageIndex>("Series"));
}
private void GetSeries(FStructFallback s)
{
if (!s.TryGetValue(out FPackageIndex[] components, "Components")) return;
if (components.FirstOrDefault(c => c.Name.Contains("Series")) is not { } seriesDef ||
!seriesDef.TryLoad(out var seriesDefObj) || seriesDefObj is null ||
!seriesDefObj.TryGetValue(out UObject series, "Series")) return;
GetSeries(series);
}
protected void GetSeries(UObject uObject)
{
if (uObject is UTexture2D texture2D)
{
SeriesBackground = texture2D.Decode();
return;
}
if (uObject.TryGetValue(out FSoftObjectPath backgroundTexture, "BackgroundTexture"))
{
SeriesBackground = Utils.GetBitmap(backgroundTexture);
}
if (uObject.TryGetValue(out FStructFallback colors, "Colors") &&
colors.TryGetValue(out FLinearColor color1, "Color1") &&
colors.TryGetValue(out FLinearColor color2, "Color2") &&
colors.TryGetValue(out FLinearColor color3, "Color3"))
{
Background = new[] { SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex) };
Border = new[] { SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex) };
}
if (uObject.Name.Equals("PlatformSeries") &&
uObject.TryGetValue(out FSoftObjectPath itemCardMaterial, "ItemCardMaterial") &&
Utils.TryLoadObject(itemCardMaterial.AssetPathName.Text, out UMaterialInstanceConstant material))
{
foreach (var vectorParameter in material.VectorParameterValues)
{
if (vectorParameter.ParameterValue == null || !vectorParameter.ParameterInfo.Name.Text.Equals("ColorCircuitBackground"))
continue;
Background[0] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex);
}
}
}
private void GetRarity(EFortRarity r)
{
if (!Utils.TryLoadObject("FortniteGame/Content/Balance/RarityData.RarityData", out UObject export)) return;
if (export.GetByIndex<FStructFallback>((int) r) is { } data &&
data.TryGetValue(out FLinearColor color1, "Color1") &&
data.TryGetValue(out FLinearColor color2, "Color2") &&
data.TryGetValue(out FLinearColor color3, "Color3"))
{
Background = new[] { SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex) };
Border = new[] { SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex) };
}
}
protected string GetCosmeticSet(string setName)
{
if (!Utils.TryLoadObject("FortniteGame/Content/Athena/Items/Cosmetics/Metadata/CosmeticSets.CosmeticSets", out UDataTable cosmeticSets))
return string.Empty;
if (!cosmeticSets.TryGetDataTableRow(setName, StringComparison.OrdinalIgnoreCase, out var uObject))
return string.Empty;
var name = string.Empty;
if (uObject.TryGetValue(out FText displayName, "DisplayName"))
name = displayName.Text;
var format = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_SetMembership_NotRich", "\nPart of the {0} set.");
return string.Format(format, name);
}
protected (int, int) GetInternalSID(int number)
{
static int GetSeasonsInChapter(int chapter) => chapter switch
{
1 => 10,
2 => 8,
3 => 4,
4 => 5,
_ => 10
};
var chapterIdx = 0;
var seasonIdx = 0;
while (number > 0)
{
var seasonsInChapter = GetSeasonsInChapter(++chapterIdx);
if (number > seasonsInChapter)
number -= seasonsInChapter;
else
{
seasonIdx = number;
number = 0;
}
}
return (chapterIdx, seasonIdx);
}
protected string GetCosmeticSeason(string seasonNumber)
{
var s = seasonNumber["Cosmetics.Filter.Season.".Length..];
var initial = int.Parse(s);
(int chapterIdx, int seasonIdx) = GetInternalSID(initial);
var season = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "SeasonTextFormat", "Season {0}");
var introduced = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_Season", "\nIntroduced in <SeasonText>{0}</>.");
if (initial <= 10) return Utils.RemoveHtmlTags(string.Format(introduced, string.Format(season, s)));
var chapter = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterTextFormat", "Chapter {0}");
var chapterFormat = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterSeasonTextFormat", "{0}, {1}");
var d = string.Format(chapterFormat, string.Format(chapter, chapterIdx), string.Format(season, seasonIdx));
return Utils.RemoveHtmlTags(string.Format(introduced, d));
}
private void CheckGameplayTags(FGameplayTagContainer gameplayTags)
{
if (gameplayTags.TryGetGameplayTag("Cosmetics.Source.", out var source))
CosmeticSource = source.Text["Cosmetics.Source.".Length..];
else if (gameplayTags.TryGetGameplayTag("Athena.ItemAction.", out var action))
CosmeticSource = action.Text["Athena.ItemAction.".Length..];
if (gameplayTags.TryGetGameplayTag("Cosmetics.Set.", out var set))
Description += GetCosmeticSet(set.Text);
if (gameplayTags.TryGetGameplayTag("Cosmetics.Filter.Season.", out var season))
Description += GetCosmeticSeason(season.Text);
GetUserFacingFlags(gameplayTags.GetAllGameplayTags(
"Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender."));
}
protected void GetUserFacingFlags(IList<string> userFacingFlags)
{
if (userFacingFlags.Count < 1 || !Utils.TryLoadObject("FortniteGame/Content/Items/ItemCategories.ItemCategories", out UObject itemCategories))
return;
if (!itemCategories.TryGetValue(out FStructFallback[] tertiaryCategories, "TertiaryCategories"))
return;
UserFacingFlags = new Dictionary<string, SKBitmap>(userFacingFlags.Count);
foreach (var flag in userFacingFlags)
{
if (flag.Equals("Cosmetics.UserFacingFlags.HasUpgradeQuests", StringComparison.OrdinalIgnoreCase))
{
if (Object.ExportType.Equals("AthenaPetCarrierItemDefinition", StringComparison.OrdinalIgnoreCase))
UserFacingFlags[flag] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Pets-64.png"))?.Stream);
else UserFacingFlags[flag] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Quests-64.png"))?.Stream);
}
else
{
foreach (var category in tertiaryCategories)
{
if (category.TryGetValue(out FGameplayTagContainer tagContainer, "TagContainer") && tagContainer.TryGetGameplayTag(flag, out _) &&
category.TryGetValue(out FStructFallback categoryBrush, "CategoryBrush") && categoryBrush.TryGetValue(out FStructFallback brushXxs, "Brush_XXS") &&
brushXxs.TryGetValue(out FPackageIndex resourceObject, "ResourceObject") && Utils.TryGetPackageIndexExport(resourceObject, out UTexture2D texture))
{
UserFacingFlags[flag] = Utils.GetBitmap(texture);
}
}
}
}
}
private void DrawUserFacingFlags(SKCanvas c)
{
if (UserFacingFlags == null) return;
const int size = 25;
var x = Margin * (int) 2.5;
foreach (var flag in UserFacingFlags.Values.Where(flag => flag != null))
{
c.DrawBitmap(flag.Resize(size), new SKPoint(x, Margin * (int) 2.5), ImagePaint);
x += size;
}
}
}

View File

@ -0,0 +1,293 @@
using System;
using System.Collections.Generic;
using CUE4Parse.GameTypes.FN.Enums;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Engine;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.Engine.Curves;
using CUE4Parse.UE4.Objects.GameplayTags;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Extensions;
using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN;
public class BaseIconStats : BaseIcon
{
private readonly IList<IconStat> _statistics;
private const int _headerHeight = 128;
private bool _screenLayer;
public BaseIconStats(UObject uObject, EIconStyle style) : base(uObject, style)
{
Width = 1024;
Height = _headerHeight;
Margin = 0;
_statistics = new List<IconStat>();
_screenLayer = uObject.ExportType.Equals("FortAccoladeItemDefinition", StringComparison.OrdinalIgnoreCase);
DefaultPreview = Utils.GetBitmap("FortniteGame/Content/Athena/HUD/Quests/Art/T_NPC_Default.T_NPC_Default");
}
public override void ParseForInfo()
{
base.ParseForInfo();
DisplayName = DisplayName.ToUpperInvariant();
if (Object.TryGetValue(out FName accoladeType, "AccoladeType") &&
accoladeType.Text.Equals("EFortAccoladeType::Medal", StringComparison.OrdinalIgnoreCase))
{
_screenLayer = false;
}
if (Object.TryGetValue(out FGameplayTagContainer poiLocations, "POILocations") &&
Utils.TryLoadObject("FortniteGame/Content/Quests/QuestIndicatorData.QuestIndicatorData", out UObject uObject) &&
uObject.TryGetValue(out FStructFallback[] challengeMapPoiData, "ChallengeMapPoiData"))
{
foreach (var location in poiLocations)
{
var locationName = "Unknown";
foreach (var poi in challengeMapPoiData)
{
if (!poi.TryGetValue(out FStructFallback locationTag, "LocationTag") || !locationTag.TryGetValue(out FName tagName, "TagName") ||
tagName != location.TagName || !poi.TryGetValue(out FText text, "Text")) continue;
locationName = text.Text;
break;
}
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "B0C091D7409B1657423C5F97E9CF4C77", "LOCATION NAME"), locationName.ToUpper()));
}
}
if (Object.TryGetValue(out FStructFallback maxStackSize, "MaxStackSize"))
{
if (maxStackSize.TryGetValue(out float v, "Value") && v > 0)
{
_statistics.Add(new IconStat("Max Stack", v, 15));
}
else if (TryGetCurveTableStat(maxStackSize, out var s))
{
_statistics.Add(new IconStat("Max Stack", s, 15));
}
}
if (Object.TryGetValue(out FStructFallback xpRewardAmount, "XpRewardAmount") && TryGetCurveTableStat(xpRewardAmount, out var x))
{
_statistics.Add(new IconStat("XP Amount", x));
}
if (Object.TryGetValue(out FStructFallback weaponStatHandle, "WeaponStatHandle") &&
weaponStatHandle.TryGetValue(out FName weaponRowName, "RowName") &&
weaponStatHandle.TryGetValue(out UDataTable dataTable, "DataTable") &&
dataTable.TryGetDataTableRow(weaponRowName.Text, StringComparison.OrdinalIgnoreCase, out var weaponRowValue))
{
if (weaponRowValue.TryGetValue(out int bpc, "BulletsPerCartridge"))
{
var multiplier = bpc != 0f ? bpc : 1;
if (weaponRowValue.TryGetValue(out float dmgPb, "DmgPB") && dmgPb != 0f)
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "35D04D1B45737BEA25B69686D9E085B9", "Damage"), dmgPb * multiplier, 200));
}
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));
}
}
if (weaponRowValue.TryGetValue(out int clipSize, "ClipSize") && clipSize != 0)
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "068239DD4327B36124498C9C5F61C038", "Magazine Size"), clipSize, 50));
}
if (weaponRowValue.TryGetValue(out float firingRate, "FiringRate") && firingRate != 0f)
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "27B80BA44805ABD5A2D2BAB2902B250C", "Fire Rate"), firingRate, 15));
}
if (weaponRowValue.TryGetValue(out float armTime, "ArmTime") && armTime != 0f)
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "3BFEB8BD41A677CC5F45B9A90D6EAD6F", "Arming Delay"), armTime, 125));
}
if (weaponRowValue.TryGetValue(out float reloadTime, "ReloadTime") && reloadTime != 0f)
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "6EA26D1A4252034FBD869A90F9A6E49A", "Reload Time"), reloadTime, 15));
}
if ((Object.ExportType.Equals("FortContextTrapItemDefinition", StringComparison.OrdinalIgnoreCase) ||
Object.ExportType.Equals("FortTrapItemDefinition", StringComparison.OrdinalIgnoreCase)) &&
weaponRowValue.TryGetValue(out UDataTable durabilityTable, "Durability") &&
weaponRowValue.TryGetValue(out FName durabilityRowName, "DurabilityRowName") &&
durabilityTable.TryGetDataTableRow(durabilityRowName.Text, StringComparison.OrdinalIgnoreCase, out var durability) &&
durability.TryGetValue(out int duraByRarity, Object.GetOrDefault("Rarity", EFortRarity.Uncommon).GetDescription()))
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "6FA2882140CB69DE32FD73A392F0585B", "Durability"), duraByRarity, 20));
}
}
if (!string.IsNullOrEmpty(Description))
Height += 40 + (int) _informationPaint.TextSize * Utils.SplitLines(Description, _informationPaint, Width - 20).Count;
Height += 50 * _statistics.Count;
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
DrawHeader(c);
DrawDisplayName(c);
DrawStatistics(c);
return new[] { ret };
}
private bool TryGetCurveTableStat(FStructFallback property, out float statValue)
{
if (property.TryGetValue(out FStructFallback curve, "Curve") &&
curve.TryGetValue(out FName rowName, "RowName") &&
curve.TryGetValue(out UCurveTable curveTable, "CurveTable") &&
curveTable.TryFindCurve(rowName, out var rowValue) &&
rowValue is FSimpleCurve s && s.Keys.Length > 0)
{
statValue = s.Keys[0].Value;
return true;
}
statValue = 0F;
return false;
}
private readonly SKPaint _informationPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Color = SKColor.Parse("#262630"), TextSize = 16,
Typeface = Utils.Typefaces.Description
};
private void DrawHeader(SKCanvas c)
{
c.DrawRect(new SKRect(0, 0, Width, Height), _informationPaint);
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4,
new[] { Background[0].WithAlpha(180), Background[1].WithAlpha(220) }, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(_headerHeight, 0, Width, _headerHeight), _informationPaint);
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4,
new[] { SKColor.Parse("#262630"), SKColor.Parse("#1f1f26") }, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(0, _headerHeight, Width, Height), _informationPaint);
_informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, _headerHeight), new SKPoint(Width / 2, 75),
new[] { SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0) }, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(0, 75, Width, _headerHeight), _informationPaint);
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4, Background, SKShaderTileMode.Clamp);
using var rect = new SKPath { FillType = SKPathFillType.EvenOdd };
rect.MoveTo(0, 0);
rect.LineTo(_headerHeight + _headerHeight / 3, 0);
rect.LineTo(_headerHeight, _headerHeight);
rect.LineTo(0, _headerHeight);
rect.Close();
c.DrawPath(rect, _informationPaint);
_informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(_headerHeight / 2, _headerHeight / 2), new SKPoint(_headerHeight / 2 + 100, _headerHeight / 2),
new[] { SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0) }, SKShaderTileMode.Clamp);
c.DrawPath(rect, _informationPaint);
_informationPaint.Shader = null;
ImagePaint.BlendMode = _screenLayer ? SKBlendMode.Screen : Preview == null ? SKBlendMode.ColorBurn : SKBlendMode.SrcOver;
c.DrawBitmap((Preview ?? DefaultPreview).Resize(_headerHeight), 0, 0, ImagePaint);
}
private new void DrawDisplayName(SKCanvas c)
{
if (string.IsNullOrEmpty(DisplayName)) return;
_informationPaint.TextSize = 50;
_informationPaint.Color = SKColors.White;
_informationPaint.Typeface = Utils.Typefaces.Bundle;
while (_informationPaint.MeasureText(DisplayName) > Width - _headerHeight * 2)
{
_informationPaint.TextSize -= 1;
}
var shaper = new CustomSKShaper(_informationPaint.Typeface);
c.DrawShapedText(shaper, DisplayName, _headerHeight + _headerHeight / 3 + 10, _headerHeight / 2f + _informationPaint.TextSize / 3, _informationPaint);
}
private void DrawStatistics(SKCanvas c)
{
var outY = _headerHeight + 25f;
if (!string.IsNullOrEmpty(Description))
{
_informationPaint.TextSize = 16;
_informationPaint.Color = SKColors.White.WithAlpha(175);
_informationPaint.Typeface = Utils.Typefaces.Description;
Utils.DrawMultilineText(c, Description, Width - 40, 0, SKTextAlign.Center,
new SKRect(20, outY, Width - 20, Height), _informationPaint, out outY);
outY += 25;
}
foreach (var stat in _statistics)
{
stat.Draw(c, Border[0].WithAlpha(100), Width, _headerHeight, ref outY);
outY += 50;
}
}
}
public class IconStat
{
private readonly string _statName;
private readonly object _value;
private readonly float _maxValue;
public IconStat(string statName, object value, float maxValue = 0)
{
_statName = statName.ToUpperInvariant();
_value = value;
_maxValue = maxValue;
}
private readonly SKPaint _statPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
TextSize = 25, Typeface = Utils.Typefaces.DisplayName,
Color = SKColors.White
};
public void Draw(SKCanvas c, SKColor sliderColor, int width, int height, ref float y)
{
while (_statPaint.MeasureText(_statName) > height * 2 - 40)
{
_statPaint.TextSize -= 1;
}
var shaper = new CustomSKShaper(_statPaint.Typeface);
c.DrawShapedText(shaper, _statName, 50, y + 10, _statPaint);
_statPaint.TextAlign = SKTextAlign.Right;
_statPaint.Typeface = Utils.Typefaces.BundleNumber;
_statPaint.Color = sliderColor;
var sliderRight = width - 100 - _statPaint.MeasureText(_value.ToString());
c.DrawRect(new SKRect(height * 2, y, Math.Min(width - height, sliderRight), y + 5), _statPaint);
_statPaint.Color = SKColors.White;
c.DrawText(_value.ToString(), new SKPoint(width - 50, y + 10), _statPaint);
if (_maxValue < 1 || !float.TryParse(_value.ToString(), out var floatValue)) return;
if (floatValue < 0)
floatValue = 0;
var sliderWidth = (sliderRight - height * 2) * (floatValue / _maxValue);
c.DrawRect(new SKRect(height * 2, y, Math.Min(height * 2 + sliderWidth, sliderRight), y + 5), _statPaint);
}
}

View File

@ -0,0 +1,99 @@
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN;
public class BaseItemAccessToken : UCreator
{
private readonly SKBitmap _locked, _unlocked;
private string _unlockedDescription, _exportName;
private BaseIcon _icon;
public BaseItemAccessToken(UObject uObject, EIconStyle style) : base(uObject, style)
{
_unlocked = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Locks/T-Icon-Unlocked-128.T-Icon-Unlocked-128").Resize(24);
_locked = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Locks/T-Icon-Lock-128.T-Icon-Lock-128").Resize(24);
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FPackageIndex accessItem, "access_item") &&
Utils.TryGetPackageIndexExport(accessItem, out UObject uObject))
{
_exportName = uObject.Name;
_icon = new BaseIcon(uObject, EIconStyle.Default);
_icon.ParseForReward(false);
}
if (Object.TryGetValue(out FText displayName, "DisplayName", "ItemName") && displayName.Text != "TBD")
DisplayName = displayName.Text;
else
DisplayName = _icon?.DisplayName;
Description = Object.TryGetValue(out FText description, "Description", "ItemDescription") ? description.Text : _icon?.Description;
if (Object.TryGetValue(out FText unlockDescription, "UnlockDescription")) _unlockedDescription = unlockDescription.Text;
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
switch (Style)
{
case EIconStyle.NoBackground:
Preview = _icon.Preview;
DrawPreview(c);
break;
case EIconStyle.NoText:
Preview = _icon.Preview;
_icon.DrawBackground(c);
DrawPreview(c);
break;
default:
_icon.DrawBackground(c);
DrawInformation(c);
DrawToBottom(c, SKTextAlign.Right, _exportName);
break;
}
return new[] { ret };
}
private void DrawInformation(SKCanvas c)
{
var size = 45;
var left = Width / 2;
while (DisplayNamePaint.MeasureText(DisplayName) > Width - _icon.Margin * 2)
{
DisplayNamePaint.TextSize = size -= 2;
}
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
var shapedText = shaper.Shape(DisplayName, DisplayNamePaint);
c.DrawShapedText(shaper, DisplayName, left - shapedText.Width / 2, _icon.Margin * 8 + size, DisplayNamePaint);
float topBase = _icon.Margin + size * 2;
if (!string.IsNullOrEmpty(_unlockedDescription))
{
c.DrawBitmap(_locked, new SKRect(50, topBase, 50 + _locked.Width, topBase + _locked.Height), ImagePaint);
Utils.DrawMultilineText(c, _unlockedDescription, Width, _icon.Margin, SKTextAlign.Left,
new SKRect(70 + _locked.Width, topBase + 10, Width - 50, 256), DescriptionPaint, out topBase);
}
if (!string.IsNullOrEmpty(Description))
{
c.DrawBitmap(_unlocked, new SKRect(50, topBase, 50 + _unlocked.Width, topBase + _unlocked.Height), ImagePaint);
Utils.DrawMultilineText(c, Description, Width, _icon.Margin, SKTextAlign.Left,
new SKRect(70 + _unlocked.Width, topBase + 10, Width - 50, 256), DescriptionPaint, out topBase);
}
var h = Width - _icon.Margin - topBase;
c.DrawBitmap(_icon.Preview ?? _icon.DefaultPreview, new SKRect(left - h / 2, topBase, left + h / 2, Width - _icon.Margin), ImagePaint);
}
}

View File

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

View File

@ -0,0 +1,92 @@
using System.Linq;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Objects.UObject;
using SkiaSharp;
namespace FModel.Creator.Bases.FN;
public class BaseMaterialInstance : BaseIcon
{
public BaseMaterialInstance(UObject uObject, EIconStyle style) : base(uObject, style)
{
Background = new[] { SKColor.Parse("4F4F69"), SKColor.Parse("4F4F69") };
Border = new[] { SKColor.Parse("9092AB") };
}
public override void ParseForInfo()
{
if (Object is not UMaterialInstanceConstant material) return;
texture_finding:
foreach (var textureParameter in material.TextureParameterValues) // get texture from base material
{
if (!textureParameter.ParameterValue.TryLoad<UTexture2D>(out var texture) || Preview != null) continue;
switch (textureParameter.ParameterInfo.Name.Text)
{
case "SeriesTexture":
GetSeries(texture);
break;
case "TextureA":
case "TextureB":
case "OfferImage":
case "CarTexture":
Preview = Utils.GetBitmap(texture);
break;
}
}
while (material.VectorParameterValues.Length == 0 || // try to get color from parent if not found here
material.VectorParameterValues.All(x => x.ParameterInfo.Name.Text.Equals("FallOff_Color"))) // use parent if it only contains FallOff_Color
{
if (material.TryGetValue(out FPackageIndex parent, "Parent"))
Utils.TryGetPackageIndexExport(parent, out material);
else return;
if (material == null) return;
}
if (Preview == null)
{
if (material.TryGetValue(out FPackageIndex parent, "Parent"))
Utils.TryGetPackageIndexExport(parent, out material);
goto texture_finding;
}
foreach (var vectorParameter in material.VectorParameterValues)
{
if (vectorParameter.ParameterValue == null) continue;
switch (vectorParameter.ParameterInfo.Name.Text)
{
case "Background_Color_A":
Background[0] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex);
Border[0] = Background[0];
break;
case "Background_Color_B": // Border color can be defaulted here in some case where Background_Color_A should be taken from parent but Background_Color_B from base
Background[1] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex);
break;
}
}
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
switch (Style)
{
case EIconStyle.NoBackground:
DrawPreview(c);
break;
default:
DrawBackground(c);
DrawPreview(c);
break;
}
return new[] { ret };
}
}

View File

@ -0,0 +1,83 @@
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.UObject;
using SkiaSharp;
namespace FModel.Creator.Bases.FN;
public class BaseMtxOffer : UCreator
{
public BaseMtxOffer(UObject uObject, EIconStyle style) : base(uObject, style)
{
Background = new[] { SKColor.Parse("4F4F69"), SKColor.Parse("4F4F69") };
Border = new[] { SKColor.Parse("9092AB") };
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FSoftObjectPath image, "SoftDetailsImage", "SoftTileImage"))
{
Preview = Utils.GetBitmap(image);
}
if (Object.TryGetValue(out FStructFallback gradient, "Gradient") &&
gradient.TryGetValue(out FLinearColor start, "Start") &&
gradient.TryGetValue(out FLinearColor stop, "Stop"))
{
Background = new[] { SKColor.Parse(start.Hex), SKColor.Parse(stop.Hex) };
}
if (Object.TryGetValue(out FLinearColor background, "Background"))
Border = new[] { SKColor.Parse(background.Hex) };
if (Object.TryGetValue(out FText displayName, "DisplayName"))
DisplayName = displayName.Text;
if (Object.TryGetValue(out FText shortDescription, "ShortDescription"))
Description = shortDescription.Text;
if (Object.TryGetValue(out FStructFallback[] details, "DetailsAttributes"))
{
foreach (var detail in details)
{
if (detail.TryGetValue(out FText detailName, "Name"))
{
Description += $"\n- {detailName.Text.TrimEnd()}";
}
if (detail.TryGetValue(out FText detailValue, "Value") && !string.IsNullOrEmpty(detailValue.Text))
{
Description += $" ({detailValue.Text})";
}
}
}
Description = Utils.RemoveHtmlTags(Description);
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
switch (Style)
{
case EIconStyle.NoBackground:
DrawPreview(c);
break;
case EIconStyle.NoText:
DrawBackground(c);
DrawPreview(c);
break;
default:
DrawBackground(c);
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
DrawDescription(c);
break;
}
return new[] { ret };
}
}

View File

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

View File

@ -0,0 +1,77 @@
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Services;
using FModel.ViewModels;
using SkiaSharp;
namespace FModel.Creator.Bases.FN;
public class BasePlaylist : UCreator
{
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
private SKBitmap _missionIcon;
public BasePlaylist(UObject uObject, EIconStyle style) : base(uObject, style)
{
Margin = 0;
Width = 1024;
Height = 512;
Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Tiles/T_Athena_Tile_Matchmaking_Default.T_Athena_Tile_Matchmaking_Default");
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FText displayName, "UIDisplayName", "DisplayName"))
DisplayName = displayName.Text;
if (Object.TryGetValue(out FText description, "UIDescription", "Description"))
Description = description.Text;
if (Object.TryGetValue(out UTexture2D missionIcon, "MissionIcon"))
_missionIcon = Utils.GetBitmap(missionIcon).Resize(25);
if (!Object.TryGetValue(out FName playlistName, "PlaylistName") || string.IsNullOrWhiteSpace(playlistName.Text))
return;
var playlist = _apiEndpointView.FortniteApi.GetPlaylist(playlistName.Text);
if (!playlist.IsSuccess || playlist.Data.Images is not { HasShowcase: true } ||
!_apiEndpointView.FortniteApi.TryGetBytes(playlist.Data.Images.Showcase, out var image))
return;
Preview = Utils.GetBitmap(image).ResizeWithRatio(1024, 512);
Width = Preview.Width;
Height = Preview.Height;
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
switch (Style)
{
case EIconStyle.NoBackground:
DrawPreview(c);
break;
case EIconStyle.NoText:
DrawPreview(c);
DrawMissionIcon(c);
break;
default:
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
DrawDescription(c);
DrawMissionIcon(c);
break;
}
return new[] { ret };
}
private void DrawMissionIcon(SKCanvas c)
{
if (_missionIcon == null) return;
c.DrawBitmap(_missionIcon, new SKPoint(5, 5), ImagePaint);
}
}

View File

@ -0,0 +1,278 @@
using System;
using System.Linq;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Engine;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Extensions;
using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN;
public class BaseQuest : BaseIcon
{
private int _count;
private Reward _reward;
private readonly bool _screenLayer;
private readonly string[] _unauthorizedReward = { "Token", "ChallengeBundle", "GiftBox" };
public string NextQuestName { get; private set; }
public BaseQuest(UObject uObject, EIconStyle style) : base(uObject, style)
{
Margin = 0;
Width = 1024;
Height = 256;
DefaultPreview = Utils.GetBitmap("FortniteGame/Content/Athena/HUD/Quests/Art/T_NPC_Default.T_NPC_Default");
if (uObject != null)
{
_screenLayer = uObject.ExportType.Equals("FortFeatItemDefinition", StringComparison.OrdinalIgnoreCase);
}
}
private BaseQuest(int completionCount, EIconStyle style) : this(null, style) // completion
{
var description = completionCount < 0 ?
Utils.GetLocalizedResource("AthenaChallengeDetailsEntry", "CompletionRewardFormat_All", "Complete <text color=\"FFF\" case=\"upper\" fontface=\"black\">all {0} challenges</> to earn the reward item") :
Utils.GetLocalizedResource("AthenaChallengeDetailsEntry", "CompletionRewardFormat", "Complete <text color=\"FFF\" case=\"upper\" fontface=\"black\">any {0} challenges</> to earn the reward item");
DisplayName = ReformatString(description, completionCount.ToString(), completionCount < 0);
}
public BaseQuest(int completionCount, FSoftObjectPath itemDefinition, EIconStyle style) : this(completionCount, style) // completion
{
_reward = Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject) ? new Reward(uObject) : new Reward();
}
public BaseQuest(int completionCount, int quantity, string reward, EIconStyle style) : this(completionCount, style) // completion
{
_reward = new Reward(quantity, reward);
}
public override void ParseForInfo()
{
ParseForReward(false);
if (Object.TryGetValue(out FStructFallback urgentQuestData, "UrgentQuestData"))
{
if (urgentQuestData.TryGetValue(out FText eventTitle, "EventTitle"))
DisplayName = eventTitle.Text;
if (urgentQuestData.TryGetValue(out FText eventDescription, "EventDescription"))
Description = eventDescription.Text;
if (urgentQuestData.TryGetValue(out FPackageIndex alertIcon, "AlertIcon", "BountyPriceImage"))
Preview = Utils.GetBitmap(alertIcon);
}
else
{
if (!string.IsNullOrEmpty(ShortDescription))
Description = ShortDescription;
if (string.IsNullOrEmpty(DisplayName) && !string.IsNullOrEmpty(Description))
DisplayName = Description;
if (DisplayName == Description)
Description = string.Empty;
if ((Object.TryGetValue(out FSoftObjectPath icon, "QuestGiverWidgetIcon", "NotificationIconOverride") &&
Utils.TryLoadObject(icon.AssetPathName.Text, out UObject iconObject)) ||
(Object.TryGetValue(out FSoftObjectPath tandemCharacterData, "TandemCharacterData") &&
Utils.TryLoadObject(tandemCharacterData.AssetPathName.Text, out UObject uObject) &&
uObject.TryGetValue(out FSoftObjectPath tandemIcon, "EntryListIcon", "ToastIcon") &&
Utils.TryLoadObject(tandemIcon.AssetPathName.Text, out iconObject)))
{
Preview = iconObject switch
{
UTexture2D text => Utils.GetBitmap(text),
UMaterialInstanceConstant mat => Utils.GetBitmap(mat),
_ => Preview
};
}
}
if (Object.TryGetValue(out int objectiveCompletionCount, "ObjectiveCompletionCount"))
_count = objectiveCompletionCount;
if (Object.TryGetValue(out FStructFallback[] objectives, "Objectives") && objectives.Length > 0)
{
// actual description doesn't exist
if (string.IsNullOrEmpty(Description) && objectives[0].TryGetValue(out FText description, "Description"))
Description = description.Text;
// ObjectiveCompletionCount doesn't exist
if (_count == 0)
{
if (objectives[0].TryGetValue(out int count, "Count") && count > 1)
_count = count;
else
_count = objectives.Length;
}
}
if (Object.TryGetValue(out FStructFallback[] rewards, "Rewards"))
{
foreach (var reward in rewards)
{
if (!reward.TryGetValue(out FStructFallback itemPrimaryAssetId, "ItemPrimaryAssetId") ||
!reward.TryGetValue(out int quantity, "Quantity")) continue;
if (!itemPrimaryAssetId.TryGetValue(out FStructFallback primaryAssetType, "PrimaryAssetType") ||
!itemPrimaryAssetId.TryGetValue(out FName primaryAssetName, "PrimaryAssetName") ||
!primaryAssetType.TryGetValue(out FName name, "Name")) continue;
if (name.Text.Equals("Quest", StringComparison.OrdinalIgnoreCase))
{
NextQuestName = primaryAssetName.Text;
}
else if (!_unauthorizedReward.Contains(name.Text))
{
_reward = new Reward(quantity, $"{name}:{primaryAssetName}");
}
}
}
if (_reward == null)
{
FName rowName = null;
if (Object.TryGetValue(out UDataTable rewardsTable, "RewardsTable"))
rowName = new FName("Default");
else if (Object.TryGetValue(out FStructFallback[] rewardTableRows, "IndividualRewardTableRows") &&
rewardTableRows.Length > 0 && rewardTableRows[0].TryGetValue(out rowName, "RowName") &&
rewardTableRows[0].TryGetValue(out rewardsTable, "DataTable")) {}
if (rewardsTable != null && rowName != null && rewardsTable.TryGetDataTableRow(rowName.Text, StringComparison.InvariantCulture, out var row))
{
if (row.TryGetValue(out FName templateId, "TemplateId") &&
row.TryGetValue(out int quantity, "Quantity"))
{
_reward = new Reward(quantity, templateId);
}
}
}
if (_reward == null && Object.TryGetValue(out FStructFallback[] hiddenRewards, "HiddenRewards"))
{
foreach (var hiddenReward in hiddenRewards)
{
if (!hiddenReward.TryGetValue(out FName templateId, "TemplateId") ||
!hiddenReward.TryGetValue(out int quantity, "Quantity")) continue;
_reward = new Reward(quantity, templateId);
break;
}
}
_reward ??= new Reward();
}
public void DrawQuest(SKCanvas c, int y)
{
DrawBackground(c, y);
DrawPreview(c, y);
DrawTexts(c, y);
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
DrawQuest(c, 0);
return new[] { ret };
}
private string ReformatString(string s, string completionCount, bool isAll)
{
s = s.Replace("({0})", "{0}").Replace("{QuestNumber}", "<text color=\"FFF\" case=\"upper\" fontface=\"black\">{0}</>");
var index = s.IndexOf("{0}|plural(", StringComparison.OrdinalIgnoreCase);
if (index > -1)
{
var p = s.Substring(index, s[index..].IndexOf(')') + 1);
s = s.Replace(p, string.Empty);
s = s.Insert(s.IndexOf("</>", StringComparison.OrdinalIgnoreCase), p.SubstringAfter("(").SubstringAfter("=").SubstringBefore(","));
}
var upper = s.SubstringAfter(">").SubstringBefore("</>");
return string.Format(Utils.RemoveHtmlTags(s.Replace(upper, upper.ToUpper())), isAll ? string.Empty : completionCount).Replace(" ", " ");
}
private readonly SKPaint _informationPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Color = SKColor.Parse("#262630")
};
private void DrawBackground(SKCanvas c, int y)
{
c.DrawRect(new SKRect(Margin, y, Width, y + Height), _informationPaint);
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, y + Height / 2), Width / 5 * 4,
new[] { Background[0].WithAlpha(50), Background[1].WithAlpha(50) }, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(Height / 2, y, Width, y + Height), _informationPaint);
_informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, y + Height), new SKPoint(Width / 2, 75),
new[] { SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0) }, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(0, y + 75, Width, y + Height), _informationPaint);
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, y + Height / 2), Width / 5 * 4, Background, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(Margin, y, Height / 2, y + Height), _informationPaint);
_informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Height / 2, y + Height / 2), new SKPoint(Height / 2 + 100, y + Height / 2),
new[] { SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0) }, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(Height / 2, y, Height / 2 + 100, y + Height), _informationPaint);
}
private void DrawPreview(SKCanvas c, int y)
{
ImagePaint.BlendMode = _screenLayer ? SKBlendMode.Screen : Preview == null ? SKBlendMode.ColorBurn : SKBlendMode.SrcOver;
c.DrawBitmap(Preview ?? DefaultPreview, new SKRect(Margin, y, Height - Margin, y + Height), ImagePaint);
}
private void DrawTexts(SKCanvas c, int y)
{
_informationPaint.Shader = null;
if (!string.IsNullOrWhiteSpace(DisplayName))
{
_informationPaint.TextSize = 40;
_informationPaint.Color = SKColors.White;
_informationPaint.Typeface = Utils.Typefaces.Bundle;
while (_informationPaint.MeasureText(DisplayName) > Width - Height - 10)
{
_informationPaint.TextSize -= 1;
}
var shaper = new CustomSKShaper(_informationPaint.Typeface);
c.DrawShapedText(shaper, DisplayName, Height, y + 50, _informationPaint);
}
var outY = y + 75f;
if (!string.IsNullOrWhiteSpace(Description))
{
_informationPaint.TextSize = 16;
_informationPaint.Color = SKColors.White.WithAlpha(175);
_informationPaint.Typeface = Utils.Typefaces.Description;
Utils.DrawMultilineText(c, Description, Width - Height, y, SKTextAlign.Left,
new SKRect(Height, outY, Width - 10, y + Height), _informationPaint, out outY);
}
_informationPaint.Color = Border[0].WithAlpha(100);
c.DrawRect(new SKRect(Height, outY, Width - 150, outY + 5), _informationPaint);
if (_count > 0)
{
_informationPaint.TextSize = 25;
_informationPaint.Color = SKColors.White;
_informationPaint.Typeface = Utils.Typefaces.BundleNumber;
c.DrawText("0 / ", new SKPoint(Width - 130, outY + 10), _informationPaint);
_informationPaint.Color = Border[0];
c.DrawText(_count.ToString(), new SKPoint(Width - 95, outY + 10), _informationPaint);
}
_reward.DrawQuest(c, new SKRect(Height, outY + 25, Width - 20, y + Height - 25));
}
}

View File

@ -0,0 +1,158 @@
using System;
using System.Linq;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN;
public class Page
{
public int LevelsNeededForUnlock;
public int RewardsNeededForUnlock;
public Reward[] RewardEntryList;
}
public class BaseSeason : UCreator
{
private Reward _firstWinReward;
private Page[] _bookXpSchedule;
private const int _headerHeight = 150;
// keep the list because rewards are ordered by least to most important
// we only care about the most but we also have filters so we can't just take the last reward
public BaseSeason(UObject uObject, EIconStyle style) : base(uObject, style)
{
Width = 1024;
Height = _headerHeight + 50;
Margin = 0;
}
public override void ParseForInfo()
{
_bookXpSchedule = Array.Empty<Page>();
if (Object.TryGetValue(out FText displayName, "DisplayName", "ItemName"))
DisplayName = displayName.Text.ToUpperInvariant();
if (Object.TryGetValue(out FStructFallback seasonFirstWinRewards, "SeasonFirstWinRewards") &&
seasonFirstWinRewards.TryGetValue(out FStructFallback[] rewards, "Rewards"))
{
foreach (var reward in rewards)
{
if (!reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition") ||
!Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject)) continue;
_firstWinReward = new Reward(uObject);
break;
}
}
if (Object.TryGetValue(out FPackageIndex[] additionalSeasonData, "AdditionalSeasonData"))
{
foreach (var data in additionalSeasonData)
{
if (!Utils.TryGetPackageIndexExport(data, out UObject packageIndex) ||
!packageIndex.TryGetValue(out FStructFallback[] pageList, "PageList")) continue;
var i = 0;
_bookXpSchedule = new Page[pageList.Length];
foreach (var page in pageList)
{
if (!page.TryGetValue(out int levelsNeededForUnlock, "LevelsNeededForUnlock") ||
!page.TryGetValue(out int rewardsNeededForUnlock, "RewardsNeededForUnlock") ||
!page.TryGetValue(out FPackageIndex[] rewardEntryList, "RewardEntryList"))
continue;
var p = new Page
{
LevelsNeededForUnlock = levelsNeededForUnlock,
RewardsNeededForUnlock = rewardsNeededForUnlock,
RewardEntryList = new Reward[rewardEntryList.Length]
};
for (var j = 0; j < p.RewardEntryList.Length; j++)
{
if (!Utils.TryGetPackageIndexExport(rewardEntryList[j], out packageIndex) ||
!packageIndex.TryGetValue(out FStructFallback battlePassOffer, "BattlePassOffer") ||
!battlePassOffer.TryGetValue(out FStructFallback rewardItem, "RewardItem") ||
!rewardItem.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition") ||
!Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject)) continue;
p.RewardEntryList[j] = new Reward(uObject);
}
_bookXpSchedule[i++] = p;
}
break;
}
}
Height += 100 * _bookXpSchedule.Sum(x => x.RewardEntryList.Length) / _bookXpSchedule.Length;
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
DrawHeader(c);
_firstWinReward?.DrawSeasonWin(c, _headerHeight);
DrawBookSchedule(c);
return new[] { ret };
}
private const int _DEFAULT_AREA_SIZE = 80;
private readonly SKPaint _headerPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Typeface = Utils.Typefaces.Bundle, TextSize = 50,
TextAlign = SKTextAlign.Center, Color = SKColor.Parse("#262630")
};
public void DrawHeader(SKCanvas c)
{
c.DrawRect(new SKRect(0, 0, Width, Height), _headerPaint);
_headerPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4,
new[] { SKColors.SkyBlue.WithAlpha(50), SKColors.Blue.WithAlpha(50) }, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(0, 0, Width, Height), _headerPaint);
_headerPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, _headerHeight), new SKPoint(Width / 2, 75),
new[] { SKColors.Black.WithAlpha(25), SKColors.Blue.WithAlpha(0) }, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(0, 75, Width, _headerHeight), _headerPaint);
_headerPaint.Shader = null;
_headerPaint.Color = SKColors.White;
while (_headerPaint.MeasureText(DisplayName) > Width)
{
_headerPaint.TextSize -= 1;
}
var shaper = new CustomSKShaper(_headerPaint.Typeface);
c.DrawShapedText(shaper, DisplayName, Width / 2f, _headerHeight / 2f + _headerPaint.TextSize / 2 - 10, _headerPaint);
}
private void DrawBookSchedule(SKCanvas c)
{
var x = 20;
var y = _headerHeight + 50;
foreach (var page in _bookXpSchedule)
{
foreach (var reward in page.RewardEntryList)
{
reward.DrawSeason(c, x, y, _DEFAULT_AREA_SIZE);
x += _DEFAULT_AREA_SIZE + 20;
}
y += _DEFAULT_AREA_SIZE + 20;
x = 20;
}
}
}

View File

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

View File

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

View File

@ -0,0 +1,191 @@
using System.Collections.Generic;
using System.Globalization;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN;
public class BaseUserControl : UCreator
{
private List<Options> _optionValues = new();
private readonly SKPaint _displayNamePaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Typeface = Utils.Typefaces.DisplayName, TextSize = 45,
Color = SKColors.White, TextAlign = SKTextAlign.Left
};
private readonly SKPaint _descriptionPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Typeface = Utils.Typefaces.DisplayName, TextSize = 25,
Color = SKColor.Parse("88DBFF"), TextAlign = SKTextAlign.Left
};
public BaseUserControl(UObject uObject, EIconStyle style) : base(uObject, style)
{
Width = 512;
Height = 128;
Margin = 32;
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FText optionDisplayName, "OptionDisplayName", "OptionText"))
DisplayName = optionDisplayName.Text.ToUpperInvariant();
if (Object.TryGetValue(out FText optionDescription, "OptionDescription", "OptionToolTip"))
{
Description = optionDescription.Text;
if (string.IsNullOrWhiteSpace(Description)) return;
Height += (int) _descriptionPaint.TextSize * Utils.SplitLines(Description, _descriptionPaint, Width - Margin).Count;
Height += (int) _descriptionPaint.TextSize;
}
if (Object.TryGetValue(out FStructFallback[] optionValues, "OptionValues", "Options"))
{
_optionValues = new List<Options>();
foreach (var option in optionValues)
{
if (option.TryGetValue(out FText displayName, "DisplayName", "DisplayText"))
{
var opt = new Options { Option = displayName.Text.ToUpperInvariant() };
if (option.TryGetValue(out FLinearColor color, "Value"))
opt.Color = SKColor.Parse(color.Hex).WithAlpha(150);
_optionValues.Add(opt);
}
else if (option.TryGetValue(out FName primaryAssetName, "PrimaryAssetName"))
{
_optionValues.Add(new Options { Option = primaryAssetName.Text });
}
}
}
if (Object.TryGetValue(out FText optionOnText, "OptionOnText"))
_optionValues.Add(new Options { Option = optionOnText.Text });
if (Object.TryGetValue(out FText optionOffText, "OptionOffText"))
_optionValues.Add(new Options { Option = optionOffText.Text });
if (Object.TryGetValue(out int iMin, "Min", "DefaultValue") &&
Object.TryGetValue(out int iMax, "Max"))
{
var increment = iMin;
if (Object.TryGetValue(out int incrementValue, "IncrementValue"))
increment = incrementValue;
var format = "{0}";
if (Object.TryGetValue(out FText unitName, "UnitName"))
format = unitName.Text;
for (var i = iMin; i <= iMax; i += increment)
{
_optionValues.Add(new Options { Option = string.Format(format, i) });
}
}
if (Object.TryGetValue(out float fMin, "Min", "DefaultValue") &&
Object.TryGetValue(out float fMax, "Max"))
{
var increment = fMin;
if (Object.TryGetValue(out float incrementValue, "IncrementValue"))
increment = incrementValue;
var format = "{0}";
if (Object.TryGetValue(out FText unitName, "UnitName"))
format = unitName.Text;
for (var i = fMin; i <= fMax; i += increment)
{
_optionValues.Add(new Options { Option = string.Format(format, i) });
}
}
Height += Margin;
Height += 35 * _optionValues.Count;
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
DrawBackground(c);
DrawInformation(c);
return new[] { ret };
}
private new void DrawBackground(SKCanvas c)
{
c.DrawRect(new SKRect(0, 0, Width, Height),
new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Shader = SKShader.CreateLinearGradient(
new SKPoint(Width / 2, Height),
new SKPoint(Width, Height / 4),
new[] { SKColor.Parse("01369C"), SKColor.Parse("1273C8") },
SKShaderTileMode.Clamp)
});
}
private void DrawInformation(SKCanvas c)
{
// display name
while (_displayNamePaint.MeasureText(DisplayName) > Width - Margin * 2)
{
_displayNamePaint.TextSize -= 2;
}
var shaper = new CustomSKShaper(_displayNamePaint.Typeface);
c.DrawShapedText(shaper, DisplayName, Margin, Margin + _displayNamePaint.TextSize, _displayNamePaint);
#if DEBUG
c.DrawRect(new SKRect(Margin, Margin, Width - Margin, Margin + _displayNamePaint.TextSize), new SKPaint { Color = SKColors.Blue, IsStroke = true });
#endif
// description
float y = Margin;
if (!string.IsNullOrEmpty(DisplayName)) y += _displayNamePaint.TextSize;
if (!string.IsNullOrEmpty(Description)) y += _descriptionPaint.TextSize + Margin / 2F;
Utils.DrawMultilineText(c, Description, Width, Margin, SKTextAlign.Left,
new SKRect(Margin, y, Width - Margin, 256), _descriptionPaint, out var top);
// options
foreach (var option in _optionValues)
{
option.Draw(c, Margin, Width, ref top);
}
}
}
public class Options
{
private const int _SPACE = 5;
private const int _HEIGHT = 30;
private readonly SKPaint _optionPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Typeface = Utils.Typefaces.DisplayName, TextSize = 20,
Color = SKColor.Parse("EEFFFF"), TextAlign = SKTextAlign.Left
};
public string Option;
public SKColor Color = SKColor.Parse("55C5FC").WithAlpha(150);
public void Draw(SKCanvas c, int margin, int width, ref float top)
{
c.DrawRect(new SKRect(margin, top, width - margin, top + _HEIGHT), new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = Color });
c.DrawText(Option, margin + _SPACE * 2, top + 20 * 1.1f, _optionPaint);
top += _HEIGHT + _SPACE;
}
}

View File

@ -0,0 +1,151 @@
using System;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN;
public class Reward
{
private string _rewardQuantity;
private BaseIcon _theReward;
public bool HasReward() => _theReward != null;
public Reward()
{
_rewardQuantity = "x0";
}
public Reward(int quantity, FName primaryAssetName) : this(quantity, primaryAssetName.Text)
{
}
public Reward(int quantity, string assetName) : this()
{
_rewardQuantity = $"x{quantity:###,###,###}".Trim();
if (assetName.Contains(':'))
{
var parts = assetName.Split(':');
if (parts[0].Equals("HomebaseBannerIcon", StringComparison.CurrentCultureIgnoreCase))
{
if (!Utils.TryLoadObject($"FortniteGame/Content/Items/BannerIcons/{parts[1]}.{parts[1]}", out UObject p))
return;
_theReward = new BaseIcon(p, EIconStyle.Default);
_theReward.ParseForReward(false);
_theReward.Border[0] = SKColors.White;
_rewardQuantity = _theReward.DisplayName;
}
else GetReward(parts[1]);
}
else GetReward(assetName);
}
public Reward(UObject uObject)
{
_theReward = new BaseIcon(uObject, EIconStyle.Default);
_theReward.ParseForReward(false);
_theReward.Border[0] = SKColors.White;
_rewardQuantity = _theReward.DisplayName;
}
private readonly SKPaint _rewardPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High
};
public void DrawQuest(SKCanvas c, SKRect rect)
{
_rewardPaint.TextSize = 50;
if (HasReward())
{
c.DrawBitmap((_theReward.Preview ?? _theReward.DefaultPreview).Resize((int) rect.Height), new SKPoint(rect.Left, rect.Top), _rewardPaint);
_rewardPaint.Color = _theReward.Border[0];
_rewardPaint.Typeface = _rewardQuantity.StartsWith("x") ? Utils.Typefaces.BundleNumber : Utils.Typefaces.Bundle;
_rewardPaint.ImageFilter = SKImageFilter.CreateDropShadow(0, 0, 5, 5, _theReward.Background[0].WithAlpha(150));
while (_rewardPaint.MeasureText(_rewardQuantity) > rect.Width)
{
_rewardPaint.TextSize -= 1;
}
var shaper = new CustomSKShaper(_rewardPaint.Typeface);
c.DrawShapedText(shaper, _rewardQuantity, rect.Left + rect.Height + 25, rect.MidY + 20, _rewardPaint);
}
else
{
_rewardPaint.Color = SKColors.White;
_rewardPaint.Typeface = Utils.Typefaces.BundleNumber;
c.DrawText("No Reward", new SKPoint(rect.Left, rect.MidY + 20), _rewardPaint);
}
}
public void DrawSeasonWin(SKCanvas c, int size)
{
if (!HasReward()) return;
c.DrawBitmap((_theReward.Preview ?? _theReward.DefaultPreview).Resize(size), new SKPoint(0, 0), _rewardPaint);
}
public void DrawSeason(SKCanvas c, int x, int y, int areaSize)
{
if (!HasReward()) return;
// area + icon
_rewardPaint.Color = SKColor.Parse("#0F5CAF");
c.DrawRect(new SKRect(x, y, x + areaSize, y + areaSize), _rewardPaint);
c.DrawBitmap(_theReward.Preview.Resize(areaSize), new SKPoint(x, y), _rewardPaint);
// rarity color
_rewardPaint.Color = _theReward.Background[0];
var pathBottom = new SKPath {FillType = SKPathFillType.EvenOdd};
pathBottom.MoveTo(x, y + areaSize);
pathBottom.LineTo(x, y + areaSize - areaSize / 25 * 2.5f);
pathBottom.LineTo(x + areaSize, y + areaSize - areaSize / 25 * 4.5f);
pathBottom.LineTo(x + areaSize, y + areaSize);
pathBottom.Close();
c.DrawPath(pathBottom, _rewardPaint);
}
private void GetReward(string trigger)
{
switch (trigger.ToLower())
{
// case "athenabattlestar":
// _theReward = new BaseIcon(null, EIconStyle.Default);
// _theReward.Border[0] = SKColor.Parse("FFDB67");
// _theReward.Background[0] = SKColor.Parse("8F4A20");
// _theReward.Preview = Utils.GetBitmap("FortniteGame/Content/Athena/UI/Frontend/Art/T_UI_BP_BattleStar_L.T_UI_BP_BattleStar_L");
// break;
// case "athenaseasonalxp":
// _theReward = new BaseIcon(null, EIconStyle.Default);
// _theReward.Border[0] = SKColor.Parse("E6FDB1");
// _theReward.Background[0] = SKColor.Parse("51830F");
// _theReward.Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Items/T-FNBR-XPUncommon-L.T-FNBR-XPUncommon-L");
// break;
// case "mtxgiveaway":
// _theReward = new BaseIcon(null, EIconStyle.Default);
// _theReward.Border[0] = SKColor.Parse("DCE6FF");
// _theReward.Background[0] = SKColor.Parse("64A0AF");
// _theReward.Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Items/T-Items-MTX.T-Items-MTX");
// break;
default:
{
var path = Utils.GetFullPath($"FortniteGame/(?:Content/Athena|Content/Items|Plugins/GameFeatures)/.*?/{trigger}.uasset"); // path has no objectname and its needed so we push the trigger again as the objectname
if (!string.IsNullOrWhiteSpace(path) && Utils.TryLoadObject(path.Replace("uasset", trigger), out UObject d))
{
_theReward = new BaseIcon(d, EIconStyle.Default);
_theReward.ParseForReward(false);
_theReward.Border[0] = SKColors.White;
_rewardQuantity = $"{_theReward.DisplayName} ({_rewardQuantity})";
}
break;
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,47 @@
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.UObject;
using SkiaSharp;
namespace FModel.Creator.Bases.SB;
public class BaseDivision : UCreator
{
public BaseDivision(UObject uObject, EIconStyle style) : base(uObject, style)
{
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FPackageIndex icon, "Icon", "IconNoTier"))
{
Preview = Utils.GetBitmap(icon);
}
if (Object.TryGetValue(out FLinearColor lightColor, "UILightColor") &&
Object.TryGetValue(out FLinearColor mediumColor, "UIMediumColor") &&
Object.TryGetValue(out FLinearColor darkColor, "UIDarkColor") &&
Object.TryGetValue(out FLinearColor cardColor, "UICardColor"))
{
Background = new[] { SKColor.Parse(lightColor.Hex), SKColor.Parse(cardColor.Hex) };
Border = new[] { SKColor.Parse(mediumColor.Hex), SKColor.Parse(darkColor.Hex) };
}
if (Object.TryGetValue(out FText displayName, "DisplayName"))
DisplayName = displayName.Text;
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
DrawBackground(c);
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
return new[] { ret };
}
}

View File

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

View File

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

View File

@ -0,0 +1,97 @@
using System;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Engine;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Creator.Bases.FN;
using SkiaSharp;
namespace FModel.Creator.Bases.SB;
public class BaseSpellIcon : BaseIcon
{
private SKBitmap _seriesBackground2;
private readonly SKPaint _overlayPaint = new()
{
FilterQuality = SKFilterQuality.High,
IsAntialias = true,
Color = SKColors.Transparent.WithAlpha(75)
};
public BaseSpellIcon(UObject uObject, EIconStyle style) : base(uObject, style)
{
Background = new[] { SKColor.Parse("FFFFFF"), SKColor.Parse("636363") };
Border = new[] { SKColor.Parse("D0D0D0"), SKColor.Parse("FFFFFF") };
Width = Object.ExportType.StartsWith("GCosmeticCard") ? 1536 : 512;
Height = Object.ExportType.StartsWith("GCosmeticCard") ? 450 : 512;
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FName rarity, "Rarity"))
GetRarity(rarity);
if (Object.TryGetValue(out FSoftObjectPath preview, "IconTexture", "OfferTexture", "PortraitTexture"))
Preview = Utils.GetBitmap(preview);
else if (Object.TryGetValue(out FPackageIndex icon, "IconTexture", "OfferTexture", "PortraitTexture"))
Preview = Utils.GetBitmap(icon);
if (Object.TryGetValue(out FText displayName, "DisplayName", "Title", "Name"))
DisplayName = displayName.Text;
if (Object.TryGetValue(out FText description, "Description"))
Description = description.Text;
SeriesBackground = Utils.GetBitmap("g3/Content/UI/Textures/assets/HUDAccentFillBox.HUDAccentFillBox");
_seriesBackground2 = Utils.GetBitmap("g3/Content/UI/Textures/assets/store/ItemBGStatic_UIT.ItemBGStatic_UIT");
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
DrawBackgrounds(c);
DrawBackground(c);
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
DrawDescription(c);
return new[] { ret };
}
private void DrawBackgrounds(SKCanvas c)
{
if (SeriesBackground != null)
c.DrawBitmap(SeriesBackground, new SKRect(0, 0, Width, Height), ImagePaint);
if (_seriesBackground2 != null)
c.DrawBitmap(_seriesBackground2, new SKRect(0, 0, Width, Height), _overlayPaint);
var x = Margin * (int) 2.5;
const int radi = 15;
c.DrawCircle(x + radi, x + radi, radi, new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Shader = SKShader.CreateRadialGradient(
new SKPoint(radi, radi), radi * 2 / 5 * 4,
Background, SKShaderTileMode.Clamp)
});
}
private void GetRarity(FName n)
{
if (!Utils.TryLoadObject("g3/Content/UI/UIKit/DT_RarityColors.DT_RarityColors", out UDataTable rarity)) return;
if (rarity.TryGetDataTableRow(n.Text["EXRarity::".Length..], StringComparison.Ordinal, out var row))
{
if (row.TryGetValue(out FLinearColor[] colors, "Colors"))
{
Background = new[] { SKColor.Parse(colors[0].Hex), SKColor.Parse(colors[2].Hex) };
Border = new[] { SKColor.Parse(colors[1].Hex), SKColor.Parse(colors[0].Hex) };
}
}
}
}

View File

@ -0,0 +1,228 @@
using System;
using System.Windows;
using CUE4Parse.UE4.Assets.Exports;
using FModel.Creator.Bases.FN;
using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases;
public abstract class UCreator
{
protected UObject Object { get; }
protected EIconStyle Style { get; }
public SKBitmap DefaultPreview { get; set; }
public SKBitmap Preview { get; set; }
public SKColor[] Background { get; protected set; }
public SKColor[] Border { get; protected set; }
public string DisplayName { get; protected set; }
public string Description { get; protected set; }
public int Margin { get; protected set; }
public int Width { get; protected set; }
public int Height { get; protected set; }
public abstract void ParseForInfo();
public abstract SKBitmap[] Draw();
protected UCreator(UObject uObject, EIconStyle style)
{
DefaultPreview = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_Placeholder_Item_Image.png"))?.Stream);
Background = new[] { SKColor.Parse("5BFD00"), SKColor.Parse("003700") };
Border = new[] { SKColor.Parse("1E8500"), SKColor.Parse("5BFD00") };
DisplayName = string.Empty;
Description = string.Empty;
Width = 512;
Height = 512;
Margin = 2;
Object = uObject;
Style = style;
}
private const int _STARTER_TEXT_POSITION = 380, _NAME_TEXT_SIZE = 45, _BOTTOM_TEXT_SIZE = 15;
protected readonly SKPaint DisplayNamePaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Typeface = Utils.Typefaces.DisplayName, TextSize = _NAME_TEXT_SIZE,
Color = SKColors.White, TextAlign = SKTextAlign.Center
};
protected readonly SKPaint DescriptionPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Typeface = Utils.Typefaces.Description, TextSize = 13,
Color = SKColors.White
};
protected readonly SKPaint ImagePaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High
};
private readonly SKPaint _textBackgroundPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = new SKColor(0, 0, 50, 75)
};
private readonly SKPaint _shortDescriptionPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Color = SKColors.White
};
public void DrawBackground(SKCanvas c)
{
// reverse doesn't affect basic rarities
if (Background[0] == Background[1]) Background[0] = Border[0];
Background[0].ToHsl(out _, out _, out var l1);
Background[1].ToHsl(out _, out _, out var l2);
var reverse = l1 > l2;
// border
c.DrawRect(new SKRect(0, 0, Width, Height),
new SKPaint
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Shader = SKShader.CreateLinearGradient(
new SKPoint(Width / 2, Height), new SKPoint(Width, Height / 4), Border, SKShaderTileMode.Clamp)
});
if (this is BaseIcon { SeriesBackground: { } } baseIcon)
c.DrawBitmap(baseIcon.SeriesBackground, new SKRect(baseIcon.Margin, baseIcon.Margin, baseIcon.Width - baseIcon.Margin,
baseIcon.Height - baseIcon.Margin), ImagePaint);
else
{
switch (Style)
{
case EIconStyle.Flat:
{
c.DrawRect(new SKRect(Margin, Margin, Width - Margin, Height - Margin),
new SKPaint
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, Height), new SKPoint(Width, Height / 4),
new[] { Background[reverse ? 0 : 1].WithAlpha(150), Border[0] }, SKShaderTileMode.Clamp)
});
if (string.IsNullOrEmpty(DisplayName) && string.IsNullOrEmpty(Description)) return;
var pathTop = new SKPath { FillType = SKPathFillType.EvenOdd };
pathTop.MoveTo(Margin, Margin);
pathTop.LineTo(Margin + Width / 17 * 10, Margin);
pathTop.LineTo(Margin, Margin + Height / 17);
pathTop.Close();
c.DrawPath(pathTop, new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Color = Background[1].WithAlpha(75)
});
break;
}
default:
{
c.DrawRect(new SKRect(Margin, Margin, Width - Margin, Height - Margin),
new SKPaint
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, Height / 2), Width / 5 * 4,
new[] { Background[reverse ? 0 : 1], Background[reverse ? 1 : 0] },
SKShaderTileMode.Clamp)
});
break;
}
}
}
}
protected void DrawPreview(SKCanvas c)
=> c.DrawBitmap(Preview ?? DefaultPreview, new SKRect(Margin, Margin, Width - Margin, Height - Margin), ImagePaint);
protected void DrawTextBackground(SKCanvas c)
{
if (string.IsNullOrEmpty(DisplayName) && string.IsNullOrEmpty(Description)) return;
switch (Style)
{
case EIconStyle.Flat:
{
var pathBottom = new SKPath { FillType = SKPathFillType.EvenOdd };
pathBottom.MoveTo(Margin, Height - Margin);
pathBottom.LineTo(Margin, Height - Margin - Height / 17 * 2.5f);
pathBottom.LineTo(Width - Margin, Height - Margin - Height / 17 * 4.5f);
pathBottom.LineTo(Width - Margin, Height - Margin);
pathBottom.Close();
c.DrawPath(pathBottom, _textBackgroundPaint);
break;
}
default:
c.DrawRect(new SKRect(Margin, _STARTER_TEXT_POSITION, Width - Margin, Height - Margin), _textBackgroundPaint);
break;
}
}
protected void DrawDisplayName(SKCanvas c)
{
if (string.IsNullOrWhiteSpace(DisplayName)) return;
while (DisplayNamePaint.MeasureText(DisplayName) > Width - Margin * 2)
{
DisplayNamePaint.TextSize -= 1;
}
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
var shapedText = shaper.Shape(DisplayName, DisplayNamePaint);
var x = Width / 2f;
var y = _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE;
switch (Style)
{
case EIconStyle.Flat:
{
DisplayNamePaint.TextAlign = SKTextAlign.Right;
x = Width - Margin * 2;
break;
}
}
#if DEBUG
var halfWidth = shapedText.Width / 2f;
c.DrawLine(x - halfWidth, 0, x - halfWidth, Width, new SKPaint { Color = SKColors.Blue, IsStroke = true });
c.DrawLine(x + halfWidth, 0, x + halfWidth, Width, new SKPaint { Color = SKColors.Blue, IsStroke = true });
c.DrawRect(new SKRect(Margin, _STARTER_TEXT_POSITION, Width - Margin, y), new SKPaint { Color = SKColors.Blue, IsStroke = true });
#endif
c.DrawShapedText(shaper, DisplayName, x, y, DisplayNamePaint);
}
protected void DrawDescription(SKCanvas c)
{
if (string.IsNullOrWhiteSpace(Description)) return;
var maxLine = string.IsNullOrEmpty(DisplayName) ? 8 : 4;
var side = SKTextAlign.Center;
switch (Style)
{
case EIconStyle.Flat:
side = SKTextAlign.Right;
break;
}
Utils.DrawCenteredMultilineText(c, Description, maxLine, Width, Margin, side,
new SKRect(Margin, string.IsNullOrEmpty(DisplayName) ? _STARTER_TEXT_POSITION : _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE, Width - Margin, Height - _BOTTOM_TEXT_SIZE), DescriptionPaint);
}
protected void DrawToBottom(SKCanvas c, SKTextAlign side, string text)
{
if (string.IsNullOrEmpty(text)) return;
_shortDescriptionPaint.TextAlign = side;
_shortDescriptionPaint.TextSize = Utils.Typefaces.Bottom == null ? 15 : 13;
switch (side)
{
case SKTextAlign.Left:
_shortDescriptionPaint.Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.DisplayName;
var shaper = new CustomSKShaper(_shortDescriptionPaint.Typeface);
c.DrawShapedText(shaper, text, Margin * 2.5f, Width - Margin * 2.5f, _shortDescriptionPaint);
break;
case SKTextAlign.Right:
_shortDescriptionPaint.Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.Default;
c.DrawText(text, Width - Margin * 2.5f, Width - Margin * 2.5f, _shortDescriptionPaint);
break;
}
}
}

View File

@ -1,60 +0,0 @@
using FModel.Utils;
using PakReader.Parsers.PropertyTagData;
using System;
namespace FModel.Creator.Bundles
{
public class CompletionReward
{
private const string _TRIGGER1 = "<text color=\"FFF\" case=\"upper\" fontface=\"black\">";
private const string _TRIGGER2 = "</>";
public string CompletionText;
public Reward Reward;
public CompletionReward(IntProperty completionCount)
{
string all = Localizations.GetLocalization("AthenaChallengeDetailsEntry", "CompletionRewardFormat_All", "Complete <text color=\"FFF\" case=\"upper\" fontface=\"black\">all {0} challenges</> to earn the reward item");
string allFormated = ReformatString(all, completionCount.Value.ToString(), true);
string any = Localizations.GetLocalization("AthenaChallengeDetailsEntry", "CompletionRewardFormat", "Complete <text color=\"FFF\" case=\"upper\" fontface=\"black\">any {0} challenges</> to earn the reward item");
string anyFormated = ReformatString(any, completionCount.Value.ToString(), false);
CompletionText = completionCount.Value >= 0 ? anyFormated : allFormated;
Reward = null;
}
public CompletionReward(IntProperty completionCount, IntProperty quantity, SoftObjectProperty itemDefinition) : this(completionCount)
{
Reward = new Reward(quantity, itemDefinition);
}
public CompletionReward(IntProperty completionCount, IntProperty quantity, string reward) : this(completionCount)
{
Reward = new Reward(quantity, reward);
}
private string ReformatString(string s, string completionCount, bool isAll)
{
s = s.Replace("({0})", "{0}").Replace("{QuestNumber}", "<text color=\"FFF\" case=\"upper\" fontface=\"black\">{0}</>");
int index = s.IndexOf("|plural(", StringComparison.CurrentCultureIgnoreCase);
if (index > -1)
{
int i = s.Substring(index).IndexOf(')', StringComparison.CurrentCultureIgnoreCase);
s = s.Replace(s.Substring(index, i + 1), string.Empty).Replace("{0} {0}", "{0}");
}
int index1 = s.IndexOf(_TRIGGER1, StringComparison.CurrentCultureIgnoreCase);
if (index1 < 0) index1 = 0;
string partOne = s.Substring(0, index1);
string partTemp = s.Substring(index1 + _TRIGGER1.Length);
int index2 = partTemp.IndexOf(_TRIGGER2, StringComparison.CurrentCultureIgnoreCase);
if (index2 < 0) index2 = 0;
string partUpper = partTemp.Substring(0, index2).ToUpper().Replace("{0}", isAll ? string.Empty : completionCount);
string partTwo = partTemp.Substring(index2 + _TRIGGER2.Length);
return string.Format("{0}{1}{2}", partOne, partUpper, partTwo).Replace(" ", " ").Replace(" ,", ",");
}
}
}

View File

@ -1,106 +0,0 @@
using PakReader.Parsers.Class;
using PakReader.Parsers.Objects;
using PakReader.Parsers.PropertyTagData;
using SkiaSharp;
using System;
using System.IO;
namespace FModel.Creator.Bundles
{
public class Header
{
public SKColor PrimaryColor;
public SKColor SecondaryColor;
public SKColor AccentColor;
public SKBitmap DisplayImage; // 256x256
public SKBitmap CustomBackground; // 1024x256
private readonly Random _random = new Random(Environment.TickCount);
private readonly string[] _randomColors = new string[255]
{
"F44336", "FFEBEE", "FFCDD2", "EF9A9A", "E57373", "EF5350", "E53935", "D32F2F", "C62828", "B71C1C",
"FF8A80", "FF5252", "FF1744", "D50000", "FCE4EC", "F8BBD0", "F48FB1", "F06292", "EC407A", "E91E63",
"D81B60", "C2185B", "AD1457", "880E4F", "FF80AB", "FF4081", "F50057", "C51162", "F3E5F5", "E1BEE7",
"CE93D8", "BA68C8", "AB47BC", "9C27B0", "8E24AA", "7B1FA2", "6A1B9A", "4A148C", "EA80FC", "E040FB",
"D500F9", "AA00FF", "EDE7F6", "D1C4E9", "B39DDB", "9575CD", "7E57C2", "673AB7", "5E35B1", "512DA8",
"4527A0", "311B92", "B388FF", "7C4DFF", "651FFF", "6200EA", "E8EAF6", "C5CAE9", "9FA8DA", "7986CB",
"5C6BC0", "3F51B5", "3949AB", "303F9F", "283593", "1A237E", "8C9EFF", "536DFE", "3D5AFE", "304FFE",
"E3F2FD", "BBDEFB", "90CAF9", "64B5F6", "42A5F5", "2196F3", "1E88E5", "1976D2", "1565C0", "0D47A1",
"82B1FF", "448AFF", "2979FF", "2962FF", "E1F5FE", "B3E5FC", "81D4FA", "4FC3F7", "29B6F6", "03A9F4",
"039BE5", "0288D1", "0277BD", "01579B", "80D8FF", "40C4FF", "00B0FF", "0091EA", "E0F7FA", "B2EBF2",
"80DEEA", "4DD0E1", "26C6DA", "00BCD4", "00ACC1", "0097A7", "00838F", "006064", "84FFFF", "18FFFF",
"00E5FF", "00B8D4", "E0F2F1", "B2DFDB", "80CBC4", "4DB6AC", "26A69A", "009688", "00897B", "00796B",
"00695C", "004D40", "A7FFEB", "64FFDA", "1DE9B6", "00BFA5", "E8F5E9", "C8E6C9", "A5D6A7", "81C784",
"66BB6A", "4CAF50", "43A047", "388E3C", "2E7D32", "1B5E20", "B9F6CA", "69F0AE", "00E676", "00C853",
"F1F8E9", "DCEDC8", "C5E1A5", "AED581", "9CCC65", "8BC34A", "7CB342", "689F38", "558B2F", "33691E",
"CCFF90", "B2FF59", "76FF03", "64DD17", "F9FBE7", "F0F4C3", "E6EE9C", "DCE775", "D4E157", "CDDC39",
"C0CA33", "AFB42B", "9E9D24", "827717", "F4FF81", "EEFF41", "C6FF00", "AEEA00", "FFFDE7", "FFF9C4",
"FFF59D", "FFF176", "FFEE58", "FFEB3B", "FDD835", "FBC02D", "F9A825", "F57F17", "FFFF8D", "FFFF00",
"FFEA00", "FFD600", "FFF8E1", "FFECB3", "FFE082", "FFD54F", "FFCA28", "FFC107", "FFB300", "FFA000",
"FF8F00", "FF6F00", "FFE57F", "FFD740", "FFC400", "FFAB00", "FFF3E0", "FFE0B2", "FFCC80", "FFB74D",
"FFA726", "FF9800", "FB8C00", "F57C00", "EF6C00", "E65100", "FFD180", "FFAB40", "FF9100", "FF6D00",
"FBE9E7", "FFCCBC", "FFAB91", "FF8A65", "FF7043", "FF5722", "F4511E", "E64A19", "D84315", "BF360C",
"FF9E80", "FF6E40", "FF3D00", "DD2C00", "EFEBE9", "D7CCC8", "BCAAA4", "A1887F", "8D6E63", "795548",
"6D4C41", "5D4037", "4E342E", "3E2723", "FAFAFA", "F5F5F5", "EEEEEE", "E0E0E0", "BDBDBD", "9E9E9E",
"757575", "616161", "424242", "212121", "ECEFF1", "CFD8DC", "B0BEC5", "90A4AE", "78909C", "607D8B",
"546E7A", "455A64", "37474F", "263238", "000000",
};
public Header()
{
if (Properties.Settings.Default.UseChallengeBanner)
{
SKColor mainColor = SKColor.Parse(Properties.Settings.Default.ChallengeBannerPrimaryColor);
mainColor.ToHsl(out float h, out float s, out float l);
float i = l + 20.0F > 100.0F ? 100.0F - l : 20.0F;
PrimaryColor = mainColor;
SecondaryColor = SKColor.Parse(Properties.Settings.Default.ChallengeBannerSecondaryColor);
AccentColor = SKColor.FromHsl(h += i, s, l);
DisplayImage = null;
if (!string.IsNullOrEmpty(Properties.Settings.Default.ChallengeBannerPath))
CustomBackground = SKBitmap.Decode(new FileInfo(Properties.Settings.Default.ChallengeBannerPath).Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
else CustomBackground = null;
}
else
{
SKColor mainColor = SKColor.Parse(_randomColors[_random.Next(0, 255)]);
mainColor.ToHsl(out float h, out float s, out float l);
while (l > 75 || l < 10)
{
mainColor = SKColor.Parse(_randomColors[_random.Next(0, 255)]);
mainColor.ToHsl(out float _, out float _, out l);
}
float i = l + 20.0F > 100.0F ? 100.0F - l : 20.0F;
PrimaryColor = mainColor;
SecondaryColor = SKColor.FromHsl(h, s, l += i);
AccentColor = SKColor.FromHsl(h += i, s, l);
DisplayImage = null;
CustomBackground = null;
}
}
public Header(StructProperty displayStyle, string assetFolder) : this()
{
if (displayStyle.Value is UObject o)
{
if (!Properties.Settings.Default.UseChallengeBanner && o.TryGetValue(out var c1, "PrimaryColor") && c1 is StructProperty s1 && s1.Value is FLinearColor primaryColor)
PrimaryColor = SKColor.Parse(primaryColor.Hex);
if (!Properties.Settings.Default.UseChallengeBanner && o.TryGetValue(out var c2, "SecondaryColor") && c2 is StructProperty s2 && s2.Value is FLinearColor secondaryColor)
SecondaryColor = SKColor.Parse(secondaryColor.Hex);
if (!Properties.Settings.Default.UseChallengeBanner && o.TryGetValue("AccentColor", out var c3) && c3 is StructProperty s3 && s3.Value is FLinearColor accentColor)
{
AccentColor = SKColor.Parse(accentColor.Hex);
if (SecondaryColor.Red + SecondaryColor.Green + SecondaryColor.Blue <= 75 || assetFolder.Equals("LTM", StringComparison.CurrentCultureIgnoreCase)) // if secondary is too dark
SecondaryColor = AccentColor; // use accent and pray for accent to be ligher
}
if (o.TryGetValue("DisplayImage", out var i) && i is SoftObjectProperty displayImage)
DisplayImage = Utils.GetSoftObjectTexture(displayImage);
if (CustomBackground == null && o.TryGetValue("CustomBackground", out var b) && b is SoftObjectProperty customBackground)
CustomBackground = Utils.GetSoftObjectTexture(customBackground);
}
}
}
}

View File

@ -1,99 +0,0 @@
using FModel.Creator.Bases;
using FModel.Creator.Texts;
using SkiaSharp;
using System;
namespace FModel.Creator.Bundles
{
static class HeaderStyle
{
public static void DrawHeaderPaint(SKCanvas c, BaseBundle icon)
{
c.DrawRect(new SKRect(0, 0, icon.Width, icon.HeaderHeight), new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Color = icon.DisplayStyle.PrimaryColor
});
if (icon.DisplayStyle.CustomBackground != null && icon.DisplayStyle.CustomBackground.Height != icon.DisplayStyle.CustomBackground.Width)
{
icon.IsDisplayNameShifted = false;
var bgPaint = new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High, BlendMode = SKBlendMode.Screen };
if (Properties.Settings.Default.UseChallengeBanner) bgPaint.Color = SKColors.Transparent.WithAlpha((byte)Properties.Settings.Default.ChallengeBannerOpacity);
c.DrawBitmap(icon.DisplayStyle.CustomBackground, new SKRect(0, 0, 1024, 256), bgPaint);
}
else if (icon.DisplayStyle.DisplayImage != null)
{
icon.IsDisplayNameShifted = true;
if (icon.DisplayStyle.CustomBackground != null && icon.DisplayStyle.CustomBackground.Height == icon.DisplayStyle.CustomBackground.Width)
c.DrawBitmap(icon.DisplayStyle.CustomBackground, new SKRect(0, 0, icon.HeaderHeight, icon.HeaderHeight),
new SKPaint {
IsAntialias = true, FilterQuality = SKFilterQuality.High, BlendMode = SKBlendMode.Screen,
ImageFilter = SKImageFilter.CreateDropShadow(2.5F, 0, 20, 0, icon.DisplayStyle.SecondaryColor.WithAlpha(25))
});
c.DrawBitmap(icon.DisplayStyle.DisplayImage, new SKRect(0, 0, icon.HeaderHeight, icon.HeaderHeight),
new SKPaint {
IsAntialias = true, FilterQuality = SKFilterQuality.High,
ImageFilter = SKImageFilter.CreateDropShadow(-2.5F, 0, 20, 0, icon.DisplayStyle.SecondaryColor.WithAlpha(50))
});
}
SKPath pathTop = new SKPath { FillType = SKPathFillType.EvenOdd };
pathTop.MoveTo(0, icon.HeaderHeight);
pathTop.LineTo(icon.Width, icon.HeaderHeight);
pathTop.LineTo(icon.Width, icon.HeaderHeight - 19);
pathTop.LineTo(icon.Width / 2 + 7, icon.HeaderHeight - 23);
pathTop.LineTo(icon.Width / 2 + 13, icon.HeaderHeight - 7);
pathTop.LineTo(0, icon.HeaderHeight - 19);
pathTop.Close();
c.DrawPath(pathTop, new SKPaint {
IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = icon.DisplayStyle.SecondaryColor,
ImageFilter = SKImageFilter.CreateDropShadow(-5, -5, 0, 0, icon.DisplayStyle.AccentColor.WithAlpha(75))
});
c.DrawRect(new SKRect(0, icon.HeaderHeight, icon.Width, icon.HeaderHeight + icon.AdditionalSize), new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Color = icon.DisplayStyle.PrimaryColor.WithAlpha(200) // default background is black, so i'm kinda lowering the brightness here and that's what i want
});
}
public static void DrawHeaderText(SKCanvas c, BaseBundle icon)
{
using SKPaint paint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Typeface = Text.TypeFaces.BundleDisplayNameTypeface,
TextSize = 50,
Color = SKColors.White,
TextAlign = SKTextAlign.Left,
};
string text = icon.DisplayName.ToUpper();
int x = icon.IsDisplayNameShifted ? 300 : 50;
while (paint.MeasureText(text) > (icon.Width - x))
{
paint.TextSize -= 2;
}
c.DrawText(text, x, 155, paint);
paint.Color = SKColors.White.WithAlpha(150);
paint.TextAlign = SKTextAlign.Right;
paint.TextSize = 23;
c.DrawText(icon.Watermark
.Replace("{BundleName}", text)
.Replace("{Date}", DateTime.Now.ToString("dd/MM/yyyy")),
icon.Width - 25, icon.HeaderHeight - 40, paint);
paint.Typeface = Text.TypeFaces.BundleDefaultTypeface;
paint.Color = icon.DisplayStyle.SecondaryColor;
paint.TextAlign = SKTextAlign.Left;
paint.TextSize = 30;
c.DrawText(icon.FolderName.ToUpper(), x, 95, paint);
}
}
}

View File

@ -1,94 +0,0 @@
using FModel.Creator.Texts;
using PakReader.Pak;
using PakReader.Parsers.Class;
using PakReader.Parsers.PropertyTagData;
namespace FModel.Creator.Bundles
{
public class Quest
{
public string Description;
public int Count;
public Reward Reward;
public Quest()
{
Description = "";
Count = 0;
Reward = null;
}
public Quest(UObject obj) : this()
{
if (obj.TryGetValue("Description", out var d) && d is TextProperty description)
Description = Text.GetTextPropertyBase(description);
if (obj.TryGetValue("ObjectiveCompletionCount", out var o) && o is IntProperty objectiveCompletionCount)
Count = objectiveCompletionCount.Value;
if (obj.TryGetValue("Objectives", out var v1) && v1 is ArrayProperty a1 &&
a1.Value.Length > 0 && a1.Value[0] is StructProperty s && s.Value is UObject objectives)
{
if (string.IsNullOrEmpty(Description) && objectives.TryGetValue("Description", out var od) && od is TextProperty objectivesDescription)
Description = Text.GetTextPropertyBase(objectivesDescription);
if (Count == 0 && objectives.TryGetValue("Count", out var c) && c is IntProperty count)
Count = count.Value;
}
if (obj.TryGetValue("RewardsTable", out var v4) && v4 is ObjectProperty rewardsTable)
{
PakPackage p = Utils.GetPropertyPakPackage(rewardsTable.Value.Resource.OuterIndex.Resource.ObjectName.String);
if (p.HasExport() && !p.Equals(default))
{
var u = p.GetExport<UDataTable>();
if (u != null && u.TryGetValue("Default", out var i) && i is UObject r &&
r.TryGetValue("TemplateId", out var i1) && i1 is NameProperty templateId &&
r.TryGetValue("Quantity", out var i2) && i2 is IntProperty quantity)
{
Reward = new Reward(quantity, templateId);
}
}
}
if (Reward == null && obj.TryGetValue("Rewards", out var v2) && v2 is ArrayProperty rewards)
{
foreach (StructProperty reward in rewards.Value)
{
if (reward.Value is UObject r1 &&
r1.TryGetValue("ItemPrimaryAssetId", out var i1) && i1 is StructProperty itemPrimaryAssetId &&
r1.TryGetValue("Quantity", out var i2) && i2 is IntProperty quantity)
{
if (itemPrimaryAssetId.Value is UObject r2 &&
r2.TryGetValue("PrimaryAssetType", out var t1) && t1 is StructProperty primaryAssetType &&
r2.TryGetValue("PrimaryAssetName", out var t2) && t2 is NameProperty primaryAssetName)
{
if (primaryAssetType.Value is UObject r3 && r3.TryGetValue("Name", out var k) && k is NameProperty name)
{
if (!name.Value.String.Equals("Quest") && !name.Value.String.Equals("Token") &&
!name.Value.String.Equals("ChallengeBundle") && !name.Value.String.Equals("GiftBox"))
{
Reward = new Reward(quantity, primaryAssetName);
break;
}
}
}
}
}
}
if (Reward == null && obj.TryGetValue("HiddenRewards", out var v3) && v3 is ArrayProperty hiddenRewards)
{
foreach (StructProperty reward in hiddenRewards.Value)
{
if (reward.Value is UObject r1 &&
r1.TryGetValue("TemplateId", out var i1) && i1 is NameProperty templateId &&
r1.TryGetValue("Quantity", out var i2) && i2 is IntProperty quantity)
{
Reward = new Reward(quantity, templateId);
break;
}
}
}
}
}
}

View File

@ -1,175 +0,0 @@
using FModel.Creator.Bases;
using FModel.Creator.Texts;
using SkiaSharp;
namespace FModel.Creator.Bundles
{
static class QuestStyle
{
public static void DrawQuests(SKCanvas c, BaseBundle icon)
{
using SKPaint paint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
TextSize = 27,
Color = SKColors.White,
TextAlign = SKTextAlign.Left,
Typeface = Text.TypeFaces.BundleDisplayNameTypeface
};
int y = icon.HeaderHeight + 50;
foreach (Quest q in icon.Quests)
{
DrawQuestBackground(c, icon, y, true);
paint.TextSize = 27;
paint.ImageFilter = null;
paint.Color = SKColors.White;
paint.TextAlign = SKTextAlign.Left;
paint.Typeface = Text.TypeFaces.BundleDisplayNameTypeface;
while (paint.MeasureText(q.Description) > icon.Width - 65 - 165)
{
paint.TextSize -= 1;
}
c.DrawText(q.Description, new SKPoint(65, y + paint.TextSize + 11), paint);
paint.TextSize = 16;
paint.Color = SKColors.White.WithAlpha(200);
paint.Typeface = Text.TypeFaces.BundleDefaultTypeface;
c.DrawText(q.Count.ToString(), new SKPoint(93 + icon.Width - (175 * 3), y + 60), paint);
if (q.Reward?.RewardIcon != null)
{
if (q.Reward.IsCountShifted)
{
int l = q.Reward.RewardQuantity.ToString().Length;
paint.TextSize = l >= 5 ? 30 : 35;
paint.TextAlign = SKTextAlign.Right;
paint.Color = SKColor.Parse(q.Reward.RewardFillColor);
paint.ImageFilter = SKImageFilter.CreateDropShadow(0, 0, 5, 5, SKColor.Parse(q.Reward.RewardBorderColor).WithAlpha(200));
c.DrawText(q.Reward.RewardQuantity.ToString(), new SKPoint(icon.Width - 85, y + 47.5F), paint);
c.DrawBitmap(q.Reward.RewardIcon, new SKPoint(icon.Width - 30 - q.Reward.RewardIcon.Width, y + 12.5F),
new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High });
}
else
c.DrawBitmap(q.Reward.RewardIcon, new SKPoint(icon.Width - 125, y + 5),
new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High });
}
y += 95;
}
}
public static void DrawCompletionRewards(SKCanvas c, BaseBundle icon)
{
using SKPaint paint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
TextSize = 35,
Color = SKColors.White,
TextAlign = SKTextAlign.Left,
Typeface = Text.TypeFaces.BundleDisplayNameTypeface
};
int y = icon.HeaderHeight + (50 * 2) + (95 * icon.Quests.Count);
foreach (CompletionReward r in icon.CompletionRewards)
{
DrawQuestBackground(c, icon, y, false);
paint.TextSize = 35;
paint.ImageFilter = null;
paint.Color = SKColors.White;
paint.TextAlign = SKTextAlign.Left;
paint.Typeface = Text.TypeFaces.BundleDisplayNameTypeface;
while (paint.MeasureText(r.CompletionText) > icon.Width - 65 - 165)
{
paint.TextSize -= 1;
}
c.DrawText(r.CompletionText, new SKPoint(65, y + paint.TextSize + 15), paint);
if (r.Reward?.RewardIcon != null)
{
if (r.Reward.IsCountShifted)
{
int l = r.Reward.RewardQuantity.ToString().Length;
paint.TextSize = l >= 5 ? 30 : 35;
paint.TextAlign = SKTextAlign.Right;
paint.Color = SKColor.Parse(r.Reward.RewardFillColor);
paint.Typeface = Text.TypeFaces.BundleDefaultTypeface;
paint.ImageFilter = SKImageFilter.CreateDropShadow(0, 0, 5, 5, SKColor.Parse(r.Reward.RewardBorderColor).WithAlpha(200));
c.DrawText(r.Reward.RewardQuantity.ToString(), new SKPoint(icon.Width - 85, y + 47.5F), paint);
c.DrawBitmap(r.Reward.RewardIcon, new SKPoint(icon.Width - 30 - r.Reward.RewardIcon.Width, y + 12.5F),
new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High });
}
else
c.DrawBitmap(r.Reward.RewardIcon, new SKPoint(icon.Width - 125, y + 5),
new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High });
}
y += 95;
}
}
private static void DrawQuestBackground(SKCanvas c, BaseBundle icon, int y, bool hasSlider)
{
SKColor baseColor = icon.DisplayStyle.PrimaryColor;
using SKPaint paint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Color = baseColor
};
using SKPath secondaryRect = new SKPath
{
FillType = SKPathFillType.EvenOdd
};
using SKPath selector = new SKPath
{
FillType = SKPathFillType.EvenOdd
};
using SKPath slider = new SKPath
{
FillType = SKPathFillType.EvenOdd
};
c.DrawRect(new SKRect(25, y, icon.Width - 25, y + 75), paint);
baseColor.ToHsl(out float h, out float s, out float l);
baseColor = SKColor.FromHsl(h, s, l + 5);
paint.Color = baseColor;
secondaryRect.MoveTo(32, y + 5);
secondaryRect.LineTo(icon.Width - 155, y + 4);
secondaryRect.LineTo(icon.Width - 175, y + 68);
secondaryRect.LineTo(29, y + 71);
secondaryRect.Close();
c.DrawPath(secondaryRect, paint);
paint.Color = icon.DisplayStyle.SecondaryColor;
selector.MoveTo(41, y + 38);
selector.LineTo(48, y + 34);
selector.LineTo(52, y + 39);
selector.LineTo(46, y + 44);
selector.Close();
c.DrawPath(selector, paint);
if (hasSlider)
{
slider.MoveTo(65, y + 53);
slider.LineTo(65 + icon.Width - (175 * 3), y + 53);
slider.LineTo(65 + icon.Width - (175 * 3), y + 58);
slider.LineTo(65, y + 58);
slider.Close();
c.DrawPath(slider, paint);
paint.TextSize = 14;
paint.Color = SKColors.White;
paint.TextAlign = SKTextAlign.Left;
paint.Typeface = Text.TypeFaces.BundleDefaultTypeface;
c.DrawText("0 / ", new SKPoint(75 + icon.Width - (175 * 3), y + 59), paint);
}
}
}
}

View File

@ -1,135 +0,0 @@
using FModel.Creator.Bases;
using PakReader.Pak;
using PakReader.Parsers.Class;
using PakReader.Parsers.PropertyTagData;
using SkiaSharp;
using System;
namespace FModel.Creator.Bundles
{
public class Reward
{
public int RewardQuantity;
public SKBitmap RewardIcon;
public string RewardFillColor;
public string RewardBorderColor;
public bool IsCountShifted;
public Reward()
{
RewardQuantity = 0;
RewardIcon = null;
RewardFillColor = "";
RewardBorderColor = "";
IsCountShifted = false;
}
public Reward(IntProperty quantity, NameProperty primaryAssetName) : this(quantity, primaryAssetName.Value.String) { }
public Reward(IntProperty quantity, string assetName) : this()
{
RewardQuantity = quantity.Value;
if (assetName.Contains(':'))
{
string[] parts = assetName.Split(':');
if (parts[0].Equals("HomebaseBannerIcon", StringComparison.CurrentCultureIgnoreCase))
{
PakPackage p = Utils.GetPropertyPakPackage("/Game/Banners/BannerIcons");
if (p.HasExport() && !p.Equals(default))
{
var c = p.GetExport<UDataTable>();
if (c != null && c.TryGetCaseInsensitiveValue(parts[1], out var s) && s is UObject banner)
{
RewardIcon = new BaseIcon(banner, "BannerIcons.uasset", false).IconImage.Resize(64, 64);
}
}
}
else GetReward(parts[1]);
}
else GetReward(assetName);
}
public Reward(IntProperty quantity, SoftObjectProperty itemDefinition) : this()
{
RewardQuantity = quantity.Value;
PakPackage p = Utils.GetPropertyPakPackage(itemDefinition.Value.AssetPathName.String);
if (p.HasExport() && !p.Equals(default))
{
var d = p.GetExport<UObject>();
if (d != null)
{
int s1 = itemDefinition.Value.AssetPathName.String.LastIndexOf('/');
if (s1 < 0) s1 = 0;
int s2 = itemDefinition.Value.AssetPathName.String.LastIndexOf('.') - s1;
switch (itemDefinition.Value.AssetPathName.String)
{
case "/Game/Items/PersistentResources/AthenaBattleStar.AthenaBattleStar":
IsCountShifted = true;
RewardFillColor = "FFDB67";
RewardBorderColor = "8F4A20";
RewardIcon = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Items/T-FNBR-BattlePoints").Resize(48, 48);
break;
case "/Game/Items/PersistentResources/AthenaSeasonalXP.AthenaSeasonalXP":
IsCountShifted = true;
RewardFillColor = "E6FDB1";
RewardBorderColor = "51830F";
RewardIcon = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Items/T-FNBR-XPMedium").Resize(48, 48);
break;
case "/Game/Items/Currency/MtxGiveaway.MtxGiveaway":
IsCountShifted = true;
RewardFillColor = "DCE6FF";
RewardBorderColor = "64A0AF";
RewardIcon = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Items/T-Items-MTX").Resize(48, 48);
break;
default:
IsCountShifted = false;
RewardIcon = new BaseIcon(d, itemDefinition.Value.AssetPathName.String.Substring(s1, s2) + ".uasset", false).IconImage.Resize(64, 64);
break;
}
}
}
}
private void GetReward(string trigger)
{
switch (trigger.ToLower())
{
case "athenabattlestar":
IsCountShifted = true;
RewardFillColor = "FFDB67";
RewardBorderColor = "8F4A20";
RewardIcon = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Items/T-FNBR-BattlePoints").Resize(48, 48);
break;
case "athenaseasonalxp":
IsCountShifted = true;
RewardFillColor = "E6FDB1";
RewardBorderColor = "51830F";
RewardIcon = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Items/T-FNBR-XPMedium").Resize(48, 48);
break;
case "mtxgiveaway":
IsCountShifted = true;
RewardFillColor = "DCE6FF";
RewardBorderColor = "64A0AF";
RewardIcon = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Items/T-Items-MTX").Resize(48, 48);
break;
default:
{
string path = Utils.GetFullPath($"/FortniteGame/Content/Athena/.*?/{trigger}.*").Replace("FortniteGame/Content", "Game");
PakPackage p = Utils.GetPropertyPakPackage(path);
if (p.HasExport() && !p.Equals(default))
{
var d = p.GetExport<UObject>();
if (d != null)
{
int i = path.LastIndexOf('/');
IsCountShifted = false;
RewardIcon = new BaseIcon(d, path.Substring(i > 0 ? i : 0) + ".uasset", false).IconImage.Resize(64, 64);
}
}
break;
}
}
}
}
}

View File

@ -1,276 +0,0 @@
using FModel.Creator.Bases;
using FModel.Creator.Bundles;
using FModel.Creator.Icons;
using FModel.Creator.Rarities;
using FModel.Creator.Stats;
using FModel.Creator.Texts;
using FModel.ViewModels.ImageBox;
using PakReader.Parsers.Class;
using PakReader.Parsers.Objects;
using SkiaSharp;
using System.IO;
namespace FModel.Creator
{
static class Creator
{
/// <summary>
/// i don't cache images because i don't wanna store a lot of SKCanvas in the memory
/// </summary>
/// <returns>true if an icon has been drawn</returns>
public static bool TryDrawIcon(string assetPath, FName[] exportTypes, IUExport[] exports)
{
var d = new DirectoryInfo(assetPath);
string assetName = d.Name;
string assetFolder = d.Parent.Name;
if (Text.TypeFaces.NeedReload(false)) Text.TypeFaces = new Typefaces(); // when opening bundle creator settings without loading paks first
int index = Globals.Game.ActualGame == EGame.Valorant ? 1 : 0;
string exportType = exportTypes.Length > index ? exportTypes[index].String : string.Empty;
switch (exportType)
{
case "AthenaConsumableEmoteItemDefinition":
case "AthenaSkyDiveContrailItemDefinition":
case "AthenaLoadingScreenItemDefinition":
case "AthenaVictoryPoseItemDefinition":
case "AthenaPetCarrierItemDefinition":
case "AthenaMusicPackItemDefinition":
case "AthenaBattleBusItemDefinition":
case "AthenaCharacterItemDefinition":
case "AthenaBackpackItemDefinition":
case "AthenaPickaxeItemDefinition":
case "AthenaGadgetItemDefinition":
case "AthenaGliderItemDefinition":
case "AthenaDailyQuestDefinition":
case "AthenaSprayItemDefinition":
case "AthenaDanceItemDefinition":
case "AthenaEmojiItemDefinition":
case "AthenaItemWrapDefinition":
case "AthenaToyItemDefinition":
case "FortHeroType":
case "FortTokenType":
case "FortAbilityKit":
case "FortWorkerType":
case "RewardGraphToken":
case "FortBannerTokenType":
case "FortVariantTokenType":
case "FortFeatItemDefinition":
case "FortStatItemDefinition":
case "FortTrapItemDefinition":
case "FortAmmoItemDefinition":
case "FortQuestItemDefinition":
case "FortBadgeItemDefinition":
case "FortAwardItemDefinition":
case "FortGadgetItemDefinition":
case "FortPlaysetItemDefinition":
case "FortGiftBoxItemDefinition":
case "FortSpyTechItemDefinition":
case "FortAccoladeItemDefinition":
case "FortCardPackItemDefinition":
case "FortDefenderItemDefinition":
case "FortCurrencyItemDefinition":
case "FortResourceItemDefinition":
case "FortSchematicItemDefinition":
case "FortIngredientItemDefinition":
case "FortAccountBuffItemDefinition":
case "FortWeaponMeleeItemDefinition":
case "FortContextTrapItemDefinition":
case "FortPlayerPerksItemDefinition":
case "FortPlaysetPropItemDefinition":
case "FortHomebaseNodeItemDefinition":
case "FortWeaponRangedItemDefinition":
case "FortNeverPersistItemDefinition":
case "RadioContentSourceItemDefinition":
case "FortPlaysetGrenadeItemDefinition":
case "FortPersonalVehicleItemDefinition":
case "FortHardcoreModifierItemDefinition":
case "FortConsumableAccountItemDefinition":
case "FortConversionControlItemDefinition":
case "FortAccountBuffCreditItemDefinition":
case "FortPersistentResourceItemDefinition":
case "FortCampaignHeroLoadoutItemDefinition":
case "FortConditionalResourceItemDefinition":
case "FortChallengeBundleScheduleDefinition":
case "FortWeaponMeleeDualWieldItemDefinition":
case "FortDailyRewardScheduleTokenDefinition":
{
BaseIcon icon = new BaseIcon(exports[index], exportType, ref assetName);
int height = icon.Size + icon.AdditionalSize;
using (var ret = new SKBitmap(icon.Size, height, SKColorType.Rgba8888, SKAlphaType.Premul))
using (var c = new SKCanvas(ret))
{
if ((EIconDesign)Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground)
{
Rarity.DrawRarity(c, icon);
}
LargeSmallImage.DrawPreviewImage(c, icon);
if ((EIconDesign)Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground)
{
if ((EIconDesign)Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoText)
{
Text.DrawBackground(c, icon);
Text.DrawDisplayName(c, icon);
Text.DrawDescription(c, icon);
if ((EIconDesign)Properties.Settings.Default.AssetsIconDesign != EIconDesign.Mini)
{
if (!icon.ShortDescription.Equals(icon.DisplayName) && !icon.ShortDescription.Equals(icon.Description))
Text.DrawToBottom(c, icon, ETextSide.Left, icon.ShortDescription);
Text.DrawToBottom(c, icon, ETextSide.Right, icon.CosmeticSource);
}
}
UserFacingFlag.DrawUserFacingFlags(c, icon);
// has more things to show
if (height > icon.Size)
{
Statistics.DrawStats(c, icon);
}
}
Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512
ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
}
return true;
}
case "FortMtxOfferData":
{
BaseOffer icon = new BaseOffer(exports[index]);
using (var ret = new SKBitmap(icon.Size, icon.Size, SKColorType.Rgba8888, SKAlphaType.Premul))
using (var c = new SKCanvas(ret))
{
if ((EIconDesign)Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground)
{
icon.DrawBackground(c);
}
icon.DrawImage(c);
Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512
ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
}
return true;
}
case "FortItemSeriesDefinition":
{
BaseIcon icon = new BaseIcon();
using (var ret = new SKBitmap(icon.Size, icon.Size, SKColorType.Rgba8888, SKAlphaType.Opaque))
using (var c = new SKCanvas(ret))
{
Serie.GetRarity(icon, exports[index]);
Rarity.DrawRarity(c, icon);
Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512
ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
}
return true;
}
case "PlaylistUserOptionEnum":
case "PlaylistUserOptionBool":
case "PlaylistUserOptionString":
case "PlaylistUserOptionIntEnum":
case "PlaylistUserOptionIntRange":
case "PlaylistUserOptionColorEnum":
case "PlaylistUserOptionFloatEnum":
case "PlaylistUserOptionFloatRange":
case "PlaylistUserOptionPrimaryAsset":
case "PlaylistUserOptionCollisionProfileEnum":
{
BaseUserOption icon = new BaseUserOption(exports[index]);
using (var ret = new SKBitmap(icon.Width, icon.Height, SKColorType.Rgba8888, SKAlphaType.Opaque))
using (var c = new SKCanvas(ret))
{
icon.Draw(c);
Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512
ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
}
return true;
}
case "FortChallengeBundleItemDefinition":
{
BaseBundle icon = new BaseBundle(exports[index], assetFolder);
using (var ret = new SKBitmap(icon.Width, icon.HeaderHeight + icon.AdditionalSize, SKColorType.Rgba8888, SKAlphaType.Opaque))
using (var c = new SKCanvas(ret))
{
HeaderStyle.DrawHeaderPaint(c, icon);
HeaderStyle.DrawHeaderText(c, icon);
QuestStyle.DrawQuests(c, icon);
QuestStyle.DrawCompletionRewards(c, icon);
ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
}
return true;
}
case "FortItemAccessTokenType":
{
BaseItemAccess icon = new BaseItemAccess(exports[index]);
using (var ret = new SKBitmap(icon.Size, icon.Size, SKColorType.Rgba8888, SKAlphaType.Opaque))
using (var c = new SKCanvas(ret))
{
icon.Draw(c);
Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512
ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
}
return true;
}
case "MapUIData":
{
BaseMapUIData icon = new BaseMapUIData(exports[index]);
using (var ret = new SKBitmap(icon.Width, icon.Height, SKColorType.Rgba8888, SKAlphaType.Premul))
using (var c = new SKCanvas(ret))
{
icon.Draw(c);
ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
}
return true;
}
case "ArmorUIData":
case "SprayUIData":
case "ThemeUIData":
case "ContractUIData":
case "CurrencyUIData":
case "GameModeUIData":
case "CharacterUIData":
case "SprayLevelUIData":
case "EquippableUIData":
case "PlayerCardUIData":
case "Gun_UIData_Base_C":
case "CharacterRoleUIData":
case "EquippableSkinUIData":
case "EquippableCharmUIData":
case "EquippableSkinLevelUIData":
case "EquippableSkinChromaUIData":
case "EquippableCharmLevelUIData":
{
BaseUIData icon = new BaseUIData(exports, index);
using (var ret = new SKBitmap(icon.Width + icon.AdditionalWidth, icon.Height, SKColorType.Rgba8888, SKAlphaType.Premul))
using (var c = new SKCanvas(ret))
{
icon.Draw(c);
Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512
ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
}
return true;
}
//case "StreamedVideoDataAsset": // must find a way to automatically gets the right version in the url
// {
// if (Globals.Game.ActualGame == EGame.Valorant && exports[index].GetExport<StructProperty>("Uuid") is StructProperty s && s.Value is FGuid uuid)
// {
// Process.Start(new ProcessStartInfo
// {
// FileName = string.Format(
// "http://valorant.dyn.riotcdn.net/x/videos/release-01.05/{0}_default_universal.mp4",
// $"{uuid.A:x8}-{uuid.B >> 16:x4}-{uuid.B & 0xFFFF:x4}-{uuid.C >> 16:x4}-{uuid.C & 0xFFFF:x4}{uuid.D:x8}"),
// UseShellExecute = true
// });
// }
// return false;
// }
}
return false;
}
}
}

View File

@ -0,0 +1,256 @@
using System;
using System.Runtime.CompilerServices;
using CUE4Parse.UE4.Assets.Exports;
using FModel.Creator.Bases;
using FModel.Creator.Bases.BB;
using FModel.Creator.Bases.FN;
using FModel.Creator.Bases.MV;
using FModel.Creator.Bases.SB;
namespace FModel.Creator;
public class CreatorPackage : IDisposable
{
private UObject _object;
private EIconStyle _style;
public CreatorPackage(UObject uObject, EIconStyle style)
{
_object = uObject;
_style = style;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public UCreator ConstructCreator()
{
TryConstructCreator(out var creator);
return creator;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryConstructCreator(out UCreator creator)
{
switch (_object.ExportType)
{
// Fortnite
case "FortCreativeWeaponMeleeItemDefinition":
case "AthenaConsumableEmoteItemDefinition":
case "AthenaSkyDiveContrailItemDefinition":
case "AthenaLoadingScreenItemDefinition":
case "AthenaVictoryPoseItemDefinition":
case "AthenaPetCarrierItemDefinition":
case "AthenaMusicPackItemDefinition":
case "AthenaBattleBusItemDefinition":
case "AthenaCharacterItemDefinition":
case "AthenaMapMarkerItemDefinition":
case "AthenaBackpackItemDefinition":
case "AthenaPickaxeItemDefinition":
case "AthenaGadgetItemDefinition":
case "AthenaGliderItemDefinition":
case "AthenaSprayItemDefinition":
case "AthenaDanceItemDefinition":
case "AthenaEmojiItemDefinition":
case "AthenaItemWrapDefinition":
case "AthenaToyItemDefinition":
case "FortHeroType":
case "FortTokenType":
case "FortAbilityKit":
case "FortWorkerType":
case "RewardGraphToken":
case "JunoKnowledgeBundle":
case "FortBannerTokenType":
case "FortVariantTokenType":
case "FortDecoItemDefinition":
case "FortStatItemDefinition":
case "FortAmmoItemDefinition":
case "FortEmoteItemDefinition":
case "FortBadgeItemDefinition":
case "SparksMicItemDefinition":
case "FortAwardItemDefinition":
case "FortStackItemDefinition":
case "FortWorldItemDefinition":
case "SparksAuraItemDefinition":
case "SparksDrumItemDefinition":
case "SparksBassItemDefinition":
case "FortGadgetItemDefinition":
case "AthenaCharmItemDefinition":
case "FortPlaysetItemDefinition":
case "FortGiftBoxItemDefinition":
case "FortOutpostItemDefinition":
case "FortVehicleItemDefinition":
case "FortMissionItemDefinition":
case "FortAccountItemDefinition":
case "SparksGuitarItemDefinition":
case "FortCardPackItemDefinition":
case "FortDefenderItemDefinition":
case "FortCurrencyItemDefinition":
case "FortResourceItemDefinition":
case "FortBackpackItemDefinition":
case "FortEventQuestMapDataAsset":
case "FortBuildingItemDefinition":
case "FortWeaponModItemDefinition":
case "FortCodeTokenItemDefinition":
case "FortSchematicItemDefinition":
case "FortAlterableItemDefinition":
case "SparksKeyboardItemDefinition":
case "FortWorldMultiItemDefinition":
case "FortAlterationItemDefinition":
case "FortExpeditionItemDefinition":
case "FortIngredientItemDefinition":
case "FortConsumableItemDefinition":
case "StWFortAccoladeItemDefinition":
case "FortAccountBuffItemDefinition":
case "FortWeaponMeleeItemDefinition":
case "FortPlayerPerksItemDefinition":
case "FortPlaysetPropItemDefinition":
case "FortPrerollDataItemDefinition":
case "JunoRecipeBundleItemDefinition":
case "FortHomebaseNodeItemDefinition":
case "FortNeverPersistItemDefinition":
case "FortPlayerAugmentItemDefinition":
case "FortSmartBuildingItemDefinition":
case "FortGiftBoxUnlockItemDefinition":
case "FortWeaponModItemDefinitionOptic":
case "RadioContentSourceItemDefinition":
case "FortPlaysetGrenadeItemDefinition":
case "JunoWeaponCreatureItemDefinition":
case "FortEventDependentItemDefinition":
case "FortPersonalVehicleItemDefinition":
case "FortGameplayModifierItemDefinition":
case "FortHardcoreModifierItemDefinition":
case "FortWeaponModItemDefinitionMagazine":
case "FortConsumableAccountItemDefinition":
case "FortConversionControlItemDefinition":
case "FortAccountBuffCreditItemDefinition":
case "JunoBuildInstructionsItemDefinition":
case "FortCharacterCosmeticItemDefinition":
case "JunoBuildingSetAccountItemDefinition":
case "FortEventCurrencyItemDefinitionRedir":
case "FortPersistentResourceItemDefinition":
case "FortWeaponMeleeOffhandItemDefinition":
case "FortHomebaseBannerIconItemDefinition":
case "FortVehicleCosmeticsVariantTokenType":
case "JunoBuildingPropAccountItemDefinition":
case "FortCampaignHeroLoadoutItemDefinition":
case "FortConditionalResourceItemDefinition":
case "FortChallengeBundleScheduleDefinition":
case "FortWeaponMeleeDualWieldItemDefinition":
case "FortDailyRewardScheduleTokenDefinition":
case "FortCreativeWeaponRangedItemDefinition":
case "FortVehicleCosmeticsItemDefinition_Body":
case "FortVehicleCosmeticsItemDefinition_Skin":
case "FortVehicleCosmeticsItemDefinition_Wheel":
case "FortCreativeRealEstatePlotItemDefinition":
case "FortDeployableBaseCloudSaveItemDefinition":
case "FortVehicleCosmeticsItemDefinition_Booster":
case "AthenaDanceItemDefinition_AdHocSquadsJoin_C":
case "FortVehicleCosmeticsItemDefinition_DriftSmoke":
case "FortVehicleCosmeticsItemDefinition_EngineAudio":
creator = _style switch
{
EIconStyle.Cataba => new BaseCommunity(_object, _style, "Cataba"),
_ => new BaseIcon(_object, _style)
};
return true;
case "JunoAthenaCharacterItemOverrideDefinition":
case "JunoAthenaDanceItemOverrideDefinition":
creator = new BaseJuno(_object, _style);
return true;
case "FortTandemCharacterData":
creator = new BaseTandem(_object, _style);
return true;
case "FortTrapItemDefinition":
case "FortSpyTechItemDefinition":
case "FortAccoladeItemDefinition":
case "FortContextTrapItemDefinition":
case "FortWeaponRangedItemDefinition":
case "Daybreak_LevelExitVehicle_PartItemDefinition_C":
creator = new BaseIconStats(_object, _style);
return true;
case "FortItemSeriesDefinition":
creator = new BaseSeries(_object, _style);
return true;
case "MaterialInstanceConstant"
when _object.Owner != null &&
(_object.Owner.Name.Contains("/MI_OfferImages/", StringComparison.OrdinalIgnoreCase) ||
_object.Owner.Name.EndsWith($"/RenderSwitch_Materials/{_object.Name}", StringComparison.OrdinalIgnoreCase) ||
_object.Owner.Name.EndsWith($"/MI_BPTile/{_object.Name}", StringComparison.OrdinalIgnoreCase)):
creator = new BaseMaterialInstance(_object, _style);
return true;
case "AthenaItemShopOfferDisplayData":
creator = new BaseOfferDisplayData(_object, _style);
return true;
case "FortMtxOfferData":
creator = new BaseMtxOffer(_object, _style);
return true;
case "FortPlaylistAthena":
creator = new BasePlaylist(_object, _style);
return true;
case "FortFeatItemDefinition":
case "FortQuestItemDefinition":
case "FortQuestItemDefinition_Athena":
case "FortQuestItemDefinition_Campaign":
case "AthenaDailyQuestDefinition":
case "FortUrgentQuestItemDefinition":
creator = new Bases.FN.BaseQuest(_object, _style);
return true;
case "FortCompendiumItemDefinition":
case "FortChallengeBundleItemDefinition":
creator = new BaseBundle(_object, _style);
return true;
// case "AthenaSeasonItemDefinition":
// creator = new BaseSeason(_object, _style);
// return true;
case "FortItemAccessTokenType":
creator = new BaseItemAccessToken(_object, _style);
return true;
case "FortCreativeOption":
case "PlaylistUserOptionEnum":
case "PlaylistUserOptionBool":
case "PlaylistUserOptionString":
case "PlaylistUserOptionIntEnum":
case "PlaylistUserOptionIntRange":
case "PlaylistUserOptionColorEnum":
case "PlaylistUserOptionFloatEnum":
case "PlaylistUserOptionFloatRange":
case "PlaylistUserTintedIconIntEnum":
case "PlaylistUserOptionPrimaryAsset":
case "PlaylistUserOptionCollisionProfileEnum":
creator = new BaseUserControl(_object, _style);
return true;
// PandaGame
case "CharacterData":
creator = new BaseFighter(_object, _style);
return true;
case "PerkGroup":
creator = new BasePerkGroup(_object, _style);
return true;
case "StatTrackingBundleData":
case "HydraSyncedDataAsset":
case "AnnouncerPackData":
case "CharacterGiftData":
case "ProfileIconData":
case "RingOutVfxData":
case "BannerData":
case "EmoteData":
case "TauntData":
case "SkinData":
case "PerkData":
creator = new BasePandaIcon(_object, _style);
return true;
case "QuestData":
creator = new Bases.MV.BaseQuest(_object, _style);
return true;
default:
creator = null;
return false;
}
}
public override string ToString() => $"{_object.ExportType} | {_style}";
public void Dispose()
{
_object = null;
}
}

View File

@ -1,57 +0,0 @@
using FModel.Creator.Bases;
using PakReader.Pak;
using PakReader.Parsers.Class;
using PakReader.Parsers.PropertyTagData;
namespace FModel.Creator.Icons
{
static class DisplayAssetImage
{
public static bool GetDisplayAssetImage(BaseIcon icon, SoftObjectProperty o, ref string assetName)
{
string imageType = "DetailsImage";
switch ("DA_Featured_" + assetName)
{
case "DA_Featured_Glider_ID_141_AshtonBoardwalk.uasset":
case "DA_Featured_Glider_ID_150_TechOpsBlue.uasset":
case "DA_Featured_Glider_ID_131_SpeedyMidnight.uasset":
case "DA_Featured_Pickaxe_ID_178_SpeedyMidnight.uasset":
case "DA_Featured_Glider_ID_015_Brite.uasset":
case "DA_Featured_Glider_ID_016_Tactical.uasset":
case "DA_Featured_Glider_ID_017_Assassin.uasset":
case "DA_Featured_Pickaxe_ID_027_Scavenger.uasset":
case "DA_Featured_Pickaxe_ID_028_Space.uasset":
case "DA_Featured_Pickaxe_ID_029_Assassin.uasset":
return false;
case "DA_Featured_Glider_ID_070_DarkViking.uasset":
case "DA_Featured_CID_319_Athena_Commando_F_Nautilus.uasset":
imageType = "TileImage";
break;
}
string path = o?.Value.AssetPathName.String;
if (string.IsNullOrEmpty(path))
path = "/Game/Catalog/DisplayAssets/DA_Featured_" + assetName.Substring(0, assetName.LastIndexOf("."));
PakPackage p = Utils.GetPropertyPakPackage(path);
if (p.HasExport() && !p.Equals(default))
{
var obj = p.GetExport<UObject>();
if (obj != null)
{
if (obj.TryGetValue(imageType, out var v1) && v1 is StructProperty s && s.Value is UObject type &&
type.TryGetValue("ResourceObject", out var v2) && v2 is ObjectProperty resourceObject)
{
if (!resourceObject.Value.Resource.OuterIndex.Resource.ObjectName.String.Contains("/Game/Athena/Prototype/Textures/"))
{
icon.IconImage = Utils.GetObjectTexture(resourceObject);
assetName = "DA_Featured_" + assetName;
return true;
}
}
}
}
return false;
}
}
}

View File

@ -1,61 +0,0 @@
using FModel.Creator.Bases;
using PakReader.Pak;
using PakReader.Parsers.Class;
using PakReader.Parsers.PropertyTagData;
using SkiaSharp;
namespace FModel.Creator.Icons
{
static class LargeSmallImage
{
public static void GetPreviewImage(BaseIcon icon, StructProperty u)
{
if (u.Value is UObject o && o.TryGetValue("ResourceObject", out var v) && v is ObjectProperty resourceObject)
icon.IconImage = Utils.GetObjectTexture(resourceObject);
}
public static void GetPreviewImage(BaseIcon icon, ObjectProperty o, string assetName) => GetPreviewImage(icon, o, assetName, true);
public static void GetPreviewImage(BaseIcon icon, ObjectProperty o, string assetName, bool hightRes)
{
string path = o.Value.Resource.OuterIndex.Resource.ObjectName.String;
if (path.Equals("/Game/Athena/Items/Weapons/WID_Harvest_Pickaxe_STWCosmetic_Tier"))
path += "_" + assetName.Substring(assetName.LastIndexOf(".") - 1, 1);
PakPackage p = Utils.GetPropertyPakPackage(path);
if (p.HasExport() && !p.Equals(default))
{
if (GetPreviewImage(icon, p.GetIndexedExport<UObject>(0), hightRes))
return;
else if (GetPreviewImage(icon, p.GetIndexedExport<UObject>(1), hightRes)) // FortniteGame/Content/Athena/Items/Cosmetics/Pickaxes/Pickaxe_ID_402_BlackKnightFemale1H.uasset
return;
}
}
public static void GetPreviewImage(BaseIcon icon, SoftObjectProperty s) => icon.IconImage = Utils.GetSoftObjectTexture(s);
private static bool GetPreviewImage(BaseIcon icon, UObject obj, bool hightRes)
{
if (obj != null)
{
if (hightRes && obj.TryGetValue("LargePreviewImage", out var sLarge) && sLarge is SoftObjectProperty largePreviewImage && !string.IsNullOrEmpty(largePreviewImage.Value.AssetPathName.String))
{
GetPreviewImage(icon, largePreviewImage);
return true;
}
else if (obj.TryGetValue("SmallPreviewImage", out var sSmall1) && sSmall1 is SoftObjectProperty smallPreviewImage1 && !string.IsNullOrEmpty(smallPreviewImage1.Value.AssetPathName.String))
{
GetPreviewImage(icon, smallPreviewImage1);
return true;
}
else if (obj.TryGetValue("SmallPreviewImage", out var sSmall2) && sSmall2 is ObjectProperty smallPreviewImage2 && !string.IsNullOrEmpty(smallPreviewImage2.Value.Resource.OuterIndex.Resource.ObjectName.String))
{
icon.IconImage = Utils.GetObjectTexture(smallPreviewImage2);
return true;
}
}
return false;
}
public static void DrawPreviewImage(SKCanvas c, BaseIcon icon) =>
c.DrawBitmap(icon.IconImage ?? icon.FallbackImage, new SKRect(icon.Margin, icon.Margin, icon.Size - icon.Margin, icon.Size - icon.Margin),
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
}
}

View File

@ -1,73 +0,0 @@
using FModel.Creator.Bases;
using PakReader.Pak;
using PakReader.Parsers.Class;
using PakReader.Parsers.Objects;
using PakReader.Parsers.PropertyTagData;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Windows;
namespace FModel.Creator.Icons
{
static class UserFacingFlag
{
public static void GetUserFacingFlags(List<string> uffs, BaseIcon icon, string exportType)
{
if (uffs.Count > 0)
{
PakPackage p = Utils.GetPropertyPakPackage("/Game/Items/ItemCategories"); //PrimaryCategories - SecondaryCategories - TertiaryCategories
if (p.HasExport() && !p.Equals(default))
{
var o = p.GetExport<UObject>();
if (o != null && o.TryGetValue("TertiaryCategories", out var tertiaryCategories) && tertiaryCategories is ArrayProperty tertiaryArray)
{
icon.UserFacingFlags = new SKBitmap[uffs.Count];
for (int i = 0; i < uffs.Count; i++)
{
if (uffs[i].Equals("Cosmetics.UserFacingFlags.HasUpgradeQuests"))
{
if (exportType.Equals("AthenaPetCarrierItemDefinition"))
icon.UserFacingFlags[i] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Pets-64.png")).Stream);
else
icon.UserFacingFlags[i] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Quests-64.png")).Stream);
}
else
{
foreach (StructProperty structProp in tertiaryArray.Value)
{
if (structProp.Value is UObject mainUObject &&
mainUObject.TryGetValue("TagContainer", out var struc1) && struc1 is StructProperty tagContainer && tagContainer.Value is FGameplayTagContainer f && f.GameplayTags.TryGetGameplayTag(uffs[i], out var _) &&
mainUObject.TryGetValue("CategoryBrush", out var struc2) && struc2 is StructProperty categoryBrush && categoryBrush.Value is UObject categoryUObject &&
categoryUObject.TryGetValue("Brush_XXS", out var struc3) && struc3 is StructProperty brushXXS && brushXXS.Value is UObject brushUObject &&
brushUObject.TryGetValue("ResourceObject", out var object1) && object1 is ObjectProperty resourceObject)
{
icon.UserFacingFlags[i] = Utils.GetObjectTexture(resourceObject);
break;
}
}
}
}
}
}
}
}
public static void DrawUserFacingFlags(SKCanvas c, BaseIcon icon)
{
if (icon.UserFacingFlags != null)
{
int size = 25;
int x = icon.Margin * (int)2.5;
foreach (SKBitmap b in icon.UserFacingFlags)
{
if (b == null)
continue;
c.DrawBitmap(b.Resize(size, size), new SKPoint(x, icon.Margin * (int)2.5), new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
x += size;
}
}
}
}
}

View File

@ -1,31 +0,0 @@
using SkiaSharp;
using System.IO;
namespace FModel.Creator.Icons
{
static class Watermark
{
public static void DrawWatermark(SKCanvas c)
{
if (Properties.Settings.Default.UseIconWatermark && !string.IsNullOrWhiteSpace(Properties.Settings.Default.IconWatermarkPath))
{
using SKBitmap watermarkBase = SKBitmap.Decode(new FileInfo(Properties.Settings.Default.IconWatermarkPath).Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
int sizeX = watermarkBase.Width * (int)Properties.Settings.Default.IconWatermarkScale / 512;
int sizeY = watermarkBase.Height * (int)Properties.Settings.Default.IconWatermarkScale / 512;
SKBitmap watermark = watermarkBase.Resize(sizeX, sizeY);
float left = Properties.Settings.Default.IconWatermarkX;
float top = Properties.Settings.Default.IconWatermarkY;
float right = left + watermark.Width;
float bottom = top + watermark.Height;
c.DrawBitmap(watermark, new SKRect(left, top, right, bottom),
new SKPaint
{
FilterQuality = SKFilterQuality.High,
IsAntialias = true,
Color = SKColors.Transparent.WithAlpha((byte)Properties.Settings.Default.IconWatermarkOpacity)
});
}
}
}
}

View File

@ -1,15 +0,0 @@
namespace FModel.Creator.Rarities
{
public enum EFortRarity : int
{
Common,
Uncommon,
Rare,
Epic,
Legendary,
Mythic,
Transcendent,
Unattainable,
//Impossible
}
}

View File

@ -1,183 +0,0 @@
using FModel.Creator.Bases;
using PakReader.Pak;
using PakReader.Parsers.Class;
using PakReader.Parsers.Objects;
using PakReader.Parsers.PropertyTagData;
using SkiaSharp;
using System.Linq;
namespace FModel.Creator.Rarities
{
static class Rarity
{
public static void GetInGameRarity(BaseIcon icon, EnumProperty e)
{
PakPackage p = Utils.GetPropertyPakPackage("/Game/Balance/RarityData");
if (p.HasExport() && !p.Equals(default))
{
var d = p.GetExport<UObject>();
if (d != null)
{
EFortRarity rarity = EFortRarity.Uncommon;
switch (e?.Value.String)
{
case "EFortRarity::Common":
case "EFortRarity::Handmade":
rarity = EFortRarity.Common;
break;
case "EFortRarity::Rare":
case "EFortRarity::Sturdy":
rarity = EFortRarity.Rare;
break;
case "EFortRarity::Epic":
case "EFortRarity::Quality":
rarity = EFortRarity.Epic;
break;
case "EFortRarity::Legendary":
case "EFortRarity::Fine":
rarity = EFortRarity.Legendary;
break;
case "EFortRarity::Mythic":
case "EFortRarity::Elegant":
rarity = EFortRarity.Mythic;
break;
case "EFortRarity::Transcendent":
case "EFortRarity::Masterwork":
rarity = EFortRarity.Transcendent;
break;
case "EFortRarity::Unattainable":
case "EFortRarity::Badass":
rarity = EFortRarity.Unattainable;
break;
}
if (d.Values.ElementAt((int)rarity) is StructProperty s && s.Value is UObject colors)
{
if (colors.TryGetValue("Color1", out var c1) && c1 is StructProperty s1 && s1.Value is FLinearColor color1 &&
colors.TryGetValue("Color2", out var c2) && c2 is StructProperty s2 && s2.Value is FLinearColor color2 &&
colors.TryGetValue("Color3", out var c3) && c3 is StructProperty s3 && s3.Value is FLinearColor color3)
{
icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex) };
icon.RarityBorderColor = new SKColor[2] { SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex) };
}
}
}
}
else GetHardCodedRarity(icon, e);
}
public static void GetHardCodedRarity(BaseIcon icon, EnumProperty e)
{
switch (e?.Value.String)
{
case "EFortRarity::Common":
case "EFortRarity::Handmade":
icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse("6D6D6D"), SKColor.Parse("333333") };
icon.RarityBorderColor = new SKColor[2] { SKColor.Parse("9E9E9E"), SKColor.Parse("9E9E9E") };
break;
case "EFortRarity::Rare":
case "EFortRarity::Sturdy":
icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse("3669BB"), SKColor.Parse("133254") };
icon.RarityBorderColor = new SKColor[2] { SKColor.Parse("5180EE"), SKColor.Parse("5180EE") };
break;
case "EFortRarity::Epic":
case "EFortRarity::Quality":
icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse("8138C2"), SKColor.Parse("35155C") };
icon.RarityBorderColor = new SKColor[2] { SKColor.Parse("B251ED"), SKColor.Parse("B251ED") };
break;
case "EFortRarity::Legendary":
case "EFortRarity::Fine":
icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse("C06A38"), SKColor.Parse("5C2814") };
icon.RarityBorderColor = new SKColor[2] { SKColor.Parse("EC9650"), SKColor.Parse("EC9650") };
break;
case "EFortRarity::Mythic":
case "EFortRarity::Elegant":
icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse("BA9C36"), SKColor.Parse("594415") };
icon.RarityBorderColor = new SKColor[2] { SKColor.Parse("EED951"), SKColor.Parse("EED951") };
break;
case "EFortRarity::Transcendent":
case "EFortRarity::Masterwork":
icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse("D51944"), SKColor.Parse("660522") };
icon.RarityBorderColor = new SKColor[2] { SKColor.Parse("FF3F58"), SKColor.Parse("FF3F58") };
break;
}
}
public static void DrawRarity(SKCanvas c, BaseIcon icon)
{
// border
c.DrawRect(new SKRect(0, 0, icon.Size, icon.Size),
new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Shader = SKShader.CreateLinearGradient(
new SKPoint(icon.Size / 2, icon.Size),
new SKPoint(icon.Size, icon.Size / 4),
icon.RarityBorderColor,
SKShaderTileMode.Clamp)
});
switch ((EIconDesign)Properties.Settings.Default.AssetsIconDesign)
{
case EIconDesign.Flat:
{
if (icon.RarityBackgroundImage != null)
c.DrawBitmap(icon.RarityBackgroundImage, new SKRect(icon.Margin, icon.Margin, icon.Size - icon.Margin, icon.Size - icon.Margin),
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
else
{
c.DrawRect(new SKRect(icon.Margin, icon.Margin, icon.Size - icon.Margin, icon.Size - icon.Margin),
new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Color = icon.RarityBackgroundColors[0]
});
var paint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Color = icon.RarityBackgroundColors[1].WithAlpha(75)
};
var pathTop = new SKPath { FillType = SKPathFillType.EvenOdd };
pathTop.MoveTo(icon.Margin, icon.Margin);
pathTop.LineTo(icon.Margin + (icon.Size / 17 * 10), icon.Margin);
pathTop.LineTo(icon.Margin, icon.Margin + (icon.Size / 17));
pathTop.Close();
c.DrawPath(pathTop, paint);
var pathBottom = new SKPath { FillType = SKPathFillType.EvenOdd };
pathBottom.MoveTo(icon.Margin, icon.Size - icon.Margin);
pathBottom.LineTo(icon.Margin, icon.Size - icon.Margin - (icon.Size / 17 * 2.5f));
pathBottom.LineTo(icon.Size - icon.Margin, icon.Size - icon.Margin - (icon.Size / 17 * 4.5f));
pathBottom.LineTo(icon.Size - icon.Margin, icon.Size - icon.Margin);
pathBottom.Close();
c.DrawPath(pathBottom, paint);
}
break;
}
default:
{
if (icon.RarityBackgroundImage != null)
c.DrawBitmap(icon.RarityBackgroundImage, new SKRect(icon.Margin, icon.Margin, icon.Size - icon.Margin, icon.Size - icon.Margin),
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
else
c.DrawRect(new SKRect(icon.Margin, icon.Margin, icon.Size - icon.Margin, icon.Size - icon.Margin),
new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Shader = SKShader.CreateRadialGradient(
new SKPoint(icon.Size / 2, icon.Size / 2),
icon.Size / 5 * 4,
icon.RarityBackgroundColors,
SKShaderTileMode.Clamp)
});
break;
}
}
}
}
}

View File

@ -1,40 +0,0 @@
using FModel.Creator.Bases;
using PakReader.Pak;
using PakReader.Parsers.Class;
using PakReader.Parsers.Objects;
using PakReader.Parsers.PropertyTagData;
using SkiaSharp;
namespace FModel.Creator.Rarities
{
static class Serie
{
public static void GetRarity(BaseIcon icon, ObjectProperty o)
{
PakPackage p = Utils.GetPropertyPakPackage(o.Value.Resource.OuterIndex.Resource.ObjectName.String);
if (p.HasExport() && !p.Equals(default))
{
var obj = p.GetExport<UObject>();
if (obj != null)
GetRarity(icon, obj);
}
}
public static void GetRarity(BaseIcon icon, IUExport export)
{
if (export.TryGetValue("BackgroundTexture", out var t) && t is SoftObjectProperty sop)
icon.RarityBackgroundImage = Utils.GetSoftObjectTexture(sop);
if (export.TryGetValue("Colors", out var v) && v is StructProperty s && s.Value is UObject colors)
{
if (colors.TryGetValue("Color1", out var c1) && c1 is StructProperty s1 && s1.Value is FLinearColor color1 &&
colors.TryGetValue("Color2", out var c2) && c2 is StructProperty s2 && s2.Value is FLinearColor color2 &&
colors.TryGetValue("Color4", out var c4) && c4 is StructProperty s4 && s4.Value is FLinearColor color4)
{
icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse(color1.Hex), SKColor.Parse(color4.Hex) };
icon.RarityBorderColor = new SKColor[2] { SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex) };
}
}
}
}
}

View File

@ -1,12 +0,0 @@
using SkiaSharp;
namespace FModel.Creator.Stats
{
public class Statistic
{
public SKBitmap Icon;
public string Description;
public string DisplayName;
public int Height;
}
}

View File

@ -1,204 +0,0 @@
using FModel.Creator.Bases;
using FModel.Creator.Texts;
using FModel.Utils;
using PakReader.Pak;
using PakReader.Parsers.Class;
using PakReader.Parsers.PropertyTagData;
using SkiaSharp;
using System;
using System.Windows;
namespace FModel.Creator.Stats
{
static class Statistics
{
public static void GetAmmoData(BaseIcon icon, SoftObjectProperty ammoData)
{
PakPackage p = Utils.GetPropertyPakPackage(ammoData.Value.AssetPathName.String);
if (p.HasExport() && !p.Equals(default))
{
var obj = p.GetExport<UObject>();
if (obj != null)
{
if (obj.TryGetValue("DisplayName", out var v1) && v1 is TextProperty displayName &&
obj.TryGetValue("SmallPreviewImage", out var v2) && v2 is SoftObjectProperty smallPreviewImage)
{
icon.Stats.Add(new Statistic
{
Icon = Utils.GetSoftObjectTexture(smallPreviewImage),
Description = Text.GetTextPropertyBase(displayName).ToUpper()
});
}
}
}
}
public static void GetWeaponStats(BaseIcon icon, StructProperty weaponStatHandle)
{
if (weaponStatHandle.Value is UObject o1 &&
o1.TryGetValue("DataTable", out var c1) && c1 is ObjectProperty dataTable &&
o1.TryGetValue("RowName", out var c2) && c2 is NameProperty rowName)
{
PakPackage p = Utils.GetPropertyPakPackage(dataTable.Value.Resource.OuterIndex.Resource.ObjectName.String);
if (p.HasExport() && !p.Equals(default))
{
var table = p.GetExport<UDataTable>();
if (table != null)
{
if (table.TryGetValue(rowName.Value.String, out var v1) && v1 is UObject stats)
{
if (stats.TryGetValue("ReloadTime", out var s1) && s1 is FloatProperty reloadTime && reloadTime.Value != 0)
icon.Stats.Add(new Statistic
{
Icon = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_ReloadTime_Weapon_Stats.png")).Stream),
Description = $"{Localizations.GetLocalization(string.Empty, "6EA26D1A4252034FBD869A90F9A6E49A", "Reload Time")} ({Localizations.GetLocalization(string.Empty, "6BA53D764BA5CC13E821D2A807A72365", "seconds")}) : {reloadTime.Value:0.0}".ToUpper()
});
if (stats.TryGetValue("ClipSize", out var s2) && s2 is IntProperty clipSize && clipSize.Value != 0)
icon.Stats.Add(new Statistic
{
Icon = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_ClipSize_Weapon_Stats.png")).Stream),
Description = $"{Localizations.GetLocalization(string.Empty, "068239DD4327B36124498C9C5F61C038", "Magazine Size")} : {clipSize.Value}".ToUpper()
});
if (stats.TryGetValue("DmgPB", out var s3) && s3 is FloatProperty dmgPB && dmgPB.Value != 0)
icon.Stats.Add(new Statistic
{
Icon = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_DamagePerBullet_Weapon_Stats.png")).Stream),
Description = $"{Localizations.GetLocalization(string.Empty, "BF7E3CF34A9ACFF52E95EAAD4F09F133", "Damage to Player")} : {dmgPB.Value}".ToUpper()
});
}
}
}
}
}
public static void GetHeroStats(BaseIcon icon, ObjectProperty heroGameplayDefinition)
{
PakPackage p = Utils.GetPropertyPakPackage(heroGameplayDefinition.Value.Resource.OuterIndex.Resource.ObjectName.String);
if (p.HasExport() && !p.Equals(default))
{
var obj = p.GetExport<UObject>();
if (obj != null)
{
if (obj.TryGetValue("HeroPerk", out var v1) && v1 is StructProperty s1 && s1.Value is UObject heroPerk)
{
GetAbilityKit(icon, heroPerk);
}
if (obj.TryGetValue("TierAbilityKits", out var v2) && v2 is ArrayProperty tierAbilityKits)
{
foreach (StructProperty abilityKit in tierAbilityKits.Value)
{
if (abilityKit.Value is UObject kit)
{
GetAbilityKit(icon, kit);
}
}
}
}
}
}
private static void GetAbilityKit(BaseIcon icon, UObject parent)
{
if (parent.TryGetValue("GrantedAbilityKit", out var v) && v is SoftObjectProperty grantedAbilityKit)
{
PakPackage k = Utils.GetPropertyPakPackage(grantedAbilityKit.Value.AssetPathName.String);
if (k.HasExport() && !k.Equals(default))
{
var kit = k.GetExport<UObject>();
if (kit != null &&
kit.GetExport<TextProperty>("DisplayName") is TextProperty displayName &&
kit.GetExport<StructProperty>("IconBrush") is StructProperty brush && brush.Value is UObject iconBrush &&
iconBrush.TryGetValue("ResourceObject", out var s) && s is ObjectProperty resourceObject)
{
icon.Stats.Add(new Statistic
{
Icon = Utils.GetObjectTexture(resourceObject),
Description = Text.GetTextPropertyBase(displayName).ToUpper()
});
}
}
}
}
public static void DrawStats(SKCanvas c, BaseIcon icon)
{
int size = 48;
int iconSize = 40;
int textSize = 25;
int y = icon.Size;
foreach (Statistic stat in icon.Stats)
{
c.DrawRect(new SKRect(0, y, icon.Size, y + size),
new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Shader = SKShader.CreateLinearGradient(
new SKPoint(icon.Size / 2, icon.Size),
new SKPoint(icon.Size, icon.Size / 4),
icon.RarityBorderColor,
SKShaderTileMode.Clamp)
});
if ((EIconDesign)Properties.Settings.Default.AssetsIconDesign == EIconDesign.Flat)
{
c.DrawRect(new SKRect(icon.Margin, y, icon.Size - icon.Margin, y + size - icon.Margin),
new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Color = icon.RarityBackgroundColors[0]
});
}
else
{
c.DrawRect(new SKRect(icon.Margin, y, icon.Size - icon.Margin, y + size - icon.Margin),
new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Shader = SKShader.CreateRadialGradient(
new SKPoint(icon.Size / 2, icon.Size / 2),
icon.Size / 5 * 4,
icon.RarityBackgroundColors,
SKShaderTileMode.Clamp)
});
}
c.DrawRect(new SKRect(icon.Margin, y, icon.Size - icon.Margin, y + size - icon.Margin),
new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Color = new SKColor(0, 0, 50, 75)
});
c.DrawBitmap(stat.Icon.Resize(iconSize, iconSize), new SKPoint(icon.Margin * (int)2.5, y + 4), new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
var statPaint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Typeface = Text.TypeFaces.DisplayNameTypeface,
TextSize = textSize,
Color = SKColors.White,
TextAlign = SKTextAlign.Center,
};
// resize if too long
while (statPaint.MeasureText(stat.Description) > (icon.Size - (icon.Margin * 2) - iconSize))
{
statPaint.TextSize = textSize -= 2;
}
c.DrawText(stat.Description, icon.Size / 2, y + 32, statPaint);
y += size;
}
}
}
}

View File

@ -1,9 +0,0 @@
namespace FModel.Creator.Texts
{
public enum ETextSide
{
Center,
Right,
Left
}
}

View File

@ -1,73 +0,0 @@
using FModel.Creator.Bases;
using FModel.Creator.Icons;
using FModel.Utils;
using PakReader.Pak;
using PakReader.Parsers.Class;
using PakReader.Parsers.Objects;
using PakReader.Parsers.PropertyTagData;
namespace FModel.Creator.Texts
{
static class GameplayTag
{
public static void GetGameplayTags(BaseIcon icon, StructProperty s, string exportType)
{
if (s.Value is FGameplayTagContainer g)
{
if (g.GameplayTags.TryGetGameplayTag("Cosmetics.Source.", out var source))
icon.CosmeticSource = source.String.Substring("Cosmetics.Source.".Length);
else if(g.GameplayTags.TryGetGameplayTag("Athena.ItemAction.", out var action))
icon.CosmeticSource = action.String.Substring("Athena.ItemAction.".Length);
if (g.GameplayTags.TryGetGameplayTag("Cosmetics.Set.", out var set))
icon.Description += GetCosmeticSet(set.String);
if (g.GameplayTags.TryGetGameplayTag("Cosmetics.Filter.Season.", out var season))
icon.Description += GetCosmeticSeason(season.String);
UserFacingFlag.GetUserFacingFlags(
g.GameplayTags.GetAllGameplayTag("Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender."),
icon, exportType);
}
}
private static string GetCosmeticSet(string setName)
{
PakPackage p = Utils.GetPropertyPakPackage("/Game/Athena/Items/Cosmetics/Metadata/CosmeticSets");
if (p.HasExport() && !p.Equals(default))
{
var d = p.GetExport<UDataTable>();
if (d != null && d.TryGetValue(setName, out var obj) && obj is UObject o)
{
if (o.TryGetValue("DisplayName", out var displayName) && displayName is TextProperty t)
{
(string n, string k, string s) = Text.GetTextPropertyBases(t);
string set = Localizations.GetLocalization(n, k, s);
string format = Localizations.GetLocalization("Fort.Cosmetics", "CosmeticItemDescription_SetMembership_NotRich", "\nPart of the {0} set.");
return string.Format(format, set);
}
}
}
return string.Empty;
}
private static string GetCosmeticSeason(string seasonNumber)
{
string s = seasonNumber.Substring("Cosmetics.Filter.Season.".Length);
int number = int.Parse(s);
if (number == 10)
s = "X";
string season = Localizations.GetLocalization("AthenaSeasonItemDefinitionInternal", "SeasonTextFormat", "Season {0}");
string introduced = Localizations.GetLocalization("Fort.Cosmetics", "CosmeticItemDescription_Season", "\nIntroduced in <SeasonText>{0}</>.")
.Replace("<SeasonText>", string.Empty).Replace("</>", string.Empty);
if (number > 10)
{
string chapter = Localizations.GetLocalization("AthenaSeasonItemDefinitionInternal", "ChapterTextFormat", "Chapter {0}");
string chapterFormat = Localizations.GetLocalization("AthenaSeasonItemDefinitionInternal", "ChapterSeasonTextFormat", "{0}, {1}");
string d = string.Format(chapterFormat, string.Format(chapter, number / 10 + 1), string.Format(season, s[^1..]));
return string.Format(introduced, d);
}
else return string.Format(introduced, string.Format(season, s));
}
}
}

View File

@ -1,111 +0,0 @@
using FModel.Creator.Bases;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Text;
namespace FModel.Creator.Texts
{
static class Helper
{
public class Line
{
public string Value { get; set; }
public float Width { get; set; }
}
public static void DrawCenteredMultilineText(SKCanvas canvas, string text, int maxLineCount, BaseIcon icon, ETextSide side, SKRect area, SKPaint paint)
=> DrawCenteredMultilineText(canvas, text, maxLineCount, icon.Size, icon.Margin, side, area, paint);
public static void DrawCenteredMultilineText(SKCanvas canvas, string text, int maxLineCount, int size, int margin, ETextSide side, SKRect area, SKPaint paint)
{
float lineHeight = paint.TextSize * 1.2f;
Line[] lines = SplitLines(text, paint, area.Width - margin);
if (lines == null)
return;
if (lines.Length <= maxLineCount)
maxLineCount = lines.Length;
float height = maxLineCount * lineHeight;
float y = area.MidY - height / 2;
for (int i = 0; i < maxLineCount; i++)
{
y += lineHeight;
float x = side switch
{
ETextSide.Center => area.MidX - lines[i].Width / 2,
ETextSide.Right => size - margin - lines[i].Width,
ETextSide.Left => margin,
_ => area.MidX - lines[i].Width / 2
};
canvas.DrawText(lines[i].Value.TrimEnd(), x, y, paint);
}
}
public static void DrawMultilineText(SKCanvas canvas, string text, int size, int margin, ETextSide side, SKRect area, SKPaint paint, out int yPos)
{
float lineHeight = paint.TextSize * 1.2f;
Line[] lines = SplitLines(text, paint, area.Width);
if (lines == null)
{
yPos = (int)area.Top;
return;
}
float y = area.Top;
for (int i = 0; i < lines.Length; i++)
{
float x = side switch
{
ETextSide.Center => area.MidX - lines[i].Width / 2,
ETextSide.Right => size - margin - lines[i].Width,
ETextSide.Left => area.Left,
_ => area.MidX - lines[i].Width / 2
};
canvas.DrawText(lines[i].Value.TrimEnd(), x, y, paint);
y += lineHeight;
}
yPos = (int)area.Top + ((int)lineHeight * lines.Length);
}
public static Line[] SplitLines(string text, SKPaint paint, float maxWidth)
{
if (string.IsNullOrEmpty(text))
return null;
float spaceWidth = paint.MeasureText(" ");
string[] lines = text.Split('\n', StringSplitOptions.RemoveEmptyEntries);
List<Line> ret = new List<Line>(lines.Length);
for (int i = 0; i < lines.Length; i++)
{
if (string.IsNullOrWhiteSpace(lines[i]))
continue;
float width = 0;
var lineResult = new StringBuilder();
string[] words = lines[i].Split(' ', StringSplitOptions.None);
foreach (var word in words)
{
float wordWidth = paint.MeasureText(word);
float wordWithSpaceWidth = wordWidth + spaceWidth;
string wordWithSpace = word + " ";
if (width + wordWidth > maxWidth)
{
ret.Add(new Line { Value = lineResult.ToString(), Width = width });
lineResult = new StringBuilder(wordWithSpace);
width = wordWithSpaceWidth;
}
else
{
lineResult.Append(wordWithSpace);
width += wordWithSpaceWidth;
}
}
ret.Add(new Line { Value = lineResult.ToString(), Width = width });
}
return ret.ToArray();
}
}
}

View File

@ -1,220 +0,0 @@
using FModel.Creator.Bases;
using PakReader.Pak;
using PakReader.Parsers.Class;
using PakReader.Parsers.Objects;
using PakReader.Parsers.PropertyTagData;
using SkiaSharp;
using System.Collections.Generic;
namespace FModel.Creator.Texts
{
static class Text
{
public static Typefaces TypeFaces = new Typefaces();
private const int _STARTER_TEXT_POSITION = 380;
private static int _BOTTOM_TEXT_SIZE = 15;
private static int _NAME_TEXT_SIZE = 45;
public static string GetTextPropertyBase(TextProperty t)
{
if (t.Value is FText text)
if (text.Text is FTextHistory.None n)
return n.CultureInvariantString;
else if (text.Text is FTextHistory.Base b)
return b.SourceString.Replace("<Emphasized>", string.Empty).Replace("</>", string.Empty);
else if (text.Text is FTextHistory.StringTableEntry s)
{
PakPackage p = Utils.GetPropertyPakPackage(s.TableId.String);
if (p.HasExport() && !p.Equals(default))
{
var table = p.GetExport<UStringTable>();
if (table != null)
{
if (table.TryGetValue("StringTable", out var v1) && v1 is FStringTable stringTable &&
stringTable.KeysToMetadata.TryGetValue(stringTable.TableNamespace, out var v2) && v2 is Dictionary<string, string> dico &&
dico.TryGetValue(s.Key, out var ret))
{
return ret;
}
}
}
}
return string.Empty;
}
public static string GetTextPropertyBase(ArrayProperty a)
{
if (a.Value.Length > 0 && a.Value[0] is TextProperty t)
return GetTextPropertyBase(t);
return string.Empty;
}
public static (string, string, string) GetTextPropertyBases(TextProperty t)
{
if (t.Value is FText text && text.Text is FTextHistory.Base b)
return (b.Namespace, b.Key, b.SourceString);
return (string.Empty, string.Empty, string.Empty);
}
public static string GetMaxStackSize(StructProperty maxStackSize)
{
if (maxStackSize.Value is UObject o1)
{
if (o1.TryGetValue("Value", out var c) && c is FloatProperty value && value.Value != -1) // old way
return $"MaxStackSize : {value.Value}";
else if (
o1.TryGetValue("Curve", out var c1) && c1 is StructProperty curve && curve.Value is UObject o2 &&
o2.TryGetValue("CurveTable", out var c2) && c2 is ObjectProperty curveTable &&
o2.TryGetValue("RowName", out var c3) && c3 is NameProperty rowName) // new way
{
PakPackage p = Utils.GetPropertyPakPackage(curveTable.Value.Resource.OuterIndex.Resource.ObjectName.String);
if (p.HasExport() && !p.Equals(default))
{
var table = p.GetExport<UCurveTable>();
if (table != null)
{
if (table.TryGetValue(rowName.Value.String, out var v1) && v1 is UObject maxStackAmount &&
maxStackAmount.TryGetValue("Keys", out var v2) && v2 is ArrayProperty keys &&
keys.Value.Length > 0 && (keys.Value[0] as StructProperty).Value is FSimpleCurveKey amount &&
amount.KeyValue != -1)
{
return $"MaxStackSize : {amount.KeyValue}";
}
}
}
}
}
return string.Empty;
}
public static void DrawBackground(SKCanvas c, BaseIcon icon)
{
switch ((EIconDesign)Properties.Settings.Default.AssetsIconDesign)
{
case EIconDesign.Flat:
{
var pathBottom = new SKPath { FillType = SKPathFillType.EvenOdd };
pathBottom.MoveTo(icon.Margin, icon.Size - icon.Margin);
pathBottom.LineTo(icon.Margin, icon.Size - icon.Margin - (icon.Size / 17 * 2.5f));
pathBottom.LineTo(icon.Size - icon.Margin, icon.Size - icon.Margin - (icon.Size / 17 * 4.5f));
pathBottom.LineTo(icon.Size - icon.Margin, icon.Size - icon.Margin);
pathBottom.Close();
c.DrawPath(pathBottom, new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Color = new SKColor(0, 0, 50, 75),
});
break;
}
default:
{
c.DrawRect(
new SKRect(icon.Margin, _STARTER_TEXT_POSITION, icon.Size - icon.Margin, icon.Size - icon.Margin),
new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Color = new SKColor(0, 0, 50, 75),
});
break;
}
}
}
public static void DrawDisplayName(SKCanvas c, BaseIcon icon)
{
_NAME_TEXT_SIZE = 45;
string text = icon.DisplayName;
SKTextAlign side = SKTextAlign.Center;
int x = icon.Size / 2;
int y = _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE;
switch ((EIconDesign)Properties.Settings.Default.AssetsIconDesign)
{
case EIconDesign.Mini:
{
_NAME_TEXT_SIZE = 47;
text = text.ToUpper();
break;
}
case EIconDesign.Flat:
{
_NAME_TEXT_SIZE = 47;
side = SKTextAlign.Right;
x = icon.Size - icon.Margin * 2;
break;
}
}
SKPaint namePaint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Typeface = TypeFaces.DisplayNameTypeface,
TextSize = _NAME_TEXT_SIZE,
Color = SKColors.White,
TextAlign = side,
};
// resize if too long
while (namePaint.MeasureText(text) > (icon.Size - (icon.Margin * 2)))
{
namePaint.TextSize = _NAME_TEXT_SIZE -= 2;
}
c.DrawText(text, x, y, namePaint);
}
public static void DrawDescription(SKCanvas c, BaseIcon icon)
{
int maxLine = 4;
_BOTTOM_TEXT_SIZE = 15;
string text = icon.Description;
ETextSide side = ETextSide.Center;
switch ((EIconDesign)Properties.Settings.Default.AssetsIconDesign)
{
case EIconDesign.Mini:
{
maxLine = 5;
_BOTTOM_TEXT_SIZE = icon.Margin;
text = text.ToUpper();
break;
}
case EIconDesign.Flat:
{
side = ETextSide.Right;
break;
}
}
SKPaint descriptionPaint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Typeface = TypeFaces.DescriptionTypeface,
TextSize = 13,
Color = SKColors.White,
};
// wrap if too long
Helper.DrawCenteredMultilineText(c, text, maxLine, icon, side,
new SKRect(icon.Margin, _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE, icon.Size - icon.Margin, icon.Size - _BOTTOM_TEXT_SIZE),
descriptionPaint);
}
public static void DrawToBottom(SKCanvas c, BaseIcon icon, ETextSide side, string text)
{
SKPaint shortDescriptionPaint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Typeface = side == ETextSide.Left ? TypeFaces.DisplayNameTypeface : TypeFaces.DefaultTypeface,
TextSize = 15,
Color = SKColors.White,
TextAlign = side == ETextSide.Left ? SKTextAlign.Left : SKTextAlign.Right,
};
c.DrawText(text, side == ETextSide.Left ? icon.Margin * 2.5f : icon.Size - (icon.Margin * 2.5f), icon.Size - (icon.Margin * 2.5f), shortDescriptionPaint);
}
}
}

View File

@ -1,152 +0,0 @@
using FModel.Utils;
using FModel.ViewModels.DataGrid;
using SkiaSharp;
using System;
using System.Windows;
namespace FModel.Creator.Texts
{
public class Typefaces
{
#pragma warning disable IDE0051
private const string _FORTNITE_BASE_PATH = "/Game/UI/Foundation/Fonts/";
private const string _ASIA_ERINM = "AsiaERINM"; // korean fortnite
private const string _BURBANK_BIG_CONDENSED_BLACK = "BurbankBigCondensed-Black"; // russian
private readonly Uri _BURBANK_BIG_CONDENSED_BOLD = new Uri("pack://application:,,,/Resources/BurbankBigCondensed-Bold.ttf"); // other languages fortnite unofficial
private const string _BURBANK_BIG_REGULAR_BLACK = "BurbankBigRegular-Black";
private const string _BURBANK_BIG_REGULAR_BOLD = "BurbankBigRegular-Bold"; // official fortnite ig
private const string _BURBANK_SMALL_MEDIUM = "BurbankSmall-Medium";
private const string _DROID_SANS_FORTNITE_SUBSET = "DroidSans-Fortnite-Subset";
private const string _NIS_JYAU = "NIS_JYAU"; // japanese fortnite
private const string _NOTO_COLOR_EMOJI = "NotoColorEmoji";
private const string _NOTO_SANS_BOLD = "NotoSans-Bold";
private const string _NOTO_SANS_FORTNITE_BOLD = "NotoSans-Fortnite-Bold";
private const string _NOTO_SANS_FORTNITE_ITALIC = "NotoSans-Fortnite-Italic";
private const string _NOTO_SANS_FORTNITE_REGULAR = "NotoSans-Fortnite-Regular";
private const string _NOTO_SANS_ITALIC = "NotoSans-Italic";
private const string _NOTO_SANS_REGULAR = "NotoSans-Regular";
private const string _NOTO_SANS_ARABIC_BLACK = "NotoSansArabic-Black"; // arabic fortnite
private const string _NOTO_SANS_ARABIC_BOLD = "NotoSansArabic-Bold";
private const string _NOTO_SANS_ARABIC_REGULAR = "NotoSansArabic-Regular";
private const string _NOTO_SANS_JP_BOLD = "NotoSansJP-Bold";
private const string _NOTO_SANS_KR_REGULAR = "NotoSansKR-Regular";
private const string _NOTO_SANS_SC_BLACK = "NotoSansSC-Black"; // simplified chinese fortnite
private const string _NOTO_SANS_SC_REGULAR = "NotoSansSC-Regular";
private const string _NOTO_SANS_TC_BLACK = "NotoSansTC-Black"; // traditional chinese fortnite
private const string _NOTO_SANS_TC_REGULAR = "NotoSansTC-Regular";
private const string _BURBANK_SMALL_BLACK = "burbanksmall-black";
private const string _BURBANK_SMALL_BOLD = "burbanksmall-bold";
private const string _VALORANT_BASE_PATH = "/Game/";
private const string _TUNGSTEN_BOLD = "Tungsten-Bold";
private const string _DINNEXT_LTARABIC_HEAVY = "UI/Fonts/FinalFonts/LOCFonts/DIN_Next_Arabic/DINNextLTArabic-Heavy";
private const string _TUNGSTEN_CYRILLIC = "UI/Fonts/FinalFonts/LOCFonts/LOC_Tungsten/Cyrillic_Tungsten";
private const string _TUNGSTEN_JAPANESE = "UI/Fonts/FinalFonts/LOCFonts/LOC_Tungsten/JP_Tungsten";
private const string _TUNGSTEN_KOREAN = "UI/Fonts/FinalFonts/LOCFonts/LOC_Tungsten/KR_Tungsten";
private const string _TUNGSTEN_SIMPLIFIED_CHINESE = "UI/Fonts/FinalFonts/LOCFonts/LOC_Tungsten/zh-CN_Tungsten";
private const string _TUNGSTEN_TRADITIONAL_CHINESE = "UI/Fonts/FinalFonts/LOCFonts/LOC_Tungsten/zh-TW_Tungsten";
private const string _DINNEXT_W1G_LIGHT = "UI/Fonts/FinalFonts/DINNextW1G-Light";
private const string _DINNEXT_LTARABIC_LIGHT = "UI/Fonts/FinalFonts/LOCFonts/DIN_Next_Arabic/DINNextLTArabic-Light";
private const string _NOTOSANS_CJK_LIGHT = "UI/Fonts/FinalFonts/LOCFonts/CJK/NotoSansCJK-Light"; // chinese, japanese, korean
private const string _DINNEXT_W1G_BOLD = "UI/Fonts/FinalFonts/DINNextW1G-Bold";
#pragma warning restore IDE0051
public SKTypeface DefaultTypeface; // used as default font for all untranslated strings (item source, ...)
public SKTypeface BundleDefaultTypeface; // used for the last folder string
public SKTypeface DisplayNameTypeface;
public SKTypeface DescriptionTypeface;
public SKTypeface BundleDisplayNameTypeface;
public Typefaces()
{
DefaultTypeface = SKTypeface.FromStream(Application.GetResourceStream(_BURBANK_BIG_CONDENSED_BOLD).Stream);
if (Globals.Game.ActualGame == EGame.Fortnite)
{
ArraySegment<byte>[] t = Utils.GetPropertyArraySegmentByte(_FORTNITE_BASE_PATH + _BURBANK_BIG_CONDENSED_BLACK);
if (t != null && t.Length == 3 && t[2].Array != null)
BundleDefaultTypeface = SKTypeface.FromStream(t[2].AsStream());
else BundleDefaultTypeface = DefaultTypeface;
string namePath = _FORTNITE_BASE_PATH + (
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _ASIA_ERINM :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Russian ? _BURBANK_BIG_CONDENSED_BLACK :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? _NIS_JYAU :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Arabic ? _NOTO_SANS_ARABIC_BLACK :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.TraditionalChinese ? _NOTO_SANS_TC_BLACK :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _NOTO_SANS_SC_BLACK :
string.Empty);
if (!namePath.Equals(_FORTNITE_BASE_PATH))
{
t = Utils.GetPropertyArraySegmentByte(namePath);
if (t != null && t.Length == 3 && t[2].Array != null)
DisplayNameTypeface = SKTypeface.FromStream(t[2].AsStream());
}
else DisplayNameTypeface = DefaultTypeface;
string descriptionPath = _FORTNITE_BASE_PATH + (
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _NOTO_SANS_KR_REGULAR :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? _NOTO_SANS_JP_BOLD :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Arabic ? _NOTO_SANS_ARABIC_REGULAR :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.TraditionalChinese ? _NOTO_SANS_TC_REGULAR :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _NOTO_SANS_SC_REGULAR :
_NOTO_SANS_REGULAR);
t = Utils.GetPropertyArraySegmentByte(descriptionPath);
if (t != null && t.Length == 3 && t[2].Array != null)
DescriptionTypeface = SKTypeface.FromStream(t[2].AsStream());
else DescriptionTypeface = DefaultTypeface;
string bundleNamePath = _FORTNITE_BASE_PATH + (
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _ASIA_ERINM :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Russian ? _BURBANK_BIG_CONDENSED_BLACK :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? _NIS_JYAU :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Arabic ? _NOTO_SANS_ARABIC_BLACK :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.TraditionalChinese ? _NOTO_SANS_TC_BLACK :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _NOTO_SANS_SC_BLACK :
string.Empty);
if (!bundleNamePath.Equals(_FORTNITE_BASE_PATH))
{
t = Utils.GetPropertyArraySegmentByte(bundleNamePath);
if (t != null && t.Length == 3 && t[2].Array != null)
BundleDisplayNameTypeface = SKTypeface.FromStream(t[2].AsStream());
}
else BundleDisplayNameTypeface = BundleDefaultTypeface;
}
else if (Globals.Game.ActualGame == EGame.Valorant)
{
string namePath = _VALORANT_BASE_PATH + (
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _TUNGSTEN_KOREAN :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Russian ? _TUNGSTEN_CYRILLIC :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? _TUNGSTEN_JAPANESE :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Arabic ? _DINNEXT_LTARABIC_HEAVY :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.TraditionalChinese ? _TUNGSTEN_TRADITIONAL_CHINESE :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _TUNGSTEN_SIMPLIFIED_CHINESE :
_TUNGSTEN_BOLD);
ArraySegment<byte>[] t = Utils.GetPropertyArraySegmentByte(namePath);
if (t != null && t.Length == 3 && t[2].Array != null)
DisplayNameTypeface = SKTypeface.FromStream(t[2].AsStream());
else DisplayNameTypeface = DefaultTypeface;
string descriptionPath = _VALORANT_BASE_PATH + (
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _NOTOSANS_CJK_LIGHT :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? _NOTOSANS_CJK_LIGHT :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Arabic ? _DINNEXT_LTARABIC_LIGHT :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.TraditionalChinese ? _NOTOSANS_CJK_LIGHT :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _NOTOSANS_CJK_LIGHT :
_DINNEXT_W1G_LIGHT);
t = Utils.GetPropertyArraySegmentByte(descriptionPath);
if (t != null && t.Length == 3 && t[2].Array != null)
DescriptionTypeface = SKTypeface.FromStream(t[2].AsStream());
else DescriptionTypeface = DefaultTypeface;
t = Utils.GetPropertyArraySegmentByte(_VALORANT_BASE_PATH + _DINNEXT_W1G_BOLD);
if (t != null && t.Length == 3 && t[2].Array != null)
BundleDefaultTypeface = SKTypeface.FromStream(t[2].AsStream());
else BundleDefaultTypeface = DefaultTypeface;
}
}
public bool NeedReload(bool forceReload) => forceReload ?
DataGridVm.dataGridViewModel.Count > 0 : //reload only if at least one pak is loaded
DataGridVm.dataGridViewModel.Count > 0 && (BundleDefaultTypeface == DefaultTypeface && DisplayNameTypeface == DefaultTypeface && DescriptionTypeface == DefaultTypeface && BundleDisplayNameTypeface == BundleDefaultTypeface);
}
}

199
FModel/Creator/Typefaces.cs Normal file
View File

@ -0,0 +1,199 @@
using System;
using System.IO;
using System.Windows;
using CUE4Parse.UE4.Versions;
using FModel.Settings;
using FModel.ViewModels;
using SkiaSharp;
namespace FModel.Creator;
public class Typefaces
{
private readonly Uri _BURBANK_BIG_CONDENSED_BOLD = new("pack://application:,,,/Resources/BurbankBigCondensed-Bold.ttf");
private const string _EXT = ".ufont";
// FortniteGame
private const string _FORTNITE_BASE_PATH = "/Game/UI/Foundation/Fonts/";
private const string _ASIA_ERINM = "AsiaERINM"; // korean fortnite
private const string _BURBANK_BIG_CONDENSED_BLACK = "BurbankBigCondensed-Black"; // russian
private const string _BURBANK_BIG_REGULAR_BLACK = "BurbankBigRegular-Black";
private const string _BURBANK_BIG_REGULAR_BOLD = "BurbankBigRegular-Bold"; // official fortnite ig
private const string _BURBANK_SMALL_MEDIUM = "BurbankSmall-Medium";
private const string _DROID_SANS_FORTNITE_SUBSET = "DroidSans-Fortnite-Subset";
private const string _NOTO_COLOR_EMOJI = "NotoColorEmoji";
private const string _NOTO_SANS_BOLD = "NotoSans-Bold";
private const string _NOTO_SANS_FORTNITE_BOLD = "NotoSans-Fortnite-Bold";
private const string _NOTO_SANS_FORTNITE_ITALIC = "NotoSans-Fortnite-Italic";
private const string _NOTO_SANS_FORTNITE_REGULAR = "NotoSans-Fortnite-Regular";
private const string _NOTO_SANS_ITALIC = "NotoSans-Italic";
private const string _NOTO_SANS_REGULAR = "NotoSans-Regular";
private const string _NOTO_SANS_ARABIC_BLACK = "NotoSansArabic-Black"; // arabic fortnite
private const string _NOTO_SANS_ARABIC_BOLD = "NotoSansArabic-Bold";
private const string _NOTO_SANS_ARABIC_REGULAR = "NotoSansArabic-Regular";
private const string _NOTO_SANS_JP_BOLD = "NotoSansJP-Bold"; // japanese fortnite
private const string _NOTO_SANS_KR_REGULAR = "NotoSansKR-Regular";
private const string _NOTO_SANS_SC_BLACK = "NotoSansSC-Black"; // simplified chinese fortnite
private const string _NOTO_SANS_SC_REGULAR = "NotoSansSC-Regular";
private const string _NOTO_SANS_TC_BLACK = "NotoSansTC-Black"; // traditional chinese fortnite
private const string _NOTO_SANS_TC_REGULAR = "NotoSansTC-Regular";
private const string _BURBANK_SMALL_BLACK = "burbanksmall-black";
private const string _BURBANK_SMALL_BOLD = "burbanksmall-bold";
// PandaGame
private const string _PANDAGAME_BASE_PATH = "/Game/Panda_Main/UI/Fonts/";
private const string _NORMS_STD_CONDENSED_EXTRABOLD_ITALIC = "Norms/TT_Norms_Std_Condensed_ExtraBold_Italic";
private const string _NORMS_PRO_EXTRABOLD_ITALIC = "Norms/TT_Norms_Pro_ExtraBold_Italic";
private const string _NORMS_STD_CONDENSED_MEDIUM = "Norms/TT_Norms_Std_Condensed_Medium";
private const string _XIANGHEHEI_SC_PRO_BLACK = "XiangHeHei_SC/MXiangHeHeiSCPro-Black";
private const string _XIANGHEHEI_SC_PRO_HEAVY = "XiangHeHei_SC/MXiangHeHeiSCPro-Heavy";
private readonly CUE4ParseViewModel _viewModel;
public readonly SKTypeface Default; // used as a fallback font for all untranslated strings (item source, ...)
public readonly SKTypeface DisplayName;
public readonly SKTypeface Description;
public readonly SKTypeface Bottom; // must be null for non-latin base languages
public readonly SKTypeface Bundle;
public readonly SKTypeface BundleNumber;
public readonly SKTypeface TandemDisplayName;
public readonly SKTypeface TandemGenDescription;
public readonly SKTypeface TandemAddDescription;
public Typefaces(CUE4ParseViewModel viewModel)
{
_viewModel = viewModel;
var language = UserSettings.Default.AssetLanguage;
Default = SKTypeface.FromStream(Application.GetResourceStream(_BURBANK_BIG_CONDENSED_BOLD)?.Stream);
switch (viewModel.Provider.InternalGameName.ToUpperInvariant())
{
case "FORTNITEGAME":
{
DisplayName = OnTheFly(_FORTNITE_BASE_PATH +
language switch
{
ELanguage.Korean => _ASIA_ERINM,
ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK,
ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
_ => string.Empty
} + _EXT);
Description = OnTheFly(_FORTNITE_BASE_PATH +
language switch
{
ELanguage.Korean => _NOTO_SANS_KR_REGULAR,
ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
ELanguage.Arabic => _NOTO_SANS_ARABIC_REGULAR,
ELanguage.TraditionalChinese => _NOTO_SANS_TC_REGULAR,
ELanguage.Chinese => _NOTO_SANS_SC_REGULAR,
_ => _NOTO_SANS_REGULAR
} + _EXT);
Bottom = OnTheFly(_FORTNITE_BASE_PATH +
language switch
{
ELanguage.Korean => string.Empty,
ELanguage.Japanese => string.Empty,
ELanguage.Arabic => string.Empty,
ELanguage.TraditionalChinese => string.Empty,
ELanguage.Chinese => string.Empty,
_ => _BURBANK_SMALL_BOLD
} + _EXT, true);
BundleNumber = OnTheFly(_FORTNITE_BASE_PATH + _BURBANK_BIG_CONDENSED_BLACK + _EXT);
Bundle = OnTheFly(_FORTNITE_BASE_PATH +
language switch
{
ELanguage.Korean => _ASIA_ERINM,
ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK,
ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
_ => string.Empty
} + _EXT, true) ?? BundleNumber;
TandemDisplayName = OnTheFly(_FORTNITE_BASE_PATH +
language switch
{
ELanguage.Korean => _ASIA_ERINM,
ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK,
ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
_ => _BURBANK_BIG_REGULAR_BLACK
} + _EXT);
TandemGenDescription = OnTheFly(_FORTNITE_BASE_PATH +
language switch
{
ELanguage.Korean => _ASIA_ERINM,
ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
_ => _BURBANK_SMALL_BLACK
} + _EXT);
TandemAddDescription = OnTheFly(_FORTNITE_BASE_PATH +
language switch
{
ELanguage.Korean => _ASIA_ERINM,
ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
_ => _BURBANK_SMALL_BOLD
} + _EXT);
break;
}
case "MULTIVERSUS":
{
DisplayName = OnTheFly(_PANDAGAME_BASE_PATH + language switch
{
ELanguage.Chinese => _XIANGHEHEI_SC_PRO_HEAVY,
_ => _NORMS_PRO_EXTRABOLD_ITALIC
} + _EXT);
Description = OnTheFly(_PANDAGAME_BASE_PATH + language switch
{
ELanguage.Chinese => _XIANGHEHEI_SC_PRO_BLACK,
_ => _NORMS_STD_CONDENSED_MEDIUM
} + _EXT);
TandemDisplayName = OnTheFly(_PANDAGAME_BASE_PATH + language switch
{
ELanguage.Chinese => _XIANGHEHEI_SC_PRO_BLACK,
_ => _NORMS_STD_CONDENSED_EXTRABOLD_ITALIC
} + _EXT);
TandemGenDescription = OnTheFly(_PANDAGAME_BASE_PATH + language switch
{
ELanguage.Chinese => _XIANGHEHEI_SC_PRO_HEAVY,
_ => _NORMS_STD_CONDENSED_MEDIUM
} + _EXT);
break;
}
default:
{
DisplayName = Default;
Description = Default;
break;
}
}
}
public SKTypeface OnTheFly(string path, bool fallback = false)
{
if (!_viewModel.Provider.TrySaveAsset(path, out var data)) return fallback ? null : Default;
var m = new MemoryStream(data) { Position = 0 };
return SKTypeface.FromStream(m);
}
}

View File

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

View File

@ -1,70 +0,0 @@
using DiscordRPC;
using DiscordRPC.Logging;
using FModel.Logger;
using System;
using System.Reflection;
namespace FModel.Discord
{
static class DiscordIntegration
{
private const string _DISCORD_APP_ID = "684489366189768767";
private static readonly Timestamps _baseTimestamp = new Timestamps { Start = DateTime.UtcNow };
private static readonly Assets _assets = new Assets
{
LargeImageKey = "official_logo",
SmallImageKey = "verified",
SmallImageText = $"v{Assembly.GetExecutingAssembly().GetName().Version.ToString().Substring(0, 5)}"
};
private static readonly DiscordRpcClient _client = new DiscordRpcClient(_DISCORD_APP_ID);
private static RichPresence _presence;
public static void Dispose() => _client.Dispose();
public static void Deinitialize() => _client.Deinitialize();
private static void Initialize()
{
_client.Logger = new ConsoleLogger() { Level = LogLevel.Warning };
_client.OnReady += (sender, e) =>
{
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[Discord RPC]", $"Ready for {e.User.Username}#{e.User.Discriminator} ({e.User.ID})");
};
_client.Initialize();
}
public static void StartClient()
{
_client.SetPresence(new RichPresence
{
Assets = _assets,
Timestamps = _baseTimestamp,
State = string.Format(Properties.Resources.Idling, Globals.Game.GetName())
});
Initialize();
SaveCurrentPresence();
}
public static void Update(string detail = null, string state = null)
{
_client.SetPresence(new RichPresence
{
Assets = _assets,
Timestamps = _baseTimestamp,
Details = string.IsNullOrEmpty(detail) ? _presence?.Details : detail,
State = string.IsNullOrEmpty(state) ? _presence?.State : state
});
_client.Invoke();
}
public static void Restore()
{
if (_presence != null)
{
_client.SetPresence(_presence);
_client.Invoke();
}
}
public static void SaveCurrentPresence() => _presence = _client.CurrentPresence;
}
}

View File

@ -1,76 +1,116 @@
namespace FModel
using System;
using System.ComponentModel;
namespace FModel;
public enum EBuildKind
{
public enum EGame
{
Unknown,
Fortnite,
Valorant,
DeadByDaylight,
Borderlands3,
MinecraftDungeons,
BattleBreakers,
Spellbreak,
StateOfDecay2, // WIP
TheCycleEA // TODO: Early Access version, change when game is released
}
public enum EFModel
{
Debug,
Release,
Unknown
}
public enum EPakLoader
{
Single,
All,
New,
Modified,
NewModified
}
public enum ECopy
{
Path,
PathNoExt,
PathNoFile,
File,
FileNoExt
}
public enum ELanguage : long
{
English,
French,
German,
Italian,
Spanish,
SpanishLatin,
Arabic,
Japanese,
Korean,
Polish,
PortugueseBrazil,
Russian,
Turkish,
Chinese,
TraditionalChinese,
AustralianEnglish
}
public enum EJsonType: long
{
Default,
Positioned
}
public enum EIconDesign : long
{
Default,
NoBackground,
NoText,
Mini,
Flat
}
Debug,
Release,
Unknown
}
public enum EErrorKind
{
Ignore,
Restart,
ResetSettings
}
public enum SettingsOut
{
ReloadLocres,
ReloadMappings
}
public enum EStatusKind
{
Ready, // ready
Loading, // doing stuff
Stopping, // trying to stop
Stopped, // stopped
Failed, // crashed
Completed // worked
}
public enum EAesReload
{
[Description("Always")]
Always,
[Description("Never")]
Never,
[Description("Once Per Day")]
OncePerDay
}
public enum EDiscordRpc
{
[Description("Always")]
Always,
[Description("Never")]
Never
}
public enum ELoadingMode
{
[Description("Multiple")]
Multiple,
[Description("All")]
All,
[Description("All (New)")]
AllButNew,
[Description("All (Modified)")]
AllButModified
}
// public enum EUpdateMode
// {
// [Description("Stable")]
// Stable,
// [Description("Beta")]
// Beta,
// [Description("QA Testing")]
// Qa
// }
public enum ECompressedAudio
{
[Description("Play the decompressed data")]
PlayDecompressed,
[Description("Play the compressed data (might not always be a valid audio data)")]
PlayCompressed
}
public enum EIconStyle
{
[Description("Default")]
Default,
[Description("No Background")]
NoBackground,
[Description("No Text")]
NoText,
[Description("Flat")]
Flat,
[Description("Cataba")]
Cataba,
// [Description("Community")]
// CommunityMade
}
public enum EEndpointType
{
Aes,
Mapping
}
[Flags]
public enum EBulkType
{
None = 0,
Auto = 1 << 0,
Properties = 1 << 1,
Textures = 1 << 2,
Meshes = 1 << 3,
Skeletons = 1 << 4,
Animations = 1 << 5
}

View File

@ -0,0 +1,54 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Xml;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Highlighting.Xshd;
namespace FModel.Extensions;
public static class AvalonExtensions
{
private static readonly IHighlightingDefinition _jsonHighlighter = LoadHighlighter("Json.xshd");
private static readonly IHighlightingDefinition _iniHighlighter = LoadHighlighter("Ini.xshd");
private static readonly IHighlightingDefinition _xmlHighlighter = LoadHighlighter("Xml.xshd");
private static readonly IHighlightingDefinition _cppHighlighter = LoadHighlighter("Cpp.xshd");
private static readonly IHighlightingDefinition _changelogHighlighter = LoadHighlighter("Changelog.xshd");
private static readonly IHighlightingDefinition _verseHighlighter = LoadHighlighter("Verse.xshd");
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static IHighlightingDefinition LoadHighlighter(string resourceName)
{
var executingAssembly = Assembly.GetExecutingAssembly();
using var stream = executingAssembly.GetManifestResourceStream($"{executingAssembly.GetName().Name}.Resources.{resourceName}");
using var reader = new XmlTextReader(stream);
return HighlightingLoader.Load(reader, HighlightingManager.Instance);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IHighlightingDefinition HighlighterSelector(string ext)
{
switch (ext)
{
case "ini":
case "csv":
return _iniHighlighter;
case "xml":
case "tps":
return _xmlHighlighter;
case "h":
case "cpp":
return _cppHighlighter;
case "changelog":
return _changelogHighlighter;
case "verse":
return _verseHighlighter;
case "bat":
case "txt":
case "pem":
case "po":
return null;
default:
return _jsonHighlighter;
}
}
}

View File

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

View File

@ -0,0 +1,36 @@
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
namespace FModel.Extensions;
public static class CollectionExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Next<T>(this IList<T> collection, T value)
{
var i = collection.IndexOf(value) + 1;
return i >= collection.Count ? collection.First() : collection[i];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Next<T>(this IList<T> collection, int index)
{
var i = index + 1;
return i >= collection.Count ? collection.First() : collection[i];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Previous<T>(this IList<T> collection, T value)
{
var i = collection.IndexOf(value) - 1;
return i < 0 ? collection.Last() : collection[i];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Previous<T>(this IList<T> collection, int index)
{
var i = index - 1;
return i < 0 ? collection.Last() : collection[i];
}
}

View File

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

View File

@ -0,0 +1,30 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
namespace FModel.Extensions;
public enum Endianness
{
LittleEndian,
BigEndian
}
public static class StreamExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ReadUInt32(this Stream s, Endianness endian = Endianness.LittleEndian)
{
var b1 = s.ReadByte();
var b2 = s.ReadByte();
var b3 = s.ReadByte();
var b4 = s.ReadByte();
return endian switch
{
Endianness.LittleEndian => (uint) (b4 << 24 | b3 << 16 | b2 << 8 | b1),
Endianness.BigEndian => (uint) (b1 << 24 | b2 << 16 | b3 << 8 | b4),
_ => throw new Exception("unknown endianness")
};
}
}

View File

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

View File

@ -1,231 +1,260 @@
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<ApplicationIcon>FModel.ico</ApplicationIcon>
<Version>4.4.4.0</Version>
<AssemblyVersion>4.4.4.0</AssemblyVersion>
<FileVersion>4.4.4.0</FileVersion>
<IsPackable>false</IsPackable>
<IsPublishable>true</IsPublishable>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PublishSingleFile Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">true</PublishSingleFile>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
<StartupObject>FModel.App</StartupObject>
<Authors>Asval</Authors>
<Company></Company>
<AssemblyVersion>3.1.1.2</AssemblyVersion>
<FileVersion>3.1.1.2</FileVersion>
<PackageIcon>FModel.ico</PackageIcon>
<PackageIconUrl />
<PackageProjectUrl>https://github.com/iAmAsval/FModel</PackageProjectUrl>
<Description></Description>
<Version>3.1.1</Version>
<Platforms>x64</Platforms>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<PlatformTarget>x64</PlatformTarget>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<NoWarn>1701;1702;NU1701</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<PlatformTarget>x64</PlatformTarget>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<NoWarn>1701;1702;NU1701</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<PlatformTarget>x64</PlatformTarget>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<NoWarn>NU1701</NoWarn>
</PropertyGroup>
<ItemGroup>
<None Remove="FModel.ico" />
<None Remove="Resources\alert.ico" />
<None Remove="Resources\alpha-a-box.png" />
<None Remove="Resources\api-off.ico" />
<None Remove="Resources\api.ico" />
<None Remove="Resources\backup-restore.png" />
<None Remove="Resources\battlebreakers.ico" />
<None Remove="Resources\borderlands3.ico" />
<None Remove="Resources\bug.png" />
<None Remove="Resources\BurbankBigCondensed-Bold.ttf" />
<None Remove="Resources\cast-audio.png" />
<None Remove="Resources\challenge-theme-creator.png" />
<None Remove="Resources\check-circle.ico" />
<None Remove="Resources\ColorPickerOne.png" />
<None Remove="Resources\ColorPickerTwo.png" />
<None Remove="Resources\content-copy.png" />
<None Remove="Resources\content-save.png" />
<None Remove="Resources\Cpp.xshd" />
<None Remove="Resources\delete-forever.png" />
<None Remove="Resources\discord.png" />
<None Remove="Resources\egl2.ico" />
<None Remove="Resources\EIconDesign_Default.png" />
<None Remove="Resources\EIconDesign_Flat.png" />
<None Remove="Resources\EIconDesign_Mini.png" />
<None Remove="Resources\EIconDesign_NoBackground.png" />
<None Remove="Resources\EIconDesign_NoText.png" />
<None Remove="Resources\file-export.png" />
<None Remove="Resources\file-image.ico" />
<None Remove="Resources\file-multiple.png" />
<None Remove="Resources\file-restore.png" />
<None Remove="Resources\file.png" />
<None Remove="Resources\fmodel.png" />
<None Remove="Resources\folder-download.png" />
<None Remove="Resources\folder-open.png" />
<None Remove="Resources\fortnite.ico" />
<None Remove="Resources\gamepad-variant.png" />
<None Remove="Resources\github-circle.png" />
<None Remove="Resources\icon-creator.png" />
<None Remove="Resources\image-filter-black-white.png" />
<None Remove="Resources\image-move.png" />
<None Remove="Resources\image-plus.png" />
<None Remove="Resources\image-remove.png" />
<None Remove="Resources\information.png" />
<None Remove="Resources\Ini.xshd" />
<None Remove="Resources\android.png" />
<None Remove="Resources\apple.png" />
<None Remove="Resources\battlebreakers.png" />
<None Remove="Resources\blueprint.png" />
<None Remove="Resources\borderlands.png" />
<None Remove="Resources\empty_folder.png" />
<None Remove="Resources\engine.png" />
<None Remove="Resources\fallenorder.png" />
<None Remove="Resources\FModel.ico" />
<None Remove="Resources\folder.png" />
<None Remove="Resources\label.png" />
<None Remove="Resources\fortnite.png" />
<None Remove="Resources\fortnitebr.png" />
<None Remove="Resources\gear.png" />
<None Remove="Resources\localization.png" />
<None Remove="Resources\materialicon.png" />
<None Remove="Resources\square.png" />
<None Remove="Resources\square_off.png" />
<None Remove="Resources\cube.png" />
<None Remove="Resources\cube_off.png" />
<None Remove="Resources\light.png" />
<None Remove="Resources\light_off.png" />
<None Remove="Resources\pc.png" />
<None Remove="Resources\puzzle.png" />
<None Remove="Resources\roguecompany.png" />
<None Remove="Resources\sound.png" />
<None Remove="Resources\creative.png" />
<None Remove="Resources\spellbreak.png" />
<None Remove="Resources\texture.png" />
<None Remove="Resources\thecycle.png" />
<None Remove="Resources\ui.png" />
<None Remove="Resources\valorant.png" />
<None Remove="Resources\weapon.png" />
<None Remove="Resources\windows.png" />
<None Remove="Resources\cinematics.png" />
<None Remove="Resources\archive.png" />
<None Remove="Resources\archive_enabled.png" />
<None Remove="Resources\archive_disabled.png" />
<None Remove="Resources\unknown_asset.png" />
<None Remove="Resources\asset.png" />
<None Remove="Resources\asset_ini.png" />
<None Remove="Resources\asset_psd.png" />
<None Remove="Resources\asset_png.png" />
<None Remove="Resources\athena.png" />
<None Remove="Resources\Json.xshd" />
<None Remove="Resources\key.png" />
<None Remove="Resources\lock-open-variant.ico" />
<None Remove="Resources\magnify.png" />
<None Remove="Resources\minecraftdungeons.ico" />
<None Remove="Resources\open-in-new.png" />
<None Remove="Resources\pause.png" />
<None Remove="Resources\paypal.png" />
<None Remove="Resources\pencil.png" />
<None Remove="Resources\play.png" />
<None Remove="Resources\playlist-plus.png" />
<None Remove="Resources\power.png" />
<None Remove="Resources\progress-download.png" />
<None Remove="Resources\refresh.png" />
<None Remove="Resources\settings.png" />
<None Remove="Resources\share-all.png" />
<None Remove="Resources\share.png" />
<None Remove="Resources\sign-direction-plus.png" />
<None Remove="Resources\sign-direction-remove.png" />
<None Remove="Resources\sign-direction.png" />
<None Remove="Resources\spellbreak.ico" />
<None Remove="Resources\stop.png" />
<None Remove="Resources\sod2.ico" />
<None Remove="Resources\T-Icon-Pets-64.png" />
<None Remove="Resources\T-Icon-Quests-64.png" />
<None Remove="Resources\thecycle.ico" />
<None Remove="Resources\trello.png" />
<None Remove="Resources\Ini.xshd" />
<None Remove="Resources\Verse.xshd" />
<None Remove="Resources\Xml.xshd" />
<None Remove="Resources\Cpp.xshd" />
<None Remove="Resources\Changelog.xshd" />
<None Remove="Resources\unix.png" />
<None Remove="Resources\linux.png" />
<None Remove="Resources\stateofdecay2.png" />
<None Remove="Resources\T_Placeholder_Item_Image.png" />
<None Remove="Resources\checker.png" />
<None Remove="Resources\T_ClipSize_Weapon_Stats.png" />
<None Remove="Resources\T_DamagePerBullet_Weapon_Stats.png" />
<None Remove="Resources\T_Placeholder_Challenge_Image.png" />
<None Remove="Resources\T_Placeholder_Item_Image.png" />
<None Remove="Resources\T_ReloadTime_Weapon_Stats.png" />
<None Remove="Resources\valorant.live.ico" />
<None Remove="Resources\view-dashboard.png" />
<None Remove="Resources\volume-minus.png" />
<None Remove="Resources\volume-mute.png" />
<None Remove="Resources\volume-plus.png" />
<None Remove="Resources\wifi-strength-off.ico" />
<None Remove="Resources\Xml.xshd" />
<None Remove="Resources\zip-box.png" />
<None Include="FModel.ico">
<Pack>True</Pack>
<PackagePath></PackagePath>
</None>
<None Remove="Resources\T-Icon-Pets-64.png" />
<None Remove="Resources\T-Icon-Quests-64.png" />
<None Remove="Resources\Default.png" />
<None Remove="Resources\NoBackground.png" />
<None Remove="Resources\NoText.png" />
<None Remove="Resources\Flat.png" />
<None Remove="Resources\Cataba.png" />
<None Remove="Resources\BurbankBigCondensed-Bold.ttf" />
<None Remove="Resources\add_directory.png" />
<None Remove="Resources\delete.png" />
<None Remove="Resources\edit.png" />
<None Remove="Resources\go_to_directory.png" />
<None Remove="Resources\npcleftside.png" />
<None Remove="Resources\default.frag" />
<None Remove="Resources\default.vert" />
<None Remove="Resources\grid.frag" />
<None Remove="Resources\grid.vert" />
<None Remove="Resources\skybox.frag" />
<None Remove="Resources\skybox.vert" />
<None Remove="Resources\framebuffer.frag" />
<None Remove="Resources\framebuffer.vert" />
<None Remove="Resources\outline.frag" />
<None Remove="Resources\outline.vert" />
<None Remove="Resources\picking.frag" />
<None Remove="Resources\picking.vert" />
<None Remove="Resources\light.frag" />
<None Remove="Resources\light.vert" />
<None Remove="Resources\bone.frag" />
<None Remove="Resources\bone.vert" />
<None Remove="Resources\collision.vert" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\Cpp.xshd" />
<EmbeddedResource Include="Resources\Ini.xshd" />
<EmbeddedResource Include="Resources\Json.xshd" />
<EmbeddedResource Include="Resources\Ini.xshd" />
<EmbeddedResource Include="Resources\Verse.xshd" />
<EmbeddedResource Include="Resources\Xml.xshd" />
<EmbeddedResource Include="Resources\Cpp.xshd" />
<EmbeddedResource Include="Resources\Changelog.xshd" />
<EmbeddedResource Include="Resources\default.frag" />
<EmbeddedResource Include="Resources\default.vert" />
<EmbeddedResource Include="Resources\grid.frag" />
<EmbeddedResource Include="Resources\grid.vert" />
<EmbeddedResource Include="Resources\skybox.frag" />
<EmbeddedResource Include="Resources\skybox.vert" />
<EmbeddedResource Include="Resources\framebuffer.frag" />
<EmbeddedResource Include="Resources\framebuffer.vert" />
<EmbeddedResource Include="Resources\outline.frag" />
<EmbeddedResource Include="Resources\outline.vert" />
<EmbeddedResource Include="Resources\picking.frag" />
<EmbeddedResource Include="Resources\picking.vert" />
<EmbeddedResource Include="Resources\light.frag" />
<EmbeddedResource Include="Resources\light.vert" />
<EmbeddedResource Include="Resources\bone.frag" />
<EmbeddedResource Include="Resources\bone.vert" />
<EmbeddedResource Include="Resources\collision.vert" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Autoupdater.NET.Official" Version="1.6.2" />
<PackageReference Include="AvalonEdit" Version="6.0.1" />
<PackageReference Include="AdonisUI" Version="1.17.1" />
<PackageReference Include="AdonisUI.ClassicTheme" Version="1.17.1" />
<PackageReference Include="Autoupdater.NET.Official" Version="1.9.2" />
<PackageReference Include="AvalonEdit" Version="6.3.0.90" />
<PackageReference Include="CSCore" Version="1.2.1.2" />
<PackageReference Include="DiscordRichPresence" Version="1.0.150" />
<PackageReference Include="DotNetZip" Version="1.13.8" />
<PackageReference Include="Extended.Wpf.Toolkit" Version="4.0.1" />
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.1.11" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="NVorbis" Version="0.10.1" />
<PackageReference Include="Ookii.Dialogs.Wpf" Version="1.1.0" />
<PackageReference Include="SkiaSharp" Version="2.80.1" />
<PackageReference Include="ToastNotifications" Version="2.5.1" />
<PackageReference Include="ToastNotifications.Messages" Version="2.5.1" />
<PackageReference Include="WriteableBitmapEx" Version="1.6.7" />
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
<PackageReference Include="EpicManifestParser" Version="2.3.3" />
<PackageReference Include="ImGui.NET" Version="1.91.0.1" />
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.3.8" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NVorbis" Version="0.10.5" />
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" />
<PackageReference Include="OpenTK" Version="4.8.2" />
<PackageReference Include="RestSharp" Version="112.1.0" />
<PackageReference Include="Serilog" Version="4.0.2" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" />
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.88.8" />
<PackageReference Include="SkiaSharp.Svg" Version="1.60.0" />
</ItemGroup>
<ItemGroup>
<Resource Include="FModel.ico" />
<Resource Include="Resources\alert.ico" />
<Resource Include="Resources\alpha-a-box.png" />
<Resource Include="Resources\api-off.ico" />
<Resource Include="Resources\api.ico" />
<Resource Include="Resources\backup-restore.png" />
<Resource Include="Resources\battlebreakers.ico" />
<Resource Include="Resources\borderlands3.ico" />
<Resource Include="Resources\bug.png" />
<Resource Include="Resources\BurbankBigCondensed-Bold.ttf" />
<Resource Include="Resources\cast-audio.png" />
<Resource Include="Resources\challenge-theme-creator.png" />
<Resource Include="Resources\check-circle.ico" />
<Resource Include="Resources\ColorPickerOne.png" />
<Resource Include="Resources\ColorPickerTwo.png" />
<Resource Include="Resources\content-copy.png" />
<Resource Include="Resources\content-save.png" />
<Resource Include="Resources\delete-forever.png" />
<Resource Include="Resources\discord.png" />
<Resource Include="Resources\egl2.ico" />
<Resource Include="Resources\EIconDesign_Default.png" />
<Resource Include="Resources\EIconDesign_Flat.png" />
<Resource Include="Resources\EIconDesign_Mini.png" />
<Resource Include="Resources\EIconDesign_NoBackground.png" />
<Resource Include="Resources\EIconDesign_NoText.png" />
<Resource Include="Resources\file-export.png" />
<Resource Include="Resources\file-image.ico" />
<Resource Include="Resources\file-multiple.png" />
<Resource Include="Resources\file-restore.png" />
<Resource Include="Resources\file.png" />
<Resource Include="Resources\fmodel.png" />
<Resource Include="Resources\folder-download.png" />
<Resource Include="Resources\folder-open.png" />
<Resource Include="Resources\fortnite.ico" />
<Resource Include="Resources\gamepad-variant.png" />
<Resource Include="Resources\github-circle.png" />
<Resource Include="Resources\icon-creator.png" />
<Resource Include="Resources\image-filter-black-white.png" />
<Resource Include="Resources\image-move.png" />
<Resource Include="Resources\image-plus.png" />
<Resource Include="Resources\image-remove.png" />
<Resource Include="Resources\information.png" />
<Resource Include="Resources\key.png" />
<Resource Include="Resources\lock-open-variant.ico" />
<Resource Include="Resources\magnify.png" />
<Resource Include="Resources\minecraftdungeons.ico" />
<Resource Include="Resources\open-in-new.png" />
<Resource Include="Resources\pause.png" />
<Resource Include="Resources\paypal.png" />
<Resource Include="Resources\pencil.png" />
<Resource Include="Resources\play.png" />
<Resource Include="Resources\playlist-plus.png" />
<Resource Include="Resources\power.png" />
<Resource Include="Resources\progress-download.png" />
<Resource Include="Resources\refresh.png" />
<Resource Include="Resources\settings.png" />
<Resource Include="Resources\share-all.png" />
<Resource Include="Resources\share.png" />
<Resource Include="Resources\sign-direction-plus.png" />
<Resource Include="Resources\sign-direction-remove.png" />
<Resource Include="Resources\sign-direction.png" />
<Resource Include="Resources\spellbreak.ico" />
<Resource Include="Resources\stop.png" />
<Resource Include="Resources\sod2.ico" />
<Resource Include="Resources\T-Icon-Pets-64.png" />
<Resource Include="Resources\T-Icon-Quests-64.png" />
<Resource Include="Resources\trello.png" />
<Resource Include="Resources\thecycle.ico" />
<ProjectReference Include="..\CUE4Parse\CUE4Parse-Conversion\CUE4Parse-Conversion.csproj" />
<ProjectReference Include="..\CUE4Parse\CUE4Parse\CUE4Parse.csproj" />
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\android.png" />
<Resource Include="Resources\apple.png" />
<Resource Include="Resources\battlebreakers.png" />
<Resource Include="Resources\blueprint.png" />
<Resource Include="Resources\borderlands.png" />
<Resource Include="Resources\fallenorder.png" />
<Resource Include="Resources\FModel.ico" />
<Resource Include="Resources\folder.png" />
<Resource Include="Resources\label.png" />
<Resource Include="Resources\fortnite.png" />
<Resource Include="Resources\fortnitebr.png" />
<Resource Include="Resources\empty_folder.png" />
<Resource Include="Resources\engine.png" />
<Resource Include="Resources\gear.png" />
<Resource Include="Resources\localization.png" />
<Resource Include="Resources\materialicon.png" />
<Resource Include="Resources\square.png" />
<Resource Include="Resources\square_off.png" />
<Resource Include="Resources\cube.png" />
<Resource Include="Resources\cube_off.png" />
<Resource Include="Resources\light.png" />
<Resource Include="Resources\light_off.png" />
<Resource Include="Resources\pc.png" />
<Resource Include="Resources\puzzle.png" />
<Resource Include="Resources\roguecompany.png" />
<Resource Include="Resources\spellbreak.png" />
<Resource Include="Resources\sound.png" />
<Resource Include="Resources\creative.png" />
<Resource Include="Resources\texture.png" />
<Resource Include="Resources\thecycle.png" />
<Resource Include="Resources\valorant.png" />
<Resource Include="Resources\ui.png" />
<Resource Include="Resources\weapon.png" />
<Resource Include="Resources\windows.png" />
<Resource Include="Resources\cinematics.png" />
<Resource Include="Resources\archive.png" />
<Resource Include="Resources\archive_enabled.png" />
<Resource Include="Resources\archive_disabled.png" />
<Resource Include="Resources\unknown_asset.png" />
<Resource Include="Resources\asset.png" />
<Resource Include="Resources\asset_ini.png" />
<Resource Include="Resources\asset_psd.png" />
<Resource Include="Resources\asset_png.png" />
<Resource Include="Resources\athena.png" />
<Resource Include="Resources\unix.png" />
<Resource Include="Resources\linux.png" />
<Resource Include="Resources\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_Placeholder_Challenge_Image.png" />
<Resource Include="Resources\T_Placeholder_Item_Image.png" />
<Resource Include="Resources\T_ReloadTime_Weapon_Stats.png" />
<Resource Include="Resources\valorant.live.ico" />
<Resource Include="Resources\view-dashboard.png" />
<Resource Include="Resources\volume-minus.png" />
<Resource Include="Resources\volume-mute.png" />
<Resource Include="Resources\volume-plus.png" />
<Resource Include="Resources\wifi-strength-off.ico" />
<Resource Include="Resources\zip-box.png" />
<Resource Include="Resources\T-Icon-Pets-64.png" />
<Resource Include="Resources\T-Icon-Quests-64.png" />
<Resource Include="Resources\Default.png" />
<Resource Include="Resources\NoBackground.png" />
<Resource Include="Resources\NoText.png" />
<Resource Include="Resources\Flat.png" />
<Resource Include="Resources\Cataba.png" />
<Resource Include="Resources\BurbankBigCondensed-Bold.ttf" />
<Resource Include="Resources\add_directory.png" />
<Resource Include="Resources\delete.png" />
<Resource Include="Resources\edit.png" />
<Resource Include="Resources\go_to_directory.png" />
<Resource Include="Resources\npcleftside.png" />
<Resource Include="Resources\nx.png" />
<Resource Include="Resources\ny.png" />
<Resource Include="Resources\nz.png" />
<Resource Include="Resources\px.png" />
<Resource Include="Resources\py.png" />
<Resource Include="Resources\pz.png" />
<Resource Include="Resources\pointlight.png" />
<Resource Include="Resources\spotlight.png" />
<Resource Include="Resources\link_on.png" />
<Resource Include="Resources\link_off.png" />
<Resource Include="Resources\link_has.png" />
<Resource Include="Resources\tl_play.png" />
<Resource Include="Resources\tl_pause.png" />
<Resource Include="Resources\tl_rewind.png" />
<Resource Include="Resources\tl_forward.png" />
<Resource Include="Resources\tl_previous.png" />
<Resource Include="Resources\tl_next.png" />
</ItemGroup>
<ItemGroup>
@ -234,11 +263,6 @@
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Update="Properties\Settings.Designer.cs">
<DesignTimeSharedInput>True</DesignTimeSharedInput>
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
@ -248,10 +272,4 @@
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Update="Properties\Settings.settings">
<Generator>PublicSettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
</Project>

37
FModel/FModel.sln Normal file
View File

@ -0,0 +1,37 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31912.275
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FModel", "FModel.csproj", "{B1F494EA-90A6-4C24-800E-2F724A1884CA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CUE4Parse", "..\CUE4Parse\CUE4Parse\CUE4Parse.csproj", "{C4620341-BBB7-4384-AC7D-5082D3E0386E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CUE4Parse-Conversion", "..\CUE4Parse\CUE4Parse-Conversion\CUE4Parse-Conversion.csproj", "{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B1F494EA-90A6-4C24-800E-2F724A1884CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B1F494EA-90A6-4C24-800E-2F724A1884CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B1F494EA-90A6-4C24-800E-2F724A1884CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B1F494EA-90A6-4C24-800E-2F724A1884CA}.Release|Any CPU.Build.0 = Release|Any CPU
{C4620341-BBB7-4384-AC7D-5082D3E0386E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C4620341-BBB7-4384-AC7D-5082D3E0386E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C4620341-BBB7-4384-AC7D-5082D3E0386E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C4620341-BBB7-4384-AC7D-5082D3E0386E}.Release|Any CPU.Build.0 = Release|Any CPU
{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {53DB7A15-4E15-4575-9402-0110BDF2794E}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,32 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks.Dataflow;
namespace FModel.Framework;
public class AsyncQueue<T> : IAsyncEnumerable<T>
{
private readonly SemaphoreSlim _semaphore = new(1);
private readonly BufferBlock<T> _buffer = new();
public int Count => _buffer.Count;
public void Enqueue(T item) => _buffer.Post(item);
public async IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken token = default)
{
await _semaphore.WaitAsync(token);
try
{
while (Count > 0)
{
token.ThrowIfCancellationRequested();
yield return await _buffer.ReceiveAsync(token);
}
}
finally
{
_semaphore.Release();
}
}
}

View File

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

View File

@ -0,0 +1,90 @@
using System;
using HarfBuzzSharp;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
using Buffer = HarfBuzzSharp.Buffer;
namespace FModel.Framework;
public class CustomSKShaper : SKShaper
{
private const int _FONT_SIZE_SCALE = 512;
private readonly Font _font;
public CustomSKShaper(SKTypeface typeface) : base(typeface)
{
using var blob = Typeface.OpenStream(out var index).ToHarfBuzzBlob();
using var face = new Face(blob, index);
face.Index = index;
face.UnitsPerEm = Typeface.UnitsPerEm;
_font = new Font(face);
_font.SetScale(_FONT_SIZE_SCALE, _FONT_SIZE_SCALE);
_font.SetFunctionsOpenType();
}
public new Result Shape(Buffer buffer, float xOffset, float yOffset, SKPaint paint)
{
if (buffer == null)
throw new ArgumentNullException(nameof(buffer));
if (paint == null)
throw new ArgumentNullException(nameof(paint));
// do the shaping
_font.Shape(buffer);
// get the shaping results
var len = buffer.Length;
var info = buffer.GlyphInfos;
var pos = buffer.GlyphPositions;
// get the sizes
var textSizeY = paint.TextSize / _FONT_SIZE_SCALE;
var textSizeX = textSizeY * paint.TextScaleX;
var points = new SKPoint[len];
var clusters = new uint[len];
var codepoints = new uint[len];
for (var i = 0; i < len; i++)
{
// move the cursor
xOffset += pos[i].XAdvance * textSizeX;
yOffset += pos[i].YAdvance * textSizeY;
codepoints[i] = info[i].Codepoint;
clusters[i] = info[i].Cluster;
points[i] = new SKPoint(xOffset + pos[i].XOffset * textSizeX, yOffset - pos[i].YOffset * textSizeY);
}
return new Result(codepoints, clusters, points, points[^1].X);
}
public new Result Shape(string text, SKPaint paint) => Shape(text, 0, 0, paint);
public new Result Shape(string text, float xOffset, float yOffset, SKPaint paint)
{
if (string.IsNullOrEmpty(text))
return new Result();
using var buffer = new Buffer();
switch (paint.TextEncoding)
{
case SKTextEncoding.Utf8:
buffer.AddUtf8(text);
break;
case SKTextEncoding.Utf16:
buffer.AddUtf16(text);
break;
case SKTextEncoding.Utf32:
buffer.AddUtf32(text);
break;
default:
throw new NotSupportedException("TextEncoding of type GlyphId is not supported.");
}
buffer.GuessSegmentProperties();
return Shape(buffer, xOffset, yOffset, paint);
}
}

View File

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

View File

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

View File

@ -0,0 +1,116 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
namespace FModel.Framework;
public class FullyObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
/// <summary>
/// Occurs when a property is changed within an item.
/// </summary>
public event EventHandler<ItemPropertyChangedEventArgs> ItemPropertyChanged;
public FullyObservableCollection()
{
}
public FullyObservableCollection(List<T> list) : base(list)
{
ObserveAll();
}
public FullyObservableCollection(IEnumerable<T> enumerable) : base(enumerable)
{
ObserveAll();
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.Action is NotifyCollectionChangedAction.Remove or
NotifyCollectionChangedAction.Replace)
{
foreach (T item in e.OldItems)
item.PropertyChanged -= ChildPropertyChanged;
}
if (e.Action is NotifyCollectionChangedAction.Add or
NotifyCollectionChangedAction.Replace)
{
foreach (T item in e.NewItems)
item.PropertyChanged += ChildPropertyChanged;
}
base.OnCollectionChanged(e);
}
protected void OnItemPropertyChanged(ItemPropertyChangedEventArgs e)
{
ItemPropertyChanged?.Invoke(this, e);
}
protected void OnItemPropertyChanged(int index, PropertyChangedEventArgs e)
{
OnItemPropertyChanged(new ItemPropertyChangedEventArgs(index, e));
}
protected override void ClearItems()
{
foreach (T item in Items)
item.PropertyChanged -= ChildPropertyChanged;
base.ClearItems();
}
private void ObserveAll()
{
foreach (var item in Items)
item.PropertyChanged += ChildPropertyChanged;
}
private void ChildPropertyChanged(object sender, PropertyChangedEventArgs e)
{
var typedSender = (T) sender;
var i = Items.IndexOf(typedSender);
if (i < 0)
throw new ArgumentException("Received property notification from item not in collection");
OnItemPropertyChanged(i, e);
}
}
/// <summary>
/// Provides data for the <see cref="FullyObservableCollection{T}.ItemPropertyChanged"/> event.
/// </summary>
public class ItemPropertyChangedEventArgs : PropertyChangedEventArgs
{
/// <summary>
/// Gets the index in the collection for which the property change has occurred.
/// </summary>
/// <value>
/// Index in parent collection.
/// </value>
public int CollectionIndex { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ItemPropertyChangedEventArgs"/> class.
/// </summary>
/// <param name="index">The index in the collection of changed item.</param>
/// <param name="name">The name of the property that changed.</param>
public ItemPropertyChangedEventArgs(int index, string name) : base(name)
{
CollectionIndex = index;
}
/// <summary>
/// Initializes a new instance of the <see cref="ItemPropertyChangedEventArgs"/> class.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="args">The <see cref="PropertyChangedEventArgs"/> instance containing the event data.</param>
public ItemPropertyChangedEventArgs(int index, PropertyChangedEventArgs args) : this(index, args.PropertyName)
{
}
}

View File

@ -0,0 +1,49 @@
using System.Text;
using System.Windows.Input;
namespace FModel.Framework;
public class Hotkey : ViewModel
{
private Key _key;
public Key Key
{
get => _key;
set => SetProperty(ref _key, value);
}
private ModifierKeys _modifiers;
public ModifierKeys Modifiers
{
get => _modifiers;
set => SetProperty(ref _modifiers, value);
}
public Hotkey(Key key, ModifierKeys modifiers = ModifierKeys.None)
{
Key = key;
Modifiers = modifiers;
}
public bool IsTriggered(Key e)
{
return e == Key && Keyboard.Modifiers.HasFlag(Modifiers);
}
public override string ToString()
{
var str = new StringBuilder();
if (Modifiers.HasFlag(ModifierKeys.Control))
str.Append("Ctrl + ");
if (Modifiers.HasFlag(ModifierKeys.Shift))
str.Append("Shift + ");
if (Modifiers.HasFlag(ModifierKeys.Alt))
str.Append("Alt + ");
if (Modifiers.HasFlag(ModifierKeys.Windows))
str.Append("Win + ");
str.Append(Key);
return str.ToString();
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,75 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using Newtonsoft.Json;
namespace FModel.Framework;
public class ViewModel : INotifyPropertyChanged, INotifyDataErrorInfo, IDataErrorInfo
{
private readonly Dictionary<string, IList<string>> _validationErrors = new();
public string this[string propertyName]
{
get
{
if (string.IsNullOrEmpty(propertyName))
return Error;
return _validationErrors.ContainsKey(propertyName) ? string.Join(Environment.NewLine, _validationErrors[propertyName]) : string.Empty;
}
}
[JsonIgnore] public string Error => string.Join(Environment.NewLine, GetAllErrors());
[JsonIgnore] public bool HasErrors => _validationErrors.Any();
public IEnumerable GetErrors(string propertyName)
{
if (string.IsNullOrEmpty(propertyName))
return _validationErrors.SelectMany(kvp => kvp.Value);
return _validationErrors.TryGetValue(propertyName, out var errors) ? errors : Enumerable.Empty<object>();
}
private IEnumerable<string> GetAllErrors()
{
return _validationErrors.SelectMany(kvp => kvp.Value).Where(e => !string.IsNullOrEmpty(e));
}
public void AddValidationError(string propertyName, string errorMessage)
{
if (!_validationErrors.ContainsKey(propertyName))
_validationErrors.Add(propertyName, new List<string>());
_validationErrors[propertyName].Add(errorMessage);
}
public void ClearValidationErrors(string propertyName)
{
if (_validationErrors.ContainsKey(propertyName))
_validationErrors.Remove(propertyName);
}
public event PropertyChangedEventHandler PropertyChanged;
#pragma warning disable 67
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
#pragma warning disable 67
protected void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(storage, value))
return false;
storage = value;
RaisePropertyChanged(propertyName);
return true;
}
}

View File

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

View File

@ -1,83 +0,0 @@
using PakReader.Pak;
using PakReader.Parsers.Objects;
using System;
using System.Collections.Generic;
using System.Windows;
using ToastNotifications;
using ToastNotifications.Lifetime;
using ToastNotifications.Position;
namespace FModel
{
static class Globals
{
/// <summary>
/// string is the pak file name
/// PakFileReader is the reader where you can grab the FPakEntries, MountPoint and more
/// </summary>
public static readonly Dictionary<string, PakFileReader> CachedPakFiles = new Dictionary<string, PakFileReader>();
public static readonly Notifier gNotifier = new Notifier(cfg =>
{
cfg.LifetimeSupervisor = new TimeAndCountBasedLifetimeSupervisor(TimeSpan.FromSeconds(7), MaximumNotificationCount.FromCount(15));
cfg.PositionProvider = new PrimaryScreenPositionProvider(Corner.BottomRight, 5, 5);
cfg.Dispatcher = Application.Current.Dispatcher;
});
public static bool bSearch = false; // trigger the event to select a file thank to the search window
public static string sSearch = string.Empty; // this will be the file name triggered
public static FGame Game = new FGame(EGame.Unknown, EPakVersion.LATEST, 0);
public const EFModel Build =
#if RELEASE
EFModel.Release;
#elif DEBUG
EFModel.Debug;
#else
EFModel.Unknown;
#endif
}
public class FGame
{
public EGame ActualGame;
public EPakVersion Version;
public int SubVersion;
public FGame(EGame game, EPakVersion version, int subVersion)
{
ActualGame = game;
Version = version;
SubVersion = subVersion;
}
public string GetName()
{
return ActualGame switch
{
EGame.Fortnite => "Fortnite",
EGame.Valorant => "Valorant",
EGame.DeadByDaylight => "Dead By Daylight",
EGame.Borderlands3 => "Borderlands 3",
EGame.MinecraftDungeons => "Minecraft Dungeons",
EGame.BattleBreakers => "Battle Breakers",
EGame.Spellbreak => "Spellbreak",
EGame.StateOfDecay2 => "State of Decay 2",
EGame.TheCycleEA => "The Cycle (Early Access)",
EGame.Unknown => "Unknown",
_ => "Unknown",
};
}
}
static class FColors
{
public const string Red = "#E06C75";
public const string Orange = "#D19A66";
public const string Yellow = "#E5C07B";
public const string Purple = "#C678DD";
public const string Blue = "#61AFEF";
public const string Discord = "#8B9BD4";
public const string Green = "#98C379";
public const string LightGray = "#BBBBBB";
public const string DarkGray = "#9B9B9B";
public const string White = "#EFEFEF";
}
}

View File

@ -1,37 +0,0 @@
using FModel.Logger;
using FModel.Utils;
using FModel.Windows.CustomNotifier;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Net.NetworkInformation;
using System.Threading.Tasks;
namespace FModel.Grabber.Aes
{
static class AesData
{
public static async Task<BenResponse> GetData()
{
if (NetworkInterface.GetIsNetworkAvailable())
{
BenResponse data = await Endpoints.GetJsonEndpoint<BenResponse>(Endpoints.BENBOT_AES, string.Empty).ConfigureAwait(false);
return data;
}
else
{
Globals.gNotifier.ShowCustomMessage(Properties.Resources.AES, Properties.Resources.NoInternet, "/FModel;component/Resources/wifi-strength-off.ico");
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[AES]", "No internet");
return null;
}
}
}
public class BenResponse
{
[JsonProperty("mainKey")]
public string MainKey { get; set; }
[JsonProperty("dynamicKeys")]
public Dictionary<string, string> DynamicKeys { get; set; }
}
}

View File

@ -1,71 +0,0 @@
using FModel.Logger;
using FModel.ViewModels.MenuItem;
using FModel.Windows.CustomNotifier;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace FModel.Grabber.Aes
{
static class AesGrabber
{
public static async Task<bool> Load(bool forceReload = false)
{
if (Globals.Game.ActualGame == EGame.Fortnite && MenuItems.pakFiles.AtLeastOnePak())
{
if (forceReload)
{
Dictionary<string, string> staticKeys = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(Properties.Settings.Default.StaticAesKeys))
staticKeys = JsonConvert.DeserializeObject<Dictionary<string, string>>(Properties.Settings.Default.StaticAesKeys);
Dictionary<string, Dictionary<string, string>> oldDynamicKeys = new Dictionary<string, Dictionary<string, string>>();
try
{
if (!string.IsNullOrEmpty(Properties.Settings.Default.DynamicAesKeys))
oldDynamicKeys = JsonConvert.DeserializeObject<Dictionary<string, Dictionary<string, string>>>(Properties.Settings.Default.DynamicAesKeys);
}
catch (JsonSerializationException) { /* Needed for the transition bewteen global dynamic keys and "per game" dynamic keys */ }
BenResponse benResponse = await AesData.GetData().ConfigureAwait(false);
if (benResponse != null)
{
if (!string.IsNullOrEmpty(benResponse.MainKey))
{
string mainKey = $"0x{benResponse.MainKey.Substring(2).ToUpper()}";
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[AES]", $"BenBot Main key is {mainKey}");
staticKeys[Globals.Game.ActualGame.ToString()] = mainKey;
Properties.Settings.Default.StaticAesKeys = JsonConvert.SerializeObject(staticKeys, Formatting.None);
}
if (oldDynamicKeys.TryGetValue(Globals.Game.ActualGame.ToString(), out var gameDict))
{
Dictionary<string, string> difference = benResponse.DynamicKeys
.Where(x => !gameDict.ContainsKey(x.Key) || !gameDict[x.Key].Equals(x.Value))
.ToDictionary(x => x.Key, x => x.Value);
foreach (KeyValuePair<string, string> KvP in difference)
{
Globals.gNotifier.ShowCustomMessage(
Properties.Resources.PakFiles,
string.Format(
Properties.Resources.PakCanBeOpened,
KvP.Key.Substring(KvP.Key.IndexOf("Paks/") + "Paks/".Length)),
"/FModel;component/Resources/lock-open-variant.ico");
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[AES]", $"{KvP.Key} with key {KvP.Value} can be opened");
}
}
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[AES]", $"BenBot Dynamic keys are {benResponse.DynamicKeys}");
oldDynamicKeys[Globals.Game.ActualGame.ToString()] = benResponse.DynamicKeys;
Properties.Settings.Default.DynamicAesKeys = JsonConvert.SerializeObject(oldDynamicKeys, Formatting.None);
Properties.Settings.Default.Save();
return true;
}
}
}
return false;
}
}
}

View File

@ -1,53 +0,0 @@
using AutoUpdaterDotNET;
using FModel.Logger;
using FModel.Utils;
using FModel.Windows.CustomNotifier;
using System.Collections.Generic;
using System.Net.NetworkInformation;
using System.Threading.Tasks;
namespace FModel.Grabber.Cdn
{
static class CdnData
{
public static readonly bool bInternet = NetworkInterface.GetIsNetworkAvailable();
public static async Task<CdnResponse> GetData()
{
if (bInternet)
return await Endpoints.GetJsonEndpoint<CdnResponse>(Endpoints.FMODEL_JSON).ConfigureAwait(false);
else
{
Globals.gNotifier.ShowCustomMessage("CDN", Properties.Resources.NoInternet, "/FModel;component/Resources/wifi-strength-off.ico");
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[CDN]", "No internet");
return null;
}
}
}
public class CdnResponse
{
public Dictionary<string, Backup[]> Backups { get; set; }
public Dictionary<string, GlobalMessage[]> GlobalMessages { get; set; }
public FUpdater Updater { get; set; }
}
public class Backup
{
public string Header { get; set; }
public string DownloadUrl { get; set; }
public double Size { get; set; }
}
public class GlobalMessage
{
public string Message { get; set; }
public string Color { get; set; }
public bool NewLine { get; set; }
}
public class FUpdater
{
public string Version { get; set; }
public string Url { get; set; }
public string Changelog { get; set; }
public Mandatory Mandatory { get; set; }
}
}

View File

@ -1,102 +0,0 @@
using FModel.Logger;
using FModel.Utils;
using FModel.ViewModels.MenuItem;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
namespace FModel.Grabber.Cdn
{
static class CdnDataGrabber
{
private static CdnResponse _data = null; // avoid multiple requests on same endpoint
public static async Task DoCDNStuff()
{
await PopulateBackups().ConfigureAwait(false); // step by step
await ShowGMessages().ConfigureAwait(false); // step by step
}
public static async Task PopulateBackups()
{
Application.Current.Dispatcher.Invoke(delegate
{
// Backup PAKs Menu Item
MenuItems.backupFiles.Add(new BackupMenuItemViewModel
{
Header = Properties.Resources.BackupPaks,
Icon = new Image { Source = new BitmapImage(new Uri("Resources/backup-restore.png", UriKind.Relative)) }
});
MenuItems.backupFiles.Add(new Separator { });
});
List<BackupMenuItemViewModel> backupsInfos = await GetBackups().ConfigureAwait(false);
if (backupsInfos.Any())
{
foreach (BackupMenuItemViewModel b in backupsInfos)
{
Application.Current.Dispatcher.Invoke(delegate
{
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[CDN]", $"{b.Header} is available to download");
MenuItems.backupFiles.Add(b);
});
}
}
}
public static async Task<FUpdater> GetUpdateData()
{
if (_data == null)
_data = await CdnData.GetData().ConfigureAwait(false);
if (_data != null)
{
return _data.Updater;
}
return null;
}
private static async Task<List<BackupMenuItemViewModel>> GetBackups()
{
if (_data == null)
_data = await CdnData.GetData().ConfigureAwait(false);
if (_data != null && _data.Backups.TryGetValue(Globals.Game.ActualGame.ToString(), out var backups))
{
return JsonConvert.DeserializeObject<List<BackupMenuItemViewModel>>(JsonConvert.SerializeObject(backups));
}
return new List<BackupMenuItemViewModel>();
}
public static async Task ShowGMessages()
{
Dictionary<string, GlobalMessage[]> globalMessages = await GetGlobalMessages().ConfigureAwait(false);
if (globalMessages.Any())
{
string version = Assembly.GetExecutingAssembly().GetName().Version.ToString();
if (globalMessages.ContainsKey(version))
if (!string.IsNullOrEmpty(globalMessages[version][0].Message))
foreach (GlobalMessage gm in globalMessages[version])
FConsole.AppendText(gm.Message, gm.Color, gm.NewLine);
}
}
private static async Task<Dictionary<string, GlobalMessage[]>> GetGlobalMessages()
{
if (_data == null && CdnData.bInternet) // if data is still null after getting backups, that means there's no internet, do not try again
_data = await CdnData.GetData().ConfigureAwait(false); // ^ will also avoid showing "No internet" notifier twice
if (_data != null)
{
return JsonConvert.DeserializeObject<Dictionary<string, GlobalMessage[]>>(JsonConvert.SerializeObject(_data.GlobalMessages));
}
return new Dictionary<string, GlobalMessage[]>();
}
}
}

View File

@ -1,11 +0,0 @@
using Newtonsoft.Json;
using System.Collections.Generic;
namespace FModel.Grabber.Paks
{
public class InstallsJson
{
[JsonProperty("associated_client")]
public Dictionary<string, string> AssociatedClient;
}
}

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