mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2025-08-30 10:25:00 +00:00
Compare commits
1215 commits
34643e08ea
...
5c077af5b2
Author | SHA1 | Date | |
---|---|---|---|
|
5c077af5b2 | ||
|
8cd3785e3c | ||
|
7e01e7332b | ||
|
c3a837b054 | ||
|
a92567886c | ||
|
4cbfaefc1a | ||
|
705e6b3308 | ||
|
56878befef | ||
|
44cdbb08c4 | ||
|
d6fe54ee79 | ||
|
32c775ef3f | ||
|
1605514424 | ||
|
743e8b13c2 | ||
|
661ddb15f7 | ||
|
3170346b83 | ||
|
e6d737125e | ||
|
3d88fbb11f | ||
|
0d6bc2bedd | ||
|
da5f691865 | ||
|
f63cbf073a | ||
|
30721c3a30 | ||
|
2b1f346097 | ||
|
2dcc528847 | ||
|
5cb33e895c | ||
|
1b91f3b57c | ||
|
e5e3270fef | ||
|
e003a38cab | ||
|
e6fd5d9b12 | ||
|
967397311b | ||
|
542f59ef72 | ||
|
065ee74aea | ||
|
312b5c8812 | ||
|
e9aa6872a3 | ||
|
4f9b63f22c | ||
|
962519e7f0 | ||
|
34bae4a431 | ||
|
79478080f6 | ||
|
65a3ccc8fe | ||
|
4ac851c387 | ||
|
70d5730b70 | ||
|
c527fed70c | ||
|
054a5cd00b | ||
|
19c5de8fe2 | ||
|
dbc26fd621 | ||
|
eb1986b246 | ||
|
d661d93184 | ||
|
4a07d0a347 | ||
|
832f01345f | ||
|
e8d41c938c | ||
|
a94bd7f36c | ||
|
c185f10e65 | ||
|
52f3c63071 | ||
|
e71062b914 | ||
|
3aa6a36b5e | ||
|
a89fa9c121 | ||
|
cb2015f51a | ||
|
139ce3847c | ||
|
87b40c1203 | ||
|
b9852c316b | ||
|
444697620c | ||
|
6abeefb15d | ||
|
530bd1f11d | ||
|
76c0dea92b | ||
|
2d7a2202f5 | ||
|
1f18a3d1f4 | ||
|
0d57f61ffa | ||
|
9fa70775b9 | ||
|
463afb43c0 | ||
|
2833827fbf | ||
|
b4e52df156 | ||
|
cf70bf2c6c | ||
|
fb6d1b36d3 | ||
|
0c0c63bc03 | ||
|
01620fda9b | ||
|
3bcd6813c1 | ||
|
d44966efd2 | ||
|
79c3e2de11 | ||
|
8372d47d11 | ||
|
d89ebdf58f | ||
|
1e8c472084 | ||
|
17cef6f897 | ||
|
2cc43ee1f6 | ||
|
2277acccfa | ||
|
ed86b326ab | ||
|
596c5813d7 | ||
|
ae68925911 | ||
|
d7c3799562 | ||
|
47ab14a855 | ||
|
940e165612 | ||
|
5181023887 | ||
|
32e774b005 | ||
|
c353cc54a1 | ||
|
48d0798e5f | ||
|
0463b4e26b | ||
|
0c1842038f | ||
|
aa0b8cbbbe | ||
|
52cd4f6c36 | ||
|
6be7e89222 | ||
|
abea46939f | ||
|
3a5f1e14cf | ||
|
849e413bb7 | ||
|
b4a7bd7462 | ||
|
beb64528fb | ||
|
eac940bccb | ||
|
aa1c71cafd | ||
|
f4bedc359a | ||
|
01428dd5cf | ||
|
c6e19c8512 | ||
|
e30a4ebb7b | ||
|
949f409cff | ||
|
4c5aa9743e | ||
|
3fa2b3574b | ||
|
1e41ac3052 | ||
|
936812ae1e | ||
|
ad3bf2d2d2 | ||
|
1b1c2a124b | ||
|
2aff2fa201 | ||
|
7ec602f6ec | ||
|
4fbeffb57c | ||
|
f029a27a3f | ||
|
8a99fa4184 | ||
|
f21b015292 | ||
|
bc8e35409b | ||
|
006f1dce0a | ||
|
78186188d7 | ||
|
762486639e | ||
|
b5bb9721da | ||
|
4029e92181 | ||
|
9c7f87c225 | ||
|
43420092f3 | ||
|
d8cd580535 | ||
|
13c08b285d | ||
|
6238b72d8c | ||
|
bf92bf3f60 | ||
|
6467cd75a8 | ||
|
32dc25bbf6 | ||
|
11cb409bd2 | ||
|
f245365ed3 | ||
|
e0d0230b88 | ||
|
f8634e2b81 | ||
|
4f68f63e2d | ||
|
e9e0567f8b | ||
|
8db768fd2a | ||
|
cf814ee4e2 | ||
|
43b7be4e71 | ||
|
7d555b4155 | ||
|
88ad99c3ef | ||
|
d92b7ff3f5 | ||
|
66c6a78319 | ||
|
e6a2a09801 | ||
|
8944e07fe1 | ||
|
188260ea81 | ||
|
22c6ce42ba | ||
|
d8445fe54a | ||
|
c3592ee6f5 | ||
|
e402328969 | ||
|
150a1d5b0a | ||
|
d468477663 | ||
|
ecaf51ef41 | ||
|
05627a50d5 | ||
|
efe333da92 | ||
|
e946b54249 | ||
|
1d4a94041b | ||
|
11da19ce1e | ||
|
126ed3a67a | ||
|
468197e74e | ||
|
7f17ad8bb8 | ||
|
e9ef340c63 | ||
|
61bda6c2c2 | ||
|
07f649ca37 | ||
|
11ecd07606 | ||
|
822c5ba405 | ||
|
d61f89b0e4 | ||
|
9dfae07225 | ||
|
01fad012f2 | ||
|
9843f9549c | ||
|
044106e8a9 | ||
|
d338be9270 | ||
|
e4d3e18e4f | ||
|
dd104365e7 | ||
|
b0581f728a | ||
|
5e50432b30 | ||
|
314d8af9c8 | ||
|
f91261f748 | ||
|
0567849665 | ||
|
a701e6a4a9 | ||
|
abfc4ca79c | ||
|
797a8bfcba | ||
|
d70838cca9 | ||
|
0d845154ef | ||
|
9e04db2c5c | ||
|
c0d3b9daa2 | ||
|
0f336ae0e6 | ||
|
8c8eedf17a | ||
|
793b533b88 | ||
|
852a74c33d | ||
|
bfdafd7f37 | ||
|
37d0d1b14e | ||
|
2649e247fa | ||
|
fafe157a70 | ||
|
39a4292dc5 | ||
|
aad0a0afb6 | ||
|
f8516cfb4d | ||
|
3987445bd6 | ||
|
eea829ca7d | ||
|
d98628ca0f | ||
|
a6b3eb0b81 | ||
|
49e64a517e | ||
|
ef9fd48ab2 | ||
|
efdc869206 | ||
|
077e0c920f | ||
|
bb714f210b | ||
|
f0a112f185 | ||
|
1025fed57f | ||
|
9e182f70d2 | ||
|
87fea7674b | ||
|
e309bac626 | ||
|
8c6324641e | ||
|
6b7b5d0bf7 | ||
|
a74dcbc6f9 | ||
|
d6d7a0886f | ||
|
7e76cf6634 | ||
|
48013168ef | ||
|
1201a781c9 | ||
|
9ad5126806 | ||
|
c2755a745c | ||
|
a73b879fee | ||
|
e7c4b1ba38 | ||
|
62d24fcf4c | ||
|
3b897417fb | ||
|
7bb2336972 | ||
|
f13eb5be53 | ||
|
7aa77a11cf | ||
|
f54e140b65 | ||
|
0ea42e18e9 | ||
|
a8a99c327d | ||
|
e2722434e0 | ||
|
a08ada7e69 | ||
|
21e5501835 | ||
|
125aa0a438 | ||
|
6591fd8f2a | ||
|
b205a0b931 | ||
|
7a6326b178 | ||
|
df1dbeaef0 | ||
|
91a16f6dc3 | ||
|
acd10ffbe5 | ||
|
86241d53da | ||
|
99769cb50b | ||
|
9036f94970 | ||
|
f00f631db8 | ||
|
d0b64fe4da | ||
|
f378c0adb6 | ||
|
ae6562f42b | ||
|
313cf4b253 | ||
|
dbd52a5bf1 | ||
|
810162de27 | ||
|
711656bcf4 | ||
|
95262b316a | ||
|
82bbb9d30d | ||
|
b06a28e79c | ||
|
811cc4b4b0 | ||
|
00a4f47e42 | ||
|
d703609a0a | ||
|
2c035d77cc | ||
|
96d1324af1 | ||
|
543d6454f6 | ||
|
c414de48f2 | ||
|
70fbd200bd | ||
|
ec10f273b6 | ||
|
2f223f84f5 | ||
|
32f32ba0bd | ||
|
25bf04d581 | ||
|
f036593037 | ||
|
9ec7a5068a | ||
|
55e1967423 | ||
|
d3207de231 | ||
|
afc08e285c | ||
|
28fc5109d4 | ||
|
2b5ba8dfa9 | ||
|
0ef1df33c9 | ||
|
b3aa6a0b8d | ||
|
126da44a95 | ||
|
f6d9acb63a | ||
|
f78431da3c | ||
|
21bbef5542 | ||
|
46c40f67cc | ||
|
cdfce1af12 | ||
|
8aff75b308 | ||
|
58f101bf01 | ||
|
c49666c4b4 | ||
|
73ec37a473 | ||
|
adf2b49a72 | ||
|
a11bde6f76 | ||
|
8b6b91cc23 | ||
|
94eae116c7 | ||
|
faadf406c3 | ||
|
d4c7339373 | ||
|
55d34c3c9d | ||
|
7ed374f1e5 | ||
|
4e87c7d40c | ||
|
377697a358 | ||
|
32701d9ab0 | ||
|
c897d89b35 | ||
|
1f65a9c5a1 | ||
|
d061ba4e43 | ||
|
d4d52a1394 | ||
|
cedf17fb99 | ||
|
97620e6038 | ||
|
1d3b5eb246 | ||
|
40c292127d | ||
|
79cc20ffe8 | ||
|
4eb3c764ab | ||
|
e2efd2bc81 | ||
|
894d8cb463 | ||
|
3fd80504e3 | ||
|
42ec7f7796 | ||
|
bc4c944d4d | ||
|
715d2ba65f | ||
|
067b56c5fa | ||
|
d73eb4e392 | ||
|
5a19397481 | ||
|
eb80a52841 | ||
|
57d0871b5c | ||
|
066ed53a6d | ||
|
6f35209d63 | ||
|
5be5abf4c9 | ||
|
ab89cd33f9 | ||
|
7febe5b5ad | ||
|
4cc922c373 | ||
|
4557a681d1 | ||
|
2e99737485 | ||
|
7db71574d1 | ||
|
b1376f7401 | ||
|
06a440f21c | ||
|
6dcec592f4 | ||
|
a0d3f8ec55 | ||
|
9f9af722ca | ||
|
3962989f83 | ||
|
b3d1adbd7f | ||
|
cf0da45dd8 | ||
|
68695329cf | ||
|
1b68c3a8d6 | ||
|
647ce880e1 | ||
|
e9915e52fa | ||
|
18fe868233 | ||
|
0869362fa1 | ||
|
87a09cae21 | ||
|
4766a92946 | ||
|
74b30c0be4 | ||
|
d0ff60ad0a | ||
|
1dd6fb8dd4 | ||
|
2042f3db8a | ||
|
b67cbc132d | ||
|
720d279f89 | ||
|
97754e5ed3 | ||
|
e2e8c383cd | ||
|
19d1a8c593 | ||
|
8dd007a7c2 | ||
|
3747b94246 | ||
|
7cc9464573 | ||
|
7b9bce3b4e | ||
|
91550181f5 | ||
|
c5308ccbb9 | ||
|
dcac362003 | ||
|
ae51c2ea5a | ||
|
905181c9af | ||
|
11d9998e5c | ||
|
37dc66bc18 | ||
|
74627795d2 | ||
|
903ca851bc | ||
|
24c469dc31 | ||
|
eb6becc03f | ||
|
fbd80ea41f | ||
|
ccd0148e9b | ||
|
3a7379e851 | ||
|
a7055efb27 | ||
|
98cd45453b | ||
|
c13167ece8 | ||
|
7d9a9f452d | ||
|
c30b519bc4 | ||
|
9b06bd6b11 | ||
|
f3db69cca8 | ||
|
9174b83c93 | ||
|
74a8b8862e | ||
|
88ed66affa | ||
|
3ac2a02291 | ||
|
c1d4bba813 | ||
|
80f3fb6150 | ||
|
94742eb9ba | ||
|
5c06d263b2 | ||
|
be60e52725 | ||
|
9e07f5dd5a | ||
|
f1bef04165 | ||
|
9687cff9d4 | ||
|
f9c1f7a5f7 | ||
|
3d3e2bd378 | ||
|
9804111583 | ||
|
7cbb896a57 | ||
|
0001017c00 | ||
|
7d6a4cb763 | ||
|
fee5ab49af | ||
|
5201160973 | ||
|
4772dfdc13 | ||
|
9e3efc0de7 | ||
|
c4d24e6d3e | ||
|
fc82b79c9a | ||
|
a98f6f9778 | ||
|
4ca42c3975 | ||
|
51127dfd87 | ||
|
cae7c8a785 | ||
|
204501779d | ||
|
d7c83a5b70 | ||
|
4e31003a0f | ||
|
cce8c18822 | ||
|
ea6cb75b77 | ||
|
9bd2c0b13b | ||
|
af716f9273 | ||
|
88d0e8c3b0 | ||
|
6114dfcedb | ||
|
52d891c8ff | ||
|
d040e1f72f | ||
|
8393c789e2 | ||
|
39104fe77f | ||
|
0e9bb20592 | ||
|
56a18e1cf6 | ||
|
948030ef1d | ||
|
8dce8afdfc | ||
|
e7c6798f44 | ||
|
5482dee750 | ||
|
5549fddec3 | ||
|
65461d839b | ||
|
2a63c1a7ba | ||
|
fee29f69ec | ||
|
539720f84d | ||
|
576f8e54ff | ||
|
50ca508fdd | ||
|
8824a382b9 | ||
|
d6ec32f334 | ||
|
d356cfaee6 | ||
|
49eff01afd | ||
|
c4aff95886 | ||
|
9f41e22428 | ||
|
123c9396ba | ||
|
136a5dfad4 | ||
|
f23eddcf2c | ||
|
13d8127e75 | ||
|
7d0772170f | ||
|
43bf102d68 | ||
|
24101d654d | ||
|
e82b578eb8 | ||
|
3eace5a7c8 | ||
|
153f2be177 | ||
|
380f9e70d5 | ||
|
ff2dff0635 | ||
|
24d1116deb | ||
|
8e085171d5 | ||
|
4819a74c29 | ||
|
f95258358c | ||
|
962d0b0dc2 | ||
|
6fa4280e31 | ||
|
0d2a737a8a | ||
|
779919d11b | ||
|
4a9e8490fc | ||
|
80db44be2b | ||
|
0f85b4a85d | ||
|
c0e0523651 | ||
|
41a246c9a5 | ||
|
cfed20983b | ||
|
2273a1b346 | ||
|
a69e08e986 | ||
|
1c7abb6391 | ||
|
839ff43123 | ||
|
84e0104984 | ||
|
092732a535 | ||
|
ff6aea9efd | ||
|
f1c15c8a84 | ||
|
beab53a489 | ||
|
8bbdc8c0f4 | ||
|
80948ba4ad | ||
|
5c2cc82340 | ||
|
d8a6abc48d | ||
|
5cbea2851c | ||
|
2cf29a67d9 | ||
|
e8cd87c1f1 | ||
|
075f03235d | ||
|
fcb5fe85ff | ||
|
bbd598a560 | ||
|
5ca21d1bb3 | ||
|
a9ba2a7a1c | ||
|
a2c0625137 | ||
|
d0d1d3c376 | ||
|
d0fe228e21 | ||
|
1b7dc5f1ac | ||
|
ea0e4ee56d | ||
|
1b1d0a978d | ||
|
bf007e2a2c | ||
|
aa24e08fdb | ||
|
d72b2bb2da | ||
|
49693f06e9 | ||
|
99ad45997e | ||
|
849553e0f0 | ||
|
48ca0ecef3 | ||
|
403c6250f8 | ||
|
ffbc8c80c8 | ||
|
aacb200638 | ||
|
603e370908 | ||
|
43d8b522cb | ||
|
b75cfcb833 | ||
|
dc800dc48e | ||
|
9706a55eca | ||
|
6b11f8b852 | ||
|
6ad9ffc7f9 | ||
|
895eeed852 | ||
|
4541f91d84 | ||
|
068a995cdd | ||
|
1a07e082c9 | ||
|
4fa12a3486 | ||
|
0a9ac0e93c | ||
|
974a78919d | ||
|
fb9bf59a7e | ||
|
e0b4c30719 | ||
|
80ffe6faa7 | ||
|
15083a4776 | ||
|
0dbffa6875 | ||
|
a776ce1a81 | ||
|
738bd8b7c7 | ||
|
d79f99475e | ||
|
221c3db6e6 | ||
|
e92c6ec8f7 | ||
|
de0a56ec95 | ||
|
aefa8d149f | ||
|
29ee386dfe | ||
|
0d965ba91c | ||
|
a29e20098b | ||
|
a2c5758112 | ||
|
01fd37e08b | ||
|
5eecef17a1 | ||
|
9df5395ff8 | ||
|
50d9584a38 | ||
|
0e24ae911f | ||
|
c87a39b6c2 | ||
|
760190d135 | ||
|
9f8e42bd3c | ||
|
1f75a641e0 | ||
|
47f0fc7236 | ||
|
79185596c1 | ||
|
8cae34eed7 | ||
|
a0ae4de5e8 | ||
|
e7df920e74 | ||
|
07958590a4 | ||
|
bc01d5cffa | ||
|
295e6f652e | ||
|
ea01052b18 | ||
|
cacb95bdd3 | ||
|
44fa64542a | ||
|
43c7f4e883 | ||
|
157a0a36dc | ||
|
034a861c69 | ||
|
333e5326a2 | ||
|
8e7de54aa0 | ||
|
ea81e061d2 | ||
|
cd3b7d7b95 | ||
|
5d4e1d276b | ||
|
55221646b8 | ||
|
f599e53448 | ||
|
7629a1a92d | ||
|
1c8f320a54 | ||
|
e7ddf9fbc8 | ||
|
9d6450454e | ||
|
b80a8d60af | ||
|
d968fb271c | ||
|
70f0a54191 | ||
|
29b94ea736 | ||
|
8ca4d933aa | ||
|
20e8a4566d | ||
|
69c5c05039 | ||
|
299a720575 | ||
|
8d7500e8c0 | ||
|
cadfa3b7ce | ||
|
5342601f8b | ||
|
f6b1ff13bc | ||
|
d43e9506b3 | ||
|
753f51dcf6 | ||
|
9303e4c272 | ||
|
b0f4a075b5 | ||
|
031f888a25 | ||
|
bc94a5a6ac | ||
|
2450961b28 | ||
|
8b8737006f | ||
|
5c4fa108c0 | ||
|
f4245ee0da | ||
|
aa8f5087b5 | ||
|
edc668dad5 | ||
|
2771a13d5e | ||
|
b63e02d9e7 | ||
|
9d3595c855 | ||
|
250b5cb54f | ||
|
e457b3e03b | ||
|
38e0cb6ab1 | ||
|
4746e8d7a5 | ||
|
db2644c0ff | ||
|
f9aff5b11b | ||
|
cc9797c452 | ||
|
a97acfe18e | ||
|
65ed58350b | ||
|
9e6c17507f | ||
|
3360493444 | ||
|
e657bc900b | ||
|
5a5f1f83f3 | ||
|
277416b6a3 | ||
|
37a40c24a1 | ||
|
e90b74f788 | ||
|
ad11fb26b9 | ||
|
287603ad30 | ||
|
1c25713464 | ||
|
7cbc5b0dc7 | ||
|
7ccf75cd86 | ||
|
eec304f42f | ||
|
7e4508f991 | ||
|
f74d1655d5 | ||
|
5057f3d1de | ||
|
a103b9641f | ||
|
9e58ef70fb | ||
|
f5a453b2e7 | ||
|
d432e086cc | ||
|
f2280b1c0f | ||
|
7f619640f2 | ||
|
9d5613cc95 | ||
|
96c788a420 | ||
|
07f792e358 | ||
|
24db9235e1 | ||
|
370e00c9b9 | ||
|
6d5767aef2 | ||
|
b3ebfd0910 | ||
|
a9a4dad511 | ||
|
dc04ff23d7 | ||
|
72f7317db2 | ||
|
57ebeb25e4 | ||
|
7fd1d312d7 | ||
|
c2643c3873 | ||
|
a1c1187b43 | ||
|
a7e46ed55b | ||
|
22c24f53f6 | ||
|
262c4fb9a1 | ||
|
75aea15539 | ||
|
6e8494e615 | ||
|
4392f5e642 | ||
|
21616e9399 | ||
|
814c563c55 | ||
|
537c7cb5a1 | ||
|
1801c542a6 | ||
|
f854a6e07a | ||
|
e193f7392a | ||
|
eb8d878aeb | ||
|
f19ae72b27 | ||
|
63eca96c98 | ||
|
b008f88e0d | ||
|
a50a014de3 | ||
|
f78ab4da1d | ||
|
1d1c1403c3 | ||
|
743462d96e | ||
|
72a6b92daf | ||
|
285a607d9a | ||
|
c6526527f1 | ||
|
7b3c9f1131 | ||
|
402f2707b3 | ||
|
e89f9f50dc | ||
|
4eaa3842c6 | ||
|
1d9e34359c | ||
|
5361df2544 | ||
|
60a707b963 | ||
|
6222c389e3 | ||
|
6265d751f8 | ||
|
aff83bab4d | ||
|
d3d8aaae7b | ||
|
c263b4bc83 | ||
|
7a2be205b9 | ||
|
cd0e8a301d | ||
|
8b80478229 | ||
|
0c62428fc8 | ||
|
2904e660e9 | ||
|
e2b9cc9ed5 | ||
|
5ac9cee76c | ||
|
62fa3ef934 | ||
|
d81e409d93 | ||
|
aac09f86fe | ||
|
428c5443f6 | ||
|
1e5ec7850f | ||
|
2e0dfab9fb | ||
|
d78117e377 | ||
|
7aa0db844b | ||
|
d72d93083f | ||
|
9769a626bc | ||
|
afcf9a26cc | ||
|
28363652ef | ||
|
42ff442ef8 | ||
|
183cec62dc | ||
|
da0964a7b7 | ||
|
fabc16d265 | ||
|
57bb462a38 | ||
|
2f647127b1 | ||
|
bbddb34853 | ||
|
1d88ec473e | ||
|
bb98883807 | ||
|
451d4d1399 | ||
|
b66a955d65 | ||
|
34d5ed1169 | ||
|
ea6e2aef7b | ||
|
099d1b3eb1 | ||
|
ab817bb1ea | ||
|
6077ba67f7 | ||
|
3bbb4b06c8 | ||
|
f92fe3ff15 | ||
|
067a04e069 | ||
|
e9697e6f40 | ||
|
0345107e71 | ||
|
5177e1275e | ||
|
be57d2fa3c | ||
|
ff7b7b5d3e | ||
|
e7c42dae0c | ||
|
07ad53a09b | ||
|
113326089a | ||
|
ed0abb8215 | ||
|
405fe21c53 | ||
|
e5b280c646 | ||
|
36cbfe91bb | ||
|
4081f329d2 | ||
|
402d5b730b | ||
|
5e7e2f45f3 | ||
|
80393bbc6d | ||
|
2c44785c39 | ||
|
329be321eb | ||
|
e79d4e69c2 | ||
|
fda944a9f7 | ||
|
ab00a126fa | ||
|
7689c7d2e4 | ||
|
91d1fb74f7 | ||
|
a5921ab96c | ||
|
7fca9d4c7e | ||
|
0e26f9d936 | ||
|
af71c3acd7 | ||
|
9e8c4f1fa1 | ||
|
268166b7e0 | ||
|
9bd65a4782 | ||
|
8a3e623d28 | ||
|
9d671888ef | ||
|
1fc71a3852 | ||
|
af546a14a2 | ||
|
7d5904b539 | ||
|
fb2d587a15 | ||
|
2b4a42ac0a | ||
|
0f36a05311 | ||
|
12fc467312 | ||
|
f3dbd763d2 | ||
|
9ed4d7e8fc | ||
|
43abebcafd | ||
|
a5ae068801 | ||
|
3f8e96965b | ||
|
367c56c5e7 | ||
|
b2aa111b71 | ||
|
c6c48bd9a8 | ||
|
835b79e3de | ||
|
ba12d505d6 | ||
|
193f83e000 | ||
|
b69ef72d51 | ||
|
44eb13ef9b | ||
|
3fca9cee63 | ||
|
e7a460042b | ||
|
ad1021ee33 | ||
|
e9c411f770 | ||
|
8711db49c7 | ||
|
7485bc990e | ||
|
c333bbaa23 | ||
|
28d3999797 | ||
|
8f216ada02 | ||
|
52dfee61fa | ||
|
a62d2b94a9 | ||
|
2e486d987b | ||
|
83e1e1f68f | ||
|
3d6882c282 | ||
|
11383e9385 | ||
|
7d567de24f | ||
|
dc56ccada5 | ||
|
d3490f8c99 | ||
|
1959a5250f | ||
|
ae2c2db28f | ||
|
82af988dce | ||
|
e3fca16793 | ||
|
2221594883 | ||
|
be7313453f | ||
|
c9c284787e | ||
|
93e4f799f4 | ||
|
c7abb19698 | ||
|
7c7dc11f18 | ||
|
b883ad3d50 | ||
|
426a9c0c10 | ||
|
0b7a94b1cc | ||
|
711d3896cb | ||
|
98236a541a | ||
|
3179d33a17 | ||
|
3f23b79087 | ||
|
f16b8630ff | ||
|
1c747657d2 | ||
|
924f72f96c | ||
|
26dc895a27 | ||
|
58257f6ac1 | ||
|
b52c73f2b0 | ||
|
564d679f96 | ||
|
5695bc20e7 | ||
|
3dc7699aac | ||
|
dc60c12963 | ||
|
6b63884e38 | ||
|
4eff23a6f2 | ||
|
d8db5f7d88 | ||
|
23d31ff73b | ||
|
a89351f083 | ||
|
fb3277ea35 | ||
|
71cadd31ed | ||
|
1bd281830e | ||
|
751f569e69 | ||
|
c363a53f9d | ||
|
fc83685b0f | ||
|
25e04cf061 | ||
|
8965bb059e | ||
|
d61cc1d5be | ||
|
45bcb93c21 | ||
|
2e8338a5dc | ||
|
cb43dd34b4 | ||
|
83216e171c | ||
|
3b55c5b15f | ||
|
d4ef73fb93 | ||
|
7f924cd563 | ||
|
bbfbce6404 | ||
|
91e9d5d55c | ||
|
6ec380e699 | ||
|
d311a16459 | ||
|
a5b4a7d2cc | ||
|
59a2b4ce6f | ||
|
5788d04890 | ||
|
30e9b2fb67 | ||
|
a60560e36c | ||
|
511fd88ee9 | ||
|
a73ba04706 | ||
|
17f614ea06 | ||
|
29143505cc | ||
|
c462a5f8d0 | ||
|
0d5fd4a0c5 | ||
|
54547e7dd1 | ||
|
4b58cd0812 | ||
|
dd809e0455 | ||
|
22d2b95e7f | ||
|
310c074d78 | ||
|
1dba74c704 | ||
|
d127b824c8 | ||
|
fda568c6a8 | ||
|
be027e2a01 | ||
|
0ec6233119 | ||
|
94f1720870 | ||
|
4f3f43d6e7 | ||
|
6689ae9e9d | ||
|
8e7ddc0f53 | ||
|
fda719ae7b | ||
|
666b57a1cb | ||
|
d86dacf354 | ||
|
a413318782 | ||
|
6289dfdd02 | ||
|
c77e164513 | ||
|
d2acb5d167 | ||
|
adfd73d6ab | ||
|
728558b279 | ||
|
d104e3381b | ||
|
14e8e1f42f | ||
|
24e535c90a | ||
|
cbff9a3719 | ||
|
9a3e58a7d4 | ||
|
c8a86d3df8 | ||
|
44c32cb0fc | ||
|
96f4b8effc | ||
|
15fcb2b15f | ||
|
50f99f9ba7 | ||
|
33084db558 | ||
|
35f7113091 | ||
|
83d198493c | ||
|
cd71102f8d | ||
|
283d7c0971 | ||
|
e9b1d5fc04 | ||
|
13a2b192b9 | ||
|
27b1cd32b7 | ||
|
fffea09270 | ||
|
d3705042cf | ||
|
f9b0acb998 | ||
|
377626c279 | ||
|
b536619773 | ||
|
b189518383 | ||
|
f6569907c8 | ||
|
a34cbaf87f | ||
|
ec5a736be6 | ||
|
00b54150a4 | ||
|
b0c3c020e0 | ||
|
045b240949 | ||
|
b84b531a2c | ||
|
fc78eeee4f | ||
|
910500ea26 | ||
|
3dc693f8b6 | ||
|
a7b4f3d420 | ||
|
c992180f7c | ||
|
b0468fa056 | ||
|
c026c104f5 | ||
|
353f1780d1 | ||
|
c648a5dbf2 | ||
|
a1a4acc12d | ||
|
2c2da142da | ||
|
2777ec03a4 | ||
|
86ead7096e | ||
|
bdb8857d5c | ||
|
2325317902 | ||
|
adb74b4539 | ||
|
f737ce6ed3 | ||
|
f650c3962d | ||
|
b2fb2916dc | ||
|
0927966c09 | ||
|
642331b2d0 | ||
|
372e43d181 | ||
|
2fdf63da35 | ||
|
ccb75e8d4e | ||
|
23996095e1 | ||
|
7352ab79bd | ||
|
0ad2780c5d | ||
|
68b12b8a97 | ||
|
6c489812f9 | ||
|
6f8d62ac33 | ||
|
a19413b1ea | ||
|
6bdfde4a72 | ||
|
360b73dd0b | ||
|
62510c2403 | ||
|
a2e647ac8b | ||
|
c823bba3d1 | ||
|
e917da0a7b | ||
|
8de74c361c | ||
|
6c842fea20 | ||
|
41ce874595 | ||
|
9216fff9b0 | ||
|
0253df7269 | ||
|
40f44ece5b | ||
|
49114c5d37 | ||
|
9b8330ced1 | ||
|
1baf7fea80 | ||
|
588eea019d | ||
|
9307f514fe | ||
|
e6df6d5116 | ||
|
d82d231539 | ||
|
0d7c78305d | ||
|
e71bd1f5a4 | ||
|
7e62782fa1 | ||
|
7fe621a839 | ||
|
7dc4bb940c | ||
|
5279aefec9 | ||
|
b541c5d974 | ||
|
4d77d21b15 | ||
|
7f19faabc4 | ||
|
2ba225265f | ||
|
10d58c26b0 | ||
|
e0e660f357 | ||
|
647b69bb0c | ||
|
94cd22e243 | ||
|
324ef37505 | ||
|
3cc189869b | ||
|
cc7def5178 | ||
|
842d1a72a4 | ||
|
3d52aab7a3 | ||
|
f3b20dbbd3 | ||
|
a54bf9c7fd | ||
|
2ae503cfec | ||
|
b8db4947d6 | ||
|
53794ab4cc | ||
|
804984b8c1 | ||
|
63ec721aa1 | ||
|
0444d47458 | ||
|
2ac0627063 | ||
|
b755ed8d98 | ||
|
64126a3df5 | ||
|
e78d3ecf8f | ||
|
c246de1ee1 | ||
|
a7ef5580a4 | ||
|
f961b1cae2 | ||
|
827066b589 | ||
|
1dfe8866ab | ||
|
14cf382192 | ||
|
1f22313b8a | ||
|
e359dad1e8 | ||
|
4d88df2120 | ||
|
1c11a7003f | ||
|
2932c24418 | ||
|
bb46b699eb | ||
|
dc000b33eb | ||
|
75d8101030 | ||
|
90fb8a1883 | ||
|
50baf0c869 | ||
|
9e133b0a8a | ||
|
da3f5a48c9 | ||
|
36d361aa22 | ||
|
b6e31e581a | ||
|
5dd26101f2 | ||
|
81c92fc11f | ||
|
44e166d411 | ||
|
5b8b943b99 | ||
|
c9138e4d1f | ||
|
2bfc24f22b | ||
|
ebb9d373c3 | ||
|
fc2e28ab18 | ||
|
b4f30c341e | ||
|
995f6c5adb | ||
|
7dee8e6e54 | ||
|
bcdbe2993c | ||
|
8bf14b7d0c | ||
|
261a237934 | ||
|
ede705987c | ||
|
edfcfc8c55 | ||
|
828de1c512 | ||
|
85e77486f7 | ||
|
50f467c366 | ||
|
97f8de0de4 | ||
|
dfa8e635f1 | ||
|
ed5b7fbb31 | ||
|
418f7d71f9 | ||
|
7111492578 | ||
|
baaaaddead | ||
|
c2956cd253 | ||
|
999557b501 | ||
|
73914c6858 | ||
|
4976a099af | ||
|
ec5f8344ed | ||
|
219c46f56b | ||
|
4f5b0f2453 | ||
|
0d2c48c374 | ||
|
359e40bf4d | ||
|
bd39e2023c | ||
|
809f4107ab | ||
|
e838fef8e6 | ||
|
2c5c66286a | ||
|
a64f7aa5d3 | ||
|
5ca22a6910 | ||
|
bed90dc230 | ||
|
ff144cf30b | ||
|
58468abba7 | ||
|
5d26f8b433 | ||
|
b68ba97ca6 | ||
|
ba449cabd6 | ||
|
61f1bdd89a | ||
|
6d6441de6f | ||
|
f7f502dd92 | ||
|
6133087c2c | ||
|
4226ba8978 | ||
|
b012096151 | ||
|
39313be86b | ||
|
b1986fb9c5 | ||
|
4d74ccbc4e | ||
|
bef3815901 | ||
|
37fa11fe08 | ||
|
e8d3fa8a35 | ||
|
0a0789faea | ||
|
5d6e44d5f1 | ||
|
7119d6a476 | ||
|
1093dd3400 | ||
|
c7906edc1e | ||
|
3fcc2cc5bc | ||
|
34f97e3e3e | ||
|
e0fcd655e7 | ||
|
b1089958c7 | ||
|
ea2b63b1bb | ||
|
7fb5d8c65c | ||
|
86f8cb76f2 | ||
|
b03eef01ca | ||
|
5d7517b839 | ||
|
1790c13fac | ||
|
180ec17ce3 | ||
|
5a39439fcc | ||
|
2f6e6ef7cd | ||
|
75e0992509 | ||
|
67f0425e89 | ||
|
3a084accc9 | ||
|
31c96bc6a1 | ||
|
f439624404 | ||
|
0b2bb3391c | ||
|
ffee837bcd | ||
|
e4733f9a5c | ||
|
4dcaae5786 | ||
|
8fa6809723 | ||
|
6b3fb46ae5 | ||
|
d05a3519a3 | ||
|
1a75c094b3 | ||
|
def86a44eb | ||
|
3b3f4543b4 | ||
|
d92d3130c8 | ||
|
01cc9f1ae4 | ||
|
9775034d7a | ||
|
824767af66 | ||
|
07818b84fd | ||
|
ae4384453c | ||
|
a80e3df799 | ||
|
52929de50a | ||
|
b81ff3c136 | ||
|
f11d7e8147 | ||
|
8f123e4ab9 | ||
|
25d27e62ce | ||
|
b9230b6eb2 | ||
|
2236865f0f | ||
|
e197b7325a | ||
|
6080cd5bf3 | ||
|
ef35ba759e | ||
|
f246fa2e6c | ||
|
2973b65c69 | ||
|
f6ee1c8f92 | ||
|
3389eaef96 | ||
|
86d050a11f | ||
|
5c0de03a74 | ||
|
afe0087591 | ||
|
616004d7f6 | ||
|
854f62ba54 | ||
|
7374986c05 | ||
|
6a064dc7f7 | ||
|
d9bc989b91 | ||
|
7bd6e09dcf | ||
|
45986f34c1 | ||
|
3386d62951 | ||
|
85330f73a1 | ||
|
318e73a030 | ||
|
62acfd8365 | ||
|
cbc3b6c0b5 | ||
|
3e71f12871 | ||
|
1a28e48434 | ||
|
d57409ca20 | ||
|
195e3082df | ||
|
27adb17a38 | ||
|
85f3f4b047 | ||
|
16f8678d0f | ||
|
64877bc999 | ||
|
6b2572c176 | ||
|
7a747993e7 | ||
|
5d2bd38f72 | ||
|
1e40568ca9 | ||
|
51b82a0a7c | ||
|
6c2ff099da | ||
|
8119dcca8e | ||
|
a289fb7f54 | ||
|
6f9b8f0284 | ||
|
46d10ede43 | ||
|
16884ba28e | ||
|
c636024f75 | ||
|
a4207c1020 | ||
|
a2c2b8d9dd | ||
|
c466e1aa12 | ||
|
3bdd7b20b0 | ||
|
5b4ed6e726 | ||
|
96ce6086e1 | ||
|
0e256021b1 | ||
|
18dcb8f446 | ||
|
ca391c6e32 | ||
|
5ae73361b0 | ||
|
4cee6a9d56 | ||
|
c3ed2019b5 | ||
|
6720f7f9ca | ||
|
6b090542f1 | ||
|
eb3022a0fc | ||
|
ea9938a318 | ||
|
f17b9d234c | ||
|
5b9bd1af00 | ||
|
9d48a04564 | ||
|
f970ea6acf | ||
|
8b1b81a925 | ||
|
2d8a493897 | ||
|
3bcb63cb84 | ||
|
0d35b66903 | ||
|
db84186627 | ||
|
e3e98dc057 | ||
|
5d70cdbeb2 | ||
|
3e75701c1c | ||
|
0fc5b84237 | ||
|
f23a6a8ac8 | ||
|
4b797d3526 | ||
|
82a9dabc7f | ||
|
3f7ae02d54 | ||
|
e9b1800ec9 | ||
|
bee846ec8c | ||
|
e5f3223e76 | ||
|
d80afc35b4 | ||
|
5c9d76791b | ||
|
2a6720da2e | ||
|
068747b01a | ||
|
4962d55b22 | ||
|
66e6ea9d41 | ||
|
d43dfac3e4 | ||
|
9cf134fc2a | ||
|
7b1abe437f | ||
|
193d293dea | ||
|
530ca0d929 | ||
|
c99778266b | ||
|
2680b064fe | ||
|
9dc48978f8 | ||
|
f6a261e5bd | ||
|
a707472270 | ||
|
7babcfaf6b | ||
|
1b2f75395f | ||
|
8704666edb | ||
|
66683c11a0 | ||
|
5d624fe1b9 | ||
|
c4bb161865 | ||
|
b1b30ed09f | ||
|
f5ffdbe82d | ||
|
ad4b1ce51b | ||
|
9657b7eb72 | ||
|
381bc6521c | ||
|
8a60bc26b5 | ||
|
a732d27e93 | ||
|
944141292d |
4
.github/ISSUE_TEMPLATE/bug.yml
vendored
4
.github/ISSUE_TEMPLATE/bug.yml
vendored
|
@ -25,6 +25,8 @@ body:
|
|||
- Itch.io (Downloadable Build) - Windows
|
||||
- Itch.io (Downloadable Build) - MacOS
|
||||
- Itch.io (Downloadable Build) - Linux
|
||||
- Google Playstore - Android
|
||||
- App Store - iOS
|
||||
- Compiled from GitHub Source Code
|
||||
validations:
|
||||
required: true
|
||||
|
@ -45,7 +47,7 @@ body:
|
|||
attributes:
|
||||
label: Version
|
||||
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
|
||||
placeholder: ex. 0.6.3
|
||||
placeholder: ex. 0.6.4
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
|
2
.github/ISSUE_TEMPLATE/charting.yml
vendored
2
.github/ISSUE_TEMPLATE/charting.yml
vendored
|
@ -46,7 +46,7 @@ body:
|
|||
attributes:
|
||||
label: Version
|
||||
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
|
||||
placeholder: ex. 0.6.3
|
||||
placeholder: ex. 0.6.4
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
|
2
.github/ISSUE_TEMPLATE/compiling.yml
vendored
2
.github/ISSUE_TEMPLATE/compiling.yml
vendored
|
@ -34,7 +34,7 @@ body:
|
|||
attributes:
|
||||
label: Version
|
||||
description: Which version are you compiling? The game version is in the bottom left corner of the main menu or in the project.hxp file.
|
||||
placeholder: ex. 0.6.3
|
||||
placeholder: ex. 0.6.4
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
|
2
.github/ISSUE_TEMPLATE/crash.yml
vendored
2
.github/ISSUE_TEMPLATE/crash.yml
vendored
|
@ -46,7 +46,7 @@ body:
|
|||
attributes:
|
||||
label: Version
|
||||
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
|
||||
placeholder: ex. 0.6.3
|
||||
placeholder: ex. 0.6.4
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
|
24
.github/changed-lines-count-labeler.yml
vendored
24
.github/changed-lines-count-labeler.yml
vendored
|
@ -1,12 +1,22 @@
|
|||
# Add 'size: small' to any changes below 10 lines
|
||||
# Add 'size: tiny' to any changes of at most 4 lines
|
||||
'size: tiny':
|
||||
max: 4
|
||||
|
||||
# Add 'size: small' to any changes between 5 and 10 lines
|
||||
'size: small':
|
||||
max: 9
|
||||
min: 5
|
||||
max: 10
|
||||
|
||||
# Add 'size: medium' to any changes between 10 and 100 lines
|
||||
# Add 'size: medium' to any changes between 11 and 100 lines
|
||||
'size: medium':
|
||||
min: 10
|
||||
max: 99
|
||||
min: 11
|
||||
max: 100
|
||||
|
||||
# Add 'size: large' to any changes of at least 100 lines
|
||||
# Add 'size: large' to any changes between 101 and 500 lines
|
||||
'size: large':
|
||||
min: 100
|
||||
min: 101
|
||||
max: 500
|
||||
|
||||
# Add 'size: huge' to any changes of more than 500 lines
|
||||
'size: huge':
|
||||
min: 501
|
||||
|
|
10
.github/pull_request_template.md
vendored
10
.github/pull_request_template.md
vendored
|
@ -1,6 +1,10 @@
|
|||
<!-- Please read the Contributing Guide (https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md) before submitting this PR. -->
|
||||
## Does this PR close any issues? If so, link them below.
|
||||
|
||||
## Briefly describe the issue(s) fixed.
|
||||
<!-- Does this PR close any issues? If so, link them below. -->
|
||||
## Linked Issues
|
||||
|
||||
## Include any relevant screenshots or videos.
|
||||
<!-- Briefly describe the issue(s) fixed. -->
|
||||
## Description
|
||||
|
||||
<!-- Include any relevant screenshots or videos. -->
|
||||
## Screenshots/Videos
|
||||
|
|
21
.github/workflows/build-game.yml
vendored
21
.github/workflows/build-game.yml
vendored
|
@ -1,3 +1,6 @@
|
|||
# Builds the game on all platforms, to ensure it compiles on all target platforms.
|
||||
# This helps to ensure workers focus on the master branch.
|
||||
|
||||
name: Build and Upload nightly game builds
|
||||
|
||||
on:
|
||||
|
@ -21,7 +24,7 @@ jobs:
|
|||
trigger-build: ${{ steps.should-trigger.outputs.result }}
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: funkincrew/ci-checkout@v6
|
||||
uses: funkincrew/ci-checkout@v7.3.3
|
||||
with:
|
||||
submodules: false
|
||||
- uses: dorny/paths-filter@v3
|
||||
|
@ -93,7 +96,7 @@ jobs:
|
|||
packages: write
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: funkincrew/ci-checkout@v6
|
||||
uses: funkincrew/ci-checkout@v7.3.3
|
||||
with:
|
||||
submodules: false
|
||||
- name: Log into GitHub Container Registry
|
||||
|
@ -125,6 +128,9 @@ jobs:
|
|||
runs-on: windows
|
||||
- target: macos
|
||||
runs-on: macos
|
||||
# TODO: Install XCode to build iOS
|
||||
# - target: ios
|
||||
# runs-on: macos
|
||||
runs-on:
|
||||
- ${{ matrix.runs-on }}
|
||||
defaults:
|
||||
|
@ -144,11 +150,14 @@ jobs:
|
|||
private-key: ${{ secrets.APP_PEM }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
- name: Checkout repo
|
||||
uses: funkincrew/ci-checkout@v6
|
||||
uses: funkincrew/ci-checkout@v7.3.3
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
token: ${{ steps.app_token.outputs.token }}
|
||||
persist-credentials: false
|
||||
submodule-aliases: |
|
||||
https://github.com/FunkinCrew/Funkin.assets > https://github.com/FunkinCrew/Funkin-assets-secret
|
||||
https://github.com/FunkinCrew/Funkin.art > https://github.com/FunkinCrew/Funkin-art-secret
|
||||
- name: Setup build environment
|
||||
uses: ./.github/actions/setup-haxe
|
||||
with:
|
||||
|
@ -190,6 +199,7 @@ jobs:
|
|||
include:
|
||||
- target: linux
|
||||
- target: html5
|
||||
# - target: android
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
@ -204,11 +214,14 @@ jobs:
|
|||
private-key: ${{ secrets.APP_PEM }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
- name: Checkout repo
|
||||
uses: funkincrew/ci-checkout@v6
|
||||
uses: funkincrew/ci-checkout@v7.3.3
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
token: ${{ steps.app_token.outputs.token }}
|
||||
persist-credentials: false
|
||||
submodule-aliases: |
|
||||
https://github.com/FunkinCrew/Funkin.assets > https://github.com/FunkinCrew/Funkin-assets-secret
|
||||
https://github.com/FunkinCrew/Funkin.art > https://github.com/FunkinCrew/Funkin-art-secret
|
||||
- name: Config haxelib
|
||||
run: |
|
||||
haxelib --never newrepo
|
||||
|
|
5
.github/workflows/cancel-merged-branches.yml
vendored
5
.github/workflows/cancel-merged-branches.yml
vendored
|
@ -1,3 +1,6 @@
|
|||
# When a pull request is merged, cancel all queued workflows for that branch
|
||||
# This helps to ensure workers focus on the master branch.
|
||||
|
||||
name: Cancel queued workflows on PR merge
|
||||
|
||||
on:
|
||||
|
@ -23,7 +26,7 @@ jobs:
|
|||
let branch_workflows = await github.rest.actions.listWorkflowRuns({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
workflow_id: "build-shit.yml",
|
||||
workflow_id: "build-game.yml",
|
||||
status: "queued",
|
||||
branch: "${{ github.event.pull_request.head.ref }}"
|
||||
});
|
||||
|
|
1
.github/workflows/label-actions.yml
vendored
1
.github/workflows/label-actions.yml
vendored
|
@ -1,5 +1,6 @@
|
|||
# Perform actions when labels are applied to issues, discussions, or pull requests
|
||||
# See .github/label-actions.yml
|
||||
|
||||
name: 'Label Actions'
|
||||
|
||||
on:
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
# Applies the following labels to pull requests when created:
|
||||
# - status: pending triage
|
||||
|
||||
name: "Pull Request Labeler 2 (Runs on PR creation)"
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
|
||||
|
||||
jobs:
|
||||
# Apply `status: pending triage` to newly created pull requests
|
||||
apply-pending-triage:
|
||||
|
|
12
.github/workflows/label-pull-request.yml
vendored
12
.github/workflows/label-pull-request.yml
vendored
|
@ -1,7 +1,19 @@
|
|||
# Applies the following labels to pull requests whenver they are created or modified:
|
||||
# - pr: documentation
|
||||
# - pr: haxe
|
||||
# - pr: github
|
||||
# - size: tiny
|
||||
# - size: small
|
||||
# - size: medium
|
||||
# - size: large
|
||||
# - size: huge
|
||||
# see .github/labeler.yml and .github/changed-lines-count-labeler.yml
|
||||
|
||||
name: "Pull Request Labeler"
|
||||
on:
|
||||
- pull_request_target
|
||||
|
||||
|
||||
jobs:
|
||||
# Apply labels to pull requests based on which files were edited
|
||||
labeler:
|
||||
|
|
9
.gitignore
vendored
9
.gitignore
vendored
|
@ -3,14 +3,21 @@
|
|||
.vs/
|
||||
APIStuff.hx
|
||||
dump/
|
||||
temp/
|
||||
docs/temp/
|
||||
export/
|
||||
project/
|
||||
astc-textures/
|
||||
RECOVER_*.fla
|
||||
shitAudio/
|
||||
.build_time
|
||||
.swp
|
||||
NewgroundsCredentials.hx
|
||||
|
||||
# Mobile signing stuff
|
||||
.apple/
|
||||
.env
|
||||
key.keystore
|
||||
|
||||
# Exclude JS stuff
|
||||
node_modules/
|
||||
package.json
|
||||
|
|
66
.vscode/schema/hmm.json
vendored
Normal file
66
.vscode/schema/hmm.json
vendored
Normal file
|
@ -0,0 +1,66 @@
|
|||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"dependencies": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"title": "Name",
|
||||
"type": "string",
|
||||
"description": "The name of the Haxe library"
|
||||
},
|
||||
"type": {
|
||||
"title": "Type",
|
||||
"type": "string",
|
||||
"description": "one of haxelib, git, hg, or dev",
|
||||
"enum": ["haxelib", "git", "hg", "dev"]
|
||||
},
|
||||
"ref": {
|
||||
"type": "string",
|
||||
"description": "the git/hg ref (branch, commit, tag, etc.)"
|
||||
},
|
||||
"url": {
|
||||
"title": "URL",
|
||||
"type": "string",
|
||||
"description": "the git/hg URL"
|
||||
},
|
||||
"version": {
|
||||
"title": "Version",
|
||||
"type": "string",
|
||||
"description": "the haxelib library version. Must be in SemVer format (MAJOR.MINOR.PATCH)",
|
||||
"pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"
|
||||
}
|
||||
},
|
||||
"required": ["name", "type"],
|
||||
"dependentSchemas": {
|
||||
"type": {
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "git"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"required": ["url"]
|
||||
},
|
||||
"else": {
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "haxelib"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"required": ["version"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
25
.vscode/settings.json
vendored
25
.vscode/settings.json
vendored
|
@ -90,6 +90,8 @@
|
|||
"haxecheckstyle.configurationFile": "checkstyle.json",
|
||||
"haxecheckstyle.codeSimilarityBufferSize": 100,
|
||||
|
||||
"lime.projectFile": "project.hxp",
|
||||
|
||||
"lime.targetConfigurations": [
|
||||
{
|
||||
"label": "Windows / Debug (Discord)",
|
||||
|
@ -150,6 +152,16 @@
|
|||
"target": "windows",
|
||||
"args": ["-debug", "-DRESULTS"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (Straight to Stage Editor)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DSTAGING", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (Straight to Stage Builder)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DSTAGEBUILD", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (Straight to Animation Editor)",
|
||||
"target": "windows",
|
||||
|
@ -158,7 +170,7 @@
|
|||
{
|
||||
"label": "Windows / Debug (Debug hxvlc)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DHXC_LIBVLC_LOGGING", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
"args": ["-debug", "-DHXVLC_LOGGING", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug (Straight to Animation Editor)",
|
||||
|
@ -201,6 +213,10 @@
|
|||
"label": "Debug",
|
||||
"args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "Debug (Unlock Everything)",
|
||||
"args": ["-debug", "-DUNLOCK_EVERYTHING"]
|
||||
},
|
||||
{
|
||||
"label": "Debug (Tracy)",
|
||||
"args": ["-debug", "-DFEATURE_DEBUG_TRACY", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
|
@ -225,5 +241,12 @@
|
|||
"coverage.xml",
|
||||
"jacoco.xml",
|
||||
"coverage.cobertura.xml"
|
||||
],
|
||||
"vscord.app.privacyMode.enable": true,
|
||||
"json.schemas": [
|
||||
{
|
||||
"fileMatch": ["/hmm.json"],
|
||||
"url": "./.vscode/schema/hmm.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
314
CHANGELOG.md
314
CHANGELOG.md
|
@ -4,6 +4,303 @@ All notable changes will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.7.3] - 2025-07-21
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed stuttering throughout the game caused by the Polymod upgrade. (Thanks NotHyper-474!)
|
||||
- [MOBILE] Fixed buttons in the Main Menu not working.
|
||||
- [iOS] The Upgrade button no longer appears if you have already purchased it (actually this time).
|
||||
- Fixed the countdown overlapping itself when restarting the song. (Thanks NotHyper-474!)
|
||||
- Optimized the Week 6 Erect stage.
|
||||
- Fixed an oversight when clearing the cache. (Thanks cherrythecool!)
|
||||
- The Input Offset Test menu text now displays in the correct position.
|
||||
- Fixed script errors appearing in the Week 3 Erect stage.
|
||||
- Fixed adding variations in the Chart Editor erasing difficulties. (Thanks NotHyper-474!)
|
||||
|
||||
## New Contributors for 0.7.3
|
||||
|
||||
* @cherrythecool made their first contribution in [#5458](https://github.com/FunkinCrew/Funkin/pull/5458)
|
||||
|
||||
|
||||
|
||||
## [0.7.2] - 2025-07-18
|
||||
|
||||
### Added
|
||||
|
||||
- [ANDROID] Added a button in the Options menu to access the mods folder.
|
||||
- [MOBILE] Added a preference to adjust the intensity of haptic feedback, ranging from 0.1 to 5.
|
||||
- [MOBILE] Added an easter egg when tapping the player's healthbar icon.
|
||||
|
||||
### Changed
|
||||
|
||||
- Changed default OpenAL configuration settings to improve audio quality. (Thanks Smokey555, cyn0x8, and CCobaltDev!)
|
||||
- The difference may be more or less noticeable on different devices and hardware.
|
||||
- Applies to Desktop and Android, but not iOS yet.
|
||||
- Made several improvements to Polymod and HScript. These changes might break some mods, so please update them accordingly!
|
||||
- Fixed an issue where scripted classes can define two or more fields with the same name.
|
||||
- Fixed an issue causing some syntax errors (such as missing commas) to be ignored by the parser.
|
||||
- Scripted classes can now create static fields and functions.
|
||||
- Scripted classes can now create variables with the `final` keyword.
|
||||
- Scripted classes can now access variables from another scripted class with `class.someVariable`, instead of `class.scriptGet("someVariable")`
|
||||
- This applies to functions too: `class.someFunction()`
|
||||
- Scripted classes that don't extend another class can now be created!
|
||||
- This only works if you access the class in a static context. Creating an instance of said class doesn't work just yet!
|
||||
- Added support for properties (`get_` and `set_` functions) (Thanks KoloInDaCrib!)
|
||||
- Added support for abstracts in a static context. (Thanks lemz1!)
|
||||
- You can now use classes like `FlxColor` properly!
|
||||
- Added support for creating and using enums. (Thanks lemz1!)
|
||||
- You can import them in another script as usual.
|
||||
- Added support for renaming imported classes using the `as` keyword. (Thanks KoloInDaCrib!)
|
||||
- Fixed `try`/`catch` blocks not working properly. (Thanks NotHyper-474!)
|
||||
- Fixed null-safe field access not working properly for functions (ex. `class?.someFunction()). (Thanks KoloInDaCrib!)
|
||||
|
||||
### Fixed
|
||||
|
||||
- [MOBILE] Weekend 1 Story Mode no longer crashes before loading into Blazin'.
|
||||
- [MOBILE] Beating 2hot from Freeplay no longer crashes in the Results screen.
|
||||
- [MOBILE] Retrying and pressing the Back button at the same time no longer crashes the game.
|
||||
- [MOBILE] Pressing the Options and Back buttons at the same time no longer softlocks the game.
|
||||
- [HTML5] Pausing while the train passes by on the Week 3 Erect stage no longer crashes.
|
||||
- [DESKTOP] Getting a Bad/Shit rating on Blazin' no longer breaks animations.
|
||||
- The scroll sound no longer plays once after entering Freeplay.
|
||||
- The Freeplay song preview and album cover now update properly when switching variations.
|
||||
- The Freeplay clear percent counter now consistently displays the correct value on unranked songs.
|
||||
- The Freeplay difficulty star flames no longer become offset from the stars.
|
||||
- The Freeplay difficulty star flames no longer appear during a new rank animation.
|
||||
- The Freeplay menu now correctly assigns the `currentCharacterId`. (Thanks TechnikTil!)
|
||||
- Boyfriend's Perfect (Gold) Results animation now loops properly.
|
||||
- [DESKTOP] The Input Offsets menu no longer activates the debug cursor.
|
||||
- The Input Offsets Test menu no longer generates stacked notes.
|
||||
- The Input Offsets Test menu drums no longer desync from the rest of the track.
|
||||
- The Input Offsets Test menu no longer breaks when a keyboard or controller is connected.
|
||||
- [MOBILE] Sustain trails now display properly with upscroll enabled.
|
||||
- [MOBILE] Added a Back button to the keyboard/gamepad Controls menu.
|
||||
- [iOS] Fixed app name spacing on the Home Screen.
|
||||
- [iOS] Adjusted the preloader to accommodate for different screen sizes.
|
||||
- [iOS] The Upgrade button no longer appears if you have already purchased it.
|
||||
- [ANDROID] Fixed some issues with scrolling.
|
||||
- [ANDROID] Toasts with blank messages no longer appear.
|
||||
- Fixed a critical security vulnerability that could be exploited in mods.
|
||||
- A few more bugfixes and optimizations here and there.
|
||||
|
||||
## New Contributors for 0.7.2
|
||||
|
||||
* @Smokey555 made their first contribution in [#3318](https://github.com/FunkinCrew/Funkin/pull/3318)
|
||||
* @CCobaltDev made their first contribution in [#3318](https://github.com/FunkinCrew/Funkin/pull/3318)
|
||||
|
||||
|
||||
|
||||
## [0.7.1] - 2025-07-15
|
||||
|
||||
### Fixed
|
||||
- Properly implemented ad playback on iOS devices.
|
||||
|
||||
|
||||
|
||||
## [0.7.0] - 2025-07-15
|
||||
|
||||
### Added
|
||||
|
||||
- Friday Night Funkin' now has OFFICIAL mobile versions for Android and iOS, available on the Google Play Store and Apple App Store!
|
||||
- This version contains 100% of the songs from the desktop version of the game.
|
||||
- [MOBILE] New touch input compatibility for all menus.
|
||||
- [MOBILE] Added banner and interstitial advertisements to the game. You can upgrade to the full version through an in-app purchase to permanently disable advertisements.
|
||||
- [MOBILE] Graphics are compressed using the ASTC algorithm, decreasing memory usage in exchange for a slightly larger file size.
|
||||
- [MOBILE] Added haptic feedback to several areas of the game.
|
||||
- Added a visual indicator that shows available difficulties for the currently selected song in Freeplay.
|
||||
- Overhauled the input offsets system, including:
|
||||
- One unified "offset" value.
|
||||
- An "Offset Calibration" screen where the game determines your ideal offset.
|
||||
- A "Test" screen where you can play a short note pattern to try out your offset.
|
||||
- A brand new offset testing theme: Syncobation by Kawai Sprite!
|
||||
- The ability to change your offsets in the Pause Menu, mid-song!
|
||||
- The Input Offsets menu isn't yet available on HTML5, but offsets are still configurable through the Pause Menu.
|
||||
- Added null-safety to a bunch of classes in the source code.
|
||||
- Added the Changelog back to the game files, written by Hundrec and AbnormalPoof!
|
||||
- Added a few sandboxed classes to give mods limited access to the Discord and Newgrounds APIs. ([50d9584](https://github.com/FunkinCrew/Funkin/commit/50d9584a388bd891aa2f8b68a5cde894a6e1ede6)) - by @KoloInDaCrib in [#5040](https://github.com/FunkinCrew/Funkin/pull/5040)
|
||||
- Added script support for Freeplay Backing Cards. ([0001017](https://github.com/FunkinCrew/Funkin/commit/0001017c003be653236c6cc56487c7d0ee33633e)) - by @KoloInDaCrib in [#5233](https://github.com/FunkinCrew/Funkin/pull/5233)
|
||||
- Sparrow results screen animations can now be scriptable. ([7bb2336](https://github.com/FunkinCrew/Funkin/commit/7bb23369727ca4955aa1fbe25e5798809e8169bd)) - by @KoloInDaCrib in [#5168](https://github.com/FunkinCrew/Funkin/pull/5168)
|
||||
- Added a blank `Object` class for scripts to extend, and made `FlxObject` and `FlxBasic` scriptable. ([eb6becc](https://github.com/FunkinCrew/Funkin/commit/eb6becc03fff76117ee3fcbeb32fe254236ca232)) - by @cyn0x8 in [#3119](https://github.com/FunkinCrew/Funkin/pull/3119)
|
||||
- Added default gamepad controls for two recently added Freeplay controls. ([a0d3f8e](https://github.com/FunkinCrew/Funkin/commit/a0d3f8ec553e06b625b463c7989658edbebbbdf5)) - by @MrMadera in [#4559](https://github.com/FunkinCrew/Funkin/pull/4559)
|
||||
- Added the ability to press the Chart Editor keybind in Freeplay with a song capsule selected. ([2221594](https://github.com/FunkinCrew/Funkin/commit/2221594883afa7cd0e518fca7ea975d05626692a)) - by @Lasercar in [#4114](https://github.com/FunkinCrew/Funkin/pull/4114)
|
||||
- The Chart Editor now highlights and deletes stacked notes using a customizable threshold. ([8cae34e](https://github.com/FunkinCrew/Funkin/commit/8cae34eed711bff70e5348ffc6178a0fd69b5846)) - by @NotHyper-474 in [#3574](https://github.com/FunkinCrew/Funkin/pull/3574)
|
||||
- Added a variation indicator next to the Chart Editor playbar difficulty. ([ccd0148](https://github.com/FunkinCrew/Funkin/commit/ccd0148e9b46d512a22b4958d3f289cfc7854965)) - by @KoloInDaCrib in [#5236](https://github.com/FunkinCrew/Funkin/pull/5236)
|
||||
- Added more tween types to certain Chart Editor events. ([5177e12](https://github.com/FunkinCrew/Funkin/commit/5177e1275eb2fb2b016224c139e84debb421b895)) - by @Lasercar in [#4249](https://github.com/FunkinCrew/Funkin/pull/4249)
|
||||
- Pressing Ctrl + N now creates a new stage in the Stage Editor. ([576f8e5](https://github.com/FunkinCrew/Funkin/commit/576f8e54ff8ca8e205241fafa33d0256b62d11d5)) - by @Lasercar in [#5175](https://github.com/FunkinCrew/Funkin/pull/5175)
|
||||
- Added "Flip character horizontally" to the list of shortcuts in the Animation Editor. ([c464cae](https://github.com/FunkinCrew/funkin.assets/commit/c464caec921dcefef7b0b74b2abf95e76ce64491)) - by @AbnormalPoof in [funkin.assets#60](https://github.com/FunkinCrew/funkin.assets/pull/60)
|
||||
- Added Perfect (Gold) to the list of available ranks in Results Debug menu. ([c5308cc](https://github.com/FunkinCrew/Funkin/commit/c5308ccbb9d2b98c62fa4974b8ad7ac1e1ec7d19)) - by @AbnormalPoof in [#4642](https://github.com/FunkinCrew/Funkin/pull/4642)
|
||||
- [MOBILE] Implemented Kevin and Michael.
|
||||
|
||||
### Changed
|
||||
|
||||
- The mod API version now supports v0.7.0, along with v0.6.3. Be sure to check that your mods still work!
|
||||
- Updated the app icon for Desktop platforms.
|
||||
- [MOBILE] Modified several parts of the game to look better on phone screens with wider aspect ratios, up to 20:9.
|
||||
- [DESKTOP] The game now tries to match the window's aspect ratio when changing states, extending as wide as 20:9.
|
||||
- [DESKTOP] Included Mobile stage expansions on Desktop as well. Now you'll have more room for camera events!
|
||||
- Playable Pico and Weekend 1 songs are now always unlocked in Freeplay, even on new saves.
|
||||
- The Freeplay difficulty graphic now scrolls smoothly when changing difficulties.
|
||||
- The "Pause on Unfocus" preference now opens the Pause Menu when unfocusing during a song.
|
||||
- Scripts can now make hold note trails semi-transparent.
|
||||
- Completely reformatted every script file within the game's assets for better readability.
|
||||
- Completely reformatted and optimized every single chart file in the game.
|
||||
- Recharted pico-speaker's chart in Stress
|
||||
- Tweaked charts for the following songs:
|
||||
- Bopeebo [all difficulties] - Removed an extra hey animation event
|
||||
- Bopeebo (Pico Mix) [Hard] - Added a missing note in Section 24
|
||||
- Fresh Erect [Nightmare] - Added a missing grace note for BF in Section 24
|
||||
- South Erect [Nightmare] - Added missing grace notes for BF in Sections 13, 17, and 53
|
||||
- Philly Nice [Hard] - Added missing grace notes for Pico in Sections 30 and 62
|
||||
- Philly Nice [all difficulties] - Added hey animations throughout the song
|
||||
- Philly Nice Erect [Erect] - Added a grace note for BF in Section 33, removed a stacked note for opponent in Section 12
|
||||
- Philly Nice (Pico Mix) [Normal] - Adjusted a left note by 1/96 in Section 60
|
||||
- Blammed (Pico Mix) [Hard] - Added a missing jack in Section 46
|
||||
- Satin Panties [Hard] - Added grace notes in Sections 7-10
|
||||
- Satin Panties [Normal/Hard] - Made Mom sing a sustain rather than two notes in Section 30
|
||||
- High Erect [Erect/Nightmare] - Added a missing note in Section 16
|
||||
- Cocoa [Easy] - Added some notes to reduce sparseness, fixed Mom singing Dad's notes
|
||||
- Cocoa Erect [Erect/Nightmare] - Reimplemented BF's censored notes for Nightmare, adjusted one note by 1/48 in Section 63
|
||||
- Eggnog Erect [Erect/Nightmare] Added two grace notes in Sections 10 and 14 and a missing note for Dad in Section 44
|
||||
- Eggnog (Pico Mix) [Hard] - Added a missing grace note for Pico that was present on Normal in Section 20
|
||||
- Roses [Normal/Hard] - Made Senpai sing a sustain rather than two notes (sneaky)
|
||||
- Roses Erect [Erect/Nightmare] - Mirrored the changes from normal Roses
|
||||
- Guns [all difficulties] - Added a missing note in Sections 28 and 32 and adjusted a hold note's length in Section 73
|
||||
- Stress [Hard] - Split whole notes in halves in Sections 57-60
|
||||
- Darnell [Hard] - Added one missing note for Pico in Section 35
|
||||
- Darnell [all difficulties] - Adjusted camera event timings for consistency
|
||||
- Darnell (BF Mix) [all difficulties] - Removed 3 extra notes and fixed Darnell's pattern being offset
|
||||
- Lit Up [all difficulties] - Added 4 sustains for Darnell throughout the song
|
||||
- Lit Up (BF Mix) [all difficulties] - Added 4 sustains for Darnell throughout the song
|
||||
- 2hot [Easy/Hard] - Fixed remaining offset rhythms (for real this time)
|
||||
- Notes now scroll more smoothly by rendering based on delta timing. ([6ad9ffc](https://github.com/FunkinCrew/Funkin/commit/6ad9ffc7f9d66bbaf6ba343663de4c7268f4be3b)) - by @KutikiPlayz in [#3544](https://github.com/FunkinCrew/Funkin/pull/3544)
|
||||
- The Freeplay character select hint now always displays if you have more than one character unlocked. ([7ccf75c](https://github.com/FunkinCrew/Funkin/commit/7ccf75cd869ba4b6f18a5adc01e65e52ae7bb809)) - by @Hundrec in [#5023](https://github.com/FunkinCrew/Funkin/pull/5023)
|
||||
- Favorite songs in Freeplay are now sorted by Week order instead of alphabetically. ([da0964a](https://github.com/FunkinCrew/Funkin/commit/da0964a7b7bbe4ece1bdbd19233eb6dba0de3ac5)) - by @Hundrec in [#3609](https://github.com/FunkinCrew/Funkin/pull/3609)
|
||||
- Shifted Mommy Mearest's pixel icon to the left in Freeplay. ([d861eba](https://github.com/FunkinCrew/funkin.assets/commit/d861ebac027dd07d0254c79d7c89b59ee04b38f1)) - by @KoloInDaCrib in [funkin.assets#197](https://github.com/FunkinCrew/funkin.assets/pull/197)
|
||||
- The Character Select screen now opens on the currently selected character. ([4819a74](https://github.com/FunkinCrew/Funkin/commit/4819a74c2959cc9b32dfe2cb76c3a4c00e7c7f9a)) - by @Lasercar in [#4072](https://github.com/FunkinCrew/Funkin/pull/4072)
|
||||
- Visualizers now zero out when the game audio is muted. ([6dcec59](https://github.com/FunkinCrew/Funkin/commit/6dcec592f467a0daeb8ff1e0ce122916e36ca869)) - by @Lasercar in [#5266](https://github.com/FunkinCrew/Funkin/pull/5266)
|
||||
- The Options Menu can now scroll to display more menu items. ([70f0a54](https://github.com/FunkinCrew/Funkin/commit/70f0a54191597bd72a6d30d4d12ef5ece6ba078c)) - by @AbnormalPoof in [#4706](https://github.com/FunkinCrew/Funkin/pull/4706)
|
||||
- Raised the FPS cap preference from 300 to 500. ([be73134](https://github.com/FunkinCrew/Funkin/commit/be7313453f70983fff55e69a8b52d741f0cc53b4)) - by @Hundrec in [#5044](https://github.com/FunkinCrew/Funkin/pull/5044)
|
||||
- The Credits menu now uses less memory, especially with many entries. ([1b68c3a](https://github.com/FunkinCrew/Funkin/commit/1b68c3a8d6f66905a9a508a1cb692fe3beb7b4a2)) - by @lemz1 in [#2655](https://github.com/FunkinCrew/Funkin/pull/2655)
|
||||
- Added a timer sequence class to queue up multiple timers in scripts with ease. ([9e182f7](https://github.com/FunkinCrew/Funkin/commit/9e182f70d2bcc92eb68d730d74af143c45f7dcf8)) - by @cyn0x8 in [#2391](https://github.com/FunkinCrew/Funkin/pull/2391)
|
||||
- Replaced smoothLerp and coolLerp with smoothLerpPrecision to fix a few lerp-related bugs. ([94eae11](https://github.com/FunkinCrew/Funkin/commit/94eae116c7a5e6039683d6391208b169378b5ff1)) - by @cyn0x8 in [#3617](https://github.com/FunkinCrew/Funkin/pull/3617)
|
||||
- Fixed empty text strings softlocking the dialogue box. ([88d0e8c](https://github.com/FunkinCrew/Funkin/commit/88d0e8c3b0529654fb7eee8aebb099f7fb346f66)) - by @xenkap in [#4671](https://github.com/FunkinCrew/Funkin/pull/4671)
|
||||
- Adjusted the size of the Beat/Step display in the Chart Editor. ([905181c](https://github.com/FunkinCrew/Funkin/commit/905181c9af29bb11280bda33ef9343069678a762)) - by @NotHyper-474 in [#4994](https://github.com/FunkinCrew/Funkin/pull/4994)
|
||||
- The Chart Editor will now only fall back to the first available difficulty if the selected difficulty cannot be found. ([1c25713](https://github.com/FunkinCrew/Funkin/commit/1c257134648ebd89acf6c9d07f5a0c088fb915c6)) - by @Lasercar in [#4949](https://github.com/FunkinCrew/Funkin/pull/4949)
|
||||
- The undo/redo history is now cleared when loading another song in the Chart Editor. ([426a9c0](https://github.com/FunkinCrew/Funkin/commit/426a9c0c108ac65a042295194679d46444ec1ea5)) - by @Lasercar in [#4308](https://github.com/FunkinCrew/Funkin/pull/4308)
|
||||
- Blacklisted more classes for security reasons. ([cadfa3b](https://github.com/FunkinCrew/Funkin/commit/cadfa3b7ceae2ecabe2d544ddc4c9f453b0dfd56)) - by @NotHyper-474 in [#5185](https://github.com/FunkinCrew/Funkin/pull/5185)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed a ton of performance issues to help the game run better on mobile devices.
|
||||
- Exiting the Freeplay Menu no longer freezes the game for a really long time (thanks NotHyper-474!)
|
||||
- Notestyle graphics are now preloaded before the song starts, fixing the stutter at the beginning of the song.
|
||||
- Hitting many hold note trails in one song no longer leads to a lag spike.
|
||||
- The first lightning strike in Week 2 Erect no longer creates a lag spike.
|
||||
- [DESKTOP] The conductor and music no longer gradually drift out of sync to eventually trigger a resync.
|
||||
- Pixel notestyle strumlines are now properly positioned when Downscroll is enabled.
|
||||
- Added the missing graffiti to the wall in the Weekend 1 Blazin' stage.
|
||||
- Accept keybinds now properly scroll faster through the Credits.
|
||||
- Typing in most text fields in debug editors no longer triggers keyboard shortcuts.
|
||||
- The Chart Editor playback speed feature now works properly.
|
||||
- The Chart Editor metronome and hitsounds now play at exactly the right time.
|
||||
- The Chart Editor notification box no longer covers playbar info.
|
||||
- Selecting a Recent File too quickly in the Stage Editor no longer crashes the game.
|
||||
- Blacklisted a few classes for security.
|
||||
- Opening the logs or backups folder before it's created no longer crashes the game. ([d3490f8](https://github.com/FunkinCrew/Funkin/commit/d3490f8c9929eefb9879ad65ce43038c193642d6)) - by @NotHyper-474 in [#4940](https://github.com/FunkinCrew/Funkin/pull/4940)
|
||||
- Fixed a crash when mashing D or I during startup. ([b52c73f](https://github.com/FunkinCrew/Funkin/commit/b52c73f2b0fa32fbc349804621cf947dff2d364e)) - by @CrusherNotDrip in [#5160](https://github.com/FunkinCrew/Funkin/pull/5160)
|
||||
- Hot-reloading with F5 during gameplay no longer crashes the game. ([d2acb5d](https://github.com/FunkinCrew/Funkin/commit/d2acb5d167afd42299d7200ab8c67972044a09c6)) - by @AbnormalPoof in [#5065](https://github.com/FunkinCrew/Funkin/pull/5065)
|
||||
- Hot-reloading with F5 in the Input Offsets menu no longer crashes the game. ([58257f6](https://github.com/FunkinCrew/Funkin/commit/58257f6ac187925f3b23d3f1eef2c812ae569a6b)) - by @NotHyper-474 in [#5085](https://github.com/FunkinCrew/Funkin/pull/5085)
|
||||
- Songs no longer skip forward at the beginning with high offsets. ([1f75a64](https://github.com/FunkinCrew/Funkin/commit/1f75a641e0c80d15f1af10bae7ee71a6ffecf219)) - by @xenkap in [#3732](https://github.com/FunkinCrew/Funkin/pull/3732)
|
||||
- The song countdown no longer stacks when restarting or continues behind the Pause Menu.
|
||||
([63eca96](https://github.com/FunkinCrew/Funkin/commit/63eca96c98a87e7155df8b2a1735f269ea83e1b5)) - by @KoloInDaCrib and @NotHyper-474 in [#4875](https://github.com/FunkinCrew/Funkin/pull/4875)
|
||||
- Fixed incorrect highlighting and squashed text on Freeplay song capsules. ([0c62428](https://github.com/FunkinCrew/Funkin/commit/0c62428fc883c0fa6d09cd403efb287ff3af8c53)) - by @VioletSnowLeopard in [#5036](https://github.com/FunkinCrew/Funkin/pull/5036)
|
||||
- The Freeplay song preview no longer plays twice after returning from Character
|
||||
Select. ([3d3e2bd](https://github.com/FunkinCrew/Funkin/commit/3d3e2bd3786b858143d214caf55be2ee3e9483fc)) - by @Lasercar in [#5248](https://github.com/FunkinCrew/Funkin/pull/5248)
|
||||
- Freeplay song ranks no longer disappear after changing variations. ([7cc9464](https://github.com/FunkinCrew/Funkin/commit/7cc9464573d07996e4bd0d557f82847809d3a786)) - by @VioletSnowLeopard in [#4583](https://github.com/FunkinCrew/Funkin/pull/4583)
|
||||
- Freeplay song capsules now cycle through long names consistently. ([e193f73](https://github.com/FunkinCrew/Funkin/commit/e193f7392a83a04e4aac85fba4f441a78f7b6668)) - by @VioletSnowLeopard in [#4677](https://github.com/FunkinCrew/Funkin/pull/4677)
|
||||
- Fixed a few visual issues with Freeplay's rank slam animation. ([ab817bb](https://github.com/FunkinCrew/Funkin/commit/ab817bb1eab74ae71c1b1fd74d7512a11e6d4339)) - by @Lasercar in [#4986](https://github.com/FunkinCrew/Funkin/pull/4986)
|
||||
- Fixed visual errors in Freeplay after exiting Character Select. ([56a18e1](https://github.com/FunkinCrew/Funkin/commit/56a18e1cf6a15971feebcc828be8818037948cef)) - by @KoloInDaCrib in [#5245](https://github.com/FunkinCrew/Funkin/pull/5245)
|
||||
- Freeplay styles are now reloaded when hot-reloading with F5. ([f54e140](https://github.com/FunkinCrew/Funkin/commit/f54e140b65e36fcf810c52ce464799dbc0c73c6d)) - by @Keoiki in [#5286](https://github.com/FunkinCrew/Funkin/pull/5286)
|
||||
- Adjusted offsets for Freeplay DJ Pico's fistPump animation. ([382e286](https://github.com/FunkinCrew/funkin.assets/commit/382e286a2478939d3d6aca1c7c90719f33815014)) - by @AbnormalPoof in [funkin.assets#91](https://github.com/FunkinCrew/funkin.assets/pull/91)
|
||||
- The "New Highscore" text no longer appears more than once in the Results screen. ([4e31003](https://github.com/FunkinCrew/Funkin/commit/4e31003a0f60cd394edc2bd4586f9f385b3d07bc)) - by @Lasercar in [#4319](https://github.com/FunkinCrew/Funkin/pull/4319)
|
||||
- The Results Debug menu now shows the correct rank after recent scoring changes. ([11d9998](https://github.com/FunkinCrew/Funkin/commit/11d9998e5c73a3839ea39e06b7f217cbaab69c6d)) - by @NotHyper-474 in [#4905](https://github.com/FunkinCrew/Funkin/pull/4905)
|
||||
- The Input Offsets menu now exits to the Options menu instead of the Main menu. ([5361df2](https://github.com/FunkinCrew/Funkin/commit/5361df254470e68d7571b4534cf456f08d5ffd60)) - by @JackXson-Real in [#5076](https://github.com/FunkinCrew/Funkin/pull/5076)
|
||||
- The Debug Menu can no longer be opened after selecting an item in the Main Menu.
|
||||
([5695bc2](https://github.com/FunkinCrew/Funkin/commit/5695bc20e721f0afd5b97a36167f13e550c12b16)) - by @Lasercar in [#4211](https://github.com/FunkinCrew/Funkin/pull/4211)
|
||||
- The debug cursor is now always hidden when the game starts. ([6222c38](https://github.com/FunkinCrew/Funkin/commit/6222c389e301fa2bb4939697376c4e51e29a9977)) - by @Hundrec in [#4520](https://github.com/FunkinCrew/Funkin/pull/4520)
|
||||
- The Story Mode Weekend 1 level title no longer clips into other level titles below it. ([19d1a8c](https://github.com/FunkinCrew/Funkin/commit/19d1a8c59380009f0d0814c94fd0b4eccb0c80cd)) - by @KoloInDaCrib in [#4348](https://github.com/FunkinCrew/Funkin/pull/4348)
|
||||
- Hold note covers now display properly if a hold note was previously dropped. ([96d1324](https://github.com/FunkinCrew/Funkin/commit/96d1324af140858cb93edd07dc36a969c8ae84c0)) - by @T5mpler in [#5275](https://github.com/FunkinCrew/Funkin/pull/5275)
|
||||
- Girlfriend's and Nene's combo drop animations now play consistently. ([34d5ed1](https://github.com/FunkinCrew/Funkin/commit/34d5ed11695cef7348c13505f13fb1da38b7988c)) - by @VioletSnowLeopard in [#4968](https://github.com/FunkinCrew/Funkin/pull/4968)
|
||||
- Girlfriend (Tankman Stickup) now plays her combo drop animation. ([e329601](https://github.com/FunkinCrew/funkin.assets/commit/e329601834f270910ce80ea5539e4487b9895a8a)) - by @qt2k4 in [funkin.assets#149](https://github.com/FunkinCrew/funkin.assets/pull/149)
|
||||
- Darnell's idle animation now loops consistently. ([df64586](https://github.com/FunkinCrew/funkin.assets/commit/df64586771ea41c544913e049fd0f32bdc655417)) - by @qt2k4 in [funkin.assets#159](https://github.com/FunkinCrew/funkin.assets/pull/159)
|
||||
- Darnell's kneeCan animation now plays properly in 2hot. ([a2e9931](https://github.com/FunkinCrew/funkin.assets/commit/a2e993167aaa2ff6bff8806940f912e444608645)) - by @biomseed in [funkin.assets#78](https://github.com/FunkinCrew/funkin.assets/pull/78)
|
||||
- Otis and Pico (Speaker) no longer spaz out when playtesting Stress. ([3f6d75f](https://github.com/FunkinCrew/funkin.assets/commit/3f6d75f3b6f6c8deb660323e3fb9bf1974c06520)) - by @Lasercar in [funkin.assets#124](https://github.com/FunkinCrew/funkin.assets/pull/124)
|
||||
- The gasp sound now only plays once in the Week 3 Pico Mix doppelganger cutscene. ([ab4598b](https://github.com/FunkinCrew/funkin.assets/commit/ab4598baf3c6790d30cfb727d04c8b57fa18dd0d)) - by @KoloInDaCrib in [funkin.assets#126](https://github.com/FunkinCrew/funkin.assets/pull/126)
|
||||
- Fixed a few issues with the train in Week 3. ([8db2426](https://github.com/FunkinCrew/funkin.assets/commit/8db2426991caebde44d19c81b198e8e2ad86f700)) - by @ShadzXD in [funkin.assets#180](https://github.com/FunkinCrew/funkin.assets/pull/180)
|
||||
- The cars in Week 4 and Weekend 1 no longer get stuck when the song is restarted. ([9c511e3](https://github.com/FunkinCrew/funkin.assets/commit/9c511e371fd08a43ddf834766a6d35667bdef4f7)) - by @MetaBreeze in [funkin.assets#186](https://github.com/FunkinCrew/funkin.assets/pull/186)
|
||||
- A-Bot's visualizer no longer jumps to a random volume when the song ends. ([51cc118](https://github.com/FunkinCrew/funkin.assets/commit/51cc1186bc77a2ee45a47fdbde2d75d9ec69de3a)) - by @VioletSnowLeopard in [funkin.assets#183](https://github.com/FunkinCrew/funkin.assets/pull/183)
|
||||
- Pico's burpShit animation now re-enables volume for player vocals. ([cefda0e](https://github.com/FunkinCrew/funkin.assets/commit/cefda0e52fabbbe04afe60b7aed560267e2cb01e)) - by @Hundrec in [funkin.assets#71](https://github.com/FunkinCrew/funkin.assets/pull/71)
|
||||
- Removed vocals from Monster's instrumental on web builds. ([1c9473f](https://github.com/FunkinCrew/funkin.assets/commit/1c9473f3dfdfb97d97f6c8457001055322abf5ab)) - by @JVNpixels in [funkin.assets#182](https://github.com/FunkinCrew/funkin.assets/pull/182)
|
||||
- Fixed the retry sound not playing after a Tankman death quote finishes. ([e7c4b1b](https://github.com/FunkinCrew/Funkin/commit/e7c4b1ba38ba0739cfe347f6c4763f9811fb95b0)) - by @VioletSnowLeopard in [#4726](https://github.com/FunkinCrew/Funkin/pull/4726)
|
||||
- Darnell (BF Mix)'s alternate instrumental is now properly accessible. ([5abdabf](https://github.com/FunkinCrew/funkin.assets/commit/5abdabf69b39ca4eebd36ae6bbd77fab736d0b86)) - by @Hundrec in [funkin.assets#168](https://github.com/FunkinCrew/funkin.assets/pull/168)
|
||||
- Fixed Newgrounds score submissions for Lit Up and Lit Up (BF Mix). ([183cec6](https://github.com/FunkinCrew/Funkin/commit/183cec62dc1fd3c7f3f634dd3e2400e6ee77b476)) - by @Raltyro in [#4577](https://github.com/FunkinCrew/Funkin/pull/4577)
|
||||
- Inputs are now disabled before Senpai's dialogue appears. ([c43d906](https://github.com/FunkinCrew/funkin.assets/commit/c43d906d19d91d71b9096e65d5e0d3543af8cd31)) - by @anysad in [funkin.assets#165](https://github.com/FunkinCrew/funkin.assets/pull/165)
|
||||
- An easter egg now restarts the song using the correct instrumental. ([e657bc9](https://github.com/FunkinCrew/Funkin/commit/e657bc900bc62cc220276dc171dd47f0a176ac66)) - by @KoloInDaCrib in [#4956](https://github.com/FunkinCrew/Funkin/pull/4956)
|
||||
- Encountering an easter egg during a Chart Editor playtest no longer crashes the game. ([b53b5bd](https://github.com/FunkinCrew/funkin.assets/commit/b53b5bdaecf975555538725a4cdfe71d38565b08)) - by @NotHyper-474 in [funkin.assets#133](https://github.com/FunkinCrew/funkin.assets/pull/133)
|
||||
- Nonexistent characters no longer crash the Chart Editor. ([3bbb4b0](https://github.com/FunkinCrew/Funkin/commit/3bbb4b06c8c1a1885a18d4354fcfa4363a0c6c75)) - by @Lasercar in [#5008](https://github.com/FunkinCrew/Funkin/pull/5008)
|
||||
- Holding Ctrl and clicking on a hold note trail no longer crashes the Chart Editor. ([dc56cca](https://github.com/FunkinCrew/Funkin/commit/dc56ccada50e671996caf0557de1977b7ff8d236)) - by @Lasercar in [#4203](https://github.com/FunkinCrew/Funkin/pull/4203)
|
||||
- Tweens and timers are now canceled when returning to the Chart Editor. ([7e76cf6](https://github.com/FunkinCrew/Funkin/commit/7e76cf66340c00ef6ec84358e6304d62815173b6)) - by @KoloInDaCrib in [#5278](https://github.com/FunkinCrew/Funkin/pull/5278)
|
||||
- Reduced the severity of a memory leak in the Chart Editor. ([cce8c18](https://github.com/FunkinCrew/Funkin/commit/cce8c18822e083910200597f5db4d87b6e3b521f)) - by @NotHyper-474 in [#5247](https://github.com/FunkinCrew/Funkin/pull/5247)
|
||||
- Pressing the Chart Editor keybind during a song now opens to the variation and difficulty you were playing. ([e3fca16](https://github.com/FunkinCrew/Funkin/commit/e3fca167938642bea85398fe57347c10439c1892)) - by @Lasercar in [#4116](https://github.com/FunkinCrew/Funkin/pull/4116)
|
||||
- The Chart Editor now properly saves audio levels when exiting. ([f78ab4d](https://github.com/FunkinCrew/Funkin/commit/f78ab4da1db4f9527a2e1715d5cfb37670e11a74)) - by @Lasercar in [#4149](https://github.com/FunkinCrew/Funkin/pull/4149)
|
||||
- The Chart Editor "Load Metadata File" and "Load Chart File" buttons now function properly. ([9df5395](https://github.com/FunkinCrew/Funkin/commit/9df5395ff888cb6740e21204c8e19116e0472db4)) - by @Lasercar in [#4278](https://github.com/FunkinCrew/Funkin/pull/4278)
|
||||
- FNF Legacy files can now be opened in the Chart Editor on MacOS. ([d98628c](https://github.com/FunkinCrew/Funkin/commit/d98628ca0f9f60357715bd7f95fc686a83201209)) - by @AbnormalPoof in [#4580](https://github.com/FunkinCrew/Funkin/pull/4580)
|
||||
- The Chart Editor now consistently displays the correct waveform for vocal tracks. ([c0e0523](https://github.com/FunkinCrew/Funkin/commit/c0e0523651e8aaaae2a0eed6d5fef6c5ef1b7315)) - by @NotHyper-474 in [#5231](https://github.com/FunkinCrew/Funkin/pull/5231)
|
||||
- Fixed selection boxes duplicating in the Chart Editor. ([65ed583](https://github.com/FunkinCrew/Funkin/commit/65ed58350b798bca0044603510540cfe81b48611)) - by @NotHyper-474 in [#5073](https://github.com/FunkinCrew/Funkin/pull/5073)
|
||||
- Fixed the Chart Editor timer occasionally displaying incorrect millisecond values. ([26dc895](https://github.com/FunkinCrew/Funkin/commit/26dc895a27e0d7e49469251cd68b83be66384e15)) - by @Hundrec in [#4257](https://github.com/FunkinCrew/Funkin/pull/4257)
|
||||
- The Chart Editor playhead can no longer be scrolled to before the beginning of the song. ([7c7dc11](https://github.com/FunkinCrew/Funkin/commit/7c7dc11f18644882444df97ac927e11adaa4ce50)) - by @Hundrec in [#5024](https://github.com/FunkinCrew/Funkin/pull/5024)
|
||||
- The Chart Editor playbar no longer extends past the right of the grid. ([c7abb19](https://github.com/FunkinCrew/Funkin/commit/c7abb196989476cfa4db6354cc6fac5bacb2e56a)) - by @anysad in [#5090](https://github.com/FunkinCrew/Funkin/pull/5090)
|
||||
- Dragging a hold note in the Chart Editor now drags its trail along with its head. ([d3d8aaa](https://github.com/FunkinCrew/Funkin/commit/d3d8aaae7bfbfa8975a9573807b9a3ca68a1ff55)) - by @KoloInDaCrib in [#4127](https://github.com/FunkinCrew/Funkin/pull/4127)
|
||||
- Hold note trails will no longer disappear when dragged too far in the Chart Editor. ([37dc66b](https://github.com/FunkinCrew/Funkin/commit/37dc66bc189bc1941cf60587e1c068770aeec872)) - by @NotHyper-474 in
|
||||
[#5261](https://github.com/FunkinCrew/Funkin/pull/5261)
|
||||
- Undoing and redoing hold note length changes now visually updates the trail in the Chart Editor. ([06a440f](https://github.com/FunkinCrew/Funkin/commit/06a440f21c285666ce2d2bdf17a91ee82ab01061)) - by @NotHyper-474 in [#5265](https://github.com/FunkinCrew/Funkin/pull/5265)
|
||||
- The Chart Editor hold note context menu now displays the correct options. ([4801316](https://github.com/FunkinCrew/Funkin/commit/48013168ef09ddc09549268a2e5309520d3fcc18)) - by @Lasercar in [#4231](https://github.com/FunkinCrew/Funkin/pull/4231)
|
||||
- The buttons in the Chart Editor context menu for selections now do the right thing. ([62d24fc](https://github.com/FunkinCrew/Funkin/commit/62d24fcf4cbe7be328a995bad04f3eeb265262c5)) - by @Lasercar in [#4233](https://github.com/FunkinCrew/Funkin/pull/4233)
|
||||
- The charter field in the song metadata now properly displays the charter in the Chart Editor. ([894d8cb](https://github.com/FunkinCrew/Funkin/commit/894d8cb4637fd0a64359a5edc805a23403f3045c)) - by @Lasercar in [#4879](https://github.com/FunkinCrew/Funkin/pull/4879)
|
||||
- Chart Editor difficulties are now sorted in a consistent order. ([7aa77a1](https://github.com/FunkinCrew/Funkin/commit/7aa77a11cf7508fc05758045a13b2220aca96dd5)) - by @Lasercar in [#4528](https://github.com/FunkinCrew/Funkin/pull/4528)
|
||||
- Chart Editor and Stage Editor windows now consistently show a close button. ([b23b7b8](https://github.com/FunkinCrew/funkin.assets/commit/b23b7b81d843c8fa2334fbdbe1ef979633956bb9)) - by @Lasercar in [funkin.assets#121](https://github.com/FunkinCrew/funkin.assets/pull/121)
|
||||
- The Chart Editor playbar's font size no longer becomes too small. ([f9c1f7a](https://github.com/FunkinCrew/Funkin/commit/f9c1f7a5f7d3906155c2004ad2ec1d02556b9730)) - by @KoloInDaCrib in [#5253](https://github.com/FunkinCrew/Funkin/pull/5253)
|
||||
- The Chart Editor copy notification no longer chases the mouse cursor. ([0ea42e1](https://github.com/FunkinCrew/Funkin/commit/0ea42e18e93b0b57f1ae5499d70c4681f191dacb)) - by @KoloInDaCrib in [#4029](https://github.com/FunkinCrew/Funkin/pull/4029)
|
||||
- Changed "Tankman Battlefield (Erect)" to "Tankman Battlefield [Erect]" in the Chart Editor for consistency. ([52852a0](https://github.com/FunkinCrew/funkin.assets/commit/52852a02f60df81c281bfd0e49fd8fa09e118409)) - by @JVNpixels in [funkin.assets#155](https://github.com/FunkinCrew/funkin.assets/pull/155)
|
||||
- Fixed Pico (Pixel) having the incorrect name in the Chart Editor. ([3ff0e9c](https://github.com/FunkinCrew/funkin.assets/commit/3ff0e9ca4f8cdc5734c335f2d7d72fa310686d46)) - by @ExtraCode75 in [funkin.assets#158](https://github.com/FunkinCrew/funkin.assets/pull/158)
|
||||
- Fixed various issues and added missing functionalities to the Stage Editor. ([a776ce1](https://github.com/FunkinCrew/Funkin/commit/a776ce1a81a539f75f8bd2220a9768c03d347058)) - by @KoloInDaCrib in [#3974](https://github.com/FunkinCrew/Funkin/pull/3974)
|
||||
- Stage Editor windows are now able to be closed. ([65461d8](https://github.com/FunkinCrew/Funkin/commit/65461d839b3764659aab33f1a35edf64ee514952)) - by @Lasercar in [#5238](https://github.com/FunkinCrew/Funkin/pull/5238)
|
||||
- Fixed duplicate exit prompts appearing in the Stage Editor. ([136a5df](https://github.com/FunkinCrew/Funkin/commit/136a5dfad430b461a909be3a7e89f46c8be1d3b3)) - by @Lasercar in [#5239](https://github.com/FunkinCrew/Funkin/pull/5239)
|
||||
- The help guide in the Stage Editor can no longer be opened multiple times. ([564d679](https://github.com/FunkinCrew/Funkin/commit/564d679f969c500834426ce3cf50507091d69697)) - by @Lasercar in [#4128](https://github.com/FunkinCrew/Funkin/pull/4128)
|
||||
- Removed a spammy console trace from Spooky Kids (Dark). ([5935a61](https://github.com/FunkinCrew/funkin.assets/commit/5935a61dd71f51e6264dcd039f1164cd1ff295ef)) - by @NotHyper-474 in [funkin.assets#187](https://github.com/FunkinCrew/funkin.assets/pull/187)
|
||||
- Removed spammy console traces from `DiscordClient`. ([e89f9f5](https://github.com/FunkinCrew/Funkin/commit/e89f9f50dc6085e3550736459ce9e4fd02c1fc5b)) - by @AbnormalPoof in [#4207](https://github.com/FunkinCrew/Funkin/pull/4207)
|
||||
- Removed a spammy console trace from some Chart Editor events. ([b883ad3](https://github.com/FunkinCrew/Funkin/commit/b883ad3d50b990e605d7b79d042292c7371db5f0)) - by @anysad in [#5097](https://github.com/FunkinCrew/Funkin/pull/5097)
|
||||
- ANSI colors now display in the console on more computers. ([3747b94](https://github.com/FunkinCrew/Funkin/commit/3747b942461c3644fe31f25d2ce847fd74d1b1e0)) - by @AbnormalPoof in [#4676](https://github.com/FunkinCrew/Funkin/pull/4676)
|
||||
- Properly blacklist a certain class from scripts. ([3dc7699](https://github.com/FunkinCrew/Funkin/commit/3dc7699aac737998b637ff9a9f16986a434424be)) - by @charlesisfeline in [#4773](https://github.com/FunkinCrew/Funkin/pull/4773)
|
||||
|
||||
### Removed
|
||||
|
||||
- Removed the VSync preference from web builds, where it's non-functional. ([0b7a94b](https://github.com/FunkinCrew/Funkin/commit/0b7a94b1cc5c5d0dbac5a4c3d595349c4e6eb6e4)) - by @NotHyper-474 in [#5062](https://github.com/FunkinCrew/Funkin/pull/5062)
|
||||
- Removed a few non-functional screenshot preferences. ([93e4f79](https://github.com/FunkinCrew/Funkin/commit/93e4f799f4f0ea435b3d77ca8ef2ab6beeb0a955)) - by @Lasercar in [#4895](https://github.com/FunkinCrew/Funkin/pull/4895)
|
||||
|
||||
## New Contributors for 0.7.0
|
||||
|
||||
* @KutikiPlayz made their first contribution in [#3544](https://github.com/FunkinCrew/Funkin/pull/3544)
|
||||
* @xenkap made their first contribution in [#3732](https://github.com/FunkinCrew/Funkin/pull/3732)
|
||||
* @Raltyro made their first contribution in [#4577](https://github.com/FunkinCrew/Funkin/pull/4577)
|
||||
* @charlesisfeline made their first contribution in [#4773](https://github.com/FunkinCrew/Funkin/pull/4773)
|
||||
* @T5mpler made their first contribution in [#5275](https://github.com/FunkinCrew/Funkin/pull/5275)
|
||||
* @biomseed made their first contribution in [funkin.assets#78](https://github.com/FunkinCrew/funkin.assets/pull/78)
|
||||
* @qt2k4 made their first contribution in [funkin.assets#149](https://github.com/FunkinCrew/funkin.assets/pull/149)
|
||||
* @ExtraCode75 made their first contribution in [funkin.assets#158](https://github.com/FunkinCrew/funkin.assets/pull/158)
|
||||
* @MetaBreeze made their first contribution in [funkin.assets#186](https://github.com/FunkinCrew/funkin.assets/pull/186)
|
||||
|
||||
|
||||
|
||||
## [0.6.4] - 2025-05-02
|
||||
|
@ -29,6 +326,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Switching between identical Freeplay filters no longer makes the songlist invisible. ([8b9775d](https://github.com/FunkinCrew/Funkin/commit/8b9775d91f4f2b5896a637a014b4be770319968e)) - by @VioletSnowLeopard in [#4919](https://github.com/FunkinCrew/Funkin/pull/4919)
|
||||
- The millions place digit of the Freeplay score display now updates properly. ([d9fcaf0](https://github.com/FunkinCrew/Funkin/commit/d9fcaf0e6382b121f7773d23f4864364a1e6a577)) - by @Lasercar in [#4065](https://github.com/FunkinCrew/Funkin/pull/4065)
|
||||
- The Freeplay clear percent display is now more consistently aligned. ([329182e](https://github.com/FunkinCrew/funkin.assets/commit/329182ea1b6839187a1d800b2d009a4c1874479d)) - by @Hundrec in [funkin.assets#37](https://github.com/FunkinCrew/funkin.assets/pull/37)
|
||||
- The Freeplay alternate instrumental selector now disables all other inputs. ([664d9e0](https://github.com/FunkinCrew/Funkin/commit/664d9e0fb3f8b93b1f941fb3b6724c37f5b9e5b5)) - @Lasercar in [#4214](https://github.com/FunkinCrew/Funkin/pull/4214)
|
||||
- The Freeplay alternate instrumental selector no longer becomes offset when changing difficulties. ([664d9e0](https://github.com/FunkinCrew/Funkin/commit/664d9e0fb3f8b93b1f941fb3b6724c37f5b9e5b5)) - @Lasercar in [#4214](https://github.com/FunkinCrew/Funkin/pull/4214)
|
||||
- Exiting and then navigating through the Freeplay alternate instrumental selector no longer crashes the game. ([664d9e0](https://github.com/FunkinCrew/Funkin/commit/664d9e0fb3f8b93b1f941fb3b6724c37f5b9e5b5)) - @Lasercar in [#4214](https://github.com/FunkinCrew/Funkin/pull/4214)
|
||||
- Ranks no longer overwrite themselves in the Freeplay new rank animation. ([664d9e0](https://github.com/FunkinCrew/Funkin/commit/664d9e0fb3f8b93b1f941fb3b6724c37f5b9e5b5)) - @Lasercar in [#4214](https://github.com/FunkinCrew/Funkin/pull/4214)
|
||||
- The debris in Pico’s Great Results animation no longer grows in size. ([c108a7f](https://github.com/FunkinCrew/funkin.assets/commit/c108a7ff0d11bf328e7b232160b8f68c71e21bca)) - by @ThatRozebudDude in [funkin.assets#73](https://github.com/FunkinCrew/funkin.assets/pull/73)
|
||||
- Fixed the song not starting if more than 32 sounds are playing at once. ([31d3718](https://github.com/FunkinCrew/Funkin/commit/31d3718e5c33371cacd495219f9cc3908244bf71)) - by @KoloInDaCrib in [#4352](https://github.com/FunkinCrew/Funkin/pull/4352)
|
||||
- Hot reloading (pressing F5) during dialogue no longer crashes the game. ([3e0dbe2](https://github.com/FunkinCrew/Funkin/commit/3e0dbe2758d68ccf4ded1d08bec247ab05d70829)) - by @KoloInDaCrib in [#4769](https://github.com/FunkinCrew/Funkin/pull/4769)
|
||||
|
@ -53,6 +354,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
## New Contributors for 0.6.4
|
||||
|
||||
* @thesuperpig56 made their first contribution in [#4778](https://github.com/FunkinCrew/Funkin/pull/4778)
|
||||
* @CrusherNotDrip made their first contribution in [#4936](https://github.com/FunkinCrew/Funkin/pull/4936)
|
||||
* @ChillyBeanBAM made their first contribution in [funkin.assets#66](https://github.com/FunkinCrew/funkin.assets/pull/66)
|
||||
* @ThatRozebudDude made their first contribution in [funkin.assets#73](https://github.com/FunkinCrew/funkin.assets/pull/73)
|
||||
|
||||
|
||||
|
@ -89,7 +392,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- The nearest song with an Erect variation will be selected when switching.
|
||||
- Switching difficulties now always plays the capsule jump-in animation.
|
||||
- The "Random" capsule now plays animations consistent with other capsules.
|
||||
- The Eggnog Erect cutscene can now be skipped. ([0303a03](https://github.com/FunkinCrew/funkin.assets/commit/1202651db8ea938fe11b6b734fbf7884d101e6ad)) - by @ShadzXD in [#62](https://github.com/FunkinCrew/funkin.assets/pull/62)
|
||||
- The Eggnog Erect cutscene can now be skipped. ([0303a03](https://github.com/FunkinCrew/funkin.assets/commit/1202651db8ea938fe11b6b734fbf7884d101e6ad)) - by @ShadzXD in [funkin.assets#62](https://github.com/FunkinCrew/funkin.assets/pull/62)
|
||||
- Swapped the positions of “skip” and “restart” items in the dialogue pause menu for consistency. ([1f88a3b](https://github.com/FunkinCrew/Funkin/commit/1f88a3b6e13e9283ad680805deab5e27ba141d96)) - by @VioletSnowLeopard in [#4553](https://github.com/FunkinCrew/Funkin/pull/4553)
|
||||
- The Controls menu now prevents unbinding essential UI controls to safeguard against softlocking save files. ([7eebce4](https://github.com/FunkinCrew/Funkin/commit/7eebce432d7627a1cf0bbd43f22a6f9bcb63ff65)) - by @VioletSnowLeopard in [#4382](https://github.com/FunkinCrew/Funkin/pull/4382)
|
||||
- Renamed “Auto Pause” preference to “Pause on Unfocus” for clarity. ([52be941](https://github.com/FunkinCrew/Funkin/commit/52be941b4503da0ac76918e2482ab1804866f2cf)) - by @JackXson-Real in [#4346](https://github.com/FunkinCrew/Funkin/pull/4346)
|
||||
|
@ -112,7 +415,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- A-Bot's visualizer is now blank before the song starts.
|
||||
- Debug editor tooltips no longer display behind windows.
|
||||
- Clearing save data no longer crashes the game.
|
||||
- Adding custom parameters to note kinds no longer crashes the Chart Editor. ([0a7bd31](https://github.com/FunkinCrew/Funkin/commit/0a7bd3111f59efbecfe097f8bbcfdefa5ace299d)) - by @Lasercar in [#136](https://github.com/FunkinCrew/funkin.assets/pull/136)
|
||||
- Adding custom parameters to note kinds no longer crashes the Chart Editor. ([0a7bd31](https://github.com/FunkinCrew/Funkin/commit/0a7bd3111f59efbecfe097f8bbcfdefa5ace299d)) - by @Lasercar in [funkin.assets#136](https://github.com/FunkinCrew/funkin.assets/pull/136)
|
||||
- Improved performance in the Freeplay menu before entering a song. ([2b7254f](https://github.com/FunkinCrew/Funkin/commit/2b7254fbd2356e9da91e216f178a88f17874a6eb)) - by @superpowers04 in [#4729](https://github.com/FunkinCrew/Funkin/pull/4729)
|
||||
- Prevented a crash by capping Freeplay and Results screen score displays at their largest possible values (actual scores are not capped). ([51324e9](https://github.com/FunkinCrew/Funkin/commit/51324e9c283c43ca861d3369ba9b3e1db9c89765)) - by @KoloInDaCrib in [#3634](https://github.com/FunkinCrew/Funkin/pull/3634)
|
||||
- The Animation Editor no longer crashes when opened. ([b40b4b0](https://github.com/FunkinCrew/Funkin/commit/b40b4b03692910afceff361cf6ca3298fd787e3c)) - by @sector-a in [#4582](https://github.com/FunkinCrew/Funkin/pull/4582)
|
||||
|
@ -186,7 +489,7 @@ The Pit Stop 2 update!
|
|||
- Added on-screen descriptions for each item in the Preferences menu. ([a17b0e8](https://github.com/FunkinCrew/Funkin/commit/a17b0e8b3cc1d56fcdc0b51eaca9fd57cdb5bce0)) - by @anysad in [#3872](https://github.com/FunkinCrew/Funkin/pull/3872)
|
||||
- Added precise scrolling in the Chart Editor using Ctrl-Mouse Wheel. ([0d8e4a5](https://github.com/FunkinCrew/Funkin/commit/0d8e4a53305d6d069454812766300122f3581e31)) - by @ninjamuffin99 in [#3806](https://github.com/FunkinCrew/Funkin/pull/3806)
|
||||
- Added a “None” option to the character selector in the Chart Editor. ([9c2ef02](https://github.com/FunkinCrew/Funkin/commit/9c2ef0236818883ad1275571dac49eab70ca0ea0)) - by @Lasercar in [#4279](https://github.com/FunkinCrew/Funkin/pull/4279)
|
||||
- Added the ability to flip the character in the Animation Editor. ([de02137](https://github.com/FunkinCrew/Funkin/commit/de02137d7c7d1779e85aeda34743f506a5b9cc27)) - by @AbnormalPoof in [#3028](https://github.com/FunkinCrew/Funkin/pull/3028)
|
||||
- Added the ability to flip the character horizontally in the Animation Editor by pressing G. ([de02137](https://github.com/FunkinCrew/Funkin/commit/de02137d7c7d1779e85aeda34743f506a5b9cc27)) - by @AbnormalPoof in [#3028](https://github.com/FunkinCrew/Funkin/pull/3028)
|
||||
- Added offsets support for album titles. ([69d8570](https://github.com/FunkinCrew/Funkin/commit/69d8570a9eb06011ed6dd95fcbef83d90f7f8684)) - by @AbnormalPoof in [#3618](https://github.com/FunkinCrew/Funkin/pull/3618)
|
||||
- Added three new properties to stage data: `angle`, `scroll`, and `alpha`. ([ff56b19](https://github.com/FunkinCrew/Funkin/commit/ff56b1948aef42bbb6bb4ede4f9b2012d49ab044)) - by @AbnormalPoof in [#3720](https://github.com/FunkinCrew/Funkin/pull/3720)
|
||||
- Added script events for losing/gaining focus. ([4b127b6](https://github.com/FunkinCrew/Funkin/commit/4b127b64130f6f753d0574ec66a1672322e4bd13)) - by @AbnormalPoof in [#3721](https://github.com/FunkinCrew/Funkin/pull/3721)
|
||||
|
@ -234,6 +537,7 @@ The Pit Stop 2 update!
|
|||
- The Character Select screen no longer plays the unlock animation for some locked characters. ([7058126](https://github.com/FunkinCrew/Funkin/commit/7058126e99adb55e43f5f487b007d3efa9f324d5)) - by @AbnormalPoof in [#3748](https://github.com/FunkinCrew/Funkin/pull/3748)
|
||||
- All time signatures in the Chart Editor now display the correct number of beat/step tick lines. ([e570dfb](https://github.com/FunkinCrew/Funkin/commit/e570dfb8e754f9cb29ac2d8fff6e8513bc68b630)) - by @Keoiki in [#2860](https://github.com/FunkinCrew/Funkin/pull/2860)
|
||||
- The Debug menu now opens with the correct camera position. ([090ddd1](https://github.com/FunkinCrew/Funkin/commit/090ddd1f1c2aa48fdb83127b2235041643c99af5)) - by @ninjamuffin99 in [#3769](https://github.com/FunkinCrew/Funkin/pull/3769)
|
||||
- Finally added an outline to the third GF sticker. [9e62572](https://github.com/FunkinCrew/funkin.assets/commit/9e62572ae27dc676c624a81af5c755490eb2dafe) - @M7theguy in [funkin.assets#33](https://github.com/FunkinCrew/funkin.assets/pull/33)
|
||||
- Removed an unused Freeplay class left over from legacy versions. ([abe4ac8](https://github.com/FunkinCrew/Funkin/commit/abe4ac8485539cbebe527a9a75698950232b68d2)) - by @AbnormalPoof in [#4370](https://github.com/FunkinCrew/Funkin/pull/4370)
|
||||
- Blacklisted an additional class for security. ([3492d41](https://github.com/FunkinCrew/Funkin/commit/3492d412c65c7f3fd61e6fc6c9410d8467122ab0)) - by @AbnormalPoof in [#4074](https://github.com/FunkinCrew/Funkin/pull/4074)
|
||||
- Removed an unused class from Polymod blacklist. ([06c12e3](https://github.com/FunkinCrew/Funkin/commit/06c12e36c6bd6df4e2be32a3bec540172e79e162)) - by @AbnormalPoof in [#3729](https://github.com/FunkinCrew/Funkin/pull/3729)
|
||||
|
@ -247,6 +551,7 @@ The Pit Stop 2 update!
|
|||
* @MidyGamy made their first contribution in [#4068](https://github.com/FunkinCrew/Funkin/pull/4068)
|
||||
* @Lasercar made their first contribution in [#4082](https://github.com/FunkinCrew/Funkin/pull/4082)
|
||||
* @MrScottyPieey made their first contribution in [#4085](https://github.com/FunkinCrew/Funkin/pull/4085)
|
||||
* @M7theguy made their first contribution in [funkin.assets#33](https://github.com/FunkinCrew/funkin.assets/pull/33)
|
||||
|
||||
|
||||
|
||||
|
@ -598,6 +903,7 @@ The Pit Stop 1 update!
|
|||
|
||||
## New Contributors for 0.4.0
|
||||
|
||||
* @doggogit made their first contribution in [#2325](https://github.com/FunkinCrew/Funkin/pull/2325)
|
||||
* @Noobz4Life made their first contribution in [#2472](https://github.com/FunkinCrew/Funkin/pull/2472)
|
||||
* @MaybeMaru made their first contribution in [#2488](https://github.com/FunkinCrew/Funkin/pull/2488)
|
||||
* @NotHyper-474 made their first contribution in [#2490](https://github.com/FunkinCrew/Funkin/pull/2490)
|
||||
|
@ -736,7 +1042,7 @@ The Weekend 1 update!
|
|||
- Improvements to video cutscenes and dialogue, allowing them to be easily skipped or restarted.
|
||||
- Updated Polymod by several major versions, allowing for fully dynamic asset replacement and support for scripted classes.
|
||||
- Completely refactored almost every part of the game's code for performance, stability, and extensibility.
|
||||
- This is not the Ludum Dare game held together with sticks and glue you played three years ago.
|
||||
- This is not the Ludem Dare game held together with sticks and glue you played three years ago.
|
||||
- Characters, stages, songs, story levels, and dialogue are now built from JSON data registries rather than being hardcoded.
|
||||
- All of these also support attaching scripts for custom behavior, more documentation on this soon.
|
||||
- You can forcibly reload the game's JSON data and scripts by pressing F5.
|
||||
|
|
28
LaunchScreen.storyboard
Normal file
28
LaunchScreen.storyboard
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11134" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11106"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<simulatedScreenMetrics key="simulatedMetrics" type="freeform" size="375x667"/>
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="0" green="0" blue="0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
103
README.md
103
README.md
|
@ -1,48 +1,55 @@
|
|||
# Friday Night Funkin'
|
||||
|
||||
Friday Night Funkin' is a rhythm game. Built using HaxeFlixel for Ludum Dare 47.
|
||||
|
||||
This game was made with love to Newgrounds and its community. Extra love to Tom Fulp.
|
||||
|
||||
- [Playable web demo on Newgrounds!](https://www.newgrounds.com/portal/view/770371)
|
||||
- [Demo download builds for Windows, Mac, and Linux from Itch.io!](https://ninja-muffin24.itch.io/funkin)
|
||||
|
||||
# Getting Started
|
||||
|
||||
**PLEASE USE THE LINKS ABOVE IF YOU JUST WANT TO PLAY THE GAME**
|
||||
|
||||
To learn how to install the necessary dependencies and compile the game from source, please follow our [Compiling Guide](/docs/COMPILING.md).
|
||||
|
||||
# Contributing
|
||||
|
||||
Check out our [Contributing Guide](/docs/CONTRIBUTING.md) to learn how you can actively contribute to the development of Friday Night Funkin'!
|
||||
|
||||
# Modding
|
||||
|
||||
Feel free to start learning to mod the game by reading our [documentation](https://funkincrew.github.io/funkin-modding-docs/) and guide to modding.
|
||||
|
||||
# Credits and Special Thanks
|
||||
|
||||
Full credits can be found in-game, or in the `credits.json` file which is located [here](https://github.com/FunkinCrew/funkin.assets/blob/main/exclude/data/credits.json).
|
||||
|
||||
## Programming
|
||||
- [ninjamuffin99](https://twitter.com/ninja_muffin99) - Lead Programmer
|
||||
- [EliteMasterEric](https://twitter.com/EliteMasterEric) - Programmer
|
||||
- [MtH](https://twitter.com/emmnyaa) - Charting and Additional Programming
|
||||
- [GeoKureli](https://twitter.com/Geokureli/) - Additional Programming
|
||||
- Our contributors on GitHub
|
||||
|
||||
## Art / Animation / UI
|
||||
- [PhantomArcade3K](https://twitter.com/phantomarcade3k) - Artist and Animator
|
||||
- [Evilsk8r](https://twitter.com/evilsk8r) - Art
|
||||
- [Moawling](https://twitter.com/moawko) - Week 6 Pixel Art
|
||||
- [IvanAlmighty](https://twitter.com/IvanA1mighty) - Misc UI Design
|
||||
|
||||
## Music
|
||||
- [Kawaisprite](https://twitter.com/kawaisprite) - Musician
|
||||
- [BassetFilms](https://twitter.com/Bassetfilms) - Music for "Monster", Additional Character Design
|
||||
|
||||
## Special Thanks
|
||||
- [Tom Fulp](https://twitter.com/tomfulp) - For being a great guy and for Newgrounds
|
||||
- [JohnnyUtah](https://twitter.com/JohnnyUtahNG/) - Voice of Tankman
|
||||
- [L0Litsmonica](https://twitter.com/L0Litsmonica) - Voice of Mommy Mearest
|
||||
# Friday Night Funkin'
|
||||
|
||||
Friday Night Funkin' is a rhythm game. Built using HaxeFlixel for Ludum Dare 47.
|
||||
|
||||
This game was made with love to Newgrounds and its community. Extra love to Tom Fulp.
|
||||
|
||||
- [Playable web demo on Newgrounds!](https://www.newgrounds.com/portal/view/770371)
|
||||
- [Demo download builds for Windows, Mac, and Linux from Itch.io!](https://ninja-muffin24.itch.io/funkin)
|
||||
- [Download Android builds from Google Play!](https://play.google.com/store/apps/details?id=me.funkin.fnf)
|
||||
- [Download iOS builds from the App Store!](https://apps.apple.com/app/id6740428530)
|
||||
|
||||
# Getting Started
|
||||
|
||||
**PLEASE USE THE LINKS ABOVE IF YOU JUST WANT TO PLAY THE GAME**
|
||||
|
||||
To learn how to install the necessary dependencies and compile the game from source, please follow our [Compiling Guide](/docs/COMPILING.md).
|
||||
|
||||
# Contributing
|
||||
|
||||
Check out our [Contributing Guide](/docs/CONTRIBUTING.md) to learn how you can actively contribute to the development of Friday Night Funkin'!
|
||||
|
||||
# Modding
|
||||
|
||||
Feel free to start learning to mod the game by reading our [documentation](https://funkincrew.github.io/funkin-modding-docs/) and guide to modding.
|
||||
|
||||
# Credits and Special Thanks
|
||||
|
||||
Full credits can be found in-game, or in the `credits.json` file which is located [here](https://github.com/FunkinCrew/funkin.assets/blob/main/exclude/data/credits.json).
|
||||
|
||||
## Programming
|
||||
- [ninjamuffin99](https://twitter.com/ninja_muffin99) - Lead Programmer
|
||||
- [EliteMasterEric](https://twitter.com/EliteMasterEric) - Programmer
|
||||
- [MtH](https://twitter.com/emmnyaa) - Charting and Additional Programming
|
||||
- [GeoKureli](https://twitter.com/Geokureli/) - Additional Programming
|
||||
- [ZackDroid](https://x.com/ZackDroidCoder) - Lead Mobile Programmer
|
||||
- [MAJigsaw77](https://github.com/MAJigsaw77) - Mobile Programmer
|
||||
- [Karim-Akra](https://x.com/KarimAkra_0) - Mobile Programmer
|
||||
- [Sector_5](https://github.com/sector-a) - Mobile Programmer
|
||||
- [Luckydog7](https://github.com/luckydog7) - Mobile Programmer
|
||||
- Our contributors on GitHub
|
||||
|
||||
## Art / Animation / UI
|
||||
- [PhantomArcade3K](https://twitter.com/phantomarcade3k) - Artist and Animator
|
||||
- [Evilsk8r](https://twitter.com/evilsk8r) - Art
|
||||
- [Moawling](https://twitter.com/moawko) - Week 6 Pixel Art
|
||||
- [IvanAlmighty](https://twitter.com/IvanA1mighty) - Misc UI Design
|
||||
|
||||
## Music
|
||||
- [Kawaisprite](https://twitter.com/kawaisprite) - Musician
|
||||
- [BassetFilms](https://twitter.com/Bassetfilms) - Music for "Monster", Additional Character Design
|
||||
|
||||
## Special Thanks
|
||||
- [Tom Fulp](https://twitter.com/tomfulp) - For being a great guy and for Newgrounds
|
||||
- [JohnnyUtah](https://twitter.com/JohnnyUtahNG/) - Voice of Tankman
|
||||
- [L0Litsmonica](https://twitter.com/L0Litsmonica) - Voice of Mommy Mearest
|
||||
|
|
2
art
2
art
|
@ -1 +1 @@
|
|||
Subproject commit 8402339e2e63ae99c441941a46d54bf3f0c0d5fa
|
||||
Subproject commit 490e97f4c6e673a52ee4f9af98325b1aa2d0c3fe
|
2
assets
2
assets
|
@ -1 +1 @@
|
|||
Subproject commit c108a7ff0d11bf328e7b232160b8f68c71e21bca
|
||||
Subproject commit a8d15febf5e37a4fc7ffeb0d3eff9c4d36457a37
|
|
@ -70,9 +70,10 @@
|
|||
},
|
||||
{
|
||||
"props": {
|
||||
"severity": "IGNORE",
|
||||
|
||||
"policy": "aligned",
|
||||
"allowSingleline": true,
|
||||
"severity": "INFO"
|
||||
"allowSingleline": true
|
||||
},
|
||||
"type": "ConditionalCompilation"
|
||||
},
|
||||
|
|
|
@ -87,4 +87,3 @@ filter_commits = false
|
|||
topo_order = false
|
||||
# sort the commits inside sections by oldest/newest order
|
||||
sort_commits = "newest"
|
||||
|
||||
|
|
21
compression-excludes.txt
Normal file
21
compression-excludes.txt
Normal file
|
@ -0,0 +1,21 @@
|
|||
assets/preload/images/cursor/*
|
||||
assets/preload/images/freeplay/*
|
||||
assets/preload/images/icons/*
|
||||
assets/preload/images/titleEnter.png
|
||||
assets/preload/images/titleEnter_mobile.png
|
||||
assets/preload/images/soundtray/*
|
||||
assets/preload/images/stageBuild/*
|
||||
assets/preload/images/ui/popup/pixel/
|
||||
assets/shared/images/characters/abotPixel/
|
||||
assets/shared/images/characters/bfPixel.png
|
||||
assets/shared/images/characters/bfPixelsDEAD.png
|
||||
assets/shared/images/characters/gfPixel.png
|
||||
assets/shared/images/characters/nenePixel/
|
||||
assets/shared/images/characters/picoPixel/
|
||||
assets/shared/images/characters/senpai.png
|
||||
assets/shared/images/characters/spirit.png
|
||||
assets/shared/images/resultScreen/*
|
||||
assets/shared/images/ui/chart-editor/*
|
||||
assets/shared/images/ui/countdown/pixel/
|
||||
assets/week1/
|
||||
assets/week6/*
|
|
@ -23,7 +23,7 @@
|
|||
- Mac: [`lime setup mac` Documentation](https://lime.openfl.org/docs/advanced-setup/macos/)
|
||||
- Linux: [`lime setup linux` Documentation](https://lime.openfl.org/docs/advanced-setup/linux/)
|
||||
- One of Funkin's dependencies uses libVLC, which requires you to install some development packages to be able to compile.
|
||||
Command for Ubuntu/Debian based systems: `sudo apt install libvlc-dev libvlccore-dev libvlccore9`
|
||||
Command for Ubuntu/Debian based systems: `sudo apt install libvlc-dev libvlccore-dev libvlccore9`, and for other distros, please refer to [hxvlc's documentation](https://github.com/MAJigsaw77/hxvlc?tab=readme-ov-file#dependencies)
|
||||
- HTML5: Compiles without any extra setup
|
||||
10. If you are targeting for native, you may need to run `lime rebuild <PLATFORM>` and `lime rebuild <PLATFORM> -debug`
|
||||
11. `lime test <PLATFORM>` to build and launch the game for your platform (for example, `lime test windows`)
|
||||
|
@ -40,8 +40,8 @@ There are several useful build flags you can add to a build to affect how it wor
|
|||
- This feature causes the game to load exported assets from the project's assets folder rather than the exported one. Great for fast iteration, but the game will break if you try to zip it up and send it to someone, so it's disabled for release builds.
|
||||
- `-DFEATURE_DISCORD_RPC` or `-DNO_FEATURE_DISCORD_RPC` to forcibly enable or disable support for Discord Rich Presence.
|
||||
- `-DFEATURE_VIDEO_PLAYBACK` or `-DNO_FEATURE_VIDEO_PLAYBACK` to forcibly enable or disable video cutscene support.
|
||||
- `-DFEATURE_CHART_EDITOR` or `-DNO_FEATURE_CHART_EDITOR` to forcibly enable or disable the chart editor in the Debug menu.
|
||||
- `-DFEATURE_SCREENSHOTS` or `-DNO_FEATURE_SCREENSHOTS` to forcibly enable or disable the screenshots feature.
|
||||
- `-DFEATURE_CHART_EDITOR` or `-DNO_FEATURE_CHART_EDITOR` to forcibly enable or disable the chart editor in the Debug menu.
|
||||
- `-DFEATURE_STAGE_EDITOR` to forcibly enable the experimental stage editor.
|
||||
- `-DFEATURE_GHOST_TAPPING` to forcibly enable an experimental gameplay change to the anti-mash system.
|
||||
|
||||
|
|
98
docs/COMPILING_MOBILE.md
Normal file
98
docs/COMPILING_MOBILE.md
Normal file
|
@ -0,0 +1,98 @@
|
|||
# Compiling Friday Night Funkin' for Mobile Devices
|
||||
|
||||
Before starting, **make sure your game builds on desktop.**
|
||||
Check [COMPILING.md](./COMPILING.md) if you haven’t done that yet.
|
||||
|
||||
## Android
|
||||
|
||||
0. **Create a new folder** – this will store Android tools (remember where you put it!).
|
||||
1. **Open a terminal as Administrator.**
|
||||
2. Run this in the terminal (replace the path with your actual folder):
|
||||
```bash
|
||||
setx ANDROID_HOME "C:\path\to\your\folder" /M
|
||||
```
|
||||
3. Download [Android Studio Command-line Tools](https://developer.android.com/studio#command-line-tools-only).
|
||||
4. Extract the ZIP into your folder from step 1.
|
||||
5. (Optional) Close and reopen the terminal if needed.
|
||||
6. Run:
|
||||
```bash
|
||||
sdkmanager --install "build-tools;35.0.0" "ndk;29.0.13113456" "platforms;android-29" "platforms;android-35"
|
||||
```
|
||||
- The latest NDK is not compatible with Lime you have to use the old one.
|
||||
7. Download and install [JDK 17 (MSI)](https://adoptium.net/temurin/releases/?version=17&os=windows).
|
||||
8. Run:
|
||||
```bash
|
||||
lime setup android
|
||||
```
|
||||
Use these when asked:
|
||||
- **Android SDK:** `C:\path\to\your\folder`
|
||||
- **Android NDK:** `C:\path\to\your\folder\ndk\29.0.13113456`
|
||||
- **JDK:** `C:\Program Files\Java\jdk-17`
|
||||
|
||||
9. Now build your game:
|
||||
```bash
|
||||
lime test android
|
||||
```
|
||||
|
||||
|
||||
### macOS
|
||||
|
||||
0. **Create a new folder** – this will store Android tools (remember where you put it!).
|
||||
1. Open **Terminal** (Command ⌘ + Space → type “terminal” → Enter).
|
||||
2. In Terminal:
|
||||
```bash
|
||||
cd /path/to/your/folder
|
||||
export ANDROID_HOME=/path/to/your/folder
|
||||
export PATH=$PATH:$ANDROID_HOME/cmdline-tools:$ANDROID_HOME/cmdline-tools/bin:$ANDROID_HOME/platform-tools
|
||||
```
|
||||
3. Download [Android Studio Command-line Tools](https://developer.android.com/studio#command-line-tools-only).
|
||||
4. Extract the ZIP into your folder from step 1.
|
||||
5. (Optional) Restart Terminal if needed.
|
||||
6. Run:
|
||||
```bash
|
||||
sdkmanager --install "build-tools;35.0.0" "ndk;29.0.13113456" "platforms;android-29" "platforms;android-35"
|
||||
```
|
||||
7. Download and install [JDK 17 for macOS](https://adoptium.net/temurin/releases/?os=mac&version=17).
|
||||
8. Run:
|
||||
```bash
|
||||
lime setup android
|
||||
```
|
||||
Use these when asked:
|
||||
- **Android SDK:** `/path/to/your/folder`
|
||||
- **Android NDK:** `/path/to/your/folder/ndk/28.0.13004108`
|
||||
- **JDK:** `/Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home`
|
||||
_(If not asked for JDK, don’t worry — just skip it.)_
|
||||
|
||||
9. Build your game:
|
||||
```bash
|
||||
lime test android
|
||||
```
|
||||
|
||||
## iOS
|
||||
Note that you can only build the game for iOS on a computer running MacOS.
|
||||
|
||||
0. Build the game for desktop to make sure everything works. Check [COMPILING.md](./COMPILING.md).
|
||||
1. Get Xcode from the app store on your MacOS Machine.
|
||||
2. Download the iPhone SDK (First thing that pops up in Xcode)
|
||||
3. Open up a terminal tab and run `lime test ios -xcode`
|
||||
4. You will need to sign your own copy in order to run the game with a real iOS device! That requires an Apple Developer account, sorry!
|
||||
- To run with an iOS simulator instead of `-xcode` use `-simulator`
|
||||
|
||||
### iOS Troubleshooting
|
||||
|
||||
- **A required plugin failed to load. Please ensure system content is up-to-date — try running 'xcodebuild -runFirstLaunch'.**
|
||||
Make sure you have the iOS SDK isntalled, see Step 2.
|
||||
|
||||
- **error: No Accounts: Add a new account in Accounts settings. (in target 'Funkin' from project 'Funkin')**
|
||||
|
||||
Open XCode, press CMD+, to open Settings, select Accounts, add an Apple ID.
|
||||
|
||||
- error: No Account for Team "Z7G7AVNGSH". Add a new account in Accounts settings or verify that your accounts have valid credentials.
|
||||
|
||||
Open `project.hxp` and change `IOS_TEAM_ID` to your personal team's ID.
|
||||
|
||||
- error: Failed Registering Bundle Identifier: The app identifier "me.funkin.fnf" cannot be registered to your development team because it is not available.
|
||||
|
||||
The Funkin' Crew are the only ones that can build an iOS app with the identifier `me.funkin.fnf`. Open `project.hxp` and change `PACKAGE_NAME` to a unique value.
|
||||
|
||||
- error: No profiles for 'me.funkin.fnf' were found: Xcode couldn't find any iOS App Development provisioning profiles matching 'me.funkin.fnf'
|
|
@ -60,7 +60,7 @@ This section provides guidelines to follow when [opening an issue](https://githu
|
|||
|
||||
## Requirements
|
||||
Make sure you're playing:
|
||||
- the latest version of the game (currently v0.6.3)
|
||||
- the latest version of the game (currently v0.6.4)
|
||||
- without any mods
|
||||
- on [Newgrounds](https://www.newgrounds.com/portal/view/770371) or downloaded from [itch.io](https://ninja-muffin24.itch.io/funkin)
|
||||
|
||||
|
@ -77,19 +77,16 @@ Here's a list of commonly suggested features and the reasons why they won't be a
|
|||
| Flashy Combo Milestones | https://github.com/FunkinCrew/Funkin/pull/4700#issuecomment-2798916579 |
|
||||
| Losing Icons for DD and Parents | https://github.com/FunkinCrew/Funkin/issues/3048#issuecomment-2243491536 |
|
||||
| Playable GF / Speaker BF / Speaker Pico | https://github.com/FunkinCrew/Funkin/issues/2953#issuecomment-2216985230 |
|
||||
| New Intro Text Lines | https://github.com/FunkinCrew/Funkin/issues/5031#issuecomment-2855593376 |
|
||||
| Fresh (Chill Mix) as Title Screen Music | https://github.com/FunkinCrew/Funkin/pull/4282#issuecomment-2709334718 |
|
||||
| Adjusted Difficulty Ratings | https://github.com/FunkinCrew/Funkin/issues/2781#issuecomment-2172053144 |
|
||||
| Difficulty Ratings above 20 | https://github.com/FunkinCrew/Funkin/issues/3075#issuecomment-2368984497 |
|
||||
| Ability to Reset a Song's Score | https://github.com/FunkinCrew/Funkin/issues/3916#issuecomment-2525408261 |
|
||||
| Quick Restart Keybind (not R) | https://github.com/FunkinCrew/Funkin/issues/3268#issuecomment-2351095232 |
|
||||
| Countdown after Unpausing Song | https://github.com/FunkinCrew/Funkin/issues/2721#issuecomment-2159330106 |
|
||||
| 4:3 Aspect Ratio for Week 6 | https://github.com/FunkinCrew/Funkin/issues/3840#issuecomment-2689158438 |
|
||||
| "Philly Glow" Effect from Psych Engine | https://github.com/FunkinCrew/Funkin/issues/3788#issuecomment-2688966982 |
|
||||
| Importing Charts from Psych Engine (and other mod content) | https://github.com/FunkinCrew/Funkin/issues/2586#issuecomment-2125733327 |
|
||||
| Backwards Compatibility for Modding | https://github.com/FunkinCrew/Funkin/issues/3949#issuecomment-2608391329 |
|
||||
| Lua Support | https://github.com/FunkinCrew/Funkin/issues/2643#issuecomment-2143718093 |
|
||||
|
||||
|
||||
## Issue Types
|
||||
Choose the issue template that best suits your needs!
|
||||
Here's what each template is designed for:
|
||||
|
@ -156,14 +153,14 @@ Choosing the right base branch helps keep your commit history clean and avoid me
|
|||
Once you’re satisfied with the changes you’ve made, open a PR and base it on the same branch you previously chose.
|
||||
|
||||
## Maintaining your pull request
|
||||
Keeping your pull request clean and easy to review increases the chance that it will be accepted!
|
||||
Keeping your pull request clean and easy to review increases the chance that it will be accepted!
|
||||
|
||||
Our maintenance policy is as follows:
|
||||
- If we require changes to your PR, we will label your PR `status: needs revision`.
|
||||
- We may also leave a comment under your PR specifying what changes you should make.
|
||||
- If you receive a comment, you have 90 days to implement the requested changes.
|
||||
- After this period, your PR will be closed due to inactivity and labeled `status: stale`.
|
||||
- Even after your PR is closed, you may request for us to reopen it. Just be sure to address the issues!
|
||||
- Even after your PR is closed, you may request for us to reopen it. Just be sure to address the issues!
|
||||
|
||||
This policy ensures that PRs awaiting review are up to date and ready to merge.
|
||||
|
||||
|
|
32
docs/INSTALLING_MODS.md
Normal file
32
docs/INSTALLING_MODS.md
Normal file
|
@ -0,0 +1,32 @@
|
|||
# HTML5/Web
|
||||
1. Nope
|
||||
|
||||
# Windows
|
||||
1. Start the game at least once. This will create a `mods` folder if it doesn't already exist, alongside the executable.
|
||||
2. Extract the mod you downloaded from its ZIP file, and place the mod folder into the game's `mods` folder.
|
||||
3. Restart the game. The game should detect the mod and start with it.
|
||||
|
||||
# Linux
|
||||
1. Start the game at least once. This will create a `mods` folder if it doesn't already exist, alongside the executable.
|
||||
2. Extract the mod you downloaded from its ZIP file, and place the mod folder into the game's `mods` folder.
|
||||
3. Restart the game. The game should detect the mod and start with it.
|
||||
|
||||
# MacOS
|
||||
1. Start the game at least once. This will create a `mods` folder if it doesn't already exist in the game's system files.
|
||||
2. Right click `Funkin.app` and select `Show Package Contents`.
|
||||
3. Navigate to `Contents/Resources/mods`.
|
||||
4. Extract the mod you downloaded from its ZIP file, and place the mod folder into the game's `mods` folder.
|
||||
5. Restart the game. The game should detect the mod and start with it.
|
||||
|
||||
# Android
|
||||
1. Start the game at least once. This will create a `mods` folder deep in your system files.
|
||||
2. Get an Android file browser that lets you view the app data files, or use Android Studio and open up the Device Explorer.
|
||||
3. Locate the `/sdcard/Android/obb/me.funkin.fnf/mods` folder.
|
||||
4. Extract the mod you downloaded from its ZIP file, and place the mod folder into the game's `mods` folder.
|
||||
5. Restart the game (you may have to [force close](https://support.google.com/android/answer/9079646?hl=en) the app first). The game should detect the mod and start with it.
|
||||
|
||||
# iOS
|
||||
1. Start the game at least once. This will create a `mods` folder in your system files.
|
||||
2. Open the Files app, and navigate to `On My iPhone` -> `Friday Night Funkin` -> `mods`.
|
||||
3. Extract the mod you downloaded from its ZIP file, and place the mod folder into the game's `mods` folder.
|
||||
4. Restart the game (you may have to [force close](https://support.apple.com/en-us/109359) the app first). The game should detect the mod and start with it.
|
12
docs/TRACY.md
Normal file
12
docs/TRACY.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
# Tracy Performance Profiling
|
||||
|
||||
In v0.5.1, Funkin' gained support for a powerful instrumentation-based profiler known as Tracy. This development tool allows you to see exactly what the game is doing at any given moment, how long each function is taking to call, and even how memory is allocated and deallocated. This is the most powerful tool in your toolbox for diagnosing and resolving performance issues.
|
||||
|
||||
## How to Use Tracy
|
||||
|
||||
1. Download [Tracy](https://github.com/wolfpld/tracy)
|
||||
2. Build the game with the `FEATURE_DEBUG_TRACY` flag. For example, `lime build windows -debug -DFEATURE_DEBUG_TRACY`
|
||||
3. Start Tracy, click Connect. Tracy will start waiting for Funkin' to start.
|
||||
4. Start the game with Tracy enabled.
|
||||
|
||||

|
66
docs/setup-android-win64.bat
Normal file
66
docs/setup-android-win64.bat
Normal file
|
@ -0,0 +1,66 @@
|
|||
@echo off
|
||||
|
||||
set ZIP_FILE="./temp/_temp_jdk.zip"
|
||||
set OUTPUT_DIR="./temp/"
|
||||
set SIX_LINK="https://drive.usercontent.google.com/download?id=1GqFpIk_bkxFb0tNN3x9LxnN-Zh_oDUX5&export=download&authuser=0&confirm=t&uuid=43108c0a-bd53-4465-86f3-80aaceaa7a38&at=APZUnTVNS_BV9cNyC_iicDInosmz%3A1718921284514"
|
||||
set EIGHT_LINK="https://drive.usercontent.google.com/download?id=1X8jjtYYos8aDfZKwehGS9B3zFQa-sCb-&export=download&authuser=0&confirm=t&uuid=07b24a6c-5352-4ba5-9fb8-cff151a6d91e&at=APZUnTUfw26NBAl0nCMn6HBKgHwK%3A1718922303598"
|
||||
|
||||
echo MAKING TEMP
|
||||
mkdir %OUTPUT_DIR%
|
||||
echo MADE TEMP
|
||||
|
||||
|
||||
|
||||
echo INSTALLING ANDROID BUILD TOOLS
|
||||
call .\asclt\bin\sdkmanager "build-tools;32.0.0" --sdk_root="%LOCALAPPDATA%/Android/Sdk/"
|
||||
call .\asclt\bin\sdkmanager "build-tools;32.1.0-rc1" --sdk_root="%LOCALAPPDATA%/Android/Sdk/"
|
||||
echo INSTALLED ANDROID BUILD TOOLS
|
||||
|
||||
|
||||
|
||||
echo INSTALLING ANDROID SDK
|
||||
REM First install the sdks
|
||||
call .\asclt\bin\sdkmanager "platforms;android-29" --sdk_root="%LOCALAPPDATA%/Android/Sdk/"
|
||||
echo ANDROID SDK INSTALLED
|
||||
|
||||
|
||||
|
||||
echo INSTALLING ANDROID NDK
|
||||
REM then the ndks
|
||||
call ./asclt/bin/sdkmanager "ndk;21.4.7075529" --sdk_root="%LOCALAPPDATA%/Android/Sdk/"
|
||||
echo ANDROID NDK INSTALLED
|
||||
|
||||
|
||||
|
||||
echo DOWNLOADING JDK
|
||||
call curl -o %ZIP_FILE% %SIX_LINK%
|
||||
echo DOWNLOADED JDK
|
||||
|
||||
|
||||
|
||||
echo UNZIPPING JDK
|
||||
call powershell -Command "Expand-Archive -Path '%ZIP_FILE%' -DestinationPath '%OUTPUT_DIR%' -Force"
|
||||
echo UNZIPPED JDK
|
||||
|
||||
|
||||
|
||||
echo MAKING JDK PATH
|
||||
mkdir "%LOCALAPPDATA%/Android/jdk"
|
||||
echo MADE JDK PATH
|
||||
|
||||
|
||||
|
||||
echo MOVING JDK TO PROPER PATH
|
||||
call move "%OUTPUT_DIR%/jdk-17.0.11+9" "%LOCALAPPDATA%/Android/jdk/"
|
||||
echo MOVED JDK
|
||||
|
||||
|
||||
|
||||
echo LIME SETTING UP
|
||||
haxelib run lime config ANDROID_SDK %LOCALAPPDATA%\Android\Sdk
|
||||
haxelib run lime config ANDROID_NDK_ROOT %LOCALAPPDATA%\Android\Sdk\ndk\21.4.7075529
|
||||
haxelib run lime config JAVA_HOME %LOCALAPPDATA%\Android\Sdk\jdk\jdk-17.0.11+9
|
||||
haxelib run lime config ANDROID_SETUP true
|
||||
echo DONE
|
||||
|
||||
pause
|
66
docs/setup-android-win86.bat
Normal file
66
docs/setup-android-win86.bat
Normal file
|
@ -0,0 +1,66 @@
|
|||
@echo off
|
||||
|
||||
set ZIP_FILE="./temp/_temp_jdk.zip"
|
||||
set OUTPUT_DIR="./temp/"
|
||||
set SIX_LINK="https://drive.usercontent.google.com/download?id=1GqFpIk_bkxFb0tNN3x9LxnN-Zh_oDUX5&export=download&authuser=0&confirm=t&uuid=43108c0a-bd53-4465-86f3-80aaceaa7a38&at=APZUnTVNS_BV9cNyC_iicDInosmz%3A1718921284514"
|
||||
set EIGHT_LINK="https://drive.usercontent.google.com/download?id=1X8jjtYYos8aDfZKwehGS9B3zFQa-sCb-&export=download&authuser=0&confirm=t&uuid=07b24a6c-5352-4ba5-9fb8-cff151a6d91e&at=APZUnTUfw26NBAl0nCMn6HBKgHwK%3A1718922303598"
|
||||
|
||||
echo MAKING TEMP
|
||||
mkdir %OUTPUT_DIR%
|
||||
echo MADE TEMP
|
||||
|
||||
|
||||
|
||||
echo INSTALLING ANDROID BUILD TOOLS
|
||||
call .\asclt\bin\sdkmanager "build-tools;32.0.0" --sdk_root="%LOCALAPPDATA%/Android/Sdk/"
|
||||
call .\asclt\bin\sdkmanager "build-tools;32.1.0-rc1" --sdk_root="%LOCALAPPDATA%/Android/Sdk/"
|
||||
echo INSTALLED ANDROID BUILD TOOLS
|
||||
|
||||
|
||||
|
||||
echo INSTALLING ANDROID SDK
|
||||
REM First install the sdks
|
||||
call .\asclt\bin\sdkmanager "platforms;android-29" --sdk_root="%LOCALAPPDATA%/Android/Sdk/"
|
||||
echo ANDROID SDK INSTALLED
|
||||
|
||||
|
||||
|
||||
echo INSTALLING ANDROID NDK
|
||||
REM then the ndks
|
||||
call ./asclt/bin/sdkmanager "ndk;21.4.7075529" --sdk_root="%LOCALAPPDATA%/Android/Sdk/"
|
||||
echo ANDROID NDK INSTALLED
|
||||
|
||||
|
||||
|
||||
echo DOWNLOADING JDK
|
||||
call curl -o %ZIP_FILE% %EIGHT_LINK%
|
||||
echo DOWNLOADED JDK
|
||||
|
||||
|
||||
|
||||
echo UNZIPPING JDK
|
||||
call powershell -Command "Expand-Archive -Path '%ZIP_FILE%' -DestinationPath '%OUTPUT_DIR%' -Force"
|
||||
echo UNZIPPED JDK
|
||||
|
||||
|
||||
|
||||
echo MAKING JDK PATH
|
||||
mkdir "%LOCALAPPDATA%/Android/jdk"
|
||||
echo MADE JDK PATH
|
||||
|
||||
|
||||
|
||||
echo MOVING JDK TO PROPER PATH
|
||||
call move "%OUTPUT_DIR%/jdk-17.0.11+9" "%LOCALAPPDATA%/Android/jdk/"
|
||||
echo MOVED JDK
|
||||
|
||||
|
||||
|
||||
echo LIME SETTING UP
|
||||
haxelib run lime config ANDROID_SDK %LOCALAPPDATA%\Android\Sdk
|
||||
haxelib run lime config ANDROID_NDK_ROOT %LOCALAPPDATA%\Android\Sdk\ndk\21.4.7075529
|
||||
haxelib run lime config JAVA_HOME %LOCALAPPDATA%\Android\Sdk\jdk\jdk-17.0.11+9
|
||||
haxelib run lime config ANDROID_SETUP true
|
||||
echo DONE
|
||||
|
||||
pause
|
71
hmm.json
71
hmm.json
|
@ -4,14 +4,48 @@
|
|||
"name": "FlxPartialSound",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "41f35ddb1eb9d10bc742e6f8b5bcc62f9ef8ad84",
|
||||
"url": "https://github.com/FunkinCrew/FlxPartialSound.git"
|
||||
"ref": "3c9f63e3501c20c0b60442089dc05306f5a87968",
|
||||
"url": "https://github.com/FunkinDroidTeam/FlxPartialSound.git"
|
||||
},
|
||||
{
|
||||
"name": "astc-compressor",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "8c9f56927c523df7b849352c6951f04112fe15cc",
|
||||
"url": "https://github.com/KarimAkra/astc-compressor"
|
||||
},
|
||||
{
|
||||
"name": "extension-admob",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "02334589ff9603a5f483077a44395009644f6274",
|
||||
"url": "https://github.com/FunkinCrew/extension-admob"
|
||||
},
|
||||
{
|
||||
"name": "extension-androidtools",
|
||||
"type": "haxelib",
|
||||
"version": "2.2.2"
|
||||
},
|
||||
{
|
||||
"name": "extension-haptics",
|
||||
"type": "haxelib",
|
||||
"version": "1.0.4"
|
||||
},
|
||||
{
|
||||
"name": "extension-iapcore",
|
||||
"type": "haxelib",
|
||||
"version": "1.0.4"
|
||||
},
|
||||
{
|
||||
"name": "extension-iarcore",
|
||||
"type": "haxelib",
|
||||
"version": "1.0.3"
|
||||
},
|
||||
{
|
||||
"name": "flixel",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "fffb1a74cf08f63dacc2ab09976340563f5b6e6d",
|
||||
"ref": "08fc955ca87f192a971719a675f1d3b21709725d",
|
||||
"url": "https://github.com/FunkinCrew/flixel"
|
||||
},
|
||||
{
|
||||
|
@ -21,18 +55,11 @@
|
|||
"ref": "b9118f47f43a66bc0e5fbfcfd9903f0425e918ee",
|
||||
"url": "https://github.com/FunkinCrew/flixel-addons"
|
||||
},
|
||||
{
|
||||
"name": "flixel-text-input",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "951a0103a17bfa55eed86703ce50b4fb0d7590bc",
|
||||
"url": "https://github.com/FunkinCrew/flixel-text-input"
|
||||
},
|
||||
{
|
||||
"name": "flxanimate",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "f8842cea9883d6112a2c854cf93fa72856171428",
|
||||
"ref": "b1faf19885dad06c899cb71ffe07b4e40b8c6d0c",
|
||||
"url": "https://github.com/FunkinCrew/flxanimate"
|
||||
},
|
||||
{
|
||||
|
@ -51,8 +78,8 @@
|
|||
"name": "grig.audio",
|
||||
"type": "git",
|
||||
"dir": "src",
|
||||
"ref": "57f5d47f2533fd0c3dcd025a86cb86c0dfa0b6d2",
|
||||
"url": "https://gitlab.com/haxe-grig/grig.audio.git"
|
||||
"ref": "8567c4dad34cfeaf2ff23fe12c3796f5db80685e",
|
||||
"url": "https://github.com/FunkinCrew/grig.audio"
|
||||
},
|
||||
{
|
||||
"name": "hamcrest",
|
||||
|
@ -63,29 +90,29 @@
|
|||
"name": "haxeui-core",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "07fc7aa8098deaea633b0726d01f83eb4ef8a832",
|
||||
"ref": "47ee9f5fa02d422e186e844f87e68220cabfcc5b",
|
||||
"url": "https://github.com/haxeui/haxeui-core"
|
||||
},
|
||||
{
|
||||
"name": "haxeui-flixel",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "b899a4c7d7318c5ff2b1bb645fbc73728fad1ac9",
|
||||
"ref": "100f2c96beab619cfe72c567a058c41c71e3e998",
|
||||
"url": "https://github.com/haxeui/haxeui-flixel"
|
||||
},
|
||||
{
|
||||
"name": "hscript",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "27c86f9a761c1d16d4433c4cf252eccb7b2e18de",
|
||||
"ref": "d60bb2947fa609fdc875ccfae89666a6984eeaf2",
|
||||
"url": "https://github.com/FunkinCrew/hscript"
|
||||
},
|
||||
{
|
||||
"name": "hxcpp",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "v4.3.75",
|
||||
"url": "https://github.com/HaxeFoundation/hxcpp"
|
||||
"ref": "5a0dc3f644dc676a4a092b7e6c8edc8be941f024",
|
||||
"url": "https://github.com/FunkinCrew/hxcpp"
|
||||
},
|
||||
{
|
||||
"name": "hxcpp-debug-server",
|
||||
|
@ -116,7 +143,7 @@
|
|||
{
|
||||
"name": "hxvlc",
|
||||
"type": "haxelib",
|
||||
"version": "2.1.2"
|
||||
"version": "2.2.2"
|
||||
},
|
||||
{
|
||||
"name": "json2object",
|
||||
|
@ -143,7 +170,7 @@
|
|||
"name": "lime",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "be81ad7e4e1a92c3482bcc009648a4ac892cfa35",
|
||||
"ref": "e5f8c27124598505917a001588b560244731adfb",
|
||||
"url": "https://github.com/FunkinCrew/lime"
|
||||
},
|
||||
{
|
||||
|
@ -185,14 +212,14 @@
|
|||
"name": "openfl",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "d061c936b462f040304ec2bd42d9f59d2e59e285",
|
||||
"ref": "a0df7c3afe360c9af59a76e45007dbf4e53b5131",
|
||||
"url": "https://github.com/FunkinCrew/openfl"
|
||||
},
|
||||
{
|
||||
"name": "polymod",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "0fbdf27fe124549730accd540cec8a183f8652c0",
|
||||
"ref": "d4142dd15a3b57ed4eb149f9f6a2c3ad9935bf7b",
|
||||
"url": "https://github.com/larsiusprime/polymod"
|
||||
},
|
||||
{
|
||||
|
|
2537
project.hxp
2537
project.hxp
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,12 +1,17 @@
|
|||
package;
|
||||
|
||||
import flixel.FlxG;
|
||||
import flixel.FlxGame;
|
||||
import flixel.FlxState;
|
||||
import funkin.ui.FullScreenScaleMode;
|
||||
import funkin.Preferences;
|
||||
import funkin.util.logging.CrashHandler;
|
||||
import funkin.ui.debug.MemoryCounter;
|
||||
import funkin.save.Save;
|
||||
import haxe.ui.Toolkit;
|
||||
#if hxvlc
|
||||
import hxvlc.util.Handle;
|
||||
#end
|
||||
import openfl.display.FPS;
|
||||
import openfl.display.Sprite;
|
||||
import openfl.events.Event;
|
||||
|
@ -31,6 +36,15 @@ class Main extends Sprite
|
|||
|
||||
public static function main():Void
|
||||
{
|
||||
// Set the current working directory for Android and iOS devices
|
||||
#if android
|
||||
// On Android use External Files Dir.
|
||||
Sys.setCwd(haxe.io.Path.addTrailingSlash(extension.androidtools.content.Context.getExternalFilesDir()));
|
||||
#elseif ios
|
||||
// On iOS use Documents Dir.
|
||||
Sys.setCwd(haxe.io.Path.addTrailingSlash(lime.system.System.documentsDirectory));
|
||||
#end
|
||||
|
||||
// We need to make the crash handler LITERALLY FIRST so nothing EVER gets past it.
|
||||
CrashHandler.initialize();
|
||||
CrashHandler.queryStatus();
|
||||
|
@ -97,9 +111,19 @@ class Main extends Sprite
|
|||
memoryCounter = new MemoryCounter(10, 13, 0xFFFFFF);
|
||||
#end
|
||||
|
||||
#if mobile
|
||||
// Add this signal so we can reposition and resize the memory and fps counter.
|
||||
FlxG.signals.preUpdate.add(repositionCounters.bind(true));
|
||||
#end
|
||||
|
||||
// George recommends binding the save before FlxGame is created.
|
||||
Save.load();
|
||||
|
||||
|
||||
#if hxvlc
|
||||
// Initialize hxvlc's Handle here so the videos are loading faster.
|
||||
Handle.init();
|
||||
#end
|
||||
|
||||
// Don't call anything from the preferences until the save is loaded!
|
||||
#if web
|
||||
// set this variable (which is a function) from the lime version at lime/_internal/backend/html5/HTML5Application.hx
|
||||
|
@ -109,7 +133,6 @@ class Main extends Sprite
|
|||
|
||||
WindowUtil.setVSyncMode(funkin.Preferences.vsyncMode);
|
||||
|
||||
|
||||
var game:FlxGame = new FlxGame(gameWidth, gameHeight, initialState, Preferences.framerate, Preferences.framerate, skipSplash, startFullscreen);
|
||||
|
||||
// FlxG.game._customSoundTray wants just the class, it calls new from
|
||||
|
@ -124,6 +147,15 @@ class Main extends Sprite
|
|||
game.debugger.interaction.addTool(new funkin.util.TrackerToolButtonUtil());
|
||||
#end
|
||||
|
||||
#if !html5
|
||||
FlxG.scaleMode = new FullScreenScaleMode();
|
||||
#end
|
||||
|
||||
#if mobile
|
||||
// Reposition and resize the memory and fps counter without lerping.
|
||||
repositionCounters(false);
|
||||
#end
|
||||
|
||||
#if hxcpp_debug_server
|
||||
trace('hxcpp_debug_server is enabled! You can now connect to the game with a debugger.');
|
||||
#else
|
||||
|
@ -145,4 +177,56 @@ class Main extends Sprite
|
|||
funkin.input.Cursor.registerHaxeUICursors();
|
||||
haxe.ui.tooltips.ToolTipManager.defaultDelay = 200;
|
||||
}
|
||||
|
||||
#if mobile
|
||||
function repositionCounters(lerp:Bool):Void
|
||||
{
|
||||
// Calling this so it gets scaled based on the resolution of the game and device's resolution.
|
||||
var scale:Float = Math.min(FlxG.stage.stageWidth / FlxG.width, FlxG.stage.stageHeight / FlxG.height);
|
||||
|
||||
#if android
|
||||
scale = Math.max(scale, 1);
|
||||
#else
|
||||
scale = Math.min(scale, 1);
|
||||
#end
|
||||
final thypos:Float = Math.max(FullScreenScaleMode.notchSize.x, 10);
|
||||
if (fpsCounter != null)
|
||||
{
|
||||
fpsCounter.scaleX = fpsCounter.scaleY = scale;
|
||||
|
||||
if (FlxG.game != null)
|
||||
{
|
||||
if (lerp)
|
||||
{
|
||||
fpsCounter.x = flixel.math.FlxMath.lerp(fpsCounter.x, FlxG.game.x + thypos, FlxG.elapsed * 3);
|
||||
}
|
||||
else
|
||||
{
|
||||
fpsCounter.x = FlxG.game.x + FullScreenScaleMode.notchSize.x + 10;
|
||||
}
|
||||
|
||||
fpsCounter.y = FlxG.game.y + (3 * scale);
|
||||
}
|
||||
}
|
||||
|
||||
if (memoryCounter != null)
|
||||
{
|
||||
memoryCounter.scaleX = memoryCounter.scaleY = scale;
|
||||
|
||||
if (FlxG.game != null)
|
||||
{
|
||||
if (lerp)
|
||||
{
|
||||
memoryCounter.x = flixel.math.FlxMath.lerp(memoryCounter.x, FlxG.game.x + thypos, FlxG.elapsed * 3);
|
||||
}
|
||||
else
|
||||
{
|
||||
memoryCounter.x = FlxG.game.x + FullScreenScaleMode.notchSize.x + 10;
|
||||
}
|
||||
|
||||
memoryCounter.y = FlxG.game.y + (13 * scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ class Postbuild
|
|||
|
||||
var buildTime:Float = roundToTwoDecimals(end - start);
|
||||
|
||||
trace('Build took: ${buildTime} seconds');
|
||||
Sys.println('[INFO] Build took: ${buildTime} seconds');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,48 +9,24 @@ class Prebuild
|
|||
{
|
||||
static inline final BUILD_TIME_FILE:String = '.build_time';
|
||||
|
||||
static final NG_CREDS_PATH:String = './source/funkin/api/newgrounds/NewgroundsCredentials.hx';
|
||||
|
||||
static final NG_CREDS_TEMPLATE:String = "package funkin.api.newgrounds;
|
||||
|
||||
class NewgroundsCredentials
|
||||
{
|
||||
public static final APP_ID:String = #if API_NG_APP_ID haxe.macro.Compiler.getDefine(\"API_NG_APP_ID\") #else 'INSERT APP ID HERE' #end;
|
||||
public static final ENCRYPTION_KEY:String = #if API_NG_ENC_KEY haxe.macro.Compiler.getDefine(\"API_NG_ENC_KEY\") #else 'INSERT ENCRYPTION KEY HERE' #end;
|
||||
}";
|
||||
|
||||
static function main():Void
|
||||
{
|
||||
trace('Building...');
|
||||
var start:Float = Sys.time();
|
||||
Sys.println('[INFO] Performing pre-build tasks...');
|
||||
|
||||
saveBuildTime();
|
||||
|
||||
buildCredsFile();
|
||||
var end:Float = Sys.time();
|
||||
var duration:Float = end - start;
|
||||
Sys.println('[INFO] Finished pre-build tasks in $duration seconds.');
|
||||
}
|
||||
|
||||
static function saveBuildTime():Void
|
||||
{
|
||||
// PostBuild.hx reads this file and computes the total build duration.
|
||||
var fo:sys.io.FileOutput = File.write(BUILD_TIME_FILE);
|
||||
var now:Float = Sys.time();
|
||||
fo.writeDouble(now);
|
||||
fo.close();
|
||||
}
|
||||
|
||||
static function buildCredsFile():Void
|
||||
{
|
||||
#if sys
|
||||
if (sys.FileSystem.exists(NG_CREDS_PATH))
|
||||
{
|
||||
trace('NewgroundsCredentials.hx already exists, skipping.');
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Creating NewgroundsCredentials.hx...');
|
||||
|
||||
var fileContents:String = NG_CREDS_TEMPLATE;
|
||||
|
||||
sys.io.File.saveContent(NG_CREDS_PATH, fileContents);
|
||||
}
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,27 @@ extern class Native_TracyProfiler
|
|||
@:native('::__hxcpp_tracy_framemark')
|
||||
public static function frameMark():Void;
|
||||
|
||||
/**
|
||||
Mark a named frame. Allows creating multiple frame sets for different timing categories.
|
||||
Each unique name creates a separate frame set in the Tracy timeline.
|
||||
**/
|
||||
@:native('::__hxcpp_tracy_framemark_named')
|
||||
public static function frameMarkNamed(_name:String):Void;
|
||||
|
||||
/**
|
||||
Mark the start of a discontinuous frame. Use for periodic work with gaps.
|
||||
Must be paired with frameMarkEnd() using the same name.
|
||||
**/
|
||||
@:native('::__hxcpp_tracy_framemark_start')
|
||||
public static function frameMarkStart(_name:String):Void;
|
||||
|
||||
/**
|
||||
Mark the end of a discontinuous frame. Use for periodic work with gaps.
|
||||
Must be paired with frameMarkStart() using the same name.
|
||||
**/
|
||||
@:native('::__hxcpp_tracy_framemark_end')
|
||||
public static function frameMarkEnd(_name:String):Void;
|
||||
|
||||
/**
|
||||
Print a message into Tracy's log.
|
||||
**/
|
||||
|
@ -71,6 +92,18 @@ class Cppia_TracyProfiler
|
|||
public static function frameMark()
|
||||
Native_TracyProfiler.frameMark();
|
||||
|
||||
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.frameMarkNamed)
|
||||
public static function frameMarkNamed(_name:String)
|
||||
Native_TracyProfiler.frameMarkNamed(_name);
|
||||
|
||||
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.frameMarkStart)
|
||||
public static function frameMarkStart(_name:String)
|
||||
Native_TracyProfiler.frameMarkStart(_name);
|
||||
|
||||
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.frameMarkEnd)
|
||||
public static function frameMarkEnd(_name:String)
|
||||
Native_TracyProfiler.frameMarkEnd(_name);
|
||||
|
||||
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.message)
|
||||
public static function message(_msg:String, ?_color:Int = 0x000000)
|
||||
Native_TracyProfiler.message(_msg, _color);
|
||||
|
|
|
@ -9,6 +9,11 @@ import openfl.utils.Future;
|
|||
@:nullSafety
|
||||
class Assets
|
||||
{
|
||||
/**
|
||||
* The assets cache.
|
||||
*/
|
||||
public static var cache:openfl.utils.IAssetCache = openfl.utils.Assets.cache;
|
||||
|
||||
/**
|
||||
* Get the file system path for an asset
|
||||
* @param path The asset path to load from, relative to the assets folder
|
||||
|
|
|
@ -6,6 +6,7 @@ import flixel.math.FlxMath;
|
|||
import funkin.data.song.SongData.SongTimeChange;
|
||||
import funkin.data.song.SongDataUtils;
|
||||
import funkin.save.Save;
|
||||
import funkin.util.TimerUtil.SongSequence;
|
||||
import haxe.Timer;
|
||||
import flixel.sound.FlxSound;
|
||||
|
||||
|
@ -92,6 +93,12 @@ class Conductor
|
|||
*/
|
||||
public var songPosition(default, null):Float = 0;
|
||||
|
||||
/**
|
||||
* The offset between frame time and music time.
|
||||
* Used in `getTimeWithDelta()` to get a more accurate music time when on higher framerates.
|
||||
*/
|
||||
var songPositionDelta(default, null):Float = 0;
|
||||
|
||||
var prevTimestamp:Float = 0;
|
||||
var prevTime:Float = 0;
|
||||
|
||||
|
@ -242,25 +249,18 @@ class Conductor
|
|||
* No matter if you're using a local conductor or not, this always loads
|
||||
* to/from the save file
|
||||
*/
|
||||
public var inputOffset(get, set):Int;
|
||||
public var globalOffset(get, never):Int;
|
||||
|
||||
/**
|
||||
* An offset set by the user to compensate for audio/visual lag
|
||||
* No matter if you're using a local conductor or not, this always loads
|
||||
* to/from the save file
|
||||
*/
|
||||
public var audioVisualOffset(get, set):Int;
|
||||
public var audioVisualOffset(get, never):Int;
|
||||
|
||||
function get_inputOffset():Int
|
||||
function get_globalOffset():Int
|
||||
{
|
||||
return Save?.instance?.options?.inputOffset ?? 0;
|
||||
}
|
||||
|
||||
function set_inputOffset(value:Int):Int
|
||||
{
|
||||
Save.instance.options.inputOffset = value;
|
||||
Save.instance.flush();
|
||||
return Save.instance.options.inputOffset;
|
||||
return Preferences.globalOffset;
|
||||
}
|
||||
|
||||
function get_audioVisualOffset():Int
|
||||
|
@ -268,18 +268,11 @@ class Conductor
|
|||
return Save?.instance?.options?.audioVisualOffset ?? 0;
|
||||
}
|
||||
|
||||
function set_audioVisualOffset(value:Int):Int
|
||||
{
|
||||
Save.instance.options.audioVisualOffset = value;
|
||||
Save.instance.flush();
|
||||
return Save.instance.options.audioVisualOffset;
|
||||
}
|
||||
|
||||
public var combinedOffset(get, never):Float;
|
||||
|
||||
function get_combinedOffset():Float
|
||||
{
|
||||
return instrumentalOffset + audioVisualOffset + inputOffset;
|
||||
return instrumentalOffset + formatOffset + globalOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -401,8 +394,10 @@ class Conductor
|
|||
* @param songPosition The current position in the song in milliseconds.
|
||||
* Leave blank to use the FlxG.sound.music position.
|
||||
* @param applyOffsets If it should apply the instrumentalOffset + formatOffset + audioVisualOffset
|
||||
* @param forceDispatch If it should force the dispatch of onStepHit, onBeatHit, and onMeasureHit
|
||||
* even if the current step, beat, or measure hasn't changed.
|
||||
*/
|
||||
public function update(?songPos:Float, applyOffsets:Bool = true, forceDispatch:Bool = false)
|
||||
public function update(?songPos:Float, applyOffsets:Bool = true, forceDispatch:Bool = false):Void
|
||||
{
|
||||
var currentTime:Float = (FlxG.sound.music != null) ? FlxG.sound.music.time : 0.0;
|
||||
var currentLength:Float = (FlxG.sound.music != null) ? FlxG.sound.music.length : 0.0;
|
||||
|
@ -422,7 +417,8 @@ class Conductor
|
|||
// If the song is playing, limit the song position to the length of the song or beginning of the song.
|
||||
if (FlxG.sound.music != null && FlxG.sound.music.playing)
|
||||
{
|
||||
this.songPosition = Math.min(currentLength, Math.max(0, songPos));
|
||||
this.songPosition = FlxMath.bound(Math.min(this.combinedOffset, 0), songPos, currentLength);
|
||||
this.songPositionDelta += FlxG.elapsed * 1000 * FlxG.sound.music.pitch;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -488,10 +484,23 @@ class Conductor
|
|||
// which it doesn't do every frame!
|
||||
if (prevTime != this.songPosition)
|
||||
{
|
||||
this.songPositionDelta = 0;
|
||||
|
||||
// Update the timestamp for use in-between frames
|
||||
prevTime = this.songPosition;
|
||||
prevTimestamp = Std.int(Timer.stamp() * 1000);
|
||||
}
|
||||
|
||||
if (this == Conductor.instance) @:privateAccess SongSequence.update.dispatch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a more accurate music time for higher framerates.
|
||||
* @return Float
|
||||
*/
|
||||
public function getTimeWithDelta():Float
|
||||
{
|
||||
return this.songPosition + this.songPositionDelta;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
428
source/funkin/FunkinMemory.hx
Normal file
428
source/funkin/FunkinMemory.hx
Normal file
|
@ -0,0 +1,428 @@
|
|||
package funkin;
|
||||
|
||||
import flixel.graphics.FlxGraphic;
|
||||
import flixel.FlxG;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
import openfl.utils.AssetType;
|
||||
import openfl.Assets;
|
||||
import openfl.system.System;
|
||||
import openfl.media.Sound;
|
||||
import lime.app.Future;
|
||||
import lime.app.Promise;
|
||||
|
||||
/**
|
||||
* Handles caching of textures and sounds for the game.
|
||||
* TODO: Remove this once Eric finishes the memory system.
|
||||
*/
|
||||
@:nullSafety
|
||||
class FunkinMemory
|
||||
{
|
||||
static var permanentCachedTextures:Map<String, FlxGraphic> = [];
|
||||
static var currentCachedTextures:Map<String, FlxGraphic> = [];
|
||||
static var previousCachedTextures:Map<String, FlxGraphic> = [];
|
||||
|
||||
// waow
|
||||
static var permanentCachedSounds:Map<String, Sound> = [];
|
||||
static var currentCachedSounds:Map<String, Sound> = [];
|
||||
static var previousCachedSounds:Map<String, Sound> = [];
|
||||
|
||||
static var purgeFilter:Array<String> = ["/week", "/characters", "/charSelect", "/results"];
|
||||
|
||||
/**
|
||||
* Caches textures that are always required.
|
||||
*/
|
||||
public static inline function initialCache():Void
|
||||
{
|
||||
var allImages:Array<String> = Assets.list();
|
||||
|
||||
for (file in allImages)
|
||||
{
|
||||
if (!(file.endsWith(".png") #if FEATURE_COMPRESSED_TEXTURES || file.endsWith(".astc") #end)
|
||||
|| file.contains("chart-editor")
|
||||
|| !file.contains("ui/"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
file = file.replace(" ", ""); // Handle stray spaces.
|
||||
|
||||
if (file.contains("shared") || Assets.exists('shared:$file', AssetType.IMAGE))
|
||||
{
|
||||
file = 'shared:$file';
|
||||
}
|
||||
permanentCacheTexture(file);
|
||||
}
|
||||
|
||||
permanentCacheTexture(Paths.image("healthBar"));
|
||||
permanentCacheTexture(Paths.image("menuDesat"));
|
||||
permanentCacheTexture(Paths.image("notes", "shared"));
|
||||
permanentCacheTexture(Paths.image("noteSplashes", "shared"));
|
||||
permanentCacheTexture(Paths.image("noteStrumline", "shared"));
|
||||
permanentCacheTexture(Paths.image("NOTE_hold_assets"));
|
||||
// dude
|
||||
permanentCacheTexture(Paths.image("fonts/bold", null));
|
||||
permanentCacheTexture(Paths.image("fonts/default", null));
|
||||
permanentCacheTexture(Paths.image("fonts/freeplay-clear", null));
|
||||
|
||||
var allSounds:Array<String> = Assets.list(AssetType.SOUND);
|
||||
|
||||
for (file in allSounds)
|
||||
{
|
||||
if (!file.endsWith(".ogg") || !file.contains("countdown/")) continue;
|
||||
|
||||
file = file.replace(" ", "");
|
||||
|
||||
if (file.contains("shared") || Assets.exists('shared:$file', AssetType.SOUND))
|
||||
{
|
||||
file = 'shared:$file';
|
||||
}
|
||||
|
||||
permanentCacheSound(file);
|
||||
}
|
||||
|
||||
permanentCacheSound(Paths.sound("cancelMenu"));
|
||||
permanentCacheSound(Paths.sound("confirmMenu"));
|
||||
permanentCacheSound(Paths.sound("screenshot"));
|
||||
permanentCacheSound(Paths.sound("scrollMenu"));
|
||||
permanentCacheSound(Paths.sound("soundtray/Voldown"));
|
||||
permanentCacheSound(Paths.sound("soundtray/VolMAX"));
|
||||
permanentCacheSound(Paths.sound("soundtray/Volup"));
|
||||
permanentCacheSound(Paths.music("freakyMenu/freakyMenu"));
|
||||
permanentCacheSound(Paths.music("offsetsLoop/offsetsLoop"));
|
||||
permanentCacheSound(Paths.music("offsetsLoop/drumsLoop"));
|
||||
permanentCacheSound(Paths.sound("missnote1", "shared"));
|
||||
permanentCacheSound(Paths.sound("missnote2", "shared"));
|
||||
permanentCacheSound(Paths.sound("missnote3", "shared"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the current texture and sound caches.
|
||||
*/
|
||||
public static inline function purgeCache(callGarbageCollector:Bool = false):Void
|
||||
{
|
||||
preparePurgeTextureCache();
|
||||
purgeTextureCache();
|
||||
preparePurgeSoundCache();
|
||||
purgeSoundCache();
|
||||
#if (cpp || neko || hl)
|
||||
if (callGarbageCollector) funkin.util.MemoryUtil.collect(true);
|
||||
#end
|
||||
}
|
||||
|
||||
///// TEXTURES /////
|
||||
|
||||
/**
|
||||
* Ensures a texture with the given key is cached.
|
||||
* @param key The key of the texture to cache.
|
||||
*/
|
||||
public static function cacheTexture(key:String):Void
|
||||
{
|
||||
if (currentCachedTextures.exists(key))
|
||||
{
|
||||
return; // Already cached.
|
||||
}
|
||||
|
||||
if (previousCachedTextures.exists(key))
|
||||
{
|
||||
// Move the texture from the previous cache to the current cache.
|
||||
var graphic:Null<FlxGraphic> = previousCachedTextures.get(key);
|
||||
previousCachedTextures.remove(key);
|
||||
if (graphic != null) currentCachedTextures.set(key, graphic);
|
||||
return;
|
||||
}
|
||||
|
||||
var graphic:Null<FlxGraphic> = FlxGraphic.fromAssetKey(key, false, null, true);
|
||||
if (graphic == null)
|
||||
{
|
||||
FlxG.log.warn('Failed to cache graphic: $key');
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Successfully cached graphic: $key');
|
||||
graphic.persist = true;
|
||||
currentCachedTextures.set(key, graphic);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Permanently caches a texture with the given key.
|
||||
* @param key The key of the texture to cache.
|
||||
*/
|
||||
static function permanentCacheTexture(key:String):Void
|
||||
{
|
||||
if (permanentCachedTextures.exists(key))
|
||||
{
|
||||
return; // Already cached.
|
||||
}
|
||||
|
||||
var graphic:Null<FlxGraphic> = FlxGraphic.fromAssetKey(key, false, null, true);
|
||||
if (graphic == null)
|
||||
{
|
||||
FlxG.log.warn('Failed to cache graphic: $key');
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Successfully cached graphic: $key');
|
||||
graphic.persist = true;
|
||||
permanentCachedTextures.set(key, graphic);
|
||||
}
|
||||
|
||||
currentCachedTextures = permanentCachedTextures;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the cache for purging unused textures.
|
||||
*/
|
||||
public inline static function preparePurgeTextureCache():Void
|
||||
{
|
||||
previousCachedTextures = currentCachedTextures;
|
||||
|
||||
for (graphicKey in previousCachedTextures.keys())
|
||||
{
|
||||
if (permanentCachedTextures.exists(graphicKey))
|
||||
{
|
||||
previousCachedTextures.remove(graphicKey);
|
||||
}
|
||||
}
|
||||
|
||||
currentCachedTextures = permanentCachedTextures;
|
||||
}
|
||||
|
||||
/**
|
||||
* Purges unused textures from the cache.
|
||||
*/
|
||||
public static function purgeTextureCache():Void
|
||||
{
|
||||
for (graphicKey in previousCachedTextures.keys())
|
||||
{
|
||||
if (permanentCachedTextures.exists(graphicKey))
|
||||
{
|
||||
previousCachedTextures.remove(graphicKey);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (graphicKey.contains("fonts")) continue;
|
||||
|
||||
var graphic:Null<FlxGraphic> = previousCachedTextures.get(graphicKey);
|
||||
if (graphic != null)
|
||||
{
|
||||
FlxG.bitmap.remove(graphic);
|
||||
graphic.destroy();
|
||||
previousCachedTextures.remove(graphicKey);
|
||||
Assets.cache.clear(graphicKey);
|
||||
}
|
||||
}
|
||||
@:privateAccess
|
||||
if (FlxG.bitmap._cache == null)
|
||||
{
|
||||
@:privateAccess
|
||||
FlxG.bitmap._cache = new Map();
|
||||
}
|
||||
|
||||
@:privateAccess
|
||||
for (key in FlxG.bitmap._cache.keys())
|
||||
{
|
||||
var obj:Null<FlxGraphic> = FlxG.bitmap.get(key);
|
||||
|
||||
if (obj == null || obj.persist || permanentCachedTextures.exists(key) || key.contains("fonts"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (obj.useCount > 0)
|
||||
{
|
||||
for (purgeEntry in purgeFilter)
|
||||
{
|
||||
if (key.contains(purgeEntry))
|
||||
{
|
||||
FlxG.bitmap.removeKey(key);
|
||||
obj.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///// NOTE STYLE //////
|
||||
|
||||
public static function cacheNoteStyle(style:NoteStyle):Void
|
||||
{
|
||||
// TODO: Texture paths should fall back to the default values.
|
||||
cacheTexture(Paths.image(style.getNoteAssetPath() ?? "note"));
|
||||
cacheTexture(style.getHoldNoteAssetPath() ?? "noteHold");
|
||||
cacheTexture(Paths.image(style.getStrumlineAssetPath() ?? "strumline"));
|
||||
cacheTexture(Paths.image(style.getSplashAssetPath() ?? "noteSplash"));
|
||||
|
||||
cacheTexture(Paths.image(style.getHoldCoverDirectionAssetPath(LEFT) ?? "LEFT"));
|
||||
cacheTexture(Paths.image(style.getHoldCoverDirectionAssetPath(RIGHT) ?? "RIGHT"));
|
||||
cacheTexture(Paths.image(style.getHoldCoverDirectionAssetPath(UP) ?? "UP"));
|
||||
cacheTexture(Paths.image(style.getHoldCoverDirectionAssetPath(DOWN) ?? "DOWN"));
|
||||
|
||||
// cacheTexture(Paths.image(style.buildCountdownSpritePath(THREE) ?? "THREE"));
|
||||
cacheTexture(Paths.image(style.buildCountdownSpritePath(TWO) ?? "TWO"));
|
||||
cacheTexture(Paths.image(style.buildCountdownSpritePath(ONE) ?? "ONE"));
|
||||
cacheTexture(Paths.image(style.buildCountdownSpritePath(GO) ?? "GO"));
|
||||
|
||||
cacheSound(style.getCountdownSoundPath(THREE) ?? "THREE");
|
||||
cacheSound(style.getCountdownSoundPath(TWO) ?? "TWO");
|
||||
cacheSound(style.getCountdownSoundPath(ONE) ?? "ONE");
|
||||
cacheSound(style.getCountdownSoundPath(GO) ?? "GO");
|
||||
|
||||
cacheTexture(Paths.image(style.buildJudgementSpritePath("sick") ?? 'sick'));
|
||||
cacheTexture(Paths.image(style.buildJudgementSpritePath("good") ?? 'good'));
|
||||
cacheTexture(Paths.image(style.buildJudgementSpritePath("bad") ?? 'bad'));
|
||||
cacheTexture(Paths.image(style.buildJudgementSpritePath("shit") ?? 'shit'));
|
||||
|
||||
cacheTexture(Paths.image(style.buildComboNumSpritePath(0) ?? '0'));
|
||||
cacheTexture(Paths.image(style.buildComboNumSpritePath(1) ?? '1'));
|
||||
cacheTexture(Paths.image(style.buildComboNumSpritePath(2) ?? '2'));
|
||||
cacheTexture(Paths.image(style.buildComboNumSpritePath(3) ?? '3'));
|
||||
cacheTexture(Paths.image(style.buildComboNumSpritePath(4) ?? '4'));
|
||||
cacheTexture(Paths.image(style.buildComboNumSpritePath(5) ?? '5'));
|
||||
cacheTexture(Paths.image(style.buildComboNumSpritePath(6) ?? '6'));
|
||||
cacheTexture(Paths.image(style.buildComboNumSpritePath(7) ?? '7'));
|
||||
cacheTexture(Paths.image(style.buildComboNumSpritePath(8) ?? '8'));
|
||||
cacheTexture(Paths.image(style.buildComboNumSpritePath(9) ?? '9'));
|
||||
}
|
||||
|
||||
///// SOUND //////
|
||||
|
||||
public static function cacheSound(key:String):Void
|
||||
{
|
||||
if (currentCachedSounds.exists(key)) return;
|
||||
|
||||
if (previousCachedSounds.exists(key))
|
||||
{
|
||||
// Move the texture from the previous cache to the current cache.
|
||||
var sound:Null<Sound> = previousCachedSounds.get(key);
|
||||
previousCachedSounds.remove(key);
|
||||
if (sound != null) currentCachedSounds.set(key, sound);
|
||||
return;
|
||||
}
|
||||
|
||||
var sound:Null<Sound> = Assets.getSound(key, true);
|
||||
if (sound == null) return;
|
||||
else
|
||||
currentCachedSounds.set(key, sound);
|
||||
}
|
||||
|
||||
public static function permanentCacheSound(key:String):Void
|
||||
{
|
||||
if (permanentCachedSounds.exists(key)) return;
|
||||
|
||||
var sound:Null<Sound> = Assets.getSound(key, true);
|
||||
if (sound == null) return;
|
||||
else
|
||||
permanentCachedSounds.set(key, sound);
|
||||
|
||||
if (sound != null) currentCachedSounds.set(key, sound);
|
||||
}
|
||||
|
||||
public static function preparePurgeSoundCache():Void
|
||||
{
|
||||
previousCachedSounds = currentCachedSounds;
|
||||
|
||||
for (key in previousCachedSounds.keys())
|
||||
{
|
||||
if (permanentCachedSounds.exists(key))
|
||||
{
|
||||
previousCachedSounds.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
currentCachedSounds = permanentCachedSounds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Purges unused sounds from the cache.
|
||||
*/
|
||||
public static inline function purgeSoundCache():Void
|
||||
{
|
||||
for (key in previousCachedSounds.keys())
|
||||
{
|
||||
if (permanentCachedSounds.exists(key))
|
||||
{
|
||||
previousCachedSounds.remove(key);
|
||||
continue;
|
||||
}
|
||||
|
||||
var sound:Null<Sound> = previousCachedSounds.get(key);
|
||||
if (sound != null)
|
||||
{
|
||||
Assets.cache.removeSound(key);
|
||||
previousCachedSounds.remove(key);
|
||||
}
|
||||
}
|
||||
Assets.cache.clear("songs");
|
||||
Assets.cache.clear("music");
|
||||
// Felt lazy.
|
||||
var key = Paths.music("freakyMenu/freakyMenu");
|
||||
var sound:Null<Sound> = Assets.getSound(key, true);
|
||||
if (sound != null)
|
||||
{
|
||||
permanentCachedSounds.set(key, sound);
|
||||
currentCachedSounds.set(key, sound);
|
||||
}
|
||||
}
|
||||
|
||||
///// MISC /////
|
||||
|
||||
public static inline function clearFreeplay():Void
|
||||
{
|
||||
var keysToRemove:Array<String> = [];
|
||||
|
||||
@:privateAccess
|
||||
for (key in FlxG.bitmap._cache.keys())
|
||||
{
|
||||
if (!key.contains("freeplay")) continue;
|
||||
if (permanentCachedTextures.exists(key) || key.contains("fonts")) continue;
|
||||
|
||||
keysToRemove.push(key);
|
||||
}
|
||||
|
||||
@:privateAccess
|
||||
for (key in keysToRemove)
|
||||
{
|
||||
trace('Cleaning up $key');
|
||||
var obj:Null<FlxGraphic> = FlxG.bitmap.get(key);
|
||||
if (obj != null)
|
||||
{
|
||||
obj.destroy();
|
||||
}
|
||||
FlxG.bitmap.removeKey(key);
|
||||
if (currentCachedTextures.exists(key)) currentCachedTextures.remove(key);
|
||||
Assets.cache.clear(key);
|
||||
}
|
||||
|
||||
preparePurgeSoundCache();
|
||||
purgeSoundCache();
|
||||
}
|
||||
|
||||
public static inline function clearStickers():Void
|
||||
{
|
||||
var keysToRemove:Array<String> = [];
|
||||
|
||||
@:privateAccess
|
||||
for (key in FlxG.bitmap._cache.keys())
|
||||
{
|
||||
if (!key.contains("stickers")) continue;
|
||||
if (permanentCachedTextures.exists(key) || key.contains("fonts")) continue;
|
||||
|
||||
keysToRemove.push(key);
|
||||
}
|
||||
|
||||
@:privateAccess
|
||||
for (key in keysToRemove)
|
||||
{
|
||||
trace('Cleaning up $key');
|
||||
var obj:Null<FlxGraphic> = FlxG.bitmap.get(key);
|
||||
if (obj != null)
|
||||
{
|
||||
obj.destroy();
|
||||
}
|
||||
FlxG.bitmap.removeKey(key);
|
||||
if (currentCachedTextures.exists(key)) currentCachedTextures.remove(key);
|
||||
Assets.cache.clear(key);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ package funkin;
|
|||
/**
|
||||
* A core class which handles tracking score and combo for the current song.
|
||||
*/
|
||||
@:nullSafety
|
||||
class Highscore
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -10,6 +10,7 @@ import flixel.math.FlxPoint;
|
|||
import flixel.math.FlxRect;
|
||||
import flixel.system.debug.log.LogStyle;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.data.dialogue.conversation.ConversationRegistry;
|
||||
import funkin.data.dialogue.dialoguebox.DialogueBoxRegistry;
|
||||
import funkin.data.dialogue.speaker.SpeakerRegistry;
|
||||
|
@ -27,12 +28,12 @@ import funkin.play.character.CharacterData.CharacterDataParser;
|
|||
import funkin.play.notes.notekind.NoteKindManager;
|
||||
import funkin.play.PlayStatePlaylist;
|
||||
import funkin.ui.debug.charting.ChartEditorState;
|
||||
import funkin.ui.debug.stageeditor.StageEditorState;
|
||||
import funkin.ui.title.TitleState;
|
||||
import funkin.ui.transition.LoadingState;
|
||||
import funkin.util.CLIUtil;
|
||||
import funkin.util.CLIUtil.CLIParams;
|
||||
import funkin.util.macro.MacroUtil;
|
||||
import funkin.util.TimerUtil;
|
||||
import funkin.util.TrackerUtil;
|
||||
import funkin.util.WindowUtil;
|
||||
import openfl.display.BitmapData;
|
||||
|
@ -51,6 +52,7 @@ import funkin.api.newgrounds.NewgroundsClient;
|
|||
*
|
||||
* It should not contain any sprites or rendering.
|
||||
*/
|
||||
@:nullSafety
|
||||
class InitState extends FlxState
|
||||
{
|
||||
/**
|
||||
|
@ -90,6 +92,31 @@ class InitState extends FlxState
|
|||
funkin.util.WindowUtil.initTracy();
|
||||
#end
|
||||
|
||||
#if FEATURE_HAPTICS
|
||||
// Setup Haptic feedback
|
||||
extension.haptics.Haptic.initialize();
|
||||
#end
|
||||
|
||||
#if FEATURE_MOBILE_ADVERTISEMENTS
|
||||
// Setup Admob
|
||||
funkin.mobile.util.AdMobUtil.init();
|
||||
#end
|
||||
|
||||
#if FEATURE_MOBILE_IAP
|
||||
// Setup In-App purchases
|
||||
funkin.mobile.util.InAppPurchasesUtil.init();
|
||||
#end
|
||||
|
||||
#if FEATURE_MOBILE_IAR
|
||||
// Setup In-App purchases
|
||||
funkin.mobile.util.InAppReviewUtil.init();
|
||||
#end
|
||||
|
||||
#if ios
|
||||
// Setup Audio session
|
||||
funkin.external.ios.AudioSession.initialize();
|
||||
#end
|
||||
|
||||
// This ain't a pixel art game! (most of the time)
|
||||
FlxSprite.defaultAntialiasing = true;
|
||||
|
||||
|
@ -102,13 +129,15 @@ class InitState extends FlxState
|
|||
// but that makes our soundtray not show up on init if we have the game muted.
|
||||
// We set it to active so it at least calls it's update function once (see FlxGame.onEnterFrame(), it's called there)
|
||||
// and also see FunkinSoundTray.update() to see what we do and how we check if we are muted or not
|
||||
#if !mobile
|
||||
FlxG.game.soundTray.active = true;
|
||||
#end
|
||||
|
||||
// Set the game to a lower frame rate while it is in the background.
|
||||
FlxG.game.focusLostFramerate = 30;
|
||||
|
||||
// Makes Flixel use frame times instead of locked movements per frame for things like tweens
|
||||
FlxG.fixedTimestep = false;
|
||||
FlxG.fixedTimestep = false;
|
||||
|
||||
setupFlixelDebug();
|
||||
|
||||
|
@ -131,6 +160,25 @@ class InitState extends FlxState
|
|||
// Don't play transition in when entering the title state.
|
||||
FlxTransitionableState.skipNextTransIn = true;
|
||||
|
||||
FlxG.signals.gameResized.add(function(width:Int, height:Int) {
|
||||
FlxTransitionableState.defaultTransIn = new TransitionData(FADE, FlxColor.BLACK, 1, new FlxPoint(0, -1), tileData,
|
||||
new FlxRect(-200, -200, FlxG.width * 1.4, FlxG.height * 1.4));
|
||||
FlxTransitionableState.defaultTransOut = new TransitionData(FADE, FlxColor.BLACK, 0.7, new FlxPoint(0, 1), tileData,
|
||||
new FlxRect(-200, -200, FlxG.width * 1.4, FlxG.height * 1.4));
|
||||
});
|
||||
|
||||
// SDL for some reason enables VSync on focus lost/gained in Android
|
||||
// Since we don't really need VSync on Android we're gonna forcefully disable it on these signals for now
|
||||
// This is fixed on SDL3 from what I've heared but that doodoo isn't working poperly for Android
|
||||
#if android
|
||||
FlxG.signals.focusLost.add(function() {
|
||||
WindowUtil.setVSyncMode(lime.ui.WindowVSyncMode.OFF);
|
||||
});
|
||||
FlxG.signals.focusGained.add(function() {
|
||||
WindowUtil.setVSyncMode(lime.ui.WindowVSyncMode.OFF);
|
||||
});
|
||||
#end
|
||||
|
||||
//
|
||||
// NEWGROUNDS API SETUP
|
||||
//
|
||||
|
@ -154,6 +202,7 @@ class InitState extends FlxState
|
|||
//
|
||||
#if android
|
||||
FlxG.android.preventDefaultKeys = [flixel.input.android.FlxAndroidKey.BACK];
|
||||
funkin.external.android.CallbackUtil.init();
|
||||
#end
|
||||
|
||||
//
|
||||
|
@ -168,12 +217,20 @@ class InitState extends FlxState
|
|||
#if FEATURE_SCREENSHOTS
|
||||
funkin.util.plugins.ScreenshotPlugin.initialize();
|
||||
#end
|
||||
#if FEATURE_NEWGROUNDS
|
||||
funkin.util.plugins.NewgroundsMedalPlugin.initialize();
|
||||
#end
|
||||
funkin.util.plugins.EvacuateDebugPlugin.initialize();
|
||||
funkin.util.plugins.ForceCrashPlugin.initialize();
|
||||
funkin.util.plugins.ReloadAssetsDebugPlugin.initialize();
|
||||
#if !mobile
|
||||
funkin.util.plugins.VolumePlugin.initialize();
|
||||
#end
|
||||
funkin.util.plugins.WatchPlugin.initialize();
|
||||
#if mobile
|
||||
funkin.util.plugins.TouchPointerPlugin.initialize();
|
||||
funkin.mobile.input.ControlsHandler.initInputTrackers();
|
||||
#end
|
||||
|
||||
//
|
||||
// GAME DATA PARSING
|
||||
|
@ -182,7 +239,6 @@ class InitState extends FlxState
|
|||
// NOTE: Registries must be imported and not referenced with fully qualified names,
|
||||
// to ensure build macros work properly.
|
||||
trace('Parsing game data...');
|
||||
var perfStart:Float = TimerUtil.start();
|
||||
SongEventRegistry.loadEventCache(); // SongEventRegistry is structured differently so it's not a BaseRegistry.
|
||||
SongRegistry.instance.loadEntries();
|
||||
LevelRegistry.instance.loadEntries();
|
||||
|
@ -208,7 +264,10 @@ class InitState extends FlxState
|
|||
|
||||
funkin.input.Cursor.hide();
|
||||
|
||||
trace('Parsing game data took: ${TimerUtil.ms(perfStart)}');
|
||||
#if !html5
|
||||
// This fucking breaks on HTML5 builds because the "shared" library isn't loaded yet.
|
||||
funkin.FunkinMemory.initialCache();
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -241,6 +300,9 @@ class InitState extends FlxState
|
|||
#elseif CHARTING
|
||||
// -DCHARTING
|
||||
FlxG.switchState(() -> new funkin.ui.debug.charting.ChartEditorState());
|
||||
#elseif STAGING
|
||||
// -DSTAGING
|
||||
FlxG.switchState(() -> new funkin.ui.debug.stageeditor.StageEditorState());
|
||||
#elseif STAGEBUILD
|
||||
// -DSTAGEBUILD
|
||||
FlxG.switchState(() -> new funkin.ui.debug.stage.StageBuilderState());
|
||||
|
@ -303,9 +365,16 @@ class InitState extends FlxState
|
|||
fnfcTargetPath: params.chart.chartPath,
|
||||
}));
|
||||
}
|
||||
else if (params.stage.shouldLoadStage)
|
||||
{
|
||||
FlxG.switchState(() -> new StageEditorState(
|
||||
{
|
||||
fnfsTargetPath: params.stage.stagePath,
|
||||
}));
|
||||
}
|
||||
else
|
||||
{
|
||||
FlxG.sound.cache(Paths.music('freakyMenu/freakyMenu'));
|
||||
// FlxG.sound.cache(Paths.music('freakyMenu/freakyMenu'));
|
||||
FlxG.switchState(() -> new TitleState());
|
||||
}
|
||||
}
|
||||
|
@ -317,7 +386,7 @@ class InitState extends FlxState
|
|||
*/
|
||||
function startSong(songId:String, difficultyId:String = 'normal'):Void
|
||||
{
|
||||
var songData:funkin.play.song.Song = funkin.data.song.SongRegistry.instance.fetchEntry(songId);
|
||||
var songData:Null<funkin.play.song.Song> = funkin.data.song.SongRegistry.instance.fetchEntry(songId);
|
||||
|
||||
if (songData == null)
|
||||
{
|
||||
|
@ -354,6 +423,7 @@ class InitState extends FlxState
|
|||
PlayStatePlaylist.campaignId = 'weekend1';
|
||||
}
|
||||
|
||||
@:nullSafety(Off) // Cannot unify?
|
||||
LoadingState.loadPlayState(
|
||||
{
|
||||
targetSong: songData,
|
||||
|
@ -368,7 +438,7 @@ class InitState extends FlxState
|
|||
*/
|
||||
function startLevel(levelId:String, difficultyId:String = 'normal'):Void
|
||||
{
|
||||
var currentLevel:funkin.ui.story.Level = funkin.data.story.level.LevelRegistry.instance.fetchEntry(levelId);
|
||||
var currentLevel:Null<funkin.ui.story.Level> = funkin.data.story.level.LevelRegistry.instance.fetchEntry(levelId);
|
||||
|
||||
if (currentLevel == null)
|
||||
{
|
||||
|
@ -384,10 +454,19 @@ class InitState extends FlxState
|
|||
PlayStatePlaylist.isStoryMode = true;
|
||||
PlayStatePlaylist.campaignScore = 0;
|
||||
|
||||
var targetSongId:String = PlayStatePlaylist.playlistSongIds.shift();
|
||||
var targetSongId:Null<String> = PlayStatePlaylist.playlistSongIds.shift();
|
||||
|
||||
var targetSong:funkin.play.song.Song = SongRegistry.instance.fetchEntry(targetSongId);
|
||||
var targetSong:Null<funkin.play.song.Song> = null;
|
||||
|
||||
if (targetSongId != null) targetSong = SongRegistry.instance.fetchEntry(targetSongId);
|
||||
|
||||
if (targetSongId == null)
|
||||
{
|
||||
startGameNormally();
|
||||
return;
|
||||
}
|
||||
|
||||
@:nullSafety(Off)
|
||||
LoadingState.loadPlayState(
|
||||
{
|
||||
targetSong: targetSong,
|
||||
|
@ -395,6 +474,7 @@ class InitState extends FlxState
|
|||
});
|
||||
}
|
||||
|
||||
@:nullSafety(Off) // Meh, remove when flixel.system.debug.log.LogStyle is null safe
|
||||
function setupFlixelDebug():Void
|
||||
{
|
||||
//
|
||||
|
@ -474,17 +554,17 @@ class InitState extends FlxState
|
|||
#end
|
||||
}
|
||||
|
||||
function defineSong():String
|
||||
function defineSong():Null<String>
|
||||
{
|
||||
return MacroUtil.getDefine('SONG');
|
||||
}
|
||||
|
||||
function defineLevel():String
|
||||
function defineLevel():Null<String>
|
||||
{
|
||||
return MacroUtil.getDefine('LEVEL');
|
||||
}
|
||||
|
||||
function defineDifficulty():String
|
||||
function defineDifficulty():Null<String>
|
||||
{
|
||||
return MacroUtil.getDefine('DIFFICULTY');
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import openfl.utils.AssetType;
|
|||
/**
|
||||
* A core class which handles determining asset paths.
|
||||
*/
|
||||
@:nullSafety
|
||||
class Paths
|
||||
{
|
||||
static var currentLevel:Null<String> = null;
|
||||
|
@ -136,7 +137,7 @@ class Paths
|
|||
* @param withExtension if it should return with the audio file extension `.mp3` or `.ogg`.
|
||||
* @return String
|
||||
*/
|
||||
public static function inst(song:String, ?suffix:String = '', ?withExtension:Bool = true):String
|
||||
public static function inst(song:String, ?suffix:String = '', withExtension:Bool = true):String
|
||||
{
|
||||
var ext:String = withExtension ? '.${Constants.EXT_SOUND}' : '';
|
||||
return 'songs:assets/songs/${song.toLowerCase()}/Inst$suffix$ext';
|
||||
|
|
|
@ -9,12 +9,17 @@ import flixel.util.FlxSignal.FlxTypedSignal;
|
|||
/**
|
||||
* A core class which represents the current player(s) and their controls and other configuration.
|
||||
*/
|
||||
@:nullSafety
|
||||
class PlayerSettings
|
||||
{
|
||||
// TODO: Finish implementation of second player.
|
||||
public static var numPlayers(default, null) = 0;
|
||||
public static var numAvatars(default, null) = 0;
|
||||
// TODO: Making both of these null makes a lot of errors with the controls.
|
||||
// That'd explain why unplugging input devices can cause the game to crash?
|
||||
@:nullSafety(Off)
|
||||
public static var player1(default, null):PlayerSettings;
|
||||
@:nullSafety(Off)
|
||||
public static var player2(default, null):PlayerSettings;
|
||||
|
||||
public static var onAvatarAdd(default, null) = new FlxTypedSignal<PlayerSettings->Void>();
|
||||
|
@ -70,6 +75,7 @@ class PlayerSettings
|
|||
/**
|
||||
* Forcibly destroy the PlayerSettings singletons for each player.
|
||||
*/
|
||||
@:nullSafety(Off)
|
||||
public static function reset():Void
|
||||
{
|
||||
player1 = null;
|
||||
|
|
|
@ -1,15 +1,22 @@
|
|||
package funkin;
|
||||
|
||||
#if mobile
|
||||
import funkin.mobile.ui.FunkinHitbox;
|
||||
import funkin.mobile.util.InAppPurchasesUtil;
|
||||
#end
|
||||
import funkin.save.Save;
|
||||
import funkin.util.WindowUtil;
|
||||
import funkin.util.HapticUtil.HapticsMode;
|
||||
|
||||
/**
|
||||
* A core class which provides a store of user-configurable, globally relevant values.
|
||||
*/
|
||||
@:nullSafety
|
||||
class Preferences
|
||||
{
|
||||
/**
|
||||
* FPS
|
||||
* Always the refresh rate of the display on mobile, or 60 on web.
|
||||
* @default `60`
|
||||
*/
|
||||
public static var framerate(get, set):Int;
|
||||
|
@ -18,6 +25,12 @@ class Preferences
|
|||
{
|
||||
#if web
|
||||
return 60;
|
||||
#elseif mobile
|
||||
var refreshRate:Int = FlxG.stage.window.displayMode.refreshRate;
|
||||
|
||||
if (refreshRate < 60) refreshRate = 60;
|
||||
|
||||
return refreshRate;
|
||||
#else
|
||||
return Save?.instance?.options?.framerate ?? 60;
|
||||
#end
|
||||
|
@ -45,11 +58,19 @@ class Preferences
|
|||
|
||||
static function get_naughtyness():Bool
|
||||
{
|
||||
return Save?.instance?.options?.naughtyness;
|
||||
#if NO_FEATURE_NAUGHTYNESS
|
||||
return false;
|
||||
#else
|
||||
return Save?.instance?.options?.naughtyness ?? true;
|
||||
#end
|
||||
}
|
||||
|
||||
static function set_naughtyness(value:Bool):Bool
|
||||
{
|
||||
#if NO_FEATURE_NAUGHTYNESS
|
||||
value = false;
|
||||
#end
|
||||
|
||||
var save:Save = Save.instance;
|
||||
save.options.naughtyness = value;
|
||||
save.flush();
|
||||
|
@ -64,7 +85,7 @@ class Preferences
|
|||
|
||||
static function get_downscroll():Bool
|
||||
{
|
||||
return Save?.instance?.options?.downscroll;
|
||||
return Save?.instance?.options?.downscroll #if mobile ?? true #else ?? false #end;
|
||||
}
|
||||
|
||||
static function set_downscroll(value:Bool):Bool
|
||||
|
@ -102,7 +123,7 @@ class Preferences
|
|||
|
||||
static function get_zoomCamera():Bool
|
||||
{
|
||||
return Save?.instance?.options?.zoomCamera;
|
||||
return Save?.instance?.options?.zoomCamera ?? true;
|
||||
}
|
||||
|
||||
static function set_zoomCamera(value:Bool):Bool
|
||||
|
@ -115,13 +136,17 @@ class Preferences
|
|||
|
||||
/**
|
||||
* If enabled, an FPS and memory counter will be displayed even if this is not a debug build.
|
||||
* Always disabled on mobile.
|
||||
* @default `false`
|
||||
*/
|
||||
public static var debugDisplay(get, set):Bool;
|
||||
|
||||
static function get_debugDisplay():Bool
|
||||
{
|
||||
return Save?.instance?.options?.debugDisplay;
|
||||
#if mobile
|
||||
return false;
|
||||
#end
|
||||
return Save?.instance?.options?.debugDisplay ?? false;
|
||||
}
|
||||
|
||||
static function set_debugDisplay(value:Bool):Bool
|
||||
|
@ -137,14 +162,78 @@ class Preferences
|
|||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* If enabled, haptic feedback will be enabled.
|
||||
* @default `All`
|
||||
*/
|
||||
public static var hapticsMode(get, set):HapticsMode;
|
||||
|
||||
static function get_hapticsMode():HapticsMode
|
||||
{
|
||||
var value = Save?.instance?.options?.hapticsMode ?? "All";
|
||||
|
||||
return switch (value)
|
||||
{
|
||||
case "None":
|
||||
HapticsMode.NONE;
|
||||
case "Notes Only":
|
||||
HapticsMode.NOTES_ONLY;
|
||||
default:
|
||||
HapticsMode.ALL;
|
||||
};
|
||||
}
|
||||
|
||||
static function set_hapticsMode(value:HapticsMode):HapticsMode
|
||||
{
|
||||
var string;
|
||||
|
||||
switch (value)
|
||||
{
|
||||
case HapticsMode.NONE:
|
||||
string = "None";
|
||||
case HapticsMode.NOTES_ONLY:
|
||||
string = "Notes Only";
|
||||
default:
|
||||
string = "All";
|
||||
};
|
||||
|
||||
var save:Save = Save.instance;
|
||||
save.options.hapticsMode = string;
|
||||
save.flush();
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiplier of intensity for all the haptic feedback effects.
|
||||
* @default `2.5`
|
||||
*/
|
||||
public static var hapticsIntensityMultiplier(get, set):Float;
|
||||
|
||||
static function get_hapticsIntensityMultiplier():Float
|
||||
{
|
||||
return Save?.instance?.options?.hapticsIntensityMultiplier ?? 1;
|
||||
}
|
||||
|
||||
static function set_hapticsIntensityMultiplier(value:Float):Float
|
||||
{
|
||||
var save:Save = Save.instance;
|
||||
save.options.hapticsIntensityMultiplier = value;
|
||||
save.flush();
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* If enabled, the game will automatically pause when tabbing out.
|
||||
* Always enabled on mobile.
|
||||
* @default `true`
|
||||
*/
|
||||
public static var autoPause(get, set):Bool;
|
||||
|
||||
static function get_autoPause():Bool
|
||||
{
|
||||
#if mobile
|
||||
return true;
|
||||
#end
|
||||
return Save?.instance?.options?.autoPause ?? true;
|
||||
}
|
||||
|
||||
|
@ -177,6 +266,26 @@ class Preferences
|
|||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* A global audio offset in milliseconds.
|
||||
* This is used to sync the audio.
|
||||
* @default `0`
|
||||
*/
|
||||
public static var globalOffset(get, set):Int;
|
||||
|
||||
static function get_globalOffset():Int
|
||||
{
|
||||
return Save?.instance?.options?.globalOffset ?? 0;
|
||||
}
|
||||
|
||||
static function set_globalOffset(value:Int):Int
|
||||
{
|
||||
var save:Save = Save.instance;
|
||||
save.options.globalOffset = value;
|
||||
save.flush();
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* If enabled, the game will utilize VSync (or adaptive VSync) on startup.
|
||||
* @default `OFF`
|
||||
|
@ -228,7 +337,7 @@ class Preferences
|
|||
|
||||
static function get_unlockedFramerate():Bool
|
||||
{
|
||||
return Save?.instance?.options?.unlockedFramerate;
|
||||
return Save?.instance?.options?.unlockedFramerate ?? false;
|
||||
}
|
||||
|
||||
static function set_unlockedFramerate(value:Bool):Bool
|
||||
|
@ -343,44 +452,6 @@ class Preferences
|
|||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The game will save any screenshots taken to this format.
|
||||
* @default `PNG`
|
||||
*/
|
||||
public static var saveFormat(get, set):Any;
|
||||
|
||||
static function get_saveFormat():Any
|
||||
{
|
||||
return Save?.instance?.options?.screenshot?.saveFormat ?? 'PNG';
|
||||
}
|
||||
|
||||
static function set_saveFormat(value):Any
|
||||
{
|
||||
var save:Save = Save.instance;
|
||||
save.options.screenshot.saveFormat = value;
|
||||
save.flush();
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The game will save JPEG screenshots with this quality percentage.
|
||||
* @default `80`
|
||||
*/
|
||||
public static var jpegQuality(get, set):Int;
|
||||
|
||||
static function get_jpegQuality():Int
|
||||
{
|
||||
return Save?.instance?.options?.screenshot?.jpegQuality ?? 80;
|
||||
}
|
||||
|
||||
static function set_jpegQuality(value:Int):Int
|
||||
{
|
||||
var save:Save = Save.instance;
|
||||
save.options.screenshot.jpegQuality = value;
|
||||
save.flush();
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the user's preferences from the save data and apply them.
|
||||
*/
|
||||
|
@ -394,8 +465,16 @@ class Preferences
|
|||
#if web
|
||||
toggleFramerateCap(Preferences.unlockedFramerate);
|
||||
#end
|
||||
|
||||
#if desktop
|
||||
// Apply the autoFullscreen setting (launches the game in fullscreen automatically)
|
||||
FlxG.fullscreen = Preferences.autoFullscreen;
|
||||
#end
|
||||
|
||||
#if mobile
|
||||
// Apply the allowScreenTimeout setting.
|
||||
lime.system.System.allowScreenTimeout = Preferences.screenTimeout;
|
||||
#end
|
||||
}
|
||||
|
||||
static function toggleFramerateCap(unlocked:Bool):Void
|
||||
|
@ -411,18 +490,88 @@ class Preferences
|
|||
if (show)
|
||||
{
|
||||
// Enable the debug display.
|
||||
FlxG.stage.addChild(Main.fpsCounter);
|
||||
FlxG.game.parent.addChild(Main.fpsCounter);
|
||||
|
||||
#if !html5
|
||||
FlxG.stage.addChild(Main.memoryCounter);
|
||||
FlxG.game.parent.addChild(Main.memoryCounter);
|
||||
#end
|
||||
}
|
||||
else
|
||||
{
|
||||
// Disable the debug display.
|
||||
FlxG.stage.removeChild(Main.fpsCounter);
|
||||
FlxG.game.parent.removeChild(Main.fpsCounter);
|
||||
|
||||
#if !html5
|
||||
FlxG.stage.removeChild(Main.memoryCounter);
|
||||
FlxG.game.parent.removeChild(Main.memoryCounter);
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
#if mobile
|
||||
/**
|
||||
* If enabled, device will be able to sleep on its own.
|
||||
* @default `false`
|
||||
*/
|
||||
public static var screenTimeout(get, set):Bool;
|
||||
|
||||
static function get_screenTimeout():Bool
|
||||
{
|
||||
return Save?.instance?.mobileOptions?.screenTimeout ?? false;
|
||||
}
|
||||
|
||||
static function set_screenTimeout(value:Bool):Bool
|
||||
{
|
||||
if (value != Save.instance.mobileOptions.screenTimeout) lime.system.System.allowScreenTimeout = value;
|
||||
|
||||
var save:Save = Save.instance;
|
||||
save.mobileOptions.screenTimeout = value;
|
||||
save.flush();
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Controls Scheme for the hitbox.
|
||||
* @default `4 Lanes`
|
||||
*/
|
||||
public static var controlsScheme(get, set):String;
|
||||
|
||||
static function get_controlsScheme():String
|
||||
{
|
||||
return Save?.instance?.mobileOptions?.controlsScheme ?? FunkinHitboxControlSchemes.Arrows;
|
||||
}
|
||||
|
||||
static function set_controlsScheme(value:String):String
|
||||
{
|
||||
var save:Save = Save.instance;
|
||||
save.mobileOptions.controlsScheme = value;
|
||||
save.flush();
|
||||
return value;
|
||||
}
|
||||
|
||||
#if FEATURE_MOBILE_IAP
|
||||
/**
|
||||
* If bought, the game will not show any ads.
|
||||
* @default `false`
|
||||
*/
|
||||
@:unreflective
|
||||
public static var noAds(get, set):Bool;
|
||||
|
||||
@:unreflective
|
||||
static function get_noAds():Bool
|
||||
{
|
||||
if (InAppPurchasesUtil.hasInitialized) noAds = InAppPurchasesUtil.isPurchased(InAppPurchasesUtil.UPGRADE_PRODUCT_ID);
|
||||
var returnedValue = Save?.instance?.mobileOptions?.noAds ?? false;
|
||||
return returnedValue;
|
||||
}
|
||||
|
||||
@:unreflective
|
||||
static function set_noAds(value:Bool):Bool
|
||||
{
|
||||
var save:Save = Save.instance;
|
||||
save.mobileOptions.noAds = value;
|
||||
save.flush();
|
||||
return value;
|
||||
}
|
||||
#end
|
||||
#end
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package funkin.api.discord;
|
||||
|
||||
import funkin.util.macro.EnvironmentConfigMacro;
|
||||
#if FEATURE_DISCORD_RPC
|
||||
import hxdiscord_rpc.Discord;
|
||||
import hxdiscord_rpc.Types.DiscordButton;
|
||||
|
@ -8,9 +9,10 @@ import hxdiscord_rpc.Types.DiscordRichPresence;
|
|||
import hxdiscord_rpc.Types.DiscordUser;
|
||||
import sys.thread.Thread;
|
||||
|
||||
@:nullSafety
|
||||
class DiscordClient
|
||||
{
|
||||
static final CLIENT_ID:String = "816168432860790794";
|
||||
static final CLIENT_ID:Null<String> = EnvironmentConfigMacro.environmentConfig?.get("DESKTOP_DISCORD_CLIENT_ID");
|
||||
|
||||
public static var instance(get, never):DiscordClient;
|
||||
static var _instance:Null<DiscordClient> = null;
|
||||
|
@ -39,13 +41,29 @@ class DiscordClient
|
|||
{
|
||||
trace('[DISCORD] Initializing connection...');
|
||||
|
||||
// Discord.initialize(CLIENT_ID, handlers, true, null);
|
||||
Discord.Initialize(CLIENT_ID, cpp.RawPointer.addressOf(handlers), 1, null);
|
||||
if (!hasValidCredentials())
|
||||
{
|
||||
FlxG.log.warn("Tried to initialize Discord connection, but credentials are invalid!");
|
||||
return;
|
||||
}
|
||||
|
||||
@:nullSafety(Off)
|
||||
{
|
||||
Discord.Initialize(CLIENT_ID, cpp.RawPointer.addressOf(handlers), 1, "");
|
||||
}
|
||||
|
||||
createDaemon();
|
||||
}
|
||||
|
||||
var daemon:Thread = null;
|
||||
/**
|
||||
* @returns `false` if the client ID is invalid.
|
||||
*/
|
||||
static function hasValidCredentials():Bool
|
||||
{
|
||||
return !(CLIENT_ID == null || CLIENT_ID == "" || (CLIENT_ID != null && CLIENT_ID.contains(" ")));
|
||||
}
|
||||
|
||||
var daemon:Null<Thread> = null;
|
||||
|
||||
function createDaemon():Void
|
||||
{
|
||||
|
@ -56,8 +74,6 @@ class DiscordClient
|
|||
{
|
||||
while (true)
|
||||
{
|
||||
trace('[DISCORD] Performing client update...');
|
||||
|
||||
#if DISCORD_DISABLE_IO_THREAD
|
||||
Discord.updateConnection();
|
||||
#end
|
||||
|
@ -76,8 +92,6 @@ class DiscordClient
|
|||
|
||||
public function setPresence(params:DiscordClientPresenceParams):Void
|
||||
{
|
||||
trace('[DISCORD] Updating presence... (${params})');
|
||||
|
||||
Discord.updatePresence(buildPresence(params));
|
||||
}
|
||||
|
||||
|
@ -92,17 +106,15 @@ class DiscordClient
|
|||
presence.largeImageText = "Friday Night Funkin'";
|
||||
|
||||
// State should be generally what the person is doing, like "In the Menus" or "Pico (Pico Mix) [Freeplay Hard]"
|
||||
presence.state = cast(params.state, Null<String>);
|
||||
presence.state = cast(params.state, Null<String>) ?? "";
|
||||
// Details should be what the person is specifically doing, including stuff like timestamps (maybe something like "03:24 elapsed").
|
||||
presence.details = cast(params.details, Null<String>);
|
||||
presence.details = cast(params.details, Null<String>) ?? "";
|
||||
|
||||
// The large image displaying what the user is doing.
|
||||
// This should probably be album art.
|
||||
// IMPORTANT NOTE: This can be an asset key uploaded to Discord's developer panel OR any URL you like.
|
||||
presence.largeImageKey = cast(params.largeImageKey, Null<String>) ?? "album-volume1";
|
||||
|
||||
trace('[DISCORD] largeImageKey: ${presence.largeImageKey}');
|
||||
|
||||
// TODO: Make this use the song's album art.
|
||||
// presence.largeImageKey = "icon";
|
||||
// presence.largeImageKey = "https://f4.bcbits.com/img/a0746694746_16.jpg";
|
||||
|
@ -110,7 +122,7 @@ class DiscordClient
|
|||
// The small inset image for what the user is doing.
|
||||
// This can be the opponent's health icon?
|
||||
// NOTE: Like largeImageKey, this can be a URL, or an asset key.
|
||||
presence.smallImageKey = cast(params.smallImageKey, Null<String>);
|
||||
presence.smallImageKey = cast(params.smallImageKey, Null<String>) ?? "";
|
||||
|
||||
// NOTE: In previous versions, this showed as "Elapsed", but now shows as playtime and doesn't look good
|
||||
// presence.startTimestamp = time - 10;
|
||||
|
@ -136,9 +148,9 @@ class DiscordClient
|
|||
|
||||
final username:String = request[0].username;
|
||||
final globalName:String = request[0].username;
|
||||
final discriminator:Int = Std.parseInt(request[0].discriminator);
|
||||
final discriminator:Null<Int> = Std.parseInt(request[0].discriminator);
|
||||
|
||||
if (discriminator != 0)
|
||||
if (discriminator != null && discriminator != 0)
|
||||
{
|
||||
trace('[DISCORD] User: ${username}#${discriminator} (${globalName})');
|
||||
}
|
||||
|
@ -204,4 +216,30 @@ typedef DiscordClientPresenceParams =
|
|||
*/
|
||||
var ?smallImageKey:String;
|
||||
}
|
||||
|
||||
class DiscordClientSandboxed
|
||||
{
|
||||
public static function setPresence(params:DiscordClientPresenceParams):Void
|
||||
{
|
||||
DiscordClient.instance.setPresence(params);
|
||||
}
|
||||
|
||||
public static function shutdown():Void
|
||||
{
|
||||
DiscordClient.instance.shutdown();
|
||||
}
|
||||
}
|
||||
#else
|
||||
class DiscordClientSandboxed
|
||||
{
|
||||
public static function setPresence(params:Dynamic):Void
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
public static function shutdown():Void
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package funkin.api.newgrounds;
|
||||
|
||||
#if FEATURE_NEWGROUNDS_EVENTS
|
||||
import io.newgrounds.Call.CallOutcome;
|
||||
import io.newgrounds.NG;
|
||||
import io.newgrounds.objects.events.Outcome;
|
||||
import io.newgrounds.objects.events.Result;
|
||||
#end
|
||||
|
||||
/**
|
||||
* Use Newgrounds to perform basic telemetry. Ignore if not logged in to Newgrounds.
|
||||
|
@ -31,6 +33,7 @@ class Events
|
|||
#end
|
||||
}
|
||||
|
||||
#if FEATURE_NEWGROUNDS_EVENTS
|
||||
static function onEventLogged(eventName:String, outcome:CallOutcome<LogEventData>)
|
||||
{
|
||||
switch (outcome)
|
||||
|
@ -55,6 +58,7 @@ class Events
|
|||
}
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
public static inline function logStartGame():Void
|
||||
{
|
||||
|
|
|
@ -2,10 +2,14 @@ package funkin.api.newgrounds;
|
|||
|
||||
#if FEATURE_NEWGROUNDS
|
||||
import io.newgrounds.Call.CallError;
|
||||
import io.newgrounds.components.ScoreBoardComponent;
|
||||
import io.newgrounds.objects.Score;
|
||||
import io.newgrounds.objects.ScoreBoard as LeaderboardData;
|
||||
import io.newgrounds.objects.User;
|
||||
import io.newgrounds.objects.events.Outcome;
|
||||
import io.newgrounds.utils.ScoreBoardList;
|
||||
|
||||
@:nullSafety
|
||||
class Leaderboards
|
||||
{
|
||||
public static function listLeaderboardData():Map<Leaderboard, LeaderboardData>
|
||||
|
@ -16,21 +20,8 @@ class Leaderboards
|
|||
trace('[NEWGROUNDS] Not logged in, cannot fetch medal data!');
|
||||
return [];
|
||||
}
|
||||
else
|
||||
{
|
||||
var result:Map<Leaderboard, LeaderboardData> = [];
|
||||
|
||||
for (leaderboardId in leaderboardList.keys())
|
||||
{
|
||||
var leaderboardData = leaderboardList.get(leaderboardId);
|
||||
if (leaderboardData == null) continue;
|
||||
|
||||
// A little hacky, but it works.
|
||||
result.set(cast leaderboardId, leaderboardData);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
return @:privateAccess leaderboardList._map?.copy() ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -66,6 +57,41 @@ class Leaderboards
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request to receive scores from Newgrounds.
|
||||
* @param leaderboard The leaderboard to fetch scores from.
|
||||
* @param params Additional parameters for fetching the score.
|
||||
*/
|
||||
public static function requestScores(leaderboard:Leaderboard, ?params:RequestScoresParams)
|
||||
{
|
||||
// Silently reject retrieving scores from unknown leaderboards.
|
||||
if (leaderboard == Leaderboard.Unknown) return;
|
||||
|
||||
var leaderboardList = NewgroundsClient.instance.leaderboards;
|
||||
if (leaderboardList == null) return;
|
||||
|
||||
var leaderboardData:Null<LeaderboardData> = leaderboardList.get(leaderboard.getId());
|
||||
if (leaderboardData == null) return;
|
||||
|
||||
var user:Null<User> = null;
|
||||
if ((params?.useCurrentUser ?? false) && NewgroundsClient.instance.isLoggedIn()) user = NewgroundsClient.instance.user;
|
||||
|
||||
leaderboardData.requestScores(params?.limit ?? 10, params?.skip ?? 0, params?.period ?? ALL, params?.social ?? false, params?.tag, user,
|
||||
function(outcome:Outcome<CallError>):Void {
|
||||
switch (outcome)
|
||||
{
|
||||
case SUCCESS:
|
||||
trace('[NEWGROUNDS] Fetched scores!');
|
||||
if (params != null && params.onComplete != null) params.onComplete(leaderboardData.scores);
|
||||
|
||||
case FAIL(error):
|
||||
trace('[NEWGROUNDS] Failed to fetch scores!');
|
||||
trace(error);
|
||||
if (params != null && params.onFail != null) params.onFail();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit a score for a Story Level to Newgrounds.
|
||||
*/
|
||||
|
@ -84,9 +110,77 @@ class Leaderboards
|
|||
Leaderboards.submitScore(Leaderboard.getLeaderboardBySong(songId, difficultyId), score, tag);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for `Leaderboards` that prevents submitting scores.
|
||||
*/
|
||||
@:nullSafety
|
||||
class LeaderboardsSandboxed
|
||||
{
|
||||
public static function getLeaderboardBySong(songId:String, difficultyId:String)
|
||||
{
|
||||
return Leaderboard.getLeaderboardBySong(songId, difficultyId);
|
||||
}
|
||||
|
||||
public static function getLeaderboardByLevel(levelId:String)
|
||||
{
|
||||
return Leaderboard.getLeaderboardByLevel(levelId);
|
||||
}
|
||||
|
||||
public function requestScores(leaderboard:Leaderboard, params:RequestScoresParams)
|
||||
{
|
||||
Leaderboards.requestScores(leaderboard, params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional parameters for `Leaderboards.requestScores()`
|
||||
*/
|
||||
typedef RequestScoresParams =
|
||||
{
|
||||
/**
|
||||
* How many scores to include in a list.
|
||||
* @default `10`
|
||||
*/
|
||||
var ?limit:Int;
|
||||
|
||||
/**
|
||||
* How many scores to skip before starting the list.
|
||||
* @default `0`
|
||||
*/
|
||||
var ?skip:Int;
|
||||
|
||||
/**
|
||||
* The time-frame to pull the scores from.
|
||||
* @default `Period.ALL`
|
||||
*/
|
||||
var ?period:Period;
|
||||
|
||||
/**
|
||||
* If true, only scores by the user and their friends will be loaded. Ignored if no user is set.
|
||||
* @default `false`
|
||||
*/
|
||||
var ?social:Bool;
|
||||
|
||||
/**
|
||||
* An optional tag to filter the results by.
|
||||
* @default `null`
|
||||
*/
|
||||
var ?tag:String;
|
||||
|
||||
/**
|
||||
* If true, only the scores from the currently logged in user will be loaded.
|
||||
* Additionally, if `social` is set to true, the scores of the user's friend will be loaded.
|
||||
* @default `false`
|
||||
*/
|
||||
var ?useCurrentUser:Bool;
|
||||
|
||||
var ?onComplete:Array<Score>->Void;
|
||||
var ?onFail:Void->Void;
|
||||
}
|
||||
#end
|
||||
|
||||
enum abstract Leaderboard(Int)
|
||||
enum abstract Leaderboard(Int) from Int to Int
|
||||
{
|
||||
/**
|
||||
* Represents an undefined or invalid leaderboard.
|
||||
|
@ -285,7 +379,7 @@ enum abstract Leaderboard(Int)
|
|||
{
|
||||
case "darnell":
|
||||
return DarnellBFMix;
|
||||
case "litup":
|
||||
case "lit-up":
|
||||
return LitUpBFMix;
|
||||
default:
|
||||
return Unknown;
|
||||
|
@ -379,7 +473,7 @@ enum abstract Leaderboard(Int)
|
|||
return Stress;
|
||||
case "darnell":
|
||||
return Darnell;
|
||||
case "litup":
|
||||
case "lit-up":
|
||||
return LitUp;
|
||||
case "2hot":
|
||||
return TwoHot;
|
||||
|
|
|
@ -8,6 +8,7 @@ import openfl.display.BitmapData;
|
|||
import io.newgrounds.utils.MedalList;
|
||||
import haxe.Json;
|
||||
|
||||
@:nullSafety
|
||||
class Medals
|
||||
{
|
||||
public static var medalJSON:Array<MedalJSON> = [];
|
||||
|
@ -21,22 +22,8 @@ class Medals
|
|||
trace('[NEWGROUNDS] Not logged in, cannot fetch medal data!');
|
||||
return [];
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Why do I have to do this, @:nullSafety is fucked up
|
||||
var result:Map<Medal, MedalData> = [];
|
||||
|
||||
for (medalId in medalList.keys())
|
||||
{
|
||||
var medalData = medalList.get(medalId);
|
||||
if (medalData == null) continue;
|
||||
|
||||
// A little hacky, but it works.
|
||||
result.set(cast medalId, medalData);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
return @:privateAccess medalList._map?.copy() ?? [];
|
||||
}
|
||||
|
||||
public static function award(medal:Medal):Void
|
||||
|
@ -131,32 +118,78 @@ class Medals
|
|||
}
|
||||
}
|
||||
|
||||
public static function awardStoryLevel(id:String):Void
|
||||
public static function fetchMedalData(medal:Medal):Null<FetchedMedalData>
|
||||
{
|
||||
switch (id)
|
||||
var medalList = NewgroundsClient.instance.medals;
|
||||
@:privateAccess
|
||||
if (medalList == null || medalList._map == null) return null;
|
||||
|
||||
var medalData:Null<MedalData> = medalList.get(medal.getId());
|
||||
@:privateAccess
|
||||
if (medalData == null || medalData._data == null)
|
||||
{
|
||||
case 'tutorial':
|
||||
Medals.award(Medal.StoryTutorial);
|
||||
case 'week1':
|
||||
Medals.award(Medal.StoryWeek1);
|
||||
case 'week2':
|
||||
Medals.award(Medal.StoryWeek2);
|
||||
case 'week3':
|
||||
Medals.award(Medal.StoryWeek3);
|
||||
case 'week4':
|
||||
Medals.award(Medal.StoryWeek4);
|
||||
case 'week5':
|
||||
Medals.award(Medal.StoryWeek5);
|
||||
case 'week6':
|
||||
Medals.award(Medal.StoryWeek6);
|
||||
case 'week7':
|
||||
Medals.award(Medal.StoryWeek7);
|
||||
case 'weekend1':
|
||||
Medals.award(Medal.StoryWeekend1);
|
||||
default:
|
||||
trace('[NEWGROUNDS] Story level does not have a medal! (${id}).');
|
||||
trace('[NEWGROUNDS] Could not retrieve data for medal: ${medal}');
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: medalData.id,
|
||||
name: medalData.name,
|
||||
description: medalData.description,
|
||||
icon: medalData.icon,
|
||||
value: medalData.value,
|
||||
difficulty: medalData.difficulty,
|
||||
secret: medalData.secret,
|
||||
unlocked: medalData.unlocked
|
||||
}
|
||||
}
|
||||
|
||||
public static function awardStoryLevel(id:String):Void
|
||||
{
|
||||
var medal:Medal = Medal.getMedalByStoryLevel(id);
|
||||
if (medal == Medal.Unknown)
|
||||
{
|
||||
trace('[NEWGROUNDS] Story level does not have a medal! (${id}).');
|
||||
return;
|
||||
}
|
||||
Medals.award(medal);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for `Medals` that prevents awarding medals.
|
||||
*/
|
||||
class MedalsSandboxed
|
||||
{
|
||||
public static function fetchMedalData(medal:Medal):Null<FetchedMedalData>
|
||||
{
|
||||
return Medals.fetchMedalData(medal);
|
||||
}
|
||||
|
||||
public static function getMedalByStoryLevel(id:String):Medal
|
||||
{
|
||||
return Medal.getMedalByStoryLevel(id);
|
||||
}
|
||||
|
||||
public static function getAllMedals():Array<Medal>
|
||||
{
|
||||
return Medal.getAllMedals();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains data for a Medal, but excludes functions like `sendUnlock()`.
|
||||
*/
|
||||
typedef FetchedMedalData =
|
||||
{
|
||||
var id:Int;
|
||||
var name:String;
|
||||
var description:String;
|
||||
var icon:String;
|
||||
var value:Int;
|
||||
var difficulty:Int;
|
||||
var secret:Bool;
|
||||
var unlocked:Bool;
|
||||
}
|
||||
#end
|
||||
|
||||
|
@ -324,6 +357,8 @@ enum abstract Medal(Int) from Int to Int
|
|||
{
|
||||
switch (levelId)
|
||||
{
|
||||
case "tutorial":
|
||||
return StoryTutorial;
|
||||
case "week1":
|
||||
return StoryWeek1;
|
||||
case "week2":
|
||||
|
@ -344,4 +379,33 @@ enum abstract Medal(Int) from Int to Int
|
|||
return Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all medals aside from the `Unknown` one.
|
||||
*/
|
||||
public static function getAllMedals()
|
||||
{
|
||||
return [
|
||||
StartGame,
|
||||
StoryTutorial,
|
||||
StoryWeek1,
|
||||
StoryWeek2,
|
||||
StoryWeek3,
|
||||
StoryWeek4,
|
||||
StoryWeek5,
|
||||
StoryWeek6,
|
||||
StoryWeek7,
|
||||
StoryWeekend1,
|
||||
CharSelect,
|
||||
FreeplayPicoMix,
|
||||
FreeplayStressPico,
|
||||
LossRating,
|
||||
PerfectRatingHard,
|
||||
GoldPerfectRatingHard,
|
||||
ErectDifficulty,
|
||||
GoldPerfectRatingNightmare,
|
||||
FridayNight,
|
||||
Nice
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
156
source/funkin/api/newgrounds/NGSaveSlot.hx
Normal file
156
source/funkin/api/newgrounds/NGSaveSlot.hx
Normal file
|
@ -0,0 +1,156 @@
|
|||
package funkin.api.newgrounds;
|
||||
|
||||
#if FEATURE_NEWGROUNDS
|
||||
import io.newgrounds.utils.SaveSlotList;
|
||||
import io.newgrounds.objects.SaveSlot;
|
||||
import io.newgrounds.Call.CallError;
|
||||
import io.newgrounds.objects.events.Outcome;
|
||||
import funkin.save.Save;
|
||||
|
||||
@:nullSafety
|
||||
@:access(funkin.save.Save)
|
||||
class NGSaveSlot
|
||||
{
|
||||
public static var instance(get, never):NGSaveSlot;
|
||||
static var _instance:Null<NGSaveSlot> = null;
|
||||
|
||||
static function get_instance():NGSaveSlot
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
return loadInstance();
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
|
||||
public static function loadInstance():NGSaveSlot
|
||||
{
|
||||
var loadedSave:NGSaveSlot = loadSlot(Save.BASE_SAVE_SLOT);
|
||||
if (_instance == null) _instance = loadedSave;
|
||||
|
||||
return loadedSave;
|
||||
}
|
||||
|
||||
static function loadSlot(slot:Int):NGSaveSlot
|
||||
{
|
||||
trace('[NEWGROUNDS] Getting save slot from ID $slot');
|
||||
|
||||
var saveSlot:Null<SaveSlot> = NewgroundsClient.instance.saveSlots?.getById(slot);
|
||||
|
||||
var saveSlotObj:NGSaveSlot = new NGSaveSlot(saveSlot);
|
||||
return saveSlotObj;
|
||||
}
|
||||
|
||||
public var ngSaveSlot:Null<SaveSlot> = null;
|
||||
|
||||
public function new(?ngSaveSlot:Null<SaveSlot>)
|
||||
{
|
||||
this.ngSaveSlot = ngSaveSlot;
|
||||
|
||||
#if FLX_DEBUG
|
||||
FlxG.console.registerClass(NGSaveSlot);
|
||||
FlxG.console.registerClass(Save);
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves `data` to the newgrounds save slot.
|
||||
* @param data The raw save data.
|
||||
*/
|
||||
public function save(data:RawSaveData):Void
|
||||
{
|
||||
var encodedData:String = haxe.Serializer.run(data);
|
||||
|
||||
try
|
||||
{
|
||||
ngSaveSlot?.save(encodedData, function(outcome:Outcome<CallError>) {
|
||||
switch (outcome)
|
||||
{
|
||||
case SUCCESS:
|
||||
trace('[NEWGROUNDS] Successfully saved save data to save slot!');
|
||||
case FAIL(error):
|
||||
trace('[NEWGROUNDS] Failed to save data to save slot!');
|
||||
trace(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (error:String)
|
||||
{
|
||||
trace('[NEWGROUNDS] Failed to save data to save slot!');
|
||||
trace(error);
|
||||
}
|
||||
}
|
||||
|
||||
public function load(?onComplete:Null<Dynamic->Void>, ?onError:Null<CallError->Void>):Void
|
||||
{
|
||||
try
|
||||
{
|
||||
ngSaveSlot?.load(function(outcome:SaveSlotOutcome):Void {
|
||||
switch (outcome)
|
||||
{
|
||||
case SUCCESS(value):
|
||||
trace('[NEWGROUNDS] Loaded save slot with the ID of ${ngSaveSlot?.id}!');
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
trace('Save Slot Data:');
|
||||
trace(value);
|
||||
#end
|
||||
|
||||
if (onComplete != null && value != null)
|
||||
{
|
||||
var decodedData:Dynamic = haxe.Unserializer.run(value);
|
||||
onComplete(decodedData);
|
||||
}
|
||||
case FAIL(error):
|
||||
trace('[NEWGROUNDS] Failed to load save slot with the ID of ${ngSaveSlot?.id}!');
|
||||
trace(error);
|
||||
|
||||
if (onError != null)
|
||||
{
|
||||
onError(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (error:String)
|
||||
{
|
||||
trace('[NEWGROUNDS] Failed to load save slot with the ID of ${ngSaveSlot?.id}!');
|
||||
trace(error);
|
||||
|
||||
if (onError != null)
|
||||
{
|
||||
onError(RESPONSE({message: error, code: 500}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function clear():Void
|
||||
{
|
||||
try
|
||||
{
|
||||
ngSaveSlot?.clear(function(outcome:Outcome<CallError>) {
|
||||
switch (outcome)
|
||||
{
|
||||
case SUCCESS:
|
||||
trace('[NEWGROUNDS] Successfully cleared save slot!');
|
||||
case FAIL(error):
|
||||
trace('[NEWGROUNDS] Failed to clear save slot!');
|
||||
trace(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (error:String)
|
||||
{
|
||||
trace('[NEWGROUNDS] Failed to clear save slot!');
|
||||
trace(error);
|
||||
}
|
||||
}
|
||||
|
||||
public function checkSlot():Void
|
||||
{
|
||||
trace('[NEWGROUNDS] Checking save slot with the ID of ${ngSaveSlot?.id}...');
|
||||
|
||||
trace(' Is null? ${ngSaveSlot == null}');
|
||||
trace(' Is empty? ${ngSaveSlot?.isEmpty() ?? false}');
|
||||
}
|
||||
}
|
||||
#end
|
|
@ -1,5 +1,6 @@
|
|||
package funkin.api.newgrounds;
|
||||
|
||||
import funkin.util.macro.EnvironmentConfigMacro;
|
||||
import funkin.save.Save;
|
||||
import funkin.api.newgrounds.Medals.Medal;
|
||||
#if FEATURE_NEWGROUNDS
|
||||
|
@ -10,13 +11,18 @@ import io.newgrounds.NGLite.LoginOutcome;
|
|||
import io.newgrounds.NGLite.LoginFail;
|
||||
import io.newgrounds.objects.events.Outcome;
|
||||
import io.newgrounds.utils.MedalList;
|
||||
import io.newgrounds.utils.SaveSlotList;
|
||||
import io.newgrounds.utils.ScoreBoardList;
|
||||
import io.newgrounds.objects.User;
|
||||
|
||||
@:nullSafety
|
||||
class NewgroundsClient
|
||||
{
|
||||
static final APP_ID:Null<String> = EnvironmentConfigMacro.environmentConfig?.get("API_NG_APP_ID");
|
||||
static final ENCRYPTION_KEY:Null<String> = EnvironmentConfigMacro.environmentConfig?.get("API_NG_ENC_KEY");
|
||||
|
||||
public static var instance(get, never):NewgroundsClient;
|
||||
|
||||
static var _instance:Null<NewgroundsClient> = null;
|
||||
|
||||
static function get_instance():NewgroundsClient
|
||||
|
@ -29,14 +35,15 @@ class NewgroundsClient
|
|||
public var user(get, never):Null<User>;
|
||||
public var medals(get, never):Null<MedalList>;
|
||||
public var leaderboards(get, never):Null<ScoreBoardList>;
|
||||
public var saveSlots(get, never):Null<SaveSlotList>;
|
||||
|
||||
private function new()
|
||||
{
|
||||
trace('[NEWGROUNDS] Initializing client...');
|
||||
|
||||
#if FEATURE_NEWGROUNDS_DEBUG
|
||||
trace('[NEWGROUNDS] App ID: ${NewgroundsCredentials.APP_ID}');
|
||||
trace('[NEWGROUNDS] Encryption Key: ${NewgroundsCredentials.ENCRYPTION_KEY}');
|
||||
trace('[NEWGROUNDS] App ID: ${APP_ID}');
|
||||
trace('[NEWGROUNDS] Encryption Key: ${ENCRYPTION_KEY}');
|
||||
#end
|
||||
|
||||
if (!hasValidCredentials())
|
||||
|
@ -45,9 +52,12 @@ class NewgroundsClient
|
|||
return;
|
||||
}
|
||||
|
||||
var debug = #if FEATURE_NEWGROUNDS_DEBUG true #else false #end;
|
||||
NG.create(NewgroundsCredentials.APP_ID, getSessionId(), debug, onLoginResolved);
|
||||
NG.core.setupEncryption(NewgroundsCredentials.ENCRYPTION_KEY);
|
||||
@:nullSafety(Off)
|
||||
{
|
||||
NG.create(APP_ID, getSessionId(), #if FEATURE_NEWGROUNDS_DEBUG true #else false #end, onLoginResolved);
|
||||
|
||||
NG.core.setupEncryption(ENCRYPTION_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
public function init()
|
||||
|
@ -166,12 +176,12 @@ class NewgroundsClient
|
|||
*/
|
||||
static function hasValidCredentials():Bool
|
||||
{
|
||||
return !(NewgroundsCredentials.APP_ID == null
|
||||
|| NewgroundsCredentials.APP_ID == ""
|
||||
|| NewgroundsCredentials.APP_ID.contains(" ")
|
||||
|| NewgroundsCredentials.ENCRYPTION_KEY == null
|
||||
|| NewgroundsCredentials.ENCRYPTION_KEY == ""
|
||||
|| NewgroundsCredentials.ENCRYPTION_KEY.contains(" "));
|
||||
return !(APP_ID == null
|
||||
|| APP_ID == ""
|
||||
|| (APP_ID != null && APP_ID.contains(" "))
|
||||
|| ENCRYPTION_KEY == null
|
||||
|| ENCRYPTION_KEY == ""
|
||||
|| (ENCRYPTION_KEY != null && ENCRYPTION_KEY.contains(" ")));
|
||||
}
|
||||
|
||||
function onLoginResolved(outcome:LoginOutcome):Void
|
||||
|
@ -236,6 +246,8 @@ class NewgroundsClient
|
|||
|
||||
trace('[NEWGROUNDS] Submitting leaderboard request...');
|
||||
NG.core.scoreBoards.loadList(onFetchedLeaderboards);
|
||||
trace('[NEWGROUNDS] Submitting save slot request...');
|
||||
NG.core.saveSlots.loadList(onFetchedSaveSlots);
|
||||
}
|
||||
|
||||
function onLoginFailed(result:LoginFail):Void
|
||||
|
@ -301,6 +313,13 @@ class NewgroundsClient
|
|||
// trace(funkin.api.newgrounds.Leaderboards.listLeaderboardData());
|
||||
}
|
||||
|
||||
function onFetchedSaveSlots(outcome:Outcome<CallError>):Void
|
||||
{
|
||||
trace('[NEWGROUNDS] Fetched save slots!');
|
||||
|
||||
NGSaveSlot.instance.checkSlot();
|
||||
}
|
||||
|
||||
function get_user():Null<User>
|
||||
{
|
||||
if (NG.core == null || !this.isLoggedIn()) return null;
|
||||
|
@ -319,6 +338,12 @@ class NewgroundsClient
|
|||
return NG.core.scoreBoards;
|
||||
}
|
||||
|
||||
function get_saveSlots():Null<SaveSlotList>
|
||||
{
|
||||
if (NG.core == null || !this.isLoggedIn()) return null;
|
||||
return NG.core.saveSlots;
|
||||
}
|
||||
|
||||
static function getSessionId():Null<String>
|
||||
{
|
||||
#if js
|
||||
|
@ -331,4 +356,22 @@ class NewgroundsClient
|
|||
return Save.instance.ngSessionId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for `NewgroundsClient` that prevents submitting cheated data.
|
||||
*/
|
||||
class NewgroundsClientSandboxed
|
||||
{
|
||||
public static var user(get, never):Null<User>;
|
||||
|
||||
static function get_user()
|
||||
{
|
||||
return NewgroundsClient.instance.user;
|
||||
}
|
||||
|
||||
public static function isLoggedIn()
|
||||
{
|
||||
return NewgroundsClient.instance.isLoggedIn();
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
|
|
@ -11,6 +11,7 @@ import openfl.utils.AssetType;
|
|||
/**
|
||||
* a FlxSound that just overrides loadEmbedded to allow for "streamed" sounds to load with better performance!
|
||||
*/
|
||||
@:nullSafety
|
||||
class FlxStreamSound extends FlxSound
|
||||
{
|
||||
public function new()
|
||||
|
@ -18,7 +19,7 @@ class FlxStreamSound extends FlxSound
|
|||
super();
|
||||
}
|
||||
|
||||
override public function loadEmbedded(EmbeddedSound:FlxSoundAsset, Looped:Bool = false, AutoDestroy:Bool = false, ?OnComplete:Void->Void):FlxSound
|
||||
override public function loadEmbedded(EmbeddedSound:Null<FlxSoundAsset>, Looped:Bool = false, AutoDestroy:Bool = false, ?OnComplete:Void->Void):FlxSound
|
||||
{
|
||||
if (EmbeddedSound == null) return this;
|
||||
|
||||
|
|
|
@ -20,9 +20,6 @@ import openfl.media.Sound;
|
|||
import openfl.media.SoundChannel;
|
||||
import openfl.media.SoundMixer;
|
||||
|
||||
#if (openfl >= "8.0.0")
|
||||
#end
|
||||
|
||||
/**
|
||||
* A FlxSound which adds additional functionality:
|
||||
* - Delayed playback via negative song position.
|
||||
|
@ -45,9 +42,9 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
|||
if (_onVolumeChanged == null)
|
||||
{
|
||||
_onVolumeChanged = new FlxTypedSignal<Float->Void>();
|
||||
FlxG.sound.volumeHandler = function(volume:Float) {
|
||||
FlxG.sound.onVolumeChange.add(function(volume:Float) {
|
||||
_onVolumeChanged.dispatch(volume);
|
||||
}
|
||||
});
|
||||
}
|
||||
return _onVolumeChanged;
|
||||
}
|
||||
|
@ -478,7 +475,7 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
|||
|
||||
if (autoPlay) sound.play();
|
||||
sound.volume = volume;
|
||||
sound.group = FlxG.sound.defaultSoundGroup;
|
||||
FlxG.sound.defaultSoundGroup.add(sound);
|
||||
sound.persist = persist;
|
||||
sound.important = important;
|
||||
|
||||
|
@ -551,6 +548,7 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
|||
}
|
||||
FlxTween.cancelTweensOf(this);
|
||||
this._label = 'unknown';
|
||||
this._waveformData = null;
|
||||
}
|
||||
|
||||
@:access(openfl.media.Sound)
|
||||
|
|
|
@ -7,6 +7,7 @@ import flixel.tweens.FlxTween;
|
|||
* A group of FunkinSounds that are all synced together.
|
||||
* Unlike FlxSoundGroup, you can also control their time and pitch.
|
||||
*/
|
||||
@:nullSafety
|
||||
class SoundGroup extends FlxTypedGroup<FunkinSound>
|
||||
{
|
||||
public var time(get, set):Float;
|
||||
|
@ -36,6 +37,7 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
|
|||
return result;
|
||||
}
|
||||
|
||||
@:nullSafety(Off)
|
||||
for (sndFile in files)
|
||||
{
|
||||
var snd:FunkinSound = FunkinSound.load(Paths.voices(song, '$sndFile'));
|
||||
|
@ -70,7 +72,7 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
|
|||
/**
|
||||
* Add a sound to the group.
|
||||
*/
|
||||
public override function add(sound:FunkinSound):FunkinSound
|
||||
public override function add(sound:FunkinSound):Null<FunkinSound>
|
||||
{
|
||||
var result:FunkinSound = super.add(sound);
|
||||
|
||||
|
@ -134,6 +136,7 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
|
|||
/**
|
||||
* Fade in all the sounds in the group.
|
||||
*/
|
||||
@:nullSafety(Off)
|
||||
public function fadeIn(duration:Float, ?from:Float = 0.0, ?to:Float = 1.0, ?onComplete:FlxTween->Void):Void
|
||||
{
|
||||
forEachAlive(function(sound:FunkinSound) {
|
||||
|
@ -144,6 +147,7 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
|
|||
/**
|
||||
* Fade out all the sounds in the group.
|
||||
*/
|
||||
@:nullSafety(Off)
|
||||
public function fadeOut(duration:Float, ?to:Float = 0.0, ?onComplete:FlxTween->Void):Void
|
||||
{
|
||||
forEachAlive(function(sound:FunkinSound) {
|
||||
|
@ -238,7 +242,7 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
|
|||
|
||||
function get_muted():Bool
|
||||
{
|
||||
if (getFirstAlive() != null) return getFirstAlive().muted;
|
||||
if (getFirstAlive() != null) return getFirstAlive()?.muted ?? false;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -3,10 +3,11 @@ package funkin.audio;
|
|||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import funkin.audio.waveform.WaveformData;
|
||||
|
||||
@:nullSafety
|
||||
class VoicesGroup extends SoundGroup
|
||||
{
|
||||
var playerVoices:FlxTypedGroup<FunkinSound>;
|
||||
var opponentVoices:FlxTypedGroup<FunkinSound>;
|
||||
var playerVoices:Null<FlxTypedGroup<FunkinSound>>;
|
||||
var opponentVoices:Null<FlxTypedGroup<FunkinSound>>;
|
||||
|
||||
/**
|
||||
* Control the volume of only the sounds in the player group.
|
||||
|
@ -41,12 +42,12 @@ class VoicesGroup extends SoundGroup
|
|||
public function addPlayerVoice(sound:FunkinSound):Void
|
||||
{
|
||||
super.add(sound);
|
||||
playerVoices.add(sound);
|
||||
playerVoices?.add(sound);
|
||||
}
|
||||
|
||||
function set_playerVolume(volume:Float):Float
|
||||
{
|
||||
playerVoices.forEachAlive(function(voice:FunkinSound) {
|
||||
playerVoices?.forEachAlive(function(voice:FunkinSound) {
|
||||
voice.volume = volume;
|
||||
});
|
||||
return playerVolume = volume;
|
||||
|
@ -59,10 +60,10 @@ class VoicesGroup extends SoundGroup
|
|||
snd.time = time;
|
||||
});
|
||||
|
||||
playerVoices.forEachAlive(function(voice:FunkinSound) {
|
||||
playerVoices?.forEachAlive(function(voice:FunkinSound) {
|
||||
voice.time -= playerVoicesOffset;
|
||||
});
|
||||
opponentVoices.forEachAlive(function(voice:FunkinSound) {
|
||||
opponentVoices?.forEachAlive(function(voice:FunkinSound) {
|
||||
voice.time -= opponentVoicesOffset;
|
||||
});
|
||||
|
||||
|
@ -71,7 +72,7 @@ class VoicesGroup extends SoundGroup
|
|||
|
||||
function set_playerVoicesOffset(offset:Float):Float
|
||||
{
|
||||
playerVoices.forEachAlive(function(voice:FunkinSound) {
|
||||
playerVoices?.forEachAlive(function(voice:FunkinSound) {
|
||||
voice.time += playerVoicesOffset;
|
||||
voice.time -= offset;
|
||||
});
|
||||
|
@ -80,7 +81,7 @@ class VoicesGroup extends SoundGroup
|
|||
|
||||
function set_opponentVoicesOffset(offset:Float):Float
|
||||
{
|
||||
opponentVoices.forEachAlive(function(voice:FunkinSound) {
|
||||
opponentVoices?.forEachAlive(function(voice:FunkinSound) {
|
||||
voice.time += opponentVoicesOffset;
|
||||
voice.time -= offset;
|
||||
});
|
||||
|
@ -93,12 +94,12 @@ class VoicesGroup extends SoundGroup
|
|||
public function addOpponentVoice(sound:FunkinSound):Void
|
||||
{
|
||||
super.add(sound);
|
||||
opponentVoices.add(sound);
|
||||
opponentVoices?.add(sound);
|
||||
}
|
||||
|
||||
function set_opponentVolume(volume:Float):Float
|
||||
{
|
||||
opponentVoices.forEachAlive(function(voice:FunkinSound) {
|
||||
opponentVoices?.forEachAlive(function(voice:FunkinSound) {
|
||||
voice.volume = volume;
|
||||
});
|
||||
return opponentVolume = volume;
|
||||
|
@ -106,26 +107,26 @@ class VoicesGroup extends SoundGroup
|
|||
|
||||
public function getPlayerVoice(index:Int = 0):Null<FunkinSound>
|
||||
{
|
||||
return playerVoices.members[index];
|
||||
return playerVoices?.members[index];
|
||||
}
|
||||
|
||||
public function getOpponentVoice(index:Int = 0):Null<FunkinSound>
|
||||
{
|
||||
return opponentVoices.members[index];
|
||||
return opponentVoices?.members[index];
|
||||
}
|
||||
|
||||
public function getPlayerVoiceWaveform():Null<WaveformData>
|
||||
{
|
||||
if (playerVoices.members.length == 0) return null;
|
||||
if (playerVoices?.members.length == 0) return null;
|
||||
|
||||
return playerVoices.members[0].waveformData;
|
||||
return playerVoices?.members[0].waveformData;
|
||||
}
|
||||
|
||||
public function getOpponentVoiceWaveform():Null<WaveformData>
|
||||
{
|
||||
if (opponentVoices.members.length == 0) return null;
|
||||
if (opponentVoices?.members.length == 0) return null;
|
||||
|
||||
return opponentVoices.members[0].waveformData;
|
||||
return opponentVoices?.members[0].waveformData;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -133,9 +134,9 @@ class VoicesGroup extends SoundGroup
|
|||
*/
|
||||
public function getPlayerVoiceLength():Float
|
||||
{
|
||||
if (playerVoices.members.length == 0) return 0.0;
|
||||
if (playerVoices?.members.length == 0) return 0.0;
|
||||
|
||||
return playerVoices.members[0].length;
|
||||
return playerVoices?.members[0]?.length ?? 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -143,15 +144,15 @@ class VoicesGroup extends SoundGroup
|
|||
*/
|
||||
public function getOpponentVoiceLength():Float
|
||||
{
|
||||
if (opponentVoices.members.length == 0) return 0.0;
|
||||
if (opponentVoices?.members.length == 0) return 0.0;
|
||||
|
||||
return opponentVoices.members[0].length;
|
||||
return opponentVoices?.members[0]?.length ?? 0.0;
|
||||
}
|
||||
|
||||
public override function clear():Void
|
||||
{
|
||||
playerVoices.clear();
|
||||
opponentVoices.clear();
|
||||
playerVoices?.clear();
|
||||
opponentVoices?.clear();
|
||||
super.clear();
|
||||
}
|
||||
|
||||
|
@ -159,13 +160,13 @@ class VoicesGroup extends SoundGroup
|
|||
{
|
||||
if (playerVoices != null)
|
||||
{
|
||||
playerVoices.destroy();
|
||||
playerVoices?.destroy();
|
||||
playerVoices = null;
|
||||
}
|
||||
|
||||
if (opponentVoices != null)
|
||||
{
|
||||
opponentVoices.destroy();
|
||||
opponentVoices?.destroy();
|
||||
opponentVoices = null;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package funkin.audio.visualize;
|
|||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
||||
|
||||
@:nullSafety
|
||||
class ABot extends FlxTypedSpriteGroup<FlxSprite>
|
||||
{
|
||||
public function new()
|
||||
|
|
|
@ -72,8 +72,8 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
|||
// we use a very low minFreq since some songs use low low subbass like a boss
|
||||
analyzer.minFreq = 10;
|
||||
|
||||
#if desktop
|
||||
// On desktop it uses FFT stuff that isn't as optimized as the direct browser stuff we use on HTML5
|
||||
#if sys
|
||||
// On native it uses FFT stuff that isn't as optimized as the direct browser stuff we use on HTML5
|
||||
// So we want to manually change it!
|
||||
analyzer.fftN = 256;
|
||||
#end
|
||||
|
@ -116,7 +116,7 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
|||
|
||||
for (i in 0...min(group.members.length, levels.length))
|
||||
{
|
||||
var animFrame:Int = Math.round(levels[i].value * 6);
|
||||
var animFrame:Int = (FlxG.sound.volume == 0 || FlxG.sound.muted) ? 0 : Math.round(levels[i].value * 6);
|
||||
|
||||
// don't display if we're at 0 volume from the level
|
||||
group.members[i].visible = animFrame > 0;
|
||||
|
|
|
@ -3,11 +3,12 @@ package funkin.audio.visualize;
|
|||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import flixel.sound.FlxSound;
|
||||
|
||||
@:nullSafety
|
||||
class PolygonVisGroup extends FlxTypedGroup<PolygonSpectogram>
|
||||
{
|
||||
public var playerVis:PolygonSpectogram;
|
||||
public var opponentVis:PolygonSpectogram;
|
||||
public var instVis:PolygonSpectogram;
|
||||
public var playerVis:Null<PolygonSpectogram>;
|
||||
public var opponentVis:Null<PolygonSpectogram>;
|
||||
public var instVis:Null<PolygonSpectogram>;
|
||||
|
||||
public function new()
|
||||
{
|
||||
|
@ -99,8 +100,14 @@ class PolygonVisGroup extends FlxTypedGroup<PolygonSpectogram>
|
|||
|
||||
public override function destroy():Void
|
||||
{
|
||||
playerVis.destroy();
|
||||
opponentVis.destroy();
|
||||
if (playerVis != null)
|
||||
{
|
||||
playerVis.destroy();
|
||||
}
|
||||
if (opponentVis != null)
|
||||
{
|
||||
opponentVis.destroy();
|
||||
}
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ package funkin.audio.visualize.dsp;
|
|||
Complex number representation.
|
||||
**/
|
||||
@:forward(real, imag) @:notNull @:pure
|
||||
@:nullSafety
|
||||
abstract Complex({
|
||||
final real:Float;
|
||||
final imag:Float;
|
||||
|
|
|
@ -8,6 +8,7 @@ using funkin.audio.visualize.dsp.Signal;
|
|||
/**
|
||||
Fast/Finite Fourier Transforms.
|
||||
**/
|
||||
@:nullSafety
|
||||
class FFT
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -6,6 +6,7 @@ package funkin.audio.visualize.dsp;
|
|||
Usages include 1-indexed sequences or zero-centered buffers with negative indexing.
|
||||
**/
|
||||
@:forward(array, offset)
|
||||
@:nullSafety
|
||||
abstract OffsetArray<T>({
|
||||
final array:Array<T>;
|
||||
final offset:Int;
|
||||
|
|
|
@ -5,12 +5,13 @@ using Lambda;
|
|||
/**
|
||||
Signal processing miscellaneous utilities.
|
||||
**/
|
||||
@:nullSafety
|
||||
class Signal
|
||||
{
|
||||
/**
|
||||
Returns a smoothed version of the input array using a moving average.
|
||||
**/
|
||||
public static function smooth(y:Array<Float>, n:Int):Array<Float>
|
||||
public static function smooth(y:Array<Float>, n:Int):Null<Array<Float>>
|
||||
{
|
||||
if (n <= 0)
|
||||
{
|
||||
|
|
|
@ -243,9 +243,10 @@ class WaveformDataChannel
|
|||
}
|
||||
|
||||
/**
|
||||
* Retrieve a given minimum point at an index.
|
||||
* @param i Index
|
||||
* @return minimum point at an index.
|
||||
*/
|
||||
public function minSample(i:Int)
|
||||
public function minSample(i:Int):Int
|
||||
{
|
||||
var offset = (i * parent.channels + this.channelId) * 2;
|
||||
return inline parent.get(offset);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package funkin.audio.waveform;
|
||||
|
||||
import funkin.util.TimerUtil;
|
||||
|
||||
@:nullSafety
|
||||
class WaveformDataParser
|
||||
{
|
||||
static final INT16_MAX:Int = 32767;
|
||||
|
@ -10,7 +9,7 @@ class WaveformDataParser
|
|||
static final INT8_MAX:Int = 127;
|
||||
static final INT8_MIN:Int = -128;
|
||||
|
||||
public static function interpretFlxSound(sound:flixel.sound.FlxSound):Null<WaveformData>
|
||||
public static function interpretFlxSound(sound:Null<flixel.sound.FlxSound>):Null<WaveformData>
|
||||
{
|
||||
if (sound == null) return null;
|
||||
|
||||
|
@ -44,73 +43,58 @@ class WaveformDataParser
|
|||
|
||||
public static function interpretAudioBuffer(soundBuffer:lime.media.AudioBuffer):Null<WaveformData>
|
||||
{
|
||||
var sampleRate = soundBuffer.sampleRate;
|
||||
var channels = soundBuffer.channels;
|
||||
var bitsPerSample = soundBuffer.bitsPerSample;
|
||||
var samplesPerPoint:Int = 256; // I don't think we need to configure this.
|
||||
var pointsPerSecond:Float = sampleRate / samplesPerPoint; // 172 samples per second for most songs is plenty precise while still being performant..
|
||||
|
||||
// TODO: Make this work better on HTML5.
|
||||
var soundData:lime.utils.Int16Array = cast soundBuffer.data;
|
||||
|
||||
var soundDataRawLength:Int = soundData.length;
|
||||
var soundDataSampleCount:Int = Std.int(Math.ceil(soundDataRawLength / channels / (bitsPerSample == 16 ? 2 : 1)));
|
||||
var soundDataSampleCount:Int = Std.int(Math.ceil(soundData.length / channels / (bitsPerSample == 16 ? 2 : 1)));
|
||||
var outputPointCount:Int = Std.int(Math.ceil(soundDataSampleCount / samplesPerPoint));
|
||||
|
||||
// trace('Interpreting audio buffer:');
|
||||
// trace(' sampleRate: ${sampleRate}');
|
||||
// trace(' channels: ${channels}');
|
||||
// trace(' bitsPerSample: ${bitsPerSample}');
|
||||
// trace(' samplesPerPoint: ${samplesPerPoint}');
|
||||
// trace(' pointsPerSecond: ${pointsPerSecond}');
|
||||
// trace(' soundDataRawLength: ${soundDataRawLength}');
|
||||
// trace(' soundDataSampleCount: ${soundDataSampleCount}');
|
||||
// trace(' soundDataRawLength/4: ${soundDataRawLength / 4}');
|
||||
// trace(' outputPointCount: ${outputPointCount}');
|
||||
// Pre-allocate Vector with exact final size for better performance and memory efficiency
|
||||
var outputDataLength:Int = outputPointCount * channels * 2;
|
||||
var outputData = new haxe.ds.Vector<Int>(outputDataLength);
|
||||
|
||||
var minSampleValue:Int = bitsPerSample == 16 ? INT16_MIN : INT8_MIN;
|
||||
var maxSampleValue:Int = bitsPerSample == 16 ? INT16_MAX : INT8_MAX;
|
||||
|
||||
var outputData:Array<Int> = [];
|
||||
|
||||
var perfStart:Float = TimerUtil.start();
|
||||
// Reusable min/max tracking arrays to avoid allocation in the loop
|
||||
var minValues = new haxe.ds.Vector<Int>(channels);
|
||||
var maxValues = new haxe.ds.Vector<Int>(channels);
|
||||
|
||||
for (pointIndex in 0...outputPointCount)
|
||||
{
|
||||
// minChannel1, maxChannel1, minChannel2, maxChannel2, ...
|
||||
var values:Array<Int> = [];
|
||||
var rangeStart:Int = pointIndex * samplesPerPoint;
|
||||
var rangeEnd:Int = Std.int(Math.min(rangeStart + samplesPerPoint, soundDataSampleCount));
|
||||
|
||||
// Reset min/max values for this range
|
||||
for (i in 0...channels)
|
||||
{
|
||||
values.push(bitsPerSample == 16 ? INT16_MAX : INT8_MAX);
|
||||
values.push(bitsPerSample == 16 ? INT16_MIN : INT8_MIN);
|
||||
minValues[i] = bitsPerSample == 16 ? INT16_MAX : INT8_MAX;
|
||||
maxValues[i] = bitsPerSample == 16 ? INT16_MIN : INT8_MIN;
|
||||
}
|
||||
|
||||
var rangeStart = pointIndex * samplesPerPoint;
|
||||
var rangeEnd = rangeStart + samplesPerPoint;
|
||||
if (rangeEnd > soundDataSampleCount) rangeEnd = soundDataSampleCount;
|
||||
|
||||
// Process all samples in this range
|
||||
for (sampleIndex in rangeStart...rangeEnd)
|
||||
{
|
||||
for (channelIndex in 0...channels)
|
||||
{
|
||||
var sampleIndex:Int = sampleIndex * channels + channelIndex;
|
||||
var sampleValue = soundData[sampleIndex];
|
||||
var sampleValue:Int = soundData[sampleIndex * channels + channelIndex];
|
||||
|
||||
if (sampleValue < values[channelIndex * 2]) values[(channelIndex * 2)] = sampleValue;
|
||||
if (sampleValue > values[channelIndex * 2 + 1]) values[(channelIndex * 2) + 1] = sampleValue;
|
||||
if (sampleValue < minValues[channelIndex]) minValues[channelIndex] = sampleValue;
|
||||
if (sampleValue > maxValues[channelIndex]) maxValues[channelIndex] = sampleValue;
|
||||
}
|
||||
}
|
||||
|
||||
// We now have the min and max values for the range.
|
||||
for (value in values)
|
||||
outputData.push(value);
|
||||
// Write directly to final positions in output Vector
|
||||
var baseIndex:Int = pointIndex * channels * 2;
|
||||
for (channelIndex in 0...channels)
|
||||
{
|
||||
outputData[baseIndex + channelIndex * 2] = minValues[channelIndex];
|
||||
outputData[baseIndex + channelIndex * 2 + 1] = maxValues[channelIndex];
|
||||
}
|
||||
}
|
||||
|
||||
var outputDataLength:Int = Std.int(outputData.length / channels / 2);
|
||||
var result = new WaveformData(null, channels, sampleRate, samplesPerPoint, bitsPerSample, outputPointCount, outputData);
|
||||
|
||||
trace('[WAVEFORM] Interpreted audio buffer in ${TimerUtil.seconds(perfStart)}.');
|
||||
var result = new WaveformData(null, channels, soundBuffer.sampleRate, samplesPerPoint, bitsPerSample, outputPointCount, outputData.toArray());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ typedef EntryConstructorFunction = String->Void;
|
|||
* @param T The type to construct. Must implement `IRegistryEntry`.
|
||||
* @param J The type of the JSON data used when constructing.
|
||||
*/
|
||||
@:nullSafety
|
||||
@:generic
|
||||
@:autoBuild(funkin.util.macro.DataRegistryMacro.buildRegistry())
|
||||
abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructorFunction>), J>
|
||||
|
@ -115,7 +116,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
|||
{
|
||||
try
|
||||
{
|
||||
var entry:T = createEntry(entryId);
|
||||
var entry:Null<T> = createEntry(entryId);
|
||||
if (entry != null)
|
||||
{
|
||||
trace(' Loaded entry data: ${entry}');
|
||||
|
@ -165,7 +166,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
|||
* @param id The ID of the entry.
|
||||
* @return The class name, or `null` if it does not exist.
|
||||
*/
|
||||
public function getScriptedEntryClassName(id:String):String
|
||||
public function getScriptedEntryClassName(id:String):Null<String>
|
||||
{
|
||||
return scriptedEntryIds.get(id);
|
||||
}
|
||||
|
@ -216,7 +217,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
|||
public function fetchEntryVersion(id:String):Null<thx.semver.Version>
|
||||
{
|
||||
var entryStr:String = loadEntryFile(id).contents;
|
||||
var entryVersion:thx.semver.Version = VersionUtil.getVersionFromJSON(entryStr);
|
||||
var entryVersion:Null<thx.semver.Version> = VersionUtil.getVersionFromJSON(entryStr);
|
||||
return entryVersion;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import json2object.Position;
|
|||
import json2object.Position.Line;
|
||||
import json2object.Error;
|
||||
|
||||
@:nullSafety
|
||||
class DataError
|
||||
{
|
||||
public static function printError(error:Error):Void
|
||||
|
|
|
@ -19,6 +19,7 @@ import thx.semver.VersionRule;
|
|||
*
|
||||
* Functions must be of the signature `(hxjsonast.Json, String) -> T`, where the String is the property name and `T` is the type of the property.
|
||||
*/
|
||||
@:nullSafety
|
||||
class DataParse
|
||||
{
|
||||
/**
|
||||
|
@ -146,7 +147,6 @@ class DataParse
|
|||
throw 'Expected Backdrop property $name to be specify a valid "type", but it was "${backdropType}".';
|
||||
}
|
||||
|
||||
return null;
|
||||
default:
|
||||
throw 'Expected property $name to be an object, but it was ${json.value}.';
|
||||
}
|
||||
|
@ -310,6 +310,7 @@ class DataParse
|
|||
var length:Null<Float> = values[2] == null ? null : Tools.getValue(values[2]);
|
||||
var alt:Null<Bool> = values[3] == null ? null : Tools.getValue(values[3]);
|
||||
|
||||
if (time == null || data == null) throw 'Property $name note is missing time and/or data values.';
|
||||
return new LegacyNote(time, data, length, alt);
|
||||
// return null;
|
||||
default:
|
||||
|
|
|
@ -12,6 +12,7 @@ import haxe.ds.Either;
|
|||
*
|
||||
* NOTE: Result must include quotation marks if the value is a string! json2object will not add them for you!
|
||||
*/
|
||||
@:nullSafety
|
||||
class DataWrite
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
package funkin.data.animation;
|
||||
|
||||
@:nullSafety
|
||||
class AnimationDataUtil
|
||||
{
|
||||
public static function toNamed(data:UnnamedAnimationData, ?name:String = ""):AnimationData
|
||||
public static function toNamed(data:UnnamedAnimationData, name:String = ""):AnimationData
|
||||
{
|
||||
return {
|
||||
name: name,
|
||||
|
@ -22,7 +23,7 @@ class AnimationDataUtil
|
|||
* @param name (adds index to name)
|
||||
* @return Array<AnimationData>
|
||||
*/
|
||||
public static function toNamedArray(data:Array<UnnamedAnimationData>, ?name:String = ""):Array<AnimationData>
|
||||
public static function toNamedArray(data:Array<UnnamedAnimationData>, name:String = ""):Array<AnimationData>
|
||||
{
|
||||
return data.mapi(function(animItem, ind) return toNamed(animItem, '$name$ind'));
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import funkin.play.cutscene.dialogue.ScriptedConversation;
|
|||
import funkin.util.tools.ISingleton;
|
||||
import funkin.data.DefaultRegistryImpl;
|
||||
|
||||
@:nullSafety
|
||||
class ConversationRegistry extends BaseRegistry<Conversation, ConversationData> implements ISingleton implements DefaultRegistryImpl
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -6,6 +6,7 @@ import funkin.play.cutscene.dialogue.ScriptedDialogueBox;
|
|||
import funkin.util.tools.ISingleton;
|
||||
import funkin.data.DefaultRegistryImpl;
|
||||
|
||||
@:nullSafety
|
||||
class DialogueBoxRegistry extends BaseRegistry<DialogueBox, DialogueBoxData> implements ISingleton implements DefaultRegistryImpl
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -5,6 +5,7 @@ import funkin.play.cutscene.dialogue.ScriptedSpeaker;
|
|||
import funkin.util.tools.ISingleton;
|
||||
import funkin.data.DefaultRegistryImpl;
|
||||
|
||||
@:nullSafety
|
||||
class SpeakerRegistry extends BaseRegistry<Speaker, SpeakerData> implements ISingleton implements DefaultRegistryImpl
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -8,6 +8,7 @@ import funkin.play.event.ScriptedSongEvent;
|
|||
/**
|
||||
* This class statically handles the parsing of internal and scripted song event handlers.
|
||||
*/
|
||||
@:nullSafety
|
||||
class SongEventRegistry
|
||||
{
|
||||
/**
|
||||
|
@ -87,14 +88,14 @@ class SongEventRegistry
|
|||
return eventCache.values();
|
||||
}
|
||||
|
||||
public static function getEvent(id:String):SongEvent
|
||||
public static function getEvent(id:String):Null<SongEvent>
|
||||
{
|
||||
return eventCache.get(id);
|
||||
}
|
||||
|
||||
public static function getEventSchema(id:String):SongEventSchema
|
||||
public static function getEventSchema(id:String):Null<SongEventSchema>
|
||||
{
|
||||
var event:SongEvent = getEvent(id);
|
||||
var event:Null<SongEvent> = getEvent(id);
|
||||
if (event == null) return null;
|
||||
|
||||
return event.getEventSchema();
|
||||
|
@ -108,7 +109,7 @@ class SongEventRegistry
|
|||
public static function handleEvent(data:SongEventData):Void
|
||||
{
|
||||
var eventKind:String = data.eventKind;
|
||||
var eventHandler:SongEvent = eventCache.get(eventKind);
|
||||
var eventHandler:Null<SongEvent> = eventCache.get(eventKind);
|
||||
|
||||
if (eventHandler != null)
|
||||
{
|
||||
|
|
|
@ -6,6 +6,7 @@ import funkin.ui.freeplay.ScriptedAlbum;
|
|||
import funkin.util.tools.ISingleton;
|
||||
import funkin.data.DefaultRegistryImpl;
|
||||
|
||||
@:nullSafety
|
||||
class AlbumRegistry extends BaseRegistry<Album, AlbumData> implements ISingleton implements DefaultRegistryImpl
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -7,6 +7,7 @@ import funkin.save.Save;
|
|||
import funkin.util.tools.ISingleton;
|
||||
import funkin.data.DefaultRegistryImpl;
|
||||
|
||||
@:nullSafety
|
||||
class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData> implements ISingleton implements DefaultRegistryImpl
|
||||
{
|
||||
/**
|
||||
|
@ -56,7 +57,11 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData> impleme
|
|||
var player = fetchEntry(charId);
|
||||
if (player == null) continue;
|
||||
|
||||
#if UNLOCK_EVERYTHING
|
||||
count++;
|
||||
#else
|
||||
if (player.isUnlocked()) count++;
|
||||
#end
|
||||
}
|
||||
|
||||
return count;
|
||||
|
@ -64,6 +69,7 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData> impleme
|
|||
|
||||
public function hasNewCharacter():Bool
|
||||
{
|
||||
#if (!UNLOCK_EVERYTHING)
|
||||
var charactersSeen = Save.instance.charactersSeen.clone();
|
||||
|
||||
for (charId in listEntryIds())
|
||||
|
@ -77,6 +83,7 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData> impleme
|
|||
// This character is unlocked but we haven't seen them in Freeplay yet.
|
||||
return true;
|
||||
}
|
||||
#end
|
||||
|
||||
// Fallthrough case.
|
||||
return false;
|
||||
|
@ -84,9 +91,10 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData> impleme
|
|||
|
||||
public function listNewCharacters():Array<String>
|
||||
{
|
||||
var charactersSeen = Save.instance.charactersSeen.clone();
|
||||
var result = [];
|
||||
|
||||
#if (!UNLOCK_EVERYTHING)
|
||||
var charactersSeen = Save.instance.charactersSeen.clone();
|
||||
for (charId in listEntryIds())
|
||||
{
|
||||
var player = fetchEntry(charId);
|
||||
|
@ -98,6 +106,7 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData> impleme
|
|||
// This character is unlocked but we haven't seen them in Freeplay yet.
|
||||
result.push(charId);
|
||||
}
|
||||
#end
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -116,6 +125,7 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData> impleme
|
|||
/**
|
||||
* Return true if the given stage character is associated with a specific playable character.
|
||||
* If so, the level should only appear if that character is selected in Freeplay.
|
||||
* NOTE: This is NOT THE SAME as `player.isUnlocked()`!
|
||||
* @param characterId The stage character ID.
|
||||
* @return Whether the character is owned by any one character.
|
||||
*/
|
||||
|
@ -123,4 +133,17 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData> impleme
|
|||
{
|
||||
return ownedCharacterIds.exists(characterId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param characterId The character ID to check.
|
||||
* @return Whether the player saw the character unlock animation in Character Select.
|
||||
*/
|
||||
public function isCharacterSeen(characterId:String):Bool
|
||||
{
|
||||
#if UNLOCK_EVERYTHING
|
||||
return true;
|
||||
#else
|
||||
return Save.instance.charactersSeen.contains(characterId);
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import funkin.ui.freeplay.ScriptedFreeplayStyle;
|
|||
import funkin.util.tools.ISingleton;
|
||||
import funkin.data.DefaultRegistryImpl;
|
||||
|
||||
@:nullSafety
|
||||
class FreeplayStyleRegistry extends BaseRegistry<FreeplayStyle, FreeplayStyleData> implements ISingleton implements DefaultRegistryImpl
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -6,6 +6,7 @@ import funkin.data.notestyle.NoteStyleData;
|
|||
import funkin.util.tools.ISingleton;
|
||||
import funkin.data.DefaultRegistryImpl;
|
||||
|
||||
@:nullSafety
|
||||
class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData> implements ISingleton implements DefaultRegistryImpl
|
||||
{
|
||||
/**
|
||||
|
@ -24,6 +25,8 @@ class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData> implement
|
|||
|
||||
public function fetchDefault():NoteStyle
|
||||
{
|
||||
return fetchEntry(Constants.DEFAULT_NOTE_STYLE);
|
||||
var notestyle:Null<NoteStyle> = fetchEntry(Constants.DEFAULT_NOTE_STYLE);
|
||||
if (notestyle == null) throw 'Default notestyle was null! This should not happen!';
|
||||
return notestyle;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,11 +68,12 @@ class SongMetadata implements ICloneable<SongMetadata>
|
|||
@:jignored
|
||||
public var variation:String;
|
||||
|
||||
public function new(songName:String, artist:String, ?variation:String)
|
||||
public function new(songName:String, artist:String, ?charter:String, ?variation:String)
|
||||
{
|
||||
this.version = SongRegistry.SONG_METADATA_VERSION;
|
||||
this.songName = songName;
|
||||
this.artist = artist;
|
||||
this.charter = (charter == null) ? null : charter;
|
||||
this.timeFormat = 'ms';
|
||||
this.divisions = null;
|
||||
this.offsets = new SongOffsets();
|
||||
|
@ -96,7 +97,7 @@ class SongMetadata implements ICloneable<SongMetadata>
|
|||
*/
|
||||
public function clone():SongMetadata
|
||||
{
|
||||
var result:SongMetadata = new SongMetadata(this.songName, this.artist, this.variation);
|
||||
var result:SongMetadata = new SongMetadata(this.songName, this.artist, this.charter, this.variation);
|
||||
result.version = this.version;
|
||||
result.timeFormat = this.timeFormat;
|
||||
result.divisions = this.divisions;
|
||||
|
@ -139,7 +140,7 @@ class SongMetadata implements ICloneable<SongMetadata>
|
|||
*/
|
||||
public function toString():String
|
||||
{
|
||||
return 'SongMetadata(${this.songName} by ${this.artist}, variation ${this.variation})';
|
||||
return 'SongMetadata(${this.songName} by ${this.artist}, charted by ${this.charter}, variation ${this.variation})';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1116,6 +1117,23 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
|
|||
return 'SongNoteData(${this.time}ms, ' + (this.length > 0 ? '[${this.length}ms hold]' : '') + ' ${this.data}'
|
||||
+ (this.kind != '' ? ' [kind: ${this.kind}])' : ')');
|
||||
}
|
||||
|
||||
public function buildTooltip():String
|
||||
{
|
||||
if ((this.kind?.length ?? 0) == 0) return "";
|
||||
|
||||
var result:String = 'Kind: ${this.kind}';
|
||||
if (this.params.length == 0) return result;
|
||||
|
||||
result += "\nParams:";
|
||||
|
||||
for (param in params)
|
||||
{
|
||||
result += '\n- ${param.name}: ${param.value}';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -5,13 +5,13 @@ import funkin.data.song.SongData.SongEventData;
|
|||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongData.SongTimeChange;
|
||||
import funkin.util.ClipboardUtil;
|
||||
import funkin.util.SerializerUtil;
|
||||
|
||||
using Lambda;
|
||||
|
||||
/**
|
||||
* Utility functions for working with song data, including note data, event data, metadata, etc.
|
||||
*/
|
||||
@:nullSafety
|
||||
class SongDataUtils
|
||||
{
|
||||
/**
|
||||
|
@ -28,7 +28,7 @@ class SongDataUtils
|
|||
var time:Float = note.time + offset;
|
||||
var data:Int = note.data;
|
||||
var length:Float = note.length;
|
||||
var kind:String = note.kind;
|
||||
var kind:Null<String> = note.kind;
|
||||
return new SongNoteData(time, data, length, kind);
|
||||
};
|
||||
|
||||
|
@ -132,7 +132,7 @@ class SongDataUtils
|
|||
* Create an array of notes whose note data is flipped (player becomes opponent and vice versa)
|
||||
* Does not mutate the original array.
|
||||
*/
|
||||
public static function flipNotes(notes:Array<SongNoteData>, ?strumlineSize:Int = 4):Array<SongNoteData>
|
||||
public static function flipNotes(notes:Array<SongNoteData>, strumlineSize:Int = 4):Array<SongNoteData>
|
||||
{
|
||||
return notes.map(function(note:SongNoteData):SongNoteData {
|
||||
var newData = note.data;
|
||||
|
@ -150,7 +150,7 @@ class SongDataUtils
|
|||
*
|
||||
* Offset the provided array of notes such that the first note is at 0 milliseconds.
|
||||
*/
|
||||
public static function buildNoteClipboard(notes:Array<SongNoteData>, ?timeOffset:Int = null):Array<SongNoteData>
|
||||
public static function buildNoteClipboard(notes:Array<SongNoteData>, ?timeOffset:Int):Array<SongNoteData>
|
||||
{
|
||||
if (notes.length == 0) return notes;
|
||||
if (timeOffset == null) timeOffset = Std.int(notes[0].time);
|
||||
|
@ -162,7 +162,7 @@ class SongDataUtils
|
|||
*
|
||||
* Offset the provided array of events such that the first event is at 0 milliseconds.
|
||||
*/
|
||||
public static function buildEventClipboard(events:Array<SongEventData>, ?timeOffset:Int = null):Array<SongEventData>
|
||||
public static function buildEventClipboard(events:Array<SongEventData>, ?timeOffset:Int):Array<SongEventData>
|
||||
{
|
||||
if (events.length == 0) return events;
|
||||
if (timeOffset == null) timeOffset = Std.int(events[0].time);
|
||||
|
|
138
source/funkin/data/song/SongNoteDataUtils.hx
Normal file
138
source/funkin/data/song/SongNoteDataUtils.hx
Normal file
|
@ -0,0 +1,138 @@
|
|||
package funkin.data.song;
|
||||
|
||||
using SongData.SongNoteData;
|
||||
|
||||
/**
|
||||
* Utility class for extra handling of song notes
|
||||
*/
|
||||
@:nullSafety
|
||||
class SongNoteDataUtils
|
||||
{
|
||||
static final CHUNK_INTERVAL_MS:Float = 2500;
|
||||
|
||||
/**
|
||||
* Retrieves all stacked notes. It does this by cycling through "chunks" of notes within a certain interval.
|
||||
*
|
||||
* @param notes Sorted notes by time.
|
||||
* @param threshold The note stack threshold. Refer to `doNotesStack` for more details.
|
||||
* @param includeOverlapped (Optional) If overlapped notes should be included.
|
||||
* @param overlapped (Optional) An array that gets populated with overlapped notes.
|
||||
* Note that it's only guaranteed to work properly if the provided notes are sorted.
|
||||
* @return Stacked notes.
|
||||
*/
|
||||
public static function listStackedNotes(notes:Array<SongNoteData>, threshold:Float, includeOverlapped:Bool = true,
|
||||
?overlapped:Array<SongNoteData>):Array<SongNoteData>
|
||||
{
|
||||
var stackedNotes:Array<SongNoteData> = [];
|
||||
|
||||
var chunkTime:Float = 0;
|
||||
var chunks:Array<Array<SongNoteData>> = [[]];
|
||||
|
||||
for (note in notes)
|
||||
{
|
||||
if (note == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
while (note.time >= chunkTime + CHUNK_INTERVAL_MS)
|
||||
{
|
||||
chunkTime += CHUNK_INTERVAL_MS;
|
||||
chunks.push([]);
|
||||
}
|
||||
|
||||
chunks[chunks.length - 1].push(note);
|
||||
}
|
||||
|
||||
for (chunk in chunks)
|
||||
{
|
||||
for (i in 0...(chunk.length - 1))
|
||||
{
|
||||
for (j in (i + 1)...chunk.length)
|
||||
{
|
||||
var noteI:SongNoteData = chunk[i];
|
||||
var noteJ:SongNoteData = chunk[j];
|
||||
|
||||
if (doNotesStack(noteI, noteJ, threshold))
|
||||
{
|
||||
if (!stackedNotes.fastContains(noteI))
|
||||
{
|
||||
if (includeOverlapped) stackedNotes.push(noteI);
|
||||
|
||||
if (overlapped != null && !overlapped.contains(noteI)) overlapped.push(noteI);
|
||||
}
|
||||
|
||||
if (!stackedNotes.fastContains(noteJ))
|
||||
{
|
||||
stackedNotes.push(noteJ);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stackedNotes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Concatenates two arrays of notes but overwrites notes in `lhs` that are overlapped by notes in `rhs`.
|
||||
* Hold notes are only overwritten by longer hold notes.
|
||||
* This operation only modifies the second array and `overwrittenNotes`.
|
||||
*
|
||||
* @param lhs An array of notes
|
||||
* @param rhs An array of notes to concatenate into `lhs`
|
||||
* @param overwrittenNotes An optional array that is modified in-place with the notes in `lhs` that were overwritten.
|
||||
* @param threshold The note stack threshold. Refer to `doNotesStack` for more details.
|
||||
* @return The unsorted resulting array.
|
||||
*/
|
||||
public static function concatOverwrite(lhs:Array<SongNoteData>, rhs:Array<SongNoteData>, ?overwrittenNotes:Array<SongNoteData>,
|
||||
threshold:Float = 0):Array<SongNoteData>
|
||||
{
|
||||
if (lhs == null || rhs == null || rhs.length == 0) return lhs;
|
||||
if (lhs.length == 0) return rhs;
|
||||
|
||||
var result = lhs.copy();
|
||||
for (i in 0...rhs.length)
|
||||
{
|
||||
var noteB:SongNoteData = rhs[i];
|
||||
var hasOverlap:Bool = false;
|
||||
|
||||
for (j in 0...lhs.length)
|
||||
{
|
||||
var noteA:SongNoteData = lhs[j];
|
||||
if (doNotesStack(noteA, noteB, threshold))
|
||||
{
|
||||
// Long hold notes should have priority over shorter hold notes
|
||||
if (noteA.length <= noteB.length)
|
||||
{
|
||||
overwrittenNotes?.push(result[j].clone());
|
||||
result[j] = noteB;
|
||||
}
|
||||
hasOverlap = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasOverlap) result.push(noteB);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param noteA First note.
|
||||
* @param noteB Second note.
|
||||
* @param threshold The note stack threshold, in steps.
|
||||
* @return Returns `true` if both notes are on the same strumline, have the same direction
|
||||
* and their time difference in steps is less than the step-based threshold.
|
||||
* A threshold of 0 will return `true` if notes are nearly perfectly aligned.
|
||||
*/
|
||||
public static function doNotesStack(noteA:SongNoteData, noteB:SongNoteData, threshold:Float = 0):Bool
|
||||
{
|
||||
if (noteA.data != noteB.data) return false;
|
||||
else if (threshold == 0) return Math.ffloor(Math.abs(noteA.time - noteB.time)) < 1;
|
||||
|
||||
final stepDiff:Float = Math.abs(noteA.getStepTime() - noteB.getStepTime());
|
||||
return stepDiff <= threshold + 0.001;
|
||||
}
|
||||
}
|
|
@ -324,7 +324,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata> implements ISingleto
|
|||
}
|
||||
else
|
||||
{
|
||||
throw '[${registryId}] Chart entry ${id}:${variation} does not support migration to version ${SONG_CHART_DATA_VERSION_RULE}.';
|
||||
throw '[${registryId}] Chart entry ${id}:${variation} does not support migration to version ${SONG_MUSIC_DATA_VERSION_RULE}.';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -337,7 +337,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata> implements ISingleto
|
|||
}
|
||||
else
|
||||
{
|
||||
throw '[${registryId}] Chart entry "$fileName" does not support migration to version ${SONG_CHART_DATA_VERSION_RULE}.';
|
||||
throw '[${registryId}] Chart entry "$fileName" does not support migration to version ${SONG_MUSIC_DATA_VERSION_RULE}.';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,9 +9,10 @@ import funkin.data.song.SongData.SongTimeChange;
|
|||
import funkin.data.song.importer.FNFLegacyData;
|
||||
import funkin.data.song.importer.FNFLegacyData.LegacyNoteSection;
|
||||
|
||||
@:nullSafety
|
||||
class FNFLegacyImporter
|
||||
{
|
||||
public static function parseLegacyDataRaw(input:String, fileName:String = 'raw'):FNFLegacyData
|
||||
public static function parseLegacyDataRaw(input:String, fileName:String = 'raw'):Null<FNFLegacyData>
|
||||
{
|
||||
var parser = new json2object.JsonParser<FNFLegacyData>();
|
||||
parser.ignoreUnknownVariables = true; // Set to true to ignore extra variables that might be included in the JSON.
|
||||
|
@ -36,18 +37,16 @@ class FNFLegacyImporter
|
|||
{
|
||||
trace('Migrating song metadata from FNF Legacy.');
|
||||
|
||||
var songMetadata:SongMetadata = new SongMetadata('Import', Constants.DEFAULT_ARTIST, 'default');
|
||||
|
||||
var hadError:Bool = false;
|
||||
var songMetadata:SongMetadata = new SongMetadata('Import', Constants.DEFAULT_ARTIST, Constants.DEFAULT_CHARTER, Constants.DEFAULT_VARIATION);
|
||||
|
||||
// Set generatedBy string for debugging.
|
||||
songMetadata.generatedBy = 'Chart Editor Import (FNF Legacy)';
|
||||
|
||||
songMetadata.playData.stage = songData?.song?.stageDefault ?? 'mainStage';
|
||||
songMetadata.songName = songData?.song?.song ?? 'Import';
|
||||
songMetadata.playData.stage = songData.song?.stageDefault ?? 'mainStage';
|
||||
songMetadata.songName = songData.song?.song ?? 'Import';
|
||||
songMetadata.playData.difficulties = [];
|
||||
|
||||
if (songData?.song?.notes != null)
|
||||
if (songData.song?.notes != null)
|
||||
{
|
||||
switch (songData.song.notes)
|
||||
{
|
||||
|
@ -65,7 +64,7 @@ class FNFLegacyImporter
|
|||
|
||||
songMetadata.timeChanges = rebuildTimeChanges(songData);
|
||||
|
||||
songMetadata.playData.characters = new SongCharacterData(songData?.song?.player1 ?? 'bf', 'gf', songData?.song?.player2 ?? 'dad');
|
||||
songMetadata.playData.characters = new SongCharacterData(songData.song?.player1 ?? 'bf', 'gf', songData.song?.player2 ?? 'dad');
|
||||
|
||||
return songMetadata;
|
||||
}
|
||||
|
@ -76,7 +75,7 @@ class FNFLegacyImporter
|
|||
|
||||
var songChartData:SongChartData = new SongChartData([difficulty => 1.0], [], [difficulty => []]);
|
||||
|
||||
if (songData?.song?.notes != null)
|
||||
if (songData.song?.notes != null)
|
||||
{
|
||||
switch (songData.song.notes)
|
||||
{
|
||||
|
@ -84,7 +83,6 @@ class FNFLegacyImporter
|
|||
// One difficulty of notes.
|
||||
songChartData.notes.set(difficulty, migrateNoteSections(notes));
|
||||
case Right(difficulties):
|
||||
var baseDifficulty = null;
|
||||
if (difficulties.easy != null) songChartData.notes.set('easy', migrateNoteSections(difficulties.easy));
|
||||
if (difficulties.normal != null) songChartData.notes.set('normal', migrateNoteSections(difficulties.normal));
|
||||
if (difficulties.hard != null) songChartData.notes.set('hard', migrateNoteSections(difficulties.hard));
|
||||
|
@ -124,8 +122,8 @@ class FNFLegacyImporter
|
|||
noteSections = notes;
|
||||
case Right(difficulties):
|
||||
if (difficulties.normal != null) noteSections = difficulties.normal;
|
||||
if (difficulties.hard != null) noteSections = difficulties.normal;
|
||||
if (difficulties.easy != null) noteSections = difficulties.normal;
|
||||
if (difficulties.hard != null) noteSections = difficulties.hard;
|
||||
if (difficulties.easy != null) noteSections = difficulties.easy;
|
||||
}
|
||||
|
||||
if (noteSections == null || noteSections.length == 0) return result;
|
||||
|
@ -158,7 +156,7 @@ class FNFLegacyImporter
|
|||
{
|
||||
var result:Array<SongTimeChange> = [];
|
||||
|
||||
result.push(new SongTimeChange(0, songData?.song?.bpm ?? Constants.DEFAULT_BPM));
|
||||
result.push(new SongTimeChange(0, songData.song?.bpm ?? Constants.DEFAULT_BPM));
|
||||
|
||||
var noteSections = [];
|
||||
switch (songData.song.notes)
|
||||
|
@ -168,8 +166,8 @@ class FNFLegacyImporter
|
|||
noteSections = notes;
|
||||
case Right(difficulties):
|
||||
if (difficulties.normal != null) noteSections = difficulties.normal;
|
||||
if (difficulties.hard != null) noteSections = difficulties.normal;
|
||||
if (difficulties.easy != null) noteSections = difficulties.normal;
|
||||
if (difficulties.hard != null) noteSections = difficulties.hard;
|
||||
if (difficulties.easy != null) noteSections = difficulties.easy;
|
||||
}
|
||||
|
||||
if (noteSections == null || noteSections.length == 0) return result;
|
||||
|
@ -179,7 +177,7 @@ class FNFLegacyImporter
|
|||
if (noteSection.changeBPM ?? false)
|
||||
{
|
||||
var firstNote:LegacyNote = noteSection.sectionNotes[0];
|
||||
if (firstNote != null) result.push(new SongTimeChange(firstNote.time, noteSection.bpm));
|
||||
if (firstNote != null) result.push(new SongTimeChange(firstNote.time, noteSection.bpm ?? Constants.DEFAULT_BPM));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ class SongDataMigrator
|
|||
|
||||
public static function migrate_SongMetadata_v2_1_0(input:SongData_v2_1_0.SongMetadata_v2_1_0):SongMetadata
|
||||
{
|
||||
var result:SongMetadata = new SongMetadata(input.songName, input.artist, input.variation);
|
||||
var result:SongMetadata = new SongMetadata(input.songName, input.artist, Constants.DEFAULT_CHARTER, input.variation);
|
||||
result.version = SongRegistry.SONG_METADATA_VERSION;
|
||||
result.timeFormat = input.timeFormat;
|
||||
result.divisions = input.divisions;
|
||||
|
@ -66,7 +66,7 @@ class SongDataMigrator
|
|||
|
||||
public static function migrate_SongMetadata_v2_0_0(input:SongData_v2_0_0.SongMetadata_v2_0_0):SongMetadata
|
||||
{
|
||||
var result:SongMetadata = new SongMetadata(input.songName, input.artist, input.variation);
|
||||
var result:SongMetadata = new SongMetadata(input.songName, input.artist, Constants.DEFAULT_CHARTER, input.variation);
|
||||
result.version = SongRegistry.SONG_METADATA_VERSION;
|
||||
result.timeFormat = input.timeFormat;
|
||||
result.divisions = input.divisions;
|
||||
|
|
|
@ -205,7 +205,7 @@ typedef StageDataProp =
|
|||
|
||||
/**
|
||||
* The angle of the prop, as a float.
|
||||
* @default 1.0
|
||||
* @default 0.0
|
||||
*/
|
||||
@:optional
|
||||
@:default(0.0)
|
||||
|
@ -284,7 +284,7 @@ typedef StageDataCharacter =
|
|||
|
||||
/**
|
||||
* The angle of the character, as a float.
|
||||
* @default 1.0
|
||||
* @default 0.0
|
||||
*/
|
||||
@:optional
|
||||
@:default(0.0)
|
||||
|
|
|
@ -5,6 +5,7 @@ import funkin.play.stage.ScriptedStage;
|
|||
import funkin.util.tools.ISingleton;
|
||||
import funkin.data.DefaultRegistryImpl;
|
||||
|
||||
@:nullSafety
|
||||
class StageRegistry extends BaseRegistry<Stage, StageData> implements ISingleton implements DefaultRegistryImpl
|
||||
{
|
||||
/**
|
||||
|
@ -12,7 +13,7 @@ class StageRegistry extends BaseRegistry<Stage, StageData> implements ISingleton
|
|||
* Handle breaking changes by incrementing this value
|
||||
* and adding migration to the `migrateStageData()` function.
|
||||
*/
|
||||
public static final STAGE_DATA_VERSION:thx.semver.Version = "1.0.3";
|
||||
public static final STAGE_DATA_VERSION:thx.semver.Version = "1.0.2";
|
||||
|
||||
public static final STAGE_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <1.1.0";
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import funkin.data.stickers.StickerData;
|
|||
import funkin.ui.transition.stickers.StickerPack;
|
||||
import funkin.ui.transition.stickers.ScriptedStickerPack;
|
||||
|
||||
@:nullSafety
|
||||
class StickerRegistry extends BaseRegistry<StickerPack, StickerData>
|
||||
{
|
||||
/**
|
||||
|
@ -24,7 +25,9 @@ class StickerRegistry extends BaseRegistry<StickerPack, StickerData>
|
|||
|
||||
public function fetchDefault():StickerPack
|
||||
{
|
||||
return fetchEntry(Constants.DEFAULT_STICKER_PACK);
|
||||
var stickerPack:Null<StickerPack> = fetchEntry(Constants.DEFAULT_STICKER_PACK);
|
||||
if (stickerPack == null) throw 'Default sticker pack was null! This should not happen!';
|
||||
return stickerPack;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,6 +6,7 @@ import funkin.ui.story.ScriptedLevel;
|
|||
import funkin.util.tools.ISingleton;
|
||||
import funkin.data.DefaultRegistryImpl;
|
||||
|
||||
@:nullSafety
|
||||
class LevelRegistry extends BaseRegistry<Level, LevelData> implements ISingleton implements DefaultRegistryImpl
|
||||
{
|
||||
/**
|
||||
|
|
70
source/funkin/external/android/CallbackUtil.hx
vendored
Normal file
70
source/funkin/external/android/CallbackUtil.hx
vendored
Normal file
|
@ -0,0 +1,70 @@
|
|||
package funkin.external.android;
|
||||
|
||||
#if android
|
||||
import lime.system.JNI;
|
||||
import flixel.util.FlxSignal;
|
||||
|
||||
/**
|
||||
* A Utility class to handle Android API level callbacks and events.
|
||||
*/
|
||||
@:unreflective
|
||||
class CallbackUtil
|
||||
{
|
||||
/**
|
||||
* The result code for `DATA_FOLDER_CLOSED` activity.
|
||||
*/
|
||||
public static var DATA_FOLDER_CLOSED(get, never):Int;
|
||||
|
||||
@:noCompletion
|
||||
static function get_DATA_FOLDER_CLOSED():Int
|
||||
{
|
||||
final field:Null<Dynamic> = JNIUtil.createStaticField('funkin/extensions/CallbackUtil', 'DATA_FOLDER_CLOSED', 'I');
|
||||
|
||||
return field != null ? field.get() : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal triggered when an activity result is received.
|
||||
*
|
||||
* First argument is the request code, second is the result code.
|
||||
*/
|
||||
public static var onActivityResult:FlxTypedSignal<Int->Int->Void> = new FlxTypedSignal<Int->Int->Void>();
|
||||
|
||||
/**
|
||||
* Initializes the callback utility.
|
||||
*/
|
||||
public static function init():Void
|
||||
{
|
||||
final initCallBackJNI:Null<Dynamic> = JNIUtil.createStaticMethod('funkin/extensions/CallbackUtil', 'initCallBack', '(Lorg/haxe/lime/HaxeObject;)V');
|
||||
|
||||
if (initCallBackJNI != null)
|
||||
{
|
||||
initCallBackJNI(new CallbackHandler());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal class to handle native callback events.
|
||||
*/
|
||||
class CallbackHandler #if (lime >= "8.0.0") implements JNISafety #end
|
||||
{
|
||||
@:allow(funkin.external.android.CallbackUtil)
|
||||
function new():Void {}
|
||||
|
||||
/**
|
||||
* Handles the activity result callback from native code.
|
||||
*
|
||||
* @param requestCode The request code of the acitivty.
|
||||
* @param resultCode The result code of the acitivty.
|
||||
*/
|
||||
@:keep
|
||||
#if (lime >= "8.0.0")
|
||||
@:runOnMainThread
|
||||
#end
|
||||
public function onActivityResult(requestCode:Int, resultCode:Int):Void
|
||||
{
|
||||
if (CallbackUtil.onActivityResult != null) CallbackUtil.onActivityResult.dispatch(requestCode, resultCode);
|
||||
}
|
||||
}
|
||||
#end
|
23
source/funkin/external/android/DataFolderUtil.hx
vendored
Normal file
23
source/funkin/external/android/DataFolderUtil.hx
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
package funkin.external.android;
|
||||
|
||||
#if android
|
||||
/**
|
||||
* A Utility class to manage the Application's Data folder on Android.
|
||||
*/
|
||||
@:unreflective
|
||||
class DataFolderUtil
|
||||
{
|
||||
/**
|
||||
* Opens the data folder on an Android device using JNI.
|
||||
*/
|
||||
public static function openDataFolder():Void
|
||||
{
|
||||
final openDataFolderJNI:Null<Dynamic> = JNIUtil.createStaticMethod('funkin/util/DataFolderUtil', 'openDataFolder', '(I)V');
|
||||
|
||||
if (openDataFolderJNI != null)
|
||||
{
|
||||
openDataFolderJNI(CallbackUtil.DATA_FOLDER_CLOSED);
|
||||
}
|
||||
}
|
||||
}
|
||||
#end
|
111
source/funkin/external/android/JNIUtil.hx
vendored
Normal file
111
source/funkin/external/android/JNIUtil.hx
vendored
Normal file
|
@ -0,0 +1,111 @@
|
|||
package funkin.external.android;
|
||||
|
||||
#if android
|
||||
import lime.system.JNI;
|
||||
|
||||
/**
|
||||
* A utility class for caching JNI method and field references.
|
||||
*/
|
||||
class JNIUtil
|
||||
{
|
||||
@:noCompletion
|
||||
private static var staticMethodCache:Map<String, Dynamic> = [];
|
||||
|
||||
@:noCompletion
|
||||
private static var memberMethodCache:Map<String, Dynamic> = [];
|
||||
|
||||
@:noCompletion
|
||||
private static var staticFieldCache:Map<String, JNIStaticField> = [];
|
||||
|
||||
@:noCompletion
|
||||
private static var memberFieldCache:Map<String, JNIMemberField> = [];
|
||||
|
||||
/**
|
||||
* Retrieves or creates a cached static method reference.
|
||||
*
|
||||
* @param className The name of the Java class containing the method.
|
||||
* @param methodName The name of the method.
|
||||
* @param signature The method signature in JNI format.
|
||||
* @param cache Whether to cache the result (default true).
|
||||
* @return A dynamic reference to the static method.
|
||||
*/
|
||||
public static function createStaticMethod(className:String, methodName:String, signature:String, cache:Bool = true):Null<Dynamic>
|
||||
{
|
||||
@:privateAccess
|
||||
className = JNI.transformClassName(className);
|
||||
|
||||
final key:String = '$className::$methodName::$signature';
|
||||
|
||||
if (cache && !staticMethodCache.exists(key)) staticMethodCache.set(key, JNI.createStaticMethod(className, methodName, signature));
|
||||
else if (!cache) return JNI.createStaticMethod(className, methodName, signature);
|
||||
|
||||
return staticMethodCache.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves or creates a cached member method reference.
|
||||
*
|
||||
* @param className The name of the Java class containing the method.
|
||||
* @param methodName The name of the method.
|
||||
* @param signature The method signature in JNI format.
|
||||
* @param cache Whether to cache the result (default true).
|
||||
* @return A dynamic reference to the member method.
|
||||
*/
|
||||
public static function createMemberMethod(className:String, methodName:String, signature:String, cache:Bool = true):Null<Dynamic>
|
||||
{
|
||||
@:privateAccess
|
||||
className = JNI.transformClassName(className);
|
||||
|
||||
final key:String = '$className::$methodName::$signature';
|
||||
|
||||
if (cache && !memberMethodCache.exists(key)) memberMethodCache.set(key, JNI.createMemberMethod(className, methodName, signature));
|
||||
else if (!cache) return JNI.createMemberMethod(className, methodName, signature);
|
||||
|
||||
return memberMethodCache.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves or creates a cached static field reference.
|
||||
*
|
||||
* @param className The name of the Java class containing the field.
|
||||
* @param fieldName The name of the field.
|
||||
* @param signature The field signature in JNI format.
|
||||
* @param cache Whether to cache the result (default true).
|
||||
* @return A reference to the static field.
|
||||
*/
|
||||
public static function createStaticField(className:String, fieldName:String, signature:String, cache:Bool = true):Null<JNIStaticField>
|
||||
{
|
||||
@:privateAccess
|
||||
className = JNI.transformClassName(className);
|
||||
|
||||
final key:String = '$className::$fieldName::$signature';
|
||||
|
||||
if (cache && !staticFieldCache.exists(key)) staticFieldCache.set(key, JNI.createStaticField(className, fieldName, signature));
|
||||
else if (!cache) return JNI.createStaticField(className, fieldName, signature);
|
||||
|
||||
return staticFieldCache.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves or creates a cached member field reference.
|
||||
*
|
||||
* @param className The name of the Java class containing the field.
|
||||
* @param fieldName The name of the field.
|
||||
* @param signature The field signature in JNI format.
|
||||
* @param cache Whether to cache the result (default true).
|
||||
* @return A reference to the member field.
|
||||
*/
|
||||
public static function createMemberField(className:String, fieldName:String, signature:String, cache:Bool = true):Null<JNIMemberField>
|
||||
{
|
||||
@:privateAccess
|
||||
className = JNI.transformClassName(className);
|
||||
|
||||
final key:String = '$className::$fieldName::$signature';
|
||||
|
||||
if (cache && !memberFieldCache.exists(key)) memberFieldCache.set(key, JNI.createMemberField(className, fieldName, signature));
|
||||
else if (!cache) return JNI.createMemberField(className, fieldName, signature);
|
||||
|
||||
return memberFieldCache.get(key);
|
||||
}
|
||||
}
|
||||
#end
|
52
source/funkin/external/android/ScreenUtil.hx
vendored
Normal file
52
source/funkin/external/android/ScreenUtil.hx
vendored
Normal file
|
@ -0,0 +1,52 @@
|
|||
package funkin.external.android;
|
||||
|
||||
#if android
|
||||
import lime.math.Rectangle;
|
||||
import lime.system.JNI;
|
||||
|
||||
/**
|
||||
* A Utility class to get Android screen related informations.
|
||||
*/
|
||||
@:unreflective
|
||||
class ScreenUtil
|
||||
{
|
||||
/**
|
||||
* Retrieves the dimensions of display cutouts (such as notches) on Android devices.
|
||||
*
|
||||
* @return An array of `Rectangle` objects, each representing a display cutout's position and size.
|
||||
*/
|
||||
public static function getCutoutDimensions():Array<Rectangle>
|
||||
{
|
||||
final getCutoutDimensionsJNI:Null<Dynamic> = JNIUtil.createStaticMethod('funkin/util/ScreenUtil', 'getCutoutDimensions', '()[Landroid/graphics/Rect;');
|
||||
|
||||
if (getCutoutDimensionsJNI != null)
|
||||
{
|
||||
final rectangles:Array<Rectangle> = [];
|
||||
|
||||
for (rectangle in cast(getCutoutDimensionsJNI(), Array<Dynamic>))
|
||||
{
|
||||
if (rectangle == null) continue;
|
||||
|
||||
final topJNI:Null<JNIMemberField> = JNIUtil.createMemberField('android/graphics/Rect', 'top', 'I');
|
||||
final leftJNI:Null<JNIMemberField> = JNIUtil.createMemberField('android/graphics/Rect', 'left', 'I');
|
||||
final rightJNI:Null<JNIMemberField> = JNIUtil.createMemberField('android/graphics/Rect', 'right', 'I');
|
||||
final bottomJNI:Null<JNIMemberField> = JNIUtil.createMemberField('android/graphics/Rect', 'bottom', 'I');
|
||||
|
||||
if (topJNI != null && leftJNI != null && rightJNI != null && bottomJNI != null)
|
||||
{
|
||||
final top:Int = topJNI.get(rectangle);
|
||||
final left:Int = leftJNI.get(rectangle);
|
||||
final right:Int = rightJNI.get(rectangle);
|
||||
final bottom:Int = bottomJNI.get(rectangle);
|
||||
|
||||
rectangles.push(new Rectangle(left, top, right - left, bottom - top));
|
||||
}
|
||||
}
|
||||
|
||||
return rectangles;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
#end
|
36
source/funkin/external/android/java/funkin/extension/CallbackUtil.java
vendored
Normal file
36
source/funkin/external/android/java/funkin/extension/CallbackUtil.java
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
package funkin.extensions;
|
||||
|
||||
import android.content.Intent;
|
||||
|
||||
import org.haxe.extension.Extension;
|
||||
|
||||
import org.haxe.lime.HaxeObject;
|
||||
|
||||
public class CallbackUtil extends Extension
|
||||
{
|
||||
/**
|
||||
* Constant representing the event when the data folder is closed.
|
||||
*/
|
||||
public static int DATA_FOLDER_CLOSED = 0x01;
|
||||
|
||||
private static HaxeObject haxeObject;
|
||||
|
||||
/**
|
||||
* Initializes the callback object for handling Haxe callbacks.
|
||||
*
|
||||
* @param haxeObject The HaxeObject instance to handle callbacks.
|
||||
*/
|
||||
public static void initCallBack(final HaxeObject haxeObject)
|
||||
{
|
||||
CallbackUtil.haxeObject = haxeObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onActivityResult(int requestCode, int resultCode, Intent data)
|
||||
{
|
||||
if (haxeObject != null)
|
||||
haxeObject.call2("onActivityResult", requestCode, resultCode);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
346
source/funkin/external/android/java/funkin/provider/DataFolderProvider.java
vendored
Normal file
346
source/funkin/external/android/java/funkin/provider/DataFolderProvider.java
vendored
Normal file
|
@ -0,0 +1,346 @@
|
|||
package funkin.provider;
|
||||
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.graphics.Point;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.DocumentsContract.Document;
|
||||
import android.provider.DocumentsContract.Root;
|
||||
import android.provider.DocumentsProvider;
|
||||
import android.webkit.MimeTypeMap;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* @see https://github.com/termux/termux-app/blob/7bceab88e2272f961d1b94ef736f1a9e20173247/app/src/main/java/com/termux/filepicker/TermuxDocumentsProvider.java
|
||||
*/
|
||||
public class DataFolderProvider extends DocumentsProvider
|
||||
{
|
||||
private static File BASE_DIR;
|
||||
|
||||
private static String BASE_DIR_PATH;
|
||||
|
||||
private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
|
||||
Root.COLUMN_ROOT_ID,
|
||||
Root.COLUMN_MIME_TYPES,
|
||||
Root.COLUMN_FLAGS,
|
||||
Root.COLUMN_ICON,
|
||||
Root.COLUMN_TITLE,
|
||||
Root.COLUMN_SUMMARY,
|
||||
Root.COLUMN_DOCUMENT_ID,
|
||||
Root.COLUMN_AVAILABLE_BYTES
|
||||
};
|
||||
|
||||
private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
|
||||
Document.COLUMN_DOCUMENT_ID,
|
||||
Document.COLUMN_MIME_TYPE,
|
||||
Document.COLUMN_DISPLAY_NAME,
|
||||
Document.COLUMN_LAST_MODIFIED,
|
||||
Document.COLUMN_FLAGS,
|
||||
Document.COLUMN_SIZE
|
||||
};
|
||||
|
||||
@Override
|
||||
public Cursor queryRoots(String[] projection)
|
||||
{
|
||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_ROOT_PROJECTION);
|
||||
|
||||
if (BASE_DIR == null)
|
||||
return result;
|
||||
|
||||
final MatrixCursor.RowBuilder row = result.newRow();
|
||||
|
||||
row.add(Root.COLUMN_ROOT_ID, getDocIdForFile(BASE_DIR));
|
||||
row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(BASE_DIR));
|
||||
row.add(Root.COLUMN_SUMMARY, "Data Folder");
|
||||
row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD);
|
||||
row.add(Root.COLUMN_TITLE, "::APP_TITLE::");
|
||||
row.add(Root.COLUMN_MIME_TYPES, "*/*");
|
||||
row.add(Root.COLUMN_AVAILABLE_BYTES, BASE_DIR.getFreeSpace());
|
||||
::if (APP_PACKAGE != "")::
|
||||
row.add(Root.COLUMN_ICON, ::APP_PACKAGE::.R.mipmap.ic_launcher);
|
||||
::end::
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException
|
||||
{
|
||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||
includeFile(result, documentId, null);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException
|
||||
{
|
||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||
|
||||
File parent = getFileForDocId(parentDocumentId);
|
||||
|
||||
if (parent != null)
|
||||
{
|
||||
File[] children = null;
|
||||
|
||||
try {
|
||||
children = parent.listFiles();
|
||||
} catch (SecurityException e) {
|
||||
children = new File[0];
|
||||
}
|
||||
|
||||
if (children != null)
|
||||
{
|
||||
for (File file : children)
|
||||
includeFile(result, null, file);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openDocument(String documentId, String mode, CancellationSignal signal) throws FileNotFoundException
|
||||
{
|
||||
return ParcelFileDescriptor.open(getFileForDocId(documentId), ParcelFileDescriptor.parseMode(mode));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AssetFileDescriptor openDocumentThumbnail(String documentId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException
|
||||
{
|
||||
final File file = getFileForDocId(documentId);
|
||||
final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||
return new AssetFileDescriptor(pfd, 0, file.length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreate()
|
||||
{
|
||||
BASE_DIR = getContext().getExternalFilesDir(null);
|
||||
|
||||
if (BASE_DIR == null)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
BASE_DIR_PATH = BASE_DIR.getCanonicalPath();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
BASE_DIR_PATH = BASE_DIR.getAbsolutePath();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createDocument(String parentDocumentId, String mimeType, String displayName) throws FileNotFoundException
|
||||
{
|
||||
File parentFile = getFileForDocId(parentDocumentId);
|
||||
|
||||
File newFile = new File(parentFile, displayName);
|
||||
|
||||
int noConflictId = 2;
|
||||
|
||||
while (newFile.exists())
|
||||
{
|
||||
newFile = new File(parentFile, displayName + " (" + noConflictId++ + ")");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
boolean succeeded;
|
||||
|
||||
if (Document.MIME_TYPE_DIR.equals(mimeType))
|
||||
succeeded = newFile.mkdir();
|
||||
else
|
||||
succeeded = newFile.createNewFile();
|
||||
|
||||
if (!succeeded)
|
||||
throw new FileNotFoundException("Failed to create document with id " + newFile.getPath());
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new FileNotFoundException("Failed to create document with id " + newFile.getPath());
|
||||
}
|
||||
|
||||
return newFile.getPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteDocument(String documentId) throws FileNotFoundException
|
||||
{
|
||||
if (!deleteRecursive(getFileForDocId(documentId)))
|
||||
throw new FileNotFoundException("Failed to delete document with id " + documentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDocumentType(String documentId) throws FileNotFoundException
|
||||
{
|
||||
return getMimeType(getFileForDocId(documentId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor querySearchDocuments(String rootId, String query, String[] projection) throws FileNotFoundException
|
||||
{
|
||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||
|
||||
final LinkedList<File> pending = new LinkedList<>();
|
||||
|
||||
pending.add(getFileForDocId(rootId));
|
||||
|
||||
final int MAX_SEARCH_RESULTS = 50;
|
||||
|
||||
while (!pending.isEmpty() && result.getCount() < MAX_SEARCH_RESULTS)
|
||||
{
|
||||
final File file = pending.removeFirst();
|
||||
|
||||
boolean isInsideHome;
|
||||
|
||||
try
|
||||
{
|
||||
isInsideHome = file.getCanonicalPath().startsWith(BASE_DIR_PATH);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
isInsideHome = true;
|
||||
}
|
||||
|
||||
if (isInsideHome)
|
||||
{
|
||||
if (file.isDirectory())
|
||||
{
|
||||
try {
|
||||
Collections.addAll(pending, file.listFiles());
|
||||
} catch (SecurityException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
else if (file.getName().toLowerCase().contains(query))
|
||||
{
|
||||
includeFile(result, null, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChildDocument(String parentDocumentId, String documentId)
|
||||
{
|
||||
try
|
||||
{
|
||||
File parent = getFileForDocId(parentDocumentId).getCanonicalFile();
|
||||
File child = getFileForDocId(documentId).getCanonicalFile();
|
||||
return child.getPath().startsWith(parent.getPath() + "/");
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean deleteRecursive(File file)
|
||||
{
|
||||
if (file.isDirectory())
|
||||
{
|
||||
File[] children = file.listFiles();
|
||||
|
||||
if (children != null)
|
||||
{
|
||||
for (File child : children)
|
||||
{
|
||||
if (!deleteRecursive(child))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return file.delete();
|
||||
}
|
||||
|
||||
private static String getDocIdForFile(File file)
|
||||
{
|
||||
return file.getAbsolutePath();
|
||||
}
|
||||
|
||||
private static File getFileForDocId(String docId) throws FileNotFoundException
|
||||
{
|
||||
if (BASE_DIR == null)
|
||||
throw new FileNotFoundException("Base directory not available");
|
||||
|
||||
final File f = (docId == null || docId.isEmpty()) ? BASE_DIR : new File(docId);
|
||||
|
||||
if (!f.exists())
|
||||
throw new FileNotFoundException(f.getAbsolutePath() + " not found");
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
private static String getMimeType(File file)
|
||||
{
|
||||
if (file == null || file.isDirectory())
|
||||
return Document.MIME_TYPE_DIR;
|
||||
|
||||
String name = file.getName();
|
||||
|
||||
int lastDot = name.lastIndexOf('.');
|
||||
|
||||
if (lastDot >= 0)
|
||||
{
|
||||
String extension = name.substring(lastDot + 1).toLowerCase();
|
||||
|
||||
String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||
|
||||
if (mime != null)
|
||||
return mime;
|
||||
}
|
||||
|
||||
return "application/octet-stream";
|
||||
}
|
||||
|
||||
private void includeFile(MatrixCursor result, String docId, File file) throws FileNotFoundException
|
||||
{
|
||||
if (docId == null)
|
||||
docId = getDocIdForFile(file);
|
||||
else
|
||||
file = getFileForDocId(docId);
|
||||
|
||||
int flags = 0;
|
||||
|
||||
if (file.isDirectory())
|
||||
{
|
||||
if (file.canWrite())
|
||||
flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
|
||||
}
|
||||
else if (file.canWrite())
|
||||
{
|
||||
flags |= Document.FLAG_SUPPORTS_WRITE;
|
||||
}
|
||||
|
||||
if (file.getParentFile() != null && file.getParentFile().canWrite())
|
||||
flags |= Document.FLAG_SUPPORTS_DELETE;
|
||||
|
||||
final String mimeType = getMimeType(file);
|
||||
|
||||
if (mimeType.startsWith("image/"))
|
||||
flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
|
||||
|
||||
final MatrixCursor.RowBuilder row = result.newRow();
|
||||
row.add(Document.COLUMN_DOCUMENT_ID, docId);
|
||||
row.add(Document.COLUMN_DISPLAY_NAME, file.getName());
|
||||
row.add(Document.COLUMN_SIZE, file.length());
|
||||
row.add(Document.COLUMN_MIME_TYPE, mimeType);
|
||||
row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
|
||||
row.add(Document.COLUMN_FLAGS, flags);
|
||||
::if (APP_PACKAGE != "")::
|
||||
row.add(Document.COLUMN_ICON, ::APP_PACKAGE::.R.mipmap.ic_launcher);
|
||||
::end::
|
||||
}
|
||||
}
|
34
source/funkin/external/android/java/funkin/util/DataFolderUtil.java
vendored
Normal file
34
source/funkin/external/android/java/funkin/util/DataFolderUtil.java
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
package funkin.util;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.ContentResolver;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.content.pm.PackageInfo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.haxe.extension.Extension;
|
||||
|
||||
public class DataFolderUtil
|
||||
{
|
||||
/**
|
||||
* A method that opens the Application's data folder for browsing through the Storage Access Framework.
|
||||
* It's highly based on some code borrowed from Mterial Files
|
||||
* https://github.com/zhanghai/MaterialFiles
|
||||
*/
|
||||
public static void openDataFolder(int requestCode)
|
||||
{
|
||||
::if (APP_PACKAGE != "")::
|
||||
if (Extension.mainActivity != null)
|
||||
{
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setDataAndType(DocumentsContract.buildRootUri("::APP_PACKAGE::.docprovider", ""), "vnd.android.document/directory");
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
Extension.mainActivity.startActivityForResult(intent, requestCode);
|
||||
}
|
||||
::end::
|
||||
}
|
||||
}
|
39
source/funkin/external/android/java/funkin/util/ScreenUtil.java
vendored
Normal file
39
source/funkin/external/android/java/funkin/util/ScreenUtil.java
vendored
Normal file
|
@ -0,0 +1,39 @@
|
|||
package funkin.util;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.view.DisplayCutout;
|
||||
import android.view.WindowInsets;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.haxe.extension.Extension;
|
||||
|
||||
public class ScreenUtil
|
||||
{
|
||||
public static Rect[] getCutoutDimensions()
|
||||
{
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
|
||||
{
|
||||
if (Extension.mainActivity != null)
|
||||
{
|
||||
WindowInsets insets = Extension.mainActivity.getWindow().getDecorView().getRootWindowInsets();
|
||||
|
||||
if (insets != null)
|
||||
{
|
||||
DisplayCutout cutout = insets.getDisplayCutout();
|
||||
|
||||
if (cutout != null)
|
||||
{
|
||||
List<Rect> boundingRects = cutout.getBoundingRects();
|
||||
|
||||
if (boundingRects != null && !boundingRects.isEmpty())
|
||||
return boundingRects.toArray(new Rect[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Rect[0];
|
||||
}
|
||||
}
|
17
source/funkin/external/ios/AudioSession.hx
vendored
Normal file
17
source/funkin/external/ios/AudioSession.hx
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
package funkin.external.ios;
|
||||
|
||||
#if ios
|
||||
/**
|
||||
* A Utility class to manage iOS audio.
|
||||
*/
|
||||
@:build(funkin.util.macro.LinkerMacro.xml('project/Build.xml'))
|
||||
@:include('AudioSession.hpp')
|
||||
@:unreflective
|
||||
extern class AudioSession
|
||||
{
|
||||
@:native('initialize')
|
||||
static function initialize():Void;
|
||||
@:native('setActive')
|
||||
static function setActive(active:Bool):Void;
|
||||
}
|
||||
#end
|
18
source/funkin/external/ios/ScreenUtil.hx
vendored
Normal file
18
source/funkin/external/ios/ScreenUtil.hx
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
package funkin.external.ios;
|
||||
|
||||
#if ios
|
||||
/**
|
||||
* A Utility class to get iOS screen related informations.
|
||||
*/
|
||||
@:build(funkin.util.macro.LinkerMacro.xml('project/Build.xml'))
|
||||
@:include('ScreenUtil.hpp')
|
||||
@:unreflective
|
||||
extern class ScreenUtil
|
||||
{
|
||||
@:native('getSafeAreaInsets')
|
||||
static function getSafeAreaInsets(top:cpp.RawPointer<Float>, bottom:cpp.RawPointer<Float>, left:cpp.RawPointer<Float>, right:cpp.RawPointer<Float>):Void;
|
||||
|
||||
@:native('getScreenSize')
|
||||
static function getScreenSize(width:cpp.RawPointer<Float>, height:cpp.RawPointer<Float>):Void;
|
||||
}
|
||||
#end
|
29
source/funkin/external/ios/project/Build.xml
vendored
Normal file
29
source/funkin/external/ios/project/Build.xml
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xml>
|
||||
<pragma once="true" />
|
||||
|
||||
<files id="haxe">
|
||||
<compilerflag value="-I${this_dir}/ios/include" if="iphoneos || iphonesim" />
|
||||
</files>
|
||||
|
||||
<files id="__main__">
|
||||
<compilerflag value="-I${this_dir}/ios/include" if="iphoneos || iphonesim" />
|
||||
</files>
|
||||
|
||||
<files id="external-ios" dir="${this_dir}/ios" if="iphoneos || iphonesim">
|
||||
<compilerflag value="-I${this_dir}/ios/include" />
|
||||
|
||||
<file name="src/ScreenUtil.mm" />
|
||||
<file name="src/AudioSession.mm" />
|
||||
</files>
|
||||
|
||||
<target id="haxe">
|
||||
<section if="iphoneos || iphonesim">
|
||||
<vflag name="-framework" value="UIKit" />
|
||||
<vflag name="-framework" value="Foundation" />
|
||||
<vflag name="-framework" value="AVFAudio" />
|
||||
</section>
|
||||
|
||||
<files id="external-ios" if="iphoneos || iphonesim" />
|
||||
</target>
|
||||
</xml>
|
4
source/funkin/external/ios/project/ios/include/AudioSession.hpp
vendored
Normal file
4
source/funkin/external/ios/project/ios/include/AudioSession.hpp
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
#pragma once
|
||||
|
||||
void initialize();
|
||||
void setActive(bool active);
|
4
source/funkin/external/ios/project/ios/include/ScreenUtil.hpp
vendored
Normal file
4
source/funkin/external/ios/project/ios/include/ScreenUtil.hpp
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
#pragma once
|
||||
|
||||
void getSafeAreaInsets(double* top, double* bottom, double* left, double* right);
|
||||
void getScreenSize(double* width, double* height);
|
41
source/funkin/external/ios/project/ios/src/AudioSession.mm
vendored
Normal file
41
source/funkin/external/ios/project/ios/src/AudioSession.mm
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
#import <AVFAudio/AVFAudio.h>
|
||||
|
||||
void initialize()
|
||||
{
|
||||
AVAudioSession *session = [AVAudioSession sharedInstance];
|
||||
|
||||
NSError *error;
|
||||
[session setCategory:AVAudioSessionCategoryPlayback
|
||||
mode:AVAudioSessionModeDefault
|
||||
options:AVAudioSessionCategoryOptionAllowBluetoothA2DP|AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers
|
||||
error:&error];
|
||||
|
||||
if (@available(iOS 17.0, *))
|
||||
{
|
||||
[session setPrefersInterruptionOnRouteDisconnect:false error:nil];
|
||||
}
|
||||
|
||||
if (@available(iOS 14.5, *))
|
||||
{
|
||||
[session setPrefersNoInterruptionsFromSystemAlerts:true error:nil];
|
||||
}
|
||||
|
||||
if (error)
|
||||
{
|
||||
NSLog(@"Unable to set category of audio session: %@", error);
|
||||
}
|
||||
}
|
||||
|
||||
void setActive(bool active)
|
||||
{
|
||||
AVAudioSession *session = [AVAudioSession sharedInstance];
|
||||
NSError *error;
|
||||
|
||||
[session setActive:YES error:&error];
|
||||
|
||||
if (error)
|
||||
{
|
||||
NSLog(@"Unable to set active of audio session: %@", error);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue