Compare commits

..

841 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
290 changed files with 27602 additions and 12509 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

View File

@ -1,7 +1,12 @@
name: Artifact Generator
name: FModel Builder
on:
workflow_dispatch:
inputs:
appVersion:
description: 'FModel Version And Release Tag'
required: true
default: '4.0.X.X'
jobs:
build:
@ -12,21 +17,32 @@ jobs:
uses: actions/checkout@v2
with:
submodules: 'true'
token: ${{ secrets.PAT_TOKEN }}
- name: .NET 5 Setup
uses: actions/setup-dotnet@v1
- name: Fetch Submodules Recursively
run: git submodule update --init --recursive
- name: .NET 8 Setup
uses: actions/setup-dotnet@v2
with:
dotnet-version: 5.0.x
dotnet-version: '8.0.x'
- name: .NET Restore
run: dotnet restore FModel
- name: .NET Publish
run: dotnet publish FModel -c Release -f net5.0-windows -o "./FModel/bin/Publish/" -p:PublishReadyToRun=true -p:PublishSingleFile=true -p:DebugType=None -p:GenerateDocumentationFile=false -p:DebugSymbols=false --no-self-contained -r win-x64
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: EXE Upload
uses: actions/upload-artifact@v2
- name: ZIP File
uses: papeloto/action-zip@v1
with:
name: FModel
path: D:\a\FModel\FModel\FModel\bin\Publish\
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"}'

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

View File

@ -1,10 +1,10 @@
using AdonisUI.Controls;
using AdonisUI.Controls;
using Microsoft.Win32;
using Serilog;
using System;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
using FModel.Framework;
@ -16,109 +16,163 @@ 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
[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)
{
protected override void OnStartup(StartupEventArgs e)
#if DEBUG
AttachConsole(-1);
#endif
base.OnStartup(e);
try
{
base.OnStartup(e);
try
{
UserSettings.Default = JsonConvert.DeserializeObject<UserSettings>(
File.ReadAllText(UserSettings.FilePath), JsonNetSerializer.SerializerSettings);
}
catch
{
UserSettings.Default = new UserSettings();
}
if (!Directory.Exists(UserSettings.Default.OutputDirectory))
{
UserSettings.Default.OutputDirectory = Path.Combine(Directory.GetCurrentDirectory(), "Output");
}
Directory.CreateDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FModel"));
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Backups"));
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Exports"));
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Saves"));
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Textures"));
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Sounds"));
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Logs"));
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data"));
Log.Logger = new LoggerConfiguration().WriteTo.Console(theme: AnsiConsoleTheme.Literate).WriteTo.File(
path: Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Log-{DateTime.Now:yyyy-MM-dd}.txt"),
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [FModel] [{Level:u3}] {Message:lj}{NewLine}{Exception}").CreateLogger();
Log.Information("Version {Version}", Constants.APP_VERSION);
Log.Information("{OS}", GetOperatingSystemProductName());
Log.Information("{RuntimeVer}", RuntimeInformation.FrameworkDescription);
Log.Information("Culture {SysLang}", Thread.CurrentThread.CurrentUICulture);
UserSettings.Default = JsonConvert.DeserializeObject<UserSettings>(
File.ReadAllText(UserSettings.FilePath), JsonNetSerializer.SerializerSettings);
}
catch
{
UserSettings.Default = new UserSettings();
}
private void AppExit(object sender, ExitEventArgs e)
var createMe = false;
if (!Directory.Exists(UserSettings.Default.OutputDirectory))
{
Log.Information("");
Log.CloseAndFlush();
UserSettings.Save();
Environment.Exit(0);
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");
}
private void OnUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
if (!Directory.Exists(UserSettings.Default.RawDataDirectory))
{
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[]
{
MessageBoxButtons.Custom("Restart", EErrorKind.Restart),
MessageBoxButtons.Custom("OK", EErrorKind.Ignore)
},
IsSoundEnabled = false
};
MessageBox.Show(messageBox);
if (messageBox.Result == MessageBoxResult.Custom && (EErrorKind) messageBox.ButtonPressed.Id != EErrorKind.Ignore)
{
ApplicationService.ApplicationView.Restart();
}
e.Handled = true;
createMe = true;
UserSettings.Default.RawDataDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
}
private string GetOperatingSystemProductName()
if (!Directory.Exists(UserSettings.Default.PropertiesDirectory))
{
var productName = string.Empty;
try
{
productName = GetRegistryValue(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "ProductName", RegistryHive.LocalMachine);
}
catch
{
// ignored
}
if (string.IsNullOrEmpty(productName))
productName = Environment.OSVersion.VersionString;
return $"{productName} ({(Environment.Is64BitOperatingSystem ? "64" : "32")}-bit)";
createMe = true;
UserSettings.Default.PropertiesDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
}
private string GetRegistryValue(string path, string name = null, RegistryHive root = RegistryHive.CurrentUser)
if (!Directory.Exists(UserSettings.Default.TextureDirectory))
{
using var rk = RegistryKey.OpenBaseKey(root, RegistryView.Default).OpenSubKey(path);
if (rk != null)
return rk.GetValue(name, null) as string;
return string.Empty;
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[]
{
MessageBoxButtons.Custom("Reset Settings", EErrorKind.ResetSettings),
MessageBoxButtons.Custom("Restart", EErrorKind.Restart),
MessageBoxButtons.Custom("OK", EErrorKind.Ignore)
},
IsSoundEnabled = false
};
MessageBox.Show(messageBox);
if (messageBox.Result == MessageBoxResult.Custom && (EErrorKind) messageBox.ButtonPressed.Id != EErrorKind.Ignore)
{
if ((EErrorKind) messageBox.ButtonPressed.Id == EErrorKind.ResetSettings)
UserSettings.Delete();
ApplicationService.ApplicationView.Restart();
}
e.Handled = true;
}
private string GetOperatingSystemProductName()
{
var productName = string.Empty;
try
{
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;
}
}

View File

@ -1,63 +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
namespace FModel;
public static class Constants
{
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 =
{
public static readonly string APP_VERSION = Assembly.GetExecutingAssembly().GetName().Version?.ToString();
public const string ZERO_64_CHAR = "0000000000000000000000000000000000000000000000000000000000000000";
public static readonly FGuid ZERO_GUID = new(0U);
public const string WHITE = "#DAE5F2";
public const string RED = "#E06C75";
public const string GREEN = "#98C379";
public const string YELLOW = "#E5C07B";
public const string BLUE = "#528BCC";
public const string DONATE_LINK = "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=EP9SSWG8MW4UC&source=url";
public const string CHANGELOG_LINK = "https://github.com/iAmAsval/FModel/releases/latest";
public const string ISSUE_LINK = "https://github.com/iAmAsval/FModel/issues/new";
public const string DISCORD_LINK = "https://discord.gg/fdkNYYQ";
public const string _FN_LIVE_TRIGGER = "fortnite-live.manifest";
public const string _VAL_LIVE_TRIGGER = "valorant-live.manifest";
public static string GetRandomColor()
{
return _randomColors[_random.Next(0, 255)];
}
private static readonly Random _random = new(Environment.TickCount);
private static readonly string[] _randomColors =
{
"F44336", "FFEBEE", "FFCDD2", "EF9A9A", "E57373", "EF5350", "E53935", "D32F2F", "C62828", "B71C1C",
"FF8A80", "FF5252", "FF1744", "D50000", "FCE4EC", "F8BBD0", "F48FB1", "F06292", "EC407A", "E91E63",
"D81B60", "C2185B", "AD1457", "880E4F", "FF80AB", "FF4081", "F50057", "C51162", "F3E5F5", "E1BEE7",
"CE93D8", "BA68C8", "AB47BC", "9C27B0", "8E24AA", "7B1FA2", "6A1B9A", "4A148C", "EA80FC", "E040FB",
"D500F9", "AA00FF", "EDE7F6", "D1C4E9", "B39DDB", "9575CD", "7E57C2", "673AB7", "5E35B1", "512DA8",
"4527A0", "311B92", "B388FF", "7C4DFF", "651FFF", "6200EA", "E8EAF6", "C5CAE9", "9FA8DA", "7986CB",
"5C6BC0", "3F51B5", "3949AB", "303F9F", "283593", "1A237E", "8C9EFF", "536DFE", "3D5AFE", "304FFE",
"E3F2FD", "BBDEFB", "90CAF9", "64B5F6", "42A5F5", "2196F3", "1E88E5", "1976D2", "1565C0", "0D47A1",
"82B1FF", "448AFF", "2979FF", "2962FF", "E1F5FE", "B3E5FC", "81D4FA", "4FC3F7", "29B6F6", "03A9F4",
"039BE5", "0288D1", "0277BD", "01579B", "80D8FF", "40C4FF", "00B0FF", "0091EA", "E0F7FA", "B2EBF2",
"80DEEA", "4DD0E1", "26C6DA", "00BCD4", "00ACC1", "0097A7", "00838F", "006064", "84FFFF", "18FFFF",
"00E5FF", "00B8D4", "E0F2F1", "B2DFDB", "80CBC4", "4DB6AC", "26A69A", "009688", "00897B", "00796B",
"00695C", "004D40", "A7FFEB", "64FFDA", "1DE9B6", "00BFA5", "E8F5E9", "C8E6C9", "A5D6A7", "81C784",
"66BB6A", "4CAF50", "43A047", "388E3C", "2E7D32", "1B5E20", "B9F6CA", "69F0AE", "00E676", "00C853",
"F1F8E9", "DCEDC8", "C5E1A5", "AED581", "9CCC65", "8BC34A", "7CB342", "689F38", "558B2F", "33691E",
"CCFF90", "B2FF59", "76FF03", "64DD17", "F9FBE7", "F0F4C3", "E6EE9C", "DCE775", "D4E157", "CDDC39",
"C0CA33", "AFB42B", "9E9D24", "827717", "F4FF81", "EEFF41", "C6FF00", "AEEA00", "FFFDE7", "FFF9C4",
"FFF59D", "FFF176", "FFEE58", "FFEB3B", "FDD835", "FBC02D", "F9A825", "F57F17", "FFFF8D", "FFFF00",
"FFEA00", "FFD600", "FFF8E1", "FFECB3", "FFE082", "FFD54F", "FFCA28", "FFC107", "FFB300", "FFA000",
"FF8F00", "FF6F00", "FFE57F", "FFD740", "FFC400", "FFAB00", "FFF3E0", "FFE0B2", "FFCC80", "FFB74D",
"FFA726", "FF9800", "FB8C00", "F57C00", "EF6C00", "E65100", "FFD180", "FFAB40", "FF9100", "FF6D00",
"FBE9E7", "FFCCBC", "FFAB91", "FF8A65", "FF7043", "FF5722", "F4511E", "E64A19", "D84315", "BF360C",
"FF9E80", "FF6E40", "FF3D00", "DD2C00", "EFEBE9", "D7CCC8", "BCAAA4", "A1887F", "8D6E63", "795548",
"6D4C41", "5D4037", "4E342E", "3E2723", "FAFAFA", "F5F5F5", "EEEEEE", "E0E0E0", "BDBDBD", "9E9E9E",
"757575", "616161", "424242", "212121", "ECEFF1", "CFD8DC", "B0BEC5", "90A4AE", "78909C", "607D8B",
"546E7A", "455A64", "37474F", "263238", "000000",
};
}
}
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

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

View File

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

View File

@ -1,8 +1,10 @@
using CUE4Parse.UE4.Assets.Exports;
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 CUE4Parse_Fortnite.Enums;
using FModel.Extensions;
using FModel.Framework;
using FModel.Services;
@ -11,260 +13,150 @@ using FModel.ViewModels.ApiEndpoints.Models;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN
namespace FModel.Creator.Bases.FN;
public class BaseCommunity : BaseIcon
{
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)
{
private readonly CommunityDesign _design;
private string _rarityName;
private string _source;
private string _season;
private bool _lowerDrawn;
Margin = 0;
_lowerDrawn = false;
_design = ApplicationService.ApiEndpointView.FModelApi.GetDesign(designName);
}
public BaseCommunity(UObject uObject, EIconStyle style, string designName) : base(uObject, style)
public override void ParseForInfo()
{
ParseForReward(UserSettings.Default.CosmeticDisplayAsset);
if (Object.TryGetValue(out FPackageIndex series, "Series"))
{
Margin = 0;
_lowerDrawn = false;
_design = ApplicationService.ApiEndpointView.FModelApi.GetDesign(designName);
_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();
}
public override void ParseForInfo()
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)
{
ParseForReward(UserSettings.Default.CosmeticDisplayAsset == EEnabledDisabled.Enabled);
if (Object.TryGetValue(out FPackageIndex series, "Series") && Utils.TryGetPackageIndexExport(series, out UObject export))
_rarityName = export.Name;
else
_rarityName = GetRarityName(Object.GetOrDefault<FName>("Rarity"));
if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags"))
CheckGameplayTags(gameplayTags);
if (Object.TryGetValue(out FPackageIndex cosmeticItem, "cosmetic_item"))
CosmeticSource = cosmeticItem.Name.ToUpper();
DisplayName = DisplayName.ToUpper();
Description = Description.ToUpper();
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);
}
public override SKImage Draw()
return new[] { ret };
}
private void CheckGameplayTags(FGameplayTagContainer gameplayTags)
{
if (_design == null) return;
if (_design.DrawSource)
{
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
if (_design == null)
{
base.Draw(c);
}
else
{
DrawBackground(c);
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
DrawDescription(c);
if (_design.DrawSeason && _design.Fonts.TryGetValue("Season", out var font))
DrawToBottom(c, font, _season);
if (_design.DrawSource && _design.Fonts.TryGetValue("Source", out font))
DrawToBottom(c, font, _source);
DrawUserFacingFlags(c, _design.GameplayTags.DrawCustomOnly);
}
return SKImage.FromBitmap(ret);
if (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();
}
private void CheckGameplayTags(FGameplayTagContainer gameplayTags)
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))
{
if (_design == null) return;
if (_design.DrawSource)
{
if (gameplayTags.TryGetGameplayTag("Cosmetics.Source.", out var source))
_source = source.Text["Cosmetics.Source.".Length..].ToUpper();
else if (gameplayTags.TryGetGameplayTag("Athena.ItemAction.", out var action))
_source = action.Text["Athena.ItemAction.".Length..].ToUpper();
}
if (_design.DrawSet && gameplayTags.TryGetGameplayTag("Cosmetics.Set.", out var set))
Description += GetCosmeticSet(set.Text, _design.DrawSetShort);
if (_design.DrawSeason && gameplayTags.TryGetGameplayTag("Cosmetics.Filter.Season.", out var season))
_season = GetCosmeticSeason(season.Text, _design.DrawSeasonShort);
GetUserFacingFlags(gameplayTags.GetAllGameplayTags(
"Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender."));
c.DrawBitmap(rarity.Background, 0, 0, ImagePaint);
c.DrawBitmap(rarity.Upper, 0, 0, ImagePaint);
}
private string GetCosmeticSet(string setName, bool bShort)
else
{
return bShort ? setName["Cosmetics.Set.".Length..] : base.GetCosmeticSet(setName);
base.DrawBackground(c);
}
}
private string GetCosmeticSeason(string seasonNumber, bool bShort)
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))
{
if (!bShort) return base.GetCosmeticSeason(seasonNumber);
var s = seasonNumber["Cosmetics.Filter.Season.".Length..];
var number = int.Parse(s);
if (number == 10)
s = "X";
return number > 10 ? $"C{number / 10 + 1} S{s[^1..]}" : $"C1 S{s}";
c.DrawBitmap(rarity.Lower, 0, 0, ImagePaint);
}
private string GetRarityName(FName r)
else
{
var rarity = EFortRarity.Uncommon;
switch (r.Text)
{
case "EFortRarity::Common":
case "EFortRarity::Handmade":
rarity = EFortRarity.Common;
break;
case "EFortRarity::Rare":
case "EFortRarity::Sturdy":
rarity = EFortRarity.Rare;
break;
case "EFortRarity::Epic":
case "EFortRarity::Quality":
rarity = EFortRarity.Epic;
break;
case "EFortRarity::Legendary":
case "EFortRarity::Fine":
rarity = EFortRarity.Legendary;
break;
case "EFortRarity::Mythic":
case "EFortRarity::Elegant":
rarity = EFortRarity.Mythic;
break;
case "EFortRarity::Transcendent":
case "EFortRarity::Masterwork":
rarity = EFortRarity.Transcendent;
break;
case "EFortRarity::Unattainable":
case "EFortRarity::Badass":
rarity = EFortRarity.Unattainable;
break;
}
return rarity.GetDescription();
base.DrawTextBackground(c);
}
}
private new void DrawBackground(SKCanvas c)
private new void DrawDisplayName(SKCanvas c)
{
if (string.IsNullOrEmpty(DisplayName)) return;
if (_design.Fonts.TryGetValue(nameof(DisplayName), out var font))
{
if (_design.Rarities.TryGetValue(_rarityName, out var rarity))
{
c.DrawBitmap(rarity.Background, 0, 0, ImagePaint);
c.DrawBitmap(rarity.Upper, 0, 0, ImagePaint);
}
else
{
base.DrawBackground(c);
}
}
private new void DrawTextBackground(SKCanvas c)
{
if (!_lowerDrawn && string.IsNullOrEmpty(DisplayName) && string.IsNullOrEmpty(Description)) return;
_lowerDrawn = true;
if (_design.Rarities.TryGetValue(_rarityName, out var rarity))
{
c.DrawBitmap(rarity.Lower, 0, 0, ImagePaint);
}
else
{
base.DrawTextBackground(c);
}
}
private new void DrawDisplayName(SKCanvas c)
{
if (string.IsNullOrEmpty(DisplayName)) return;
if (_design.Fonts.TryGetValue(nameof(DisplayName), out var font))
{
DisplayNamePaint.TextSize = font.FontSize;
DisplayNamePaint.TextScaleX = font.FontScale;
DisplayNamePaint.Color = font.FontColor;
DisplayNamePaint.TextSkewX = font.SkewValue;
DisplayNamePaint.TextAlign = font.Alignment;
if (font.ShadowValue > 0)
DisplayNamePaint.ImageFilter = SKImageFilter.CreateDropShadow(2, 2, 4, 4, new SKColor(0, 0, 0, font.ShadowValue));
if (font.Typeface.TryGetValue(UserSettings.Default.AssetLanguage, out var path) ||
font.Typeface.TryGetValue(ELanguage.English, out path))
DisplayNamePaint.Typeface = Utils.Typefaces.OnTheFly(path);
while (DisplayNamePaint.MeasureText(DisplayName) > Width - Margin * 2)
{
DisplayNamePaint.TextSize -= 1;
}
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
var shapedText = shaper.Shape(DisplayName, DisplayNamePaint);
var x = font.Alignment switch
{
SKTextAlign.Center => (Width - shapedText.Points[^1].X) / 2f,
_ => font.X
};
c.DrawShapedText(shaper, DisplayName, x, font.Y, DisplayNamePaint);
}
else
{
base.DrawDisplayName(c);
}
}
private new void DrawDescription(SKCanvas c)
{
if (string.IsNullOrEmpty(Description)) return;
if (_design.Fonts.TryGetValue(nameof(Description), out var font))
{
DescriptionPaint.TextSize = font.FontSize;
DescriptionPaint.TextScaleX = font.FontScale;
DescriptionPaint.Color = font.FontColor;
DescriptionPaint.TextSkewX = font.SkewValue;
DescriptionPaint.TextAlign = font.Alignment;
if (font.ShadowValue > 0)
DescriptionPaint.ImageFilter = SKImageFilter.CreateDropShadow(2, 2, 4, 4, new SKColor(0, 0, 0, font.ShadowValue));
if (font.Typeface.TryGetValue(UserSettings.Default.AssetLanguage, out var path) ||
font.Typeface.TryGetValue(ELanguage.English, out path))
DescriptionPaint.Typeface = Utils.Typefaces.OnTheFly(path);
while (DescriptionPaint.MeasureText(Description) > Width - Margin * 2)
{
DescriptionPaint.TextSize -= 1;
}
var shaper = new CustomSKShaper(DescriptionPaint.Typeface);
var shapedText = shaper.Shape(Description, DescriptionPaint);
var x = font.Alignment switch
{
SKTextAlign.Center => (Width - shapedText.Points[^1].X) / 2f,
_ => font.X
};
if (font.MaxLineCount < 2)
{
c.DrawShapedText(shaper, Description, x, font.Y, DescriptionPaint);
}
else
{
Utils.DrawMultilineText(c, Description, Width - Margin * 2, Margin, DescriptionPaint.TextAlign,
new SKRect(Margin, font.Y, Width - Margin, Height), DescriptionPaint, out _);
}
}
else
{
base.DrawDescription(c);
}
}
private void DrawToBottom(SKCanvas c, FontDesign font, string text)
{
if (string.IsNullOrEmpty(text)) return;
if (!_lowerDrawn)
{
_lowerDrawn = true;
DrawTextBackground(c);
}
DisplayNamePaint.TextSize = font.FontSize;
DisplayNamePaint.TextScaleX = font.FontScale;
DisplayNamePaint.Color = font.FontColor;
@ -276,30 +168,111 @@ namespace FModel.Creator.Bases.FN
font.Typeface.TryGetValue(ELanguage.English, out path))
DisplayNamePaint.Typeface = Utils.Typefaces.OnTheFly(path);
while (DisplayNamePaint.MeasureText(DisplayName) > Width - Margin * 2)
{
DisplayNamePaint.TextSize -= 1;
}
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
var shapedText = shaper.Shape(text, DisplayNamePaint);
var x = font.Alignment switch
{
SKTextAlign.Center => (Width - shapedText.Points[^1].X) / 2f,
SKTextAlign.Right => font.X - DisplayNamePaint.MeasureText(text),
SKTextAlign.Center => Width / 2f,
_ => font.X
};
c.DrawShapedText(shaper, text, x, font.Y, DisplayNamePaint);
c.DrawShapedText(shaper, DisplayName, x, font.Y, DisplayNamePaint);
}
private void DrawUserFacingFlags(SKCanvas c, bool customOnly)
else
{
if (UserFacingFlags == null || UserFacingFlags.Length < 1) return;
if (customOnly)
base.DrawDisplayName(c);
}
}
private new void DrawDescription(SKCanvas c)
{
if (string.IsNullOrEmpty(Description)) return;
if (_design.Fonts.TryGetValue(nameof(Description), out var font))
{
DescriptionPaint.TextSize = font.FontSize;
DescriptionPaint.TextScaleX = font.FontScale;
DescriptionPaint.Color = font.FontColor;
DescriptionPaint.TextSkewX = font.SkewValue;
DescriptionPaint.TextAlign = font.Alignment;
if (font.ShadowValue > 0)
DescriptionPaint.ImageFilter = SKImageFilter.CreateDropShadow(2, 2, 4, 4, new SKColor(0, 0, 0, font.ShadowValue));
if (font.Typeface.TryGetValue(UserSettings.Default.AssetLanguage, out var path) ||
font.Typeface.TryGetValue(ELanguage.English, out path))
DescriptionPaint.Typeface = Utils.Typefaces.OnTheFly(path);
while (DescriptionPaint.MeasureText(Description) > Width - Margin * 2)
{
c.DrawBitmap(_design.GameplayTags.Custom, 0, 0, ImagePaint);
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
{
// add size to api
// draw
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

@ -1,297 +1,325 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Engine;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.GameplayTags;
using CUE4Parse.UE4.Objects.UObject;
using CUE4Parse_Conversion.Textures;
using CUE4Parse_Fortnite.Enums;
using FModel.Settings;
using SkiaSharp;
namespace FModel.Creator.Bases.FN
{
public class BaseIcon : UCreator
{
public SKBitmap SeriesBackground { get; protected set; }
protected string ShortDescription { get; set; }
protected string CosmeticSource { get; set; }
protected SKBitmap[] UserFacingFlags { get; set; }
public BaseIcon(UObject uObject, EIconStyle style) : base(uObject, style)
{
}
public void ParseForReward(bool isUsingDisplayAsset)
{
// rarity
if (Object.TryGetValue(out FPackageIndex series, "Series")) GetSeries(series);
else GetRarity(Object.GetOrDefault<FName>("Rarity")); // default is uncommon
// preview
if (isUsingDisplayAsset && Utils.TryGetDisplayAsset(Object, out var preview))
Preview = preview;
else if (Object.TryGetValue(out FPackageIndex itemDefinition, "HeroDefinition", "WeaponDefinition"))
Preview = Utils.GetBitmap(itemDefinition);
else if (Object.TryGetValue(out FSoftObjectPath largePreview, "LargePreviewImage", "SidePanelIcon", "EntryListIcon", "SmallPreviewImage", "ItemDisplayAsset", "LargeIcon", "ToastIcon", "SmallIcon"))
Preview = Utils.GetBitmap(largePreview);
else if (Object.TryGetValue(out string s, "LargePreviewImage") && !string.IsNullOrEmpty(s))
Preview = Utils.GetBitmap(s);
else if (Object.TryGetValue(out FPackageIndex otherPreview, "SmallPreviewImage", "ToastIcon", "access_item"))
Preview = Utils.GetBitmap(otherPreview);
// text
if (Object.TryGetValue(out FText displayName, "DisplayName", "DefaultHeaderText", "UIDisplayName", "EntryName"))
DisplayName = displayName.Text;
if (Object.TryGetValue(out FText description, "Description", "GeneralDescription", "DefaultBodyText", "UIDescription", "UIDisplayDescription", "EntryDescription"))
Description = description.Text;
else if (Object.TryGetValue(out FText[] descriptions, "Description"))
Description = string.Join('\n', descriptions.Select(x => x.Text));
if (Object.TryGetValue(out FText shortDescription, "ShortDescription", "UIDisplaySubName"))
ShortDescription = shortDescription.Text;
else if (Object.ExportType.Equals("AthenaItemWrapDefinition", StringComparison.OrdinalIgnoreCase))
ShortDescription = "Wrap";
Description = Utils.RemoveHtmlTags(Description);
}
public override void ParseForInfo()
{
ParseForReward(UserSettings.Default.CosmeticDisplayAsset == EEnabledDisabled.Enabled);
if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags"))
CheckGameplayTags(gameplayTags);
if (Object.TryGetValue(out FPackageIndex cosmeticItem, "cosmetic_item"))
CosmeticSource = cosmeticItem.Name;
}
protected void Draw(SKCanvas c)
{
switch (Style)
{
case EIconStyle.NoBackground:
DrawPreview(c);
break;
case EIconStyle.NoText:
DrawBackground(c);
DrawPreview(c);
DrawUserFacingFlags(c);
break;
default:
DrawBackground(c);
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
DrawDescription(c);
DrawToBottom(c, SKTextAlign.Right, CosmeticSource);
if (Description != ShortDescription)
DrawToBottom(c, SKTextAlign.Left, ShortDescription);
DrawUserFacingFlags(c);
break;
}
}
public override SKImage Draw()
{
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
Draw(c);
return SKImage.FromBitmap(ret);
}
private void GetSeries(FPackageIndex s)
{
if (!Utils.TryGetPackageIndexExport(s, out UObject export)) return;
GetSeries(export);
}
protected void GetSeries(UObject uObject)
{
if (uObject is UTexture2D texture2D)
{
SeriesBackground = SKBitmap.Decode(texture2D.Decode()?.Encode());
return;
}
if (uObject.TryGetValue(out FSoftObjectPath backgroundTexture, "BackgroundTexture"))
{
SeriesBackground = Utils.GetBitmap(backgroundTexture);
}
if (uObject.TryGetValue(out FStructFallback colors, "Colors") &&
colors.TryGetValue(out FLinearColor color1, "Color1") &&
colors.TryGetValue(out FLinearColor color2, "Color2") &&
colors.TryGetValue(out FLinearColor color3, "Color3"))
{
Background = new[] {SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex)};
Border = new[] {SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex)};
}
if (uObject.Name.Equals("PlatformSeries") &&
uObject.TryGetValue(out FSoftObjectPath itemCardMaterial, "ItemCardMaterial") &&
Utils.TryLoadObject(itemCardMaterial.AssetPathName.Text, out UMaterialInstanceConstant material))
{
foreach (var vectorParameter in material.VectorParameterValues)
{
if (vectorParameter.ParameterValue == null || !vectorParameter.ParameterInfo.Name.Text.Equals("ColorCircuitBackground"))
continue;
Background[0] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex);
}
}
}
private void GetRarity(FName r)
{
if (!Utils.TryLoadObject("FortniteGame/Content/Balance/RarityData.RarityData", out UObject export)) return;
var rarity = EFortRarity.Uncommon;
switch (r.Text)
{
case "EFortRarity::Common":
case "EFortRarity::Handmade":
rarity = EFortRarity.Common;
break;
case "EFortRarity::Rare":
case "EFortRarity::Sturdy":
rarity = EFortRarity.Rare;
break;
case "EFortRarity::Epic":
case "EFortRarity::Quality":
rarity = EFortRarity.Epic;
break;
case "EFortRarity::Legendary":
case "EFortRarity::Fine":
rarity = EFortRarity.Legendary;
break;
case "EFortRarity::Mythic":
case "EFortRarity::Elegant":
rarity = EFortRarity.Mythic;
break;
case "EFortRarity::Transcendent":
case "EFortRarity::Masterwork":
rarity = EFortRarity.Transcendent;
break;
case "EFortRarity::Unattainable":
case "EFortRarity::Badass":
rarity = EFortRarity.Unattainable;
break;
}
if (export.GetByIndex<FStructFallback>((int) rarity) is { } data &&
data.TryGetValue(out FLinearColor color1, "Color1") &&
data.TryGetValue(out FLinearColor color2, "Color2") &&
data.TryGetValue(out FLinearColor color3, "Color3"))
{
Background = new[] {SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex)};
Border = new[] {SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex)};
}
}
protected string GetCosmeticSet(string setName)
{
if (!Utils.TryLoadObject("FortniteGame/Content/Athena/Items/Cosmetics/Metadata/CosmeticSets.CosmeticSets", out UDataTable cosmeticSets))
return string.Empty;
if (!cosmeticSets.TryGetDataTableRow(setName, StringComparison.OrdinalIgnoreCase, out var uObject))
return string.Empty;
var name = string.Empty;
if (uObject.TryGetValue(out FText displayName, "DisplayName"))
name = displayName.Text;
var format = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_SetMembership_NotRich", "\nPart of the {0} set.");
return string.Format(format, name);
}
protected string GetCosmeticSeason(string seasonNumber)
{
var s = seasonNumber["Cosmetics.Filter.Season.".Length..];
var number = int.Parse(s);
if (number == 10)
s = "X";
var season = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "SeasonTextFormat", "Season {0}");
var introduced = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_Season", "\nIntroduced in <SeasonText>{0}</>.");
if (number <= 10) return Utils.RemoveHtmlTags(string.Format(introduced, string.Format(season, s)));
var chapter = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterTextFormat", "Chapter {0}");
var chapterFormat = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterSeasonTextFormat", "{0}, {1}");
var d = string.Format(chapterFormat, string.Format(chapter, number / 10 + 1), string.Format(season, s[^1..]));
return Utils.RemoveHtmlTags(string.Format(introduced, d));
}
private void CheckGameplayTags(FGameplayTagContainer gameplayTags)
{
if (gameplayTags.TryGetGameplayTag("Cosmetics.Source.", out var source))
CosmeticSource = source.Text["Cosmetics.Source.".Length..];
else if (gameplayTags.TryGetGameplayTag("Athena.ItemAction.", out var action))
CosmeticSource = action.Text["Athena.ItemAction.".Length..];
if (gameplayTags.TryGetGameplayTag("Cosmetics.Set.", out var set))
Description += GetCosmeticSet(set.Text);
if (gameplayTags.TryGetGameplayTag("Cosmetics.Filter.Season.", out var season))
Description += GetCosmeticSeason(season.Text);
GetUserFacingFlags(gameplayTags.GetAllGameplayTags(
"Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender."));
}
protected void GetUserFacingFlags(IList<string> userFacingFlags)
{
if (userFacingFlags.Count < 1 || !Utils.TryLoadObject("FortniteGame/Content/Items/ItemCategories.ItemCategories", out UObject itemCategories))
return;
if (!itemCategories.TryGetValue(out FStructFallback[] tertiaryCategories, "TertiaryCategories"))
return;
UserFacingFlags = new SKBitmap[userFacingFlags.Count];
for (var i = 0; i < UserFacingFlags.Length; i++)
{
if (userFacingFlags[i].Equals("Cosmetics.UserFacingFlags.HasUpgradeQuests", StringComparison.OrdinalIgnoreCase))
{
if (Object.ExportType.Equals("AthenaPetCarrierItemDefinition", StringComparison.OrdinalIgnoreCase))
UserFacingFlags[i] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Pets-64.png"))?.Stream);
else UserFacingFlags[i] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Quests-64.png"))?.Stream);
}
else
{
foreach (var category in tertiaryCategories)
{
if (category.TryGetValue(out FGameplayTagContainer tagContainer, "TagContainer") && tagContainer.TryGetGameplayTag(userFacingFlags[i], out _) &&
category.TryGetValue(out FStructFallback categoryBrush, "CategoryBrush") && categoryBrush.TryGetValue(out FStructFallback brushXxs, "Brush_XXS") &&
brushXxs.TryGetValue(out FPackageIndex resourceObject, "ResourceObject") && Utils.TryGetPackageIndexExport(resourceObject, out UTexture2D texture))
{
UserFacingFlags[i] = Utils.GetBitmap(texture);
}
}
}
}
}
private void DrawUserFacingFlags(SKCanvas c)
{
if (UserFacingFlags == null) return;
const int size = 25;
var x = Margin * (int) 2.5;
foreach (var flag in UserFacingFlags)
{
if (flag == null) continue;
c.DrawBitmap(flag.Resize(size), new SKPoint(x, Margin * (int) 2.5), ImagePaint);
x += size;
}
}
}
}
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

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

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

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

View File

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

@ -1,4 +1,4 @@
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.UObject;
@ -6,71 +6,72 @@ using FModel.Services;
using FModel.ViewModels;
using SkiaSharp;
namespace FModel.Creator.Bases.FN
namespace FModel.Creator.Bases.FN;
public class BasePlaylist : UCreator
{
public class BasePlaylist : UCreator
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
private SKBitmap _missionIcon;
public BasePlaylist(UObject uObject, EIconStyle style) : base(uObject, style)
{
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.HasShowcase ||
!_apiEndpointView.FortniteApi.TryGetBytes(playlist.Data.Images.Showcase, out var image))
return;
Preview = Utils.GetBitmap(image).Resize(1024, 512); // Force size to 1024x512 to prevent huge previews.
}
public override SKImage Draw()
{
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
switch (Style)
{
case EIconStyle.NoBackground:
DrawPreview(c);
break;
case EIconStyle.NoText:
DrawPreview(c);
DrawMissionIcon(c);
break;
default:
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
DrawDescription(c);
DrawMissionIcon(c);
break;
}
return SKImage.FromBitmap(ret);
}
private void DrawMissionIcon(SKCanvas c)
{
if (_missionIcon == null) return;
c.DrawBitmap(_missionIcon, new SKPoint(5, 5), ImagePaint);
}
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

@ -1,7 +1,9 @@
using System;
using System;
using System.Linq;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Engine;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.UObject;
@ -10,250 +12,267 @@ using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN
namespace FModel.Creator.Bases.FN;
public class BaseQuest : BaseIcon
{
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)
{
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)
{
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
{
Description = ShortDescription;
if (Object.TryGetValue(out FText completionText, "CompletionText"))
Description += "\n" + completionText.Text;
if (Object.TryGetValue(out FSoftObjectPath tandemCharacterData, "TandemCharacterData") &&
Utils.TryLoadObject(tandemCharacterData.AssetPathName.Text, out UObject uObject) &&
uObject.TryGetValue(out FSoftObjectPath tandemIcon, "SidePanelIcon", "EntryListIcon", "ToastIcon"))
{
Preview = Utils.GetBitmap(tandemIcon);
}
}
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, primaryAssetName);
}
}
}
if (_reward == null && Object.TryGetValue(out UDataTable rewardsTable, "RewardsTable"))
{
if (rewardsTable.TryGetDataTableRow("Default", StringComparison.InvariantCulture, out var row))
{
if (row.TryGetValue(out FName templateId, "TemplateId") &&
row.TryGetValue(out int quantity, "Quantity"))
{
_reward = new Reward(quantity, templateId);
}
}
}
if (_reward == null && Object.TryGetValue(out FStructFallback[] hiddenRewards, "HiddenRewards"))
{
foreach (var hiddenReward in hiddenRewards)
{
if (!hiddenReward.TryGetValue(out FName templateId, "TemplateId") ||
!hiddenReward.TryGetValue(out int quantity, "Quantity")) continue;
_reward = new Reward(quantity, templateId);
break;
}
}
_reward ??= new Reward();
}
public void DrawQuest(SKCanvas c, int y)
{
DrawBackground(c, y);
DrawPreview(c, y);
DrawTexts(c, y);
}
public override SKImage Draw()
{
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
DrawQuest(c, 0);
return SKImage.FromBitmap(ret);
}
private string ReformatString(string s, string completionCount, bool isAll)
{
s = s.Replace("({0})", "{0}").Replace("{QuestNumber}", "<text color=\"FFF\" case=\"upper\" fontface=\"black\">{0}</>");
var index = s.IndexOf("{0}|plural(", StringComparison.OrdinalIgnoreCase);
if (index > -1)
{
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);
shaper.Shape(DisplayName, _informationPaint);
c.DrawShapedText(shaper, DisplayName, Height, y + 50, _informationPaint);
}
var outY = y + 75f;
if (!string.IsNullOrWhiteSpace(Description))
{
_informationPaint.TextSize = 16;
_informationPaint.Color = SKColors.White.WithAlpha(175);
_informationPaint.Typeface = Utils.Typefaces.Description;
Utils.DrawMultilineText(c, Description, Width - Height, y, SKTextAlign.Left,
new SKRect(Height, outY, Width - 10, y + Height), _informationPaint, out outY);
}
_informationPaint.Color = Border[0].WithAlpha(100);
c.DrawRect(new SKRect(Height, outY, Width - 150, outY + 5), _informationPaint);
if (_count > 0)
{
_informationPaint.TextSize = 25;
_informationPaint.Color = SKColors.White;
_informationPaint.Typeface = Utils.Typefaces.BundleNumber;
c.DrawText("0 / ", new SKPoint(Width - 130, outY + 10), _informationPaint);
_informationPaint.Color = Border[0];
c.DrawText(_count.ToString(), new SKPoint(Width - 95, outY + 10), _informationPaint);
}
_reward.DrawQuest(c, new SKRect(Height, outY + 25, Width - 20, y + Height - 25));
_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

@ -1,5 +1,5 @@
using System;
using System.Collections.Generic;
using System;
using System.Linq;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Core.i18N;
@ -8,167 +8,151 @@ using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN
namespace FModel.Creator.Bases.FN;
public class Page
{
public class BaseSeason : UCreator
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)
{
private Reward _firstWinReward;
private Dictionary<int, List<Reward>> _bookXpSchedule;
private const int _headerHeight = 150;
// keep the list because rewards are ordered by least to most important
// we only care about the most but we also have filters so we can't just take the last reward
Width = 1024;
Height = _headerHeight + 50;
Margin = 0;
}
public BaseSeason(UObject uObject, EIconStyle style) : base(uObject, style)
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"))
{
Width = 1024;
Height = _headerHeight + 50;
Margin = 0;
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;
}
}
public override void ParseForInfo()
if (Object.TryGetValue(out FPackageIndex[] additionalSeasonData, "AdditionalSeasonData"))
{
_bookXpSchedule = new Dictionary<int, List<Reward>>();
if (Object.TryGetValue(out FText displayName, "DisplayName"))
DisplayName = displayName.Text.ToUpperInvariant();
if (Object.TryGetValue(out FStructFallback seasonFirstWinRewards, "SeasonFirstWinRewards") &&
seasonFirstWinRewards.TryGetValue(out FStructFallback[] rewards, "Rewards"))
foreach (var data in additionalSeasonData)
{
foreach (var reward in rewards)
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 (!reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition") ||
!Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject)) continue;
if (!page.TryGetValue(out int levelsNeededForUnlock, "LevelsNeededForUnlock") ||
!page.TryGetValue(out int rewardsNeededForUnlock, "RewardsNeededForUnlock") ||
!page.TryGetValue(out FPackageIndex[] rewardEntryList, "RewardEntryList"))
continue;
_firstWinReward = new Reward(uObject);
break;
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;
}
var freeLevels = Array.Empty<FStructFallback>();
var paidLevels = Array.Empty<FStructFallback>();
if (Object.TryGetValue(out FPackageIndex[] additionalSeasonData, "AdditionalSeasonData") &&
additionalSeasonData.Length > 0 && Utils.TryGetPackageIndexExport(additionalSeasonData[0], out UObject data) &&
data.TryGetValue(out FStructFallback battlePassXpScheduleFree, "BattlePassXpScheduleFree") &&
battlePassXpScheduleFree.TryGetValue(out freeLevels, "Levels") &&
data.TryGetValue(out FStructFallback battlePassXpSchedulePaid, "BattlePassXpSchedulePaid") &&
battlePassXpSchedulePaid.TryGetValue(out paidLevels, "Levels"))
{
// we got them boys
}
else if (Object.TryGetValue(out FStructFallback bookXpScheduleFree, "BookXpScheduleFree") &&
bookXpScheduleFree.TryGetValue(out freeLevels, "Levels") &&
Object.TryGetValue(out FStructFallback bookXpSchedulePaid, "BookXpSchedulePaid") &&
bookXpSchedulePaid.TryGetValue(out paidLevels, "Levels"))
{
// we got them boys
}
for (var i = 0; i < freeLevels.Length; i++)
{
_bookXpSchedule[i] = new List<Reward>();
if (!freeLevels[i].TryGetValue(out rewards, "Rewards")) continue;
foreach (var reward in rewards)
{
if (!reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition") ||
itemDefinition.AssetPathName.Text.Contains("/Items/Tokens/") ||
!Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject)) continue;
_bookXpSchedule[i].Add(new Reward(uObject));
break;
}
}
for (var i = 0; i < paidLevels.Length; i++)
{
if (!paidLevels[i].TryGetValue(out rewards, "Rewards")) continue;
foreach (var reward in rewards)
{
if (!reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition") ||
!Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject)) continue;
_bookXpSchedule[i].Add(new Reward(uObject));
break;
}
}
Height += 100 * _bookXpSchedule.Count / 10;
}
public override SKImage Draw()
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)
{
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
DrawHeader(c);
_firstWinReward?.DrawSeasonWin(c, _headerHeight);
DrawBookSchedule(c);
return SKImage.FromBitmap(ret);
_headerPaint.TextSize -= 1;
}
private const int _DEFAULT_AREA_SIZE = 80;
private readonly SKPaint _headerPaint = new()
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)
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Typeface = Utils.Typefaces.Bundle, TextSize = 50,
TextAlign = SKTextAlign.Center, Color = SKColor.Parse("#262630")
};
private readonly SKPaint _bookPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.BundleNumber,
Color = SKColors.White, TextAlign = SKTextAlign.Center, TextSize = 15
};
public void DrawHeader(SKCanvas c)
{
c.DrawRect(new SKRect(0, 0, Width, Height), _headerPaint);
_headerPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4,
new[] {SKColors.SkyBlue.WithAlpha(50), SKColors.Blue.WithAlpha(50)}, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(0, 0, Width, Height), _headerPaint);
_headerPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, _headerHeight), new SKPoint(Width / 2, 75),
new[] {SKColors.Black.WithAlpha(25), SKColors.Blue.WithAlpha(0)}, SKShaderTileMode.Clamp);
c.DrawRect(new SKRect(0, 75, Width, _headerHeight), _headerPaint);
_headerPaint.Shader = null;
_headerPaint.Color = SKColors.White;
while (_headerPaint.MeasureText(DisplayName) > Width)
foreach (var reward in page.RewardEntryList)
{
_headerPaint.TextSize -= 1;
reward.DrawSeason(c, x, y, _DEFAULT_AREA_SIZE);
x += _DEFAULT_AREA_SIZE + 20;
}
var shaper = new CustomSKShaper(_headerPaint.Typeface);
var shapedText = shaper.Shape(DisplayName, _headerPaint);
c.DrawShapedText(shaper, DisplayName, (Width - shapedText.Points[^1].X) / 2, _headerHeight / 2 + _headerPaint.TextSize / 2 - 10, _headerPaint);
}
private void DrawBookSchedule(SKCanvas c)
{
var x = 20;
var y = _headerHeight + 50;
foreach (var (index, reward) in _bookXpSchedule)
{
if (index == 0 || reward.Count == 0 || !reward[0].HasReward())
continue;
c.DrawText(index.ToString(), new SKPoint(x + _DEFAULT_AREA_SIZE / 2, y - 5), _bookPaint);
reward[0].DrawSeason(c, x, y, _DEFAULT_AREA_SIZE);
if (index != 1 && index % 10 == 0)
{
y += _DEFAULT_AREA_SIZE + 20;
x = 20;
}
else
{
x += _DEFAULT_AREA_SIZE + 20;
}
}
y += _DEFAULT_AREA_SIZE + 20;
x = 20;
}
}
}
}

View File

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

@ -9,185 +9,183 @@ using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN
namespace FModel.Creator.Bases.FN;
public class BaseUserControl : UCreator
{
public class BaseUserControl : UCreator
private List<Options> _optionValues = new();
private readonly SKPaint _displayNamePaint = new()
{
private List<Options> _optionValues = 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
};
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 BaseUserControl(UObject uObject, EIconStyle style) : base(uObject, style)
public override void ParseForInfo()
{
if (Object.TryGetValue(out FText optionDisplayName, "OptionDisplayName", "OptionText"))
DisplayName = optionDisplayName.Text.ToUpperInvariant();
if (Object.TryGetValue(out FText optionDescription, "OptionDescription", "OptionToolTip"))
{
Width = 512;
Height = 128;
Margin = 32;
Description = optionDescription.Text;
if (string.IsNullOrWhiteSpace(Description)) return;
Height += (int) _descriptionPaint.TextSize * Utils.SplitLines(Description, _descriptionPaint, Width - Margin).Count;
Height += (int) _descriptionPaint.TextSize;
}
public override void ParseForInfo()
if (Object.TryGetValue(out FStructFallback[] optionValues, "OptionValues", "Options"))
{
if (Object.TryGetValue(out FText optionDisplayName, "OptionDisplayName"))
DisplayName = optionDisplayName.Text.ToUpperInvariant();
if (Object.TryGetValue(out FText optionDescription, "OptionDescription"))
_optionValues = new List<Options>();
foreach (var option in optionValues)
{
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"))
{
_optionValues = new List<Options>();
foreach (var option in optionValues)
if (option.TryGetValue(out FText displayName, "DisplayName", "DisplayText"))
{
if (option.TryGetValue(out FText displayName, "DisplayName"))
{
var opt = new Options {Option = displayName.Text.ToUpperInvariant()};
if (option.TryGetValue(out FLinearColor color, "Value"))
opt.Color = SKColor.Parse(color.Hex).WithAlpha(150);
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});
}
_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 SKImage Draw()
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"))
{
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
var increment = iMin;
if (Object.TryGetValue(out int incrementValue, "IncrementValue"))
increment = incrementValue;
DrawBackground(c);
DrawInformation(c);
var format = "{0}";
if (Object.TryGetValue(out FText unitName, "UnitName"))
format = unitName.Text;
return SKImage.FromBitmap(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)
for (var i = iMin; i <= iMax; i += increment)
{
_displayNamePaint.TextSize -= 2;
_optionValues.Add(new Options { Option = string.Format(format, i) });
}
}
var shaper = new CustomSKShaper(_displayNamePaint.Typeface);
shaper.Shape(DisplayName, _displayNamePaint);
c.DrawShapedText(shaper, DisplayName, Margin, Margin + _displayNamePaint.TextSize, _displayNamePaint);
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});
c.DrawRect(new SKRect(Margin, Margin, Width - Margin, Margin + _displayNamePaint.TextSize), new SKPaint { Color = SKColors.Blue, IsStroke = true });
#endif
// description
float y = Margin;
if (!string.IsNullOrEmpty(DisplayName)) y += _displayNamePaint.TextSize;
if (!string.IsNullOrEmpty(Description)) y += _descriptionPaint.TextSize + Margin / 2F;
// description
float y = Margin;
if (!string.IsNullOrEmpty(DisplayName)) y += _displayNamePaint.TextSize;
if (!string.IsNullOrEmpty(Description)) y += _descriptionPaint.TextSize + Margin / 2F;
Utils.DrawMultilineText(c, Description, Width, Margin, SKTextAlign.Left,
new SKRect(Margin, y, Width - Margin, 256), _descriptionPaint, out var top);
Utils.DrawMultilineText(c, Description, Width, Margin, SKTextAlign.Left,
new SKRect(Margin, y, Width - Margin, 256), _descriptionPaint, out var top);
// options
foreach (var option in _optionValues)
{
option.Draw(c, Margin, Width, ref top);
}
// options
foreach (var option in _optionValues)
{
option.Draw(c, Margin, Width, ref top);
}
}
}
public class Options
public class Options
{
private const int _SPACE = 5;
private const int _HEIGHT = 30;
private readonly SKPaint _optionPaint = new()
{
private const int _SPACE = 5;
private const int _HEIGHT = 30;
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Typeface = Utils.Typefaces.DisplayName, TextSize = 20,
Color = SKColor.Parse("EEFFFF"), TextAlign = SKTextAlign.Left
};
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 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;
}
public void Draw(SKCanvas c, int margin, int width, ref float top)
{
c.DrawRect(new SKRect(margin, top, width - margin, top + _HEIGHT), new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = Color });
c.DrawText(Option, margin + _SPACE * 2, top + 20 * 1.1f, _optionPaint);
top += _HEIGHT + _SPACE;
}
}
}

View File

@ -5,149 +5,147 @@ using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN
namespace FModel.Creator.Bases.FN;
public class Reward
{
public class Reward
private string _rewardQuantity;
private BaseIcon _theReward;
public bool HasReward() => _theReward != null;
public Reward()
{
private string _rewardQuantity;
private BaseIcon _theReward;
_rewardQuantity = "x0";
}
public bool HasReward() => _theReward != null;
public Reward(int quantity, FName primaryAssetName) : this(quantity, primaryAssetName.Text)
{
}
public Reward()
public Reward(int quantity, string assetName) : this()
{
_rewardQuantity = $"x{quantity:###,###,###}".Trim();
if (assetName.Contains(':'))
{
_rewardQuantity = "x0";
}
var parts = assetName.Split(':');
public Reward(int quantity, FName primaryAssetName) : this(quantity, primaryAssetName.Text)
{
}
public Reward(int quantity, string assetName) : this()
{
_rewardQuantity = $"x{quantity:###,###,###}".Trim();
if (assetName.Contains(':'))
if (parts[0].Equals("HomebaseBannerIcon", StringComparison.CurrentCultureIgnoreCase))
{
var parts = assetName.Split(':');
if (!Utils.TryLoadObject($"FortniteGame/Content/Items/BannerIcons/{parts[1]}.{parts[1]}", out UObject p))
return;
if (parts[0].Equals("HomebaseBannerIcon", StringComparison.CurrentCultureIgnoreCase))
_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))
{
if (!Utils.TryLoadObject($"FortniteGame/Content/Items/BannerIcons/{parts[1]}.{parts[1]}", out UObject p))
return;
_theReward = new BaseIcon(p, EIconStyle.Default);
_theReward = new BaseIcon(d, 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.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;
_rewardQuantity = $"{_theReward.DisplayName} ({_rewardQuantity})";
}
var shaper = new CustomSKShaper(_rewardPaint.Typeface);
shaper.Shape(_rewardQuantity, _rewardPaint);
c.DrawShapedText(shaper, _rewardQuantity, rect.Left + rect.Height + 25, rect.MidY + 20, _rewardPaint);
}
else
{
_rewardPaint.Color = SKColors.White;
_rewardPaint.Typeface = Utils.Typefaces.BundleNumber;
c.DrawText("No Reward", new SKPoint(rect.Left, rect.MidY + 20), _rewardPaint);
}
}
public void DrawSeasonWin(SKCanvas c, int size)
{
if (!HasReward()) return;
c.DrawBitmap(_theReward.Preview.Resize(size), new SKPoint(0, 0), _rewardPaint);
}
public void DrawSeason(SKCanvas c, int x, int y, int areaSize)
{
if (!HasReward()) return;
// area + icon
_rewardPaint.Color = SKColor.Parse("#0F5CAF");
c.DrawRect(new SKRect(x, y, x + areaSize, y + areaSize), _rewardPaint);
c.DrawBitmap(_theReward.Preview.Resize(areaSize), new SKPoint(x, y), _rewardPaint);
// rarity color
_rewardPaint.Color = _theReward.Background[0];
var pathBottom = new SKPath {FillType = SKPathFillType.EvenOdd};
pathBottom.MoveTo(x, y + areaSize);
pathBottom.LineTo(x, y + areaSize - areaSize / 25 * 2.5f);
pathBottom.LineTo(x + areaSize, y + areaSize - areaSize / 25 * 4.5f);
pathBottom.LineTo(x + areaSize, y + areaSize);
pathBottom.Close();
c.DrawPath(pathBottom, _rewardPaint);
}
private void GetReward(string trigger)
{
switch (trigger.ToLower())
{
case "athenabattlestar":
_theReward = new BaseIcon(null, EIconStyle.Default);
_theReward.Border[0] = SKColor.Parse("FFDB67");
_theReward.Background[0] = SKColor.Parse("8F4A20");
_theReward.Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Items/T-FNBR-BattlePoints.T-FNBR-BattlePoints");
break;
case "athenaseasonalxp":
_theReward = new BaseIcon(null, EIconStyle.Default);
_theReward.Border[0] = SKColor.Parse("E6FDB1");
_theReward.Background[0] = SKColor.Parse("51830F");
_theReward.Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Items/T-FNBR-XPMedium.T-FNBR-XPMedium");
break;
case "mtxgiveaway":
_theReward = new BaseIcon(null, EIconStyle.Default);
_theReward.Border[0] = SKColor.Parse("DCE6FF");
_theReward.Background[0] = SKColor.Parse("64A0AF");
_theReward.Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Items/T-Items-MTX.T-Items-MTX");
break;
default:
{
var path = Utils.GetFullPath($"FortniteGame/Content/Athena/.*?/{trigger}.*"); // path has no objectname and its needed so we push the trigger again as the objectname
if (!string.IsNullOrWhiteSpace(path) && Utils.TryLoadObject(path.Replace("uasset", trigger), out UObject d))
{
_theReward = new BaseIcon(d, EIconStyle.Default);
_theReward.ParseForReward(false);
_theReward.Border[0] = SKColors.White;
_rewardQuantity = _theReward.DisplayName;
}
break;
}
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

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

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

View File

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

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Windows;
using CUE4Parse.UE4.Assets.Exports;
using FModel.Creator.Bases.FN;
@ -6,225 +6,223 @@ using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases
namespace FModel.Creator.Bases;
public abstract class UCreator
{
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)
{
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; }
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;
}
public abstract void ParseForInfo();
public abstract SKImage Draw();
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
};
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;
}
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;
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
// border
c.DrawRect(new SKRect(0, 0, Width, Height),
new SKPaint
{
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;
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Shader = SKShader.CreateLinearGradient(
new SKPoint(Width / 2, Height), new SKPoint(Width, Height / 4), Border, SKShaderTileMode.Clamp)
});
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 (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
{
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);
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, _STARTER_TEXT_POSITION, Width - Margin, Height - Margin), _textBackgroundPaint);
break;
}
}
protected void DrawDisplayName(SKCanvas c)
{
if (string.IsNullOrWhiteSpace(DisplayName)) return;
while (DisplayNamePaint.MeasureText(DisplayName) > Width - Margin * 2)
{
DisplayNamePaint.TextSize -= 1;
}
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
var shapedText = shaper.Shape(DisplayName, DisplayNamePaint);
var x = (Width - shapedText.Points[^1].X) / 2;
var y = _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE;
switch (Style)
{
case EIconStyle.Flat:
{
DisplayNamePaint.TextAlign = SKTextAlign.Right;
x = Width - Margin * 2 - shapedText.Points[^1].X;
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;
}
}
#if DEBUG
c.DrawLine(x, 0, x, Width, new SKPaint {Color = SKColors.Blue, IsStroke = true});
c.DrawLine(x + shapedText.Points[^1].X, 0, x + shapedText.Points[^1].X, Width, new SKPaint {Color = SKColors.Blue, IsStroke = true});
c.DrawRect(new SKRect(Margin, _STARTER_TEXT_POSITION, Width - Margin, y), new SKPaint {Color = SKColors.Blue, IsStroke = true});
#endif
c.DrawShapedText(shaper, DisplayName, x, y, DisplayNamePaint);
}
protected void DrawDescription(SKCanvas c)
{
if (string.IsNullOrWhiteSpace(Description)) return;
var maxLine = string.IsNullOrEmpty(DisplayName) ? 8 : 4;
var side = SKTextAlign.Center;
switch (Style)
{
case EIconStyle.Flat:
side = SKTextAlign.Right;
break;
}
Utils.DrawCenteredMultilineText(c, Description, maxLine, Width, Margin, side,
new SKRect(Margin, string.IsNullOrEmpty(DisplayName) ? _STARTER_TEXT_POSITION : _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE, Width - Margin, Height - _BOTTOM_TEXT_SIZE), DescriptionPaint);
}
protected void DrawToBottom(SKCanvas c, SKTextAlign side, string text)
{
if (string.IsNullOrEmpty(text)) return;
_shortDescriptionPaint.TextAlign = side;
_shortDescriptionPaint.TextSize = Utils.Typefaces.Bottom == null ? 15 : 13;
switch (side)
{
case SKTextAlign.Left:
_shortDescriptionPaint.Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.DisplayName;
var shaper = new CustomSKShaper(_shortDescriptionPaint.Typeface);
shaper.Shape(text, _shortDescriptionPaint);
c.DrawShapedText(shaper, text, Margin * 2.5f, Width - Margin * 2.5f, _shortDescriptionPaint);
break;
case SKTextAlign.Right:
_shortDescriptionPaint.Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.Default;
c.DrawText(text, Width - Margin * 2.5f, Width - Margin * 2.5f, _shortDescriptionPaint);
break;
}
}
}
}
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,202 +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.SB;
namespace FModel.Creator
{
public class CreatorPackage : IDisposable
{
private UObject _object;
private EIconStyle _style;
public CreatorPackage(UObject uObject, EIconStyle style)
{
_object = uObject;
_style = style;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public UCreator ConstructCreator()
{
TryConstructCreator(out var creator);
return creator;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryConstructCreator(out UCreator creator)
{
switch (_object.ExportType)
{
// Fortnite
case "AthenaConsumableEmoteItemDefinition":
case "AthenaSkyDiveContrailItemDefinition":
case "AthenaLoadingScreenItemDefinition":
case "AthenaVictoryPoseItemDefinition":
case "AthenaPetCarrierItemDefinition":
case "AthenaMusicPackItemDefinition":
case "AthenaBattleBusItemDefinition":
case "AthenaCharacterItemDefinition":
case "AthenaMapMarkerItemDefinition":
case "AthenaBackpackItemDefinition":
case "AthenaPickaxeItemDefinition":
case "AthenaGadgetItemDefinition":
case "AthenaGliderItemDefinition":
case "AthenaSprayItemDefinition":
case "AthenaDanceItemDefinition":
case "AthenaEmojiItemDefinition":
case "AthenaItemWrapDefinition":
case "AthenaToyItemDefinition":
case "FortHeroType":
case "FortTokenType":
case "FortAbilityKit":
case "FortWorkerType":
case "RewardGraphToken":
case "FortBannerTokenType":
case "FortVariantTokenType":
case "FortDecoItemDefinition":
case "FortStatItemDefinition":
case "FortAmmoItemDefinition":
case "FortEmoteItemDefinition":
case "FortBadgeItemDefinition":
case "FortAwardItemDefinition":
case "FortGadgetItemDefinition":
case "FortPlaysetItemDefinition":
case "FortGiftBoxItemDefinition":
case "FortOutpostItemDefinition":
case "FortVehicleItemDefinition":
case "FortCardPackItemDefinition":
case "FortDefenderItemDefinition":
case "FortCurrencyItemDefinition":
case "FortResourceItemDefinition":
case "FortBackpackItemDefinition":
case "FortCodeTokenItemDefinition":
case "FortSchematicItemDefinition":
case "FortWorldMultiItemDefinition":
case "FortAlterationItemDefinition":
case "FortExpeditionItemDefinition":
case "FortIngredientItemDefinition":
case "FortAccountBuffItemDefinition":
case "FortWeaponMeleeItemDefinition":
case "FortPlayerPerksItemDefinition":
case "FortPlaysetPropItemDefinition":
case "FortHomebaseNodeItemDefinition":
case "FortNeverPersistItemDefinition":
case "RadioContentSourceItemDefinition":
case "FortPlaysetGrenadeItemDefinition":
case "FortPersonalVehicleItemDefinition":
case "FortGameplayModifierItemDefinition":
case "FortHardcoreModifierItemDefinition":
case "FortConsumableAccountItemDefinition":
case "FortConversionControlItemDefinition":
case "FortAccountBuffCreditItemDefinition":
case "FortEventCurrencyItemDefinitionRedir":
case "FortPersistentResourceItemDefinition":
case "FortHomebaseBannerIconItemDefinition":
case "FortCampaignHeroLoadoutItemDefinition":
case "FortConditionalResourceItemDefinition":
case "FortChallengeBundleScheduleDefinition":
case "FortWeaponMeleeDualWieldItemDefinition":
case "FortDailyRewardScheduleTokenDefinition":
case "FortCreativeRealEstatePlotItemDefinition":
case "AthenaDanceItemDefinition_AdHocSquadsJoin_C":
creator = _style switch
{
EIconStyle.Cataba => new BaseCommunity(_object, _style, "Cataba"),
_ => new BaseIcon(_object, _style)
};
return true;
case "FortTrapItemDefinition":
case "FortTandemCharacterData":
case "FortSpyTechItemDefinition":
case "FortAccoladeItemDefinition":
case "FortContextTrapItemDefinition":
case "FortWeaponRangedItemDefinition":
case "Daybreak_LevelExitVehicle_PartItemDefinition_C":
creator = new BaseIconStats(_object, _style);
return true;
case "FortItemSeriesDefinition":
creator = new BaseSeries(_object, _style);
return true;
case "MaterialInstanceConstant"
when _object.Owner != null &&
(_object.Owner.Name.EndsWith($"/MI_OfferImages/{_object.Name}") ||
_object.Owner.Name.EndsWith($"/RenderSwitch_Materials/{_object.Name}")):
creator = new BaseMaterialInstance(_object, _style);
return true;
case "FortMtxOfferData":
creator = new BaseMtxOffer(_object, _style);
return true;
case "FortPlaylistAthena":
creator = new BasePlaylist(_object, _style);
return true;
case "FortFeatItemDefinition":
case "FortQuestItemDefinition":
case "AthenaDailyQuestDefinition":
case "FortUrgentQuestItemDefinition":
creator = new BaseQuest(_object, _style);
return true;
case "FortCompendiumItemDefinition":
case "FortChallengeBundleItemDefinition":
creator = new BaseBundle(_object, _style);
return true;
case "AthenaSeasonItemDefinition":
creator = new BaseSeason(_object, _style);
return true;
case "FortItemAccessTokenType":
creator = new BaseItemAccessToken(_object, _style);
return true;
case "PlaylistUserOptionEnum":
case "PlaylistUserOptionBool":
case "PlaylistUserOptionString":
case "PlaylistUserOptionIntEnum":
case "PlaylistUserOptionIntRange":
case "PlaylistUserOptionColorEnum":
case "PlaylistUserOptionFloatEnum":
case "PlaylistUserOptionFloatRange":
case "PlaylistUserOptionPrimaryAsset":
case "PlaylistUserOptionCollisionProfileEnum":
creator = new BaseUserControl(_object, _style);
return true;
// Battle Breakers
case "WExpGenericAccountItemDefinition":
creator = new BaseBreakersIcon(_object, EIconStyle.Default);
return true;
// Spellbreak
case "GQuest":
case "GAccolade":
case "GCosmeticCard":
case "GCosmeticSkin":
case "GCharacterPerk":
case "GCosmeticTitle":
case "GCosmeticBadge":
case "GCosmeticEmote":
case "GCosmeticTriumph":
case "GCosmeticRunTrail":
case "GCosmeticArtifact":
case "GCosmeticDropTrail":
creator = new BaseSpellIcon(_object, EIconStyle.Default);
return true;
case "GLeagueTier":
creator = new BaseLeague(_object, EIconStyle.Default);
return true;
case "GLeagueDivision":
creator = new BaseDivision(_object, EIconStyle.Default);
return true;
default:
creator = null;
return false;
}
}
public override string ToString() => $"{_object.ExportType} | {_style}";
public void Dispose()
{
_object = null;
}
}
}
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,4 +1,4 @@
using System;
using System;
using System.IO;
using System.Windows;
using CUE4Parse.UE4.Versions;
@ -6,219 +6,194 @@ using FModel.Settings;
using FModel.ViewModels;
using SkiaSharp;
namespace FModel.Creator
namespace FModel.Creator;
public class Typefaces
{
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)
{
private readonly Uri _BURBANK_BIG_CONDENSED_BOLD = new Uri("pack://application:,,,/Resources/BurbankBigCondensed-Bold.ttf");
private const string _EXT = ".ufont";
_viewModel = viewModel;
var language = UserSettings.Default.AssetLanguage;
// FortniteGame
private const string _FORTNITE_BASE_PATH = "/Game/UI/Foundation/Fonts/";
private const string _ASIA_ERINM = "AsiaERINM"; // korean fortnite
private const string _BURBANK_BIG_CONDENSED_BLACK = "BurbankBigCondensed-Black"; // russian
private const string _BURBANK_BIG_REGULAR_BLACK = "BurbankBigRegular-Black";
private const string _BURBANK_BIG_REGULAR_BOLD = "BurbankBigRegular-Bold"; // official fortnite ig
private const string _BURBANK_SMALL_MEDIUM = "BurbankSmall-Medium";
private const string _DROID_SANS_FORTNITE_SUBSET = "DroidSans-Fortnite-Subset";
private const string _NIS_JYAU = "NIS_JYAU"; // japanese fortnite
private const string _NOTO_COLOR_EMOJI = "NotoColorEmoji";
private const string _NOTO_SANS_BOLD = "NotoSans-Bold";
private const string _NOTO_SANS_FORTNITE_BOLD = "NotoSans-Fortnite-Bold";
private const string _NOTO_SANS_FORTNITE_ITALIC = "NotoSans-Fortnite-Italic";
private const string _NOTO_SANS_FORTNITE_REGULAR = "NotoSans-Fortnite-Regular";
private const string _NOTO_SANS_ITALIC = "NotoSans-Italic";
private const string _NOTO_SANS_REGULAR = "NotoSans-Regular";
private const string _NOTO_SANS_ARABIC_BLACK = "NotoSansArabic-Black"; // arabic fortnite
private const string _NOTO_SANS_ARABIC_BOLD = "NotoSansArabic-Bold";
private const string _NOTO_SANS_ARABIC_REGULAR = "NotoSansArabic-Regular";
private const string _NOTO_SANS_JP_BOLD = "NotoSansJP-Bold";
private const string _NOTO_SANS_KR_REGULAR = "NotoSansKR-Regular";
private const string _NOTO_SANS_SC_BLACK = "NotoSansSC-Black"; // simplified chinese fortnite
private const string _NOTO_SANS_SC_REGULAR = "NotoSansSC-Regular";
private const string _NOTO_SANS_TC_BLACK = "NotoSansTC-Black"; // traditional chinese fortnite
private const string _NOTO_SANS_TC_REGULAR = "NotoSansTC-Regular";
private const string _BURBANK_SMALL_BLACK = "burbanksmall-black";
private const string _BURBANK_SMALL_BOLD = "burbanksmall-bold";
Default = SKTypeface.FromStream(Application.GetResourceStream(_BURBANK_BIG_CONDENSED_BOLD)?.Stream);
// WorldExplorers
private const string _BATTLE_BREAKERS_BASE_PATH = "/Game/UMG/Fonts/Faces/";
private const string _HEMIHEAD426 = "HemiHead426";
private const string _NOTO_SANS_JP_REGULAR = "NotoSansJP-Regular";
private const string _LATO_BLACK = "Lato-Black";
private const string _LATO_BLACK_ITALIC = "Lato-BlackItalic";
private const string _LATO_LIGHT = "Lato-Light";
private const string _LATO_MEDIUM = "Lato-Medium";
private readonly CUE4ParseViewModel _viewModel;
public readonly SKTypeface Default; // used as a fallback font for all untranslated strings (item source, ...)
public readonly SKTypeface DisplayName;
public readonly SKTypeface Description;
public readonly SKTypeface Bottom; // must be null for non-latin base languages
public readonly SKTypeface Bundle;
public readonly SKTypeface BundleNumber;
public Typefaces(CUE4ParseViewModel viewModel)
switch (viewModel.Provider.InternalGameName.ToUpperInvariant())
{
byte[] data;
_viewModel = viewModel;
var language = UserSettings.Default.AssetLanguage;
Default = SKTypeface.FromStream(Application.GetResourceStream(_BURBANK_BIG_CONDENSED_BOLD)?.Stream);
switch (viewModel.Game)
case "FORTNITEGAME":
{
case FGame.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
{
var namePath = _FORTNITE_BASE_PATH +
language switch
{
ELanguage.Korean => _ASIA_ERINM,
ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK,
ELanguage.Japanese => _NIS_JYAU,
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
_ => string.Empty
};
if (viewModel.Provider.TrySaveAsset(namePath + _EXT, out data))
{
var m = new MemoryStream(data) {Position = 0};
DisplayName = SKTypeface.FromStream(m);
}
else DisplayName = Default;
ELanguage.Chinese => _XIANGHEHEI_SC_PRO_HEAVY,
_ => _NORMS_PRO_EXTRABOLD_ITALIC
} + _EXT);
var descriptionPath = _FORTNITE_BASE_PATH +
language switch
{
ELanguage.Korean => _NOTO_SANS_KR_REGULAR,
ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
ELanguage.Arabic => _NOTO_SANS_ARABIC_REGULAR,
ELanguage.TraditionalChinese => _NOTO_SANS_TC_REGULAR,
ELanguage.Chinese => _NOTO_SANS_SC_REGULAR,
_ => _NOTO_SANS_REGULAR
};
if (viewModel.Provider.TrySaveAsset(descriptionPath + _EXT, out data))
{
var m = new MemoryStream(data) {Position = 0};
Description = SKTypeface.FromStream(m);
}
else Description = Default;
var bottomPath = _FORTNITE_BASE_PATH +
language switch
{
ELanguage.Korean => string.Empty,
ELanguage.Japanese => string.Empty,
ELanguage.Arabic => string.Empty,
ELanguage.TraditionalChinese => string.Empty,
ELanguage.Chinese => string.Empty,
_ => _BURBANK_SMALL_BOLD
};
if (viewModel.Provider.TrySaveAsset(bottomPath + _EXT, out data))
{
var m = new MemoryStream(data) {Position = 0};
Bottom = SKTypeface.FromStream(m);
}
// else keep it null
if (viewModel.Provider.TrySaveAsset(_FORTNITE_BASE_PATH + _BURBANK_BIG_CONDENSED_BLACK + _EXT, out data))
{
var m = new MemoryStream(data) {Position = 0};
BundleNumber = SKTypeface.FromStream(m);
}
else BundleNumber = Default;
var bundleNamePath = _FORTNITE_BASE_PATH +
language switch
{
ELanguage.Korean => _ASIA_ERINM,
ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK,
ELanguage.Japanese => _NIS_JYAU,
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
_ => string.Empty
};
if (viewModel.Provider.TrySaveAsset(bundleNamePath + _EXT, out data))
{
var m = new MemoryStream(data) {Position = 0};
Bundle = SKTypeface.FromStream(m);
}
else Bundle = BundleNumber;
break;
}
case FGame.WorldExplorers:
Description = OnTheFly(_PANDAGAME_BASE_PATH + language switch
{
var namePath = _BATTLE_BREAKERS_BASE_PATH +
language switch
{
ELanguage.Korean => _NOTO_SANS_KR_REGULAR,
ELanguage.Russian => _LATO_BLACK,
ELanguage.Japanese => _NOTO_SANS_JP_REGULAR,
ELanguage.Chinese => _NOTO_SANS_SC_REGULAR,
_ => _HEMIHEAD426
};
if (viewModel.Provider.TrySaveAsset(namePath + _EXT, out data))
{
var m = new MemoryStream(data) {Position = 0};
DisplayName = SKTypeface.FromStream(m);
}
else DisplayName = Default;
ELanguage.Chinese => _XIANGHEHEI_SC_PRO_BLACK,
_ => _NORMS_STD_CONDENSED_MEDIUM
} + _EXT);
var descriptionPath = _BATTLE_BREAKERS_BASE_PATH +
language switch
{
ELanguage.Korean => _NOTO_SANS_KR_REGULAR,
ELanguage.Russian => _LATO_BLACK,
ELanguage.Japanese => _NOTO_SANS_JP_REGULAR,
ELanguage.Chinese => _NOTO_SANS_SC_REGULAR,
_ => _HEMIHEAD426
};
if (viewModel.Provider.TrySaveAsset(descriptionPath + _EXT, out data))
{
var m = new MemoryStream(data) {Position = 0};
Description = SKTypeface.FromStream(m);
}
else Description = Default;
TandemDisplayName = OnTheFly(_PANDAGAME_BASE_PATH + language switch
{
ELanguage.Chinese => _XIANGHEHEI_SC_PRO_BLACK,
_ => _NORMS_STD_CONDENSED_EXTRABOLD_ITALIC
} + _EXT);
break;
}
case FGame.ShooterGame:
break;
case FGame.DeadByDaylight:
break;
case FGame.OakGame:
break;
case FGame.Dungeons:
break;
case FGame.g3:
break;
case FGame.StateOfDecay2:
break;
case FGame.Prospect:
break;
case FGame.Indiana:
break;
case FGame.RogueCompany:
break;
case FGame.SwGame:
break;
case FGame.Platform:
break;
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)
{
if (!_viewModel.Provider.TrySaveAsset(path, out var data)) return Default;
var m = new MemoryStream(data) {Position = 0};
return SKTypeface.FromStream(m);
}
}
}
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,309 +1,420 @@
using System;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.UObject;
using CUE4Parse.Utils;
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
{
public 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)
{
private static ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
private static readonly Regex _htmlRegex = new("<.*?>");
public static Typefaces Typefaces;
public static string RemoveHtmlTags(string s)
var match = _htmlRegex.Match(s);
while (match.Success)
{
var match = _htmlRegex.Match(s);
while (match.Success)
{
s = s.Replace(match.Value, string.Empty);
match = match.NextMatch();
}
return s;
s = s.Replace(match.Value, string.Empty);
match = match.NextMatch();
}
public static bool TryGetDisplayAsset(UObject uObject, out SKBitmap preview)
return s;
}
public static bool TryGetDisplayAsset(UObject uObject, out SKBitmap preview)
{
if (uObject.TryGetValue(out FSoftObjectPath sidePanelIcon, "SidePanelIcon"))
{
if (uObject.TryGetValue(out FSoftObjectPath displayAsset, "DisplayAssetPath"))
{
preview = GetDisplayAsset(displayAsset);
return preview != null;
}
if (uObject.TryGetValue(out FSoftObjectPath sidePanelIcon, "SidePanelIcon"))
{
preview = GetBitmap(sidePanelIcon);
return preview != null;
}
var path = $"/Game/Catalog/MI_OfferImages/MI_{uObject.Name.Replace("Athena_Commando_", string.Empty)}";
if (!TryLoadObject(path, out UMaterialInstanceConstant material)) // non-obfuscated item definition
{
if (!TryLoadObject($"{path[..path.LastIndexOf('_')]}_{path.SubstringAfterLast('_').ToLower()}", out material)) // Try to get MI with lowercase obfuscation
TryLoadObject(path[..path.LastIndexOf('_')], out material); // hopefully gets obfuscated item definition
}
preview = GetBitmap(material);
preview = GetBitmap(sidePanelIcon);
return preview != null;
}
public static SKBitmap GetDisplayAsset(FSoftObjectPath path)
var path = $"/Game/Catalog/MI_OfferImages/MI_{uObject.Name.Replace("Athena_Commando_", string.Empty)}";
if (!TryLoadObject(path, out UMaterialInstanceConstant material)) // non-obfuscated item definition
{
if (!TryLoadObject(path.AssetPathName.Text, out UObject obj)) return null;
if (obj.TryGetValue(out FStructFallback type, "DetailsImage") &&
type.TryGetValue(out FPackageIndex resource, "ResourceObject") && resource.ResolvedObject?.Outer != null &&
!resource.ResolvedObject.Outer.Name.Text.Contains("FortniteGame/Content/Athena/Prototype/Textures/"))
{
return GetBitmap(resource);
}
return null;
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 GetBitmap(FPackageIndex packageIndex)
{
while (true)
{
if (!TryGetPackageIndexExport(packageIndex, out UExport export)) return null;
switch (export)
{
case UTexture2D texture:
return GetBitmap(texture);
case UMaterialInstanceConstant material:
return GetBitmap(material);
case UObject uObject:
{
if (uObject.TryGetValue(out FSoftObjectPath previewImage, "LargePreviewImage", "SmallPreviewImage")) return GetBitmap(previewImage);
if (uObject.TryGetValue(out string largePreview, "LargePreviewImage")) return GetBitmap(largePreview);
if (uObject.TryGetValue(out FPackageIndex smallPreview, "SmallPreviewImage"))
{
packageIndex = smallPreview;
continue;
}
return null;
preview = GetBitmap(material);
return preview != null;
}
public static SKBitmap GetBitmap(FPackageIndex packageIndex)
{
while (true)
{
if (!TryGetPackageIndexExport(packageIndex, out UObject export)) return null;
switch (export)
{
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;
}
default:
return null;
return null;
}
}
}
public static SKBitmap GetBitmap(UMaterialInstanceConstant material)
{
if (material == null) return null;
foreach (var textureParameter in material.TextureParameterValues)
{
if (!(textureParameter.ParameterValue is UTexture2D texture)) continue;
switch (textureParameter.ParameterInfo.Name.Text)
{
case "TextureA":
case "TextureB":
case "OfferImage":
{
return GetBitmap(texture);
}
}
}
return null;
}
public static SKBitmap GetB64Bitmap(string b64) => SKBitmap.Decode(new MemoryStream(Convert.FromBase64String(b64)) {Position = 0});
public static SKBitmap GetBitmap(FSoftObjectPath softObjectPath) => GetBitmap(softObjectPath.AssetPathName.Text);
public static SKBitmap GetBitmap(string fullPath) => TryLoadObject(fullPath, out UTexture2D texture) ? GetBitmap(texture) : null;
public static SKBitmap GetBitmap(UTexture2D texture) => texture.IsVirtual ? null : SKBitmap.Decode(texture.Decode()?.Encode());
public static SKBitmap GetBitmap(byte[] data) => SKBitmap.Decode(data);
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 : UExport
{
if (packageIndex.ResolvedObject == null)
{
export = default;
return false;
}
var outerChain = new List<string>();
var current = packageIndex.ResolvedObject.Outer;
while (current != null)
{
outerChain.Add(current.Name.Text);
current = current.Outer;
}
if (outerChain.Count < 1)
{
export = default;
return false;
}
if (!_applicationView.CUE4Parse.Provider.TryLoadPackage(outerChain[^1], out var pkg))
{
export = default;
return false;
}
export = pkg.GetExport(packageIndex.ResolvedObject.Index) as T;
return export != null;
}
// fullpath must be either without any extension or with the export objectname
public static bool TryLoadObject<T>(string fullPath, out T export) where T : UExport
{
return _applicationView.CUE4Parse.Provider.TryLoadObject(fullPath, out export);
}
public static IEnumerable<UExport> LoadExports(string fullPath)
{
return _applicationView.CUE4Parse.Provider.LoadObjectExports(fullPath);
}
public static string GetLocalizedResource(string namespacee, string key, string defaultValue)
{
return _applicationView.CUE4Parse.Provider.GetLocalizedString(namespacee, key, defaultValue);
}
public static string GetFullPath(string partialPath)
{
var regex = new Regex(partialPath, RegexOptions.IgnoreCase | RegexOptions.Compiled);
foreach (var path in _applicationView.CUE4Parse.Provider.Files.Keys)
{
if (regex.IsMatch(path))
{
return path;
}
}
return string.Empty;
}
public static void DrawCenteredMultilineText(SKCanvas c, string text, int maxCount, int size, int margin, SKTextAlign side, SKRect area, SKPaint paint)
{
var lineHeight = paint.TextSize * 1.2f;
var lines = SplitLines(text, paint, area.Width - margin);
#if DEBUG
c.DrawRect(new SKRect(area.Left, area.Top, area.Right, area.Bottom), new SKPaint {Color = SKColors.Red, IsStroke = true});
#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.Points[^1].X / 2,
SKTextAlign.Right => size - margin - shapedText.Points[^1].X,
SKTextAlign.Left => margin,
_ => throw new NotImplementedException()
};
c.DrawShapedText(shaper, lineText, x, y, paint);
}
}
public static void DrawMultilineText(SKCanvas c, string text, int size, int margin, SKTextAlign side, SKRect area, SKPaint paint, out float yPos)
{
yPos = area.Top;
var lineHeight = paint.TextSize * 1.2f;
var lines = SplitLines(text, paint, area.Width);
if (lines == null) return;
foreach (var line in lines)
{
if (line == null) continue;
var lineText = line.Trim();
var shaper = new CustomSKShaper(paint.Typeface);
var shapedText = shaper.Shape(lineText, paint);
var x = side switch
{
SKTextAlign.Center => area.MidX - shapedText.Points[^1].X / 2,
SKTextAlign.Right => size - margin - shapedText.Points[^1].X,
SKTextAlign.Left => area.Left,
_ => throw new NotImplementedException()
};
c.DrawShapedText(shaper, lineText, x, yPos, paint);
yPos += lineHeight;
}
#if DEBUG
c.DrawRect(new SKRect(area.Left, area.Top - paint.TextSize, area.Right, yPos), new SKPaint {Color = SKColors.Red, IsStroke = true});
#endif
}
public static List<string> SplitLines(string text, SKPaint paint, float maxWidth)
{
if (string.IsNullOrEmpty(text)) return null;
var spaceWidth = paint.MeasureText(" ");
var lines = text.Split('\n', StringSplitOptions.RemoveEmptyEntries);
var ret = new List<string>(lines.Length);
foreach (var line in lines)
{
if (string.IsNullOrWhiteSpace(line)) continue;
float width = 0;
var words = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
var lineResult = new StringBuilder();
foreach (var word in words)
{
var wordWidth = paint.MeasureText(word);
var wordWithSpaceWidth = wordWidth + spaceWidth;
var wordWithSpace = word + " ";
if (width + wordWidth > maxWidth)
{
ret.Add(lineResult.ToString());
lineResult = new StringBuilder(wordWithSpace);
width = wordWithSpaceWidth;
}
else
{
lineResult.Append(wordWithSpace);
width += wordWithSpaceWidth;
}
}
ret.Add(lineResult.ToString());
}
return ret;
}
}
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,139 +1,116 @@
using System.ComponentModel;
using System;
using System.ComponentModel;
namespace FModel
namespace FModel;
public enum EBuildKind
{
public enum EBuildKind
{
Debug,
Release,
Unknown
}
Debug,
Release,
Unknown
}
public enum EErrorKind
{
Close,
Ignore,
Restart
}
public enum EErrorKind
{
Ignore,
Restart,
ResetSettings
}
public enum SettingsOut
{
Restart,
ReloadLocres,
Nothing
}
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 EStatusKind
{
Ready, // ready
Loading, // doing stuff
Stopping, // trying to stop
Stopped, // stopped
Failed, // crashed
Completed // worked
}
public enum EAesReload
{
[Description("Always")]
Always,
[Description("Never")]
Never,
[Description("Once Per Day")]
OncePerDay
}
public enum EAesReload
{
[Description("Always")]
Always,
[Description("Never")]
Never,
[Description("Once Per Day")]
OncePerDay
}
public enum EDiscordRpc
{
[Description("Always")]
Always,
[Description("Never")]
Never
}
public enum EDiscordRpc
{
[Description("Always")]
Always,
[Description("Never")]
Never
}
public enum EEnabledDisabled
{
[Description("Disabled")]
Disabled,
[Description("Enabled")]
Enabled
}
public enum ELoadingMode
{
[Description("Multiple")]
Multiple,
[Description("All")]
All,
[Description("All (New)")]
AllButNew,
[Description("All (Modified)")]
AllButModified
}
public enum FGame
{
[Description("Unknown")]
Unknown,
[Description("Fortnite")]
FortniteGame,
[Description("Valorant")]
ShooterGame,
[Description("Dead By Daylight")]
DeadByDaylight,
[Description("Borderlands 3")]
OakGame,
[Description("Minecraft Dungeons")]
Dungeons,
[Description("Battle Breakers")]
WorldExplorers,
[Description("Spellbreak")]
g3,
[Description("State Of Decay 2")]
StateOfDecay2,
[Description("The Cycle")]
Prospect,
[Description("The Outer Worlds")]
Indiana,
[Description("Rogue Company")]
RogueCompany,
[Description("Star Wars: Jedi Fallen Order")]
SwGame,
[Description("Core")]
Platform
}
// public enum EUpdateMode
// {
// [Description("Stable")]
// Stable,
// [Description("Beta")]
// Beta,
// [Description("QA Testing")]
// Qa
// }
public enum ELoadingMode
{
[Description("Single")]
Single,
[Description("Multiple")]
Multiple,
[Description("All")]
All,
[Description("All (New)")]
AllButNew,
[Description("All (Modified)")]
AllButModified
}
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 EUpdateMode
{
[Description("Stable")]
Stable,
[Description("Beta")]
Beta
}
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 ECompressedAudio
{
[Description("Play the decompressed data")]
PlayDecompressed,
[Description("Play the compressed data (might not always be a valid audio data)")]
PlayCompressed
}
public enum EEndpointType
{
Aes,
Mapping
}
public enum EIconStyle
{
[Description("Default")]
Default,
[Description("No Background")]
NoBackground,
[Description("No Text")]
NoText,
[Description("Flat")]
Flat,
[Description("Cataba")]
Cataba,
// [Description("Community")]
// CommunityMade
}
}
[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

@ -4,44 +4,51 @@ using System.Xml;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Highlighting.Xshd;
namespace FModel.Extensions
namespace FModel.Extensions;
public static class AvalonExtensions
{
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)
{
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");
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)]
private static IHighlightingDefinition LoadHighlighter(string resourceName)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IHighlightingDefinition HighlighterSelector(string ext)
{
switch (ext)
{
var executingAssembly = Assembly.GetExecutingAssembly();
using var stream = executingAssembly.GetManifestResourceStream($"{executingAssembly.GetName().Name}.Resources.{resourceName}");
using var reader = new XmlTextReader(stream);
return HighlightingLoader.Load(reader, HighlightingManager.Instance);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IHighlightingDefinition HighlighterSelector(string ext)
{
switch (ext)
{
case "ini":
case "csv":
return _iniHighlighter;
case "xml":
return _xmlHighlighter;
case "h":
case "cpp":
return _cppHighlighter;
case "bat":
case "txt":
case "po":
return null;
default:
return _jsonHighlighter;
}
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

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

View File

@ -1,84 +1,59 @@
using FModel.Properties;
using System;
using System.ComponentModel;
using System.Resources;
using System.Runtime.CompilerServices;
namespace FModel.Extensions
namespace FModel.Extensions;
public static class EnumExtensions
{
public static class EnumExtensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string GetDescription(this Enum value)
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string GetDescription(this Enum value)
{
var fi = value.GetType().GetField(value.ToString());
if (fi == null) return $"{value} ({value:D})";
var attributes = (DescriptionAttribute[]) fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
return attributes.Length > 0 ? attributes[0].Description : $"{value} ({value:D})";
}
var fi = value.GetType().GetField(value.ToString());
if (fi == null) return $"{value} ({value:D})";
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string GetLocalizedDescription(this Enum value) => value.GetLocalizedDescription(Resources.ResourceManager);
var attributes = (DescriptionAttribute[]) fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes.Length > 0) return attributes[0].Description;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string GetLocalizedDescription(this Enum value, ResourceManager resourceManager)
{
var resourceName = value.GetType().Name + "_" + value;
var description = resourceManager.GetString(resourceName);
if (string.IsNullOrEmpty(description))
{
description = value.GetDescription();
}
return description;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string GetLocalizedCategory(this Enum value) => value.GetLocalizedCategory(Resources.ResourceManager);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string GetLocalizedCategory(this Enum value, ResourceManager resourceManager)
{
var resourceName = value.GetType().Name + "_" + value + "_Category";
var description = resourceManager.GetString(resourceName);
return description;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T ToEnum<T>(this string value, T defaultValue)
{
if (!Enum.TryParse(typeof(T), value, true, out var ret))
return defaultValue;
return (T) ret;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool HasAnyFlags<T>(this T flags, T contains) where T : Enum, IConvertible => (flags.ToInt32(null) & contains.ToInt32(null)) != 0;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetIndex(this Enum value)
var suffix = $"{value:D}";
var current = Convert.ToInt32(suffix);
var target = current & ~0xF;
if (current != target)
{
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);
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

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

View File

@ -1,134 +1,185 @@
using System;
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using ICSharpCode.AvalonEdit.Document;
namespace FModel.Extensions
namespace FModel.Extensions;
public static class StringExtensions
{
public static class StringExtensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string GetReadableSize(double size)
{
[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)
{
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();
order++;
size /= 1024;
}
[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 GetLineNumber(this string s, string lineToFind)
{
if (int.TryParse(lineToFind, out var index))
return s.GetLineNumber(index);
lineToFind = $" \"Name\": \"{lineToFind}\",";
using var reader = new StringReader(s);
var lineNum = 0;
string line;
while ((line = reader.ReadLine()) != null)
{
lineNum++;
if (line.Equals(lineToFind, StringComparison.OrdinalIgnoreCase))
return lineNum;
}
return 1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetLineNumber(this string s, int index)
{
using var reader = new StringReader(s);
var lineNum = 0;
string line;
while ((line = reader.ReadLine()) != null)
{
lineNum++;
if (line.Equals(" {"))
index--;
if (index == -1)
return lineNum + 1;
}
return 1;
}
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

@ -2,17 +2,18 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0-windows</TargetFramework>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<ApplicationIcon>FModel.ico</ApplicationIcon>
<Version>4.0.0</Version>
<AssemblyVersion>4.0.0.0</AssemblyVersion>
<FileVersion>4.0.0.0</FileVersion>
<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>true</PublishSingleFile>
<PublishSingleFile Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">true</PublishSingleFile>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
<StartupObject>FModel.App</StartupObject>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@ -38,11 +39,18 @@
<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" />
@ -67,18 +75,20 @@
<None Remove="Resources\athena.png" />
<None Remove="Resources\Json.xshd" />
<None Remove="Resources\Ini.xshd" />
<None Remove="Resources\Verse.xshd" />
<None Remove="Resources\Xml.xshd" />
<None Remove="Resources\Cpp.xshd" />
<None Remove="Resources\Changelog.xshd" />
<None Remove="Resources\unix.png" />
<None Remove="Resources\linux.png" />
<None Remove="Resources\stateofdecay2.png" />
<None Remove="Resources\T_Placeholder_Item_Image.png" />
<None Remove="Resources\checker.png" />
<None Remove="Resources\T_ClipSize_Weapon_Stats.png" />
<None Remove="Resources\T_DamagePerBullet_Weapon_Stats.png" />
<None Remove="Resources\T_ReloadTime_Weapon_Stats.png" />
<None Remove="Resources\T-Icon-Pets-64.png" />
<None Remove="Resources\T-Icon-Quests-64.png" />
<None Remove="Resources\city_pin.png" />
<None Remove="Resources\pin.png" />
<None Remove="Resources\Default.png" />
<None Remove="Resources\NoBackground.png" />
<None Remove="Resources\NoText.png" />
@ -89,36 +99,76 @@
<None Remove="Resources\delete.png" />
<None Remove="Resources\edit.png" />
<None Remove="Resources\go_to_directory.png" />
<None Remove="Resources\npcleftside.png" />
<None Remove="Resources\default.frag" />
<None Remove="Resources\default.vert" />
<None Remove="Resources\grid.frag" />
<None Remove="Resources\grid.vert" />
<None Remove="Resources\skybox.frag" />
<None Remove="Resources\skybox.vert" />
<None Remove="Resources\framebuffer.frag" />
<None Remove="Resources\framebuffer.vert" />
<None Remove="Resources\outline.frag" />
<None Remove="Resources\outline.vert" />
<None Remove="Resources\picking.frag" />
<None Remove="Resources\picking.vert" />
<None Remove="Resources\light.frag" />
<None Remove="Resources\light.vert" />
<None Remove="Resources\bone.frag" />
<None Remove="Resources\bone.vert" />
<None Remove="Resources\collision.vert" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\Json.xshd" />
<EmbeddedResource Include="Resources\Ini.xshd" />
<EmbeddedResource Include="Resources\Verse.xshd" />
<EmbeddedResource Include="Resources\Xml.xshd" />
<EmbeddedResource Include="Resources\Cpp.xshd" />
<EmbeddedResource Include="Resources\Changelog.xshd" />
<EmbeddedResource Include="Resources\default.frag" />
<EmbeddedResource Include="Resources\default.vert" />
<EmbeddedResource Include="Resources\grid.frag" />
<EmbeddedResource Include="Resources\grid.vert" />
<EmbeddedResource Include="Resources\skybox.frag" />
<EmbeddedResource Include="Resources\skybox.vert" />
<EmbeddedResource Include="Resources\framebuffer.frag" />
<EmbeddedResource Include="Resources\framebuffer.vert" />
<EmbeddedResource Include="Resources\outline.frag" />
<EmbeddedResource Include="Resources\outline.vert" />
<EmbeddedResource Include="Resources\picking.frag" />
<EmbeddedResource Include="Resources\picking.vert" />
<EmbeddedResource Include="Resources\light.frag" />
<EmbeddedResource Include="Resources\light.vert" />
<EmbeddedResource Include="Resources\bone.frag" />
<EmbeddedResource Include="Resources\bone.vert" />
<EmbeddedResource Include="Resources\collision.vert" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AdonisUI.ClassicTheme.NET5" Version="1.17.1" />
<PackageReference Include="AdonisUI.NET5" Version="1.17.1" />
<PackageReference Include="Autoupdater.NET.Official" Version="1.6.4" />
<PackageReference Include="AvalonEdit" Version="6.0.1" />
<PackageReference Include="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.175" />
<PackageReference Include="EpicManifestParser" Version="1.2.0" />
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="NVorbis" Version="0.10.1" />
<PackageReference Include="Ookii.Dialogs.Wpf" Version="3.1.0" />
<PackageReference Include="RestSharp" Version="106.11.7" />
<PackageReference Include="Serilog" Version="2.10.0" />
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.80.2" />
<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>
<ProjectReference Include="..\CUE4Parse\CUE4Parse-Conversion\CUE4Parse-Conversion.csproj" />
<ProjectReference Include="..\CUE4Parse\CUE4Parse-Fortnite\CUE4Parse-Fortnite.csproj" />
<ProjectReference Include="..\CUE4Parse\CUE4Parse\CUE4Parse.csproj" />
</ItemGroup>
@ -131,6 +181,7 @@
<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" />
@ -138,6 +189,12 @@
<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" />
@ -162,14 +219,14 @@
<Resource Include="Resources\athena.png" />
<Resource Include="Resources\unix.png" />
<Resource Include="Resources\linux.png" />
<Resource Include="Resources\stateofdecay2.png" />
<Resource Include="Resources\T_Placeholder_Item_Image.png" />
<Resource Include="Resources\checker.png" />
<Resource Include="Resources\T_ClipSize_Weapon_Stats.png" />
<Resource Include="Resources\T_DamagePerBullet_Weapon_Stats.png" />
<Resource Include="Resources\T_ReloadTime_Weapon_Stats.png" />
<Resource Include="Resources\T-Icon-Pets-64.png" />
<Resource Include="Resources\T-Icon-Quests-64.png" />
<Resource Include="Resources\city_pin.png" />
<Resource Include="Resources\pin.png" />
<Resource Include="Resources\Default.png" />
<Resource Include="Resources\NoBackground.png" />
<Resource Include="Resources\NoText.png" />
@ -180,6 +237,24 @@
<Resource Include="Resources\delete.png" />
<Resource Include="Resources\edit.png" />
<Resource Include="Resources\go_to_directory.png" />
<Resource Include="Resources\npcleftside.png" />
<Resource Include="Resources\nx.png" />
<Resource Include="Resources\ny.png" />
<Resource Include="Resources\nz.png" />
<Resource Include="Resources\px.png" />
<Resource Include="Resources\py.png" />
<Resource Include="Resources\pz.png" />
<Resource Include="Resources\pointlight.png" />
<Resource Include="Resources\spotlight.png" />
<Resource Include="Resources\link_on.png" />
<Resource Include="Resources\link_off.png" />
<Resource Include="Resources\link_has.png" />
<Resource Include="Resources\tl_play.png" />
<Resource Include="Resources\tl_pause.png" />
<Resource Include="Resources\tl_rewind.png" />
<Resource Include="Resources\tl_forward.png" />
<Resource Include="Resources\tl_previous.png" />
<Resource Include="Resources\tl_next.png" />
</ItemGroup>
<ItemGroup>

View File

@ -1,14 +1,12 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30804.86
# 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-Fortnite", "..\CUE4Parse\CUE4Parse-Fortnite\CUE4Parse-Fortnite.csproj", "{7765FB4C-B54D-427B-ABB6-1073687E56BD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CUE4Parse-Conversion", "..\CUE4Parse\CUE4Parse-Conversion\CUE4Parse-Conversion.csproj", "{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}"
EndProject
Global
@ -25,10 +23,6 @@ Global
{C4620341-BBB7-4384-AC7D-5082D3E0386E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C4620341-BBB7-4384-AC7D-5082D3E0386E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C4620341-BBB7-4384-AC7D-5082D3E0386E}.Release|Any CPU.Build.0 = Release|Any CPU
{7765FB4C-B54D-427B-ABB6-1073687E56BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7765FB4C-B54D-427B-ABB6-1073687E56BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7765FB4C-B54D-427B-ABB6-1073687E56BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7765FB4C-B54D-427B-ABB6-1073687E56BD}.Release|Any CPU.Build.0 = Release|Any CPU
{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}.Release|Any CPU.ActiveCfg = Release|Any CPU

View File

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

View File

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

View File

@ -4,93 +4,87 @@ using SkiaSharp;
using SkiaSharp.HarfBuzz;
using Buffer = HarfBuzzSharp.Buffer;
namespace FModel.Framework
namespace FModel.Framework;
public class CustomSKShaper : SKShaper
{
public class CustomSKShaper : SKShaper
private const int _FONT_SIZE_SCALE = 512;
private readonly Font _font;
public CustomSKShaper(SKTypeface typeface) : base(typeface)
{
private const int _FONT_SIZE_SCALE = 512;
private readonly Font _font;
private readonly Buffer _buffer;
using var blob = Typeface.OpenStream(out var index).ToHarfBuzzBlob();
using var face = new Face(blob, index);
face.Index = index;
face.UnitsPerEm = Typeface.UnitsPerEm;
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();
}
_buffer = new Buffer();
}
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);
}
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);
}
_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

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

View File

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

View File

@ -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

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

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

View File

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

@ -1,50 +1,49 @@
using System;
namespace FModel.Framework
namespace FModel.Framework;
public abstract class ViewModelCommand<TContextViewModel> : Command where TContextViewModel : ViewModel
{
public abstract class ViewModelCommand<TContextViewModel> : Command where TContextViewModel : ViewModel
private WeakReference _parent;
public TContextViewModel ContextViewModel
{
private WeakReference _parent;
public TContextViewModel ContextViewModel
get
{
get
{
if (_parent is {IsAlive: true})
return (TContextViewModel) _parent.Target;
if (_parent is { IsAlive: true })
return (TContextViewModel) _parent.Target;
return null;
}
private set
{
if (ContextViewModel == value)
return;
_parent = value != null ? new WeakReference(value) : null;
}
return null;
}
protected ViewModelCommand(TContextViewModel contextViewModel)
private set
{
ContextViewModel = contextViewModel /*?? throw new ArgumentNullException(nameof(contextViewModel))*/;
}
if (ContextViewModel == value)
return;
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;
_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,88 +1,114 @@
using System;
using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Windows;
namespace FModel
namespace FModel;
public static class Helper
{
public static class Helper
public static string FixKey(string key)
{
[StructLayout(LayoutKind.Explicit)]
private struct NanUnion
if (string.IsNullOrEmpty(key))
return string.Empty;
var keySpan = key.AsSpan().Trim();
if (keySpan.Length > sizeof(char) * (2 /* 0x */ + 32 /* FAES = 256 bit */)) // maybe strictly check for length?
return string.Empty; // bullshit key
Span<char> resultSpan = stackalloc char[keySpan.Length + 2 /* pad for 0x */];
keySpan.ToUpperInvariant(resultSpan[2..]);
if (resultSpan[2..].StartsWith("0X"))
resultSpan = resultSpan[2..];
else
resultSpan[0] = '0';
resultSpan[1] = 'x';
return new string(resultSpan);
}
public static void OpenWindow<T>(string windowName, Action action) where T : Window
{
if (!IsWindowOpen<T>(windowName))
{
[FieldOffset(0)]
internal double DoubleValue;
[FieldOffset(0)]
internal readonly ulong UlongValue;
action();
}
public static void OpenWindow<T>(string windowName, Action action) where T : Window
else
{
if (!IsWindowOpen<T>(windowName))
{
action();
}
else
{
var w = GetOpenedWindow<T>(windowName);
if (windowName == "Search View") w.WindowState = WindowState.Normal;
w.Focus();
}
}
public static T GetWindow<T>(string windowName, Action action) where T : Window
{
if (!IsWindowOpen<T>(windowName))
{
action();
}
var ret = (T) GetOpenedWindow<T>(windowName);
ret.Focus();
ret.Activate();
return ret;
}
public static void CloseWindow<T>(string windowName) where T : Window
{
if (!IsWindowOpen<T>(windowName)) return;
GetOpenedWindow<T>(windowName).Close();
}
private static bool IsWindowOpen<T>(string name = "") where T : Window
{
return string.IsNullOrEmpty(name)
? Application.Current.Windows.OfType<T>().Any()
: Application.Current.Windows.OfType<T>().Any(w => w.Title.Equals(name));
}
private static Window GetOpenedWindow<T>(string name) where T : Window
{
return Application.Current.Windows.OfType<T>().FirstOrDefault(w => w.Title.Equals(name));
}
public static bool IsNaN(double value)
{
var t = new NanUnion {DoubleValue = value};
var exp = t.UlongValue & 0xfff0000000000000;
var man = t.UlongValue & 0x000fffffffffffff;
return exp is 0x7ff0000000000000 or 0xfff0000000000000 && man != 0;
}
public static bool AreVirtuallyEqual(double d1, double d2)
{
if (double.IsPositiveInfinity(d1))
return double.IsPositiveInfinity(d2);
if (double.IsNegativeInfinity(d1))
return double.IsNegativeInfinity(d2);
if (IsNaN(d1))
return IsNaN(d2);
var n = d1 - d2;
var d = (Math.Abs(d1) + Math.Abs(d2) + 10) * 1.0e-15;
return -d < n && d > n;
var w = GetOpenedWindow<T>(windowName);
if (windowName == "Search View") w.WindowState = WindowState.Normal;
w.Focus();
}
}
}
public static T GetWindow<T>(string windowName, Action action) where T : Window
{
if (!IsWindowOpen<T>(windowName))
{
action();
}
var ret = (T) GetOpenedWindow<T>(windowName);
ret.Focus();
ret.Activate();
return ret;
}
public static void CloseWindow<T>(string windowName) where T : Window
{
if (!IsWindowOpen<T>(windowName)) return;
GetOpenedWindow<T>(windowName).Close();
}
private static bool IsWindowOpen<T>(string name = "") where T : Window
{
return string.IsNullOrEmpty(name)
? Application.Current.Windows.OfType<T>().Any()
: Application.Current.Windows.OfType<T>().Any(w => w.Title.Equals(name));
}
private static Window GetOpenedWindow<T>(string name) where T : Window
{
return Application.Current.Windows.OfType<T>().FirstOrDefault(w => w.Title.Equals(name));
}
public static bool IsNaN(double value)
{
var ulongValue = Unsafe.As<double, ulong>(ref value);
var exp = ulongValue & 0xfff0000000000000;
var man = ulongValue & 0x000fffffffffffff;
return exp is 0x7ff0000000000000 or 0xfff0000000000000 && man != 0;
}
public static bool AreVirtuallyEqual(double d1, double d2)
{
if (double.IsPositiveInfinity(d1))
return double.IsPositiveInfinity(d2);
if (double.IsNegativeInfinity(d1))
return double.IsNegativeInfinity(d2);
if (IsNaN(d1))
return IsNaN(d2);
var n = d1 - d2;
var d = (Math.Abs(d1) + Math.Abs(d2) + 10) * 1.0e-15;
return -d < n && d > n;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float DegreesToRadians(float degrees)
{
const float ratio = MathF.PI / 180f;
return ratio * degrees;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float RadiansToDegrees(float radians)
{
const float ratio = 180f / MathF.PI;
return radians * ratio;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

View File

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

BIN
FModel/Resources/cube.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 831 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

BIN
FModel/Resources/label.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 B

View File

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

BIN
FModel/Resources/light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

BIN
FModel/Resources/nx.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
FModel/Resources/ny.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
FModel/Resources/nz.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
FModel/Resources/px.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
FModel/Resources/py.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
FModel/Resources/pz.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

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