Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bdedc0aad2 | ||
|
|
b23b008861 | ||
|
|
acbfa69d48 | ||
|
|
648ca8fd13 | ||
|
|
90eabf0b38 | ||
|
|
b3959a5935 | ||
|
|
cdca3bf307 | ||
|
|
e491c5e841 | ||
|
|
f9b125e44a | ||
|
|
1ceb4fad1a | ||
|
|
21fc59c997 | ||
|
|
0cbde53f86 | ||
|
|
5119cf1b72 | ||
|
|
927f3fc300 | ||
|
|
d1e4a17f46 | ||
|
|
ec868f8ef5 | ||
|
|
1e0a238463 | ||
|
|
e07e59a288 | ||
|
|
d6410d5e42 | ||
|
|
5974c1a482 | ||
|
|
ffdcbdbf21 | ||
|
|
3b27ec5b66 | ||
|
|
56a56dbfaa | ||
|
|
537bd5dd83 | ||
|
|
b0fd471f8a | ||
|
|
3633df3fdc | ||
|
|
e6234397f5 | ||
|
|
68ce5ce90b | ||
|
|
a6066f9905 | ||
|
|
75506f71a9 | ||
|
|
c4b5462a5a | ||
|
|
47d4d77c86 | ||
|
|
acf17f6165 | ||
|
|
2db26070c6 | ||
|
|
e0f9c7336b | ||
|
|
a28a28ae4c | ||
|
|
7160f748eb | ||
|
|
c8e60bed34 | ||
|
|
11c826daa6 | ||
|
|
6c887b4632 | ||
|
|
ecfb897dae | ||
|
|
56d5767004 | ||
|
|
7d3bfa32ca | ||
|
|
348647f486 | ||
|
|
48ed659800 | ||
|
|
8aafe62f9c | ||
|
|
0f7859bfb6 | ||
|
|
c3d3d2e547 | ||
|
|
37dc3461af | ||
|
|
c11ac1f658 | ||
|
|
47bbf67ae6 | ||
|
|
528e9e4f03 | ||
|
|
7626700495 | ||
|
|
0d2a663346 | ||
|
|
22736b27da | ||
|
|
dcfad7b7e8 | ||
|
|
ef625b3d9d | ||
|
|
6ce3a2e93e | ||
|
|
b8c2854007 | ||
|
|
04fffe8ade | ||
|
|
c37862445f | ||
|
|
0613e9d295 | ||
|
|
bed249dca0 | ||
|
|
139940f9ec | ||
|
|
36e2f47577 | ||
|
|
ce493a03c9 | ||
|
|
aabd1aa9f7 | ||
|
|
89029220e4 | ||
|
|
dca54de087 | ||
|
|
15bef89d71 | ||
|
|
6c1f3800db | ||
|
|
8507f799a1 | ||
|
|
dba2794780 | ||
|
|
48752e444c | ||
|
|
8a1d74b82c | ||
|
|
0d1db6ae9e | ||
|
|
1f1851990f | ||
|
|
67d2a9811b | ||
|
|
a782b3f9e8 | ||
|
|
74e28a18ff | ||
|
|
220636e01e | ||
|
|
4f316003af | ||
|
|
1a04e35db7 | ||
|
|
d3e83dd72d | ||
|
|
c4e2b900c1 | ||
|
|
ac3178118c | ||
|
|
681f7a6d07 | ||
|
|
aa24c94782 | ||
|
|
7075c8af4b | ||
|
|
efc33403b6 | ||
|
|
25a42b8182 | ||
|
|
0389af2202 | ||
|
|
b694ec08d6 | ||
|
|
423ccfab98 | ||
|
|
edc1f7113b | ||
|
|
f0b50de9fa | ||
|
|
0ad2d507ef | ||
|
|
6c13444211 | ||
|
|
5a0b4310a2 | ||
|
|
6b1fc0372b | ||
|
|
cf69125d25 | ||
|
|
84b2d5b48d | ||
|
|
dcb5b3a935 | ||
|
|
011f1541b1 | ||
|
|
13c1bf8ac0 | ||
|
|
1893f2b2f6 | ||
|
|
a0caa41edc | ||
|
|
456b2ff6c4 | ||
|
|
a1d1b1b2d1 | ||
|
|
0db4ce11b7 | ||
|
|
340fd5dcf1 | ||
|
|
d569690a51 | ||
|
|
4a5afa2fc7 | ||
|
|
59c593b812 | ||
|
|
6ba9e00a9f | ||
|
|
ec7bdd32e2 | ||
|
|
939528a9bd | ||
|
|
08b3af959d | ||
|
|
cea54e394c | ||
|
|
e710198afe | ||
|
|
bf3601ea8d | ||
|
|
c5566c8644 | ||
|
|
5944f5b55d | ||
|
|
077fbf6978 | ||
|
|
f02bdc4225 | ||
|
|
8da9ed4ea0 | ||
|
|
5ed4a2ddd0 | ||
|
|
6293e2808b | ||
|
|
c6dc562769 | ||
|
|
a607396d04 | ||
|
|
365f8dcfea | ||
|
|
b3eb709f4e | ||
|
|
fcbce0a66b | ||
|
|
05032e3126 | ||
|
|
b8a7189e24 | ||
|
|
881aba3b03 | ||
|
|
5480774afe | ||
|
|
d62c8af20c | ||
|
|
7d0644f3c8 | ||
|
|
fe21c0addd | ||
|
|
219143c02d | ||
|
|
03930a29c3 | ||
|
|
5d378cd9f2 | ||
|
|
e5db402025 | ||
|
|
d348ef3127 | ||
|
|
9301c0c7dd | ||
|
|
02ee8e64c8 | ||
|
|
40eab2e461 | ||
|
|
d6a65ca681 | ||
|
|
f8841bab35 | ||
|
|
dc23a116dd | ||
|
|
024994e85a | ||
|
|
9be1633320 | ||
|
|
3b15873fc7 | ||
|
|
786f19c57d | ||
|
|
ccb874e97a | ||
|
|
f7fd7a57c3 | ||
|
|
23f79aef58 | ||
|
|
2c394cc636 | ||
|
|
d7a0139e4f | ||
|
|
562d23005d | ||
|
|
671c152463 | ||
|
|
3ce7c7939f | ||
|
|
28450ccfc6 | ||
|
|
31dbd68194 | ||
|
|
29d776061c | ||
|
|
0805ec914e | ||
|
|
98ac6a6ce1 | ||
|
|
823120cdef | ||
|
|
1b13c6c53b | ||
|
|
036de96143 | ||
|
|
0b046cae9c | ||
|
|
a4c5341b3b | ||
|
|
5cbfab4f99 | ||
|
|
baf887155b | ||
|
|
a5bbe94871 | ||
|
|
ab26b28130 | ||
|
|
2606031ed2 | ||
|
|
72f9d32a2f | ||
|
|
4b87bbf1cd | ||
|
|
c9802fdcf2 | ||
|
|
e7f252953d | ||
|
|
7f299d8348 | ||
|
|
9d83d05c96 | ||
|
|
d6919683c3 | ||
|
|
879955c4a2 | ||
|
|
6026e0b908 | ||
|
|
32ef5404d8 | ||
|
|
3544a36de0 | ||
|
|
06a1894946 | ||
|
|
40bb5b7a74 | ||
|
|
062c2fa331 | ||
|
|
49846dbc13 | ||
|
|
e5a47aa74d | ||
|
|
77fffdf370 | ||
|
|
eaad0f2450 | ||
|
|
77faf38d61 | ||
|
|
134d220298 | ||
|
|
d4610f5f16 | ||
|
|
f5129b3283 | ||
|
|
ee6583fa82 | ||
|
|
af72895e54 | ||
|
|
8d17ad3dd1 | ||
|
|
723dd81e2b | ||
|
|
5c45d24e2e | ||
|
|
ffe72b89a7 | ||
|
|
bc92d6cebf | ||
|
|
b4bc3ca6cf | ||
|
|
8439272d00 | ||
|
|
c5a7442415 | ||
|
|
31b7fdf02b | ||
|
|
dafe4fe918 | ||
|
|
6737146571 | ||
|
|
33d1fd578e | ||
|
|
0c9edf93c3 | ||
|
|
241f140a98 | ||
|
|
0311c7373e | ||
|
|
ca95fa78c4 | ||
|
|
7fa5d77db7 | ||
|
|
dcc3baae26 | ||
|
|
ccb270612c | ||
|
|
404b601012 | ||
|
|
02d83a766d | ||
|
|
f40da353a2 | ||
|
|
c0c550e209 | ||
|
|
29ac2ad58b | ||
|
|
9b0b273004 | ||
|
|
17f120f5ee | ||
|
|
284cfc7ebc | ||
|
|
aa5d30eecc | ||
|
|
c11ecfcb6f | ||
|
|
aa251af116 | ||
|
|
ea7bc750d2 | ||
|
|
a043512f0e | ||
|
|
5805c310c1 | ||
|
|
bf0135d48e | ||
|
|
e1f950d3e6 | ||
|
|
018780620d | ||
|
|
ba2f8df30b | ||
|
|
ba77fc6677 | ||
|
|
ff3a5c4b9b | ||
|
|
3aef0e5432 | ||
|
|
83fb754e8e | ||
|
|
5042448e98 | ||
|
|
bca18170e7 | ||
|
|
524468135d | ||
|
|
de5fe9b950 | ||
|
|
17ed98ee4d | ||
|
|
1b6c2d1f93 | ||
|
|
7c90aee994 | ||
|
|
96bdfac0b9 | ||
|
|
359661b549 | ||
|
|
1e43902fff | ||
|
|
84a79f6833 | ||
|
|
bcd3b03a75 | ||
|
|
1ff79a7825 | ||
|
|
c3cdddc46e | ||
|
|
ac2f1f602b | ||
|
|
40ea803c56 | ||
|
|
dce987b345 | ||
|
|
5804aa100d | ||
|
|
f1276a64a0 | ||
|
|
503bbaaa46 | ||
|
|
0dbd8439bd | ||
|
|
40e5d1a4c1 | ||
|
|
58803cd221 | ||
|
|
ff0a226f53 | ||
|
|
1d19286f6b | ||
|
|
5cf114143c | ||
|
|
8331a1fa7f | ||
|
|
db7bee7902 | ||
|
|
2ba0f37a5a | ||
|
|
31f873ffc2 | ||
|
|
f7b5d5c62f | ||
|
|
94570d8c7c | ||
|
|
f6036343c9 | ||
|
|
cc8060db5b | ||
|
|
511f4d5b64 | ||
|
|
b04a7708b1 | ||
|
|
dd512d02e7 | ||
|
|
ff89c09b6f | ||
|
|
429e018749 | ||
|
|
babe37a0f0 | ||
|
|
122df21223 | ||
|
|
016c53061c | ||
|
|
429704676a | ||
|
|
cda1ca6897 | ||
|
|
8954dee6d6 | ||
|
|
b71e12ec5c | ||
|
|
c2055a322d | ||
|
|
583700006c | ||
|
|
928c2fcc19 | ||
|
|
3c913d29e3 | ||
|
|
52e015ad14 | ||
|
|
bef8cf9a0e | ||
|
|
eae4cb2160 | ||
|
|
955c94ed79 | ||
|
|
95109daedb | ||
|
|
00f28fdb79 | ||
|
|
aad014a0ba | ||
|
|
4917bd0591 | ||
|
|
54943b22f8 | ||
|
|
ec6e26c298 | ||
|
|
3db958a76b | ||
|
|
f5f2a2ac4c | ||
|
|
26fe00b559 | ||
|
|
899d793e1c | ||
|
|
430e2b9dcc | ||
|
|
9189c35585 | ||
|
|
0855b4da4e | ||
|
|
ad9c6581ff | ||
|
|
293d303c16 | ||
|
|
e4a4197482 | ||
|
|
2886e29d58 | ||
|
|
5868cb1d78 | ||
|
|
188870dbe6 | ||
|
|
e2c90dced3 | ||
|
|
9fe64c0ec3 | ||
|
|
75873d2b92 | ||
|
|
cd2cc9b6a2 | ||
|
|
3bf345247e | ||
|
|
16f3e038d8 | ||
|
|
2cd7be00af | ||
|
|
92df70c864 | ||
|
|
10816add64 | ||
|
|
cd58eebf96 | ||
|
|
b04ad86537 | ||
|
|
6d2caca39c | ||
|
|
9472fac99d | ||
|
|
0c5b5900a5 | ||
|
|
c63935a34a | ||
|
|
f4f94eafce | ||
|
|
c546e8a709 | ||
|
|
bd533da585 | ||
|
|
609d56613a | ||
|
|
3675df76f5 | ||
|
|
26dd6eac3f | ||
|
|
eb5eb6b1eb | ||
|
|
bc79de3c4d | ||
|
|
2a57c57c11 | ||
|
|
9e0b51efc6 | ||
|
|
43abb25114 | ||
|
|
8feac720ac | ||
|
|
8ac9209c3b | ||
|
|
18a1f51397 | ||
|
|
a12ea5d020 | ||
|
|
413b9503a5 | ||
|
|
8fe9297a37 | ||
|
|
bfb4ad5743 | ||
|
|
f400ad99b8 | ||
|
|
029c3614a6 | ||
|
|
5c5b8f5e0c | ||
|
|
9354895f73 | ||
|
|
b502d5dcc6 | ||
|
|
dffd6eee25 | ||
|
|
f2bf74b4d1 | ||
|
|
38e7874b42 | ||
|
|
bc200f3677 | ||
|
|
f7cc0ffd27 | ||
|
|
91429de35d | ||
|
|
7bf3830c62 | ||
|
|
0bdc79715e | ||
|
|
cef369110b | ||
|
|
63f42de828 | ||
|
|
f862274a41 | ||
|
|
7197c28f62 | ||
|
|
8640a2a18b | ||
|
|
e248562592 | ||
|
|
1d2a47aea0 | ||
|
|
5c338f8f5c | ||
|
|
dafd087783 | ||
|
|
1163ae5a41 | ||
|
|
81d85711fc | ||
|
|
5567e76ce6 | ||
|
|
e4ab06cd73 | ||
|
|
622dd4b6fa | ||
|
|
3b5e5893c0 | ||
|
|
7fd8a46e94 | ||
|
|
1f310a1068 | ||
|
|
342c3cd7d6 | ||
|
|
31b8faefca | ||
|
|
9ddd3b1ee8 | ||
|
|
a7b1a4546e | ||
|
|
110200f6b2 | ||
|
|
b776e6e200 | ||
|
|
580d6da37f | ||
|
|
fd6ae9cf6c | ||
|
|
200f798437 | ||
|
|
00bc6c3d19 | ||
|
|
54a0234c6b | ||
|
|
e0fcb1a3ed | ||
|
|
696a1755b3 | ||
|
|
4631950db8 | ||
|
|
3bb9449c60 | ||
|
|
a966299926 | ||
|
|
c2bf9da77d | ||
|
|
2ac6471cff | ||
|
|
c79d27eca8 | ||
|
|
99c67e963b | ||
|
|
a7b253ce9f | ||
|
|
c25c784feb | ||
|
|
b2d7e0a681 | ||
|
|
da40dfb824 | ||
|
|
2177534a5b | ||
|
|
77ac4d8d00 | ||
|
|
d56fbf0318 | ||
|
|
e9ac655835 | ||
|
|
203e4cce81 | ||
|
|
4d842ec1c9 | ||
|
|
66d6e14edb | ||
|
|
25879b234c | ||
|
|
fcc4df0342 | ||
|
|
70e5ad8874 | ||
|
|
612b5d8009 | ||
|
|
d2fdda09d5 | ||
|
|
fc4bdb04b9 | ||
|
|
de4d1370c7 | ||
|
|
a06500d4ed | ||
|
|
8d3b99fbf7 | ||
|
|
c90b7832df | ||
|
|
9f59231d58 | ||
|
|
592bf1a922 | ||
|
|
6e326e8e18 | ||
|
|
e888708b0a | ||
|
|
3f668fa5c8 | ||
|
|
6e08aaeb34 | ||
|
|
c32f794a6f | ||
|
|
d2680209eb | ||
|
|
253bb2d7f6 | ||
|
|
8d4ced40a6 | ||
|
|
2cc04ee79d | ||
|
|
e123e7ba8f | ||
|
|
8920d9f992 | ||
|
|
6c905a59c8 | ||
|
|
5464394dff | ||
|
|
90f8242dc0 | ||
|
|
3fc00b02dd | ||
|
|
2b55a4c5d6 | ||
|
|
a8799fd60c | ||
|
|
8fb4a985f6 | ||
|
|
79d09e75fc | ||
|
|
5589b5a4d5 | ||
|
|
1e661c597a | ||
|
|
4e7755865c | ||
|
|
960d2f3cf1 | ||
|
|
7debbc155f | ||
|
|
fbdd68e9d3 | ||
|
|
12b2222819 | ||
|
|
da3b1ec2f2 | ||
|
|
66b49a6369 | ||
|
|
9d3ae3c974 | ||
|
|
5f80db48de | ||
|
|
8481785b3b | ||
|
|
1a6f6aee96 | ||
|
|
e1e49bb19b | ||
|
|
ca7891e5cb | ||
|
|
dbf0776dbf | ||
|
|
040605e2a9 | ||
|
|
16f047f643 | ||
|
|
f99eeae7a6 | ||
|
|
8cb5ed91ba | ||
|
|
3fe7644f72 | ||
|
|
9f6879ecc9 | ||
|
|
5c44707bc9 | ||
|
|
ee2a77d570 | ||
|
|
770bad3a6d | ||
|
|
e8ad0e605f | ||
|
|
3267ebf0d9 | ||
|
|
5a2c364c50 | ||
|
|
d3c16b45c6 | ||
|
|
dff2a1e487 | ||
|
|
5dce8af395 | ||
|
|
a844618ca2 | ||
|
|
8c43f6f67a | ||
|
|
09e1b17905 | ||
|
|
2294ef80d5 | ||
|
|
4dd87e7308 | ||
|
|
47e462e836 | ||
|
|
00a1a7d1e2 | ||
|
|
eeba677da9 | ||
|
|
9f0a4988a3 | ||
|
|
ede100c9da | ||
|
|
3b65050ef3 | ||
|
|
63fedc046b | ||
|
|
741c8aa5fc | ||
|
|
bdddf364a0 | ||
|
|
109fa509fa | ||
|
|
90f79be163 | ||
|
|
2d0a1cdabb | ||
|
|
3bc8b0317e | ||
|
|
d361a323a2 | ||
|
|
ceb364472e | ||
|
|
41f04a11b3 | ||
|
|
1a24ed42e1 | ||
|
|
75bc9300fb | ||
|
|
e76d3c3d26 | ||
|
|
6f3264676c | ||
|
|
67846d6c4b | ||
|
|
2c588b6a93 | ||
|
|
f102b0d681 | ||
|
|
86a0c8a04e | ||
|
|
75b13d0052 | ||
|
|
9654d5ad98 | ||
|
|
07fb33be62 | ||
|
|
9d8a653ab8 | ||
|
|
4a7115b103 | ||
|
|
b78d989394 | ||
|
|
88baab88e9 | ||
|
|
3f0082d6e7 | ||
|
|
0df50ecf5d | ||
|
|
55144f0a9e | ||
|
|
a8d5651ab0 | ||
|
|
535aecdc01 | ||
|
|
f7727c0584 | ||
|
|
1e52320014 | ||
|
|
3cd12d4298 | ||
|
|
f94ec82c96 | ||
|
|
3a0e32d24f | ||
|
|
6eba4bbeda | ||
|
|
724286f497 | ||
|
|
7c8b7eee57 | ||
|
|
c23472903a | ||
|
|
846dbd6259 | ||
|
|
05447c7894 | ||
|
|
c4c7e17361 | ||
|
|
f3057dd9c3 | ||
|
|
0e195cfb9e | ||
|
|
611280b069 | ||
|
|
74e2ff7b85 | ||
|
|
326b81c225 | ||
|
|
3d4a281da6 | ||
|
|
ff4a08541c | ||
|
|
7f5acf3b2d | ||
|
|
eed0665e42 | ||
|
|
85c549c15c | ||
|
|
5763ae5b72 | ||
|
|
55cad46640 | ||
|
|
65985173fa | ||
|
|
7dfd6ab8cd | ||
|
|
ba3481356c | ||
|
|
58467843d5 | ||
|
|
3219c40629 | ||
|
|
d35fe9726b | ||
|
|
d311a3ad5b | ||
|
|
358d91251e | ||
|
|
25256fad3c | ||
|
|
3f4056c9f9 | ||
|
|
055f62026e | ||
|
|
593456e53e | ||
|
|
ef9f77c665 | ||
|
|
dc88d62f43 | ||
|
|
6909b5eca1 | ||
|
|
949195fb9c | ||
|
|
23aa7156dc | ||
|
|
52694d0999 | ||
|
|
3e4c15e019 | ||
|
|
9e645db007 | ||
|
|
3040692852 | ||
|
|
193c443bc4 | ||
|
|
cb184f89ec | ||
|
|
c2d89243b6 | ||
|
|
75d085d733 | ||
|
|
e550440407 | ||
|
|
f1eb67bf52 | ||
|
|
efafb1acea | ||
|
|
56c6c392b3 | ||
|
|
d5afb340a2 | ||
|
|
f95fbf57b6 | ||
|
|
6b5db32522 | ||
|
|
300ef8fef1 | ||
|
|
e09c256c80 | ||
|
|
7174e776ac | ||
|
|
f10ea83530 | ||
|
|
e19f2155b1 | ||
|
|
86a18067dd | ||
|
|
15caa7a6f7 | ||
|
|
b2897bc86c | ||
|
|
cc486ad94b | ||
|
|
c5c22fe1a5 | ||
|
|
302beb2d6e | ||
|
|
a702040dfb | ||
|
|
1510e5922e | ||
|
|
92344d2e30 | ||
|
|
5ce3315967 | ||
|
|
858199e298 | ||
|
|
0739a40abf | ||
|
|
215bbdb9da | ||
|
|
c9d4d07dc7 | ||
|
|
7e1442e969 | ||
|
|
fcfde61b24 | ||
|
|
fe6f5eb821 | ||
|
|
512a3fc927 | ||
|
|
3bf06a1cc9 | ||
|
|
2c3d61e629 | ||
|
|
20051109dd | ||
|
|
267cc55e40 | ||
|
|
766937d5fe | ||
|
|
18d8fe7248 | ||
|
|
f7cb738803 | ||
|
|
400ef63c5a | ||
|
|
1ff6a1656c | ||
|
|
20da3f85f1 | ||
|
|
d5e14a7101 | ||
|
|
ffa8622903 | ||
|
|
8b559639e8 | ||
|
|
628c0e8975 | ||
|
|
1734ec1dbf | ||
|
|
b10ae8dfa5 | ||
|
|
dc634fa35c | ||
|
|
06246aa3d7 | ||
|
|
7c1eb4f88c | ||
|
|
38634ef5ca | ||
|
|
5d76d23d33 | ||
|
|
da422c913f | ||
|
|
ba52b932ee | ||
|
|
1b90b7bebf | ||
|
|
23ca9c14c1 | ||
|
|
ab300be709 | ||
|
|
e6aaca19f6 | ||
|
|
0fe226132d | ||
|
|
452e3a58d8 | ||
|
|
e8f60a23ef | ||
|
|
958a6f49ce | ||
|
|
2005628358 | ||
|
|
439901b42e | ||
|
|
7bbe356009 | ||
|
|
9d710b0490 | ||
|
|
d140caf4ba | ||
|
|
15b691ad7b | ||
|
|
02b53e4900 | ||
|
|
5b5d76aa59 | ||
|
|
ae861214cb | ||
|
|
4d5558f3d8 | ||
|
|
0af6f61fb9 | ||
|
|
72a2e9c895 | ||
|
|
4f1bbd3f9b | ||
|
|
b99089dd88 | ||
|
|
380eb5cbaf | ||
|
|
a398eb17c4 | ||
|
|
9765f5efe5 | ||
|
|
d6e1f0c90d | ||
|
|
f56fa1955e | ||
|
|
277abfab2b | ||
|
|
5c1b0ae184 | ||
|
|
ee952c0789 | ||
|
|
eb1d54e750 | ||
|
|
0d8fd214a1 | ||
|
|
07b0af53f1 | ||
|
|
7ffa3d5bf2 | ||
|
|
615d52aff2 | ||
|
|
4961d4ee99 | ||
|
|
33a020a34b | ||
|
|
6ba44d6cb0 | ||
|
|
277d16895b | ||
|
|
dbd19159b6 | ||
|
|
9f5c88ffa1 | ||
|
|
a528a11b8c | ||
|
|
a52f4c5e95 | ||
|
|
0be42bf047 | ||
|
|
ba31f3611a | ||
|
|
3119a0cb6e | ||
|
|
5326c40347 | ||
|
|
bbcb19d476 | ||
|
|
e6d6972ab0 | ||
|
|
4f8da59577 | ||
|
|
6686812504 | ||
|
|
80993df87f | ||
|
|
30361954f3 | ||
|
|
04965f38c6 | ||
|
|
5d8360333a | ||
|
|
2a88adf6b3 | ||
|
|
38e70cb678 | ||
|
|
878427edb5 | ||
|
|
4dcf83f909 | ||
|
|
c14bf35144 | ||
|
|
3ae09b6d5a | ||
|
|
b778195dd1 | ||
|
|
5f8bb643b8 | ||
|
|
af35608953 | ||
|
|
647f158788 | ||
|
|
e42b8c7b9f | ||
|
|
d9387f6a09 | ||
|
|
1804cdb183 | ||
|
|
2ad593341f | ||
|
|
1d18a14356 | ||
|
|
f1a1db0834 | ||
|
|
1ebce748d4 | ||
|
|
836129ec8a | ||
|
|
97a5e2ba14 | ||
|
|
f75f83f653 | ||
|
|
7a16a7465a | ||
|
|
62a3f73de9 | ||
|
|
113fa19bed | ||
|
|
7b8d6577ed | ||
|
|
36f9701b5b | ||
|
|
ec4ebf5a15 | ||
|
|
c1988f1858 | ||
|
|
be85dd06e5 | ||
|
|
4366fbe512 | ||
|
|
987b3846ef | ||
|
|
f40c74b153 | ||
|
|
23ca7357d4 | ||
|
|
95af46cbf7 | ||
|
|
56af0d6a11 | ||
|
|
c39046d999 | ||
|
|
476336be68 | ||
|
|
e0bd5b8038 | ||
|
|
0691207413 | ||
|
|
16ea626c28 | ||
|
|
81788550bf | ||
|
|
ed2bd14a71 | ||
|
|
7b66ff2304 | ||
|
|
54f2e75139 | ||
|
|
279b0755ea | ||
|
|
b44deb393d | ||
|
|
714af8ebe0 | ||
|
|
b4a1730e37 | ||
|
|
a85b3be037 | ||
|
|
90b8946429 | ||
|
|
09192474d7 | ||
|
|
6061d5f438 | ||
|
|
7aa9a0c9a8 | ||
|
|
b1a37a7674 | ||
|
|
ee413f06dd | ||
|
|
fbbc0b2dcc | ||
|
|
5c07c41e62 | ||
|
|
9dd4791bb8 | ||
|
|
9a842207ac | ||
|
|
e139ae252f | ||
|
|
fb8fd8b7d4 | ||
|
|
6bab076329 | ||
|
|
ec65cb3d27 | ||
|
|
96720e235f | ||
|
|
c2b68dcd90 | ||
|
|
7a09f0c02c | ||
|
|
1b0a0a476d | ||
|
|
28883fb9ed | ||
|
|
1b38dab9aa | ||
|
|
ab99cc6f74 | ||
|
|
8ba14b0e7b | ||
|
|
a7fc70a5c9 | ||
|
|
204dae213c | ||
|
|
fdaab2b229 | ||
|
|
e67643e0b1 | ||
|
|
d1d77dd85c | ||
|
|
766bdbcd62 | ||
|
|
7245553588 | ||
|
|
fddf0e92cb | ||
|
|
83c2ea2881 | ||
|
|
c170c25693 | ||
|
|
03f6f90636 | ||
|
|
bcc0a44979 | ||
|
|
6c20b70a14 | ||
|
|
c6bdd753df | ||
|
|
99ad396fa3 | ||
|
|
cdb3acc691 | ||
|
|
71b4a58cab | ||
|
|
934bce7bdc | ||
|
|
dd5ea7748b | ||
|
|
4fef6dc3b6 | ||
|
|
cb669335a3 | ||
|
|
68970b6716 | ||
|
|
d80682333a | ||
|
|
a8dec0cd70 | ||
|
|
6d0279fc37 | ||
|
|
f5fb39538c | ||
|
|
1b693b0752 | ||
|
|
4ca0e0deb5 | ||
|
|
26dbf918cc | ||
|
|
82adf357d2 | ||
|
|
8022643b8e | ||
|
|
3a44168d8c | ||
|
|
2818539686 | ||
|
|
6ad0502cce | ||
|
|
dccafc5e27 | ||
|
|
1653839753 | ||
|
|
7e09c25f5b | ||
|
|
f7fef85353 | ||
|
|
83667e6232 | ||
|
|
fa86e53b7d | ||
|
|
e43ec564e0 | ||
|
|
a25233ccdc | ||
|
|
a3a75b89b9 | ||
|
|
8733afe512 | ||
|
|
6059f21f2d | ||
|
|
02d9a507b4 | ||
|
|
d0a9a59de7 | ||
|
|
6dc5ac826e | ||
|
|
807b9ab09f | ||
|
|
45fc68f08a | ||
|
|
18f2334ddd | ||
|
|
6607645786 | ||
|
|
9497378600 | ||
|
|
4215b1c72e | ||
|
|
4300aff991 | ||
|
|
ca88b00d95 | ||
|
|
e3a5f329ad | ||
|
|
42916a8b6a | ||
|
|
12ed0107ab | ||
|
|
6016ee966e | ||
|
|
555ec09ef5 | ||
|
|
b30ea36fcc | ||
|
|
4c0d92507a | ||
|
|
8421e4c150 | ||
|
|
be3a6dee93 | ||
|
|
a76a868cb6 | ||
|
|
707d519110 | ||
|
|
a93e8a9f10 | ||
|
|
65da3049ba | ||
|
|
4a00429a57 | ||
|
|
da6303a0b0 | ||
|
|
96f8ee85e0 | ||
|
|
9f9a6924a7 | ||
|
|
8e6fa51c27 | ||
|
|
047187257b | ||
|
|
2f77ccfc6e | ||
|
|
d19245c23d | ||
|
|
52703536a6 | ||
|
|
c5dc625550 | ||
|
|
6405fe6bb3 | ||
|
|
c7367ae33d | ||
|
|
1df9632e31 | ||
|
|
94a1ec3902 | ||
|
|
c05c0160c3 | ||
|
|
6193aa2faa | ||
|
|
ca79c71851 | ||
|
|
d83149dc70 | ||
|
|
2c4167bc11 | ||
|
|
117f3be390 | ||
|
|
b81035f9bf | ||
|
|
8b38bcfbdd | ||
|
|
f47de1b71c | ||
|
|
61f2ea3511 | ||
|
|
9169beaa12 | ||
|
|
9f0041b196 | ||
|
|
6cc35815ba | ||
|
|
c88dfb4947 | ||
|
|
a27149aa70 | ||
|
|
8e9dea898f | ||
|
|
73d3c27c25 | ||
|
|
b9eeebdbea | ||
|
|
1f33aec825 | ||
|
|
2d891a4248 | ||
|
|
e533c87d08 | ||
|
|
c5f5fdb089 | ||
|
|
87fdb84202 | ||
|
|
d78ead2927 | ||
|
|
a4148102eb | ||
|
|
69affd43bb | ||
|
|
1dce731aa4 | ||
|
|
f3c0541ade | ||
|
|
eb40d3bda5 | ||
|
|
533c8f7ede | ||
|
|
b29339cd1a | ||
|
|
814bc4a3bf | ||
|
|
b05a5c7f54 | ||
|
|
eba71982c6 | ||
|
|
4a6c81b136 | ||
|
|
4f671f1e5b | ||
|
|
01a076b6ed | ||
|
|
f0f3e65bb0 | ||
|
|
2398fabffb | ||
|
|
a03c93b006 | ||
|
|
5c78c5ba2c | ||
|
|
c8d0ae6a9d | ||
|
|
3867e4bdca | ||
|
|
bdad46e96d | ||
|
|
d03199ccda | ||
|
|
0c4f22173e | ||
|
|
17bafc2b5e | ||
|
|
666aaf4428 | ||
|
|
618e093e1c | ||
|
|
2f865a5c7f | ||
|
|
aaf5084fd9 | ||
|
|
44c14ec4ce | ||
|
|
66882d9677 | ||
|
|
bc1d36ddea | ||
|
|
2d5ef19b3b | ||
|
|
a61016d2df | ||
|
|
f2f6b4c859 | ||
|
|
9f92d64641 | ||
|
|
bb71dcba91 | ||
|
|
23a38f76f9 | ||
|
|
9a8bda8b11 | ||
|
|
3c10cf5181 | ||
|
|
c5095e0a8d | ||
|
|
3486fe3dde | ||
|
|
56fe047e3a | ||
|
|
26b253d8ca | ||
|
|
3f85f1dd70 | ||
|
|
dad829dd91 |
@@ -48,19 +48,19 @@ body:
|
||||
attributes:
|
||||
label: Mobile Device Model
|
||||
description: (Mobile users only) What mobile device are you playing on?
|
||||
placeholder: ex. iPhone 17, Galaxy S25, iPad 11th Gen
|
||||
placeholder: ex. iPhone 17, Galaxy S26, iPad 11th Gen
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: Mobile OS Version
|
||||
description: (Mobile users only) What version is your Operating System?
|
||||
placeholder: ex. iOS 26.0, Android 15, iPadOS 26.0
|
||||
placeholder: ex. iOS 26.4, Android 16, iPadOS 26.4
|
||||
|
||||
- type: input
|
||||
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.7.5
|
||||
placeholder: ex. 0.8.4 or 0.8.5
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
@@ -36,7 +36,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.7.5
|
||||
placeholder: ex. 0.8.4 or 0.8.5
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -52,7 +52,7 @@ body:
|
||||
attributes:
|
||||
label: Location
|
||||
description: Where did you find the issue(s)? Include the name of the song, the variation, the difficulty, the time/section of the song, and any images or videos.
|
||||
placeholder: ex. Cocoa Erect on Erect/Nightmare difficulties at Section 30
|
||||
placeholder: ex. Cocoa Erect [Nightmare] at Section 30, step 5
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
@@ -36,7 +36,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.7.5
|
||||
placeholder: ex. 0.8.4 or 0.8.5
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
@@ -49,19 +49,19 @@ body:
|
||||
attributes:
|
||||
label: Mobile Device Model
|
||||
description: (Mobile users only) What mobile device are you playing on?
|
||||
placeholder: ex. iPhone 17, Galaxy S25, iPad 11th Gen
|
||||
placeholder: ex. iPhone 17, Galaxy S26, iPad 11th Gen
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: Mobile OS Version
|
||||
description: (Mobile users only) What version is your Operating System?
|
||||
placeholder: ex. iOS 26.0, Android 15, iPadOS 26.0
|
||||
placeholder: ex. iOS 26.4, Android 16, iPadOS 26.4
|
||||
|
||||
- type: input
|
||||
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.7.5
|
||||
placeholder: ex. 0.8.4 or 0.8.5
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
@@ -35,14 +35,14 @@ jobs:
|
||||
docker:
|
||||
- '.github/workflows/build-game.yml'
|
||||
- '**/Dockerfile'
|
||||
- uses: actions/github-script@v7
|
||||
- uses: actions/github-script@v8
|
||||
id: should-trigger
|
||||
with:
|
||||
result-encoding: string
|
||||
script: |
|
||||
const { payload } = context
|
||||
const changes = ${{ steps.filter.outputs.changes }}
|
||||
const manual = payload.commits
|
||||
const manual = payload?.commits
|
||||
.some(c => c.message.toLowerCase().replace(/\W/g, " ").includes("docker rebuild"))
|
||||
|
||||
console.log({ payload, changes, manual, commits: payload.commits })
|
||||
@@ -167,6 +167,11 @@ jobs:
|
||||
cd .haxelib/hxcpp/git/tools/hxcpp
|
||||
haxe compile.hxml
|
||||
cd ../../../../..
|
||||
- name: Populate .env
|
||||
run: |
|
||||
touch .env
|
||||
grep API_NG_APP_ID .env || echo API_NG_APP_ID=${{ secrets.API_NG_APP_ID }} >> .env
|
||||
grep API_NG_ENC_KEY .env || echo API_NG_ENC_KEY=${{ secrets.API_NG_ENC_KEY }} >> .env
|
||||
- name: Build game (windows)
|
||||
if: ${{ matrix.target == 'windows' }}
|
||||
run: |
|
||||
@@ -246,6 +251,11 @@ jobs:
|
||||
path: /usr/share/hxcpp
|
||||
key: haxe-hxcpp-${{ runner.os }}-${{ github.ref_name }}-${{ github.sha }}
|
||||
restore-keys: haxe-hxcpp-${{ runner.os }}-${{ github.ref_name }}
|
||||
- name: Populate .env
|
||||
run: |
|
||||
touch .env
|
||||
grep API_NG_APP_ID .env || echo API_NG_APP_ID=${{ secrets.API_NG_APP_ID }} >> .env
|
||||
grep API_NG_ENC_KEY .env || echo API_NG_ENC_KEY=${{ secrets.API_NG_ENC_KEY }} >> .env
|
||||
- name: Build game
|
||||
run: |
|
||||
haxelib run lime build ${{ matrix.target }} -v -release --times $BUILD_DEFINES
|
||||
|
||||
@@ -13,6 +13,10 @@ shitAudio/
|
||||
.swp
|
||||
NewgroundsCredentials.hx
|
||||
|
||||
# Exclude IntelliJ IDEA stuff
|
||||
.idea/
|
||||
*.iml
|
||||
|
||||
# Mobile signing stuff
|
||||
.apple/
|
||||
.env
|
||||
|
||||
@@ -4,3 +4,4 @@
|
||||
[submodule "art"]
|
||||
path = art
|
||||
url = https://github.com/FunkinCrew/Funkin.art
|
||||
shallow = true
|
||||
|
||||
Vendored
+25
-88
@@ -1,31 +1,22 @@
|
||||
{
|
||||
"[haxe]": {
|
||||
// Automatically keep Haxe files formatted.
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnPaste": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": "never"
|
||||
},
|
||||
"editor.codeActionsOnSave": { "source.organizeImports": "never" },
|
||||
"editor.defaultFormatter": "nadako.vshaxe",
|
||||
"editor.tabSize": 2
|
||||
},
|
||||
|
||||
"[json]": {
|
||||
// Automatically keep JSON files formatted.
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnPaste": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
|
||||
"[jsonc]": {
|
||||
// Automatically keep JSONC files formatted.
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnPaste": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"prettier.tabWidth": 2,
|
||||
|
||||
// XML formatting style configuration
|
||||
// XML Formatting
|
||||
"xml.format.enabled": true,
|
||||
"xml.format.legacy": false,
|
||||
"xml.format.emptyElements": "collapse",
|
||||
@@ -33,7 +24,7 @@
|
||||
"xml.format.enforceQuoteStyle": "preferred",
|
||||
"xml.format.preserveAttributeLineBreaks": false,
|
||||
"xml.format.preservedNewlines": 0,
|
||||
"xml.format.splitAttributes": false,
|
||||
"xml.format.splitAttributes": "preserve",
|
||||
"xml.format.joinCDATALines": true,
|
||||
"xml.format.preserveEmptyContent": false,
|
||||
"xml.format.joinCommentLines": false,
|
||||
@@ -56,26 +47,19 @@
|
||||
"xml.format.maxLineWidth": 0,
|
||||
"xml.format.grammarAwareFormatting": true,
|
||||
|
||||
// Generic file formatting style configuration
|
||||
// General formatting
|
||||
"files.insertFinalNewline": true,
|
||||
"files.trimFinalNewlines": false,
|
||||
"files.trimTrailingWhitespace": true,
|
||||
|
||||
// Automatically detect indentation.
|
||||
"editor.detectIndentation": true,
|
||||
"editor.insertSpaces": true,
|
||||
"editor.tabSize": 2,
|
||||
|
||||
// Automatically enforce Linux style line endings.
|
||||
"prettier.tabWidth": 2,
|
||||
"files.eol": "\n",
|
||||
|
||||
"haxe.displayPort": "auto",
|
||||
"haxe.enableCompilationServer": true,
|
||||
"haxe.enableServerView": true,
|
||||
"haxe.displayServer": {
|
||||
"arguments": ["-v"]
|
||||
},
|
||||
// Fix file associations for HScript.
|
||||
"haxe.displayServer": { "arguments": ["-v"] },
|
||||
"files.associations": {
|
||||
"*.hxp": "haxe",
|
||||
"*.hscript": "haxe",
|
||||
@@ -84,14 +68,26 @@
|
||||
"*.hxc": "haxe"
|
||||
},
|
||||
"projectManager.git.baseFolders": ["./"],
|
||||
|
||||
"haxecheckstyle.sourceFolders": ["src", "source"],
|
||||
"haxecheckstyle.externalSourceRoots": [],
|
||||
"haxecheckstyle.configurationFile": "checkstyle.json",
|
||||
"haxecheckstyle.codeSimilarityBufferSize": 100,
|
||||
|
||||
"lime.projectFile": "project.hxp",
|
||||
|
||||
"lime.targets": [
|
||||
{ "name": "windows", "enabled": true, "label": "Windows" },
|
||||
{ "name": "mac", "enabled": true, "label": "macOS" },
|
||||
{ "name": "linux", "enabled": true, "label": "Linux" },
|
||||
{ "name": "html5", "enabled": true, "label": "HTML5" },
|
||||
{ "name": "android", "enabled": true, "label": "Android" },
|
||||
{ "name": "ios", "enabled": true, "label": "iOS" },
|
||||
{ "name": "hl", "enabled": true, "label": "HashLink" },
|
||||
// Disabled targets
|
||||
{ "name": "air", "enabled": false },
|
||||
{ "name": "electron", "enabled": false },
|
||||
{ "name": "flash", "enabled": false },
|
||||
{ "name": "neko", "enabled": false },
|
||||
{ "name": "tvos", "enabled": false }
|
||||
],
|
||||
"lime.defaultTargetConfiguration": "Windows / Debug",
|
||||
"lime.targetConfigurations": [
|
||||
{
|
||||
"label": "Windows / Debug (Discord)",
|
||||
@@ -103,21 +99,11 @@
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DANIMATE", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug (FlxAnimate Test)",
|
||||
"target": "hl",
|
||||
"args": ["-debug", "-DANIMATE"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (Straight to Freeplay)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DFREEPLAY", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug (Straight to Freeplay)",
|
||||
"target": "hl",
|
||||
"args": ["-debug", "-DFREEPLAY"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (Straight to Play - Bopeebo Normal)",
|
||||
"target": "windows",
|
||||
@@ -132,26 +118,6 @@
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DSONG=2hot", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug (Straight to Play - Bopeebo Normal)",
|
||||
"target": "hl",
|
||||
"args": ["-debug", "-DSONG=bopeebo -DDIFFICULTY=normal"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (Conversation Test)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DDIALOGUE", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug (Conversation Test)",
|
||||
"target": "hl",
|
||||
"args": ["-debug", "-DDIALOGUE"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (Results Screen Test)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DRESULTS"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (Straight to Stage Editor)",
|
||||
"target": "windows",
|
||||
@@ -172,36 +138,16 @@
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DHXVLC_LOGGING", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug (Straight to Animation Editor)",
|
||||
"target": "hl",
|
||||
"args": ["-debug", "-DANIMDEBUG"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (Latency Test)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DLATENCY", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug (Latency Test)",
|
||||
"target": "hl",
|
||||
"args": ["-debug", "-DLATENCY"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (Waveform Test)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DWAVEFORM", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Release (GitHub Actions)",
|
||||
"target": "windows",
|
||||
"args": ["-release", "-DGITHUB_BUILD"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug (Waveform Test)",
|
||||
"target": "hl",
|
||||
"args": ["-debug", "-DWAVEFORM"]
|
||||
},
|
||||
{
|
||||
"label": "HTML5 / Debug (Watch)",
|
||||
"target": "html5",
|
||||
@@ -209,10 +155,7 @@
|
||||
}
|
||||
],
|
||||
"lime.buildTypes": [
|
||||
{
|
||||
"label": "Debug",
|
||||
"args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{ "label": "Debug", "args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS"] },
|
||||
{
|
||||
"label": "Debug (Unlock Everything)",
|
||||
"args": ["-debug", "-DUNLOCK_EVERYTHING"]
|
||||
@@ -225,10 +168,7 @@
|
||||
"label": "Debug (Straight to Chart Editor)",
|
||||
"args": ["-debug", "-DCHARTING", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "Release",
|
||||
"args": ["-release"]
|
||||
},
|
||||
{ "label": "Release", "args": ["-release"] },
|
||||
{
|
||||
"label": "Release (GitHub Actions)",
|
||||
"args": ["-release", "-DGITHUB_BUILD"]
|
||||
@@ -244,9 +184,6 @@
|
||||
],
|
||||
"vscord.app.privacyMode.enable": true,
|
||||
"json.schemas": [
|
||||
{
|
||||
"fileMatch": ["/hmm.json"],
|
||||
"url": "./.vscode/schema/hmm.json"
|
||||
}
|
||||
{ "fileMatch": ["/hmm.json"], "url": "./.vscode/schema/hmm.json" }
|
||||
]
|
||||
}
|
||||
|
||||
+480
@@ -4,6 +4,486 @@ 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.8.5] - 2026-04-06
|
||||
|
||||
### Fixed
|
||||
|
||||
- [ANDROID] Fixed various issues with visuals, controls, and gamepad functionality.
|
||||
|
||||
|
||||
|
||||
## [0.8.4] - 2026-03-26
|
||||
|
||||
### Added
|
||||
|
||||
- Added several new features to Polymod:
|
||||
- Added FULL support for abstracts! (Previously, only `FlxColor` was supported)
|
||||
- Added support for getting the code of a single character string (i.e `’h’.code`) (Thanks NotHyper-474!)
|
||||
- Added support for import defaults (`import.hx`/`import.hxc`), allowing scripted classes with the same package to share imports. (Thanks KoloInDaCrib!)
|
||||
- Added `scriptInit` and `listScriptClasses` functions to scripted classes. (Thanks KoloInDaCrib!)
|
||||
- `.hx` files can now be parsed. (Thanks Starexify!)
|
||||
- Added support for preprocessing (`#if`/`#else` syntax), allowing for platform-specific code to be implemented in an easier way. (Thanks KoloInDaCrib!)
|
||||
- Added support for mod IDs. (Thanks Starexify!)
|
||||
- Implemented `_merge` support for scripts. (Thanks KoloInDaCrib!)
|
||||
- Please refer to the Cookbook to learn how to use this.
|
||||
- Added widescreen scale properties to the Zoom Camera event in the Chart Editor to account for widescreen resolutions.
|
||||
- Added a flipY field for Story Mode level props. ([48ed659](https://github.com/FunkinCrew/Funkin/commit/48ed65980005366371fda7a66c04f73bb5b86200)) - by @ExtraCode75 in [#6340](https://github.com/FunkinCrew/Funkin/pull/6340)
|
||||
- Implemented working start and end times for Freeplay song previews. ([40eab2e](https://github.com/FunkinCrew/Funkin/commit/40eab2e4618ebb6dc67a06ef67df817920773c56)) - by @ADA-Funni in [#6725](https://github.com/FunkinCrew/Funkin/pull/6725)
|
||||
- Added a new albumOSTName property to Freeplay album data to make the "OFFICIAL OST" text moddable. ([67d2a98](https://github.com/FunkinCrew/Funkin/commit/67d2a9811b5c92409bdc76e287c984d5777d8621)) - by @NotHyper-474 in [#6578](https://github.com/FunkinCrew/Funkin/pull/6578)
|
||||
- Added an optional 'capsule' field to level data to display any week label on Freeplay capsules. ([528e9e4](https://github.com/FunkinCrew/Funkin/commit/528e9e4f03c40e59f3932d8b4c522b6eeb66783b)) - by @Trayfellow in [#7149](https://github.com/FunkinCrew/Funkin/pull/7149)
|
||||
- Implemented ScriptedBaseFreeplayDJ, allowing modders to customize how custom Freeplay DJs are rendered. ([423ccfa](https://github.com/FunkinCrew/Funkin/commit/423ccfab98a29646e4998dacedaf403319d956ff)) - by @PurSnake in [#6806](https://github.com/FunkinCrew/Funkin/pull/6806)
|
||||
- Added a new FunkinGroup class to make groups of sprites easier to modify. ([4f31600](https://github.com/FunkinCrew/Funkin/commit/4f316003af9a6ca46c77bf4971059e20543cbd7b)) - by @MightyTheArmiddilo in [#6758](https://github.com/FunkinCrew/Funkin/pull/6758)
|
||||
- Added `Reflect.makeVarArgs` to allow modders to create functions with a variable number of arguments. ([3633df3](https://github.com/FunkinCrew/Funkin/commit/3633df3fdc6dc0af4f9886fcb1e1d4a8d2b8d5aa)) - by @Starexify in [#6738](https://github.com/FunkinCrew/Funkin/pull/6738)
|
||||
- Added script event support for song events. ([15bef89](https://github.com/FunkinCrew/Funkin/commit/15bef89d71982c5685f4ce6f6d7e6e4d8321508c)) - by @KoloInDaCrib in [#4130](https://github.com/FunkinCrew/Funkin/pull/4130)
|
||||
- Added a new Chart Editor event type: Set Target Bop Speed. ([1893f2b](https://github.com/FunkinCrew/Funkin/commit/1893f2b2f679d8a4d262052793dde24e94719ee6)) - by @roma-perec-bp in [#6769](https://github.com/FunkinCrew/Funkin/pull/6769)
|
||||
- Added shortcuts to select all notes above or below the playhead in the Chart Editor. ([08b3af9](https://github.com/FunkinCrew/Funkin/commit/08b3af959d6657511bfe8418da545731050cb24a)) - by @Lasercar in [#4306](https://github.com/FunkinCrew/Funkin/pull/4306)
|
||||
- Added the ability to view and edit the timing of a note or event through the Chart Editor context menu. ([a782b3f](https://github.com/FunkinCrew/Funkin/commit/a782b3f9e8498634babb864341fd1437bb2c01f6)) - by @Lasercar in [#4200](https://github.com/FunkinCrew/Funkin/pull/4200)
|
||||
- Added an "Edit Note" option to the Chart Editor context menu that opens the Notes window. ([e710198](https://github.com/FunkinCrew/Funkin/commit/e710198afec9a0fe19effe4cd196ab3a247f4784)) - by @Honton129 in [#7010](https://github.com/FunkinCrew/Funkin/pull/7010)
|
||||
- Added the ability to Ctrl-click on health icons in the Chart Editor to open character previews. ([011f154](https://github.com/FunkinCrew/Funkin/commit/011f1541b197fd4f962ab8a910cbd417f78c2d09)) - by @Trofem in [#7089](https://github.com/FunkinCrew/Funkin/pull/7089)
|
||||
|
||||
### Changed
|
||||
|
||||
- Upgraded the input handling system from SDL2 to SDL3!
|
||||
- Added an “Unlocked Framerate” preference to Desktop platforms.
|
||||
- The input system is now much more responsive and accurate down to the nanosecond.
|
||||
- The game now properly supports more controllers and input devices.
|
||||
- The game now shows popups when installed mods have issues, such as when the API version is incorrect.
|
||||
- Unfocusing the game now reduces the game’s volume to 25% (down from 50%).
|
||||
- The notekinds dropdown are no longer hardcoded in the Chart Editor.
|
||||
- The Character Select key is now hidden in the Controls menu until a new character is unlocked. ([6c13444](https://github.com/FunkinCrew/Funkin/commit/6c134442118d3f5f61b9e3faa631512d210422fb)) - by @Lasercar in [#4882](https://github.com/FunkinCrew/Funkin/pull/4882)
|
||||
- The Story Mode level score now displays with commas. ([04fffe8](https://github.com/FunkinCrew/Funkin/commit/04fffe8ade4c4b236d59a236f08534e8eede179f)) - by @JVNpixels in [#4965](https://github.com/FunkinCrew/Funkin/pull/4965)
|
||||
- Freeplay capsule week labels are now softcoded and rendered using a font. ([48752e4](https://github.com/FunkinCrew/Funkin/commit/48752e444cc5f1b8cd0a3b579f843e9ab6580c46)) - by @KoloInDaCrib in [#5249](https://github.com/FunkinCrew/Funkin/pull/5249)
|
||||
- The Freeplay change character hint now changes when a gamepad is connected. ([0f7859b](https://github.com/FunkinCrew/Funkin/commit/0f7859bfb6b829290804efab9d5c67fbdbec0903)) - by @MrMadera in [#6609](https://github.com/FunkinCrew/Funkin/pull/6609)
|
||||
- Moved the Winter Horrorland intro cutscene logic into a script. ([9bbe73b](https://github.com/FunkinCrew/funkin.assets/commit/9bbe73b21ccbea29a5d5187709f91b4822bf7e31)) - by @mario64thane in [funkin.assets#63](https://github.com/FunkinCrew/funkin.assets/pull/63)
|
||||
- Merged FlxFilteredSprite functionality into FunkinSprite. ([22736b2](https://github.com/FunkinCrew/Funkin/commit/22736b27da7d8de8b11ab6cc6c2a622e5579dccd)) - by @TechnikTil in [#7114](https://github.com/FunkinCrew/Funkin/pull/7114)
|
||||
- Adjusted some wording in the Chart/Stage Editor backup window to be clearer. ([a607396](https://github.com/FunkinCrew/Funkin/commit/a607396d04f24388691ee23bd04022681ca54756)) - by @Marcosayfi in [#7071](https://github.com/FunkinCrew/Funkin/pull/7071)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Conjoined string interpolation `$a$b` now parses properly in Polymod.
|
||||
- Video cutscenes are no longer muted at volumes of 3 or lower.
|
||||
- Video cutscenes no longer occasionally show glitches at the beginning.
|
||||
- [MOBILE] Loading a cloud save from Newgrounds no longer carries over the volume level from Desktop.
|
||||
- [HTML5] Scrolling the game out of view now pauses the game instead of allowing gameplay to continue processing.
|
||||
- The Boyfriend Freeplay DJ new rank animation now transitions smoothly back to its idle animation.
|
||||
- Hold notes now score much more consistently across different framerate values.
|
||||
- The bricks in Week 7 stages are now better quality.
|
||||
- Trimmed out the silent beat at the beginning of Ugh Erect.
|
||||
- Camera bopping no longer stops at zoom values of 1.35x or higher.
|
||||
- Charting many hold notes in a row no longer breaks audio handling.
|
||||
- Adding collapsible frames in event schemas no longer crashes the game.
|
||||
- Some event properties have been grouped under Advanced frames.
|
||||
- Chart Editor dropdowns no longer refocus when a tooltip appears.
|
||||
- Focus and Zoom Camera event durations now account for playback speed.
|
||||
- [WINDOWS] Exiting the game no longer leaves it running as a background process. ([881aba3](https://github.com/FunkinCrew/Funkin/commit/881aba3b03a26764374441cab244cd2d360a30b9)) - by @AnimatingLegend in [#6885](https://github.com/FunkinCrew/Funkin/pull/6885)
|
||||
- Story Mode now only shows difficulties that all of a level's songs have. ([365f8dc](https://github.com/FunkinCrew/Funkin/commit/365f8dcfea9569f2edba1ebfceb3d49819ad2b57)) - by @Starexify in [#7046](https://github.com/FunkinCrew/Funkin/pull/7046)
|
||||
- The Girlfriend Story Mode prop now bops continuously when scrolling through levels. ([84b2d5b](https://github.com/FunkinCrew/Funkin/commit/84b2d5b48d9eb76c918fdba6987ccc1f5da76133)) - by @Redar13 in [#6830](https://github.com/FunkinCrew/Funkin/pull/6830)
|
||||
- The Freeplay new rank slam animation now properly plays its glow effect. ([fcbce0a](https://github.com/FunkinCrew/Funkin/commit/fcbce0a66b75b0c1224c6a0e2907c170b71998ad)) - by @FuroYT in [#7015](https://github.com/FunkinCrew/Funkin/pull/7015)
|
||||
- The Freeplay Random capsule no longer displays BPM and difficulty numbers. ([0ad2d50](https://github.com/FunkinCrew/Funkin/commit/0ad2d507efa9dcb577184e28bfd04091b3424bfb)) - by @Lasercar in [#4927](https://github.com/FunkinCrew/Funkin/pull/4927)
|
||||
- Favoriting a song in Freeplay no longer breaks capsule text cycling. ([ce493a0](https://github.com/FunkinCrew/Funkin/commit/ce493a03c9ffb4c43c2c27885ed0fab0c1c3531d)) - by @VirtuGuy in [#7134](https://github.com/FunkinCrew/Funkin/pull/7134)
|
||||
- Freeplay songs with invalid pixel icons no longer duplicate the icon from the previous song in the list. ([b3eb709](https://github.com/FunkinCrew/Funkin/commit/b3eb709f4ed535ed2544f3bb530ff3724e07be3f)) - by @FuroYT in [#7006](https://github.com/FunkinCrew/Funkin/pull/7006)
|
||||
- A Freeplay clear percent of 100% now properly exits to the right when returning to the Main Menu. ([5974c1a](https://github.com/FunkinCrew/Funkin/commit/5974c1a48293f3185c5f249acb9130ebd6b35ae4)) - by @Trayfellow in [#7183](https://github.com/FunkinCrew/Funkin/pull/7183)
|
||||
- Nudged the Freeplay backing image down by one pixel to align with the bottom of the screen. ([b8c2854](https://github.com/FunkinCrew/Funkin/commit/b8c2854007f36ac7a2b6897eead3f979e11f0ad7)) - by @SuperEric1 in [#6893](https://github.com/FunkinCrew/Funkin/pull/6893)
|
||||
- Improved performance for songs that use a lot of events. ([3b27ec5](https://github.com/FunkinCrew/Funkin/commit/3b27ec5b66b2fa00d59a77f6ec07aff68e440860)) - by @KoloInDaCrib in [#7171](https://github.com/FunkinCrew/Funkin/pull/7171)
|
||||
- Adjusted hit window logic to accurately reflect threshold values for all judgments. ([dcb5b3a](https://github.com/FunkinCrew/Funkin/commit/dcb5b3a9358a9bb126153b57aa2d38dc0684c39d)) - by @Rudyrue in [#7104](https://github.com/FunkinCrew/Funkin/pull/7104)
|
||||
- Adjusted offsets for all health icons to be more consistent. ([83d8134](https://github.com/FunkinCrew/funkin.assets/commit/83d8134f9d0b3cf3b59e9e1d972fa1e200a98ba1)) - by @LeGooeyy in [funkin.assets#278](https://github.com/FunkinCrew/funkin.assets/pull/278)
|
||||
- Hold notes no longer shift upward when hit early. ([d62c8af](https://github.com/FunkinCrew/Funkin/commit/d62c8af20c90ec3d6e4274aba8ddbfb378ab0256)) - by @VirtuGuy in [#7060](https://github.com/FunkinCrew/Funkin/pull/7060)
|
||||
- The opponent no longer stops singing upon missing if Boyfriend has no vocals file. ([cea54e3](https://github.com/FunkinCrew/Funkin/commit/cea54e394c78544dd0ec0339721faaac140caa65)) - by @FuroYT in [#7007](https://github.com/FunkinCrew/Funkin/pull/7007)
|
||||
- onPause script functions now properly trigger when pausing during a cutscene or dialogue. ([5a0b431](https://github.com/FunkinCrew/Funkin/commit/5a0b4310a2c39b033989e0e36b8f456bdac65a5b)) - by @MightyTheArmiddilo in [#6804](https://github.com/FunkinCrew/Funkin/pull/6804)
|
||||
- Camera events now consistently pause when the Pause Menu is opened. ([e623439](https://github.com/FunkinCrew/Funkin/commit/e6234397f57d2af37035a0ff2504b8241e23949d)) - by @VirtuGuy in [#7145](https://github.com/FunkinCrew/Funkin/pull/7145)
|
||||
- The Pause Menu global offset value now adjusts at the same rate across all framerates. ([348647f](https://github.com/FunkinCrew/Funkin/commit/348647f48629a24ee3bd2b759bbfc1bff168fb81)) - by @KolzeYT in [#7147](https://github.com/FunkinCrew/Funkin/pull/7147)
|
||||
- Spirit's trail effect now renders consistently across all framerates. ([75506f7](https://github.com/FunkinCrew/Funkin/commit/75506f71a97d468f516a0232c8be5560afb0682e)) - by @KolzeYT in [#7179](https://github.com/FunkinCrew/Funkin/pull/7179)
|
||||
- The sound tray now appears consistently and plays sound effects at the right volume. ([2b9906d](https://github.com/FunkinCrew/Funkin/commit/2b9906df29660057410d3054e897012896896d6a)) - by @Raltyro in [#7068](https://github.com/FunkinCrew/Funkin/pull/7068)
|
||||
- The sticker transition now properly pauses when losing focus with Pause on Unfocus enabled. ([8a1d74b](https://github.com/FunkinCrew/Funkin/commit/8a1d74b82cceab97e777dc49e7a2117b8d27e59b)) - by @ComedyLost in [#7129](https://github.com/FunkinCrew/Funkin/pull/7129)
|
||||
- [HTML5] Rimlight alt masks now render properly. ([0613e9d](https://github.com/FunkinCrew/Funkin/commit/0613e9d295179e66d30db3d2f6805870eac847d6)) - by @VirtuGuy in [#6900](https://github.com/FunkinCrew/Funkin/pull/6900)
|
||||
- The Week 2 Erect stage lightning strike fade now pauses and cancels appropriately. ([cd05267](https://github.com/FunkinCrew/funkin.assets/commit/cd05267023859d04cc8920c787b8280682e5f0f5)) - by @ComedyLost in [funkin.assets#390](https://github.com/FunkinCrew/funkin.assets/pull/390)
|
||||
- A-Bot (Dark)'s frame no longer renders as completely dark in Week 2 Erect. ([a142530](https://github.com/FunkinCrew/funkin.assets/commit/a142530cae6198ca17b560ff73e36d50f1582768)) - by @ComedyLost in [funkin.assets#368](https://github.com/FunkinCrew/funkin.assets/pull/368)
|
||||
- Dying on the same frame as a lightning strike in Week 2 Erect no longer throws a script error. ([ef89bde](https://github.com/FunkinCrew/funkin.assets/commit/ef89bde19c397f15a53b0fda2f64ba93ff96f56f)) - by @KoloInDaCrib in [funkin.assets#363](https://github.com/FunkinCrew/funkin.assets/pull/363)
|
||||
- Pico (Dark) and Nene (Dark) no longer throw script errors in debug editors. ([b16ce44](https://github.com/FunkinCrew/funkin.assets/commit/b16ce44beb43d4511c61418c824929fb4dfabb63)) - by @ComedyLost in [funkin.assets#367](https://github.com/FunkinCrew/funkin.assets/pull/367)
|
||||
- Girlfriend's hair no longer falls when restarting the song in a Week 3 stage. ([ade80ab](https://github.com/FunkinCrew/funkin.assets/commit/ade80ab72915ddd47724c380bb41d6df4c1b79bd)) - by @AnimatingLegend in [funkin.assets#370](https://github.com/FunkinCrew/funkin.assets/pull/370)
|
||||
- The camera transition to Results no longer reveals the edges of stage art in the Week 6 Erect stage. ([ecf4c54](https://github.com/FunkinCrew/funkin.assets/commit/ecf4c5458a2e1fadba7db31c3272cea485d22b2f)) - by @ComedyLost in [funkin.assets#383](https://github.com/FunkinCrew/funkin.assets/pull/383)
|
||||
- Fine-tuned offsets for pixel characters. ([8b3c268](https://github.com/FunkinCrew/funkin.assets/commit/8b3c2680a6a966e81b62598e783c60f46355c759)) - by @CEliuxJV in [funkin.assets#376](https://github.com/FunkinCrew/funkin.assets/pull/376)
|
||||
- Tankman's game over lines now properly play during game overs in Ugh (Pico Mix) and Guns (Pico Mix). ([b900208](https://github.com/FunkinCrew/funkin.assets/commit/b900208143f980adee25d70db247e0b176dd659c)) - by @hucks5 in [funkin.assets#373](https://github.com/FunkinCrew/funkin.assets/pull/373)
|
||||
- Restarting during the mid-song animation in Stress (Pico Mix) now properly resets the characters' animations. ([2ca92de](https://github.com/FunkinCrew/funkin.assets/commit/2ca92de46eced3f53d305579704fcb7e8c051f3a)) - by @requazar in [funkin.assets#364](https://github.com/FunkinCrew/funkin.assets/pull/364)
|
||||
- The rain shader now properly fades out with the transition to the Results Screen. ([a0caa41](https://github.com/FunkinCrew/Funkin/commit/a0caa41edcb26298e4f8c243a5eed0b4fe93fcf7)) - by @VirtuGuy in [#6776](https://github.com/FunkinCrew/Funkin/pull/6776)
|
||||
- Exiting a Chart Editor playtest while using the pixel notestyle no longer crashes the game. ([6c1f380](https://github.com/FunkinCrew/Funkin/commit/6c1f3800dbc9b13fb4b409abce53c1dafc9bbe8f)) - by @Starexify and @KoloInDaCrib in [#7108](https://github.com/FunkinCrew/Funkin/pull/7108)
|
||||
- Chart Editor Edit buttons are now appropriately disabled when nothing is selected. ([6b1fc03](https://github.com/FunkinCrew/Funkin/commit/6b1fc0372bca202d80f4ccf316580b13a5a80879)) - by @Lasercar in [#4386](https://github.com/FunkinCrew/Funkin/pull/4386)
|
||||
- Undoing a Chart Editor event now plays the undo sound. ([f7fd7a5](https://github.com/FunkinCrew/Funkin/commit/f7fd7a57c36f0461f56c2ab627a17d752cb82320)) - by @VirtuGuy in [#6963](https://github.com/FunkinCrew/Funkin/pull/6963)
|
||||
- The playback speed audio setting no longer rounds incorrectly upon reopening the Chart Editor. ([3b15873](https://github.com/FunkinCrew/Funkin/commit/3b15873fc7d0c20840c313f1d2cfed91014c664f)) - by @AnimatingLegend in [#7023](https://github.com/FunkinCrew/Funkin/pull/7023)
|
||||
- The Chart Editor easing preview graph now properly displays tweens that exceed start/end values. ([7626700](https://github.com/FunkinCrew/Funkin/commit/7626700495743862caddbf50a7dafef51529a2a6)) - by @echolotl in [#7152](https://github.com/FunkinCrew/Funkin/pull/7152)
|
||||
- The Chart Editor playbar head is no longer draggable during a playtest. ([ccb874e](https://github.com/FunkinCrew/Funkin/commit/ccb874e97a121859c26a8a71b394743d27f6ed37)) - by @ComedyLost in [#6987](https://github.com/FunkinCrew/Funkin/pull/6987)
|
||||
- The Results Screen no longer shows up after a Chart Editor playtest with Bot Play enabled. ([786f19c](https://github.com/FunkinCrew/Funkin/commit/786f19c57d999dba9309cae28d912b080968670d)) - by @ComedyLost in [#7022](https://github.com/FunkinCrew/Funkin/pull/7022)
|
||||
- The Osu!Mania Chart Editor importer no longer accepts non-Osu!Mania beatmaps. ([456b2ff](https://github.com/FunkinCrew/Funkin/commit/456b2ff6c4a127733120cef3d03d6102bdd7522f)) - by @FuroYT in [#6624](https://github.com/FunkinCrew/Funkin/pull/6624)
|
||||
- Characters in the Stage Editor no longer fly away when clicking and dragging them. ([0389af2](https://github.com/FunkinCrew/Funkin/commit/0389af2202aa4dc6a21ce9b7eea95944bfaa6946)) - by @KoloInDaCrib in [#7002](https://github.com/FunkinCrew/Funkin/pull/7002)
|
||||
- Characters with alternating idle animations now properly play danceLeft when pressing Space in the Animation Editor. ([05032e3](https://github.com/FunkinCrew/Funkin/commit/05032e3126eda78a2bbf70e8c975bcb04eed201b)) - by @ComedyLost in [#7056](https://github.com/FunkinCrew/Funkin/pull/7056)
|
||||
- [LINUX] Paths are now properly sanitized as they are on other platforms. ([219143c](https://github.com/FunkinCrew/Funkin/commit/219143c02dcf65e68aec80701a82023ca0d70db6)) - by @NotHyper-474 in [#6395](https://github.com/FunkinCrew/Funkin/pull/6395)
|
||||
- OpenFL and some Lime classes are now accessible to modders. ([56a56db](https://github.com/FunkinCrew/Funkin/commit/56a56dbfaa3f62a3f684df49c682f69a496e45f4)) - by @FuroYT in [#7115](https://github.com/FunkinCrew/Funkin/pull/7115)
|
||||
- Sandboxed API classes can now be accessed by mods. ([03930a2](https://github.com/FunkinCrew/Funkin/commit/03930a29c3d8aa6a310147a7131c6b7cddefd475)) - by @MightyTheArmiddilo in [#6952](https://github.com/FunkinCrew/Funkin/pull/6952)
|
||||
- Blacklisted some more classes for security.
|
||||
- And even more fixes!
|
||||
|
||||
## New Contributors for 0.8.4
|
||||
|
||||
* @roma-perec-bp made their first contribution in [#6769](https://github.com/FunkinCrew/Funkin/pull/6769)
|
||||
* @Redar13 made their first contribution in [#6830](https://github.com/FunkinCrew/Funkin/pull/6830)
|
||||
* @SuperEric1 made their first contribution in [#6893](https://github.com/FunkinCrew/Funkin/pull/6893)
|
||||
* @AnimatingLegend made their first contribution in [#7023](https://github.com/FunkinCrew/Funkin/pull/7023)
|
||||
* @Marcosayfi made their first contribution in [#7071](https://github.com/FunkinCrew/Funkin/pull/7071)
|
||||
* @Rudyrue made their first contribution in [#7104](https://github.com/FunkinCrew/Funkin/pull/7104)
|
||||
* @KolzeYT made their first contribution in [#7147](https://github.com/FunkinCrew/Funkin/pull/7147)
|
||||
* @echolotl made their first contribution in [#7152](https://github.com/FunkinCrew/Funkin/pull/7152)
|
||||
* @mario64thane made their first contribution in [funkin.assets#63](https://github.com/FunkinCrew/funkin.assets/pull/63)
|
||||
* @LeGooeyy made their first contribution in [funkin.assets#278](https://github.com/FunkinCrew/funkin.assets/pull/278)
|
||||
* @requazar made their first contribution in [funkin.assets#364](https://github.com/FunkinCrew/funkin.assets/pull/364)
|
||||
|
||||
|
||||
|
||||
## [0.8.3] - 2026-02-23
|
||||
|
||||
### Fixed
|
||||
|
||||
- [HTML5] Hitting a can in 2hot no longer creates a script error. (Thanks NotHyper-474!)
|
||||
- The game no longer consistently lag spikes during gameplay. ([823120c](https://github.com/FunkinCrew/Funkin/commit/823120cdef7bfbb2d3ec37c7085878fcbeebcb30)) - by @ACrazyTown in [#6970](https://github.com/FunkinCrew/Funkin/pull/6970)
|
||||
|
||||
|
||||
|
||||
## [0.8.2] - 2026-02-23
|
||||
|
||||
### Added
|
||||
|
||||
- Added 14 new blend modes, allowing the engine to render better visual effects.
|
||||
- Added a PlayState parameter that mirrors notes during a song.
|
||||
- Added preview graphs for Chart Editor event easing types. ([eea1265](https://github.com/FunkinCrew/Funkin/commit/eea12657b1d8131014a3fdc270c3f351fae6e851)) - by @PurSnake in [#6374](https://github.com/FunkinCrew/Funkin/pull/6374)
|
||||
- Added the ability to mirror notes on the X and Y axes in the Chart Editor. ([fd0c2df](https://github.com/FunkinCrew/Funkin/commit/fd0c2df35ee8cda3f1566a297c4faec984d809bb)) - by @Lasercar in [#4372](https://github.com/FunkinCrew/Funkin/pull/4372)
|
||||
- Added a deselect button and shortcut to the Stage Editor. ([a9f1d41](https://github.com/FunkinCrew/Funkin/commit/a9f1d41771f0c97fdd70d4d6c54087ac0e64ff38)) - by @Lasercar in [#5281](https://github.com/FunkinCrew/Funkin/pull/5281)
|
||||
|
||||
### Changed
|
||||
|
||||
- Made some improvements to Polymod:
|
||||
- Added string interpolation support. (Thanks NotHyper-474!)
|
||||
- Scripted classes can now extend nothing. (Thanks NotHyper-474!)
|
||||
- Scripted classes can now extend other scripted classes. (Thanks KoloInDaCrib!)
|
||||
- Tweaked some error messages to be more verbose.
|
||||
- Added `scriptHas` and `staticScriptHas` functions to check if a script has a specific variable/function.
|
||||
- The `is` operator is now more accurate to regular Haxe. (Thanks Starexify!)
|
||||
- Static functions can now use static extensions (`using`s). (Thanks NotHyper-474!)
|
||||
- `interface`s can now be properly parsed. (Thanks Starexify!)
|
||||
- Optional fields and extensions can now be used in `typedef`s. (Thanks Starexify!)
|
||||
- Added stricter checking for default/optional arguments in functions. (Thanks Starexify!)
|
||||
- May be a breaking change for some scripts, so please update your scripts to account for this!
|
||||
- Enums now have multiple arguments. (Thanks Starexify!)
|
||||
- Basic types (e.g `String`, `Int`, `Bool`, etc.) can now be used with the `is` operator without having to import the type first.
|
||||
- Tweaked charts for the following songs:
|
||||
- Bopeebo (Pico Mix) [Normal/Hard] - Adjusted note timings and added a “hey” animation event in Sections 18, 26, and 27.
|
||||
- Fresh Erect [Nightmare] - Added 2 notes where Boyfriend’s vocals echo.
|
||||
- DadBattle Erect [Nightmare] - Added a missing jack note in Section 38, added holds to two notes in Section 63.
|
||||
- DadBattle (Pico Mix) [all difficulties] - Adjusted timings for various notes in Sections 37-40 and 60.
|
||||
- Spookeez Erect [Erect/Nightmare] - Adjusted many hold note lengths to better match vocals.
|
||||
- Spookeez (Pico Mix) - Added one missing note to Section 14.
|
||||
- South Erect [Erect/Nightmare] - Adjusted many hold note lengths to better match vocals.
|
||||
- Pico [Easy] - Adjusted timings for one note each in Sections 36 and 38.
|
||||
- Pico [Normal/Hard] - Adjusted timings for Boyfriend’s right notes to be a bit earlier in Sections 31-42.
|
||||
- Pico [all difficulties] - Added a Focus Camera event on Girlfriend during the duet.
|
||||
- Satin Panties Erect [Erect/Nightmare] - Split a hold note into two for Mom in Sections 14 and 18.
|
||||
- High Erect [Erect/Nightmare] - Adjusted timing for one note each in Sections 25 and 41.
|
||||
- M.I.L.F [Easy] - Adjusted timings for two notes in Section 73.
|
||||
- M.I.L.F [Hard] - Changed one down note into an up note in Section 15 for consistency.
|
||||
- Cocoa Erect [Nightmare] - Fixed one note not being a Mom notekind in Section 24, added holds to 2 notes in Section 67.
|
||||
- Cocoa (Pico Mix) [all difficulties] - Adjusted timings for two notes in Section 15.
|
||||
- Eggnog Erect [Erect/Nightmare] - Adjusted how a grace note is charted in Section 52, added 2 missing notes for Mom in Section 72, adjusted Camera Bop event in Section 63.
|
||||
- Senpai (Pico Mix) [all difficulties] - Adjusted timing for one note in Section 10.
|
||||
- Roses [Normal] - Adjusted timings for four notes in Sections 30, 32, 41, and 43.
|
||||
- Thorns Erect [Erect] - Adjusted timings for two notes in Section 36, removed some double notes to differentiate the chart from Nightmare.
|
||||
- Ugh Erect [Erect/Nightmare] - Adjusted some Zoom Camera events around Section 24.
|
||||
- Guns (Pico Mix) [all difficulties] - Fixed many timing issues throughout the whole song.
|
||||
- SPAGHETTI [Normal/Hard] - Added one note to match Boyfriend’s rapping.
|
||||
- Darnell (BF Mix) [all difficulties] - Removed extra notes from Sections 49 and 50, adjusted various note timings throughout the song.
|
||||
- Lit Up [Normal] - Added one missing note each in Sections 40 and 60.
|
||||
- Lit Up (BF Mix) [Normal/Hard] - Adjusted timings for various grace notes in Sections 1, 52, 55, and 56.
|
||||
- 2hot [Easy/Normal] - Recharted many sections to be more predictable.
|
||||
- [macOS] Disabled high DPI to significantly improve performance on some displays.
|
||||
- Improved the Lag Adjustment menu to better handle very large offsets.
|
||||
- Chart Editor exporting now omits default values and orders values more consistently.
|
||||
- Chart Editor playtests now processes all events before the starting position.
|
||||
- Optimized the Week 2 Spooky Mansion stage background by using a texture atlas. ([2bdd5a7](https://github.com/FunkinCrew/funkin.assets/commit/2bdd5a713f0b5c7e945c457e2a4a92bdb367cc02)) - by @MaybeMaru and @Trayfellow in [funkin.assets#348](https://github.com/FunkinCrew/funkin.assets/pull/348)
|
||||
- Added more default imports for mods. ([1620cf3](https://github.com/FunkinCrew/Funkin/commit/1620cf388973eab5d83e151eec28b2c8abc84eb1)) - by @Starexify in [#6706](https://github.com/FunkinCrew/Funkin/pull/6706)
|
||||
- The Set Camera Bop event now supports decimal values for rate and offset at 0.25 precision. ([ffaba34](https://github.com/FunkinCrew/Funkin/commit/ffaba343258c4a0e5ee7e1bd7fa0e8138ae60bc1)) - by @KoloInDaCrib in [#6741](https://github.com/FunkinCrew/Funkin/pull/6741)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed some issues with Polymod:
|
||||
- The super constructor for scripted classes can now accept more than 4 arguments. (Thanks NotHyper-474!)
|
||||
- Functions can now accept more than 8 arguments. (Thanks NotHyper-474!)
|
||||
- Script variables are no longer dropped early when passing them to a callback (ex. `FlxTimer`). (Thanks NotHyper-474!)
|
||||
- Fix a memory leak that could happen if a script tried to reference an anonymous object with a lot of `StringMap`s. (Thanks NotHyper-474!)
|
||||
- Properties (`get_`/`set_` functions) are now much more accurate to regular Haxe. (Thanks NotHyper-474!)
|
||||
- Re-exported some assets to fix some issues:
|
||||
- Re-exported all variations of Pico as texture atlases to optimize memory usage.
|
||||
- Freeplay DJ Pico’s table sparks no longer persist when his gun is lifted.
|
||||
- Pico (Blazin’)’s game over retry animation now properly displays its blend modes.
|
||||
- Pico (Christmas) no longer shows several stray marks around his sprites.
|
||||
- Nene (Christmas) no longer becomes slightly offset when her knife is raised.
|
||||
- The running tankmen in Stress (Pico Mix) no longer tank the game’s performance.
|
||||
- Daddy Dearest now properly holds his up pose.
|
||||
- Monster (Christmas)’s hat no longer moves while he holds a pose.
|
||||
- Darnell’s can in 2hot now shows a sparkling effect.
|
||||
- [DESKTOP] Modded atlas characters with both .png and .astc spritesheets no longer crash the game.
|
||||
- [DESKTOP] The game display no longer sometimes becomes offset from the window.
|
||||
- [MOBILE] Selecting a song using touch controls when a keyboard is connected no longer disables the keyboard strumline layout.
|
||||
- [ANDROID] Improved Freeplay scrolling behavior on high framerate displays.
|
||||
- [WINDOWS] Chart Editor save dialog windows no longer freeze the game.
|
||||
- The Chart Editor Results Screen playtest option now consistently shows the Results Screen after each playtest.
|
||||
- Scrolling really quickly in Freeplay no longer crashes the game. ([76699b2](https://github.com/FunkinCrew/Funkin/commit/76699b235cd5b359bc34e6950cd05bbbb13918f8)) - by @ComedyLost in [#6962](https://github.com/FunkinCrew/Funkin/pull/6962)
|
||||
- Fixed a memory leak in the Freeplay menu. ([910ba65](https://github.com/FunkinCrew/Funkin/commit/910ba650b0c1011802b151b85f4a9d8177ae95c7)) - by @KoloInDaCrib in [#6615](https://github.com/FunkinCrew/Funkin/pull/6615)
|
||||
- Modded difficulty dots and graphics now render correctly in Freeplay. ([90b856d](https://github.com/FunkinCrew/Funkin/commit/90b856d88635ac4e5517e934298e3820b2e5f45c)) - by @ComedyLost in [#6205](https://github.com/FunkinCrew/Funkin/pull/6205)
|
||||
- Freeplay capsules with long song names no longer display too far to the left during the new rank animation. ([17a914c](https://github.com/FunkinCrew/Funkin/commit/17a914c933592cb3ba79881ca6a8d900b8513cbc)) - by @VirtuGuy in [#6690](https://github.com/FunkinCrew/Funkin/pull/6690)
|
||||
- Freeplay capsule BPM values ending in 1 now display with proper spacing. ([e5b4351](https://github.com/FunkinCrew/Funkin/commit/e5b43510dd810a74a7d06f7f02c52892d11d6f8b)) - by @VirtuGuy in [#6696](https://github.com/FunkinCrew/Funkin/pull/6696)
|
||||
- The Freeplay pixel icon now properly plays its confirm animation when mashing the capsule. ([a56bc44](https://github.com/FunkinCrew/Funkin/commit/a56bc4462d1c49e8ec71462b9bd7ff338b743c93)) - by @VirtuGuy in [#6723](https://github.com/FunkinCrew/Funkin/pull/6723)
|
||||
- [HTML5] Scrolling with the mouse now works properly in Story Mode and Freeplay. ([506d80b](https://github.com/FunkinCrew/Funkin/commit/506d80b287b969121dc3601714c2459cc71f7e55)) - by @VirtuGuy in [#6621](https://github.com/FunkinCrew/Funkin/pull/6621)
|
||||
- Optimized rendering in the Character Select menu. ([2ecebf7](https://github.com/FunkinCrew/Funkin/commit/2ecebf79205a66b64148d64bbb3162c9fc073d85)) - by @MaybeMaru in [#6789](https://github.com/FunkinCrew/Funkin/pull/6789)
|
||||
- [MOBILE] Tapping on a lock in Character Select now plays its reject animation. ([05148f0](https://github.com/FunkinCrew/Funkin/commit/05148f0e5dad4d60089e6074e616b583ba29a5e6)) - by @VirtuGuy in [#6779](https://github.com/FunkinCrew/Funkin/pull/6779)
|
||||
- Hold note covers now follow when the strumline position is modified. ([77c6651](https://github.com/FunkinCrew/Funkin/commit/77c6651e245ea276b1bdf33e5d6732073dcb6037)) - by @MightyTheArmiddilo in [#6746](https://github.com/FunkinCrew/Funkin/pull/6746)
|
||||
- The pause and game over screens no longer sometimes play the wrong character's themes. ([878644a](https://github.com/FunkinCrew/Funkin/commit/878644aee7ba425e482707b94b9f887ec23f6306)) - by @KoloInDaCrib in [#6812](https://github.com/FunkinCrew/Funkin/pull/6812)
|
||||
- The game over camera focus point is now consistent, regardless of which animation was previously playing. ([18cceb4](https://github.com/FunkinCrew/Funkin/commit/18cceb4946a51fda0009d77baae1115437694415)) - by @KoloInDaCrib in [#6705](https://github.com/FunkinCrew/Funkin/pull/6705)
|
||||
- Pico (Pixel) and other pixel characters now use RetroCameraFade as Boyfriend (Pixel) does. ([d1382aa](https://github.com/FunkinCrew/Funkin/commit/d1382aa985f97e3b40b0b629cb3d04301ebfb55e)) - by @afreetoplaynoob in [#6526](https://github.com/FunkinCrew/Funkin/pull/6526)
|
||||
- [MOBILE] The back button can no longer exit during the Boyfriend fakeout death animation. ([9d06018](https://github.com/FunkinCrew/Funkin/commit/9d0601813e912de9e25a851ad9204a654316625a)) - by @ActualMandM in [#6917](https://github.com/FunkinCrew/Funkin/pull/6917)
|
||||
- The song name text is now properly hidden at the beginning of the Results Screen animation. ([a5b15b9](https://github.com/FunkinCrew/Funkin/commit/a5b15b9424f16d8eadc7a92f1c355da647990c3a)) - by @NotHyper-474 and @Trayfellow in [#6931](https://github.com/FunkinCrew/Funkin/pull/6931)
|
||||
- The sticker transition no longer covers the volume soundtray. ([616546d](https://github.com/FunkinCrew/Funkin/commit/616546d298a06c4135a32ffad41483be208552f1)) - by @VirtuGuy in [#6727](https://github.com/FunkinCrew/Funkin/pull/6727)
|
||||
- Lightning now appears more regularly during South Erect and South (Pico Mix). ([9ca2092](https://github.com/FunkinCrew/funkin.assets/commit/9ca20929bc3dde6b07d74100c45fb45e48769c35)) - by @JackXson-Real in [funkin.assets#345](https://github.com/FunkinCrew/funkin.assets/pull/345)
|
||||
- [HTML5] The city lights in the Week 3 stages now show up. ([603684f](https://github.com/FunkinCrew/funkin.assets/commit/603684ffb327f03d8216aaeaba143ca5f9cd77f0)) - by @VirtuGuy in [funkin.assets#342](https://github.com/FunkinCrew/funkin.assets/pull/342)
|
||||
- The Week 3 Pico doppelganger cutscene background music no longer cuts off at the end. ([8ef58aa](https://github.com/FunkinCrew/funkin.assets/commit/8ef58aabdba8287d4c88808e2f0ecc0ac385b383)) - by @Trayfellow in [funkin.assets#349](https://github.com/FunkinCrew/funkin.assets/pull/349)
|
||||
- The Story Mode cutscene before Stress now properly plays when viewed more than once. ([6ec45ec](https://github.com/FunkinCrew/funkin.assets/commit/6ec45ec82ca77188b1b2b9c58ada172f5c357103)) - by @NotHyper-474 in [funkin.assets#341](https://github.com/FunkinCrew/funkin.assets/pull/341)
|
||||
- Darnell's can sound effects in 2hot no longer play twice with high offsets or when lagging. ([6315e25](https://github.com/FunkinCrew/funkin.assets/commit/6315e25eaaa8be4ca85e9b607f547595c699997a)) - by @JackXson-Real in [funkin.assets#354](https://github.com/FunkinCrew/funkin.assets/pull/354)
|
||||
- [MOBILE] The cutscene after 2hot now properly accounts for different resolutions. ([dd3371a](https://github.com/FunkinCrew/Funkin/commit/dd3371a549f4e10f68f1a458f40ec1a342f07876)) - by @ActualMandM in [#6937](https://github.com/FunkinCrew/Funkin/pull/6937)
|
||||
- Made the AttractState video skipping pie smoother. ([5a161b1](https://github.com/FunkinCrew/Funkin/commit/5a161b1d7342d7931950edf37554ce3a338736f9)) - by @PurSnake in [#6728](https://github.com/FunkinCrew/Funkin/pull/6728)
|
||||
- The screenshot preview no longer breaks when changing states. ([dc78db8](https://github.com/FunkinCrew/Funkin/commit/dc78db8808a7ea205fe75eba006e4ec1c57a8190)) - by @Lasercar in [#6792](https://github.com/FunkinCrew/Funkin/pull/6792)
|
||||
- Holding Ctrl and clicking on a hold note trail no longer crashes the Chart Editor (again). ([86ac336](https://github.com/FunkinCrew/Funkin/commit/86ac336d62a65fb420cdd6b1be739c3262d21159)) - by @Lasercar in [#6912](https://github.com/FunkinCrew/Funkin/pull/6912)
|
||||
- Chart Editor tooltips no longer appear when hovering over a deleted note or event. ([091c18b](https://github.com/FunkinCrew/Funkin/commit/091c18bccd95b588ba0d4577a94d83acf5942417)) - by @KoloInDaCrib in [#6929](https://github.com/FunkinCrew/Funkin/pull/6929)
|
||||
- Chart Editor vocal waveforms now render more accurately after BPM changes in the song. ([4e8f48d](https://github.com/FunkinCrew/Funkin/commit/4e8f48d5f0f1be4438c4ef9571137eaa35935f67)) - by @gamerbross in [#6833](https://github.com/FunkinCrew/Funkin/pull/6833)
|
||||
- The Chart Editor vocal waveforms now display in the proper position regardless of the game's aspect ratio. ([1fb9040](https://github.com/FunkinCrew/Funkin/commit/1fb9040470432528a0441d7938d217e424ce27fd)) - by @Starexify in [#6193](https://github.com/FunkinCrew/Funkin/pull/6193)
|
||||
- The second section number and divider now renders consistently in the Chart Editor. ([ac539bd](https://github.com/FunkinCrew/Funkin/commit/ac539bdf26ee7b5ecdcc52665c7c95287ef491c0)) - by @NotHyper-474 in [#6765](https://github.com/FunkinCrew/Funkin/pull/6765)
|
||||
- The Chart Editor now saves the Show Results Screen setting. ([c893a73](https://github.com/FunkinCrew/Funkin/commit/c893a7320d72abd3743bc5c1ddeaecf073035eb1)) - by @MightyTheArmiddilo in [#6854](https://github.com/FunkinCrew/Funkin/pull/6854)
|
||||
- Closing the game while in the Stage Editor no longer crashes the game. ([99cd4e2](https://github.com/FunkinCrew/Funkin/commit/99cd4e2cbd7fba87249b02b072a1e60f07c2c907)) - by @VirtuGuy in [#6775](https://github.com/FunkinCrew/Funkin/pull/6775)
|
||||
- The onion skin now properly renders as transparent after changing characters in the Animation Editor. ([5c444f1](https://github.com/FunkinCrew/Funkin/commit/5c444f1109e5af04735a4383e577c69682b3713f)) - by @VirtuGuy in [#6545](https://github.com/FunkinCrew/Funkin/pull/6545)
|
||||
- Blacklisted a class for security. ([3a113c2](https://github.com/FunkinCrew/Funkin/commit/3a113c270f92a6b14dfcda221124742437e1670a)) - by @charlesisfeline in [#6573](https://github.com/FunkinCrew/Funkin/pull/6573)
|
||||
- Even more tiny fixes!
|
||||
|
||||
|
||||
|
||||
## [0.8.1] - 2026-01-04
|
||||
|
||||
### Added
|
||||
|
||||
- Added a new "Non-scoreable" notekind to the Chart Editor that doesn't affect scores and ranks, or play miss animations.
|
||||
|
||||
### Changed
|
||||
|
||||
- Changed the order in which events are dispatched in PlayState. ([16f3e03](https://github.com/FunkinCrew/Funkin/commit/16f3e038d80c37d016c9d2b6cdd1012c94ef88b9)) - by @ComedyLost in [#6606](https://github.com/FunkinCrew/Funkin/pull/6606)
|
||||
|
||||
### Fixed
|
||||
|
||||
- The last note of SPAGHETTI no longer counts toward scoring, allowing players to earn Perfect ranks.
|
||||
|
||||
|
||||
|
||||
## [0.8.0] - 2026-01-03
|
||||
|
||||
The LE SSERAFIM collab update!
|
||||
|
||||
### Added
|
||||
|
||||
- Added a new playable song: SPAGHETTI (feat. j-hope of BTS) (Clean ver.) by LE SSERAFIM (feat. j-hope)!
|
||||
- Try it out from either Story Mode or the Freeplay Menu!
|
||||
- Added subtitles for various cutscenes and songs throughout the game.
|
||||
- This can be toggled in the Preferences menu.
|
||||
- [MOBILE] Added support for Newgrounds logins and saving/loading from the cloud.
|
||||
- [MOBILE] Added support for opening FNFC chart files through the filesystem.
|
||||
- [MOBILE] Added haptics for tapping to begin on the Title Screen.
|
||||
- [MOBILE] Added the back button to the Character Select menu.
|
||||
- Added new function callbacks for Module scripts in Freeplay and Character Select.
|
||||
- Added `getOtherNotes()` function for counting notes that are not of a certain notekind.
|
||||
- Implemented stacked patches to JSON merging.
|
||||
- This allows mods to add new variations to existing songs without any extra fuss. See the [Code Cookbook](https://thekade.net/funkin-cookbook/) for more information.
|
||||
- Added the ability to import Osu!Mania and StepMania charts in the Chart Editor.
|
||||
- Added Move Difficulty and Clone Difficulty buttons to the Chart Editor Difficulty window.
|
||||
- Added a checkbox to hide vocal waveforms in the Chart Editor.
|
||||
- Added support for SRT subtitles that can display in game. ([a7fc70a](https://github.com/FunkinCrew/Funkin/commit/a7fc70a5c9cbd811b48cf2eb2f6c221814c2a3cd)) - by @PurSnake in [#6206](https://github.com/FunkinCrew/Funkin/pull/6206)
|
||||
- Added support for various render types and scripting for the Freeplay DJ. ([8b38bcf](https://github.com/FunkinCrew/Funkin/commit/8b38bcfbdd9ef1b135046022b1b93eec5dedbd28)) - by @PurSnake and @AbnormalPoof in [#5698](https://github.com/FunkinCrew/Funkin/pull/5698)
|
||||
- Level props can now have a flipX field. ([4961d4e](https://github.com/FunkinCrew/Funkin/commit/4961d4ee992445a487587dd3a7f3ab965eb4f5cf)) - by @Starexify in [#6218](https://github.com/FunkinCrew/Funkin/pull/6218)
|
||||
- [LINUX] Added support for Feral Gamemode. ([555ec09](https://github.com/FunkinCrew/Funkin/commit/555ec09ef5be3c1d1966eeaa0275835e59aa5709)) - by @Noobz4Life in [#2473](https://github.com/FunkinCrew/Funkin/pull/2473)
|
||||
- Added support for importing charts from Osu!Mania. ([0be42bf](https://github.com/FunkinCrew/Funkin/commit/0be42bf0475975d3195d4da85e653899df72a28c)) - by @FuroYT in [#6155](https://github.com/FunkinCrew/Funkin/pull/6155)
|
||||
- Added a "No Animation" notekind to the Chart Editor. ([618e093](https://github.com/FunkinCrew/Funkin/commit/618e093e1ce2d4f45fa4da5d34ed84e58394126e)) - by @Eviethecoder in [#4036](https://github.com/FunkinCrew/Funkin/pull/4036)
|
||||
- Added a way to adjust time signature/BPM changes through the Metadata window in the Chart Editor. ([b05a5c7](https://github.com/FunkinCrew/Funkin/commit/b05a5c7f54f9b41d63081837dd2c7392203b4530)) - by @Keoiki and @Lasercar in [#4770](https://github.com/FunkinCrew/Funkin/pull/4770)
|
||||
- Added character preview windows for the player and opponent in the Chart Editor. ([9f59231](https://github.com/FunkinCrew/Funkin/commit/9f59231d58a889e2c9a0f8e7e30b2e1c370e0d9a)) - by @PurSnake and @ComedyLost in [#6221](https://github.com/FunkinCrew/Funkin/pull/6221)
|
||||
- Added the option to view the Results Screen after Chart Editor playtests. ([eeba677](https://github.com/FunkinCrew/Funkin/commit/eeba677da9fac75cd34481ead01dc05dfa074bce)) - by @Lasercar in [#4087](https://github.com/FunkinCrew/Funkin/pull/4087)
|
||||
- Added the option to carry over Chart Editor volume levels into a playtest. ([4dd87e7](https://github.com/FunkinCrew/Funkin/commit/4dd87e7308789bae9a9e903bb15737e8149012f7)) - by @KoloInDaCrib in [#6302](https://github.com/FunkinCrew/Funkin/pull/6302)
|
||||
- Added the ability to time-travel during a Chart Editor playtest in release builds with PgUp and PgDown. ([4a00429](https://github.com/FunkinCrew/Funkin/commit/4a00429a57f03f60159a4e2e4f531bd6fa118658)) - by @AbnormalPoof in [#4209](https://github.com/FunkinCrew/Funkin/pull/4209)
|
||||
- Added the Rift of the Necrodancer collab trailer to the pool of videos in Attract Mode.
|
||||
|
||||
### Changed
|
||||
|
||||
- The mod API version is now 0.8.0. Please update your mods to ensure they work.
|
||||
- Made some improvements to Polymod:
|
||||
- Scripts can no longer accidentally redefine variables defined in their super class.
|
||||
- `Math` and `Std` can now properly be used in static functions. (Thanks NotHyper-474!)
|
||||
- Significantly improved the speed of retrieving modded assets, improving the performance of the game with a lot of mods installed. (Thanks PurSnake!)
|
||||
- Replaced `FlxAnimate` with `flixel-animate` to overhaul texture atlas handling:
|
||||
- Significantly improved performance in the Character Select Menu.
|
||||
- Pico (holding Nene)’s game over confirm animation now plays properly.
|
||||
- The camera now properly focuses on atlas characters after restarting the song.
|
||||
- Atlas characters now properly display the rimlight shader.
|
||||
- Implemented multiple new settings for atlas sprites.
|
||||
- Fixed a ton of bugs across various menus.
|
||||
- Re-exported many assets to improve memory usage and performance throughout the game.
|
||||
- [MOBILE] Music from external sources now pauses when opening the game.
|
||||
- [MOBILE] The game is now named “FNF” on the Home Screen.
|
||||
- [DESKTOP] Reworked window resizing behavior to behave more consistently.
|
||||
- Made it easier to implement custom Pause Menu substates.
|
||||
- Pressing F6 (rebindable) now cycles through debug display modes.
|
||||
- Boyfriend and Girlfriend’s scared animations in Week 2 now last longer.
|
||||
- Added “Change difficulty” to the list of undo-able actions in the Chart Editor.
|
||||
- Changed the default easing type for the Chart Editor Focus Camera event from Linear to Classic.
|
||||
- The game now displays a user-friendly crash message when attempting to play without a graphics card. ([5270353](https://github.com/FunkinCrew/Funkin/commit/52703536a62fc004fb7be0457da89705f812364f)) - by @ACrazyTown in [#6160](https://github.com/FunkinCrew/Funkin/pull/6160)
|
||||
- Adjusted strumline confirm animations when hitting notes to feel more responsive. ([62a3f73](https://github.com/FunkinCrew/Funkin/commit/62a3f73de9d653d4ca5a7d7f4c895fc9fd1a4be7)) - by @gamerbross in [#6261](https://github.com/FunkinCrew/Funkin/pull/6261)
|
||||
- The Lag Adjustment menu now displays a note splash when hitting perfectly. ([342c3cd](https://github.com/FunkinCrew/Funkin/commit/342c3cd7d6f33b3babb70f3d1fb604b0bdb90464)) - by @ExtraCode75 in [#6602](https://github.com/FunkinCrew/Funkin/pull/6602)
|
||||
- Freeplay song previews now fade out before restarting. ([aaf5084](https://github.com/FunkinCrew/Funkin/commit/aaf5084fd9e9c2d3b7e823767a93db82ce908d86)) - by @JVNpixels in [#6094](https://github.com/FunkinCrew/Funkin/pull/6094)
|
||||
- The game's volume is now reduced when unfocused without pausing. ([3040692](https://github.com/FunkinCrew/Funkin/commit/30406928522f88762eab4bbf571fedf1424f7569)) - by @PurSnake in [#6250](https://github.com/FunkinCrew/Funkin/pull/6250)
|
||||
- Optimized the way the windows in Week 3 are handled. ([70d433d](https://github.com/FunkinCrew/funkin.assets/commit/70d433de3027badb39027d295d1b18d20a130741)) - by @CrusherNotDrip in [funkin.assets#291](https://github.com/FunkinCrew/funkin.assets/pull/291)
|
||||
- Script create events now also run after hot-reloading with F5, rather than only during initialization. ([2f865a5](https://github.com/FunkinCrew/Funkin/commit/2f865a5c7f4d8712f1b1e0bc70cb910f4fed08dc)) - by @VirtuGuy in [#6084](https://github.com/FunkinCrew/Funkin/pull/6084)
|
||||
- Hot-reloading with F5 during a Chart Editor playtest no longer returns to the Chart Editor. ([c1988f1](https://github.com/FunkinCrew/Funkin/commit/c1988f1858344a78a82bf335774d986703cac942)) - by @KoloInDaCrib in [#6275](https://github.com/FunkinCrew/Funkin/pull/6275)
|
||||
- Opening the Chart Editor during a song now places the playhead at the current song position. ([71b4a58](https://github.com/FunkinCrew/Funkin/commit/71b4a58cab821c3b5a530e3a2be6d73966faa2a6)) - by @KoloInDaCrib in [#6210](https://github.com/FunkinCrew/Funkin/pull/6210)
|
||||
- The Easing property for Chart Editor events is now split into two separate dropdowns: type and direction. ([d1d77dd](https://github.com/FunkinCrew/Funkin/commit/d1d77dd85c3daed22ee954353fa41e81279ced3c)) - by @PurSnake in [#5612](https://github.com/FunkinCrew/Funkin/pull/5612)
|
||||
- The Chart Editor Events window now supports collapsible groups. ([9f6879e](https://github.com/FunkinCrew/Funkin/commit/9f6879ecc98e725afa3de8f9b02755047b353cd7)) - by @KoloInDaCrib in [#6554](https://github.com/FunkinCrew/Funkin/pull/6554)
|
||||
- Chart Editor BPM fields now support up to 3 decimal places. ([c863ef3](https://github.com/FunkinCrew/funkin.assets/commit/c863ef378569ce0d6b0804029e5d395b1c321cbd)) - by @roma-perec-bp in [funkin.assets#273](https://github.com/FunkinCrew/funkin.assets/pull/273)
|
||||
- Long difficulty and variation names are now shortened in the Chart Editor playbar. ([2c4167b](https://github.com/FunkinCrew/Funkin/commit/2c4167bc11aec92693494816735063c381279b3e)) - by @Lasercar in [#6150](https://github.com/FunkinCrew/Funkin/pull/6150)
|
||||
- The remaining time in the playbar now also shows a millisecond value in the Chart Editor. ([2d5ef19](https://github.com/FunkinCrew/Funkin/commit/2d5ef19b3b3c55db1eb70506b00d4f820e7ccb20)) - by @JVNpixels in [#6089](https://github.com/FunkinCrew/Funkin/pull/6089)
|
||||
- Added more hotkeys to the Chart Editor user guide. ([7d0d9eb](https://github.com/FunkinCrew/funkin.assets/commit/7d0d9ebf7ea60e04d09c9b140772c36493dc6291)) - by @afreetoplaynoob in [funkin.assets#294](https://github.com/FunkinCrew/funkin.assets/pull/294)
|
||||
- The Stage Editor can now be opened from Freeplay to the selected capsule's stage. ([1ff6a16](https://github.com/FunkinCrew/Funkin/commit/1ff6a1656cebdfe415768ffe38bd43b37c663eda)) - by @Lasercar in [#5264](https://github.com/FunkinCrew/Funkin/pull/5264)
|
||||
- The Stage Editor now saves backups upon quitting or crashing. ([5326c40](https://github.com/FunkinCrew/Funkin/commit/5326c40347b1548a682ef1f79eeb4a70618256ff)) - by @Lasercar in [#6190](https://github.com/FunkinCrew/Funkin/pull/6190)
|
||||
- Stage Editor backups are now saved in the same folder as Chart Editor backups. ([4f8da59](https://github.com/FunkinCrew/Funkin/commit/4f8da595777c4b7fe6ddf6536ffa29a50c23dcb3)) - by @CrusherNotDrip in [#6297](https://github.com/FunkinCrew/Funkin/pull/6297)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed some issues with Polymod:
|
||||
- Modded assets now properly go in their respective asset libraries. (Thanks NotHyper-474!)
|
||||
- Static functions and fields from imported scripted classes can now be used.
|
||||
- Properties can now be used in a static context.
|
||||
- Local variables in a function no longer get dropped if said function calls its own `scriptCall` function. (Thanks KoloInDaCrib!)
|
||||
- Local functions now properly report the class name.
|
||||
- [DESKTOP] Switching audio devices no longer breaks audio processing.
|
||||
- [HTML5] The Character Select Menu no longer crashes on Firefox.
|
||||
- Pressing Escape and an arrow key with the Freeplay alternate instrumental selector open no longer crashes the game.
|
||||
- Hot reloading with F5 on the Game Over screen no longer crashes the game.
|
||||
- [HTML5] Restarting a video cutscene now properly plays the video again.
|
||||
- [iOS] The navigation bar is no longer unlocked after watching an ad.
|
||||
- [iOS] The “larger text” accessibility setting no longer causes the game to not fill the whole screen.
|
||||
- [iOS] The game now displays error popups.
|
||||
- [ANDROID] Taking a screenshot no longer pauses the game for a moment.
|
||||
- The Title Screen no longer switches to Attract Mode while the Girlfriend’s Ringtone easter egg is playing.
|
||||
- Holding Escape in the Main Menu no longer quickly closes the game.
|
||||
- [MOBILE] Scrolling to another Week is no longer possible after entering a Story Week.
|
||||
- [MOBILE] Long Freeplay capsule song names now properly scroll sideways.
|
||||
- Exiting Character Select no longer allows mashing inputs in Freeplay.
|
||||
- Controller inputs now register in the Character Select Menu.
|
||||
- The player character in Character Select no longer slides in upon entering.
|
||||
- The Character Select cursor no longer flies in from the top left corner.
|
||||
- The Character Select cursor no longer continues to move without input after unfocusing the game.
|
||||
- Video cutscenes no longer start at maximum volume for a split second.
|
||||
- The input system now properly handles alternating between two keybinds bound to the same note.
|
||||
- The camera now bops independently from the framerate during songs.
|
||||
- The Pause Menu music now properly pauses when unfocusing with Pause on Unfocus enabled.
|
||||
- Changing to the same difficulty as the current one during a Story Week no longer resets the Week score.
|
||||
- More characters including parentheses now display properly in the Results Screen.
|
||||
- [MOBILE] The rimlight for Girlfriend (Tankman Stickup) now renders properly.
|
||||
- Character sprites with high global offsets no longer disappear near the edge of the screen.
|
||||
- Fixed positions for Pico (Pixel) and Nene (Pixel), and removed the School (Pico) stage.
|
||||
- Tweaked a few charts.
|
||||
- [macOS] Fixed a crash related to notifications in the Chart Editor.
|
||||
- [Windows] Opening the File Explorer no longer sometimes hangs in the Chart Editor.
|
||||
- Pressing F4 to exit a debug editor before a tooltip appears no longer crashes the game.
|
||||
- Changing the theme in the Chart Editor more than once no longer crashes the game.
|
||||
- Unplugging a controller no longer crashes the Chart Editor.
|
||||
- The Chart Editor measure ticks no longer cause a memory leak.
|
||||
- Selecting multiple events by Ctrl-clicking in the Chart Editor no longer converts one into the other.
|
||||
- Event tooltips now display all values including default ones.
|
||||
- Pause on Unfocus now properly applies to Chart Editor playtests.
|
||||
- Unfocusing while the Chart Editor is playing audio now pauses the playback.
|
||||
- Fixed a memory leak with the Freeplay backing card scrolling text. ([67846d6](https://github.com/FunkinCrew/Funkin/commit/67846d6c4bfd2f42e002fa129a51772f0717329d)) - by @FuroYT in [#5963](https://github.com/FunkinCrew/Funkin/pull/5963)
|
||||
- Using a static pixel icon in Character Select no longer crashes the game. ([96f8ee8](https://github.com/FunkinCrew/Funkin/commit/96f8ee85e0c2281834b7334b87d1862003e5fa05)) - by @VirtuGuy in [#6171](https://github.com/FunkinCrew/Funkin/pull/6171)
|
||||
- A-Bot's visualizer no longer causes a memory leak during gameplay or in Character Select. ([6607645](https://github.com/FunkinCrew/Funkin/commit/6607645786ed8611436fa5f92a35027c2a64f1d0)) - by @FuroYT in [#5908](https://github.com/FunkinCrew/Funkin/pull/5908)
|
||||
- The Credits menu no longer crashes when not using hardcoded credits. ([4366fbe](https://github.com/FunkinCrew/Funkin/commit/4366fbe51274438ad2bc190afcca42e37987386d)) - by @sphis-sinco in [#5982](https://github.com/FunkinCrew/Funkin/pull/5982)
|
||||
- The game no longer lag spikes when Darnell throws his can in 2hot. ([a0bc6d6](https://github.com/FunkinCrew/funkin.assets/commit/a0bc6d61fde5799a7fc619b7fdea691b0d8bae05)) - by @KoloInDaCrib in [funkin.assets#313](https://github.com/FunkinCrew/funkin.assets/pull/313)
|
||||
- Hold note covers no longer get permanently stuck playing after a lag spike. ([66882d9](https://github.com/FunkinCrew/Funkin/commit/66882d9677268396320ee003478a3e2627655755)) - by @KoloInDaCrib in [#6066](https://github.com/FunkinCrew/Funkin/pull/6066)
|
||||
- Scrolling through Freeplay songs for the first time no longer stutters. ([cc486ad](https://github.com/FunkinCrew/Funkin/commit/cc486ad94b5e9806d18fb65165663fea0ce0d7b4)) - by @mikolka9144 in [#4851](https://github.com/FunkinCrew/Funkin/pull/4851)
|
||||
- Re-implemented an error that appears when a video file does not exist. ([2ad5933](https://github.com/FunkinCrew/Funkin/commit/2ad593341f8bea8384646f4803dc8795a9efe32a)) - by @TechnikTil in [#6253](https://github.com/FunkinCrew/Funkin/pull/6253)
|
||||
- [ANDROID] Keyboard detection and strumline positioning now work properly. ([5763ae5](https://github.com/FunkinCrew/Funkin/commit/5763ae5b728359c5639bbc578f15b65a16fa57b1)) - by @NotHyper-474 in [#6328](https://github.com/FunkinCrew/Funkin/pull/6328)
|
||||
- The game now properly parses .json files beginning with a bracket. ([c7367ae](https://github.com/FunkinCrew/Funkin/commit/c7367ae33d7e11f57b821f7d5fbff39a3edc1604)) - by @NotHyper-474 in [#6164](https://github.com/FunkinCrew/Funkin/pull/6164)
|
||||
- The Newgrounds logo no longer covers the intro text. ([28883fb](https://github.com/FunkinCrew/Funkin/commit/28883fb9ed025abf9f9aee2621f365cd6f4d8b6c)) - by @hucks5 in [#6196](https://github.com/FunkinCrew/Funkin/pull/6196)
|
||||
- Exiting from the Save Data Options menu no longer exits the Options Menu as well. ([26b253d](https://github.com/FunkinCrew/Funkin/commit/26b253d8cac97f0dcd09a5ef233f7058cd26008e)) - by @VirtuGuy in [#6036](https://github.com/FunkinCrew/Funkin/pull/6036)
|
||||
- Numeric values in the Options Menu are now rounded to prevent near-zero precision errors. ([934bce7](https://github.com/FunkinCrew/Funkin/commit/934bce7bdc5780e09623d845eda97a27c0ad0ecf)) - by @Starexify in [#6225](https://github.com/FunkinCrew/Funkin/pull/6225)
|
||||
- The Test/Offset Calibration menu no longer starts playing after exiting the Lag Adjustment menu. ([23a38f7](https://github.com/FunkinCrew/Funkin/commit/23a38f76f9c79c5fab5b9af5e2d98e51597e05a8)) - by @VioletSnowLeopard in [#6088](https://github.com/FunkinCrew/Funkin/pull/6088)
|
||||
- [MOBILE] The Back button now fades out when entering a Week in Story Mode. ([1ebce74](https://github.com/FunkinCrew/Funkin/commit/1ebce748d4e3306b4464a7bf82fab56bcb427a62)) - by @VirtuGuy in [#6255](https://github.com/FunkinCrew/Funkin/pull/6255)
|
||||
- The Freeplay difficulty can now only be changed in one direction at a time. ([95af46c](https://github.com/FunkinCrew/Funkin/commit/95af46cbf72291d9d3226df91ed17f1aa3d3839f)) - by @Starexify in [#6262](https://github.com/FunkinCrew/Funkin/pull/6262)
|
||||
- Pico's backing card now renders properly in widescreen aspect ratios. ([aa29b3d](https://github.com/FunkinCrew/funkin.assets/commit/aa29b3dcc1ee2f8e89d2622c480b2e997410d57d)) - by @VirtuGuy in [funkin.assets#297](https://github.com/FunkinCrew/funkin.assets/pull/297)
|
||||
- The album titles in Freeplay are now positioned consistently. ([ed8cca4](https://github.com/FunkinCrew/funkin.assets/commit/ed8cca4694a098ae6aa56c4d2a231587edb9688c)) - by @Donothan73 in [funkin.assets#257](https://github.com/FunkinCrew/funkin.assets/pull/257)
|
||||
- Health icons now bop independently from the framerate. ([8e6fa51](https://github.com/FunkinCrew/Funkin/commit/8e6fa51c27ceaa830efec06bb8a52e281a008255)) - by @PurSnake in [#6035](https://github.com/FunkinCrew/Funkin/pull/6035)
|
||||
- The note resetting animation on song restart is now consistent between Downscroll and Upscroll. ([7b8d657](https://github.com/FunkinCrew/Funkin/commit/7b8d6577eddba568f20c3d23663dded054699090)) - by @FuroYT in [#6220](https://github.com/FunkinCrew/Funkin/pull/6220)
|
||||
- Offsets are now properly applied to stage props. ([33a020a](https://github.com/FunkinCrew/Funkin/commit/33a020a34bb220f56c0ff9fbad8688e10ca5b44f)) - by @Starexify in [#6224](https://github.com/FunkinCrew/Funkin/pull/6224)
|
||||
- Notestyles now accept offset values. ([193c443](https://github.com/FunkinCrew/Funkin/commit/193c443bc47c1a7d1481d280a1d0674b920a323c)) - by @NebulaStellaNova in [#6326](https://github.com/FunkinCrew/Funkin/pull/6326)
|
||||
- The path for a notestyle's countdown sound is now properly loaded as a sound instead of an image. ([dd5ea77](https://github.com/FunkinCrew/Funkin/commit/dd5ea7748b3b1878139998c209aa06db0fa05e0b)) - by @gamerbross in [#6232](https://github.com/FunkinCrew/Funkin/pull/6232)
|
||||
- The Pause Menu theme no longer plays at maximum volume for a split second when pausing. ([6ba44d6](https://github.com/FunkinCrew/Funkin/commit/6ba44d6cb0c71deb29eb23b82b61f58b2e95193e)) - by @ComedyLost in [#6334](https://github.com/FunkinCrew/Funkin/pull/6334)
|
||||
- The scrolling text in the Results Screen is now positioned consistently. ([200f798](https://github.com/FunkinCrew/Funkin/commit/200f7984379cb1bd911e49de92be312750893420)) - by @NotHyper-474 in [#6595](https://github.com/FunkinCrew/Funkin/pull/6595)
|
||||
- The scrolling text in the Results Screen is now FPS-independent. ([8c43f6f](https://github.com/FunkinCrew/Funkin/commit/8c43f6f67a362bde4de4ad8dbd72966d7c4a9f60)) - by @VirtuGuy in [#6476](https://github.com/FunkinCrew/Funkin/pull/6476)
|
||||
- The Results Screen theme now loops more smoothly. ([592bf1a](https://github.com/FunkinCrew/Funkin/commit/592bf1a9221d4dcef53ae421571eddf78ed631e4)) - by @FuroYT in [#6477](https://github.com/FunkinCrew/Funkin/pull/6477)
|
||||
- Transition stickers now render properly with the window resized to any aspect ratio. ([4215b1c](https://github.com/FunkinCrew/Funkin/commit/4215b1c72e4391b2176014825885799b81dde2ba)) - by @PurSnake in [#6156](https://github.com/FunkinCrew/Funkin/pull/6156)
|
||||
- Boyfriend now stands in the same position in the Week 3 and Week 3 Erect stages. ([d2d15f1](https://github.com/FunkinCrew/funkin.assets/commit/d2d15f114256125d53c6331b46415701ae291c1f)) - by @CEliuxJV in [funkin.assets#261](https://github.com/FunkinCrew/funkin.assets/pull/261)
|
||||
- Pico's game over music now properly loops after his explosion death in 2hot. ([f1a1db0](https://github.com/FunkinCrew/Funkin/commit/f1a1db08347c3f9248fd16909cff06027297ad13)) - by @KoloInDaCrib in [#6216](https://github.com/FunkinCrew/Funkin/pull/6216)
|
||||
- 2hot now smoothly transitions into the cutscene. ([462b1b6](https://github.com/FunkinCrew/funkin.assets/commit/462b1b6e6e9b622ad8fd15f15a1f39c7dffa4764)) - by @hucks5 in [funkin.assets#286](https://github.com/FunkinCrew/funkin.assets/pull/286)
|
||||
- Nene now swings her legs during in-game cutscenes. ([6a5d9b2](https://github.com/FunkinCrew/funkin.assets/commit/6a5d9b265732d60da28083fb1c3d7b7b854edf63)) - by @hucks5 in [funkin.assets#289](https://github.com/FunkinCrew/funkin.assets/pull/289)
|
||||
- Nene's 50 combo animation now renders its heart properly. ([bcd96ee](https://github.com/FunkinCrew/funkin.assets/commit/bcd96eeb78b9b03cf7b24dc956ac3bc36438193e)) - by @VirtuGuy in [funkin.assets#296](https://github.com/FunkinCrew/funkin.assets/pull/296)
|
||||
- A-Bot (Christmas) now properly looks at the opponent. ([f81cc16](https://github.com/FunkinCrew/funkin.assets/commit/f81cc16c8f99655f76b7e27ce221819798c9c94e)) - by @ComedyLost in [funkin.assets#279](https://github.com/FunkinCrew/funkin.assets/pull/279)
|
||||
- Nene (Pixel) now raises and lowers her knife properly. ([1d9f50d](https://github.com/FunkinCrew/funkin.assets/commit/1d9f50d405f202e07efd883aeef5d6c1e9eaaa90)) - by @VirtuGuy in [funkin.assets#274](https://github.com/FunkinCrew/funkin.assets/pull/274)
|
||||
- Pico (Pixel)'s game over theme now plays at the right time. ([e8a4d24](https://github.com/FunkinCrew/funkin.assets/commit/e8a4d2472ca2dc28b03be05953dff3d7b5ec7433)) - by @JackXson-Real and @ComedyLost in [funkin.assets#288](https://github.com/FunkinCrew/funkin.assets/pull/288)
|
||||
- Pico holding Nene's idle animation no longer lasts too long. ([3483345](https://github.com/FunkinCrew/funkin.assets/commit/348334583746ec45f1c6ab2c5e1715a67038cc5c)) - by @JackXson-Real in [funkin.assets#271](https://github.com/FunkinCrew/funkin.assets/pull/271)
|
||||
- The tankmen in Stress no longer briefly appear behind the speakers when restarting. ([7f79ad8](https://github.com/FunkinCrew/funkin.assets/commit/7f79ad8c08d66fb909a9382de2c09f0430b756f1)) - by @VirtuGuy in [funkin.assets#298](https://github.com/FunkinCrew/funkin.assets/pull/298)
|
||||
- Encountering a Set Health Icon event during Minimal playtesting no longer crashes the Chart Editor. ([9f0a498](https://github.com/FunkinCrew/Funkin/commit/9f0a4988a3cc47581916a9750731bb7e63f168f3)) - by @charlesisfeline in [#6481](https://github.com/FunkinCrew/Funkin/pull/6481)
|
||||
- Typing in Chart Editor text fields no longer triggers bound key functions. ([3b5e589](https://github.com/FunkinCrew/Funkin/commit/3b5e5893c01c2b56036e53626eaea8d41e70f640)) - by @MightyTheArmiddilo in [#6601](https://github.com/FunkinCrew/Funkin/pull/6601)
|
||||
- Clicking between Chart Editor events no longer converts the second event's dropdown values into those of the first. ([c170c25](https://github.com/FunkinCrew/Funkin/commit/c170c256934ae316a735d38620ad5e24c2f15f4d)) - by @NotHyper-474 in [#6211](https://github.com/FunkinCrew/Funkin/pull/6211)
|
||||
- Chart Editor note kinds no longer lose their data when selecting multiple notes with different data. ([5a2c364](https://github.com/FunkinCrew/Funkin/commit/5a2c364c507fb4b86a740c3b2476c336496cd729)) - by @Lasercar in [#4248](https://github.com/FunkinCrew/Funkin/pull/4248)
|
||||
- Placing a note and removing it no longer breaks the cursor in the Chart Editor. ([8cb5ed9](https://github.com/FunkinCrew/Funkin/commit/8cb5ed91baf50b6f78eefc2e50612a01195a2742)) - by @Lasercar in [#4237](https://github.com/FunkinCrew/Funkin/pull/4237)
|
||||
- Playtesting in the Chart Editor now properly loads song variation scripts. ([055f620](https://github.com/FunkinCrew/Funkin/commit/055f62026ef44a928876d9065cc388da0905e9f1)) - by @PurSnake in [#6405](https://github.com/FunkinCrew/Funkin/pull/6405)
|
||||
- Playtesting in the Chart Editor no longer sometimes moves on to another song like Story Mode. ([9497378](https://github.com/FunkinCrew/Funkin/commit/9497378600bad44639ff8b3fc92cb329052fd08a)) - by @KoloInDaCrib in [#6192](https://github.com/FunkinCrew/Funkin/pull/6192)
|
||||
- The cursor is now hidden when playtesting through the Chart Editor. ([0471872](https://github.com/FunkinCrew/Funkin/commit/047187257b6712e61bf05fdebab33c7b3e9ac0f6)) - by @VirtuGuy in [#6137](https://github.com/FunkinCrew/Funkin/pull/6137)
|
||||
- The Chart Editor note moving sound no longer plays when dragging outside the grid. ([593456e](https://github.com/FunkinCrew/Funkin/commit/593456e53e02c53e71189fdb154e4a3e4f2b3092)) - by @amyspark-ng in [#4157](https://github.com/FunkinCrew/Funkin/pull/4157)
|
||||
- The Chart Editor music now fades in after a period of silence when enabled. ([960d2f3](https://github.com/FunkinCrew/Funkin/commit/960d2f3cf166400c6fecee212b87961f3df10c2e)) - by @Lasercar in [#4148](https://github.com/FunkinCrew/Funkin/pull/4148)
|
||||
- The Chart Editor Difficulty toolbox no longer displays the incorrect song name. ([dff2a1e](https://github.com/FunkinCrew/Funkin/commit/dff2a1e487dc20f86ef4f526a259083a7726ea6a)) - by @VirtuGuy in [#6547](https://github.com/FunkinCrew/Funkin/pull/6547)
|
||||
- Chart Editor notifications now display a bit higher. ([9e645db](https://github.com/FunkinCrew/Funkin/commit/9e645db0074424ffb2de42b27afb3adaff55de62)) - by @MightyTheArmiddilo in [#6445](https://github.com/FunkinCrew/Funkin/pull/6445)
|
||||
- The Yes and No text in the Chart Editor exit confirmation prompt buttons now always render properly. ([4c0d925](https://github.com/FunkinCrew/Funkin/commit/4c0d92507ae0b81a744d223e5292bf8d27013abd)) - by @NotHyper-474 in [#6173](https://github.com/FunkinCrew/Funkin/pull/6173)
|
||||
- Exiting the Chart Editor now consistently saves your audio preferences. ([2f77ccf](https://github.com/FunkinCrew/Funkin/commit/2f77ccfc6e93fabef27d3e06447f9f6edd8e840d)) - by @VioletSnowLeopard in [#6127](https://github.com/FunkinCrew/Funkin/pull/6127)
|
||||
- The Chart Editor and Stage Editor backup window now properly retrieves the most recently edited backup. ([a8dec0c](https://github.com/FunkinCrew/Funkin/commit/a8dec0cd7053f88865757f30a7d9f0ca5eb0fb02)) - by @Lasercar in [#6119](https://github.com/FunkinCrew/Funkin/pull/6119)
|
||||
- The Chart Editor backup dialog no longer appears if the latest backup is deleted. ([bc1d36d](https://github.com/FunkinCrew/Funkin/commit/bc1d36ddeab81a790e3604598248a9b2fe869cac)) - by @KoloInDaCrib in [#6027](https://github.com/FunkinCrew/Funkin/pull/6027)
|
||||
- Switching between Animation and Spritesheet mode no longer crashes the Animation Editor. ([a61016d](https://github.com/FunkinCrew/Funkin/commit/a61016d2dfc2ac6a50aae230969c036f0753cfaa)) - by @VirtuGuy in [#6114](https://github.com/FunkinCrew/Funkin/pull/6114)
|
||||
- The Animation Editor no longer displays a missing object icon next to the dropdown menus. ([b30ea36](https://github.com/FunkinCrew/Funkin/commit/b30ea36fcc982ce4ac0be9143fd4701e180fc684)) - by @KoloInDaCrib in [#6177](https://github.com/FunkinCrew/Funkin/pull/6177)
|
||||
- The Animation Editor onion skin now properly accounts for character scale and offsets. ([a76a868](https://github.com/FunkinCrew/Funkin/commit/a76a868cb6a27c1c76d09177b90cf6df3df29d17)) - by @VirtuGuy in [#6123](https://github.com/FunkinCrew/Funkin/pull/6123)
|
||||
- The debug cursor is now properly hidden when exiting the Animation Editor. ([f2f6b4c](https://github.com/FunkinCrew/Funkin/commit/f2f6b4c8591e1ab4a8c6f0ed998d6d25d7cc9f43)) - by @VirtuGuy in [#6112](https://github.com/FunkinCrew/Funkin/pull/6112)
|
||||
- Plenty more tiny fixes.
|
||||
|
||||
## New Contributors for 0.8.0
|
||||
|
||||
* @Eviethecoder made their first contribution in [#4036](https://github.com/FunkinCrew/Funkin/pull/4036)
|
||||
* @FuroYT made their first contribution in [#5908](https://github.com/FunkinCrew/Funkin/pull/5908)
|
||||
* @HeroEyad made their first contribution in [#6049](https://github.com/FunkinCrew/Funkin/pull/6049)
|
||||
* @hucks5 made their first contribution in [#6196](https://github.com/FunkinCrew/Funkin/pull/6196)
|
||||
* @Starexify made their first contribution in [#6225](https://github.com/FunkinCrew/Funkin/pull/6225)
|
||||
* @NebulaStellaNova made their first contribution in [#6326](https://github.com/FunkinCrew/Funkin/pull/6326)
|
||||
* @MightyTheArmiddilo made their first contribution in [#6445](https://github.com/FunkinCrew/Funkin/pull/6445)
|
||||
* @Donothan73 made their first contribution in [funkin.assets#257](https://github.com/FunkinCrew/funkin.assets/pull/257)
|
||||
* @CEliuxJV made their first contribution in [funkin.assets#261](https://github.com/FunkinCrew/funkin.assets/pull/261)
|
||||
* @roma-perec-bp made their first contribution in [funkin.assets#273](https://github.com/FunkinCrew/funkin.assets/pull/273)
|
||||
* @ComedyLost made their first contribution in [funkin.assets#279](https://github.com/FunkinCrew/funkin.assets/pull/279)
|
||||
|
||||
|
||||
|
||||
## [0.7.5] - 2025-09-19
|
||||
|
||||
### Added
|
||||
|
||||
+1
-1
@@ -197,7 +197,7 @@ You can view the `funkin-assets` license here: (https://github.com/FunkinCrew/fu
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2020-2024 The Funkin' Crew Inc.
|
||||
Copyright 2020-2026 The Funkin' Crew Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div align='center'><img src="https://fridaynightfunkin.wiki.gg/images/FNF_logo.png" width="800">
|
||||
<div align='center'><img src="docs/readme_images/FNF_logo.png" width="800">
|
||||
|
||||
<h2>Friday Night Funkin' is a rhythm game. Built using HaxeFlixel for <a href="https://ldjam.com/events/ludum-dare/47">Ludum Dare 47.</a></h2>
|
||||
|
||||
@@ -14,8 +14,8 @@ This game was made with love to Newgrounds and its community. Extra love to Tom
|
||||
<div align='center'>
|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="https://fridaynightfunkin.wiki.gg/images/d/d7/Title_Card.gif" alt="Title Screen" width="350"/></td>
|
||||
<td><img src="https://fridaynightfunkin.wiki.gg/images/9/99/Menu.png" alt="Main Menu" width="350"/></td>
|
||||
<td><img src="docs/readme_images/Title_Card.gif" alt="Title Screen" width="350"/></td>
|
||||
<td><img src="docs/readme_images/Menu.png" alt="Main Menu" width="350"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
+1
-1
Submodule art updated: 094ff10919...429ab728b1
+1
-1
Submodule assets updated: 5ca3c6a574...d1d027d474
+93
-23
@@ -5,26 +5,6 @@
|
||||
"blocksize": "10x10",
|
||||
"colorprofile": "cl",
|
||||
"custom": [
|
||||
{
|
||||
"asset": "asset/shared/images/resultScreen/results-pico/resultsPERFECT/spritemap1.png",
|
||||
"blocksize": "4x4"
|
||||
},
|
||||
{
|
||||
"asset": "asset/shared/images/resultScreen/results-pico/resultsGREAT/spritemap1.png",
|
||||
"blocksize": "4x4"
|
||||
},
|
||||
{
|
||||
"asset": "asset/shared/images/resultScreen/results-pico/resultsGOOD/spritemap1.png",
|
||||
"blocksize": "4x4"
|
||||
},
|
||||
{
|
||||
"asset": "assets/shared/images/resultScreen/results-bf/resultsEXCELLENT/spritemap1.png",
|
||||
"blocksize": "4x4"
|
||||
},
|
||||
{
|
||||
"asset": "asset/shared/images/resultScreen/results-pico/resultsSHIT/spritemap1.png",
|
||||
"blocksize": "4x4"
|
||||
},
|
||||
{
|
||||
"asset": "assets/preload/images/NOTE_assets.png",
|
||||
"blocksize": "4x4"
|
||||
@@ -41,10 +21,42 @@
|
||||
"asset": "assets/preload/images/alphabet.png",
|
||||
"blocksize": "4x4"
|
||||
},
|
||||
{
|
||||
"asset": "assets/preload/images/charSelect/gfChill/spritemap1.png",
|
||||
"blocksize": "6x6"
|
||||
},
|
||||
{
|
||||
"asset": "assets/shared/images/NOTE_hold_assets.png",
|
||||
"blocksize": "4x4"
|
||||
},
|
||||
{
|
||||
"asset": "assets/shared/images/characters/nene-dark/spritemap1.png",
|
||||
"blocksize": "6x6"
|
||||
},
|
||||
{
|
||||
"asset": "assets/shared/images/characters/otis/muzzle-flashes/otis_flashes.png",
|
||||
"blocksize": "4x4"
|
||||
},
|
||||
{
|
||||
"asset": "assets/shared/images/characters/sserafim/chaewon/spritemap1.png",
|
||||
"blocksize": "4x4"
|
||||
},
|
||||
{
|
||||
"asset": "assets/shared/images/characters/pico-dark/spritemap1.png",
|
||||
"blocksize": "4x4"
|
||||
},
|
||||
{
|
||||
"asset": "assets/shared/images/characters/pico/basic-animations/spritemap1.png",
|
||||
"blocksize": "6x6"
|
||||
},
|
||||
{
|
||||
"asset": "assets/shared/images/characters/pico/explosion-death/spritemap1.png",
|
||||
"blocksize": "8x8"
|
||||
},
|
||||
{
|
||||
"asset": "assets/shared/images/characters/pico/playable-animations/spritemap1.png",
|
||||
"blocksize": "6x6"
|
||||
},
|
||||
{
|
||||
"asset": "assets/shared/images/holdCoverBlue.png",
|
||||
"blocksize": "4x4"
|
||||
@@ -81,6 +93,30 @@
|
||||
"asset": "assets/shared/images/notes.png",
|
||||
"blocksize": "4x4"
|
||||
},
|
||||
{
|
||||
"asset": "assets/shared/images/resultScreen/results-pico/resultsGOOD/spritemap1.png",
|
||||
"blocksize": "4x4"
|
||||
},
|
||||
{
|
||||
"asset": "assets/shared/images/resultScreen/results-pico/resultsGREAT/spritemap1.png",
|
||||
"blocksize": "4x4"
|
||||
},
|
||||
{
|
||||
"asset": "assets/shared/images/resultScreen/results-pico/resultsPERFECT/spritemap1.png",
|
||||
"blocksize": "4x4"
|
||||
},
|
||||
{
|
||||
"asset": "assets/shared/images/resultScreen/results-pico/resultsSHIT/spritemap1.png",
|
||||
"blocksize": "4x4"
|
||||
},
|
||||
{
|
||||
"asset": "assets/sserafim/images/sserafim-lipsync-yunjin/spritemap1.png",
|
||||
"blocksize": "4x4"
|
||||
},
|
||||
{
|
||||
"asset": "assets/sserafim/images/sserafim-lipsync/spritemap1.png",
|
||||
"blocksize": "4x4"
|
||||
},
|
||||
{
|
||||
"asset": "assets/week1/image/erect/bg.png",
|
||||
"blocksize": "8x8"
|
||||
@@ -89,6 +125,10 @@
|
||||
"asset": "assets/week1/image/erect/lights.png",
|
||||
"blocksize": "6x6"
|
||||
},
|
||||
{
|
||||
"asset": "assets/shared/images/characters/pico/death/spritemap1.png",
|
||||
"blocksize": "6x6"
|
||||
},
|
||||
{
|
||||
"asset": "assets/week1/images/erect/crowd.png",
|
||||
"blocksize": "8x8"
|
||||
@@ -96,32 +136,60 @@
|
||||
{
|
||||
"asset": "assets/week1/images/erect/server.png",
|
||||
"blocksize": "8x8"
|
||||
},
|
||||
{
|
||||
"asset": "assets/week7/images/erect/rando/spritemap1.png",
|
||||
"blocksize": "6x6"
|
||||
},
|
||||
{
|
||||
"asset": "assets/week7/images/erect/sniper/spritemap1.png",
|
||||
"blocksize": "6x6"
|
||||
}
|
||||
],
|
||||
"excludes": [
|
||||
"assets/fonts/vcr-bmp.png",
|
||||
"assets/preload/images/cursor/",
|
||||
"assets/preload/images/fonts/",
|
||||
"assets/preload/images/freeplay/",
|
||||
"assets/preload/images/icons/",
|
||||
"assets/preload/images/soundtray/",
|
||||
"assets/preload/images/stageBuild/",
|
||||
"assets/preload/images/titleEnter.png",
|
||||
"assets/preload/images/titleEnter_mobile.png",
|
||||
"assets/preload/images/storymenu/titles/",
|
||||
"assets/preload/images/title-screen-text/spritemap1.png",
|
||||
"assets/preload/images/title-screen-text-mobile/spritemap1.png",
|
||||
"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/gf-christmas/spritemap1.png",
|
||||
"assets/shared/images/characters/gf-dark/spritemap1.png",
|
||||
"assets/shared/images/characters/gf/spritemap1.png",
|
||||
"assets/shared/images/characters/gfCar/spritemap1.png",
|
||||
"assets/shared/images/characters/gfPixel.png",
|
||||
"assets/shared/images/characters/nene/spritemap1.png",
|
||||
"assets/shared/images/characters/nenePixel/*",
|
||||
"assets/shared/images/characters/pico-holding-nene/spritemap1.png",
|
||||
"assets/shared/images/characters/picoPixel/*",
|
||||
"assets/shared/images/characters/senpai.png",
|
||||
"assets/shared/images/characters/spirit.png",
|
||||
"assets/shared/images/characters/sserafim/eunchae/spritemap1.png",
|
||||
"assets/shared/images/characters/sserafim/kazuha/spritemap1.png",
|
||||
"assets/shared/images/characters/sserafim/sserafim-gf/spritemap1.png",
|
||||
"assets/shared/images/characters/sserafim/yunjin/spritemap1.png",
|
||||
"assets/shared/images/characters/sserafim/sakura/spritemap1.png",
|
||||
"assets/shared/images/healthBar.png",
|
||||
"assets/shared/images/resultScreen/*",
|
||||
"assets/shared/images/resultScreen/clearPercent/",
|
||||
"assets/shared/images/resultScreen/rankText/",
|
||||
"assets/shared/images/resultScreen/results-bf/resultsEXCELLENT/spritemap1.png",
|
||||
"assets/shared/images/resultScreen/results-bf/resultsGOOD/resultGirlfriendGOOD.png",
|
||||
"assets/shared/images/resultScreen/results-bf/resultsGREAT/gf/spritemap1.png",
|
||||
"assets/shared/images/resultScreen/results-bf/resultsSHIT/gf/spritemap1.png",
|
||||
"assets/shared/images/ui/chart-editor/",
|
||||
"assets/shared/images/ui/countdown/pixel/*",
|
||||
"assets/sserafim/images/cutscene/cutsceneMain/spritemap1.png",
|
||||
"assets/sserafim/images/cutscene/ref.png",
|
||||
"assets/sserafim/images/ref.png",
|
||||
"assets/week1/*",
|
||||
"assets/week1/images/erect/brightLightSmall.png",
|
||||
"assets/week1/images/erect/lightAbove.png",
|
||||
@@ -130,6 +198,8 @@
|
||||
"assets/week1/images/erect/orangeLight.png",
|
||||
"assets/week2/images/erect/stairsDark.png",
|
||||
"assets/week2/images/erect/stairsLight.png",
|
||||
"assets/week6/"
|
||||
"assets/week6/",
|
||||
"assets/week7/images/bricksGround.png",
|
||||
"assets/week7/images/erect/bricksGround.png"
|
||||
]
|
||||
}
|
||||
|
||||
+318
-163
@@ -1,4 +1,6 @@
|
||||
{
|
||||
"version": 1,
|
||||
"exclude": {},
|
||||
"defaultSeverity": "INFO",
|
||||
"extendsConfigPath": "",
|
||||
"numberOfCheckerThreads": 5,
|
||||
@@ -6,113 +8,124 @@
|
||||
"defineCombinations": [],
|
||||
"checks": [
|
||||
{
|
||||
"props": {},
|
||||
"comment": "If enabled, prevents using anonymous types inline without creating a typedef for them.",
|
||||
"props": { "severity": "INFO" },
|
||||
"type": "Anonymous"
|
||||
},
|
||||
{
|
||||
"comment": "If enabled, mandates spacing around array access syntax.",
|
||||
"props": {
|
||||
"spaceBefore": false,
|
||||
"spaceInside": false
|
||||
"spaceInside": false,
|
||||
"severity": "INFO"
|
||||
},
|
||||
"type": "ArrayAccess"
|
||||
},
|
||||
{
|
||||
"props": {},
|
||||
"comment": "If enabled, mandates array literals be defined with [] instead of 'new'.",
|
||||
"props": { "severity": "WARNING" },
|
||||
"type": "ArrayLiteral"
|
||||
},
|
||||
{
|
||||
"comment": "If enabled, mandates specific syntax for the use of array literals.",
|
||||
"props": {
|
||||
"allowSingleArgParens": true,
|
||||
"allowReturn": true,
|
||||
"allowFunction": true,
|
||||
"allowCurlyBody": true
|
||||
"allowCurlyBody": true,
|
||||
"severity": "INFO"
|
||||
},
|
||||
"type": "ArrowFunction"
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"avoidIdentifiers": []
|
||||
},
|
||||
"comment": "Specify a list of variable and function names which are not allowed and should be renamed. Doesn't support regex.",
|
||||
"props": { "avoidIdentifiers": [], "severity": "WARNING" },
|
||||
"type": "AvoidIdentifier"
|
||||
},
|
||||
{
|
||||
"props": {},
|
||||
"comment": "If enabled, prevents imports like 'import flixel.*' from being used.",
|
||||
"props": { "severity": "INFO" },
|
||||
"type": "AvoidStarImport"
|
||||
},
|
||||
{
|
||||
"props": {},
|
||||
"comment": "If enabled, prevents conditional compilation from crossing blocks. This prevents the case where conditional compilation could potentially create invalid syntax.",
|
||||
"props": { "severity": "INFO" },
|
||||
"type": "BlockBreakingConditional"
|
||||
},
|
||||
{
|
||||
"comment": "Specifies a specific naming format for the parameter of a catch block.",
|
||||
"props": {
|
||||
"format": "^(e|t|ex|[a-z][a-z][a-zA-Z]+)$"
|
||||
"format": "^(e|t|ex|[a-z][a-z][a-zA-Z]+)$",
|
||||
"severity": "INFO"
|
||||
},
|
||||
"type": "CatchParameterName"
|
||||
},
|
||||
{
|
||||
"comment": "If enabled, flags when large blocks of similar code should be extracted to a function.",
|
||||
"props": {
|
||||
"thresholdSimilar": 120,
|
||||
"severityIdentical": "WARNING",
|
||||
"thresholdIdentical": 60
|
||||
"thresholdIdentical": 60,
|
||||
"severityIdentical": "IGNORE"
|
||||
},
|
||||
"type": "CodeSimilarity"
|
||||
},
|
||||
{
|
||||
"props": {},
|
||||
"comment": "If enabled, flags Haxe code that is commented out. Unused code should be removed and accessed via version control later if needed.",
|
||||
"props": { "severity": "WARNING" },
|
||||
"type": "CommentedOutCode"
|
||||
},
|
||||
{
|
||||
"comment": "If enabled, flags when conditional compilation blocks are not properly aligned. NOTE:Disabled because it's kinda broken.",
|
||||
"props": {
|
||||
"severity": "IGNORE",
|
||||
"policy": "aligned",
|
||||
"allowSingleline": true
|
||||
"allowSingleline": true,
|
||||
"severity": "IGNORE"
|
||||
},
|
||||
"type": "ConditionalCompilation"
|
||||
},
|
||||
{
|
||||
"comment": "Specifies a specific naming format for constant (static) identifiers.",
|
||||
"props": {
|
||||
"ignoreExtern": true,
|
||||
"format": "^?:_[a-zA-Z0-9]+(?:_[a-zA-Z0-9]+)*$",
|
||||
"tokens": ["INLINE", "NOTINLINE"]
|
||||
"tokens": ["INLINE", "NOTINLINE"],
|
||||
"severity": "INFO"
|
||||
},
|
||||
"type": "ConstantName"
|
||||
},
|
||||
{
|
||||
"comment": "Calculates the complexity of methods using the McCabe metric, and throws warnings at certain thresholds. Indicates that a function should be refactored or split up.",
|
||||
"props": {
|
||||
"thresholds": [
|
||||
{
|
||||
"complexity": 20,
|
||||
"severity": "WARNING"
|
||||
},
|
||||
{
|
||||
"complexity": 25,
|
||||
"severity": "ERROR"
|
||||
}
|
||||
{ "complexity": 20, "severity": "WARNING" },
|
||||
{ "complexity": 25, "severity": "ERROR" }
|
||||
],
|
||||
"severity": "IGNORE"
|
||||
},
|
||||
"type": "CyclomaticComplexity"
|
||||
},
|
||||
{
|
||||
"props": {},
|
||||
"comment": "If enabled, the default case of a switch statement must be last.",
|
||||
"props": { "severity": "INFO" },
|
||||
"type": "DefaultComesLast"
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"lineStyle": "onestar",
|
||||
"startStyle": "ignore"
|
||||
},
|
||||
"comment": "Specifies the format for doc-style comments ().",
|
||||
"props": { "lineStyle": "onestar", "startStyle": "ignore" },
|
||||
"type": "DocCommentStyle"
|
||||
},
|
||||
{
|
||||
"comment": "If enabled, prevents the use of the Dynamic type.",
|
||||
"props": { "severity": "IGNORE" },
|
||||
"type": "Dynamic"
|
||||
},
|
||||
{
|
||||
"props": {},
|
||||
"comment": "If enabled, ensures ~/.../ is used instead of 'new EReg()'",
|
||||
"props": { "severity": "WARNING" },
|
||||
"type": "ERegLiteral"
|
||||
},
|
||||
{
|
||||
"comment": "If enabled, enforces specific syntax on empty blocks (require only one line, etc.)",
|
||||
"props": {
|
||||
"tokens": [
|
||||
"CLASS_DEF",
|
||||
@@ -129,11 +142,13 @@
|
||||
"TRY",
|
||||
"CATCH"
|
||||
],
|
||||
"option": "empty"
|
||||
"option": "empty",
|
||||
"severity": "IGNORE"
|
||||
},
|
||||
"type": "EmptyBlock"
|
||||
},
|
||||
{
|
||||
"comment": "If enabled, flags when consecutive empty lines are used.",
|
||||
"props": {
|
||||
"requireEmptyLineAfterPackage": true,
|
||||
"requireEmptyLineAfterInterface": true,
|
||||
@@ -141,17 +156,18 @@
|
||||
"allowEmptyLineAfterSingleLineComment": true,
|
||||
"max": 1,
|
||||
"requireEmptyLineAfterClass": true,
|
||||
"allowEmptyLineAfterMultiLineComment": true
|
||||
"allowEmptyLineAfterMultiLineComment": true,
|
||||
"severity": "IGNORE"
|
||||
},
|
||||
"type": "EmptyLines"
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"enforceEmptyPackage": true
|
||||
},
|
||||
"comment": "If enabled, mandates the use of 'package;' on root level modules.",
|
||||
"props": { "enforceEmptyPackage": true, "severity": "WARNING" },
|
||||
"type": "EmptyPackage"
|
||||
},
|
||||
{
|
||||
"comment": "If enabled, provides advanced checking for when consecutive empty lines are used.",
|
||||
"props": {
|
||||
"none": [],
|
||||
"upto": [],
|
||||
@@ -160,11 +176,13 @@
|
||||
"max": 1,
|
||||
"ignore": [],
|
||||
"atleast": [],
|
||||
"skipSingleLineTypes": true
|
||||
"skipSingleLineTypes": true,
|
||||
"severity": "IGNORE"
|
||||
},
|
||||
"type": "ExtendedEmptyLines"
|
||||
},
|
||||
{
|
||||
"comment": "If enabled, mandates documentation comments for public fields.",
|
||||
"props": {
|
||||
"requireParams": true,
|
||||
"fieldType": "BOTH",
|
||||
@@ -172,33 +190,33 @@
|
||||
"ignoreOverride": true,
|
||||
"tokens": ["ABSTRACT_DEF", "CLASS_DEF", "ENUM_DEF", "INTERFACE_DEF"],
|
||||
"modifier": "PUBLIC",
|
||||
"excludeNames": ["new", "toString"]
|
||||
"excludeNames": ["new", "toString"],
|
||||
"severity": "INFO"
|
||||
},
|
||||
"type": "FieldDocComment"
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"max": 1000,
|
||||
"ignoreEmptyLines": true,
|
||||
"severity": "IGNORE"
|
||||
},
|
||||
"comment": "If enabled, warns if files exceed a certain number of lines.",
|
||||
"props": { "max": 1000, "ignoreEmptyLines": true, "severity": "IGNORE" },
|
||||
"type": "FileLength"
|
||||
},
|
||||
{
|
||||
"comment": "If enabled, requires modules match the casing of their file name.",
|
||||
"type": "FileNameCase",
|
||||
"props": {}
|
||||
"props": { "severity": "WARNING" }
|
||||
},
|
||||
{
|
||||
"comment": "If enabled, requires inline/static variables to be declared final.",
|
||||
"type": "Final",
|
||||
"props": {}
|
||||
"props": { "severity": "IGNORE" }
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"option": "upperCase"
|
||||
},
|
||||
"comment": "If enabled, requires hexadecimal literals to be in upper case. 0xFFFFFF is valid and 0xffffff is invalid.",
|
||||
"props": { "option": "upperCase", "severity": "WARNING" },
|
||||
"type": "HexadecimalLiteral"
|
||||
},
|
||||
{
|
||||
"comment": "If enabled, warns if a local variable or parameter shadows an existing field.",
|
||||
"props": {
|
||||
"ignoreSetter": true,
|
||||
"ignoreFormat": "^(main|run)$",
|
||||
@@ -207,108 +225,167 @@
|
||||
"type": "HiddenField"
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"character": " ",
|
||||
"severity": "IGNORE"
|
||||
},
|
||||
"comment": "If enabled, mandates indentation.",
|
||||
"props": { "character": "", "severity": "IGNORE" },
|
||||
"type": "Indentation"
|
||||
},
|
||||
{
|
||||
"comment": "If enabled, mandates characters used for indentation.",
|
||||
"props": {
|
||||
"character": "space",
|
||||
"severity": "INFO",
|
||||
"severity": "IGNORE",
|
||||
"ignorePattern": "^$"
|
||||
},
|
||||
"type": "IndentationCharacter"
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"ignoreReturnAssignments": true,
|
||||
"severity": "WARNING"
|
||||
},
|
||||
"comment": "If enabled, warns about assignments performed within another expression, like 'if (a=b){}'",
|
||||
"props": { "ignoreReturnAssignments": true, "severity": "WARNING" },
|
||||
"type": "InnerAssignment"
|
||||
},
|
||||
{
|
||||
"comment": "If enabled, enforces that interfaces should not contain properties.",
|
||||
"props": {
|
||||
"allowProperties": false,
|
||||
"allowMarkerInterfaces": true
|
||||
"allowMarkerInterfaces": true,
|
||||
"severity": "WARNING"
|
||||
},
|
||||
"type": "Interface"
|
||||
},
|
||||
{
|
||||
"comment": "If enabled, enforces a newline before the left curly brace of a block.",
|
||||
"props": {
|
||||
"option": "nl",
|
||||
"ignoreEmptySingleline": true,
|
||||
"tokens": [
|
||||
"CLASS_DEF",
|
||||
"ENUM_DEF",
|
||||
"ABSTRACT_DEF",
|
||||
"TYPEDEF_DEF",
|
||||
"INTERFACE_DEF",
|
||||
"FUNCTION",
|
||||
"FOR",
|
||||
"IF",
|
||||
"WHILE",
|
||||
"SWITCH",
|
||||
"TRY",
|
||||
"CATCH",
|
||||
"REIFICATION",
|
||||
"ARRAY_COMPREHENSION"
|
||||
],
|
||||
"severity": "INFO"
|
||||
},
|
||||
"type": "LeftCurly"
|
||||
},
|
||||
{
|
||||
"comment": "If enabled, enforces the left curly brace occurs on the end of the line, without a newline.",
|
||||
"props": {
|
||||
"option": "eol",
|
||||
"ignoreEmptySingleline": true,
|
||||
"tokens": ["OBJECT_DECL"],
|
||||
"severity": "IGNORE"
|
||||
},
|
||||
"type": "LeftCurly"
|
||||
},
|
||||
{
|
||||
"comment": "If enabled, warns when lines of code exceed a certain length.",
|
||||
"props": {
|
||||
"max": 160,
|
||||
"ignorePattern": "^(\\s*['\"][^'\"]*['\"].*)$"
|
||||
"ignorePattern": "^(\\s*['\"][^'\"]*['\"].*)$",
|
||||
"severity": "INFO"
|
||||
},
|
||||
"type": "LineLength"
|
||||
},
|
||||
{
|
||||
"comment": "If enabled, mandates a specific naming convention for listener methods.",
|
||||
"props": {
|
||||
"listeners": ["addEventListener", "addListener", "on", "once"],
|
||||
"format": "^_?[a-z][a-zA-Z0-9]*$"
|
||||
"format": "^_?[a-z][a-zA-Z0-9]*$",
|
||||
"severity": "IGNORE"
|
||||
},
|
||||
"type": "ListenerName"
|
||||
},
|
||||
{
|
||||
"comment": "Specifies a specific naming format for local variables.",
|
||||
"props": {
|
||||
"ignoreExtern": true,
|
||||
"format": "^[a-z][a-zA-Z0-9]*$",
|
||||
"tokens": []
|
||||
"tokens": [],
|
||||
"severity": "INFO"
|
||||
},
|
||||
"type": "LocalVariableName"
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"ignoreNumbers": [-1, 0, 1, 2],
|
||||
"severity": "IGNORE"
|
||||
},
|
||||
"comment": "If enabled, warns when unlabelled numeric values get used. Use named constants instead to improve code clarity.",
|
||||
"props": { "ignoreNumbers": [-1, 0, 1, 2], "severity": "INFO" },
|
||||
"type": "MagicNumber"
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"format": "^[A-Z][a-zA-Z0-9]*$",
|
||||
"tokens": ["ABSTRACT"]
|
||||
},
|
||||
"type": "MemberName"
|
||||
},
|
||||
{
|
||||
"comment": "Specifies a specific naming format for public abstract member fields.",
|
||||
"props": {
|
||||
"format": "^[_a-zA-Z][a-z][a-zA-Z0-9]*$",
|
||||
"tokens": ["ABSTRACT"]
|
||||
"tokens": ["PUBLIC", "ABSTRACT"]
|
||||
},
|
||||
"type": "MemberName"
|
||||
},
|
||||
{
|
||||
"comment": "Specifies a specific naming format for private abstract member fields.",
|
||||
"props": {
|
||||
"ignoreExtern": true,
|
||||
"format": "^[_a-z][a-zA-Z0-9]*$",
|
||||
"tokens": ["PUBLIC", "PRIVATE", "CLASS", "TYPEDEF"]
|
||||
"format": "^[_a-zA-Z][a-z][a-zA-Z0-9]*$",
|
||||
"tokens": ["PRIVATE", "ABSTRACT"]
|
||||
},
|
||||
"type": "MemberName"
|
||||
},
|
||||
|
||||
{
|
||||
"comment": "Specifies a specific naming format for public class member fields.",
|
||||
"props": {
|
||||
"format": "^[_a-zA-Z][a-z][a-zA-Z0-9]*$",
|
||||
"tokens": ["PUBLIC", "CLASS"]
|
||||
},
|
||||
"type": "MemberName"
|
||||
},
|
||||
{
|
||||
"comment": "Specifies a specific naming format for private class member fields.",
|
||||
"props": {
|
||||
"format": "^[_a-zA-Z][a-z][a-zA-Z0-9]*$",
|
||||
"tokens": ["PRIVATE", "CLASS"]
|
||||
},
|
||||
"type": "MemberName"
|
||||
},
|
||||
{
|
||||
"comment": "Specifies a specific naming format for enum member fields.",
|
||||
"props": {
|
||||
"format": "^[_a-zA-Z][a-z][a-zA-Z0-9]*$",
|
||||
"tokens": ["ENUM"]
|
||||
},
|
||||
"type": "MemberName"
|
||||
},
|
||||
{
|
||||
"comment": "Specifies a specific naming format for typedef member fields.",
|
||||
"props": {
|
||||
"format": "^[_a-zA-Z][a-z][a-zA-Z0-9]*$",
|
||||
"tokens": ["TYPEDEF"]
|
||||
},
|
||||
"type": "MemberName"
|
||||
},
|
||||
{
|
||||
"comment": "If enabled, enforces that modules should not exceed a certain number of methods.",
|
||||
"props": {
|
||||
"maxPrivate": 100,
|
||||
"maxPublic": 100,
|
||||
"maxTotal": 100
|
||||
"maxTotal": 100,
|
||||
"severity": "IGNORE"
|
||||
},
|
||||
"type": "MethodCount"
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"max": 50,
|
||||
"ignoreEmptyLines": true,
|
||||
"severity": "IGNORE"
|
||||
},
|
||||
"comment": "If enabled, enforces that methods should not exceed a certain number of lines of code, excluding empty lines.",
|
||||
"props": { "max": 50, "ignoreEmptyLines": true, "severity": "IGNORE" },
|
||||
"type": "MethodLength"
|
||||
},
|
||||
{
|
||||
"comment": "Specifies a specific naming format for method names.",
|
||||
"props": {
|
||||
"ignoreExtern": true,
|
||||
"format": "^[a-z][a-zA-Z0-9]*$",
|
||||
@@ -318,6 +395,7 @@
|
||||
"type": "MethodName"
|
||||
},
|
||||
{
|
||||
"comment": "Specifies a specific order for modifiers on fields.",
|
||||
"props": {
|
||||
"modifiers": [
|
||||
"PUBLIC_PRIVATE",
|
||||
@@ -333,57 +411,64 @@
|
||||
"type": "ModifierOrder"
|
||||
},
|
||||
{
|
||||
"comment": "If enabled, complains when a specific string literal is used in the same file multiple times. Use a named constant instead.",
|
||||
"props": {
|
||||
"minLength": 2,
|
||||
"ignore": "^\\s+$",
|
||||
"allowDuplicates": 5
|
||||
"allowDuplicates": 5,
|
||||
"severity": "IGNORE"
|
||||
},
|
||||
"type": "MultipleStringLiterals"
|
||||
},
|
||||
{
|
||||
"props": {},
|
||||
"comment": "If enabled, complains when a multiple variables are declared in a single expression. Use individual lines.",
|
||||
"props": { "severity": "WARNING" },
|
||||
"type": "MultipleVariableDeclarations"
|
||||
},
|
||||
{
|
||||
"comment": "If enabled, mandates the use of braces on specific block types, unless they fit on a single line.",
|
||||
"props": {
|
||||
"allowSingleLineStatement": true,
|
||||
"tokens": ["ELSE_IF", "WHILE", "DO_WHILE"]
|
||||
"tokens": [
|
||||
"FUNCTION",
|
||||
"FOR",
|
||||
"IF",
|
||||
"ELSE_IF",
|
||||
"WHILE",
|
||||
"DO_WHILE",
|
||||
"CATCH"
|
||||
],
|
||||
"severity": "INFO"
|
||||
},
|
||||
"type": "NeedBraces"
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"max": 3,
|
||||
"severity": "IGNORE"
|
||||
},
|
||||
"comment": "If enabled, complains when control flow statements are nested too deep (indicating high complexity).",
|
||||
"props": { "max": 3, "severity": "IGNORE" },
|
||||
"type": "NestedControlFlow"
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"max": 1,
|
||||
"severity": "IGNORE"
|
||||
},
|
||||
"comment": "If enabled, complains when 'for' loops are nested too deep (indicating high complexity).",
|
||||
"props": { "max": 1, "severity": "IGNORE" },
|
||||
"type": "NestedForDepth"
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"max": 4
|
||||
},
|
||||
"comment": "If enabled, complains when 'if' statements are nested too deep (indicating high complexity).",
|
||||
"props": { "max": 4, "severity": "IGNORE" },
|
||||
"type": "NestedIfDepth"
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"max": 1
|
||||
},
|
||||
"comment": "If enabled, complains when try/catch statements are nested too deep (indicating high complexity).",
|
||||
"props": { "max": 1, "severity": "INFO" },
|
||||
"type": "NestedTryDepth"
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"option": "questionMark"
|
||||
},
|
||||
"comment": "If enabled, enforces ?value rather than value = null.",
|
||||
"props": { "option": "questionMark", "severity": "INFO" },
|
||||
"type": "NullableParameter"
|
||||
},
|
||||
{
|
||||
"comment": "If enabled, enforces that operators should be surrounded by whitespace.",
|
||||
"props": {
|
||||
"oldFunctionTypePolicy": "none",
|
||||
"unaryOpPolicy": "none",
|
||||
@@ -401,6 +486,7 @@
|
||||
"type": "OperatorWhitespace"
|
||||
},
|
||||
{
|
||||
"comment": "If enabled, enforces that most operators should be after a newline rather than before.",
|
||||
"props": {
|
||||
"tokens": [
|
||||
"+",
|
||||
@@ -443,54 +529,52 @@
|
||||
"type": "OperatorWrap"
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"tokens": ["="],
|
||||
"option": "eol"
|
||||
},
|
||||
"comment": "If enabled, enforces that assignment operators should be before a newline rather than after.",
|
||||
"props": { "tokens": ["="], "option": "eol" },
|
||||
"type": "OperatorWrap"
|
||||
},
|
||||
{
|
||||
"comment": "Specifies a specific naming format for method parameter names.",
|
||||
"props": {
|
||||
"ignoreExtern": true,
|
||||
"format": "^(_|[a-z][a-zA-Z0-9]*$)",
|
||||
"tokens": []
|
||||
"tokens": [],
|
||||
"severity": "INFO"
|
||||
},
|
||||
"type": "ParameterName"
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"max": 7,
|
||||
"ignoreOverriddenMethods": false
|
||||
},
|
||||
"comment": "If enabled, complains when there are too many parameters on a method.",
|
||||
"props": { "max": 7, "ignoreOverriddenMethods": false },
|
||||
"type": "ParameterNumber"
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"severity": "WARNING"
|
||||
},
|
||||
"comment": "If enabled, complains when you make a property accessor method public.",
|
||||
"props": { "severity": "WARNING" },
|
||||
"type": "PublicAccessor"
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"prohibitMeta": false
|
||||
},
|
||||
"comment": "If enabled, complains when an @:access metadata is redundant.",
|
||||
"props": { "prohibitMeta": false, "severity": "INFO" },
|
||||
"type": "RedundantAccessMeta"
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"prohibitMeta": false
|
||||
},
|
||||
"comment": "If enabled, complains when an @:allow metadata is redundant.",
|
||||
"props": { "prohibitMeta": false, "severity": "INFO" },
|
||||
"type": "RedundantAllowMeta"
|
||||
},
|
||||
{
|
||||
"comment": "If enabled, complains when an access modifier is redundant.",
|
||||
"props": {
|
||||
"enforcePrivate": false,
|
||||
"enforcePublic": true,
|
||||
"enforcePublicPrivate": false
|
||||
"enforcePublicPrivate": false,
|
||||
"severity": "WARNING"
|
||||
},
|
||||
"type": "RedundantModifier"
|
||||
},
|
||||
{
|
||||
"comment": "If enabled, enforces that a return type is specified for all methods.",
|
||||
"props": {
|
||||
"enforceReturnTypeForAnonymous": false,
|
||||
"allowEmptyReturn": true,
|
||||
@@ -499,44 +583,63 @@
|
||||
"type": "Return"
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"ignoreFormat": "^$",
|
||||
"max": 2,
|
||||
"severity": "IGNORE"
|
||||
},
|
||||
"comment": "If enabled, mandates a maximum number of 'return' statements in a method.",
|
||||
"props": { "ignoreFormat": "^$", "max": 2, "severity": "IGNORE" },
|
||||
"type": "ReturnCount"
|
||||
},
|
||||
{
|
||||
"comment": "If enabled, enforces a specific placement for curly braces. TODO:Choose between Allman/K&R and enforce project-wide.",
|
||||
"props": {
|
||||
"option": "aloneorsingle",
|
||||
"tokens": [
|
||||
"CLASS_DEF",
|
||||
"ENUM_DEF",
|
||||
"ABSTRACT_DEF",
|
||||
"TYPEDEF_DEF",
|
||||
"INTERFACE_DEF",
|
||||
"OBJECT_DECL",
|
||||
"FUNCTION",
|
||||
"FOR",
|
||||
"IF",
|
||||
"WHILE",
|
||||
"SWITCH",
|
||||
"TRY",
|
||||
"CATCH",
|
||||
"REIFICATION",
|
||||
"ARRAY_COMPREHENSION"
|
||||
],
|
||||
"severity": "IGNORE"
|
||||
},
|
||||
"type": "RightCurly"
|
||||
},
|
||||
{
|
||||
"comment": "Enforces specific whitespace around certain separators, such as commas and semicolons.",
|
||||
"props": {
|
||||
"commaPolicy": "after",
|
||||
"semicolonPolicy": "after",
|
||||
"allowTrailingComma": false,
|
||||
"dotPolicy": "none"
|
||||
"dotPolicy": "none",
|
||||
"severity": "INFO"
|
||||
},
|
||||
"type": "SeparatorWhitespace"
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"tokens": [","],
|
||||
"option": "eol"
|
||||
},
|
||||
"comment": "Enforces specific line wrapping for certain separators, such as commas and semicolons.",
|
||||
"props": { "tokens": [","], "option": "eol" },
|
||||
"type": "SeparatorWrap"
|
||||
},
|
||||
{
|
||||
"props": {},
|
||||
"comment": "If enabled, complains about overly complicated boolean expressions.",
|
||||
"props": { "severity": "WARNING" },
|
||||
"type": "SimplifyBooleanExpression"
|
||||
},
|
||||
{
|
||||
"props": {},
|
||||
"comment": "If enabled, complains about overly complicated boolean return statements.",
|
||||
"props": { "severity": "WARNING" },
|
||||
"type": "SimplifyBooleanReturn"
|
||||
},
|
||||
{
|
||||
"comment": "If enabled, enforce spacing in and around conditional statements and certain operators.",
|
||||
"props": {
|
||||
"spaceIfCondition": "should",
|
||||
"spaceAroundBinop": true,
|
||||
@@ -550,94 +653,147 @@
|
||||
"type": "Spacing"
|
||||
},
|
||||
{
|
||||
"comment": "Enforce a specific string literal style (either ' or \").",
|
||||
"props": {
|
||||
"policy": "onlySingle",
|
||||
"allowException": true,
|
||||
"severity": "IGNORE"
|
||||
"severity": "INFO"
|
||||
},
|
||||
"type": "StringLiteral"
|
||||
},
|
||||
{
|
||||
"comment": "Puts a warning on unresolved TODO/FIXME comments.",
|
||||
"props": {
|
||||
"format": "^\\s*(TODO|FIXME|HACK|XXX|BUG)"
|
||||
"format": "^\\s*(TODO|FIXME|HACK|XXX|BUG)",
|
||||
"severity": "INFO"
|
||||
},
|
||||
"type": "TODOComment"
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"ignorePattern": "^$"
|
||||
},
|
||||
"comment": "Checks if there are any tabs in the middle of a line.",
|
||||
"props": { "severity": "INFO" },
|
||||
"type": "TabForAligning"
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"severity": "IGNORE"
|
||||
},
|
||||
"comment": "If enabled, complains whenever trace() gets used in code.",
|
||||
"props": { "severity": "IGNORE" },
|
||||
"type": "Trace"
|
||||
},
|
||||
{
|
||||
"props": {},
|
||||
"comment": "If enabled, complains about trailing whitespaces at the end of lines.",
|
||||
"props": { "severity": "INFO" },
|
||||
"type": "TrailingWhitespace"
|
||||
},
|
||||
{
|
||||
"type": "Type",
|
||||
"props": {
|
||||
"ignoreEnumAbstractValues": true
|
||||
}
|
||||
"comment": "If enabled, requires type be specified on member fields.",
|
||||
"props": { "ignoreEnumAbstractValues": true, "severity": "INFO" },
|
||||
"type": "Type"
|
||||
},
|
||||
{
|
||||
"comment": "If enabled, mandates documentation comments on classes and interfaces.",
|
||||
"props": {
|
||||
"tokens": ["CLASS_DEF", "ENUM_DEF", "INTERFACE_DEF", "TYPEDEF_DEF"]
|
||||
"tokens": ["CLASS_DEF", "ENUM_DEF", "INTERFACE_DEF", "TYPEDEF_DEF"],
|
||||
"severity": "INFO"
|
||||
},
|
||||
"type": "TypeDocComment"
|
||||
},
|
||||
{
|
||||
"comment": "Specifies a specific naming format for abstract types.",
|
||||
"props": {
|
||||
"ignoreExtern": true,
|
||||
"format": "^[A-Z]+[a-zA-Z0-9]*$",
|
||||
"tokens": []
|
||||
"tokens": ["ABSTRACT"],
|
||||
"severity": "WARNING"
|
||||
},
|
||||
"type": "TypeName"
|
||||
},
|
||||
|
||||
{
|
||||
"comment": "Specifies a specific naming format for class types.",
|
||||
"props": {
|
||||
"ignoreExtern": true,
|
||||
"format": "^[A-Z]+[a-zA-Z0-9]*$",
|
||||
"tokens": ["CLASS"],
|
||||
"severity": "WARNING"
|
||||
},
|
||||
"type": "TypeName"
|
||||
},
|
||||
|
||||
{
|
||||
"comment": "Specifies a specific naming format for enum types.",
|
||||
"props": {
|
||||
"ignoreExtern": true,
|
||||
"format": "^[A-Z]+[a-zA-Z0-9]*$",
|
||||
"tokens": ["ENUM"],
|
||||
"severity": "WARNING"
|
||||
},
|
||||
"type": "TypeName"
|
||||
},
|
||||
|
||||
{
|
||||
"comment": "Specifies a specific naming format for interface types.",
|
||||
"props": {
|
||||
"ignoreExtern": true,
|
||||
"format": "^[A-Z]+[a-zA-Z0-9]*$",
|
||||
"tokens": ["INTERFACE"],
|
||||
"severity": "WARNING"
|
||||
},
|
||||
"type": "TypeName"
|
||||
},
|
||||
|
||||
{
|
||||
"comment": "Specifies a specific naming format for typedefs.",
|
||||
"props": {
|
||||
"ignoreExtern": true,
|
||||
"format": "^[A-Z]+[a-zA-Z0-9]*$",
|
||||
"tokens": ["TYPEDEF"],
|
||||
"severity": "WARNING"
|
||||
},
|
||||
"type": "TypeName"
|
||||
},
|
||||
{
|
||||
"props": {},
|
||||
"comment": "If enabled, complains when a class with only static fields and values has a redundant constructor.",
|
||||
"props": { "severity": "WARNING" },
|
||||
"type": "UnnecessaryConstructor"
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"moduleTypeMap": {},
|
||||
"ignoreModules": []
|
||||
},
|
||||
"comment": "If enabled, complains when a module contains unused imports. NOTE:Sometimes complains about required imports.",
|
||||
"props": { "moduleTypeMap": {}, "ignoreModules": [], "severity": "INFO" },
|
||||
"type": "UnusedImport"
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"severity": "WARNING"
|
||||
},
|
||||
"comment": "If enabled, complains when a local variable is unused.",
|
||||
"props": { "severity": "WARNING" },
|
||||
"type": "UnusedLocalVar"
|
||||
},
|
||||
{
|
||||
"type": "VarTypeHint",
|
||||
"comment": "If enabled, requires type hints for certain variables.",
|
||||
"props": {
|
||||
"typeHintPolicy": "enforce_all",
|
||||
"ignoreEnumAbstractValues": true,
|
||||
"severity": "IGNORE"
|
||||
}
|
||||
},
|
||||
"type": "VarTypeHint"
|
||||
},
|
||||
{
|
||||
"comment": "If enabled, complains when a class member variable is initialized at the class level rather than the constructor.",
|
||||
"type": "VariableInitialisation",
|
||||
"props": {
|
||||
"allowFinal": false
|
||||
"allowFinal": true,
|
||||
"severity": "IGNORE"
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "If enabled, mandates whitespace after certain tokens.",
|
||||
"props": {
|
||||
"tokens": [",", ";"],
|
||||
"allowTrailingComma": false
|
||||
"allowTrailingComma": false,
|
||||
"severity": "INFO"
|
||||
},
|
||||
"type": "WhitespaceAfter"
|
||||
},
|
||||
{
|
||||
"comment": "If enabled, mandates whitespace around certain tokens.",
|
||||
"props": {
|
||||
"tokens": [
|
||||
"=",
|
||||
@@ -672,11 +828,10 @@
|
||||
"&=",
|
||||
"^=",
|
||||
"=>"
|
||||
]
|
||||
],
|
||||
"severity": "INFO"
|
||||
},
|
||||
"type": "WhitespaceAround"
|
||||
}
|
||||
],
|
||||
"exclude": {},
|
||||
"version": 1
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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.7.5)
|
||||
- the latest version of the game (currently v0.8.5 for Android, v0.8.4 for all other platforms)
|
||||
- without any mods
|
||||
- on [Newgrounds](https://www.newgrounds.com/portal/view/770371) or downloaded from [itch.io](https://ninja-muffin24.itch.io/funkin)
|
||||
|
||||
|
||||
@@ -8,9 +8,7 @@ Most of this functionality is only available on debug builds of the game!
|
||||
- `F3`: ***SCREENSHOT***: Takes a screenshot of the game and saves it to the local `screenshots` directory. Works outside of debug builds too!
|
||||
- `F4`: ***EJECT***: Forcibly switch state to the Main Menu (with no extra transition). Useful if you're stuck in a level and you need to get out!
|
||||
- `F5`: ***HOT RELOAD***: Forcibly reload the game's scripts and data files, then restart the current state. If any files in the `assets` folder have been modified, the game should process the changes for you! NOTE: Known bug, this does not reset song charts or song scripts, but it should reset everything else (such as stage layout data and character animation data).
|
||||
- `CTRL-ALT-SHIFT-L`: ***FORCE CRASH***: Immediately crash the game with a detailed crash log and a stack trace. (Only works in the Main Menu on debug builds).
|
||||
|
||||
- `CTRL-SHIFT-L`: ***FORCE CRASH***: Immediately crash the game with a detailed crash log and a stack trace.
|
||||
- `CTRL-ALT-SHIFT-L`: ***FORCE CRASH***: Immediately crash the game with a detailed crash log and a stack trace.
|
||||
|
||||
## **Play State**
|
||||
- `H`: ***HIDE UI***: Makes the user interface invisible. Works in Pause Menu, great for screenshots.
|
||||
@@ -27,6 +25,7 @@ Most of this functionality is only available on debug builds of the game!
|
||||
- `P`: ***SWITCH TO PICO/BF***: Switch to Pico or BF (reopens Freeplay as the other character).
|
||||
- `T`: ***TEST RANK ANIMATION***: Play the Gold Perfect rank animation on the currently selected song.
|
||||
- `SHIFT-ACCEPT`: ***BOTPLAY MODE***: Start a song with Botplay mode enabled.
|
||||
- `CTRL-ACCEPT`: ***MIRRORED MODE***: Start a song with mirrored notes enabled.
|
||||
- `CTRL-Switch Song`: Play the first unlocked alternate instrumental as the song preview.
|
||||
|
||||
## **Title State**
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 501 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 541 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.0 MiB |
@@ -0,0 +1,5 @@
|
||||
Images stored in this folder come from these links:
|
||||
|
||||
FNF_logo.png - https://fridaynightfunkin.wiki.gg/images/FNF_logo.png
|
||||
Menu.png - https://fridaynightfunkin.wiki.gg/images/Menu.png
|
||||
Title_Card.gif - https://fridaynightfunkin.wiki.gg/images/d/d7/Title_Card.gif
|
||||
@@ -0,0 +1,37 @@
|
||||
<key>UTExportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>me.funkin.fnfc</string>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>FNFC Document</string>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.zip-archive</string>
|
||||
</array>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>fnfc</string>
|
||||
</array>
|
||||
<key>public.mime-type</key>
|
||||
<string>application/zip</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>FNFC Document</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>me.funkin.fnfc</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
@@ -6,7 +6,7 @@
|
||||
"name": "EliteMasterEric"
|
||||
}
|
||||
],
|
||||
"api_version": "0.7.0",
|
||||
"api_version": "0.8.0",
|
||||
"mod_version": "1.0.0",
|
||||
"license": "Apache-2.0"
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"name": "EliteMasterEric"
|
||||
}
|
||||
],
|
||||
"api_version": "0.7.0",
|
||||
"api_version": "0.8.0",
|
||||
"mod_version": "1.0.0",
|
||||
"license": "Apache-2.0"
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"name": "FlxPartialSound",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "4e86b724ca43489a59ea465a46ffc12598e5df15",
|
||||
"ref": "2f984e244f1544ca98c0e03b9b21ae570f07ac55",
|
||||
"url": "https://github.com/FunkinCrew/FlxPartialSound.git"
|
||||
},
|
||||
{
|
||||
@@ -18,7 +18,7 @@
|
||||
"name": "extension-admob",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "96385202f6f951b2bde0cbe836c5da21dde1f36e",
|
||||
"ref": "5c908b3f7c74115a8b52eddf1e05525f4873f26e",
|
||||
"url": "https://github.com/FunkinCrew/extension-admob"
|
||||
},
|
||||
{
|
||||
@@ -41,26 +41,33 @@
|
||||
"type": "haxelib",
|
||||
"version": "1.0.3"
|
||||
},
|
||||
{
|
||||
"name": "extension-webviewcore",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "d13109450d65c40678244ca23b1aa88ac18d77e6",
|
||||
"url": "https://github.com/LimeExtensions/extension-webviewcore"
|
||||
},
|
||||
{
|
||||
"name": "flixel",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "5cf3515e665812179a9b413629325b608b3a9815",
|
||||
"ref": "f7b94eebf7dbb452a929d0c67ab31a9cbd71d3a0",
|
||||
"url": "https://github.com/FunkinCrew/flixel"
|
||||
},
|
||||
{
|
||||
"name": "flixel-addons",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "b9118f47f43a66bc0e5fbfcfd9903f0425e918ee",
|
||||
"ref": "187f93b34f93c6a405d634a42913c745e443463a",
|
||||
"url": "https://github.com/FunkinCrew/flixel-addons"
|
||||
},
|
||||
{
|
||||
"name": "flxanimate",
|
||||
"name": "flixel-animate",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "49214278b9124823582cdcecd94f4a1de9a4b36b",
|
||||
"url": "https://github.com/FunkinCrew/flxanimate"
|
||||
"ref": "c5e3393c70b71a191f20fa902114cea92042e486",
|
||||
"url": "https://github.com/MaybeMaru/flixel-animate"
|
||||
},
|
||||
{
|
||||
"name": "format",
|
||||
@@ -71,14 +78,14 @@
|
||||
"name": "funkin.vis",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "1966f8fbbbc509ed90d4b520f3c49c084fc92fd6",
|
||||
"ref": "02bada154b474c2554709b9d12aef0cbf0da3ec9",
|
||||
"url": "https://github.com/FunkinCrew/funkVis"
|
||||
},
|
||||
{
|
||||
"name": "grig.audio",
|
||||
"type": "git",
|
||||
"dir": "src",
|
||||
"ref": "8567c4dad34cfeaf2ff23fe12c3796f5db80685e",
|
||||
"ref": "6409f3c6d1b4c52176813d3ede86c0d34e8af2c1",
|
||||
"url": "https://github.com/FunkinCrew/grig.audio"
|
||||
},
|
||||
{
|
||||
@@ -90,8 +97,8 @@
|
||||
"name": "haxeui-core",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "47ee9f5fa02d422e186e844f87e68220cabfcc5b",
|
||||
"url": "https://github.com/haxeui/haxeui-core"
|
||||
"ref": "09f2512564b45e92229e21433e6dd482391f7b7d",
|
||||
"url": "https://github.com/FunkinCrew/haxeui-core"
|
||||
},
|
||||
{
|
||||
"name": "haxeui-flixel",
|
||||
@@ -100,18 +107,11 @@
|
||||
"ref": "100f2c96beab619cfe72c567a058c41c71e3e998",
|
||||
"url": "https://github.com/haxeui/haxeui-flixel"
|
||||
},
|
||||
{
|
||||
"name": "hscript",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "8c1d238b069ef97cec13f5121b29d2afe2b8985d",
|
||||
"url": "https://github.com/FunkinCrew/hscript"
|
||||
},
|
||||
{
|
||||
"name": "hxcpp",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "363d3d0a8d3ac450f5ba1b4c3357353656b71e82",
|
||||
"ref": "450d112e50acff57b1bc9d584dcf1374c9e33995",
|
||||
"url": "https://github.com/FunkinCrew/hxcpp"
|
||||
},
|
||||
{
|
||||
@@ -123,10 +123,8 @@
|
||||
},
|
||||
{
|
||||
"name": "hxdiscord_rpc",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "82c47ecc1a454b7dd644e4fcac7e91155f176dec",
|
||||
"url": "https://github.com/FunkinCrew/hxdiscord_rpc"
|
||||
"type": "haxelib",
|
||||
"version": "1.3.0"
|
||||
},
|
||||
{
|
||||
"name": "hxjsonast",
|
||||
@@ -135,6 +133,11 @@
|
||||
"ref": "20e72cc68c823496359775ac1f06500e67f189d5",
|
||||
"url": "https://github.com/nadako/hxjsonast/"
|
||||
},
|
||||
{
|
||||
"name": "hxgamemode",
|
||||
"type": "haxelib",
|
||||
"version": "1.0.1"
|
||||
},
|
||||
{
|
||||
"name": "hxp",
|
||||
"type": "haxelib",
|
||||
@@ -143,56 +146,52 @@
|
||||
{
|
||||
"name": "hxvlc",
|
||||
"type": "haxelib",
|
||||
"version": "2.2.4"
|
||||
"version": "2.2.6"
|
||||
},
|
||||
{
|
||||
"name": "json2object",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "a8c26f18463c98da32f744c214fe02273e1823fa",
|
||||
"ref": "59e0467c953d1f26e3cbf2a070f140e2d2e8457d",
|
||||
"url": "https://github.com/FunkinCrew/json2object"
|
||||
},
|
||||
{
|
||||
"name": "jsonpatch",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "f9b83215acd586dc28754b4ae7f69d4c06c3b4d3",
|
||||
"url": "https://github.com/EliteMasterEric/jsonpatch"
|
||||
"type": "haxelib",
|
||||
"version": "1.1.0"
|
||||
},
|
||||
{
|
||||
"name": "jsonpath",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "7a24193717b36393458c15c0435bb7c4470ecdda",
|
||||
"url": "https://github.com/EliteMasterEric/jsonpath"
|
||||
"type": "haxelib",
|
||||
"version": "1.1.0"
|
||||
},
|
||||
{
|
||||
"name": "lime",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "f109a083023a9e5622342257993fc745d9bbb959",
|
||||
"ref": "826d25199c17329b730ae09838f3df7a2903c471",
|
||||
"url": "https://github.com/FunkinCrew/lime"
|
||||
},
|
||||
{
|
||||
"name": "newgrounds",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "c8b30832027fa04f48fe6a45fa7903d5b265d56e",
|
||||
"url": "https://github.com/Geokureli/Newgrounds/"
|
||||
"ref": "b2b53cd0a5f030af74efd325732951cddbea8826",
|
||||
"url": "https://github.com/FunkinCrew/Newgrounds"
|
||||
},
|
||||
{
|
||||
"name": "openfl",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "e6296200292a76b5f7b8684a991ead6b05ae4507",
|
||||
"ref": "88534506595a32c3f02b21b3987e789a24074ae7",
|
||||
"url": "https://github.com/FunkinCrew/openfl"
|
||||
},
|
||||
{
|
||||
"name": "polymod",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "47d9a2775e3fd8f6dd17d6f4b99b8798709da435",
|
||||
"url": "https://github.com/larsiusprime/polymod"
|
||||
"ref": "fb123758b90127f5647727727f273d27e93cabdb",
|
||||
"url": "https://github.com/FunkinCrew/polymod"
|
||||
},
|
||||
{
|
||||
"name": "thx.core",
|
||||
|
||||
+283
-7
@@ -1,30 +1,306 @@
|
||||
{
|
||||
"disableFormatting": false,
|
||||
|
||||
"emptyLines": {
|
||||
"finalNewline": true,
|
||||
|
||||
"maxAnywhereInFile": 1,
|
||||
|
||||
"beforePackage": 0,
|
||||
"afterPackage": 1,
|
||||
|
||||
"importAndUsing": {
|
||||
"betweenImports": 0,
|
||||
"betweenImportsLevel": "firstLevelPackage",
|
||||
"beforeUsing": 1,
|
||||
"beforeType": 1
|
||||
},
|
||||
|
||||
"betweenTypes": 1,
|
||||
|
||||
"lineCommentsBetweenTypes": "keep",
|
||||
"lineCommentsBetweenFunctions": "keep",
|
||||
|
||||
"betweenSingleLineTypes": 0,
|
||||
|
||||
"beforeRightCurly": "remove",
|
||||
"afterLeftCurly": "remove",
|
||||
"afterReturn": "remove",
|
||||
|
||||
"beforeBlocks": "remove",
|
||||
"afterBlocks": "remove",
|
||||
|
||||
"enumAbstractEmptyLines": {
|
||||
"beginType": 0,
|
||||
"endType": 0,
|
||||
|
||||
"betweenVars": 0,
|
||||
"afterVars": 1,
|
||||
"betweenFunctions": 1,
|
||||
"existingBetweenFields": "keep"
|
||||
},
|
||||
|
||||
"classEmptyLines": {
|
||||
"beginType": 0,
|
||||
"endType": 0,
|
||||
|
||||
"betweenStaticVars": 0,
|
||||
"afterStaticVars": 1,
|
||||
"afterPrivateVars": 1,
|
||||
"betweenVars": 0,
|
||||
"afterVars": 1,
|
||||
"afterStaticFunctions": 1,
|
||||
"betweenStaticFunctions": 1,
|
||||
"afterPrivateFunctions": 1,
|
||||
"betweenFunctions": 1,
|
||||
|
||||
"existingBetweenFields": "keep"
|
||||
},
|
||||
|
||||
"macroClassEmptyLines": {
|
||||
"beginType": 0,
|
||||
"endType": 0,
|
||||
|
||||
"betweenStaticVars": 0,
|
||||
"afterStaticVars": 1,
|
||||
"afterPrivateVars": 1,
|
||||
"betweenVars": 0,
|
||||
"afterVars": 1,
|
||||
"afterStaticFunctions": 1,
|
||||
"betweenStaticFunctions": 1,
|
||||
"afterPrivateFunctions": 1,
|
||||
"betweenFunctions": 1,
|
||||
|
||||
"existingBetweenFields": "keep"
|
||||
},
|
||||
|
||||
"externClassEmptyLines": {
|
||||
"beginType": 0,
|
||||
"endType": 0,
|
||||
|
||||
"betweenVars": 0,
|
||||
"afterVars": 0,
|
||||
"betweenFunctions": 0,
|
||||
"existingBetweenFields": "keep"
|
||||
},
|
||||
|
||||
"abstractEmptyLines": {
|
||||
"beginType": 0,
|
||||
"endType": 0,
|
||||
|
||||
"betweenStaticVars": 0,
|
||||
"afterStaticVars": 1,
|
||||
"afterPrivateVars": 1,
|
||||
"betweenVars": 0,
|
||||
"afterVars": 1,
|
||||
"afterStaticFunctions": 1,
|
||||
"betweenStaticFunctions": 1,
|
||||
"afterPrivateFunctions": 1,
|
||||
"betweenFunctions": 1,
|
||||
|
||||
"existingBetweenFields": "keep"
|
||||
},
|
||||
|
||||
"interfaceEmptyLines": {
|
||||
"beginType": 0,
|
||||
"endType": 0,
|
||||
|
||||
"betweenVars": 0,
|
||||
"afterVars": 0,
|
||||
"betweenFunctions": 0,
|
||||
"existingBetweenFields": "keep"
|
||||
},
|
||||
|
||||
"enumEmptyLines": {
|
||||
"beginType": 0,
|
||||
"endType": 0,
|
||||
|
||||
"betweenFields": 0,
|
||||
"existingBetweenFields": "keep"
|
||||
},
|
||||
|
||||
"typedefEmptyLines": {
|
||||
"beginType": 0,
|
||||
"endType": 0,
|
||||
|
||||
"betweenFields": 0,
|
||||
"existingBetweenFields": "keep"
|
||||
},
|
||||
|
||||
"conditionalsEmptyLines": {
|
||||
"afterIf": 0,
|
||||
"beforeElse": 0,
|
||||
"afterElse": 0,
|
||||
"beforeEnd": 0,
|
||||
"beforeError": 0,
|
||||
"afterError": 0
|
||||
},
|
||||
|
||||
"beforeDocCommentEmptyLines": "one",
|
||||
"afterFieldsWithDocComments": "one",
|
||||
|
||||
"afterFileHeaderComment": 1,
|
||||
"betweenMultilineComments": 0
|
||||
},
|
||||
|
||||
"indentation": {
|
||||
"conditionalPolicy": "aligned",
|
||||
|
||||
"character": " ",
|
||||
"tabWidth": 2,
|
||||
|
||||
"trailingWhitespace": false,
|
||||
|
||||
"indentObjectLiteral": true,
|
||||
"indentComplexValueExpressions": false,
|
||||
"indentCaseLabels": true
|
||||
},
|
||||
|
||||
"lineEnds": {
|
||||
"anonFunctionCurly": {
|
||||
"lineEndCharacter": "auto",
|
||||
|
||||
"metadataType": "none",
|
||||
"metadataVar": "none",
|
||||
"metadataFunction": "none",
|
||||
"metadataOther": "none",
|
||||
|
||||
"caseColon": "after",
|
||||
"sharp": "after",
|
||||
|
||||
"leftCurly": "both",
|
||||
"rightCurly": "both",
|
||||
"emptyCurly": "noBreak",
|
||||
|
||||
"blockCurly": {
|
||||
"emptyCurly": "break",
|
||||
"leftCurly": "after",
|
||||
"rightCurly": "both"
|
||||
},
|
||||
"leftCurly": "both",
|
||||
"rightCurly": "both"
|
||||
},
|
||||
|
||||
"anonFunctionCurly": {
|
||||
"emptyCurly": "break",
|
||||
"leftCurly": "both",
|
||||
"rightCurly": "both"
|
||||
},
|
||||
|
||||
"anonTypeCurly": {
|
||||
"emptyCurly": "break",
|
||||
"leftCurly": "both",
|
||||
"rightCurly": "both"
|
||||
},
|
||||
|
||||
"objectLiteralCurly": {
|
||||
"emptyCurly": "break",
|
||||
"leftCurly": "after",
|
||||
"rightCurly": "both"
|
||||
},
|
||||
|
||||
"typedefCurly": {
|
||||
"emptyCurly": "break",
|
||||
"leftCurly": "both",
|
||||
"rightCurly": "both"
|
||||
}
|
||||
},
|
||||
|
||||
"sameLine": {
|
||||
"ifBody": "same",
|
||||
"elseBody": "next",
|
||||
|
||||
"ifElse": "next",
|
||||
"doWhile": "next",
|
||||
|
||||
"elseIf": "same",
|
||||
"ifElseSemicolonNextLine": true,
|
||||
|
||||
"expressionIf": "same",
|
||||
"expressionIfWithBlocks": false,
|
||||
|
||||
"forBody": "next",
|
||||
|
||||
"comprehensionFor": "same",
|
||||
|
||||
"whileBody": "next",
|
||||
"doWhileBody": "next",
|
||||
|
||||
"doWhile": "same",
|
||||
|
||||
"tryBody": "next",
|
||||
"tryCatch": "next"
|
||||
"catchBody": "next",
|
||||
|
||||
"tryCatch": "next",
|
||||
|
||||
"caseBody": "next",
|
||||
"expressionCase": "keep",
|
||||
"expressionTry": "same",
|
||||
|
||||
"functionBody": "same",
|
||||
"anonFunctionBody": "same",
|
||||
"returnBody": "same",
|
||||
"returnBodySingleLine": "same",
|
||||
|
||||
"untypedBody": "same"
|
||||
},
|
||||
|
||||
"whitespace": {
|
||||
"switchPolicy": "around"
|
||||
"parenConfig": {},
|
||||
"bracesConfig": {},
|
||||
"bracketConfig": {},
|
||||
|
||||
"typeParamOpenPolicy": "none",
|
||||
"typeParamClosePolicy": "none",
|
||||
|
||||
"typeExtensionPolicy": "after",
|
||||
|
||||
"commaPolicy": "onlyAfter",
|
||||
"dotPolicy": "none",
|
||||
"colonPolicy": "none",
|
||||
"caseColonPolicy": "onlyAfter",
|
||||
"objectFieldColonPolicy": "after",
|
||||
"typeHintColonPolicy": "none",
|
||||
"typeCheckColonPolicy": "around",
|
||||
"ternaryPolicy": "around",
|
||||
"semicolonPolicy": "onlyAfter",
|
||||
"ifPolicy": "after",
|
||||
"doPolicy": "after",
|
||||
"whilePolicy": "after",
|
||||
"forPolicy": "after",
|
||||
"switchPolicy": "around",
|
||||
"tryPolicy": "after",
|
||||
"catchPolicy": "after",
|
||||
"binopPolicy": "around",
|
||||
"intervalPolicy": "none",
|
||||
"arrowFunctionsPolicy": "around",
|
||||
"functionTypeHaxe3Policy": "none",
|
||||
"functionTypeHaxe4Policy": "around",
|
||||
|
||||
"compressSuccessiveParenthesis": true,
|
||||
"formatStringInterpolation": true,
|
||||
"addLineCommentSpace": true
|
||||
},
|
||||
|
||||
"wrapping": {
|
||||
"maxLineLength": 160,
|
||||
|
||||
"arrayWrap": {
|
||||
"defaultWrap": "noWrap",
|
||||
"defaultLocation": "afterLast",
|
||||
"defaultAdditionalIndent": 0
|
||||
},
|
||||
|
||||
"mapWrap": {
|
||||
"defaultWrap": "noWrap",
|
||||
"defaultLocation": "afterLast",
|
||||
"defaultAdditionalIndent": 0
|
||||
},
|
||||
|
||||
"typeParameter": {
|
||||
"defaultWrap": "noWrap"
|
||||
}
|
||||
},
|
||||
|
||||
"excludes": [
|
||||
"_Provide a list of regular expressions to exclude from formatting.",
|
||||
".haxelib",
|
||||
".git",
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
|
||||
+569
-108
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,49 @@
|
||||
package scripts;
|
||||
|
||||
using DateTools;
|
||||
|
||||
class Postbuild
|
||||
{
|
||||
static function main()
|
||||
{
|
||||
/* trace('creating NG zip');
|
||||
var binFolder:String = 'export/release/html5/bin/';
|
||||
|
||||
var date = Date.now();
|
||||
var outputName:String = 'NG-' + date.format("%Y%m%d--%H%M") + '.zip';
|
||||
|
||||
var out = sys.io.File.write(outputName);
|
||||
var zip = new haxe.zip.Writer(out);
|
||||
zip.write(getEntries(binFolder));
|
||||
trace('Finished creating ZIP');
|
||||
*/
|
||||
}
|
||||
|
||||
static function getEntries(dir:String, entries:List<haxe.zip.Entry> = null, inDir:Null<String> = null)
|
||||
{
|
||||
if (entries == null) entries = new List<haxe.zip.Entry>();
|
||||
if (inDir == null) inDir = dir;
|
||||
|
||||
for (file in sys.FileSystem.readDirectory(dir))
|
||||
{
|
||||
var path = haxe.io.Path.join([dir, file]);
|
||||
if (sys.FileSystem.isDirectory(path)) getEntries(path, entries, inDir);
|
||||
else
|
||||
{
|
||||
var bytes:haxe.io.Bytes = haxe.io.Bytes.ofData(sys.io.File.getBytes(path).getData());
|
||||
var entry:haxe.zip.Entry =
|
||||
{
|
||||
fileName: StringTools.replace(path, inDir, ""),
|
||||
fileSize: bytes.length,
|
||||
fileTime: Date.now(),
|
||||
compressed: false,
|
||||
dataSize: sys.FileSystem.stat(path).size,
|
||||
data: bytes,
|
||||
crc32: haxe.crypto.Crc32.make(bytes)
|
||||
};
|
||||
entries.push(entry);
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
import haxe.Json;
|
||||
import haxe.Log;
|
||||
import haxe.macro.Compiler;
|
||||
import sys.FileSystem;
|
||||
import sys.io.File;
|
||||
|
||||
using StringTools;
|
||||
|
||||
#if !interp
|
||||
package scripts;
|
||||
|
||||
// needs this thing to be run by interp, just for me lol!
|
||||
#end
|
||||
// general use of this!!
|
||||
// if run in INTERP MODE:
|
||||
// - scripts>haxe --main SongConverter --interp
|
||||
// - song to convert: ../assets/preload/data/
|
||||
// it will run through EVERY folder in there and convert the old songs to the new format!
|
||||
// TODO make instructions for the C# script version!
|
||||
class SongConverter
|
||||
{
|
||||
public static var VERSION:String = '0.1.0';
|
||||
|
||||
// TODO
|
||||
// Update engine shit, to accomodate single file
|
||||
static function main()
|
||||
{
|
||||
// trace(Compiler.get);
|
||||
// trace(Compiler.getDefine('sysfsd'));
|
||||
|
||||
Sys.stdout().writeString('Please type directory you want to convert: ');
|
||||
Sys.stdout().flush();
|
||||
final input = Sys.stdin().readLine();
|
||||
trace('Hello ${input}!');
|
||||
|
||||
/* Sys.stdout().writeString('What type of conversion do you need?\n\t0: Old charts to new chart\n\t1: new chart to .... new chart? lol\n>');
|
||||
Sys.stdout().flush();
|
||||
final deleteShitIDK = Std.parseInt(Sys.stdin().readLine());
|
||||
|
||||
trace(deleteShitIDK); */
|
||||
|
||||
var songCount:Int = 0;
|
||||
|
||||
for (fileThing in FileSystem.readDirectory('${input}/.'))
|
||||
{
|
||||
// trace(fileThing);
|
||||
// trace(FileSystem.isDirectory(${input} + "/" + fileThing));
|
||||
|
||||
if (FileSystem.isDirectory(${input} + "/" + fileThing) && fileThing.toLowerCase() != 'smash')
|
||||
{
|
||||
trace('Formatting $fileThing');
|
||||
formatSongs(fileThing, input);
|
||||
|
||||
songCount++;
|
||||
}
|
||||
}
|
||||
|
||||
trace('CONVERTED $songCount SONGS!!');
|
||||
|
||||
var timer:Int = 5;
|
||||
|
||||
while (timer > 0)
|
||||
{
|
||||
Sys.stdout().writeString('CLOSING IN $timer SECONDS!');
|
||||
Sys.sleep(1);
|
||||
Sys.print("\r");
|
||||
timer -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
public static function formatSongs(songName:String, root:String)
|
||||
{
|
||||
var existsArray:Array<Bool> = [];
|
||||
|
||||
var songMap:Map<String, Dynamic> = new Map();
|
||||
var speedMap:Map<String, Dynamic> = new Map();
|
||||
|
||||
for (songFile in FileSystem.readDirectory('$root/$songName/'))
|
||||
{
|
||||
if (!songFile.endsWith('json') || !songFile.startsWith(songName)) continue; // ACCOMODATE FOR PICO SPEAKER EVENTUALLY!
|
||||
|
||||
var funnyFile:Dynamic = cast Json.parse(File.getContent('$root/$songName/$songFile'));
|
||||
|
||||
var songSplit:Array<String> = songFile.split('-');
|
||||
var songShit:String = 'normal';
|
||||
|
||||
if (songSplit.length > 1)
|
||||
{
|
||||
if (songSplit[1] == 'new.json')
|
||||
{
|
||||
FileSystem.deleteFile('$root/$songName/$songFile');
|
||||
trace('DELETEING $songFile');
|
||||
continue;
|
||||
}
|
||||
|
||||
songShit = songSplit[1].substr(0, songSplit[1].length - 5);
|
||||
}
|
||||
|
||||
if (songShit != 'normal')
|
||||
{
|
||||
songMap[songShit] = funnyFile.song.notes;
|
||||
speedMap[songShit] = funnyFile.song.speed;
|
||||
}
|
||||
else
|
||||
{
|
||||
//
|
||||
|
||||
if (funnyFile.song.notes is Array)
|
||||
{
|
||||
songMap[songShit] = funnyFile.song.notes[1];
|
||||
speedMap[songShit] = funnyFile.song.speed[1];
|
||||
}
|
||||
else if (Type.typeof(funnyFile.song.notes) == TObject)
|
||||
{
|
||||
var dipshitMap:Map<String, Dynamic> = cast funnyFile.song.notes;
|
||||
|
||||
// songMap[songShit] = dipshitMap.get('normal');
|
||||
}
|
||||
else
|
||||
{
|
||||
songMap[songShit] = funnyFile.song.notes;
|
||||
speedMap[songShit] = funnyFile.song.speed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var fileNormal:Dynamic = cast Json.parse(File.getContent('$root/$songName/$songName.json'));
|
||||
|
||||
fileNormal.song.notes = songMap;
|
||||
fileNormal.song.speed = speedMap;
|
||||
|
||||
fileNormal.song.hasDialogueFile = false;
|
||||
fileNormal.song.stageDefault = getStage(songName);
|
||||
|
||||
fileNormal.version = 'FNF SongConverter $VERSION';
|
||||
|
||||
var daJson = Json.stringify(fileNormal, null, "\t");
|
||||
// trace(daJson);
|
||||
File.saveContent('$root/$songName/$songName.json', daJson);
|
||||
}
|
||||
|
||||
static function noteCleaner(notes:Array<Dynamic>):Array<Dynamic>
|
||||
{
|
||||
var swagArray:Array<Dynamic> = [];
|
||||
for (i in notes)
|
||||
{
|
||||
if (i.sectionNotes.length > 0) swagArray.push(i);
|
||||
}
|
||||
|
||||
return swagArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Quicky lil function just for me formatting the old songs hehehe
|
||||
*/
|
||||
static function getStage(songName:String):String
|
||||
{
|
||||
switch (songName)
|
||||
{
|
||||
case 'spookeez' | 'monster' | 'south':
|
||||
return "spooky";
|
||||
case 'pico' | 'blammed' | 'philly':
|
||||
return 'philly';
|
||||
case "milf" | 'satin-panties' | 'high':
|
||||
return 'limo';
|
||||
case "cocoa" | 'eggnog':
|
||||
return 'mall';
|
||||
case 'winter-horrorland':
|
||||
return 'mallEvil';
|
||||
case 'senpai' | 'roses':
|
||||
return 'school';
|
||||
case 'thorns':
|
||||
return 'schoolEvil';
|
||||
case 'guns' | 'stress' | 'ugh':
|
||||
return 'tank';
|
||||
default:
|
||||
return 'stage';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import csv
|
||||
import re
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
|
||||
# This script imports a CSV file, and bulk updates the chart metadata files
|
||||
#
|
||||
# Requirements:
|
||||
# python3 I believe
|
||||
# and you need to have npm/npx, as we use that to run prettier to keep the same formatting
|
||||
#
|
||||
# Usage:
|
||||
# In our Google Sheets, export as .csv file
|
||||
#
|
||||
# Then in here from within this directory, run:
|
||||
# python3 sheetsToCharts.py path/to/csv/file.csv
|
||||
#
|
||||
# use from within this directory, as the paths to the chart metadata files are relative to this directory
|
||||
|
||||
|
||||
# Get the command line argument, if one wasn't already provided
|
||||
if len(sys.argv) < 2:
|
||||
csv_file = input("Please provide the CSV file path: ")
|
||||
else:
|
||||
csv_file = sys.argv[1]
|
||||
|
||||
# Open the CSV file
|
||||
with open(csv_file, 'r') as file:
|
||||
reader = csv.reader(file)
|
||||
|
||||
# Which rows for each difficulty are we looking for
|
||||
diff_rows = {}
|
||||
|
||||
# Iterate over each row in the CSV file
|
||||
for idx, row in enumerate(reader):
|
||||
|
||||
# The first row has headers for the columns, stuff like "Easy - Difficulty", so we want
|
||||
# to know which rows corresponde to which difficulty
|
||||
if idx == 0:
|
||||
pattern = r"\b(?:difficulty)\b"
|
||||
|
||||
diff_cols = []
|
||||
diff_words = []
|
||||
|
||||
# Go through each column, and see if it includes the word "difficulty"
|
||||
# then append the column index, and the first word of the column (usually what difficulty it is, "easy", "normal", "hard", etc.)
|
||||
for colindex, column in enumerate(row):
|
||||
txt = re.findall(pattern, column, flags=re.IGNORECASE)
|
||||
if txt:
|
||||
diff_cols.append(colindex)
|
||||
diff_words.append(column.split()[0].lower())
|
||||
|
||||
# We then create a dictionary for use later, where the key is the difficulty, and the value is the column index
|
||||
diff_rows = dict(zip(diff_words, diff_cols))
|
||||
else:
|
||||
# Some song title parsing, to match the filenames we used in for the chart files
|
||||
song_title = row[1].lower() # we use lowercase for filenames, SOUTH -> south
|
||||
song_title = song_title.replace(" ", "-") # usually we change spaces to dashes, PHILLY NICE -> philly-nice
|
||||
song_title = song_title.replace(".", "") # M.I.L.F. -> milf
|
||||
song_title = song_title.replace("'", "") # blazin' -> blazin
|
||||
|
||||
# we open the chart metadata json file for writing and reading ("r+")
|
||||
with open(f"../../../assets/preload/data/songs/{song_title}/{song_title}-metadata.json", "r+") as chart_metadata_file:
|
||||
|
||||
json_data = json.load(chart_metadata_file) # load the file as a python data structure/dict
|
||||
play_data = json_data["playData"] # we want to modify the "playData" section, as that holds the difficulty
|
||||
|
||||
# note to self, python equals(=) operator seems to create a reference for the variable,
|
||||
# so modifying play_data will also modify json_data, so we can save json_data easily later
|
||||
|
||||
# if the chart metadata file doesn't already have a "ratings" dict/section, we create one here with 0 for each
|
||||
if "ratings" not in play_data:
|
||||
play_data["ratings"] = {'easy': 0, 'normal': 0, 'hard': 0}
|
||||
|
||||
ratings = play_data["ratings"]
|
||||
|
||||
# Now we go through our data from the csv file, and the data we kept from the columns there
|
||||
# will be put into our new ratings var, if it exists
|
||||
for diff, col in diff_rows.items():
|
||||
if row[col] == "":
|
||||
continue
|
||||
if diff in ratings:
|
||||
ratings[diff] = round(float(row[col])) # convert the string to a float, and then round it to nearest int
|
||||
|
||||
# Convert the python json_data dict back to a json string
|
||||
json_output = json.dumps(json_data)
|
||||
|
||||
# Write the json string back to the file, and truncate the rest of the file
|
||||
chart_metadata_file.seek(0)
|
||||
chart_metadata_file.write(json_output)
|
||||
chart_metadata_file.truncate()
|
||||
chart_metadata_file.close()
|
||||
|
||||
# Bit hacky, but we simply run `npx prettier` using the same rules we use for FNF here
|
||||
# essnetially running it via cli and passing in the songs folder and our prettier config file
|
||||
# This should make the spacing and formatting consistent
|
||||
command = "npx prettier ../../../assets/preload/data/songs --write --config ../../../.prettierrc.js"
|
||||
subprocess.run(command, shell=True)
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Find all files in OGG format and execute ffmpeg to convert them to MP3
|
||||
# Skips files that are already in MP3 format
|
||||
find "../assets/" -name "*.ogg" -exec sh -c 'ffmpeg -n -i {} "${0%.*}.mp3"' {} \;
|
||||
@@ -0,0 +1,218 @@
|
||||
import requests
|
||||
import sys
|
||||
|
||||
PROJECT_ID = 5582053
|
||||
|
||||
TEMPLATE = 'jQuery("#new-event-name").val("{event}"); jQuery("#save-new-event").click();'
|
||||
|
||||
EVENT_TEMPLATES = [
|
||||
"start-game",
|
||||
"watch-cartoon",
|
||||
"open-credits",
|
||||
|
||||
"start-song_{songId}-{variation}",
|
||||
"blueballs_{songId}-{variation}",
|
||||
"complete-song_{songId}-{variation}",
|
||||
"start-level_{levelId}",
|
||||
"complete-level_{levelId}",
|
||||
"earn-rank_{rankName}",
|
||||
]
|
||||
|
||||
SONG_IDS = [
|
||||
{ "songId": "tutorial", "variation": "default" },
|
||||
{ "songId": "tutorial", "variation": "erect" },
|
||||
{ "songId": "tutorial", "variation": "pico" },
|
||||
|
||||
{ "songId": "bopeebo", "variation": "default" },
|
||||
{ "songId": "bopeebo", "variation": "erect" },
|
||||
{ "songId": "bopeebo", "variation": "pico" },
|
||||
|
||||
{ "songId": "fresh", "variation": "default" },
|
||||
{ "songId": "fresh", "variation": "erect" },
|
||||
{ "songId": "fresh", "variation": "pico" },
|
||||
|
||||
{ "songId": "dadbattle", "variation": "default" },
|
||||
{ "songId": "dadbattle", "variation": "erect" },
|
||||
{ "songId": "dadbattle", "variation": "pico" },
|
||||
|
||||
{ "songId": "spookeez", "variation": "default" },
|
||||
{ "songId": "spookeez", "variation": "erect" },
|
||||
{ "songId": "spookeez", "variation": "pico" },
|
||||
|
||||
{ "songId": "south", "variation": "default" },
|
||||
{ "songId": "south", "variation": "erect" },
|
||||
{ "songId": "south", "variation": "pico" },
|
||||
|
||||
{ "songId": "monster", "variation": "default" },
|
||||
{ "songId": "monster", "variation": "erect" },
|
||||
{ "songId": "monster", "variation": "pico" },
|
||||
|
||||
{ "songId": "pico", "variation": "default" },
|
||||
{ "songId": "pico", "variation": "erect" },
|
||||
{ "songId": "pico", "variation": "pico" },
|
||||
|
||||
{ "songId": "philly-nice", "variation": "default" },
|
||||
{ "songId": "philly-nice", "variation": "erect" },
|
||||
{ "songId": "philly-nice", "variation": "pico" },
|
||||
|
||||
{ "songId": "blammed", "variation": "default" },
|
||||
{ "songId": "blammed", "variation": "erect" },
|
||||
{ "songId": "blammed", "variation": "pico" },
|
||||
|
||||
{ "songId": "satin-panties", "variation": "default" },
|
||||
{ "songId": "satin-panties", "variation": "erect" },
|
||||
{ "songId": "satin-panties", "variation": "pico" },
|
||||
|
||||
{ "songId": "high", "variation": "default" },
|
||||
{ "songId": "high", "variation": "erect" },
|
||||
{ "songId": "high", "variation": "pico" },
|
||||
|
||||
{ "songId": "milf", "variation": "default" },
|
||||
{ "songId": "milf", "variation": "erect" },
|
||||
{ "songId": "milf", "variation": "pico" },
|
||||
|
||||
{ "songId": "cocoa", "variation": "default" },
|
||||
{ "songId": "cocoa", "variation": "erect" },
|
||||
{ "songId": "cocoa", "variation": "pico" },
|
||||
|
||||
{ "songId": "eggnog", "variation": "default" },
|
||||
{ "songId": "eggnog", "variation": "erect" },
|
||||
{ "songId": "eggnog", "variation": "pico" },
|
||||
|
||||
{ "songId": "winter-horrorland", "variation": "default" },
|
||||
{ "songId": "winter-horrorland", "variation": "erect" },
|
||||
{ "songId": "winter-horrorland", "variation": "pico" },
|
||||
|
||||
{ "songId": "senpai", "variation": "default" },
|
||||
{ "songId": "senpai", "variation": "erect" },
|
||||
{ "songId": "senpai", "variation": "pico" },
|
||||
|
||||
{ "songId": "roses", "variation": "default" },
|
||||
{ "songId": "roses", "variation": "erect" },
|
||||
{ "songId": "roses", "variation": "pico" },
|
||||
|
||||
{ "songId": "thorns", "variation": "default" },
|
||||
{ "songId": "thorns", "variation": "erect" },
|
||||
{ "songId": "thorns", "variation": "pico" },
|
||||
|
||||
{ "songId": "ugh", "variation": "default" },
|
||||
{ "songId": "ugh", "variation": "erect" },
|
||||
{ "songId": "ugh", "variation": "pico" },
|
||||
|
||||
{ "songId": "guns", "variation": "default" },
|
||||
{ "songId": "guns", "variation": "erect" },
|
||||
{ "songId": "guns", "variation": "pico" },
|
||||
|
||||
{ "songId": "stress", "variation": "default" },
|
||||
{ "songId": "stress", "variation": "erect" },
|
||||
{ "songId": "stress", "variation": "pico" },
|
||||
|
||||
{ "songId": "darnell", "variation": "default" },
|
||||
{ "songId": "darnell", "variation": "erect" },
|
||||
{ "songId": "darnell", "variation": "bf" },
|
||||
|
||||
{ "songId": "lit-up", "variation": "default" },
|
||||
{ "songId": "lit-up", "variation": "erect" },
|
||||
{ "songId": "lit-up", "variation": "bf" },
|
||||
|
||||
{ "songId": "2hot", "variation": "default" },
|
||||
{ "songId": "2hot", "variation": "erect" },
|
||||
{ "songId": "2hot", "variation": "bf" },
|
||||
|
||||
{ "songId": "blazin", "variation": "default" },
|
||||
{ "songId": "blazin", "variation": "erect" },
|
||||
{ "songId": "blazin", "variation": "bf" },
|
||||
]
|
||||
|
||||
LEVEL_IDS = [
|
||||
"tutorial",
|
||||
"week1",
|
||||
"week2",
|
||||
"week3",
|
||||
"week4",
|
||||
"week5",
|
||||
"week6",
|
||||
"week7",
|
||||
"weekend1",
|
||||
"week8",
|
||||
]
|
||||
|
||||
RANKS = [
|
||||
"PERFECT_GOLD",
|
||||
"PERFECT",
|
||||
"EXCELLENT",
|
||||
"GREAT",
|
||||
"GOOD",
|
||||
"SHIT",
|
||||
]
|
||||
|
||||
|
||||
def formatEventSong(event):
|
||||
result = []
|
||||
for song in SONG_IDS:
|
||||
result.append(event.format(
|
||||
songId = song["songId"],
|
||||
variation = song["variation"]
|
||||
))
|
||||
return result
|
||||
|
||||
def formatEventLevel(event):
|
||||
result = []
|
||||
for level in LEVEL_IDS:
|
||||
result.append(event.format(
|
||||
levelId = level
|
||||
))
|
||||
return result
|
||||
|
||||
def formatEventRank(event):
|
||||
result = []
|
||||
for rank in RANKS:
|
||||
result.append(event.format(
|
||||
rankName = rank
|
||||
))
|
||||
return result
|
||||
|
||||
def formatEvent(event):
|
||||
if "{songId}" in event or "{variation}" in event:
|
||||
return formatEventSong(event)
|
||||
|
||||
if "{levelId}" in event:
|
||||
return formatEventLevel(event)
|
||||
|
||||
if "{rankName}" in event:
|
||||
return formatEventRank(event)
|
||||
|
||||
return [event]
|
||||
|
||||
def createEvent(event):
|
||||
print(TEMPLATE.format(event=event))
|
||||
|
||||
def main():
|
||||
event_list = []
|
||||
|
||||
for event in EVENT_TEMPLATES:
|
||||
event_list.extend(formatEvent(event))
|
||||
|
||||
# Filter out duplicates then sort alphabetically
|
||||
event_list = list(set(event_list))
|
||||
event_list.sort()
|
||||
|
||||
for event in event_list:
|
||||
createEvent(event)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
||||
|
||||
#function sleep(ms) {
|
||||
# console.log("sleeping for " + ms + "ms");
|
||||
# return new Promise(resolve => setTimeout(resolve, ms));
|
||||
#}
|
||||
#async function example() {
|
||||
# jQuery("#new-event-name").val("blueballs_blazin-erect"); jQuery("#save-new-event").click();
|
||||
# await sleep(1000);
|
||||
# jQuery("#new-event-name").val("blueballs_blazin-erect"); jQuery("#save-new-event").click();
|
||||
# await sleep(1000);
|
||||
#}
|
||||
#example();
|
||||
@@ -0,0 +1,11 @@
|
||||
@echo off
|
||||
color 0a
|
||||
cd ..
|
||||
@echo on
|
||||
echo BUILDING GAME
|
||||
lime build html5 -final
|
||||
echo UPLOADING TO ITCH
|
||||
butler push ./export/release/html5/bin ninja-muffin24/funkin-secret:html5
|
||||
butler status ninja-muffin24/funkin-secret:html5
|
||||
echo ITCH SHIT UPDATED LMAOOOOO
|
||||
pause
|
||||
@@ -0,0 +1,11 @@
|
||||
@echo off
|
||||
color 0a
|
||||
cd ..
|
||||
@echo on
|
||||
echo BUILDING GAME
|
||||
lime build html5 -debug
|
||||
echo UPLOADING TO ITCH
|
||||
butler push ./export/debug/html5/bin ninja-muffin24/funkin-secret:html5
|
||||
butler status ninja-muffin24/funkin-secret:html5
|
||||
echo ITCH SHIT UPDATED LMAOOOOO
|
||||
pause
|
||||
@@ -0,0 +1,15 @@
|
||||
@echo off
|
||||
color 0a
|
||||
cd ..
|
||||
@echo on
|
||||
echo BUILDING GAME
|
||||
lime build windows -final -clean
|
||||
echo UPLOADING 64 BIT VERSION TO ITCH
|
||||
butler push ./export/release/windows/bin ninja-muffin24/funkin:windows-64bit
|
||||
lime build windows -final -32
|
||||
echo UPLOADING 32 BIT VERSION TO ITCH
|
||||
butler push ./export/release/windows/bin ninja-muffin24/funkin:windows-32bit
|
||||
butler status ninja-muffin24/funkin:windows-32bit
|
||||
butler status ninja-muffin24/funkin:windows-64bit
|
||||
echo ITCH SHIT UPDATED LMAOOOOO
|
||||
pause
|
||||
@@ -0,0 +1,11 @@
|
||||
@echo off
|
||||
color 0a
|
||||
cd ..
|
||||
@echo on
|
||||
echo BUILDING GAME
|
||||
lime build windows -debug
|
||||
echo UPLOADING 64 BIT VERSION TO ITCH
|
||||
butler push ./export/debug/windows/bin ninja-muffin24/funkin:windows-secretBeta
|
||||
butler status ninja-muffin24/funkin:windows-secretBeta
|
||||
echo ITCH SHIT UPDATED LMAOOOOO
|
||||
pause
|
||||
@@ -0,0 +1,15 @@
|
||||
@echo off
|
||||
color 0a
|
||||
cd ..
|
||||
@echo on
|
||||
echo BUILDING GAME
|
||||
lime build windows -release
|
||||
echo UPLOADING 64 BIT VERSION TO ITCH
|
||||
butler push ./export/release/windows/bin ninja-muffin24/funkin:windows-64bit
|
||||
lime build windows -release -32
|
||||
echo UPLOADING 32 BIT VERSION TO ITCH
|
||||
butler push ./export/release/windows/bin ninja-muffin24/funkin:windows-32bit
|
||||
butler status ninja-muffin24/funkin:windows-32bit
|
||||
butler status ninja-muffin24/funkin:windows-64bit
|
||||
echo ITCH SHIT UPDATED LMAOOOOO
|
||||
pause
|
||||
@@ -0,0 +1,12 @@
|
||||
@echo off
|
||||
color 0a
|
||||
cd ..
|
||||
@echo on
|
||||
echo BUILDING GAME
|
||||
lime build html5 -final -Dng
|
||||
echo ZIPPING
|
||||
cd export
|
||||
7z a -tzip -r swag release\html5\bin
|
||||
start .
|
||||
start https://www.newgrounds.com/projects/games/1528775
|
||||
pause
|
||||
@@ -0,0 +1,27 @@
|
||||
@echo off
|
||||
cd ..
|
||||
@echo on
|
||||
echo REBUILDING LIME FOR EXPORT (VERBOSE)
|
||||
@echo off
|
||||
color 0b
|
||||
@echo on
|
||||
lime rebuild windows -clean -v
|
||||
@echo off
|
||||
color 0a
|
||||
@echo on
|
||||
lime rebuild switch -clean -v
|
||||
@echo off
|
||||
color 0e
|
||||
@echo on
|
||||
lime rebuild tools -clean -v
|
||||
echo HOPE AND PRAY...
|
||||
@echo off
|
||||
color 0a
|
||||
@echo on
|
||||
echo BUILDING GAME
|
||||
lime build switch -final -clean -v
|
||||
@echo off
|
||||
color 0b
|
||||
@echo on
|
||||
echo NSP FILE CREATED
|
||||
pause
|
||||
@@ -0,0 +1,110 @@
|
||||
// Highlight keyframes in flash
|
||||
// drag .jsfl script in
|
||||
// will export the layer info of the first(?) element it finds?
|
||||
// maybe someone can extend this to add more depth / functionality!
|
||||
// see FramesJSFLParser.hx and CharSelectGF.hx for sample implementation
|
||||
|
||||
function splitArrayIntoChunks(array, chunkSize) {
|
||||
var result = [];
|
||||
for (var i = 0; i < array.length; i += chunkSize) {
|
||||
var chunk = array.slice(i, i + chunkSize);
|
||||
result.push(chunk);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
var isJson = false;
|
||||
|
||||
function addField(name, variable, end)
|
||||
{
|
||||
return (isJson) ? addFJson(name, variable, end) : addF(variable, end);
|
||||
}
|
||||
function addFJson(name, variable, end) {
|
||||
return "\"" + name + "\": " + variable + ((!end) ? "," : "");
|
||||
}
|
||||
function addF(variable, end) {
|
||||
return variable + ((end)? "" : " ");
|
||||
}
|
||||
|
||||
|
||||
fl.outputPanel.clear();
|
||||
|
||||
var timeline = fl.getDocumentDOM().getTimeline();
|
||||
var daSelection = timeline.getSelectedFrames();
|
||||
var splitArrays = splitArrayIntoChunks(daSelection, 3);
|
||||
var uri = undefined;
|
||||
|
||||
|
||||
if (fl.version.substring(4).split(",")[0] < 13)
|
||||
{
|
||||
var macFormat = "Folder|TEXT[*.||";
|
||||
var winFormat = "Folder|*.||";
|
||||
var previewArea = {};
|
||||
uri = fl.browseForFileURL("save", "where to save files pick folder...", previewArea, macFormat, winFormat);
|
||||
}
|
||||
else
|
||||
uri = fl.browseForFileURL("save", "where to save files pick folder..", "Folder(*.)", "*.");
|
||||
|
||||
|
||||
for (var i = 0; i < splitArrays.length; i+=1)
|
||||
{
|
||||
var curLayerInfo = splitArrays[i];
|
||||
var curLayerInd = curLayerInfo[0];
|
||||
var curLayer = fl.getDocumentDOM().getTimeline().layers[curLayerInd];
|
||||
|
||||
var layerURI = uri;
|
||||
var fileName = "";
|
||||
var uris = uri.split("/");
|
||||
|
||||
fileName = uris[uris.length - 1];
|
||||
|
||||
|
||||
FLfile.createFolder(layerURI);
|
||||
|
||||
|
||||
var fileData = "{\n";
|
||||
if (!isJson)
|
||||
fileData = "";
|
||||
|
||||
var selectedFrames = curLayer.frames;
|
||||
var sliceLength = curLayerInfo[2] - curLayerInfo[1];
|
||||
selectedFrames =selectedFrames.slice(curLayerInfo[1],curLayerInfo[2]);
|
||||
|
||||
if (isJson)
|
||||
fileData += "\"frames\": [";
|
||||
|
||||
for (var frameInd = curLayerInfo[1]; frameInd < curLayerInfo[2]; frameInd+=1)
|
||||
{
|
||||
if (isJson)
|
||||
fileData += "\n{"
|
||||
var curFrame = curLayer.frames[frameInd];
|
||||
if (curFrame.isEmpty)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (var elementInd = 0; elementInd < curFrame.elements.length; elementInd+=1)
|
||||
{
|
||||
|
||||
var curElement = curFrame.elements[elementInd];
|
||||
|
||||
fileData += addField("x", curElement.left, false);
|
||||
fileData += addField("y", curElement.top, false);
|
||||
fileData += addField("alpha", (curElement.colorAlphaPercent == undefined) ? 100 : curElement.colorAlphaPercent, false);
|
||||
fileData += addField("scaleX", curElement.scaleX, false);
|
||||
fileData += addField("scaleY", curElement.scaleY, true);
|
||||
if (frameInd != curLayerInfo[2] - 1)
|
||||
fileData += "\n";
|
||||
}
|
||||
|
||||
if (isJson)
|
||||
fileData += "},";
|
||||
}
|
||||
if (isJson)
|
||||
fileData += "]";
|
||||
|
||||
if (isJson)
|
||||
fileData += "\n}";
|
||||
|
||||
FLfile.write(layerURI + "/" + fileName + ".txt", fileData);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
# README
|
||||
|
||||
jsfl is a scripting format with javascript syntax, I believe it's old js (no `let`, idk the spec!)
|
||||
|
||||
Official Adobe JSFL docuementation
|
||||
[https://github.com/AdobeDocs/developers-animatesdk-docs/tree/master](https://github.com/AdobeDocs/developers-animatesdk-docs/tree/master)
|
||||
Executable
+120
@@ -0,0 +1,120 @@
|
||||
#!/bin/bash
|
||||
set -e # Exit immediately if a command exits with a non-zero status
|
||||
|
||||
echo "Note! This should be run from the root of the main Funkin repo, after creating a universal macOS build!"
|
||||
echo "This script will assist in codesigning the Funkin.app for macOS"
|
||||
echo ""
|
||||
|
||||
TEAM_ID="Z7G7AVNGSH"
|
||||
ISSUER_ID="5205caca-2a9b-4b5e-9390-1db124cf51f4"
|
||||
DEVELOPER_CERT_NAME="Developer ID Application: The Funkin' Crew Incorporated ($TEAM_ID)"
|
||||
APP_PATH="export/release/macosUniversal/Funkin.app"
|
||||
|
||||
# Check if app exists
|
||||
if [ ! -d "$APP_PATH" ]; then
|
||||
echo "Error: $APP_PATH does not exist! Please build the app first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Preparing/Checking the AuthKey"
|
||||
team_key_path=~/.appstoreconnect/private_keys/
|
||||
# Check if directory exists, if not, create it
|
||||
if [ ! -d "$team_key_path" ]; then
|
||||
echo "The directory $team_key_path does not exist! Creating it now."
|
||||
mkdir -p "$team_key_path"
|
||||
echo ""
|
||||
echo "$team_key_path created, put the private key file in there (should be called 'AuthKey_XXXXXXX.p8')"
|
||||
echo "Ask Cameron for the private key file!"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# wait for the user to put the file in the directory, if it's not there
|
||||
while [ ! -f "${team_key_path}"AuthKey_*.p8 ]; do
|
||||
echo "Waiting for the private key file to be placed in $team_key_path... (press Ctrl+C to exit)"
|
||||
sleep 5
|
||||
done
|
||||
|
||||
key_id=$(ls "$team_key_path" | grep -oE '[A-Z0-9]{10}' | head -n 1)
|
||||
if [ -z "$key_id" ]; then
|
||||
echo "Error: Could not extract key ID from the AuthKey file name"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Found AuthKey with key id: $key_id"
|
||||
key_path="${team_key_path}AuthKey_${key_id}.p8"
|
||||
|
||||
cd export/release/macosUniversal || { echo "Failed to change directory"; exit 1; }
|
||||
|
||||
echo "Ensuring properly formatted Entitlements.plist"
|
||||
if [ ! -f "Funkin.app/Contents/Entitlements.plist" ]; then
|
||||
echo "Error: Entitlements.plist not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
plutil -convert xml1 Funkin.app/Contents/Entitlements.plist
|
||||
plutil -convert xml1 Funkin.app/Contents/Info.plist
|
||||
plutil -lint Funkin.app/Contents/Entitlements.plist
|
||||
plutil -lint Funkin.app/Contents/Info.plist
|
||||
echo "Entitlements.plist and Info.plist should be properly formatted..."
|
||||
echo "Codesigning - Funkin.app"
|
||||
# Check the keychain for the funkin crew certificate
|
||||
echo "Checking for The Funkin' Crew certificate in the keychain..."
|
||||
if ! security find-identity -p codesigning | grep -q "The Funkin' Crew Incorporated"; then
|
||||
echo "The Funkin' Crew Developer ID Application certificate was not found in the keychain!"
|
||||
echo "Download the certificate from:"
|
||||
echo "https://developer.apple.com/account/resources/certificates/download/Z62SXF29U7"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Codesigning - Funkin.app"
|
||||
codesign --force --deep --timestamp --sign "$DEVELOPER_CERT_NAME" --options runtime Funkin.app || {
|
||||
echo "Code signing failed"
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "Codesigning - Funkin.app done!"
|
||||
# Debug info to see if the app is properly signed
|
||||
codesign -vv --deep --strict Funkin.app || {
|
||||
echo "Code signing verification failed"
|
||||
exit 1
|
||||
}
|
||||
echo ""
|
||||
echo "Zipping Funkin.app for notarization (using ditto)..."
|
||||
# Check if Funkin.app.zip already exists, if so, delete it
|
||||
if [ -f Funkin.app.zip ]; then
|
||||
echo "Funkin.app.zip already exists, deleting it..."
|
||||
rm -rf Funkin.app.zip
|
||||
fi
|
||||
|
||||
ditto -c -k --keepParent Funkin.app Funkin.app.zip || {
|
||||
echo "Failed to create zip file"
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "Funkin.app zipped, now sending to Apple for notarization..."
|
||||
xcrun notarytool submit Funkin.app.zip --issuer "$ISSUER_ID" --key-id "$key_id" --key "$key_path" --wait || {
|
||||
echo "Notarization submission failed"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Clean up zip file
|
||||
rm -rf Funkin.app.zip
|
||||
|
||||
echo "Stapling Funkin.app"
|
||||
xcrun stapler staple Funkin.app || {
|
||||
echo "Stapling failed"
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "Code sign information!"
|
||||
codesign -dv --verbose=4 Funkin.app
|
||||
|
||||
echo "Notarization information (stapler):"
|
||||
xcrun stapler validate Funkin.app || {
|
||||
echo "Stapler validation failed"
|
||||
exit 1
|
||||
}
|
||||
|
||||
cd ../../.. || { echo "Failed to return to original directory"; exit 1; }
|
||||
echo "Notarization complete! Funkin.app is now codesigned and notarized!"
|
||||
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
echo "Note! This should be run from the root of the main Funkin repo, after creating a universal macOS build!"
|
||||
echo "This script will assist in creating a universal build of Funkin.app for macOS"
|
||||
echo ""
|
||||
lime build mac -64 -release
|
||||
cd export/release
|
||||
rm -rf macosUniversal
|
||||
mkdir macosUniversal
|
||||
cp -r macos/bin/Funkin.app macosUniversal
|
||||
cd ../..
|
||||
lime build mac -arm64 -release
|
||||
cd export/release
|
||||
cp -r macos/bin/Funkin.app/Contents/MacOS/Funkin macosUniversal/Funkin.app/Contents/MacOS/FunkinArm
|
||||
cp -r macos/bin/Funkin.app/Contents/MacOS/lime.ndll macosUniversal/Funkin.app/Contents/MacOS/limeArm.ndll
|
||||
cd macosUniversal/Funkin.app/Contents/MacOS
|
||||
mv Funkin Funkin64
|
||||
mv lime.ndll lime64.ndll
|
||||
lipo -create -output lime.ndll lime64.ndll limeArm.ndll
|
||||
lipo -create -output Funkin Funkin64 FunkinArm
|
||||
rm FunkinArm Funkin64 lime64.ndll limeArm.ndll
|
||||
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Find all files in PNG format and execute oxipng to optimize file sizes
|
||||
cd ../assets/ && oxipng -o 6 --strip safe --alpha {} **/*.png
|
||||
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Resize each health icon to 512x512
|
||||
find "../art/discord/" -name "icon-*.png" -exec sh -c 'ffmpeg -i {} -vf scale=512:512 "${0%.*}2.png"' {} \;
|
||||
+91
-26
@@ -1,14 +1,16 @@
|
||||
package;
|
||||
|
||||
import lime.system.System;
|
||||
import flixel.FlxG;
|
||||
import flixel.FlxGame;
|
||||
import flixel.FlxState;
|
||||
import funkin.ui.FullScreenScaleMode;
|
||||
import funkin.Preferences;
|
||||
import funkin.PlayerSettings;
|
||||
import funkin.util.logging.CrashHandler;
|
||||
import funkin.ui.debug.FunkinDebugDisplay;
|
||||
import funkin.ui.debug.FunkinDebugDisplay.DebugDisplayMode;
|
||||
import funkin.save.Save;
|
||||
import haxe.ui.Toolkit;
|
||||
#if hxvlc
|
||||
import hxvlc.util.Handle;
|
||||
#end
|
||||
@@ -19,6 +21,8 @@ import openfl.media.Video;
|
||||
import openfl.net.NetStream;
|
||||
import funkin.util.WindowUtil;
|
||||
|
||||
using funkin.util.AnsiUtil;
|
||||
|
||||
/**
|
||||
* The main class which initializes HaxeFlixel and starts the game in its initial state.
|
||||
*/
|
||||
@@ -58,6 +62,10 @@ class Main extends Sprite
|
||||
haxe.Log.trace = funkin.util.logging.AnsiTrace.trace;
|
||||
funkin.util.logging.AnsiTrace.traceBF();
|
||||
|
||||
// Get OpenFL to stop complaining so much.
|
||||
// You can remove this line if you want to read debug messages.
|
||||
openfl.utils._internal.Log.level = openfl.utils._internal.Log.LogLevel.INFO;
|
||||
|
||||
// Load mods to override assets.
|
||||
// TODO: Replace with loadEnabledMods() once the user can configure the mod list.
|
||||
funkin.modding.PolymodHandler.loadAllMods();
|
||||
@@ -79,6 +87,40 @@ class Main extends Sprite
|
||||
removeEventListener(Event.ADDED_TO_STAGE, init);
|
||||
}
|
||||
|
||||
#if (sys && !mobile)
|
||||
// Force-kill the game to prevent background processing.
|
||||
Lib.current.stage.window.onClose.add(function()
|
||||
{
|
||||
trace(' EXITING '.bold().bg_red() + ' Game is exiting, cleaning up resources...');
|
||||
|
||||
#if hxvlc
|
||||
// Clean up VLC threads to prevent memory leaks.
|
||||
hxvlc.util.Handle.dispose();
|
||||
#end
|
||||
|
||||
Sys.exit(0);
|
||||
});
|
||||
#end
|
||||
|
||||
// Manually crash the game when using a software renderer in order to give a nicer error message.
|
||||
var context = stage.window.context.type;
|
||||
if (context != WEBGL && context != OPENGL && context != OPENGLES)
|
||||
{
|
||||
var tech:String = #if web "WebGL" #elseif desktop "OpenGL" #else "OpenGL ES" #end;
|
||||
var requiredVersion:String = #if web '$tech 1.0 or newer' #elseif desktop '$tech 3.0 or newer' #else '$tech 2.0 or newer' #end;
|
||||
var desc:String = 'Failed to initialize the $tech rendering context!\n\n';
|
||||
#if web
|
||||
desc += 'Make sure your graphics card supports $requiredVersion, your graphics drivers are up to date, and hardware acceleration is enabled on your browser.';
|
||||
#elseif desktop
|
||||
desc += 'Make sure your graphics card supports $requiredVersion, and your graphics drivers are up to date.';
|
||||
#else
|
||||
desc += 'Make sure your device supports $requiredVersion.';
|
||||
#end
|
||||
|
||||
WindowUtil.showError('Failed to initialize $tech', desc);
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
setupGame();
|
||||
}
|
||||
|
||||
@@ -89,11 +131,16 @@ class Main extends Sprite
|
||||
|
||||
function setupGame():Void
|
||||
{
|
||||
#if FEATURE_HAXEUI
|
||||
initHaxeUI();
|
||||
#end
|
||||
|
||||
// addChild gets called by the user settings code.
|
||||
debugDisplay = new FunkinDebugDisplay(10, 10, 0xFFFFFF);
|
||||
|
||||
// Add this signal so the player can toggle the debug display using a hotkey.
|
||||
FlxG.signals.postUpdate.add(handleDebugDisplayKeys);
|
||||
|
||||
#if mobile
|
||||
// Add this signal so we can reposition and resize the memory and fps counter.
|
||||
FlxG.signals.preUpdate.add(repositionCounters.bind(true));
|
||||
@@ -104,28 +151,28 @@ class Main extends Sprite
|
||||
|
||||
#if hxvlc
|
||||
// Initialize hxvlc's Handle here so the videos are loading faster.
|
||||
Handle.initAsync(function(success:Bool):Void {
|
||||
Handle.initAsync(function(success:Bool):Void
|
||||
{
|
||||
if (success)
|
||||
{
|
||||
trace('[HXVLC] LibVLC instance initialized!');
|
||||
trace(' HXVLC '.bold().bg_orange() + ' LibVLC instance initialized!');
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[HXVLC] LibVLC instance failed to initialize!');
|
||||
trace(' HXVLC '.bold().bg_orange() + ' LibVLC instance failed to initialize!');
|
||||
}
|
||||
});
|
||||
#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
|
||||
// The framerate cap will more thoroughly initialize via Preferences in InitState.hx
|
||||
funkin.Preferences.lockedFramerateFunction = untyped js.Syntax.code("window.requestAnimationFrame");
|
||||
#end
|
||||
|
||||
WindowUtil.setVSyncMode(funkin.Preferences.vsyncMode);
|
||||
|
||||
var game:FlxGame = new FlxGame(gameWidth, gameHeight, initialState, Preferences.framerate, Preferences.framerate, skipSplash,
|
||||
// Force a `FunkinCamera` to be the default camera.
|
||||
// This allows the blend mode shader to work everywhere.
|
||||
untyped FlxG.cameras = new funkin.graphics.FunkinCameraFrontEnd();
|
||||
|
||||
var framerate:Int = Preferences.unlockedFramerate ? 0 : Preferences.framerate;
|
||||
|
||||
var game:FlxGame = new FlxGame(gameWidth, gameHeight, initialState, framerate, framerate, skipSplash,
|
||||
(FlxG.stage.window.fullscreen || Preferences.autoFullscreen));
|
||||
|
||||
// FlxG.game._customSoundTray wants just the class, it calls new from
|
||||
@@ -137,7 +184,7 @@ class Main extends Sprite
|
||||
addChild(game);
|
||||
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
game.debugger.interaction.addTool(new funkin.util.TrackerToolButtonUtil());
|
||||
#if !FLX_NO_DEBUG game.debugger.interaction.addTool(new funkin.util.TrackerToolButtonUtil()); #end
|
||||
funkin.util.macro.ConsoleMacro.init();
|
||||
#end
|
||||
|
||||
@@ -157,33 +204,49 @@ class Main extends Sprite
|
||||
#end
|
||||
}
|
||||
|
||||
#if FEATURE_HAXEUI
|
||||
function initHaxeUI():Void
|
||||
{
|
||||
// This has to come before Toolkit.init since locales get initialized there
|
||||
haxe.ui.locale.LocaleManager.instance.autoSetLocale = false;
|
||||
// Calling this before any HaxeUI components get used is important:
|
||||
// - It initializes the theme styles.
|
||||
// - It scans the class path and registers any HaxeUI components.
|
||||
Toolkit.init();
|
||||
Toolkit.theme = 'dark'; // don't be cringe
|
||||
// Toolkit.theme = 'light'; // embrace cringe
|
||||
Toolkit.autoScale = false;
|
||||
haxe.ui.Toolkit.init();
|
||||
haxe.ui.Toolkit.theme = 'dark'; // don't be cringe
|
||||
// haxe.ui.Toolkit.theme = 'light'; // embrace cringe
|
||||
haxe.ui.Toolkit.autoScale = false;
|
||||
// Don't focus on UI elements when they first appear.
|
||||
haxe.ui.focus.FocusManager.instance.autoFocus = false;
|
||||
funkin.input.Cursor.registerHaxeUICursors();
|
||||
haxe.ui.tooltips.ToolTipManager.defaultDelay = 200;
|
||||
}
|
||||
#end
|
||||
|
||||
function handleDebugDisplayKeys():Void
|
||||
{
|
||||
if (PlayerSettings.player1.controls == null || !PlayerSettings.player1.controls.check(DEBUG_DISPLAY)) return;
|
||||
|
||||
var nextMode:DebugDisplayMode;
|
||||
|
||||
switch (Preferences.debugDisplay)
|
||||
{
|
||||
case DebugDisplayMode.Off:
|
||||
nextMode = DebugDisplayMode.Simple;
|
||||
case DebugDisplayMode.Simple:
|
||||
nextMode = DebugDisplayMode.Advanced;
|
||||
case DebugDisplayMode.Advanced:
|
||||
nextMode = DebugDisplayMode.Off;
|
||||
}
|
||||
|
||||
Preferences.debugDisplay = nextMode;
|
||||
}
|
||||
|
||||
#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);
|
||||
var scale:Float = Math.max(Math.min(FlxG.stage.stageWidth / FlxG.width, FlxG.stage.stageHeight / FlxG.height), 1);
|
||||
|
||||
if (debugDisplay != null)
|
||||
{
|
||||
@@ -191,13 +254,15 @@ class Main extends Sprite
|
||||
|
||||
if (FlxG.game != null)
|
||||
{
|
||||
final thypos:Float = Math.max(FullScreenScaleMode.notchSize.x, 10);
|
||||
|
||||
if (lerp)
|
||||
{
|
||||
debugDisplay.x = flixel.math.FlxMath.lerp(debugDisplay.x, FlxG.game.x + thypos, FlxG.elapsed * 3);
|
||||
}
|
||||
else
|
||||
{
|
||||
debugDisplay.x = FlxG.game.x + FullScreenScaleMode.notchSize.x + 10;
|
||||
debugDisplay.x = FlxG.game.x + thypos;
|
||||
}
|
||||
|
||||
debugDisplay.y = FlxG.game.y + (3 * scale);
|
||||
|
||||
+9
-24
@@ -4,6 +4,7 @@ import sys.FileSystem;
|
||||
import sys.io.File;
|
||||
|
||||
using StringTools;
|
||||
using tools.AnsiUtil;
|
||||
|
||||
/**
|
||||
* A script which executes after the game is built.
|
||||
@@ -29,41 +30,25 @@ class Postbuild
|
||||
|
||||
sys.FileSystem.deleteFile(BUILD_TIME_FILE);
|
||||
|
||||
Sys.println('[INFO] Build took: ${format(end - start)}');
|
||||
Sys.println(' INFO '.info() + ' Build took: ${format(end - start)}');
|
||||
}
|
||||
}
|
||||
|
||||
static function format(time:Float, decimals:Int = 1):String
|
||||
{
|
||||
var units = [
|
||||
{name: "day", secs: 86400},
|
||||
{name: "hour", secs: 3600},
|
||||
{name: "minute", secs: 60},
|
||||
{name: "second", secs: 1}
|
||||
];
|
||||
var units = [{name: "day", secs: 86400}, {name: "hour", secs: 3600}, {name: "minute", secs: 60}, {name: "second", secs: 1}];
|
||||
|
||||
var parts:Array<String> = [];
|
||||
var remaining:Float = time;
|
||||
var factor = Math.pow(10, decimals); // compute once because the old code was computing it twice.
|
||||
|
||||
for (unit in units)
|
||||
for (u in units)
|
||||
{
|
||||
var value:Float;
|
||||
if (unit.name == "second")
|
||||
{
|
||||
// handle seconds with decimals
|
||||
value = Math.round(remaining * Math.pow(10, decimals)) / Math.pow(10, decimals);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = Math.floor(remaining / unit.secs);
|
||||
remaining %= unit.secs;
|
||||
}
|
||||
var value:Float = (u.name == "second") ? Math.round(remaining * factor) / factor : Math.floor(remaining / u.secs);
|
||||
|
||||
//
|
||||
if (value > 0 || (unit.name == "second" && parts.length == 0))
|
||||
{
|
||||
parts.push('${value} ${unit.name}${value == 1 ? "" : "s"}');
|
||||
}
|
||||
if (u.name != "second") remaining %= u.secs;
|
||||
|
||||
if (value > 0 || (u.name == "second" && parts.length == 0)) parts.push('${value} ${u.name}${value == 1 ? "" : "s"}');
|
||||
}
|
||||
|
||||
return parts.join(" ");
|
||||
|
||||
+4
-2
@@ -2,6 +2,8 @@ package source; // Yeah, I know...
|
||||
|
||||
import sys.io.File;
|
||||
|
||||
using tools.AnsiUtil;
|
||||
|
||||
/**
|
||||
* A script which executes before the game is built.
|
||||
*/
|
||||
@@ -12,13 +14,13 @@ class Prebuild
|
||||
static function main():Void
|
||||
{
|
||||
var start:Float = Sys.time();
|
||||
// Sys.println('[INFO] Performing pre-build tasks...');
|
||||
// Sys.println(' INFO '.bold().bg_blue() + " Performing pre-build tasks...");
|
||||
|
||||
saveBuildTime();
|
||||
|
||||
var end:Float = Sys.time();
|
||||
var duration:Float = end - start;
|
||||
// Sys.println('[INFO] Finished pre-build tasks in $duration seconds.');
|
||||
// Sys.println(' INFO '.bold().bg_blue() + ' Finished pre-build tasks in $duration seconds.');
|
||||
}
|
||||
|
||||
static function saveBuildTime():Void
|
||||
|
||||
@@ -89,44 +89,35 @@ extern class Native_TracyProfiler
|
||||
class Cppia_TracyProfiler
|
||||
{
|
||||
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.frameMark)
|
||||
public static function frameMark()
|
||||
Native_TracyProfiler.frameMark();
|
||||
public static function frameMark() Native_TracyProfiler.frameMark();
|
||||
|
||||
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.frameMarkNamed)
|
||||
public static function frameMarkNamed(_name:String)
|
||||
Native_TracyProfiler.frameMarkNamed(_name);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
public static function message(_msg:String, ?_color:Int = 0x000000) Native_TracyProfiler.message(_msg, _color);
|
||||
|
||||
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.messageAppInfo)
|
||||
public static function messageAppInfo(_info:String)
|
||||
Native_TracyProfiler.messageAppInfo(_info);
|
||||
public static function messageAppInfo(_info:String) Native_TracyProfiler.messageAppInfo(_info);
|
||||
|
||||
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.plot)
|
||||
public static function plot(_name:String, _val:Float)
|
||||
Native_TracyProfiler.plot(_name, _val);
|
||||
public static function plot(_name:String, _val:Float) Native_TracyProfiler.plot(_name, _val);
|
||||
|
||||
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.plotConfig)
|
||||
public static function plotConfig(_name:String, _format:PlotFormatType, ?_step:Bool = false, ?_fill:Bool = false, ?_color:Int = 0x000000)
|
||||
Native_TracyProfiler.plotConfig(_name, _format, _step, _fill, _color);
|
||||
public static function plotConfig(_name:String, _format:PlotFormatType, ?_step:Bool = false, ?_fill:Bool = false,
|
||||
?_color:Int = 0x000000) Native_TracyProfiler.plotConfig(_name, _format, _step, _fill, _color);
|
||||
|
||||
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.setThreadName)
|
||||
public static function setThreadName(_name:String, ?_groupHint:Int = 1)
|
||||
Native_TracyProfiler.setThreadName(_name, _groupHint);
|
||||
public static function setThreadName(_name:String, ?_groupHint:Int = 1) Native_TracyProfiler.setThreadName(_name, _groupHint);
|
||||
|
||||
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.zoneScoped)
|
||||
public static function zoneScoped(_name:String)
|
||||
Native_TracyProfiler.zoneScoped(_name);
|
||||
public static function zoneScoped(_name:String) Native_TracyProfiler.zoneScoped(_name);
|
||||
}
|
||||
|
||||
typedef TracyProfiler = Cppia_TracyProfiler;
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package funkin;
|
||||
|
||||
import openfl.utils.Future;
|
||||
import funkin.util.macro.ConsoleMacro;
|
||||
|
||||
/**
|
||||
* A wrapper around `openfl.utils.Assets` which disallows access to the harmful functions.
|
||||
* Later we'll add Funkin-specific caching to this.
|
||||
*/
|
||||
@:nullSafety
|
||||
class Assets
|
||||
class Assets implements ConsoleClass
|
||||
{
|
||||
/**
|
||||
* The assets cache.
|
||||
@@ -114,9 +115,9 @@ class Assets
|
||||
* @param path The asset path to load from
|
||||
* @return The loaded Bitmap image
|
||||
*/
|
||||
public static function getBitmapData(path:String):openfl.display.BitmapData
|
||||
public static function getBitmapData(path:String, useCache:Bool = true):openfl.display.BitmapData
|
||||
{
|
||||
return openfl.utils.Assets.getBitmapData(path);
|
||||
return openfl.utils.Assets.getBitmapData(path, useCache);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+223
-41
@@ -18,20 +18,32 @@ import flixel.sound.FlxSound;
|
||||
@:nullSafety
|
||||
class Conductor
|
||||
{
|
||||
// onBeatHit is called every quarter note
|
||||
// onStepHit is called every sixteenth note
|
||||
// onBeatHit is called on every note determined by the denominator (4 = quarter, 8 = eighth, etc.)
|
||||
// onStepHit is called on every note which is a quarter of a beat (4 = sixteenth, 8 = thirty-second, etc.)
|
||||
// 4/4 = 4 beats per measure = 16 steps per measure
|
||||
// 120 BPM = 120 quarter notes per minute = 2 onBeatHit per second
|
||||
// 120 BPM = 480 sixteenth notes per minute = 8 onStepHit per second
|
||||
// 60 BPM = 60 quarter notes per minute = 1 onBeatHit per second
|
||||
// 60 BPM = 240 sixteenth notes per minute = 4 onStepHit per second
|
||||
// 3/4 = 3 beats per measure = 12 steps per measure
|
||||
// (IDENTICAL TO 4/4 but shorter measure length)
|
||||
// (Identical to 4/4 but has a shorter measure length)
|
||||
// 120 BPM = 120 quarter notes per minute = 2 onBeatHit per second
|
||||
// 120 BPM = 480 sixteenth notes per minute = 8 onStepHit per second
|
||||
// 60 BPM = 60 quarter notes per minute = 1 onBeatHit per second
|
||||
// 60 BPM = 240 sixteenth notes per minute = 4 onStepHit per second
|
||||
// 7/8 = 3.5 beats per measure = 14 steps per measure
|
||||
// 7/8 = 7 beats per measure = 28 steps per measure
|
||||
// Beats are EIGHTH NOTES!!
|
||||
// (Identical to 7/4 but beats happen twice as fast)
|
||||
// 120 BPM = 240 eighth notes per minute = 4 onBeatHit per second
|
||||
// 120 BPM = 960 twenty-second notes per minute = 16 onStepHit per second
|
||||
// 15/16 = 15 beats per measure = 60 steps per measure
|
||||
// Beats are SIXTEENTH NOTES!!
|
||||
// 120 BPM = 480 sixteenth notes per minute = 8 onBeatHit per second
|
||||
// 120 BPM = 1920 sixty-fourth notes per minute = 32 onStepHit per second
|
||||
// 3/2 = 3 beats per measure = 12 steps per measure
|
||||
// Beats are HALF NOTES!!
|
||||
// 120 BPM = 60 half notes per minute = 1 onBeatHit per second
|
||||
// 120 BPM = 240 eighth notes per minute = 4 onStepHit per second
|
||||
|
||||
/**
|
||||
* The current instance of the Conductor.
|
||||
@@ -113,7 +125,6 @@ class Conductor
|
||||
if (bpmOverride != null) return bpmOverride;
|
||||
|
||||
if (currentTimeChange == null) return Constants.DEFAULT_BPM;
|
||||
|
||||
@:privateAccess
|
||||
if (PlayState.instance != null && PlayState.instance.startingSong)
|
||||
{
|
||||
@@ -160,24 +171,24 @@ class Conductor
|
||||
}
|
||||
|
||||
/**
|
||||
* Duration of a beat (quarter note) in milliseconds. Calculated based on bpm.
|
||||
* Duration of a beat in milliseconds. Calculated based on bpm.
|
||||
*/
|
||||
public var beatLengthMs(get, never):Float;
|
||||
|
||||
function get_beatLengthMs():Float
|
||||
{
|
||||
// Tied directly to BPM.
|
||||
return ((Constants.SECS_PER_MIN / bpm) * Constants.MS_PER_SEC);
|
||||
return ((Constants.SECS_PER_MIN / bpm) * Constants.MS_PER_SEC) * (4 / timeSignatureDenominator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Duration of a step (sixtennth note) in milliseconds. Calculated based on bpm.
|
||||
* Duration of a step in milliseconds. Calculated based on bpm.
|
||||
*/
|
||||
public var stepLengthMs(get, never):Float;
|
||||
|
||||
function get_stepLengthMs():Float
|
||||
{
|
||||
return beatLengthMs / timeSignatureNumerator;
|
||||
return beatLengthMs / Constants.STEPS_PER_BEAT;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -246,7 +257,7 @@ class Conductor
|
||||
|
||||
function get_instrumentalOffsetSteps():Float
|
||||
{
|
||||
var startingStepLengthMs:Float = ((Constants.SECS_PER_MIN / startingBPM) * Constants.MS_PER_SEC) / timeSignatureNumerator;
|
||||
var startingStepLengthMs:Float = (((Constants.SECS_PER_MIN / startingBPM) * Constants.MS_PER_SEC) * (4 / timeSignatureDenominator)) / Constants.STEPS_PER_BEAT;
|
||||
|
||||
return instrumentalOffset / startingStepLengthMs;
|
||||
}
|
||||
@@ -288,26 +299,23 @@ class Conductor
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of beats in a measure. May be fractional depending on the time signature.
|
||||
* The number of beats in a measure.
|
||||
*/
|
||||
public var beatsPerMeasure(get, never):Float;
|
||||
|
||||
function get_beatsPerMeasure():Float
|
||||
{
|
||||
// NOTE: Not always an integer, for example 7/8 is 3.5 beats per measure
|
||||
return stepsPerMeasure / Constants.STEPS_PER_BEAT;
|
||||
return timeSignatureNumerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of steps in a measure.
|
||||
* TODO: I don't think this can be fractional?
|
||||
*/
|
||||
public var stepsPerMeasure(get, never):Int;
|
||||
|
||||
function get_stepsPerMeasure():Int
|
||||
{
|
||||
// TODO: Is this always an integer?
|
||||
return Std.int(timeSignatureNumerator / timeSignatureDenominator * Constants.STEPS_PER_BEAT * Constants.STEPS_PER_BEAT);
|
||||
return Std.int(timeSignatureNumerator * Constants.STEPS_PER_BEAT);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -373,7 +381,9 @@ class Conductor
|
||||
/**
|
||||
* The constructor.
|
||||
*/
|
||||
public function new() {}
|
||||
public function new()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Forcibly defines the current BPM of the song.
|
||||
@@ -389,11 +399,11 @@ class Conductor
|
||||
{
|
||||
if (bpm != null)
|
||||
{
|
||||
trace('[CONDUCTOR] Forcing BPM to ${bpm}');
|
||||
log('Forcing BPM to ${bpm}');
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[CONDUCTOR] Resetting BPM to default');
|
||||
log('Resetting BPM to default');
|
||||
}
|
||||
|
||||
this.bpmOverride = bpm;
|
||||
@@ -452,7 +462,7 @@ class Conductor
|
||||
|
||||
if (currentTimeChange == null && bpmOverride == null && FlxG.sound.music != null)
|
||||
{
|
||||
trace('WARNING: Conductor is broken, timeChanges is empty.');
|
||||
log(' WARNING '.warning() + 'Conductor is broken, timeChanges is empty.');
|
||||
}
|
||||
else if (currentTimeChange != null && this.songPosition > 0.0)
|
||||
{
|
||||
@@ -460,7 +470,7 @@ class Conductor
|
||||
this.currentStepTime = FlxMath.roundDecimal((currentTimeChange.beatTime * Constants.STEPS_PER_BEAT)
|
||||
+ (this.songPosition - currentTimeChange.timeStamp) / stepLengthMs, 6);
|
||||
this.currentBeatTime = currentStepTime / Constants.STEPS_PER_BEAT;
|
||||
this.currentMeasureTime = currentStepTime / stepsPerMeasure;
|
||||
this.currentMeasureTime = getTimeInMeasures(this.songPosition);
|
||||
this.currentStep = Math.floor(currentStepTime);
|
||||
this.currentBeat = Math.floor(currentBeatTime);
|
||||
this.currentMeasure = Math.floor(currentMeasureTime);
|
||||
@@ -517,7 +527,7 @@ class Conductor
|
||||
|
||||
/**
|
||||
* Can be called in-between frames, usually for input related things
|
||||
* that can potentially get processed on exact milliseconds/timestmaps.
|
||||
* that can potentially get processed on exact milliseconds/timestamps.
|
||||
* If you need song position, use `Conductor.instance.songPosition` instead
|
||||
* for use in update() related functions.
|
||||
* @param soundToCheck Which FlxSound object to check, defaults to FlxG.sound.music if no input
|
||||
@@ -526,12 +536,9 @@ class Conductor
|
||||
public function getTimeWithDiff(?soundToCheck:FlxSound):Float
|
||||
{
|
||||
if (soundToCheck == null) soundToCheck = FlxG.sound.music;
|
||||
// trace(this.songPosition);
|
||||
|
||||
@:privateAccess
|
||||
this.songPosition = soundToCheck._channel.position;
|
||||
// return this.songPosition + (Std.int(Timer.stamp() * 1000) - prevTimestamp);
|
||||
// trace("\t--> " + this.songPosition);
|
||||
return this.songPosition;
|
||||
}
|
||||
|
||||
@@ -566,7 +573,8 @@ class Conductor
|
||||
{
|
||||
var prevTimeChange:SongTimeChange = timeChanges[timeChanges.length - 1];
|
||||
songTimeChange.beatTime = FlxMath.roundDecimal(prevTimeChange.beatTime
|
||||
+ ((songTimeChange.timeStamp - prevTimeChange.timeStamp) * prevTimeChange.bpm / Constants.SECS_PER_MIN / Constants.MS_PER_SEC),
|
||||
+
|
||||
((songTimeChange.timeStamp - prevTimeChange.timeStamp) * prevTimeChange.bpm / Constants.SECS_PER_MIN / Constants.MS_PER_SEC * (prevTimeChange.timeSignatureDen / 4)),
|
||||
4);
|
||||
}
|
||||
}
|
||||
@@ -574,15 +582,118 @@ class Conductor
|
||||
timeChanges.push(songTimeChange);
|
||||
}
|
||||
|
||||
if (timeChanges.length > 0)
|
||||
if (timeChanges.length == 1)
|
||||
{
|
||||
trace('Done mapping time changes: ${timeChanges}');
|
||||
log('Done mapping single time change to ${timeChanges[0].bpm} BPM');
|
||||
}
|
||||
else if (timeChanges.length > 1)
|
||||
{
|
||||
log('Done mapping ${timeChanges.length} time changes (starting at ${timeChanges[0].bpm} BPM)');
|
||||
}
|
||||
else
|
||||
{
|
||||
log(' WARNING '.warning() + ' Conductor mapped no time changes?');
|
||||
}
|
||||
|
||||
// Update currentStepTime
|
||||
this.update(this.songPosition, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a time in milliseconds, return a time in measures.
|
||||
* @param ms The time in milliseconds.
|
||||
* @return The time in measures.
|
||||
*/
|
||||
public function getTimeInMeasures(ms:Float):Float
|
||||
{
|
||||
if (timeChanges.length == 0)
|
||||
{
|
||||
// Assume a constant BPM equal to the forced value.
|
||||
return ms / stepLengthMs / stepsPerMeasure;
|
||||
}
|
||||
else
|
||||
{
|
||||
var resultMeasureTime:Float = 0;
|
||||
ms = ms < 0 ? 0 : ms;
|
||||
|
||||
var lastTimeChange:SongTimeChange = timeChanges[0];
|
||||
var i:Int = -1;
|
||||
for (timeChange in timeChanges)
|
||||
{
|
||||
if (ms >= timeChange.timeStamp)
|
||||
{
|
||||
// We do NOT want to add the current time change's MEASURE LENGTH to the result, same for if we're inside the song's last time change.
|
||||
if (ms < timeChange.timeStamp || i == timeChanges.length - 1)
|
||||
{
|
||||
// However, we still want this for the ending calculation.
|
||||
lastTimeChange = timeChange;
|
||||
break;
|
||||
}
|
||||
var currentStepLengthMs:Float = (((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) * (4 / lastTimeChange.timeSignatureDen)) / Constants.STEPS_PER_BEAT;
|
||||
var currentStepsPerMeasure:Int = lastTimeChange.timeSignatureNum * Constants.STEPS_PER_BEAT;
|
||||
resultMeasureTime += (timeChange.timeStamp - lastTimeChange.timeStamp) / currentStepLengthMs / currentStepsPerMeasure;
|
||||
lastTimeChange = timeChange;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
var remainingStepLengthMs:Float = (((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) * (4 / lastTimeChange.timeSignatureDen)) / Constants.STEPS_PER_BEAT;
|
||||
var remainingStepsPerMeasure:Int = lastTimeChange.timeSignatureNum * Constants.STEPS_PER_BEAT;
|
||||
var remainingFractionalMeasure:Float = (ms - lastTimeChange.timeStamp) / remainingStepLengthMs / remainingStepsPerMeasure;
|
||||
resultMeasureTime += remainingFractionalMeasure;
|
||||
|
||||
return resultMeasureTime;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a time in measures and fractional measures, return a time in milliseconds.
|
||||
* @param measureTime The time in measures.
|
||||
* @return The time in milliseconds.
|
||||
*/
|
||||
public function getMeasureTimeInMs(measureTime:Float):Float
|
||||
{
|
||||
if (timeChanges.length == 0)
|
||||
{
|
||||
// Assume a constant BPM equal to the forced value.
|
||||
return measureTime * stepLengthMs * stepsPerMeasure;
|
||||
}
|
||||
else
|
||||
{
|
||||
var resultMs:Float = 0;
|
||||
measureTime = measureTime < 0 ? 0 : measureTime;
|
||||
|
||||
var lastTimeChange:SongTimeChange = timeChanges[0];
|
||||
var i:Int = -1;
|
||||
for (timeChange in timeChanges)
|
||||
{
|
||||
var currentTimeChangeMeasureTime:Float = getTimeInMeasures(timeChange.timeStamp);
|
||||
if (measureTime >= currentTimeChangeMeasureTime)
|
||||
{
|
||||
// We do NOT want to add the current time change's MEASURE LENGTH to the result, same for if we're inside the song's last time change.
|
||||
if (measureTime < currentTimeChangeMeasureTime || i == timeChanges.length - 1)
|
||||
{
|
||||
// However, we still want this for the ending calculation.
|
||||
lastTimeChange = timeChange;
|
||||
break;
|
||||
}
|
||||
var currentStepLengthMs:Float = (((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) * (4 / lastTimeChange.timeSignatureDen)) / Constants.STEPS_PER_BEAT;
|
||||
var currentStepsPerMeasure:Int = lastTimeChange.timeSignatureNum * Constants.STEPS_PER_BEAT;
|
||||
resultMs += (currentTimeChangeMeasureTime - getTimeInMeasures(lastTimeChange.timeStamp)) * currentStepLengthMs * currentStepsPerMeasure;
|
||||
lastTimeChange = timeChange;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
var remainingStepLengthMs:Float = (((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) * (4 / lastTimeChange.timeSignatureDen)) / Constants.STEPS_PER_BEAT;
|
||||
var remainingStepsPerMeasure:Int = lastTimeChange.timeSignatureNum * Constants.STEPS_PER_BEAT;
|
||||
var remainingFractionalMeasure:Float = (measureTime - getTimeInMeasures(lastTimeChange.timeStamp)) * remainingStepLengthMs * remainingStepsPerMeasure;
|
||||
resultMs += remainingFractionalMeasure;
|
||||
|
||||
return resultMs;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a time in milliseconds, return a time in steps.
|
||||
* @param ms The time in milliseconds.
|
||||
@@ -598,23 +709,28 @@ class Conductor
|
||||
else
|
||||
{
|
||||
var resultStep:Float = 0;
|
||||
ms = ms < 0 ? 0 : ms;
|
||||
|
||||
var lastTimeChange:SongTimeChange = timeChanges[0];
|
||||
var i:Int = -1;
|
||||
for (timeChange in timeChanges)
|
||||
{
|
||||
if (ms >= timeChange.timeStamp)
|
||||
{
|
||||
lastTimeChange = timeChange;
|
||||
resultStep = lastTimeChange.beatTime * Constants.STEPS_PER_BEAT;
|
||||
}
|
||||
else
|
||||
// We do NOT want to add the current time change's STEP LENGTH to the result, same for if we're inside the song's last time change.
|
||||
if (ms < timeChange.timeStamp || i == timeChanges.length - 1)
|
||||
{
|
||||
// This time change is after the requested time.
|
||||
// However, we still want this for the ending calculation.
|
||||
lastTimeChange = timeChange;
|
||||
break;
|
||||
}
|
||||
resultStep += (timeChange.beatTime - lastTimeChange.beatTime) * Constants.STEPS_PER_BEAT;
|
||||
lastTimeChange = timeChange;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
var lastStepLengthMs:Float = ((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) / timeSignatureNumerator;
|
||||
var lastStepLengthMs:Float = (((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) * (4 / lastTimeChange.timeSignatureDen)) / Constants.STEPS_PER_BEAT;
|
||||
var resultFractionalStep:Float = (ms - lastTimeChange.timeStamp) / lastStepLengthMs;
|
||||
resultStep += resultFractionalStep;
|
||||
|
||||
@@ -637,23 +753,28 @@ class Conductor
|
||||
else
|
||||
{
|
||||
var resultMs:Float = 0;
|
||||
stepTime = stepTime < 0 ? 0 : stepTime;
|
||||
|
||||
var lastTimeChange:SongTimeChange = timeChanges[0];
|
||||
var i:Int = -1;
|
||||
for (timeChange in timeChanges)
|
||||
{
|
||||
if (stepTime >= timeChange.beatTime * Constants.STEPS_PER_BEAT)
|
||||
{
|
||||
lastTimeChange = timeChange;
|
||||
resultMs = lastTimeChange.timeStamp;
|
||||
}
|
||||
else
|
||||
// We do NOT want to add the current time change's TIME LENGTH to the result, same for if we're inside the song's last time change.
|
||||
if (stepTime < (timeChange.beatTime * Constants.STEPS_PER_BEAT) || i == timeChanges.length - 1)
|
||||
{
|
||||
// This time change is after the requested time.
|
||||
// However, we still want this for the ending calculation.
|
||||
lastTimeChange = timeChange;
|
||||
break;
|
||||
}
|
||||
resultMs += timeChange.timeStamp - lastTimeChange.timeStamp;
|
||||
lastTimeChange = timeChange;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
var lastStepLengthMs:Float = ((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) / timeSignatureNumerator;
|
||||
var lastStepLengthMs:Float = (((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) * (4 / lastTimeChange.timeSignatureDen)) / Constants.STEPS_PER_BEAT;
|
||||
resultMs += (stepTime - lastTimeChange.beatTime * Constants.STEPS_PER_BEAT) * lastStepLengthMs;
|
||||
|
||||
return resultMs;
|
||||
@@ -691,13 +812,69 @@ class Conductor
|
||||
}
|
||||
}
|
||||
|
||||
var lastStepLengthMs:Float = ((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) / timeSignatureNumerator;
|
||||
var lastStepLengthMs:Float = (((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) * (4 / lastTimeChange.timeSignatureDen)) / Constants.STEPS_PER_BEAT;
|
||||
resultMs += (beatTime - lastTimeChange.beatTime) * lastStepLengthMs * Constants.STEPS_PER_BEAT;
|
||||
|
||||
return resultMs;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a time in milliseconds, return the time change that time is inside of.
|
||||
* @param ms The time in milliseconds.
|
||||
* @return The resulting time change.
|
||||
*/
|
||||
public function getTimeChange(ms:Float):SongTimeChange
|
||||
{
|
||||
if (timeChanges.length == 0)
|
||||
{
|
||||
return new SongTimeChange(0, 100);
|
||||
}
|
||||
var i:Int = 0;
|
||||
ms = ms < 0 ? 0 : ms;
|
||||
for (timeChange in timeChanges)
|
||||
{
|
||||
i++;
|
||||
if ((i == timeChanges.length && ms >= timeChange.timeStamp) || (ms >= timeChange.timeStamp && ms < timeChanges[i].timeStamp))
|
||||
{
|
||||
return timeChange;
|
||||
}
|
||||
}
|
||||
return new SongTimeChange(0, 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* An all-in-one function for getting either a step, beat, or measure's length in milliseconds from a given time change.
|
||||
* @param ms The time in milliseconds. The time change is determined by this.
|
||||
* @param type The type of length to return. Either "step", "beat", or "measure" works, along with their first character.
|
||||
* @return The length of a step/beat/measure in milliseconds.
|
||||
*/
|
||||
public function getTypeLengthAtMs(ms:Float, type:String = "beat"):Float
|
||||
{
|
||||
if (timeChanges.length == 0) return 0;
|
||||
var wantedTimeChange:SongTimeChange = timeChanges[0];
|
||||
for (timeChange in timeChanges)
|
||||
{
|
||||
if (ms >= timeChange.timeStamp)
|
||||
{
|
||||
wantedTimeChange = timeChange;
|
||||
}
|
||||
else
|
||||
{
|
||||
// This time change is after the requested time.
|
||||
break;
|
||||
}
|
||||
}
|
||||
var wantedBeatLengthMs:Float = ((Constants.SECS_PER_MIN / wantedTimeChange.bpm) * Constants.MS_PER_SEC) * (4 / wantedTimeChange.timeSignatureDen);
|
||||
return switch (type.toLowerCase())
|
||||
{
|
||||
case "measure", "m": wantedBeatLengthMs * wantedTimeChange.timeSignatureNum;
|
||||
case "beat", "b": wantedBeatLengthMs;
|
||||
case "step", "s": wantedBeatLengthMs / Constants.STEPS_PER_BEAT;
|
||||
default: wantedBeatLengthMs;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds Conductor fields to the Flixel debugger variable display.
|
||||
* @param conductorToUse The conductor to use. Defaults to `Conductor.instance`.
|
||||
@@ -712,4 +889,9 @@ class Conductor
|
||||
FlxG.watch.addQuick('currentBeatTime', target.currentBeatTime);
|
||||
FlxG.watch.addQuick('currentStepTime', target.currentStepTime);
|
||||
}
|
||||
|
||||
static function log(message:String):Void
|
||||
{
|
||||
trace(' CONDUCTOR '.bg_purple().bold() + ' ${message}');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import lime.app.Promise;
|
||||
|
||||
/**
|
||||
* Handles caching of textures and sounds for the game.
|
||||
* TODO: Remove this once Eric finishes the memory system.
|
||||
* I did this hello, this can be improved later on and I have ideas on how, but for now this functions well enough. -Zack
|
||||
*/
|
||||
@:nullSafety
|
||||
class FunkinMemory
|
||||
@@ -21,7 +21,6 @@ class FunkinMemory
|
||||
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> = [];
|
||||
@@ -97,6 +96,7 @@ class FunkinMemory
|
||||
|
||||
/**
|
||||
* Clears the current texture and sound caches.
|
||||
* @param callGarbageCollector Whether to call the system's garbage collector after purging.
|
||||
*/
|
||||
public static inline function purgeCache(callGarbageCollector:Bool = false):Void
|
||||
{
|
||||
@@ -135,7 +135,7 @@ class FunkinMemory
|
||||
return;
|
||||
}
|
||||
|
||||
trace('Successfully cached graphic: $key');
|
||||
log('Cached asset $key');
|
||||
graphic.persist = true;
|
||||
currentCachedTextures.set(key, graphic);
|
||||
forceRender(graphic);
|
||||
@@ -156,37 +156,13 @@ class FunkinMemory
|
||||
return;
|
||||
}
|
||||
|
||||
trace('Successfully cached graphic: $key');
|
||||
log('Cached graphic $key');
|
||||
graphic.persist = true;
|
||||
permanentCachedTextures.set(key, graphic);
|
||||
forceRender(graphic);
|
||||
currentCachedTextures = permanentCachedTextures;
|
||||
currentCachedTextures = permanentCachedTextures.copy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces the GPU to load and upload a FlxGraphic.
|
||||
*/
|
||||
private static function forceRender(graphic:FlxGraphic):Void
|
||||
{
|
||||
if (graphic == null) return;
|
||||
|
||||
var bmp:Null<FlxGraphic> = FlxG.bitmap.get(graphic.key);
|
||||
if (bmp != null && bmp.bitmap != null) var _:Int = bmp.bitmap.width; // Trigger
|
||||
|
||||
// Draws sprite and actually caches it.
|
||||
var sprite = new flixel.FlxSprite();
|
||||
sprite.loadGraphic(graphic);
|
||||
sprite.draw(); // Draw sprite and load it into game's memory.
|
||||
graphic.bitmap?.getTexture(FlxG.stage.context3D); // Just in case that didn't work...
|
||||
sprite.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks, if graphic with given path cached in memory.
|
||||
*/
|
||||
public static inline function isGraphicCached(path:String):Bool
|
||||
return permanentCachedTextures.exists(path) || currentCachedTextures.exists(path) || previousCachedTextures.exists(path);
|
||||
|
||||
public static function getCachedGraphic(path:String):Null<FlxGraphic>
|
||||
{
|
||||
if (permanentCachedTextures.exists(path)) return permanentCachedTextures.get(path);
|
||||
@@ -201,7 +177,7 @@ class FunkinMemory
|
||||
*/
|
||||
public inline static function preparePurgeTextureCache():Void
|
||||
{
|
||||
previousCachedTextures = currentCachedTextures;
|
||||
previousCachedTextures = currentCachedTextures.copy();
|
||||
|
||||
for (graphicKey in previousCachedTextures.keys())
|
||||
{
|
||||
@@ -211,7 +187,7 @@ class FunkinMemory
|
||||
}
|
||||
}
|
||||
|
||||
currentCachedTextures = permanentCachedTextures;
|
||||
currentCachedTextures = permanentCachedTextures.copy();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -233,6 +209,7 @@ class FunkinMemory
|
||||
if (graphic != null)
|
||||
{
|
||||
FlxG.bitmap.remove(graphic);
|
||||
graphic.persist = false;
|
||||
graphic.destroy();
|
||||
previousCachedTextures.remove(graphicKey);
|
||||
Assets.cache.clear(graphicKey);
|
||||
@@ -250,7 +227,7 @@ class FunkinMemory
|
||||
{
|
||||
var obj:Null<FlxGraphic> = FlxG.bitmap.get(key);
|
||||
|
||||
if (obj == null || obj.persist || permanentCachedTextures.exists(key) || key.contains("fonts"))
|
||||
if (obj == null || (obj.persist && permanentCachedTextures.exists(key)) || key.contains("fonts"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -262,6 +239,7 @@ class FunkinMemory
|
||||
if (key.contains(purgeEntry))
|
||||
{
|
||||
FlxG.bitmap.removeKey(key);
|
||||
obj.persist = false;
|
||||
obj.destroy();
|
||||
}
|
||||
}
|
||||
@@ -269,8 +247,42 @@ class FunkinMemory
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces the GPU to load and upload a FlxGraphic.
|
||||
* @param graphic The graphic to force render.
|
||||
*/
|
||||
private static function forceRender(graphic:FlxGraphic):Void
|
||||
{
|
||||
if (graphic == null) return;
|
||||
|
||||
var bmp:Null<FlxGraphic> = FlxG.bitmap.get(graphic.key);
|
||||
if (bmp != null && bmp.bitmap != null) var _:Int = bmp.bitmap.width; // Trigger
|
||||
|
||||
// Draws sprite and actually caches it.
|
||||
var sprite = new flixel.FlxSprite();
|
||||
sprite.loadGraphic(graphic);
|
||||
sprite.draw(); // Draw sprite and load it into game's memory.
|
||||
graphic.bitmap?.getTexture(FlxG.stage.context3D); // Just in case that didn't work...
|
||||
sprite.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the texture with the given key is cached.
|
||||
* @param key The key of the texture to check.
|
||||
* @return Whether the texture is cached.
|
||||
*/
|
||||
public static function isTextureCached(key:String):Bool
|
||||
{
|
||||
return FlxG.bitmap.get(key) != null
|
||||
&& (permanentCachedTextures.exists(key) || currentCachedTextures.exists(key) || previousCachedTextures.exists(key));
|
||||
}
|
||||
|
||||
///// NOTE STYLE //////
|
||||
|
||||
/**
|
||||
* Caches all assets for the given note style.
|
||||
* @param style The note style to cache.
|
||||
*/
|
||||
public static function cacheNoteStyle(style:NoteStyle):Void
|
||||
{
|
||||
// TODO: Texture paths should fall back to the default values.
|
||||
@@ -313,6 +325,10 @@ class FunkinMemory
|
||||
|
||||
///// SOUND //////
|
||||
|
||||
/**
|
||||
* Caches a sound with the given key.
|
||||
* @param key The key of the sound to cache.
|
||||
*/
|
||||
public static function cacheSound(key:String):Void
|
||||
{
|
||||
if (currentCachedSounds.exists(key)) return;
|
||||
@@ -332,6 +348,10 @@ class FunkinMemory
|
||||
currentCachedSounds.set(key, sound);
|
||||
}
|
||||
|
||||
/**
|
||||
* Permanently caches a sound with the given key.
|
||||
* @param key The key of the sound to cache.
|
||||
*/
|
||||
public static function permanentCacheSound(key:String):Void
|
||||
{
|
||||
if (permanentCachedSounds.exists(key)) return;
|
||||
@@ -344,9 +364,12 @@ class FunkinMemory
|
||||
if (sound != null) currentCachedSounds.set(key, sound);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the cache for purging unused sounds.
|
||||
*/
|
||||
public static function preparePurgeSoundCache():Void
|
||||
{
|
||||
previousCachedSounds = currentCachedSounds;
|
||||
previousCachedSounds = currentCachedSounds.copy();
|
||||
|
||||
for (key in previousCachedSounds.keys())
|
||||
{
|
||||
@@ -356,7 +379,7 @@ class FunkinMemory
|
||||
}
|
||||
}
|
||||
|
||||
currentCachedSounds = permanentCachedSounds;
|
||||
currentCachedSounds = permanentCachedSounds.copy();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -393,6 +416,9 @@ class FunkinMemory
|
||||
|
||||
///// MISC /////
|
||||
|
||||
/**
|
||||
* Clears all Freeplay assets from memory.
|
||||
*/
|
||||
public static inline function clearFreeplay():Void
|
||||
{
|
||||
var keysToRemove:Array<String> = [];
|
||||
@@ -409,7 +435,7 @@ class FunkinMemory
|
||||
@:privateAccess
|
||||
for (key in keysToRemove)
|
||||
{
|
||||
trace('Cleaning up $key');
|
||||
log('Cleaning asset $key');
|
||||
var obj:Null<FlxGraphic> = FlxG.bitmap.get(key);
|
||||
if (obj != null)
|
||||
{
|
||||
@@ -424,6 +450,9 @@ class FunkinMemory
|
||||
purgeSoundCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all sticker assets from memory.
|
||||
*/
|
||||
public static inline function clearStickers():Void
|
||||
{
|
||||
var keysToRemove:Array<String> = [];
|
||||
@@ -440,7 +469,7 @@ class FunkinMemory
|
||||
@:privateAccess
|
||||
for (key in keysToRemove)
|
||||
{
|
||||
trace('Cleaning up $key');
|
||||
log('Cleaning asset $key');
|
||||
var obj:Null<FlxGraphic> = FlxG.bitmap.get(key);
|
||||
if (obj != null)
|
||||
{
|
||||
@@ -451,4 +480,13 @@ class FunkinMemory
|
||||
Assets.cache.clear(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a trace with fancy ANSI colors.
|
||||
* @param message The message to log.
|
||||
*/
|
||||
private static function log(message:String):Void
|
||||
{
|
||||
trace(' MEMORY '.bg_bright_lilac().bold() + ' ${message}');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,8 +49,7 @@ abstract Tallies(RawTallies)
|
||||
{
|
||||
public function new()
|
||||
{
|
||||
this =
|
||||
{
|
||||
this = {
|
||||
combo: 0,
|
||||
missed: 0,
|
||||
shit: 0,
|
||||
|
||||
+136
-36
@@ -20,6 +20,7 @@ import funkin.data.freeplay.style.FreeplayStyleRegistry;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.data.stickers.StickerRegistry;
|
||||
import funkin.play.event.SongEventHelper;
|
||||
import funkin.data.event.SongEventRegistry;
|
||||
import funkin.data.stage.StageRegistry;
|
||||
import funkin.data.story.level.LevelRegistry;
|
||||
@@ -37,6 +38,7 @@ import funkin.util.macro.MacroUtil;
|
||||
import funkin.util.TrackerUtil;
|
||||
import funkin.util.WindowUtil;
|
||||
import openfl.display.BitmapData;
|
||||
import funkin.ui.debug.playtest.ChartPlaytestMenu;
|
||||
#if FEATURE_DISCORD_RPC
|
||||
import funkin.api.discord.DiscordClient;
|
||||
#end
|
||||
@@ -118,6 +120,11 @@ class InitState extends FlxState
|
||||
funkin.mobile.util.InAppReviewUtil.init();
|
||||
#end
|
||||
|
||||
#if FEATURE_MOBILE_WEBVIEW
|
||||
// Setup WebView
|
||||
funkin.mobile.util.WebViewUtil.init();
|
||||
#end
|
||||
|
||||
#if android
|
||||
// Setup Callback util.
|
||||
funkin.external.android.CallbackUtil.init();
|
||||
@@ -128,6 +135,13 @@ class InitState extends FlxState
|
||||
funkin.external.apple.AudioSession.initialize();
|
||||
#end
|
||||
|
||||
#if mobile
|
||||
// Setup Mobile FNFC launcher.
|
||||
funkin.mobile.util.FNFCProvider.init();
|
||||
#end
|
||||
|
||||
SongEventHelper.generateEaseGraphsBitmaps();
|
||||
|
||||
// This ain't a pixel art game! (most of the time)
|
||||
FlxSprite.defaultAntialiasing = true;
|
||||
|
||||
@@ -147,6 +161,11 @@ class InitState extends FlxState
|
||||
// Set the game to a lower frame rate while it is in the background.
|
||||
FlxG.game.focusLostFramerate = 30;
|
||||
|
||||
// Persist controls inputs between states.
|
||||
// Without this, the game would release your keybinds when you switch states,
|
||||
// and then act like you released and re-pressed them the frame after.
|
||||
FlxG.inputs.resetOnStateSwitch = false;
|
||||
|
||||
// Makes Flixel use frame times instead of locked movements per frame for things like tweens
|
||||
FlxG.fixedTimestep = false;
|
||||
|
||||
@@ -169,7 +188,8 @@ class InitState extends FlxState
|
||||
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));
|
||||
|
||||
FlxG.signals.gameResized.add(function(width:Int, height:Int) {
|
||||
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,
|
||||
@@ -180,10 +200,12 @@ class InitState extends FlxState
|
||||
// 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() {
|
||||
FlxG.signals.focusLost.add(function()
|
||||
{
|
||||
WindowUtil.setVSyncMode(lime.ui.WindowVSyncMode.OFF);
|
||||
});
|
||||
FlxG.signals.focusGained.add(function() {
|
||||
FlxG.signals.focusGained.add(function()
|
||||
{
|
||||
WindowUtil.setVSyncMode(lime.ui.WindowVSyncMode.OFF);
|
||||
});
|
||||
#end
|
||||
@@ -201,11 +223,16 @@ class InitState extends FlxState
|
||||
#if FEATURE_DISCORD_RPC
|
||||
DiscordClient.instance.init();
|
||||
|
||||
lime.app.Application.current.onExit.add(function(exitCode) {
|
||||
lime.app.Application.current.onExit.add(function(exitCode)
|
||||
{
|
||||
DiscordClient.instance.shutdown();
|
||||
});
|
||||
#end
|
||||
|
||||
#if FEATURE_LOST_FOCUS_VOLUME
|
||||
FlxG.signals.focusLost.add(onLostFocus);
|
||||
FlxG.signals.focusGained.add(onGainFocus);
|
||||
#end
|
||||
//
|
||||
// ANDROID SETUP
|
||||
//
|
||||
@@ -267,7 +294,7 @@ class InitState extends FlxState
|
||||
// Move it to use a BaseRegistry.
|
||||
CharacterDataParser.loadCharacterCache();
|
||||
|
||||
NoteKindManager.loadScripts();
|
||||
NoteKindManager.initialize();
|
||||
|
||||
ModuleHandler.buildModuleCallbacks();
|
||||
ModuleHandler.loadModuleCache();
|
||||
@@ -281,6 +308,38 @@ class InitState extends FlxState
|
||||
#end
|
||||
}
|
||||
|
||||
#if FEATURE_LOST_FOCUS_VOLUME
|
||||
@:noCompletion var _lastFocusVolume:Null<Float>;
|
||||
|
||||
function onLostFocus():Void
|
||||
{
|
||||
if (FlxG.sound.muted || FlxG.sound.volume == 0 || FlxG.autoPause) return;
|
||||
_lastFocusVolume = FlxG.sound.volume;
|
||||
FlxG.sound.volume *= Constants.LOST_FOCUS_VOLUME_MULTIPLIER;
|
||||
}
|
||||
#end
|
||||
|
||||
function onGainFocus():Void
|
||||
{
|
||||
#if !mobile
|
||||
if (Preferences.unlockedFramerate)
|
||||
{
|
||||
FlxG.updateFramerate = 0;
|
||||
FlxG.drawFramerate = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
FlxG.updateFramerate = Preferences.framerate;
|
||||
FlxG.drawFramerate = Preferences.framerate;
|
||||
}
|
||||
#end
|
||||
|
||||
#if FEATURE_LOST_FOCUS_VOLUME
|
||||
if (FlxG.sound.muted || FlxG.autoPause) return;
|
||||
if (_lastFocusVolume != null) FlxG.sound.volume = _lastFocusVolume;
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the game.
|
||||
*
|
||||
@@ -322,19 +381,16 @@ class InitState extends FlxState
|
||||
FlxG.switchState(() -> new funkin.ui.debug.stage.StageBuilderState());
|
||||
#elseif RESULTS
|
||||
// -DRESULTS
|
||||
FlxG.switchState(() -> new funkin.play.ResultState(
|
||||
{
|
||||
FlxG.switchState(() -> new funkin.play.ResultState({
|
||||
storyMode: true,
|
||||
title: "Cum Song Erect by Kawai Sprite",
|
||||
songId: "cum",
|
||||
characterId: "pico",
|
||||
difficultyId: "hard",
|
||||
isNewHighscore: true,
|
||||
scoreData:
|
||||
{
|
||||
scoreData: {
|
||||
score: 1_234_567,
|
||||
tallies:
|
||||
{
|
||||
tallies: {
|
||||
sick: 130,
|
||||
good: 60,
|
||||
bad: 69,
|
||||
@@ -370,27 +426,67 @@ class InitState extends FlxState
|
||||
function startGameNormally():Void
|
||||
{
|
||||
var params:CLIParams = CLIUtil.processArgs();
|
||||
trace('Command line args: ${params}');
|
||||
|
||||
if (params.chart.shouldLoadChart)
|
||||
{
|
||||
FlxG.switchState(() -> new ChartEditorState(
|
||||
{
|
||||
#if FEATURE_CHART_EDITOR
|
||||
FlxG.switchState(() -> new ChartEditorState({
|
||||
fnfcTargetPath: params.chart.chartPath,
|
||||
}));
|
||||
#else
|
||||
FlxG.switchState(() -> new TitleState());
|
||||
#end
|
||||
}
|
||||
else if (params.stage.shouldLoadStage)
|
||||
{
|
||||
FlxG.switchState(() -> new StageEditorState(
|
||||
{
|
||||
#if FEATURE_STAGE_EDITOR
|
||||
FlxG.switchState(() -> new StageEditorState({
|
||||
fnfsTargetPath: params.stage.stagePath,
|
||||
}));
|
||||
#else
|
||||
FlxG.switchState(() -> new TitleState());
|
||||
#end
|
||||
}
|
||||
else if (params.song.shouldLoadSong && params.song.songPath != null)
|
||||
{
|
||||
#if sys
|
||||
FlxG.switchState(() -> new ChartPlaytestMenu(params.song.songPath));
|
||||
#else
|
||||
FlxG.switchState(() -> new TitleState());
|
||||
#end
|
||||
}
|
||||
else
|
||||
{
|
||||
// FlxG.sound.cache(Paths.music('freakyMenu/freakyMenu'));
|
||||
#if mobile
|
||||
funkin.mobile.util.FNFCProvider.onFNFCOpen.add(function(fnfcFile:String)
|
||||
{
|
||||
flixel.tweens.FlxTween.globalManager.clear();
|
||||
flixel.util.FlxTimer.globalManager.clear();
|
||||
@:nullSafety(Off)
|
||||
if (FlxG.sound.music != null)
|
||||
{
|
||||
FlxG.sound.music.destroy();
|
||||
FlxG.sound.music = null;
|
||||
}
|
||||
|
||||
FlxG.switchState(() -> new ChartPlaytestMenu(fnfcFile));
|
||||
});
|
||||
|
||||
final fnfcFile = funkin.mobile.util.FNFCProvider.queryFNFC();
|
||||
if (fnfcFile != null)
|
||||
{
|
||||
trace('launching FNFC from $fnfcFile');
|
||||
FlxG.switchState(() -> new ChartPlaytestMenu(fnfcFile));
|
||||
}
|
||||
else
|
||||
{
|
||||
FlxG.switchState(() -> new TitleState());
|
||||
}
|
||||
#else
|
||||
FlxG.switchState(() -> new TitleState());
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -438,8 +534,7 @@ class InitState extends FlxState
|
||||
}
|
||||
|
||||
@:nullSafety(Off) // Cannot unify?
|
||||
LoadingState.loadPlayState(
|
||||
{
|
||||
LoadingState.loadPlayState({
|
||||
targetSong: songData,
|
||||
targetDifficulty: difficultyId,
|
||||
});
|
||||
@@ -481,8 +576,7 @@ class InitState extends FlxState
|
||||
}
|
||||
|
||||
@:nullSafety(Off)
|
||||
LoadingState.loadPlayState(
|
||||
{
|
||||
LoadingState.loadPlayState({
|
||||
targetSong: targetSong,
|
||||
targetDifficulty: difficultyId,
|
||||
});
|
||||
@@ -494,7 +588,7 @@ class InitState extends FlxState
|
||||
//
|
||||
// FLIXEL DEBUG SETUP
|
||||
//
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
#if (FEATURE_DEBUG_FUNCTIONS && !FLX_NO_DEBUG)
|
||||
trace('Initializing Flixel debugger...');
|
||||
|
||||
#if !debug
|
||||
@@ -510,23 +604,11 @@ class InitState extends FlxState
|
||||
// Disable using ~ to open the console (we use that for the Editor menu)
|
||||
FlxG.debugger.toggleKeys = [F2];
|
||||
TrackerUtil.initTrackers();
|
||||
// Adds an additional Close Debugger button.
|
||||
// This big obnoxious white button is for MOBILE, so that you can press it
|
||||
// easily with your finger when debug bullshit pops up during testing lol!
|
||||
FlxG.debugger.addButton(LEFT, new BitmapData(200, 200), function() {
|
||||
FlxG.debugger.visible = false;
|
||||
|
||||
// Make errors and warnings less annoying.
|
||||
// Forcing this always since I have never been happy to have the debugger to pop up
|
||||
LogStyle.ERROR.openConsole = false;
|
||||
LogStyle.ERROR.errorSound = null;
|
||||
LogStyle.WARNING.openConsole = false;
|
||||
LogStyle.WARNING.errorSound = null;
|
||||
});
|
||||
|
||||
// Adds a red button to the debugger.
|
||||
// This pauses the game AND the music! This ensures the Conductor stops.
|
||||
FlxG.debugger.addButton(CENTER, new BitmapData(20, 20, true, 0xFFCC2233), function() {
|
||||
FlxG.debugger.addButton(CENTER, new BitmapData(20, 20, true, 0xFFCC2233), function()
|
||||
{
|
||||
if (FlxG.vcr.paused)
|
||||
{
|
||||
FlxG.vcr.resume();
|
||||
@@ -553,7 +635,8 @@ class InitState extends FlxState
|
||||
|
||||
// Adds a blue button to the debugger.
|
||||
// This skips forward in the song.
|
||||
FlxG.debugger.addButton(CENTER, new BitmapData(20, 20, true, 0xFF2222CC), function() {
|
||||
FlxG.debugger.addButton(CENTER, new BitmapData(20, 20, true, 0xFF2222CC), function()
|
||||
{
|
||||
FlxG.game.debugger.vcr.onStep();
|
||||
|
||||
for (snd in FlxG.sound.list)
|
||||
@@ -565,6 +648,23 @@ class InitState extends FlxState
|
||||
FlxG.sound.music.pause();
|
||||
FlxG.sound.music.time += FlxG.elapsed * 1000;
|
||||
});
|
||||
|
||||
// Adds an additional Close Debugger button.
|
||||
// This big obnoxious white button is for MOBILE, so that you can press it
|
||||
// easily with your finger when debug bullshit pops up during testing lol!
|
||||
#if mobile
|
||||
FlxG.debugger.addButton(LEFT, new BitmapData(200, 200), function()
|
||||
{
|
||||
FlxG.debugger.visible = false;
|
||||
|
||||
// Make errors and warnings less annoying.
|
||||
// Forcing this always since I have never been happy to have the debugger to pop up
|
||||
LogStyle.ERROR.openConsole = false;
|
||||
LogStyle.ERROR.errorSound = null;
|
||||
LogStyle.WARNING.openConsole = false;
|
||||
LogStyle.WARNING.errorSound = null;
|
||||
});
|
||||
#end // mobile big butotn crap
|
||||
#end
|
||||
}
|
||||
|
||||
|
||||
+59
-1
@@ -1,13 +1,17 @@
|
||||
package funkin;
|
||||
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import animate.FlxAnimateFrames;
|
||||
import funkin.graphics.FunkinSprite.AtlasSpriteSettings;
|
||||
import openfl.utils.AssetType;
|
||||
import funkin.util.macro.ConsoleMacro;
|
||||
import haxe.io.Path;
|
||||
|
||||
/**
|
||||
* A core class which handles determining asset paths.
|
||||
*/
|
||||
@:nullSafety
|
||||
class Paths
|
||||
class Paths implements ConsoleClass
|
||||
{
|
||||
static var currentLevel:Null<String> = null;
|
||||
|
||||
@@ -103,6 +107,11 @@ class Paths
|
||||
return getPath('data/$key.json', TEXT, library);
|
||||
}
|
||||
|
||||
public static function srt(key:String, ?library:String, ?directory:String = "data/"):String
|
||||
{
|
||||
return getPath('$directory$key.srt', TEXT, library);
|
||||
}
|
||||
|
||||
public static function sound(key:String, ?library:String):String
|
||||
{
|
||||
return getPath('sounds/$key.${Constants.EXT_SOUND}', SOUND, library);
|
||||
@@ -120,6 +129,13 @@ class Paths
|
||||
|
||||
public static function videos(key:String, ?library:String):String
|
||||
{
|
||||
final path:Path = new Path(key);
|
||||
|
||||
if (path.ext != null)
|
||||
{
|
||||
return getPath('videos/${path.file}.${path.ext}', BINARY, library ?? 'videos');
|
||||
}
|
||||
|
||||
return getPath('videos/$key.${Constants.EXT_VIDEO}', BINARY, library ?? 'videos');
|
||||
}
|
||||
|
||||
@@ -163,6 +179,48 @@ class Paths
|
||||
return FlxAtlasFrames.fromSparrow(image(key, library), file('images/$key.xml', library));
|
||||
}
|
||||
|
||||
public static function getAnimateAtlas(key:String, ?library:String, settings:AtlasSpriteSettings):FlxAnimateFrames
|
||||
{
|
||||
var assetLibrary:String = library ?? "";
|
||||
var graphicKey:String = "";
|
||||
|
||||
if (assetLibrary != "")
|
||||
{
|
||||
graphicKey = Paths.animateAtlas(key, assetLibrary);
|
||||
}
|
||||
else
|
||||
{
|
||||
graphicKey = Paths.animateAtlas(key);
|
||||
}
|
||||
|
||||
var validatedSettings:AtlasSpriteSettings = {
|
||||
swfMode: settings?.swfMode ?? false,
|
||||
cacheOnLoad: settings?.cacheOnLoad ?? false,
|
||||
filterQuality: settings?.filterQuality ?? MEDIUM,
|
||||
spritemaps: settings?.spritemaps ?? null,
|
||||
metadataJson: settings?.metadataJson ?? null,
|
||||
cacheKey: settings?.cacheKey ?? null,
|
||||
uniqueInCache: settings?.uniqueInCache ?? false,
|
||||
onSymbolCreate: settings?.onSymbolCreate ?? null,
|
||||
applyStageMatrix: settings?.applyStageMatrix ?? false,
|
||||
useRenderTexture: settings?.useRenderTexture ?? false
|
||||
};
|
||||
|
||||
// Validate asset path.
|
||||
if (!Assets.exists('${graphicKey}/Animation.json'))
|
||||
{
|
||||
throw 'No Animation.json file exists at the specified path (${graphicKey})';
|
||||
}
|
||||
|
||||
return FlxAnimateFrames.fromAnimate(graphicKey, validatedSettings.spritemaps, validatedSettings.metadataJson, validatedSettings.cacheKey,
|
||||
validatedSettings.uniqueInCache, {
|
||||
swfMode: validatedSettings.swfMode,
|
||||
cacheOnLoad: validatedSettings.cacheOnLoad,
|
||||
filterQuality: validatedSettings.filterQuality,
|
||||
onSymbolCreate: validatedSettings.onSymbolCreate
|
||||
});
|
||||
}
|
||||
|
||||
public static function getPackerAtlas(key:String, ?library:String):FlxAtlasFrames
|
||||
{
|
||||
return FlxAtlasFrames.fromSpriteSheetPacker(image(key, library), file('images/$key.txt', library));
|
||||
|
||||
@@ -98,7 +98,7 @@ class PlayerSettings
|
||||
*/
|
||||
function new(id:Int)
|
||||
{
|
||||
trace('loading player settings for id: $id');
|
||||
trace('Loading player settings for id: $id');
|
||||
|
||||
this.id = id;
|
||||
this.controls = new Controls('player$id', None);
|
||||
|
||||
+105
-91
@@ -41,12 +41,23 @@ class Preferences
|
||||
{
|
||||
#if web
|
||||
return 60;
|
||||
#elseif mobile
|
||||
var refreshRate:Int = FlxG.stage.window.displayMode.refreshRate;
|
||||
|
||||
if (refreshRate < 60) refreshRate = 60;
|
||||
|
||||
return refreshRate;
|
||||
#else
|
||||
var save:Save = Save.instance;
|
||||
save.options.framerate = value;
|
||||
save.flush();
|
||||
Save.system.flush();
|
||||
|
||||
if (!unlockedFramerate)
|
||||
{
|
||||
FlxG.updateFramerate = value;
|
||||
FlxG.drawFramerate = value;
|
||||
}
|
||||
|
||||
return value;
|
||||
#end
|
||||
}
|
||||
@@ -69,13 +80,13 @@ class Preferences
|
||||
static function set_naughtyness(value:Bool):Bool
|
||||
{
|
||||
#if NO_FEATURE_NAUGHTYNESS
|
||||
value = false;
|
||||
#end
|
||||
|
||||
return false;
|
||||
#else
|
||||
var save:Save = Save.instance;
|
||||
save.options.naughtyness = value;
|
||||
save.flush();
|
||||
Save.system.flush();
|
||||
return value;
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,7 +104,7 @@ class Preferences
|
||||
{
|
||||
var save:Save = Save.instance;
|
||||
save.options.downscroll = value;
|
||||
save.flush();
|
||||
Save.system.flush();
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -112,7 +123,7 @@ class Preferences
|
||||
{
|
||||
var save:Save = Save.instance;
|
||||
save.options.flashingLights = value;
|
||||
save.flush();
|
||||
Save.system.flush();
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -131,58 +142,48 @@ class Preferences
|
||||
{
|
||||
var save:Save = Save.instance;
|
||||
save.options.zoomCamera = value;
|
||||
save.flush();
|
||||
Save.system.flush();
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* If enabled, an FPS and memory counter will be displayed even if this is not a debug build.
|
||||
* Always disabled on mobile.
|
||||
* Always disabled on release mobile builds.
|
||||
* @default `Off`
|
||||
*/
|
||||
public static var debugDisplay(get, set):DebugDisplayMode;
|
||||
|
||||
static function get_debugDisplay():DebugDisplayMode
|
||||
{
|
||||
#if mobile
|
||||
return DebugDisplayMode.OFF;
|
||||
#end
|
||||
var value = Save?.instance?.options?.debugDisplay ?? 'Off';
|
||||
|
||||
return switch (value)
|
||||
#if NO_FEATURE_DEBUG_DISPLAY
|
||||
return DebugDisplayMode.Off;
|
||||
#else
|
||||
#if hl
|
||||
// Account for when debugDisplay used to be a boolean
|
||||
var options:Null<SaveDataOptions> = Save.instance?.options;
|
||||
if (options != null && Std.isOfType(options.debugDisplay, Bool))
|
||||
{
|
||||
case "Simple":
|
||||
DebugDisplayMode.SIMPLE;
|
||||
case "Advanced":
|
||||
DebugDisplayMode.ADVANCED;
|
||||
default:
|
||||
DebugDisplayMode.OFF;
|
||||
};
|
||||
var convertedDebugDisplay = cast(options.debugDisplay, Bool) ? DebugDisplayMode.Simple : DebugDisplayMode.Off;
|
||||
options.debugDisplay = convertedDebugDisplay;
|
||||
Save.system.flush();
|
||||
}
|
||||
#end
|
||||
return Save?.instance?.options?.debugDisplay ?? 'Off';
|
||||
#end
|
||||
}
|
||||
|
||||
static function set_debugDisplay(value:DebugDisplayMode):DebugDisplayMode
|
||||
{
|
||||
var string;
|
||||
|
||||
switch (value)
|
||||
{
|
||||
case DebugDisplayMode.SIMPLE:
|
||||
string = "Simple";
|
||||
case DebugDisplayMode.ADVANCED:
|
||||
string = "Advanced";
|
||||
default:
|
||||
string = "Off";
|
||||
};
|
||||
|
||||
if (string != Save.instance.options.debugDisplay)
|
||||
{
|
||||
setDebugDisplayMode(value);
|
||||
}
|
||||
#if NO_FEATURE_DEBUG_DISPLAY
|
||||
return DebugDisplayMode.Off;
|
||||
#else
|
||||
if (value != Save.instance.options.debugDisplay) setDebugDisplayMode(value);
|
||||
|
||||
var save = Save.instance;
|
||||
save.options.debugDisplay = string;
|
||||
save.flush();
|
||||
save.options.debugDisplay = value;
|
||||
Save.system.flush();
|
||||
return value;
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -202,7 +203,7 @@ class Preferences
|
||||
|
||||
var save:Save = Save.instance;
|
||||
save.options.debugDisplayBGOpacity = value;
|
||||
save.flush();
|
||||
Save.system.flush();
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -243,7 +244,7 @@ class Preferences
|
||||
|
||||
var save:Save = Save.instance;
|
||||
save.options.hapticsMode = string;
|
||||
save.flush();
|
||||
Save.system.flush();
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -262,7 +263,7 @@ class Preferences
|
||||
{
|
||||
var save:Save = Save.instance;
|
||||
save.options.hapticsIntensityMultiplier = value;
|
||||
save.flush();
|
||||
Save.system.flush();
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -276,19 +277,24 @@ class Preferences
|
||||
static function get_autoPause():Bool
|
||||
{
|
||||
#if mobile
|
||||
return true;
|
||||
#end
|
||||
return false;
|
||||
#else
|
||||
return Save?.instance?.options?.autoPause ?? true;
|
||||
#end
|
||||
}
|
||||
|
||||
static function set_autoPause(value:Bool):Bool
|
||||
{
|
||||
#if mobile
|
||||
return false;
|
||||
#else
|
||||
if (value != Save.instance.options.autoPause) FlxG.autoPause = value;
|
||||
|
||||
var save:Save = Save.instance;
|
||||
save.options.autoPause = value;
|
||||
save.flush();
|
||||
Save.system.flush();
|
||||
return value;
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -306,7 +312,7 @@ class Preferences
|
||||
{
|
||||
var save:Save = Save.instance;
|
||||
save.options.autoFullscreen = value;
|
||||
save.flush();
|
||||
Save.system.flush();
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -326,7 +332,7 @@ class Preferences
|
||||
{
|
||||
var save:Save = Save.instance;
|
||||
save.options.globalOffset = value;
|
||||
save.flush();
|
||||
Save.system.flush();
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -338,6 +344,9 @@ class Preferences
|
||||
|
||||
static function get_vsyncMode():lime.ui.WindowVSyncMode
|
||||
{
|
||||
#if (mobile || web)
|
||||
return lime.ui.WindowVSyncMode.OFF;
|
||||
#else
|
||||
var value = Save?.instance?.options?.vsyncMode ?? "Off";
|
||||
|
||||
return switch (value)
|
||||
@@ -351,10 +360,14 @@ class Preferences
|
||||
default:
|
||||
lime.ui.WindowVSyncMode.OFF;
|
||||
};
|
||||
#end
|
||||
}
|
||||
|
||||
static function set_vsyncMode(value:lime.ui.WindowVSyncMode):lime.ui.WindowVSyncMode
|
||||
{
|
||||
#if (mobile || web)
|
||||
return lime.ui.WindowVSyncMode.OFF;
|
||||
#else
|
||||
var string;
|
||||
|
||||
switch (value)
|
||||
@@ -373,51 +386,38 @@ class Preferences
|
||||
|
||||
var save:Save = Save.instance;
|
||||
save.options.vsyncMode = string;
|
||||
save.flush();
|
||||
Save.system.flush();
|
||||
return value;
|
||||
#end
|
||||
}
|
||||
|
||||
public static var unlockedFramerate(get, set):Bool;
|
||||
|
||||
static function get_unlockedFramerate():Bool
|
||||
{
|
||||
#if (mobile || web)
|
||||
return false;
|
||||
#else
|
||||
return Save?.instance?.options?.unlockedFramerate ?? false;
|
||||
#end
|
||||
}
|
||||
|
||||
static function set_unlockedFramerate(value:Bool):Bool
|
||||
{
|
||||
#if (mobile || web)
|
||||
return false;
|
||||
#else
|
||||
if (value != Save.instance.options.unlockedFramerate)
|
||||
{
|
||||
#if web
|
||||
toggleFramerateCap(value);
|
||||
#end
|
||||
}
|
||||
|
||||
var save:Save = Save.instance;
|
||||
save.options.unlockedFramerate = value;
|
||||
save.flush();
|
||||
Save.system.flush();
|
||||
return value;
|
||||
}
|
||||
|
||||
#if web
|
||||
// We create a haxe version of this just for readability.
|
||||
// We use these to override `window.requestAnimationFrame` in Javascript to uncap the framerate / "animation" request rate
|
||||
// Javascript is crazy since u can just do stuff like that lol
|
||||
|
||||
public static function unlockedFramerateFunction(callback, element)
|
||||
{
|
||||
var currTime = Date.now().getTime();
|
||||
var timeToCall = 0;
|
||||
var id = js.Browser.window.setTimeout(function() {
|
||||
callback(currTime + timeToCall);
|
||||
}, timeToCall);
|
||||
return id;
|
||||
}
|
||||
|
||||
// Lime already implements their own little framerate cap, so we can just use that
|
||||
// This also gets set in the init function in Main.hx, since we need to definitely override it
|
||||
public static var lockedFramerateFunction = untyped js.Syntax.code("window.requestAnimationFrame");
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* If >0, the game will display a semi-opaque background under the notes.
|
||||
@@ -435,7 +435,7 @@ class Preferences
|
||||
{
|
||||
var save:Save = Save.instance;
|
||||
save.options.strumlineBackgroundOpacity = value;
|
||||
save.flush();
|
||||
Save.system.flush();
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -454,7 +454,7 @@ class Preferences
|
||||
{
|
||||
var save:Save = Save.instance;
|
||||
save.options.screenshot.shouldHideMouse = value;
|
||||
save.flush();
|
||||
Save.system.flush();
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -473,7 +473,7 @@ class Preferences
|
||||
{
|
||||
var save:Save = Save.instance;
|
||||
save.options.screenshot.fancyPreview = value;
|
||||
save.flush();
|
||||
Save.system.flush();
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -492,7 +492,7 @@ class Preferences
|
||||
{
|
||||
var save:Save = Save.instance;
|
||||
save.options.screenshot.previewOnSave = value;
|
||||
save.flush();
|
||||
Save.system.flush();
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -508,9 +508,7 @@ class Preferences
|
||||
setDebugDisplayMode(Preferences.debugDisplay);
|
||||
setDebugDisplayBGOpacity(Preferences.debugDisplayBGOpacity / 100);
|
||||
|
||||
#if web
|
||||
toggleFramerateCap(Preferences.unlockedFramerate);
|
||||
#end
|
||||
|
||||
#if mobile
|
||||
// Apply the allowScreenTimeout setting.
|
||||
@@ -520,22 +518,19 @@ class Preferences
|
||||
|
||||
static function toggleFramerateCap(unlocked:Bool):Void
|
||||
{
|
||||
#if web
|
||||
var framerateFunction = unlocked ? unlockedFramerateFunction : lockedFramerateFunction;
|
||||
untyped js.Syntax.code("window.requestAnimationFrame = framerateFunction;");
|
||||
#if !(mobile || web)
|
||||
FlxG.drawFramerate = unlocked ? 0 : framerate;
|
||||
FlxG.updateFramerate = unlocked ? 0 : framerate;
|
||||
#end
|
||||
}
|
||||
|
||||
static function setDebugDisplayMode(mode:DebugDisplayMode):Void
|
||||
public static function setDebugDisplayMode(mode:DebugDisplayMode):Void
|
||||
{
|
||||
if (FlxG.game.parent.contains(Main.debugDisplay))
|
||||
{
|
||||
FlxG.game.parent.removeChild(Main.debugDisplay);
|
||||
}
|
||||
if (FlxG.game.parent.contains(Main.debugDisplay)) FlxG.game.parent.removeChild(Main.debugDisplay);
|
||||
|
||||
if (mode == DebugDisplayMode.OFF) return;
|
||||
if (mode == DebugDisplayMode.Off) return;
|
||||
|
||||
Main.debugDisplay.isAdvanced = (mode == DebugDisplayMode.ADVANCED);
|
||||
Main.debugDisplay.isAdvanced = (mode == DebugDisplayMode.Advanced);
|
||||
|
||||
FlxG.game.parent.addChild(Main.debugDisplay);
|
||||
}
|
||||
@@ -547,6 +542,25 @@ class Preferences
|
||||
Main.debugDisplay.backgroundOpacity = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* If enabled, subtitles will appear during some songs and cutscenes.
|
||||
* @default `true`
|
||||
*/
|
||||
public static var subtitles(get, set):Bool;
|
||||
|
||||
static function get_subtitles():Bool
|
||||
{
|
||||
return Save?.instance?.options?.subtitles ?? true;
|
||||
}
|
||||
|
||||
static function set_subtitles(value:Bool):Bool
|
||||
{
|
||||
var save:Save = Save.instance;
|
||||
save.options.subtitles = value;
|
||||
Save.system.flush();
|
||||
return value;
|
||||
}
|
||||
|
||||
#if mobile
|
||||
/**
|
||||
* If enabled, device will be able to sleep on its own.
|
||||
@@ -565,7 +579,7 @@ class Preferences
|
||||
|
||||
var save:Save = Save.instance;
|
||||
save.mobileOptions.screenTimeout = value;
|
||||
save.flush();
|
||||
Save.system.flush();
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -584,7 +598,7 @@ class Preferences
|
||||
{
|
||||
var save:Save = Save.instance;
|
||||
save.mobileOptions.controlsScheme = value;
|
||||
save.flush();
|
||||
Save.system.flush();
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -609,7 +623,7 @@ class Preferences
|
||||
{
|
||||
var save:Save = Save.instance;
|
||||
save.mobileOptions.noAds = value;
|
||||
save.flush();
|
||||
Save.system.flush();
|
||||
return value;
|
||||
}
|
||||
#end
|
||||
|
||||
@@ -29,9 +29,9 @@ class DiscordClient
|
||||
|
||||
private function new()
|
||||
{
|
||||
trace('[DISCORD] Initializing event handlers...');
|
||||
trace(' DISCORD '.bold().bg_blue() + ' Initializing event handlers...');
|
||||
|
||||
handlers = DiscordEventHandlers.create();
|
||||
handlers = new DiscordEventHandlers();
|
||||
|
||||
handlers.ready = cpp.Function.fromStaticFunction(onReady);
|
||||
handlers.disconnected = cpp.Function.fromStaticFunction(onDisconnected);
|
||||
@@ -40,7 +40,7 @@ class DiscordClient
|
||||
|
||||
public function init():Void
|
||||
{
|
||||
trace('[DISCORD] Initializing connection...');
|
||||
trace(' DISCORD '.bold().bg_blue() + ' Initializing connection...');
|
||||
|
||||
if (!hasValidCredentials())
|
||||
{
|
||||
@@ -50,7 +50,7 @@ class DiscordClient
|
||||
|
||||
@:nullSafety(Off)
|
||||
{
|
||||
Discord.Initialize(DISCORD_CLIENT_ID, cpp.RawPointer.addressOf(handlers), 1, "");
|
||||
Discord.Initialize(DISCORD_CLIENT_ID, cpp.RawPointer.addressOf(handlers), false, "");
|
||||
}
|
||||
|
||||
createDaemon();
|
||||
@@ -79,26 +79,21 @@ class DiscordClient
|
||||
Discord.updateConnection();
|
||||
#end
|
||||
|
||||
Discord.runCallbacks();
|
||||
Discord.RunCallbacks();
|
||||
Sys.sleep(2);
|
||||
}
|
||||
}
|
||||
|
||||
public function shutdown():Void
|
||||
{
|
||||
trace('[DISCORD] Shutting down...');
|
||||
trace(' DISCORD '.bold().bg_blue() + ' Shutting down...');
|
||||
|
||||
Discord.shutdown();
|
||||
Discord.Shutdown();
|
||||
}
|
||||
|
||||
public function setPresence(params:DiscordClientPresenceParams):Void
|
||||
{
|
||||
Discord.updatePresence(buildPresence(params));
|
||||
}
|
||||
|
||||
function buildPresence(params:DiscordClientPresenceParams):DiscordRichPresence
|
||||
{
|
||||
var presence = DiscordRichPresence.create();
|
||||
var presence:DiscordRichPresence = new DiscordRichPresence();
|
||||
|
||||
// Presence should always be playing the game.
|
||||
presence.type = DiscordActivityType_Playing;
|
||||
@@ -129,23 +124,23 @@ class DiscordClient
|
||||
// presence.startTimestamp = time - 10;
|
||||
// presence.endTimestamp = time + 30;
|
||||
|
||||
final button1:DiscordButton = DiscordButton.create();
|
||||
final button1:DiscordButton = new DiscordButton();
|
||||
button1.label = "Play on Web";
|
||||
button1.url = Constants.URL_NEWGROUNDS;
|
||||
presence.buttons[0] = button1;
|
||||
|
||||
final button2:DiscordButton = DiscordButton.create();
|
||||
final button2:DiscordButton = new DiscordButton();
|
||||
button2.label = "Download";
|
||||
button2.url = Constants.URL_ITCH;
|
||||
presence.buttons[1] = button2;
|
||||
|
||||
return presence;
|
||||
Discord.UpdatePresence(cpp.RawConstPointer.addressOf(presence));
|
||||
}
|
||||
|
||||
// TODO: WHAT THE FUCK get this pointer bullfuckery out of here
|
||||
private static function onReady(request:cpp.RawConstPointer<DiscordUser>):Void
|
||||
{
|
||||
trace('[DISCORD] Client has connected!');
|
||||
trace(' DISCORD '.bold().bg_blue() + ' Client has connected!');
|
||||
|
||||
final username:String = request[0].username;
|
||||
final globalName:String = request[0].username;
|
||||
@@ -153,22 +148,22 @@ class DiscordClient
|
||||
|
||||
if (discriminator != null && discriminator != 0)
|
||||
{
|
||||
trace('[DISCORD] User: ${username}#${discriminator} (${globalName})');
|
||||
trace(' DISCORD '.bold().bg_blue() + ' User: ${username}#${discriminator} (${globalName})');
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[DISCORD] User: @${username} (${globalName})');
|
||||
trace(' DISCORD '.bold().bg_blue() + ' User: @${username} (${globalName})');
|
||||
}
|
||||
}
|
||||
|
||||
private static function onDisconnected(errorCode:Int, message:cpp.ConstCharStar):Void
|
||||
{
|
||||
trace('[DISCORD] Client has disconnected! ($errorCode) "${cast (message, String)}"');
|
||||
trace(' DISCORD '.bold().bg_blue() + ' Client has disconnected! ($errorCode) "${cast (message, String)}"');
|
||||
}
|
||||
|
||||
private static function onError(errorCode:Int, message:cpp.ConstCharStar):Void
|
||||
{
|
||||
trace('[DISCORD] Client has received an error! ($errorCode) "${cast (message, String)}"');
|
||||
trace(' DISCORD '.bold().bg_blue() + ' Client has received an error! ($errorCode) "${cast (message, String)}"');
|
||||
}
|
||||
|
||||
// public var type(get, set):DiscordActivityType;
|
||||
|
||||
@@ -39,21 +39,21 @@ class Events
|
||||
switch (outcome)
|
||||
{
|
||||
case SUCCESS(data):
|
||||
trace('[NEWGROUNDS] Logged event: ${data.eventName}');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Logged event: ${data.eventName}');
|
||||
case FAIL(outcome):
|
||||
switch (outcome)
|
||||
{
|
||||
case HTTP(error):
|
||||
trace('[NEWGROUNDS] HTTP error while logging event: ${error}');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' HTTP error while logging event: ${error}');
|
||||
case RESPONSE(error):
|
||||
trace('[NEWGROUNDS] Response error (${error.code}) while logging event: ${error.message}');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Response error (${error.code}) while logging event: ${error.message}');
|
||||
case RESULT(error):
|
||||
switch (error.code)
|
||||
{
|
||||
case 103: // Invalid custom event name
|
||||
trace('[NEWGROUNDS] Invalid custom event name: ${eventName}');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Invalid custom event name: ${eventName}');
|
||||
default:
|
||||
trace('[NEWGROUNDS] Result error (${error.code}) while logging event: ${error.message}');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Result error (${error.code}) while logging event: ${error.message}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,22 +2,29 @@ package funkin.api.newgrounds;
|
||||
|
||||
#if FEATURE_NEWGROUNDS
|
||||
import io.newgrounds.Call.CallError;
|
||||
import io.newgrounds.components.ScoreBoardComponent;
|
||||
import io.newgrounds.components.ScoreBoardComponent.Period;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Handles interactions with the leaderboards on the Newgrounds website.
|
||||
*/
|
||||
@:nullSafety
|
||||
class Leaderboards
|
||||
{
|
||||
/**
|
||||
* Retrieve the leaderboard data via the Newgrounds API.
|
||||
* @return The leaderboard data.
|
||||
*/
|
||||
public static function listLeaderboardData():Map<Leaderboard, LeaderboardData>
|
||||
{
|
||||
var leaderboardList:Null<ScoreBoardList> = NewgroundsClient.instance.leaderboards;
|
||||
if (leaderboardList == null)
|
||||
{
|
||||
trace('[NEWGROUNDS] Not logged in, cannot fetch medal data!');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Not logged in, cannot fetch medal data!');
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -33,21 +40,26 @@ class Leaderboards
|
||||
public static function submitScore(leaderboard:Leaderboard, score:Int, ?tag:String):Void
|
||||
{
|
||||
// Silently reject submissions for unknown leaderboards.
|
||||
if (leaderboard == Leaderboard.Unknown) return;
|
||||
if (leaderboard == Leaderboard.Unknown)
|
||||
{
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Unknown leaderboard, skipping score submission...');
|
||||
return;
|
||||
}
|
||||
|
||||
if (NewgroundsClient.instance.isLoggedIn())
|
||||
{
|
||||
var leaderboardData:Null<LeaderboardData> = listLeaderboardData().get(leaderboard.getId());
|
||||
if (leaderboardData != null)
|
||||
{
|
||||
leaderboardData.postScore(score, function(outcome:Outcome<CallError>):Void {
|
||||
leaderboardData.postScore(score, function(outcome:Outcome<CallError>):Void
|
||||
{
|
||||
switch (outcome)
|
||||
{
|
||||
case SUCCESS:
|
||||
trace('[NEWGROUNDS] Submitted score!');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Submitted leaderboard score!');
|
||||
case FAIL(error):
|
||||
trace('[NEWGROUNDS] Failed to submit score!');
|
||||
trace(error);
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' ERROR '.error() + ' Failed to submit leaderboard score!');
|
||||
trace(' ERROR '.error() + error);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -71,16 +83,17 @@ class Leaderboards
|
||||
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 {
|
||||
function(outcome:Outcome<CallError>):Void
|
||||
{
|
||||
switch (outcome)
|
||||
{
|
||||
case SUCCESS:
|
||||
trace('[NEWGROUNDS] Fetched scores!');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Fetched scores!');
|
||||
if (params != null && params.onComplete != null) params.onComplete(leaderboardData.scores);
|
||||
|
||||
case FAIL(error):
|
||||
trace('[NEWGROUNDS] Failed to fetch scores!');
|
||||
trace(error);
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' ERROR '.error() + ' Failed to fetch scores!');
|
||||
trace(' ERROR '.error() + error);
|
||||
if (params != null && params.onFail != null) params.onFail();
|
||||
}
|
||||
});
|
||||
@@ -88,19 +101,29 @@ class Leaderboards
|
||||
|
||||
/**
|
||||
* Submit a score for a Story Level to Newgrounds.
|
||||
*
|
||||
* @param levelId The ID for the story level.
|
||||
* @param difficultyId The current difficulty.
|
||||
* @param score The score to submit.
|
||||
*/
|
||||
public static function submitLevelScore(levelId:String, difficultyId:String, score:Int):Void
|
||||
{
|
||||
var tag = '${difficultyId}';
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + 'Submitting score for level "${levelId}"...');
|
||||
Leaderboards.submitScore(Leaderboard.getLeaderboardByLevel(levelId), score, tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit a score for a song to Newgrounds.
|
||||
*
|
||||
* @param songId The ID for the song.
|
||||
* @param difficultyId The current difficulty.
|
||||
* @param score The score to submit.
|
||||
*/
|
||||
public static function submitSongScore(songId:String, difficultyId:String, score:Int):Void
|
||||
{
|
||||
var tag = '${difficultyId}';
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + 'Submitting score for song "${songId}" (${difficultyId})...');
|
||||
Leaderboards.submitScore(Leaderboard.getLeaderboardBySong(songId, difficultyId), score, tag);
|
||||
}
|
||||
}
|
||||
@@ -111,16 +134,33 @@ class Leaderboards
|
||||
@:nullSafety
|
||||
class LeaderboardsSandboxed
|
||||
{
|
||||
public static function getLeaderboardBySong(songId:String, difficultyId:String)
|
||||
/**
|
||||
* Get the leaderboard for a given song and difficulty.
|
||||
*
|
||||
* @param songId The ID for the song.
|
||||
* @param difficultyId The current difficulty, suffixed with the variation, like `easy-pico` or `nightmare`.
|
||||
* @return The Leaderboard ID for the given level and difficulty.
|
||||
*/
|
||||
public static function getLeaderboardBySong(songId:String, difficultyId:String):Leaderboard
|
||||
{
|
||||
return Leaderboard.getLeaderboardBySong(songId, difficultyId);
|
||||
}
|
||||
|
||||
public static function getLeaderboardByLevel(levelId:String)
|
||||
/**
|
||||
* Get the leaderboard for a given story level and difficulty.
|
||||
* @param levelId The ID for the story level.
|
||||
* @return The Leaderboard ID for the given level and difficulty.
|
||||
*/
|
||||
public static function getLeaderboardByLevel(levelId:String):Leaderboard
|
||||
{
|
||||
return Leaderboard.getLeaderboardByLevel(levelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request to receive scores from Newgrounds.
|
||||
* @param leaderboard The leaderboard to fetch scores from.
|
||||
* @param params Additional parameters for fetching the score.
|
||||
*/
|
||||
public function requestScores(leaderboard:Leaderboard, params:RequestScoresParams)
|
||||
{
|
||||
Leaderboards.requestScores(leaderboard, params);
|
||||
@@ -174,106 +214,118 @@ typedef RequestScoresParams =
|
||||
}
|
||||
#end
|
||||
|
||||
/**
|
||||
* An enumeration of Newgrounds leaderboards for the game's levels and songs.
|
||||
*/
|
||||
enum abstract Leaderboard(Int) from Int to Int
|
||||
{
|
||||
/**
|
||||
* Represents an undefined or invalid leaderboard.
|
||||
*/
|
||||
var Unknown = -1;
|
||||
public var Unknown = -1;
|
||||
|
||||
//
|
||||
// STORY LEVELS
|
||||
//
|
||||
var StoryWeek1 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14239 #else 9615 #end;
|
||||
var StoryWeek2 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14240 #else 9616 #end;
|
||||
var StoryWeek3 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14242 #else 9767 #end;
|
||||
var StoryWeek4 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14241 #else 9866 #end;
|
||||
var StoryWeek5 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14243 #else 9956 #end;
|
||||
var StoryWeek6 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14244 #else 9957 #end;
|
||||
var StoryWeek7 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14245 #else 14682 #end;
|
||||
var StoryWeekend1 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14237 #else 14683 #end;
|
||||
// Tutorial only has one song.
|
||||
public var StoryWeek1 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14239 #else 9615 #end;
|
||||
public var StoryWeek2 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14240 #else 9616 #end;
|
||||
public var StoryWeek3 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14242 #else 9767 #end;
|
||||
public var StoryWeek4 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14241 #else 9866 #end;
|
||||
public var StoryWeek5 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14243 #else 9956 #end;
|
||||
public var StoryWeek6 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14244 #else 9957 #end;
|
||||
public var StoryWeek7 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14245 #else 14682 #end;
|
||||
public var StoryWeekend1 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14237 #else 14683 #end;
|
||||
|
||||
// Collab 1 only has one song.
|
||||
//
|
||||
// SONGS
|
||||
//
|
||||
// Tutorial
|
||||
var Tutorial = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14249 #else 14684 #end;
|
||||
public var Tutorial = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14249 #else 14684 #end;
|
||||
|
||||
// Week 1
|
||||
var Bopeebo = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14246 #else 9603 #end;
|
||||
var BopeeboErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14685 #end;
|
||||
var BopeeboPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14686 #end;
|
||||
var Fresh = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14247 #else 9602 #end;
|
||||
var FreshErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14687 #end;
|
||||
var FreshPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14688 #end;
|
||||
var DadBattle = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14248 #else 9605 #end;
|
||||
var DadBattleErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14689 #end;
|
||||
var DadBattlePicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14690 #end;
|
||||
public var Bopeebo = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14246 #else 9603 #end;
|
||||
public var BopeeboErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14685 #end;
|
||||
public var BopeeboPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14686 #end;
|
||||
public var Fresh = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14247 #else 9602 #end;
|
||||
public var FreshErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14687 #end;
|
||||
public var FreshPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14688 #end;
|
||||
public var DadBattle = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14248 #else 9605 #end;
|
||||
public var DadBattleErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14689 #end;
|
||||
public var DadBattlePicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14690 #end;
|
||||
|
||||
// Week 2
|
||||
var Spookeez = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9604 #end;
|
||||
var SpookeezErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14691 #end;
|
||||
var SpookeezPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14692 #end;
|
||||
var South = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9606 #end;
|
||||
var SouthErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14693 #end;
|
||||
var SouthPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14694 #end;
|
||||
var Monster = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14703 #end;
|
||||
public var Spookeez = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9604 #end;
|
||||
public var SpookeezErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14691 #end;
|
||||
public var SpookeezPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14692 #end;
|
||||
public var South = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9606 #end;
|
||||
public var SouthErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14693 #end;
|
||||
public var SouthPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14694 #end;
|
||||
public var Monster = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14703 #end;
|
||||
|
||||
// Week 3
|
||||
var Pico = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9766 #end;
|
||||
var PicoErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14695 #end;
|
||||
var PicoPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14696 #end;
|
||||
var PhillyNice = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9769 #end;
|
||||
var PhillyNiceErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14697 #end;
|
||||
var PhillyNicePicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14698 #end;
|
||||
var Blammed = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9768 #end;
|
||||
var BlammedErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14704 #end;
|
||||
var BlammedPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14705 #end;
|
||||
public var Pico = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9766 #end;
|
||||
public var PicoErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14695 #end;
|
||||
public var PicoPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14696 #end;
|
||||
public var PhillyNice = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9769 #end;
|
||||
public var PhillyNiceErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14697 #end;
|
||||
public var PhillyNicePicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14698 #end;
|
||||
public var Blammed = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9768 #end;
|
||||
public var BlammedErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14704 #end;
|
||||
public var BlammedPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14705 #end;
|
||||
|
||||
// Week 4
|
||||
var SatinPanties = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9869 #end;
|
||||
var SatinPantiesErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14701 #end;
|
||||
var High = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9867 #end;
|
||||
var HighErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14699 #end;
|
||||
var MILF = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9868 #end;
|
||||
public var SatinPanties = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9869 #end;
|
||||
public var SatinPantiesErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14701 #end;
|
||||
public var High = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9867 #end;
|
||||
public var HighErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14699 #end;
|
||||
public var MILF = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9868 #end;
|
||||
|
||||
// Week 5
|
||||
var Cocoa = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14706 #end;
|
||||
var CocoaErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14707 #end;
|
||||
var CocoaPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14708 #end;
|
||||
var Eggnog = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14709 #end;
|
||||
var EggnogErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14711 #end;
|
||||
var EggnogPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14710 #end;
|
||||
var WinterHorrorland = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14712 #end;
|
||||
public var Cocoa = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14706 #end;
|
||||
public var CocoaErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14707 #end;
|
||||
public var CocoaPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14708 #end;
|
||||
public var Eggnog = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14709 #end;
|
||||
public var EggnogErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14711 #end;
|
||||
public var EggnogPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14710 #end;
|
||||
public var WinterHorrorland = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14712 #end;
|
||||
|
||||
// Week 6
|
||||
var Senpai = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9958 #end;
|
||||
var SenpaiErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14713 #end;
|
||||
var SenpaiPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14716 #end;
|
||||
var Roses = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9959 #end;
|
||||
var RosesErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14714 #end;
|
||||
var RosesPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14717 #end;
|
||||
var Thorns = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9960 #end;
|
||||
var ThornsErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14715 #end;
|
||||
public var Senpai = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9958 #end;
|
||||
public var SenpaiErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14713 #end;
|
||||
public var SenpaiPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14716 #end;
|
||||
public var Roses = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9959 #end;
|
||||
public var RosesErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14714 #end;
|
||||
public var RosesPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14717 #end;
|
||||
public var Thorns = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9960 #end;
|
||||
public var ThornsErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14715 #end;
|
||||
|
||||
// Week 7
|
||||
var Ugh = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14718 #end;
|
||||
var UghErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14722 #end;
|
||||
var UghPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14721 #end;
|
||||
var Guns = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14719 #end;
|
||||
var GunsPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14723 #end;
|
||||
var Stress = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14720 #end;
|
||||
var StressPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14724 #end;
|
||||
public var Ugh = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14718 #end;
|
||||
public var UghErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14722 #end;
|
||||
public var UghPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14721 #end;
|
||||
public var Guns = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14719 #end;
|
||||
public var GunsPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14723 #end;
|
||||
public var Stress = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14720 #end;
|
||||
public var StressPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14724 #end;
|
||||
|
||||
// Weekend 1
|
||||
var Darnell = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14725 #end;
|
||||
var DarnellErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14727 #end;
|
||||
var DarnellBFMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14726 #end;
|
||||
var LitUp = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14728 #end;
|
||||
var LitUpBFMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14729 #end;
|
||||
var TwoHot = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14730 #end; // Variable names can't start with a number!
|
||||
var Blazin = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14731 #end;
|
||||
public var Darnell = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14725 #end;
|
||||
public var DarnellErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14727 #end;
|
||||
public var DarnellBFMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14726 #end;
|
||||
public var LitUp = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14728 #end;
|
||||
public var LitUpBFMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14729 #end;
|
||||
public var TwoHot = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14730 #end; // Variable names can't start with a number!
|
||||
public var Blazin = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14731 #end;
|
||||
|
||||
// Collab 1
|
||||
public var Spaghetti = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 15448 #else 15451 #end;
|
||||
|
||||
/**
|
||||
* Get the numeric ID for the leaderboard on the Newgrounds site.
|
||||
* @return The numeric ID for the leaderboard.
|
||||
*/
|
||||
public function getId():Int
|
||||
{
|
||||
return this;
|
||||
@@ -282,7 +334,6 @@ enum abstract Leaderboard(Int) from Int to Int
|
||||
/**
|
||||
* Get the leaderboard for a given level and difficulty.
|
||||
* @param levelId The ID for the story level.
|
||||
* @param difficulty The current difficulty.
|
||||
* @return The Leaderboard ID for the given level and difficulty.
|
||||
*/
|
||||
public static function getLeaderboardByLevel(levelId:String):Leaderboard
|
||||
@@ -305,14 +356,16 @@ enum abstract Leaderboard(Int) from Int to Int
|
||||
return StoryWeek7;
|
||||
case "weekend1":
|
||||
return StoryWeekend1;
|
||||
// Collab 1 has only one song.
|
||||
default:
|
||||
return Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the leaderboard for a given level and difficulty.
|
||||
* @param levelId The ID for the story level.
|
||||
* Get the leaderboard for a given song and difficulty.
|
||||
*
|
||||
* @param songId The ID for the song.
|
||||
* @param difficulty The current difficulty, suffixed with the variation, like `easy-pico` or `nightmare`.
|
||||
* @return The Leaderboard ID for the given level and difficulty.
|
||||
*/
|
||||
@@ -473,6 +526,8 @@ enum abstract Leaderboard(Int) from Int to Int
|
||||
return TwoHot;
|
||||
case "blazin":
|
||||
return Blazin;
|
||||
case "spaghetti":
|
||||
return Spaghetti;
|
||||
default:
|
||||
return Unknown;
|
||||
}
|
||||
|
||||
@@ -5,27 +5,40 @@ import io.newgrounds.objects.Medal as MedalData;
|
||||
import funkin.util.plugins.NewgroundsMedalPlugin;
|
||||
import flixel.graphics.FlxGraphic;
|
||||
import openfl.display.BitmapData;
|
||||
import io.newgrounds.utils.MedalList;
|
||||
import haxe.Json;
|
||||
|
||||
/**
|
||||
* Handles interactions with the medals on the Newgrounds website.
|
||||
*/
|
||||
@:nullSafety
|
||||
class Medals
|
||||
{
|
||||
/**
|
||||
* A cache for the medal data fetched from the local `medals.json`.
|
||||
*/
|
||||
public static var medalJSON:Array<MedalJSON> = [];
|
||||
|
||||
/**
|
||||
* Retrieve the leaderboard data via the Newgrounds API.
|
||||
* @return The leaderboard data.
|
||||
*/
|
||||
public static function listMedalData():Map<Medal, MedalData>
|
||||
{
|
||||
var medalList = NewgroundsClient.instance.medals;
|
||||
|
||||
if (medalList == null)
|
||||
{
|
||||
trace('[NEWGROUNDS] Not logged in, cannot fetch medal data!');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Not logged in, cannot fetch medal data!');
|
||||
return [];
|
||||
}
|
||||
|
||||
return @:privateAccess medalList._map?.copy() ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Awards the player a specific medal, updating their Newgrounds account and
|
||||
* displaying the corresponding popup.
|
||||
* @param medal The medal to be awarded.
|
||||
*/
|
||||
public static function award(medal:Medal):Void
|
||||
{
|
||||
if (NewgroundsClient.instance.isLoggedIn())
|
||||
@@ -34,18 +47,19 @@ class Medals
|
||||
@:privateAccess
|
||||
if (medalData == null || medalData._data == null)
|
||||
{
|
||||
trace('[NEWGROUNDS] Could not retrieve data for medal: ${medal}');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Could not retrieve data for medal: ${medal}');
|
||||
return;
|
||||
}
|
||||
else if (!medalData.unlocked)
|
||||
{
|
||||
trace('[NEWGROUNDS] Awarding medal (${medal}).');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Awarding medal (${medal}).');
|
||||
medalData.sendUnlock();
|
||||
|
||||
// Play the medal unlock animation, but only if the user has not already unlocked it.
|
||||
#if html5
|
||||
// Web builds support parsing the bitmap data from the URL directly.
|
||||
BitmapData.loadFromFile("https:" + medalData.icon).onComplete(function(bmp:BitmapData) {
|
||||
BitmapData.loadFromFile("https:" + medalData.icon).onComplete(function(bmp:BitmapData)
|
||||
{
|
||||
var medalGraphic = FlxGraphic.fromBitmapData(bmp);
|
||||
medalGraphic.persist = true;
|
||||
NewgroundsMedalPlugin.play(medalData.value, medalData.name, medalGraphic);
|
||||
@@ -55,7 +69,8 @@ class Medals
|
||||
// We have to use a medal image from the game files. We use a Base64 encoded image that NG spits out.
|
||||
// TODO: Wait, don't they give us the medal icon?
|
||||
|
||||
var localMedalData:Null<MedalJSON> = medalJSON.filter(function(jsonMedal) {
|
||||
var localMedalData:Null<MedalJSON> = medalJSON.filter(function(jsonMedal)
|
||||
{
|
||||
#if FEATURE_NEWGROUNDS_TESTING_MEDALS
|
||||
return medal == jsonMedal.idTest;
|
||||
#else
|
||||
@@ -81,15 +96,18 @@ class Medals
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[NEWGROUNDS] User already has medal (${medal}).');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' User already has medal (${medal}).');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[NEWGROUNDS] Attempted to award medal (${medal}), but not logged into Newgrounds.');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Attempted to award medal (${medal}), but not logged into Newgrounds.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the full medal data from local JSON data.
|
||||
*/
|
||||
public static function loadMedalJSON():Void
|
||||
{
|
||||
var jsonPath = Paths.json('medals');
|
||||
@@ -98,12 +116,12 @@ class Medals
|
||||
|
||||
var parser = new json2object.JsonParser<Array<MedalJSON>>();
|
||||
parser.ignoreUnknownVariables = false;
|
||||
trace('[NEWGROUNDS] Parsing local medal data...');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Parsing local medal data...');
|
||||
parser.fromJson(jsonString, jsonPath);
|
||||
|
||||
if (parser.errors.length > 0)
|
||||
{
|
||||
trace('[NEWGROUNDS] Failed to parse local medal data!');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Failed to parse local medal data!');
|
||||
for (error in parser.errors)
|
||||
funkin.data.DataError.printError(error);
|
||||
medalJSON = [];
|
||||
@@ -114,13 +132,18 @@ class Medals
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the data for a medal from the Newgrounds API.
|
||||
* @param medal The ID of the medal to fetch.
|
||||
* @return The fetched medal data.
|
||||
*/
|
||||
public static function fetchMedalData(medal:Medal):Null<FetchedMedalData>
|
||||
{
|
||||
var medalData:Null<MedalData> = listMedalData().get(medal.getId());
|
||||
@:privateAccess
|
||||
if (medalData == null || medalData._data == null)
|
||||
{
|
||||
trace('[NEWGROUNDS] Could not retrieve data for medal: ${medal}');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Could not retrieve data for medal: ${medal}');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -136,12 +159,16 @@ class Medals
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Awards the player a medal for completing a specific story level.
|
||||
* @param id The level ID for the story level that was completed.
|
||||
*/
|
||||
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}).');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Story level does not have a medal! (${id}).');
|
||||
return;
|
||||
}
|
||||
Medals.award(medal);
|
||||
@@ -153,16 +180,30 @@ class Medals
|
||||
*/
|
||||
class MedalsSandboxed
|
||||
{
|
||||
/**
|
||||
* Fetches the data for a medal from the Newgrounds API.
|
||||
* @param medal The ID of the medal to fetch.
|
||||
* @return The fetched medal data.
|
||||
*/
|
||||
public static function fetchMedalData(medal:Medal):Null<FetchedMedalData>
|
||||
{
|
||||
return Medals.fetchMedalData(medal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the medal for completing a specific story level.
|
||||
* @param id The ID for the story level that was completed.
|
||||
* @return The medal to award.
|
||||
*/
|
||||
public static function getMedalByStoryLevel(id:String):Medal
|
||||
{
|
||||
return Medal.getMedalByStoryLevel(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all medals aside from the `Unknown` one.
|
||||
* @return An array of all medals.
|
||||
*/
|
||||
public static function getAllMedals():Array<Medal>
|
||||
{
|
||||
return Medal.getAllMedals();
|
||||
@@ -211,140 +252,157 @@ typedef MedalJSON =
|
||||
var icon:String;
|
||||
}
|
||||
|
||||
/**
|
||||
* An enumeration of all the game's medals.
|
||||
*/
|
||||
enum abstract Medal(Int) from Int to Int
|
||||
{
|
||||
/**
|
||||
* Represents an undefined or invalid medal.
|
||||
*/
|
||||
var Unknown = -1;
|
||||
public var Unknown = -1;
|
||||
|
||||
/**
|
||||
* I Said Funkin'!
|
||||
* Start the game for the first time.
|
||||
*/
|
||||
var StartGame = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80894 #else 60960 #end;
|
||||
public var StartGame = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80894 #else 60960 #end;
|
||||
|
||||
/**
|
||||
* That's How You Do It!
|
||||
* Beat Tutoria l in Story Mode (on any difficulty).
|
||||
* Beat Tutorial in Story Mode (on any difficulty).
|
||||
*/
|
||||
var StoryTutorial = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80906 #else 83647 #end;
|
||||
public var StoryTutorial = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80906 #else 83647 #end;
|
||||
|
||||
/**
|
||||
* More Like Daddy Queerest
|
||||
* Beat Week 1 in Story Mode (on any difficulty).
|
||||
*/
|
||||
var StoryWeek1 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80899 #else 60961 #end;
|
||||
public var StoryWeek1 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80899 #else 60961 #end;
|
||||
|
||||
/**
|
||||
* IT IS THE SPOOKY MONTH
|
||||
* Beat Week 2 in Story Mode (on any difficulty).
|
||||
*/
|
||||
var StoryWeek2 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80900 #else 83648 #end;
|
||||
public var StoryWeek2 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80900 #else 83648 #end;
|
||||
|
||||
/**
|
||||
* Pico Funny
|
||||
* Beat Week 3 in Story Mode (on any difficulty).
|
||||
*/
|
||||
var StoryWeek3 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80901 #else 83649 #end;
|
||||
public var StoryWeek3 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80901 #else 83649 #end;
|
||||
|
||||
/**
|
||||
* Mommy Must Murder
|
||||
* Beat Week 4 in Story Mode (on any difficulty).
|
||||
*/
|
||||
var StoryWeek4 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80902 #else 83650 #end;
|
||||
public var StoryWeek4 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80902 #else 83650 #end;
|
||||
|
||||
/**
|
||||
* Yule Tide Joy
|
||||
* Beat Week 5 in Story Mode (on any difficulty).
|
||||
*/
|
||||
var StoryWeek5 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80903 #else 83651 #end;
|
||||
public var StoryWeek5 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80903 #else 83651 #end;
|
||||
|
||||
/**
|
||||
* A Visual Novelty
|
||||
* Beat Week 6 in Story Mode (on any difficulty).
|
||||
*/
|
||||
var StoryWeek6 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80904 #else 83652 #end;
|
||||
public var StoryWeek6 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80904 #else 83652 #end;
|
||||
|
||||
/**
|
||||
* I <3 JohnnyUtah
|
||||
* Beat Week 7 in Story Mode (on any difficulty).
|
||||
*/
|
||||
var StoryWeek7 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80905 #else 83653 #end;
|
||||
public var StoryWeek7 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80905 #else 83653 #end;
|
||||
|
||||
/**
|
||||
* Yo, Really Think So?
|
||||
* Beat Weekend 1 in Story Mode (on any difficulty).
|
||||
*/
|
||||
var StoryWeekend1 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80907 #else 83654 #end;
|
||||
public var StoryWeekend1 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80907 #else 83654 #end;
|
||||
|
||||
/**
|
||||
* Eat It Up!
|
||||
* Beat Collab 1 in Story Mode (on any difficulty).
|
||||
*/
|
||||
public var StoryCollab1 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 87697 #else 87714 #end;
|
||||
|
||||
/**
|
||||
* Stay Funky
|
||||
* Press TAB in Freeplay and unlock your first character.
|
||||
*/
|
||||
var CharSelect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 83633 #else 83655 #end;
|
||||
public var CharSelect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 83633 #else 83655 #end;
|
||||
|
||||
/**
|
||||
* A Challenger Appears
|
||||
* Beat any Pico remix in Freeplay (on any difficulty).
|
||||
*/
|
||||
var FreeplayPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80910 #else 83656 #end;
|
||||
public var FreeplayPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80910 #else 83656 #end;
|
||||
|
||||
/**
|
||||
* De-Stressing
|
||||
* Beat Stress (Pico Mix) in Freeplay (on Normal difficulty or higher).
|
||||
*/
|
||||
var FreeplayStressPico = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 83629 #else 83657 #end;
|
||||
public var FreeplayStressPico = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 83629 #else 83657 #end;
|
||||
|
||||
/**
|
||||
* L
|
||||
* Earn a Loss rating on any song (on any difficulty).
|
||||
*/
|
||||
var LossRating = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80915 #else 83658 #end;
|
||||
public var LossRating = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80915 #else 83658 #end;
|
||||
|
||||
/**
|
||||
* Getting Freaky
|
||||
* Earn a Perfect rating on any song on Hard difficulty or higher.
|
||||
* NOTE: Should also be awarded for a Gold Perfect because otherwise that would be annoying.
|
||||
*/
|
||||
var PerfectRatingHard = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80908 #else 83659 #end;
|
||||
public var PerfectRatingHard = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80908 #else 83659 #end;
|
||||
|
||||
/**
|
||||
* You Should Drink More Water
|
||||
* Earn a Golden Perfect rating on any song on Hard difficulty or higher.
|
||||
*/
|
||||
var GoldPerfectRatingHard = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80909 #else 83660 #end;
|
||||
public var GoldPerfectRatingHard = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80909 #else 83660 #end;
|
||||
|
||||
/**
|
||||
* Harder Than Hard
|
||||
* Beat any Erect remix in Freeplay on Erect or Nightmare difficulty.
|
||||
*/
|
||||
var ErectDifficulty = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80911 #else 83661 #end;
|
||||
public var ErectDifficulty = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80911 #else 83661 #end;
|
||||
|
||||
/**
|
||||
* The Rap God
|
||||
* Earn a Gold Perfect rating on any song on Nightmare difficulty.
|
||||
*/
|
||||
var GoldPerfectRatingNightmare = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80912 #else 83662 #end;
|
||||
public var GoldPerfectRatingNightmare = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80912 #else 83662 #end;
|
||||
|
||||
/**
|
||||
* Just like the game!
|
||||
* Get freaky on a Friday.
|
||||
* NOTE: You must beat at least one song on any difficulty.
|
||||
*/
|
||||
var FridayNight = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80913 #else 61034 #end;
|
||||
public var FridayNight = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80913 #else 61034 #end;
|
||||
|
||||
/**
|
||||
* Nice
|
||||
* Earn a rating of EXACTLY 69% (good luck).
|
||||
*/
|
||||
var Nice = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80914 #else 83646 #end;
|
||||
public var Nice = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80914 #else 83646 #end;
|
||||
|
||||
/**
|
||||
* @return The internal ID for the medal.
|
||||
*/
|
||||
public function getId():Int
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the medal for completing a specific story level.
|
||||
* @param levelId The ID for the story level that was completed.
|
||||
* @return The medal to award.
|
||||
*/
|
||||
public static function getMedalByStoryLevel(levelId:String):Medal
|
||||
{
|
||||
switch (levelId)
|
||||
@@ -367,6 +425,8 @@ enum abstract Medal(Int) from Int to Int
|
||||
return StoryWeek7;
|
||||
case "weekend1":
|
||||
return StoryWeekend1;
|
||||
case "sserafim":
|
||||
return StoryCollab1;
|
||||
default:
|
||||
return Unknown;
|
||||
}
|
||||
@@ -374,30 +434,11 @@ enum abstract Medal(Int) from Int to Int
|
||||
|
||||
/**
|
||||
* Lists all medals aside from the `Unknown` one.
|
||||
* @return An array of all medals.
|
||||
*/
|
||||
public static function getAllMedals()
|
||||
public static function getAllMedals():Array<Medal>
|
||||
{
|
||||
return [
|
||||
StartGame,
|
||||
StoryTutorial,
|
||||
StoryWeek1,
|
||||
StoryWeek2,
|
||||
StoryWeek3,
|
||||
StoryWeek4,
|
||||
StoryWeek5,
|
||||
StoryWeek6,
|
||||
StoryWeek7,
|
||||
StoryWeekend1,
|
||||
CharSelect,
|
||||
FreeplayPicoMix,
|
||||
FreeplayStressPico,
|
||||
LossRating,
|
||||
PerfectRatingHard,
|
||||
GoldPerfectRatingHard,
|
||||
ErectDifficulty,
|
||||
GoldPerfectRatingNightmare,
|
||||
FridayNight,
|
||||
Nice
|
||||
];
|
||||
return
|
||||
[StartGame, StoryTutorial, StoryWeek1, StoryWeek2, StoryWeek3, StoryWeek4, StoryWeek5, StoryWeek6, StoryWeek7, StoryWeekend1, CharSelect, FreeplayPicoMix, FreeplayStressPico, LossRating, PerfectRatingHard, GoldPerfectRatingHard, ErectDifficulty, GoldPerfectRatingNightmare, FridayNight, Nice];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ class NGSaveSlot
|
||||
|
||||
public static function loadInstance():NGSaveSlot
|
||||
{
|
||||
var loadedSave:NGSaveSlot = loadSlot(Save.BASE_SAVE_SLOT);
|
||||
var loadedSave:NGSaveSlot = loadSlot(Constants.BASE_SAVE_SLOT);
|
||||
if (_instance == null) _instance = loadedSave;
|
||||
|
||||
return loadedSave;
|
||||
@@ -33,7 +33,7 @@ class NGSaveSlot
|
||||
|
||||
static function loadSlot(slot:Int):NGSaveSlot
|
||||
{
|
||||
trace('[NEWGROUNDS] Getting save slot from ID $slot');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Getting save slot from ID $slot');
|
||||
|
||||
var saveSlot:Null<SaveSlot> = NewgroundsClient.instance.saveSlots?.getById(slot);
|
||||
|
||||
@@ -46,11 +46,6 @@ class NGSaveSlot
|
||||
public function new(?ngSaveSlot:Null<SaveSlot>)
|
||||
{
|
||||
this.ngSaveSlot = ngSaveSlot;
|
||||
|
||||
#if FLX_DEBUG
|
||||
FlxG.console.registerClass(NGSaveSlot);
|
||||
FlxG.console.registerClass(Save);
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,20 +58,21 @@ class NGSaveSlot
|
||||
|
||||
try
|
||||
{
|
||||
ngSaveSlot?.save(encodedData, function(outcome:Outcome<CallError>) {
|
||||
ngSaveSlot?.save(encodedData, function(outcome:Outcome<CallError>)
|
||||
{
|
||||
switch (outcome)
|
||||
{
|
||||
case SUCCESS:
|
||||
trace('[NEWGROUNDS] Successfully saved save data to save slot!');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Successfully saved save data to save slot!');
|
||||
case FAIL(error):
|
||||
trace('[NEWGROUNDS] Failed to save data to save slot!');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Failed to save data to save slot!');
|
||||
trace(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (error:String)
|
||||
{
|
||||
trace('[NEWGROUNDS] Failed to save data to save slot!');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Failed to save data to save slot!');
|
||||
trace(error);
|
||||
}
|
||||
}
|
||||
@@ -85,11 +81,12 @@ class NGSaveSlot
|
||||
{
|
||||
try
|
||||
{
|
||||
ngSaveSlot?.load(function(outcome:SaveSlotOutcome):Void {
|
||||
ngSaveSlot?.load(function(outcome:SaveSlotOutcome):Void
|
||||
{
|
||||
switch (outcome)
|
||||
{
|
||||
case SUCCESS(value):
|
||||
trace('[NEWGROUNDS] Loaded save slot with the ID of ${ngSaveSlot?.id}!');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Loaded save slot with the ID of ${ngSaveSlot?.id}!');
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
trace('Save Slot Data:');
|
||||
trace(value);
|
||||
@@ -101,7 +98,7 @@ class NGSaveSlot
|
||||
onComplete(decodedData);
|
||||
}
|
||||
case FAIL(error):
|
||||
trace('[NEWGROUNDS] Failed to load save slot with the ID of ${ngSaveSlot?.id}!');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Failed to load save slot with the ID of ${ngSaveSlot?.id}!');
|
||||
trace(error);
|
||||
|
||||
if (onError != null)
|
||||
@@ -113,7 +110,7 @@ class NGSaveSlot
|
||||
}
|
||||
catch (error:String)
|
||||
{
|
||||
trace('[NEWGROUNDS] Failed to load save slot with the ID of ${ngSaveSlot?.id}!');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Failed to load save slot with the ID of ${ngSaveSlot?.id}!');
|
||||
trace(error);
|
||||
|
||||
if (onError != null)
|
||||
@@ -127,27 +124,28 @@ class NGSaveSlot
|
||||
{
|
||||
try
|
||||
{
|
||||
ngSaveSlot?.clear(function(outcome:Outcome<CallError>) {
|
||||
ngSaveSlot?.clear(function(outcome:Outcome<CallError>)
|
||||
{
|
||||
switch (outcome)
|
||||
{
|
||||
case SUCCESS:
|
||||
trace('[NEWGROUNDS] Successfully cleared save slot!');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Successfully cleared save slot!');
|
||||
case FAIL(error):
|
||||
trace('[NEWGROUNDS] Failed to clear save slot!');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Failed to clear save slot!');
|
||||
trace(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (error:String)
|
||||
{
|
||||
trace('[NEWGROUNDS] Failed to clear save slot!');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Failed to clear save slot!');
|
||||
trace(error);
|
||||
}
|
||||
}
|
||||
|
||||
public function checkSlot():Void
|
||||
{
|
||||
trace('[NEWGROUNDS] Checking save slot with the ID of ${ngSaveSlot?.id}...');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Checking save slot with the ID of ${ngSaveSlot?.id}...');
|
||||
|
||||
trace(' Is null? ${ngSaveSlot == null}');
|
||||
trace(' Is empty? ${ngSaveSlot?.isEmpty() ?? false}');
|
||||
|
||||
@@ -13,6 +13,9 @@ import io.newgrounds.utils.MedalList;
|
||||
import io.newgrounds.utils.SaveSlotList;
|
||||
import io.newgrounds.utils.ScoreBoardList;
|
||||
import io.newgrounds.objects.User;
|
||||
#if FEATURE_MOBILE_WEBVIEW
|
||||
import funkin.mobile.util.WebViewUtil;
|
||||
#end
|
||||
|
||||
@:build(funkin.util.macro.EnvironmentMacro.build())
|
||||
@:nullSafety
|
||||
@@ -42,11 +45,11 @@ class NewgroundsClient
|
||||
|
||||
private function new()
|
||||
{
|
||||
trace('[NEWGROUNDS] Initializing client...');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Initializing client...');
|
||||
|
||||
#if FEATURE_NEWGROUNDS_DEBUG
|
||||
trace('[NEWGROUNDS] App ID: ${API_NG_APP_ID}');
|
||||
trace('[NEWGROUNDS] Encryption Key: ${API_NG_ENC_KEY}');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' App ID: ${API_NG_APP_ID}');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Encryption Key: ${API_NG_ENC_KEY}');
|
||||
#end
|
||||
|
||||
if (!hasValidCredentials())
|
||||
@@ -67,7 +70,7 @@ class NewgroundsClient
|
||||
{
|
||||
if (NG.core == null) return;
|
||||
|
||||
trace('[NEWGROUNDS] Setting up connection...');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Setting up connection...');
|
||||
|
||||
#if FEATURE_NEWGROUNDS_DEBUG
|
||||
NG.core.verbose = true;
|
||||
@@ -78,16 +81,16 @@ class NewgroundsClient
|
||||
if (NG.core.attemptingLogin)
|
||||
{
|
||||
// Session ID was valid and we should be logged in soon.
|
||||
trace('[NEWGROUNDS] Waiting for existing login!');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Waiting for existing login!');
|
||||
}
|
||||
else
|
||||
{
|
||||
#if FEATURE_NEWGROUNDS_AUTOLOGIN
|
||||
// Attempt an automatic login.
|
||||
trace('[NEWGROUNDS] Attempting new login immediately!');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Attempting new login immediately!');
|
||||
this.autoLogin();
|
||||
#else
|
||||
trace('[NEWGROUNDS] Not logged in, you have to login manually!');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Not logged in, you have to login manually!');
|
||||
#end
|
||||
}
|
||||
}
|
||||
@@ -104,20 +107,41 @@ class NewgroundsClient
|
||||
FlxG.log.warn("No Newgrounds client initialized! Are your credentials invalid?");
|
||||
return;
|
||||
}
|
||||
|
||||
if (NG.core.attemptingLogin)
|
||||
{
|
||||
trace("[NEWGROUNDS] Login attempt ongoing, will not login until finished.");
|
||||
trace(" NEWGROUNDS '.bold().bg_orange() + ' Login attempt ongoing, will not login until finished.");
|
||||
return;
|
||||
}
|
||||
|
||||
var passportHandler:String->Void = function(passportUrl:String)
|
||||
{
|
||||
// This exists so we can create a popup on mobile but with a WebView instead.
|
||||
#if FEATURE_MOBILE_WEBVIEW
|
||||
if (passportUrl != null)
|
||||
{
|
||||
NG.core.logVerbose('Loading passport from WebView: ${passportUrl}');
|
||||
|
||||
WebViewUtil.openURL(passportUrl, function():Void
|
||||
{
|
||||
NG.core.cancelLoginRequest();
|
||||
});
|
||||
|
||||
NG.core.onPassportUrlOpen();
|
||||
}
|
||||
else
|
||||
NG.core.logError("Cannot open passport");
|
||||
#else
|
||||
NG.core.openPassportUrl();
|
||||
#end
|
||||
};
|
||||
|
||||
if (onSuccess != null && onError != null)
|
||||
{
|
||||
NG.core.requestLogin(onLoginResolvedWithCallbacks.bind(_, onSuccess, onError));
|
||||
NG.core.requestLogin(onLoginResolvedWithCallbacks.bind(_, onSuccess, onError), passportHandler);
|
||||
}
|
||||
else
|
||||
{
|
||||
NG.core.requestLogin(onLoginResolved);
|
||||
NG.core.requestLogin(onLoginResolved, passportHandler);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +153,8 @@ class NewgroundsClient
|
||||
return;
|
||||
}
|
||||
|
||||
var dummyPassport:String->Void = function(_) {
|
||||
var dummyPassport:String->Void = function(_)
|
||||
{
|
||||
// just a dummy passport, so we don't create a popup
|
||||
// otherwise `NG.core.requestLogin()` will automatically attempt to open a tab at the beginning of the game
|
||||
// users should go to the Options Menu to login to NG
|
||||
@@ -165,7 +190,7 @@ class NewgroundsClient
|
||||
}
|
||||
}
|
||||
|
||||
Save.instance.ngSessionId = null;
|
||||
Save.instance.ngSessionId.value = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -195,6 +220,10 @@ class NewgroundsClient
|
||||
|
||||
function onLoginResolved(outcome:LoginOutcome):Void
|
||||
{
|
||||
#if FEATURE_MOBILE_WEBVIEW
|
||||
WebViewUtil.close();
|
||||
#end
|
||||
|
||||
switch (outcome)
|
||||
{
|
||||
case SUCCESS:
|
||||
@@ -245,17 +274,19 @@ class NewgroundsClient
|
||||
{
|
||||
if (NG.core == null) return;
|
||||
|
||||
trace('[NEWGROUNDS] Login successful!');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Login successful!');
|
||||
|
||||
// Persist the session ID.
|
||||
Save.instance.ngSessionId = NG.core.sessionId;
|
||||
Save.instance.ngSessionId.value = NG.core.sessionId;
|
||||
|
||||
trace('[NEWGROUNDS] Submitting medal request...');
|
||||
trace(NG.core.sessionId);
|
||||
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Submitting medal request...');
|
||||
NG.core.requestMedals(onFetchedMedals);
|
||||
|
||||
trace('[NEWGROUNDS] Submitting leaderboard request...');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Submitting leaderboard request...');
|
||||
NG.core.scoreBoards.loadList(onFetchedLeaderboards);
|
||||
trace('[NEWGROUNDS] Submitting save slot request...');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Submitting save slot request...');
|
||||
NG.core.saveSlots.loadList(onFetchedSaveSlots);
|
||||
}
|
||||
|
||||
@@ -267,32 +298,32 @@ class NewgroundsClient
|
||||
switch (type)
|
||||
{
|
||||
case PASSPORT:
|
||||
trace('[NEWGROUNDS] Login cancelled by passport website.');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Login cancelled by passport website.');
|
||||
case MANUAL:
|
||||
trace('[NEWGROUNDS] Login cancelled by application.');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Login cancelled by application.');
|
||||
default:
|
||||
trace('[NEWGROUNDS] Login cancelled by unknown source.');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Login cancelled by unknown source.');
|
||||
}
|
||||
case ERROR(error):
|
||||
switch (error)
|
||||
{
|
||||
case HTTP(error):
|
||||
trace('[NEWGROUNDS] Login failed due to HTTP error: ${error}');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Login failed due to HTTP error: ${error}');
|
||||
case RESPONSE(error):
|
||||
trace('[NEWGROUNDS] Login failed due to response error: ${error.message} (${error.code})');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Login failed due to response error: ${error.message} (${error.code})');
|
||||
case RESULT(error):
|
||||
trace('[NEWGROUNDS] Login failed due to result error: ${error.message} (${error.code})');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Login failed due to result error: ${error.message} (${error.code})');
|
||||
default:
|
||||
trace('[NEWGROUNDS] Login failed due to unknown error: ${error}');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Login failed due to unknown error: ${error}');
|
||||
}
|
||||
default:
|
||||
trace('[NEWGROUNDS] Login failed due to unknown reason.');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Login failed due to unknown reason.');
|
||||
}
|
||||
}
|
||||
|
||||
function onLogoutSuccessful():Void
|
||||
{
|
||||
trace('[NEWGROUNDS] Logout successful!');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Logout successful!');
|
||||
}
|
||||
|
||||
function onLogoutFailed(result:CallError):Void
|
||||
@@ -300,31 +331,31 @@ class NewgroundsClient
|
||||
switch (result)
|
||||
{
|
||||
case HTTP(error):
|
||||
trace('[NEWGROUNDS] Logout failed due to HTTP error: ${error}');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Logout failed due to HTTP error: ${error}');
|
||||
case RESPONSE(error):
|
||||
trace('[NEWGROUNDS] Logout failed due to response error: ${error.message} (${error.code})');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Logout failed due to response error: ${error.message} (${error.code})');
|
||||
case RESULT(error):
|
||||
trace('[NEWGROUNDS] Logout failed due to result error: ${error.message} (${error.code})');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Logout failed due to result error: ${error.message} (${error.code})');
|
||||
default:
|
||||
trace('[NEWGROUNDS] Logout failed due to unknown error: ${result}');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Logout failed due to unknown error: ${result}');
|
||||
}
|
||||
}
|
||||
|
||||
function onFetchedMedals(outcome:Outcome<CallError>):Void
|
||||
{
|
||||
trace('[NEWGROUNDS] Fetched medals!');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Fetched medals!');
|
||||
}
|
||||
|
||||
function onFetchedLeaderboards(outcome:Outcome<CallError>):Void
|
||||
{
|
||||
trace('[NEWGROUNDS] Fetched leaderboards!');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Fetched leaderboards!');
|
||||
|
||||
// trace(funkin.api.newgrounds.Leaderboards.listLeaderboardData());
|
||||
}
|
||||
|
||||
function onFetchedSaveSlots(outcome:Outcome<CallError>):Void
|
||||
{
|
||||
trace('[NEWGROUNDS] Fetched save slots!');
|
||||
trace(' NEWGROUNDS '.bold().bg_orange() + ' Fetched save slots!');
|
||||
|
||||
NGSaveSlot.instance.checkSlot();
|
||||
}
|
||||
@@ -362,7 +393,7 @@ class NewgroundsClient
|
||||
#end
|
||||
|
||||
// We have to fetch the session ID from the save file.
|
||||
return Save.instance.ngSessionId;
|
||||
return Save.instance.ngSessionId.value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,8 @@ class Referral
|
||||
{
|
||||
NG.core?.calls.loader.loadReferral(false)
|
||||
.addComponentParameter("referral_name", referralName)
|
||||
.addResponseHandler(response -> {
|
||||
.addResponseHandler(response ->
|
||||
{
|
||||
trace(response);
|
||||
if (response.success)
|
||||
{
|
||||
|
||||
@@ -42,7 +42,8 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
if (_onVolumeChanged == null)
|
||||
{
|
||||
_onVolumeChanged = new FlxTypedSignal<Float->Void>();
|
||||
FlxG.sound.onVolumeChange.add(function(volume:Float) {
|
||||
FlxG.sound.onVolumeChange.add(function(volume:Float)
|
||||
{
|
||||
_onVolumeChanged.dispatch(volume);
|
||||
});
|
||||
}
|
||||
@@ -124,11 +125,6 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
*/
|
||||
var _label:String = "unknown";
|
||||
|
||||
/**
|
||||
* Whether we received a focus lost event.
|
||||
*/
|
||||
var _lostFocus:Bool = false;
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
@@ -229,37 +225,6 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user clicks to focus on the window.
|
||||
*/
|
||||
override function onFocus():Void
|
||||
{
|
||||
// Flixel can sometimes toss spurious `onFocus` events, e.g. if the Flixel debugger is toggled
|
||||
// on and off. We only want to resume the sound if we actually lost focus, and if we weren't
|
||||
// already paused before we lost focus.
|
||||
if (_lostFocus && !_alreadyPaused)
|
||||
{
|
||||
// trace('Resuming audio (${this._label}) on focus!');
|
||||
resume();
|
||||
}
|
||||
else
|
||||
{
|
||||
// trace('Not resuming audio (${this._label}) on focus!');
|
||||
}
|
||||
_lostFocus = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user tabs away from the window.
|
||||
*/
|
||||
override function onFocusLost():Void
|
||||
{
|
||||
// trace('Focus lost, pausing audio!');
|
||||
_lostFocus = true;
|
||||
_alreadyPaused = _paused;
|
||||
pause();
|
||||
}
|
||||
|
||||
public override function resume():FunkinSound
|
||||
{
|
||||
if (this._time < 0)
|
||||
@@ -386,7 +351,8 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
partialQueue.push(music);
|
||||
|
||||
@:nullSafety(Off)
|
||||
music.future.onComplete(function(partialMusic:Null<FunkinSound>) {
|
||||
music.future.onComplete(function(partialMusic:Null<FunkinSound>)
|
||||
{
|
||||
FlxG.sound.music = partialMusic;
|
||||
FlxG.sound.list.remove(FlxG.sound.music);
|
||||
|
||||
@@ -405,10 +371,7 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
var music = FunkinSound.load(pathToUse, params?.startingVolume ?? 1.0, params.loop ?? true, false, true, params.persist ?? false, params.onComplete);
|
||||
if (music != null)
|
||||
{
|
||||
FlxG.sound.music = music;
|
||||
|
||||
// Prevent repeat update() and onFocus() calls.
|
||||
FlxG.sound.list.remove(FlxG.sound.music);
|
||||
setMusic(music);
|
||||
|
||||
if (FlxG.sound.music != null && params.onLoad != null) params.onLoad();
|
||||
|
||||
@@ -421,6 +384,18 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the Flixel current music object with the given `FunkinSound` object.
|
||||
* @param newMusic The new music to be set as the current music.
|
||||
*/
|
||||
public static function setMusic(newMusic:FunkinSound):Void
|
||||
{
|
||||
FlxG.sound.music = newMusic;
|
||||
|
||||
// Prevent repeat update() and onFocus() calls.
|
||||
FlxG.sound.list.remove(FlxG.sound.music);
|
||||
}
|
||||
|
||||
public static function emptyPartialQueue():Void
|
||||
{
|
||||
while (partialQueue.length > 0)
|
||||
@@ -523,11 +498,13 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
}
|
||||
else
|
||||
{
|
||||
promise.future.onError(function(e) {
|
||||
promise.future.onError(function(e)
|
||||
{
|
||||
soundRequest.error("Sound loading was errored or cancelled");
|
||||
});
|
||||
|
||||
soundRequest.future.onComplete(function(partialSound) {
|
||||
soundRequest.future.onComplete(function(partialSound)
|
||||
{
|
||||
var snd = FunkinSound.load(partialSound, volume, looped, autoDestroy, autoPlay, false, onComplete, onLoad);
|
||||
promise.complete(snd);
|
||||
});
|
||||
|
||||
@@ -58,7 +58,8 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
|
||||
{
|
||||
var error:Float = 0;
|
||||
|
||||
forEachAlive(function(snd) {
|
||||
forEachAlive(function(snd)
|
||||
{
|
||||
if (targetTime == null) targetTime = snd.time;
|
||||
else
|
||||
{
|
||||
@@ -85,7 +86,8 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
|
||||
// result.pause();
|
||||
result.time = this.time;
|
||||
|
||||
result.onComplete = function() {
|
||||
result.onComplete = function()
|
||||
{
|
||||
this.onComplete();
|
||||
}
|
||||
|
||||
@@ -96,14 +98,17 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
|
||||
return result;
|
||||
}
|
||||
|
||||
public dynamic function onComplete():Void {}
|
||||
public dynamic function onComplete():Void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause all the sounds in the group.
|
||||
*/
|
||||
public function pause()
|
||||
{
|
||||
forEachAlive(function(sound:FunkinSound) {
|
||||
forEachAlive(function(sound:FunkinSound)
|
||||
{
|
||||
sound.pause();
|
||||
});
|
||||
}
|
||||
@@ -113,7 +118,8 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
|
||||
*/
|
||||
public function play(forceRestart:Bool = false, startTime:Float = 0.0, ?endTime:Float)
|
||||
{
|
||||
forEachAlive(function(sound:FunkinSound) {
|
||||
forEachAlive(function(sound:FunkinSound)
|
||||
{
|
||||
if (sound.length < startTime)
|
||||
{
|
||||
// trace('Queuing sound (${sound.toString()} past its length! Skipping...)');
|
||||
@@ -128,7 +134,8 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
|
||||
*/
|
||||
public function resume()
|
||||
{
|
||||
forEachAlive(function(sound:FunkinSound) {
|
||||
forEachAlive(function(sound:FunkinSound)
|
||||
{
|
||||
sound.resume();
|
||||
});
|
||||
}
|
||||
@@ -139,7 +146,8 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
|
||||
@:nullSafety(Off)
|
||||
public function fadeIn(duration:Float, ?from:Float = 0.0, ?to:Float = 1.0, ?onComplete:FlxTween->Void):Void
|
||||
{
|
||||
forEachAlive(function(sound:FunkinSound) {
|
||||
forEachAlive(function(sound:FunkinSound)
|
||||
{
|
||||
sound.fadeIn(duration, from, to, onComplete);
|
||||
});
|
||||
}
|
||||
@@ -150,7 +158,8 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
|
||||
@:nullSafety(Off)
|
||||
public function fadeOut(duration:Float, ?to:Float = 0.0, ?onComplete:FlxTween->Void):Void
|
||||
{
|
||||
forEachAlive(function(sound:FunkinSound) {
|
||||
forEachAlive(function(sound:FunkinSound)
|
||||
{
|
||||
sound.fadeOut(duration, to, onComplete);
|
||||
});
|
||||
}
|
||||
@@ -162,7 +171,8 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
|
||||
{
|
||||
if (members != null)
|
||||
{
|
||||
forEachAlive(function(sound:FunkinSound) {
|
||||
forEachAlive(function(sound:FunkinSound)
|
||||
{
|
||||
sound.stop();
|
||||
});
|
||||
}
|
||||
@@ -198,7 +208,8 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
|
||||
|
||||
function set_time(time:Float):Float
|
||||
{
|
||||
forEachAlive(function(snd:FunkinSound) {
|
||||
forEachAlive(function(snd:FunkinSound)
|
||||
{
|
||||
// account for different offsets per sound?
|
||||
snd.time = time;
|
||||
});
|
||||
@@ -233,7 +244,8 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
|
||||
// in PlayState, adjust the code so that it only mutes the player1 vocal tracks?
|
||||
function set_volume(volume:Float):Float
|
||||
{
|
||||
forEachAlive(function(snd:FunkinSound) {
|
||||
forEachAlive(function(snd:FunkinSound)
|
||||
{
|
||||
snd.volume = volume;
|
||||
});
|
||||
|
||||
@@ -249,7 +261,8 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
|
||||
|
||||
function set_muted(muted:Bool):Bool
|
||||
{
|
||||
forEachAlive(function(snd:FunkinSound) {
|
||||
forEachAlive(function(snd:FunkinSound)
|
||||
{
|
||||
snd.muted = muted;
|
||||
});
|
||||
|
||||
@@ -269,7 +282,8 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
|
||||
{
|
||||
#if FLX_PITCH
|
||||
trace('Setting audio pitch to ' + val);
|
||||
forEachAlive(function(snd:FunkinSound) {
|
||||
forEachAlive(function(snd:FunkinSound)
|
||||
{
|
||||
snd.pitch = val;
|
||||
});
|
||||
#end
|
||||
|
||||
@@ -6,6 +6,13 @@ import funkin.audio.waveform.WaveformData;
|
||||
@:nullSafety
|
||||
class VoicesGroup extends SoundGroup
|
||||
{
|
||||
/**
|
||||
* Whenever or not the game is using the legacy vocals system (shared Voices.ogg)
|
||||
*/
|
||||
public var legacyVoiceSystem:Bool = false;
|
||||
|
||||
public var legacyVoiceUsesPlayer:Bool = false;
|
||||
|
||||
var playerVoices:Null<FlxTypedGroup<FunkinSound>>;
|
||||
var opponentVoices:Null<FlxTypedGroup<FunkinSound>>;
|
||||
|
||||
@@ -47,7 +54,8 @@ class VoicesGroup extends SoundGroup
|
||||
|
||||
function set_playerVolume(volume:Float):Float
|
||||
{
|
||||
playerVoices?.forEachAlive(function(voice:FunkinSound) {
|
||||
playerVoices?.forEachAlive(function(voice:FunkinSound)
|
||||
{
|
||||
voice.volume = volume;
|
||||
});
|
||||
return playerVolume = volume;
|
||||
@@ -55,15 +63,18 @@ class VoicesGroup extends SoundGroup
|
||||
|
||||
override function set_time(time:Float):Float
|
||||
{
|
||||
forEachAlive(function(snd) {
|
||||
forEachAlive(function(snd)
|
||||
{
|
||||
// account for different offsets per sound?
|
||||
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;
|
||||
});
|
||||
|
||||
@@ -72,7 +83,8 @@ 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;
|
||||
});
|
||||
@@ -81,7 +93,8 @@ 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;
|
||||
});
|
||||
@@ -99,7 +112,8 @@ class VoicesGroup extends SoundGroup
|
||||
|
||||
function set_opponentVolume(volume:Float):Float
|
||||
{
|
||||
opponentVoices?.forEachAlive(function(voice:FunkinSound) {
|
||||
opponentVoices?.forEachAlive(function(voice:FunkinSound)
|
||||
{
|
||||
voice.volume = volume;
|
||||
});
|
||||
return opponentVolume = volume;
|
||||
|
||||
@@ -14,6 +14,7 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
||||
{
|
||||
// public var vis:VisShit;
|
||||
var analyzer:Null<SpectralAnalyzer> = null;
|
||||
var analyzerLevelsCache:Array<Bar> = new Array<Bar>();
|
||||
|
||||
var volumes:Array<Float> = [];
|
||||
|
||||
@@ -112,11 +113,11 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
||||
*/
|
||||
function drawFFT():Void
|
||||
{
|
||||
var levels = (analyzer != null) ? analyzer.getLevels() : getDefaultLevels();
|
||||
analyzerLevelsCache = (analyzer != null) ? analyzer.getLevels(analyzerLevelsCache) : getDefaultLevels();
|
||||
|
||||
for (i in 0...min(group.members.length, levels.length))
|
||||
for (i in 0...min(group.members.length, analyzerLevelsCache.length))
|
||||
{
|
||||
var animFrame:Int = (FlxG.sound.volume == 0 || FlxG.sound.muted) ? 0 : Math.round(levels[i].value * 6);
|
||||
var animFrame:Int = (FlxG.sound.volume == 0 || FlxG.sound.muted) ? 0 : Math.round(analyzerLevelsCache[i].value * 6);
|
||||
|
||||
// don't display if we're at 0 volume from the level
|
||||
group.members[i].visible = animFrame > 0;
|
||||
|
||||
@@ -69,7 +69,8 @@ class SpectogramSprite extends FlxTypedSpriteGroup<FlxSprite>
|
||||
default:
|
||||
}
|
||||
|
||||
forEach(spr -> {
|
||||
forEach(spr ->
|
||||
{
|
||||
spr.visible = spr.ID % wavOptimiz == 0;
|
||||
});
|
||||
|
||||
@@ -176,7 +177,7 @@ class SpectogramSprite extends FlxTypedSpriteGroup<FlxSprite>
|
||||
for (i in 0...group.members.length)
|
||||
{
|
||||
// needs to be exponential growth / scaling
|
||||
// still need to optmize the FFT to run better, gets only samples needed?
|
||||
// still need to optimize the FFT to run better, gets only samples needed?
|
||||
// not every frequency is built the same!
|
||||
// 20hz to 40z is a LOT of subtle low ends, but somethin like 20,000hz to 20,020hz, the difference is NOT the same!
|
||||
|
||||
|
||||
@@ -40,7 +40,8 @@ class VisShit
|
||||
|
||||
// helpers, note that spectrum indexes suppose non-negative frequencies
|
||||
final binSize = fs / fftN;
|
||||
final indexToFreq = function(k:Int) {
|
||||
final indexToFreq = function(k:Int)
|
||||
{
|
||||
var powShit:Float = FlxMath.remapToRange(k, 0, halfN, 0, MathUtil.logBase(10, halfN)); // 4.3 is almost the log of 20Khz or so. Close enuf lol
|
||||
|
||||
return 1.0 * (Math.pow(10, powShit)); // we need the `1.0` to avoid overflows
|
||||
@@ -49,7 +50,8 @@ class VisShit
|
||||
// "melodic" band-pass filter
|
||||
final minFreq = 20.70;
|
||||
final maxFreq = 4000.01;
|
||||
final melodicBandPass = function(k:Int, s:Float) {
|
||||
final melodicBandPass = function(k:Int, s:Float)
|
||||
{
|
||||
final freq = indexToFreq(k);
|
||||
final filter = freq > minFreq - binSize && freq < maxFreq + binSize ? 1 : 0;
|
||||
return s;
|
||||
@@ -62,10 +64,7 @@ class VisShit
|
||||
while (c < samples.length)
|
||||
{
|
||||
// take a chunk (zero-padded if needed) and apply the window
|
||||
final chunk = [
|
||||
for (n in 0...fftN)
|
||||
(c + n < samples.length ? samples[c + n] : 0.0) * window(n)
|
||||
];
|
||||
final chunk = [for (n in 0...fftN) (c + n < samples.length ? samples[c + n] : 0.0) * window(n)];
|
||||
|
||||
// compute positive spectrum with sampling correction and BP filter
|
||||
final freqs = FFT.rfft(chunk).map(z -> z.scale(1 / fftN).magnitude).mapi(melodicBandPass);
|
||||
|
||||
@@ -10,58 +10,48 @@ abstract Complex({
|
||||
final imag:Float;
|
||||
})
|
||||
{
|
||||
public inline function new(real:Float, imag:Float)
|
||||
this = {real: real, imag: imag};
|
||||
public inline function new(real:Float, imag:Float) this = {real: real, imag: imag};
|
||||
|
||||
/**
|
||||
Makes a Complex number with the given Float as its real part and a zero imag part.
|
||||
**/
|
||||
@:from
|
||||
public static inline function fromReal(r:Float)
|
||||
return new Complex(r, 0);
|
||||
public static inline function fromReal(r:Float) return new Complex(r, 0);
|
||||
|
||||
/**
|
||||
Complex argument, in radians.
|
||||
**/
|
||||
public var angle(get, never):Float;
|
||||
|
||||
inline function get_angle()
|
||||
return Math.atan2(this.imag, this.real);
|
||||
inline function get_angle() return Math.atan2(this.imag, this.real);
|
||||
|
||||
/**
|
||||
Complex module.
|
||||
**/
|
||||
public var magnitude(get, never):Float;
|
||||
|
||||
inline function get_magnitude()
|
||||
return Math.sqrt(this.real * this.real + this.imag * this.imag);
|
||||
inline function get_magnitude() return Math.sqrt(this.real * this.real + this.imag * this.imag);
|
||||
|
||||
@:op(A + B)
|
||||
public inline function add(rhs:Complex):Complex
|
||||
return new Complex(this.real + rhs.real, this.imag + rhs.imag);
|
||||
public inline function add(rhs:Complex):Complex return new Complex(this.real + rhs.real, this.imag + rhs.imag);
|
||||
|
||||
@:op(A - B)
|
||||
public inline function sub(rhs:Complex):Complex
|
||||
return new Complex(this.real - rhs.real, this.imag - rhs.imag);
|
||||
public inline function sub(rhs:Complex):Complex return new Complex(this.real - rhs.real, this.imag - rhs.imag);
|
||||
|
||||
@:op(A * B)
|
||||
public inline function mult(rhs:Complex):Complex
|
||||
return new Complex(this.real * rhs.real - this.imag * rhs.imag, this.real * rhs.imag + this.imag * rhs.real);
|
||||
public inline function mult(rhs:Complex):Complex return new Complex(this.real * rhs.real - this.imag * rhs.imag, this.real * rhs.imag + this.imag * rhs.real);
|
||||
|
||||
/**
|
||||
Returns the complex conjugate, does not modify this object.
|
||||
**/
|
||||
public inline function conj():Complex
|
||||
return new Complex(this.real, -this.imag);
|
||||
public inline function conj():Complex return new Complex(this.real, -this.imag);
|
||||
|
||||
/**
|
||||
Multiplication by a real factor, does not modify this object.
|
||||
**/
|
||||
public inline function scale(k:Float):Complex
|
||||
return new Complex(this.real * k, this.imag * k);
|
||||
public inline function scale(k:Float):Complex return new Complex(this.real * k, this.imag * k);
|
||||
|
||||
public inline function copy():Complex
|
||||
return new Complex(this.real, this.imag);
|
||||
public inline function copy():Complex return new Complex(this.real, this.imag);
|
||||
|
||||
/**
|
||||
The imaginary unit.
|
||||
@@ -76,6 +66,5 @@ abstract Complex({
|
||||
/**
|
||||
Computes the complex exponential `e^(iw)`.
|
||||
**/
|
||||
public static inline function exp(w:Float)
|
||||
return new Complex(Math.cos(w), Math.sin(w));
|
||||
public static inline function exp(w:Float) return new Complex(Math.cos(w), Math.sin(w));
|
||||
}
|
||||
|
||||
@@ -19,8 +19,7 @@ class FFT
|
||||
samples from the Discrete-Time Fourier Transform (DTFT) - which is Fs-periodic -
|
||||
with a spacing of Fs/N Hz between them and a scaling factor of Fs.
|
||||
**/
|
||||
public static function fft(input:Array<Complex>):Array<Complex>
|
||||
return do_fft(input, false);
|
||||
public static function fft(input:Array<Complex>):Array<Complex> return do_fft(input, false);
|
||||
|
||||
/**
|
||||
Like `fft`, but for a real (Float) sequence input.
|
||||
@@ -41,8 +40,7 @@ class FFT
|
||||
from each other, the result will consist of N data points as sampled
|
||||
from a time signal at intervals of 1/Fs with a scaling factor of 1/Fs.
|
||||
**/
|
||||
public static function ifft(input:Array<Complex>):Array<Complex>
|
||||
return do_fft(input, true);
|
||||
public static function ifft(input:Array<Complex>):Array<Complex> return do_fft(input, true);
|
||||
|
||||
// Handles padding and scaling for forwards and inverse FFTs.
|
||||
static function do_fft(input:Array<Complex>, inverse:Bool):Array<Complex>
|
||||
|
||||
@@ -12,35 +12,28 @@ abstract OffsetArray<T>({
|
||||
final offset:Int;
|
||||
})
|
||||
{
|
||||
public inline function new(array:Array<T>, offset:Int)
|
||||
this = {array: array, offset: offset};
|
||||
public inline function new(array:Array<T>, offset:Int) this = {array: array, offset: offset};
|
||||
|
||||
public var length(get, never):Int;
|
||||
|
||||
inline function get_length()
|
||||
return this.array.length;
|
||||
inline function get_length() return this.array.length;
|
||||
|
||||
@:arrayAccess
|
||||
public inline function get(index:Int):T
|
||||
return this.array[index - this.offset];
|
||||
public inline function get(index:Int):T return this.array[index - this.offset];
|
||||
|
||||
@:arrayAccess
|
||||
public inline function set(index:Int, value:T):Void
|
||||
this.array[index - this.offset] = value;
|
||||
public inline function set(index:Int, value:T):Void this.array[index - this.offset] = value;
|
||||
|
||||
/**
|
||||
Iterates through items in their original order while providing the altered indexes as keys.
|
||||
**/
|
||||
public inline function keyValueIterator():KeyValueIterator<Int, T>
|
||||
return new OffsetArrayIterator(this.array, this.offset);
|
||||
public inline function keyValueIterator():KeyValueIterator<Int, T> return new OffsetArrayIterator(this.array, this.offset);
|
||||
|
||||
@:from
|
||||
static inline function fromArray<T>(array:Array<T>)
|
||||
return new OffsetArray(array, 0);
|
||||
static inline function fromArray<T>(array:Array<T>) return new OffsetArray(array, 0);
|
||||
|
||||
@:to
|
||||
inline function toArray()
|
||||
return this.array;
|
||||
inline function toArray() return this.array;
|
||||
|
||||
/**
|
||||
Makes a shifted version of the given `array`, where elements are in the
|
||||
@@ -76,12 +69,12 @@ private class OffsetArrayIterator<T>
|
||||
this.enumeration = 0;
|
||||
}
|
||||
|
||||
public inline function next():{key:Int, value:T}
|
||||
public inline function next():
|
||||
{key:Int, value:T}
|
||||
{
|
||||
final i = this.enumeration++;
|
||||
return {key: i + this.offset, value: this.array[i]};
|
||||
}
|
||||
|
||||
public inline function hasNext():Bool
|
||||
return this.enumeration < this.array.length;
|
||||
public inline function hasNext():Bool return this.enumeration < this.array.length;
|
||||
}
|
||||
|
||||
@@ -86,30 +86,25 @@ class Signal
|
||||
/**
|
||||
Returns the average value of an array.
|
||||
**/
|
||||
public static function mean(y:Array<Float>):Float
|
||||
return sum(y) / y.length;
|
||||
public static function mean(y:Array<Float>):Float return sum(y) / y.length;
|
||||
|
||||
/**
|
||||
Returns the global maximum.
|
||||
**/
|
||||
public static function max(y:Array<Float>):Float
|
||||
return y.fold(Math.max, y[0]);
|
||||
public static function max(y:Array<Float>):Float return y.fold(Math.max, y[0]);
|
||||
|
||||
/**
|
||||
Returns the global maximum's index.
|
||||
**/
|
||||
public static function maxi(y:Array<Float>):Int
|
||||
return y.foldi((yi, m, i) -> yi > y[m] ? i : m, 0);
|
||||
public static function maxi(y:Array<Float>):Int return y.foldi((yi, m, i) -> yi > y[m] ? i : m, 0);
|
||||
|
||||
/**
|
||||
Returns the global minimum.
|
||||
**/
|
||||
public static function min(y:Array<Float>):Float
|
||||
return y.fold(Math.min, y[0]);
|
||||
public static function min(y:Array<Float>):Float return y.fold(Math.min, y[0]);
|
||||
|
||||
/**
|
||||
Returns the global minimum's index.
|
||||
**/
|
||||
public static function mini(y:Array<Float>):Int
|
||||
return y.foldi((yi, m, i) -> yi < y[m] ? i : m, 0);
|
||||
public static function mini(y:Array<Float>):Int return y.foldi((yi, m, i) -> yi < y[m] ? i : m, 0);
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ class WaveformData
|
||||
|
||||
var ratio = newSamplesPerPoint / samplesPerPoint;
|
||||
if (ratio == 1) return result;
|
||||
if (ratio < 1) trace('[WARNING] Downsampling will result in a low precision.');
|
||||
if (ratio < 1) trace(' WARNING '.warning() + ' Downsampling will result in a low precision.');
|
||||
|
||||
var inputSampleCount = this.lenSamples();
|
||||
var outputSampleCount = Std.int(inputSampleCount * ratio);
|
||||
|
||||
@@ -3,10 +3,14 @@ package funkin.audio.waveform;
|
||||
@:nullSafety
|
||||
class WaveformDataParser
|
||||
{
|
||||
@:native("int16_max")
|
||||
static final INT16_MAX:Int = 32767;
|
||||
@:native("int16_min")
|
||||
static final INT16_MIN:Int = -32768;
|
||||
|
||||
@:native("int8_max")
|
||||
static final INT8_MAX:Int = 127;
|
||||
@:native("int8_min")
|
||||
static final INT8_MIN:Int = -128;
|
||||
|
||||
public static function interpretFlxSound(sound:Null<flixel.sound.FlxSound>):Null<WaveformData>
|
||||
|
||||
@@ -2,7 +2,12 @@ package funkin.audio.waveform;
|
||||
|
||||
import funkin.graphics.rendering.MeshRender;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.play.character.BaseCharacter.CharacterType;
|
||||
|
||||
/**
|
||||
* A sprite which displays the waveform of audio data.
|
||||
* Generate a WaveformData and provide it to this sprite.
|
||||
*/
|
||||
class WaveformSprite extends MeshRender
|
||||
{
|
||||
static final DEFAULT_COLOR:FlxColor = FlxColor.WHITE;
|
||||
@@ -13,19 +18,24 @@ class WaveformSprite extends MeshRender
|
||||
static final DEFAULT_WIDTH:Float = 100.0;
|
||||
static final DEFAULT_HEIGHT:Float = 100.0;
|
||||
|
||||
public var iconId:CharacterType;
|
||||
|
||||
/**
|
||||
* Set this to true to tell the waveform to rebuild itself.
|
||||
* Do this any time the data or drawable area of the waveform changes.
|
||||
* This often (but not always) needs to be done every frame.
|
||||
*/
|
||||
var isWaveformDirty:Bool = true;
|
||||
var isWaveformDirty:Bool;
|
||||
|
||||
/**
|
||||
* If true, force the waveform to redraw every frame.
|
||||
* Useful if the waveform's clipRect is constantly changing.
|
||||
*/
|
||||
public var forceUpdate:Bool = false;
|
||||
public var forceUpdate:Bool;
|
||||
|
||||
/**
|
||||
* The data to render the waveform with.
|
||||
*/
|
||||
public var waveformData(default, set):Null<WaveformData>;
|
||||
|
||||
function set_waveformData(value:Null<WaveformData>):Null<WaveformData>
|
||||
@@ -52,6 +62,9 @@ class WaveformSprite extends MeshRender
|
||||
return waveformColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the Waveform is horizontal or vertical.
|
||||
*/
|
||||
public var orientation(default, set):WaveformOrientation;
|
||||
|
||||
function set_orientation(value:WaveformOrientation):WaveformOrientation
|
||||
@@ -68,7 +81,7 @@ class WaveformSprite extends MeshRender
|
||||
*/
|
||||
public var time(default, set):Float;
|
||||
|
||||
function set_time(value:Float)
|
||||
function set_time(value:Float):Float
|
||||
{
|
||||
if (time == value) return value;
|
||||
|
||||
@@ -77,13 +90,22 @@ class WaveformSprite extends MeshRender
|
||||
return time;
|
||||
}
|
||||
|
||||
override function set_visible(value:Bool):Bool
|
||||
{
|
||||
if (visible == value) return value;
|
||||
|
||||
visible = value;
|
||||
isWaveformDirty = true;
|
||||
return visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* The duration, in seconds, that the waveform represents.
|
||||
* The section of waveform from `time` to `time + duration` and `width` are used to determine how many samples each pixel represents.
|
||||
*/
|
||||
public var duration(default, set):Float;
|
||||
|
||||
function set_duration(value:Float)
|
||||
function set_duration(value:Float):Float
|
||||
{
|
||||
if (duration == value) return value;
|
||||
|
||||
@@ -120,13 +142,13 @@ class WaveformSprite extends MeshRender
|
||||
*
|
||||
* NOTE: This is technically doubled since it's applied above and below the center of the waveform.
|
||||
*/
|
||||
public var minWaveformSize:Int = 1;
|
||||
public var minWaveformSize:Int;
|
||||
|
||||
/**
|
||||
* A multiplier on the size of the waveform.
|
||||
* Still capped at the width and height set for the sprite.
|
||||
*/
|
||||
public var amplitude:Float = 1.0;
|
||||
public var amplitude:Float;
|
||||
|
||||
public function new(?waveformData:WaveformData, ?orientation:WaveformOrientation, ?color:FlxColor, ?duration:Float)
|
||||
{
|
||||
@@ -135,6 +157,11 @@ class WaveformSprite extends MeshRender
|
||||
this.width = DEFAULT_WIDTH;
|
||||
this.height = DEFAULT_HEIGHT;
|
||||
|
||||
this.minWaveformSize = 1;
|
||||
this.amplitude = 1.0;
|
||||
this.isWaveformDirty = true;
|
||||
this.forceUpdate = false;
|
||||
|
||||
this.waveformData = waveformData;
|
||||
this.orientation = orientation ?? DEFAULT_ORIENTATION;
|
||||
this.time = 0.0;
|
||||
@@ -151,7 +178,7 @@ class WaveformSprite extends MeshRender
|
||||
isWaveformDirty = true;
|
||||
}
|
||||
|
||||
public override function update(elapsed:Float)
|
||||
public override function update(elapsed:Float):Void
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
@@ -170,6 +197,11 @@ class WaveformSprite extends MeshRender
|
||||
makeGraphic(1, 1, this.waveformColor);
|
||||
}
|
||||
|
||||
public override function draw():Void
|
||||
{
|
||||
super.draw();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param offsetX Horizontal offset to draw the waveform at, in samples.
|
||||
*/
|
||||
@@ -183,12 +215,12 @@ class WaveformSprite extends MeshRender
|
||||
|
||||
this.clear();
|
||||
|
||||
if (waveformData == null) return;
|
||||
if (waveformData == null || !this.visible) return;
|
||||
|
||||
// Center point of the waveform. When horizontal this is half the height, when vertical this is half the width.
|
||||
var waveformCenterPos:Int = orientation == HORIZONTAL ? Std.int(this.height / 2) : Std.int(this.width / 2);
|
||||
|
||||
var oneSecondInIndices:Int = waveformData.secondsToIndex(1);
|
||||
// var oneSecondInIndices:Int = waveformData.secondsToIndex(1)
|
||||
|
||||
var startTime:Float = time;
|
||||
var endTime:Float = time + duration;
|
||||
@@ -223,7 +255,10 @@ class WaveformSprite extends MeshRender
|
||||
|
||||
var isBeforeClipRect:Bool = (clipRect != null) && ((orientation == HORIZONTAL) ? pixelPos < clipRect.x : pixelPos < clipRect.y);
|
||||
|
||||
if (isBeforeClipRect) continue;
|
||||
if (isBeforeClipRect)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var isAfterClipRect:Bool = (clipRect != null)
|
||||
&& ((orientation == HORIZONTAL) ? pixelPos > (clipRect.x + clipRect.width) : pixelPos > (clipRect.y + clipRect.height));
|
||||
@@ -426,20 +461,40 @@ class WaveformSprite extends MeshRender
|
||||
}
|
||||
}
|
||||
|
||||
public static function buildFromWaveformData(data:WaveformData, ?orientation:WaveformOrientation, ?color:FlxColor, ?duration:Float)
|
||||
/**
|
||||
* Build a WaveformSprite from waveform data.
|
||||
* @param data The data for the waveform to use.
|
||||
* @param orientation Whether the waveform should be horizontal or vertical.
|
||||
* @param color The color of the waveform.
|
||||
* @param duration The width of the waveform, in seconds.
|
||||
*
|
||||
* @return The resulting WaveformSprite.
|
||||
*/
|
||||
public static function buildFromWaveformData(data:WaveformData, ?orientation:WaveformOrientation, ?color:FlxColor, ?duration:Float):WaveformSprite
|
||||
{
|
||||
return new WaveformSprite(data, orientation, color, duration);
|
||||
}
|
||||
|
||||
public static function buildFromFunkinSound(sound:FunkinSound, ?orientation:WaveformOrientation, ?color:FlxColor, ?duration:Float)
|
||||
/**
|
||||
* Build a WaveformSprite from a FunkinSound's waveform data.
|
||||
* @param sound The audio for the waveform to use.
|
||||
* @param orientation Whether the waveform should be horizontal or vertical.
|
||||
* @param color The color of the waveform.
|
||||
* @param duration The width of the waveform, in seconds.
|
||||
*
|
||||
* @return The resulting WaveformSprite.
|
||||
*/
|
||||
public static function buildFromFunkinSound(sound:FunkinSound, ?orientation:WaveformOrientation, ?color:FlxColor, ?duration:Float):WaveformSprite
|
||||
{
|
||||
// TODO: Build waveform data from FunkinSound.
|
||||
var data = null;
|
||||
var data = WaveformDataParser.interpretFlxSound(sound);
|
||||
|
||||
return buildFromWaveformData(data, orientation, color, duration);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The possible orientations of a waveform.
|
||||
*/
|
||||
enum WaveformOrientation
|
||||
{
|
||||
HORIZONTAL;
|
||||
|
||||
@@ -18,7 +18,7 @@ typedef EntryConstructorFunction = (String, ?Dynamic) -> Void;
|
||||
*/
|
||||
@:nullSafety
|
||||
@:generic
|
||||
@:autoBuild(funkin.util.macro.DataRegistryMacro.buildRegistry())
|
||||
@:autoBuild(funkin.util.macro.RegistryMacro.buildRegistry())
|
||||
abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructorFunction>), J, P>
|
||||
{
|
||||
/**
|
||||
@@ -78,7 +78,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||
// SCRIPTED ENTRIES
|
||||
//
|
||||
var scriptedEntryClassNames:Array<String> = getScriptedClassNames();
|
||||
log('Parsing ${scriptedEntryClassNames.length} scripted entries...');
|
||||
log(' INFO '.info() + 'Parsing ${scriptedEntryClassNames.length} scripted entries...');
|
||||
|
||||
for (entryCls in scriptedEntryClassNames)
|
||||
{
|
||||
@@ -109,10 +109,11 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||
// UNSCRIPTED ENTRIES
|
||||
//
|
||||
var entryIdList:Array<String> = DataAssets.listDataFilesInPath('${dataFilePath}/');
|
||||
var unscriptedEntryIds:Array<String> = entryIdList.filter(function(entryId:String):Bool {
|
||||
var unscriptedEntryIds:Array<String> = entryIdList.filter(function(entryId:String):Bool
|
||||
{
|
||||
return !entries.exists(entryId);
|
||||
});
|
||||
log('Parsing ${unscriptedEntryIds.length} unscripted entries...');
|
||||
log(' INFO '.info() + 'Parsing ${unscriptedEntryIds.length} unscripted entries...');
|
||||
for (entryId in unscriptedEntryIds)
|
||||
{
|
||||
try
|
||||
@@ -120,14 +121,14 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||
var entry:Null<T> = createEntry(entryId);
|
||||
if (entry != null)
|
||||
{
|
||||
trace(' Loaded entry data: ${entry}');
|
||||
log('Loaded entry data: ${entry}');
|
||||
entries.set(entry.id, entry);
|
||||
}
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
// Print the error.
|
||||
trace(' Failed to load entry data: ${entryId}');
|
||||
log(' WARNING '.warning() + ' Failed to load entry data: ${entryId}');
|
||||
trace(e);
|
||||
continue;
|
||||
}
|
||||
@@ -157,7 +158,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||
* @param id The ID of the entry.
|
||||
* @return `true` if the entry has an attached script, `false` otherwise.
|
||||
*/
|
||||
public function isScriptedEntry(id:String):Bool
|
||||
public function isScriptedEntry(id:String, ?params:Null<P>):Bool
|
||||
{
|
||||
return scriptedEntryIds.exists(id);
|
||||
}
|
||||
@@ -167,7 +168,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):Null<String>
|
||||
public function getScriptedEntryClassName(id:String, ?params:Null<P>):Null<String>
|
||||
{
|
||||
return scriptedEntryIds.get(id);
|
||||
}
|
||||
@@ -224,7 +225,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||
|
||||
function log(message:String):Void
|
||||
{
|
||||
trace('[' + registryId + '] ' + message);
|
||||
trace(' $registryId '.bold().bg_note_down() + ' $message');
|
||||
}
|
||||
|
||||
function loadEntryFile(id:String):JsonFile
|
||||
@@ -333,7 +334,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||
|
||||
function printErrors(errors:Array<json2object.Error>, id:String = ''):Void
|
||||
{
|
||||
trace('[${registryId}] Failed to parse entry data: ${id}');
|
||||
trace(' $registryId '.bold().bg_note_down() + ' ERROR '.error() + 'Failed to parse entry data: ${id}');
|
||||
|
||||
for (error in errors)
|
||||
{
|
||||
|
||||
@@ -12,27 +12,27 @@ class DataError
|
||||
switch (error)
|
||||
{
|
||||
case IncorrectType(vari, expected, pos):
|
||||
trace(' Expected field "$vari" to be of type "$expected".');
|
||||
trace(' ERROR '.error() + 'Expected field "$vari" to be of type "$expected".');
|
||||
printPos(pos);
|
||||
case IncorrectEnumValue(value, expected, pos):
|
||||
trace(' Invalid enum value (expected "$expected", got "$value")');
|
||||
trace(' ERROR '.error() + 'Invalid enum value (expected "$expected", got "$value")');
|
||||
printPos(pos);
|
||||
case InvalidEnumConstructor(value, expected, pos):
|
||||
trace(' Invalid enum constructor (epxected "$expected", got "$value")');
|
||||
trace(' ERROR '.error() + 'Invalid enum constructor (epxected "$expected", got "$value")');
|
||||
printPos(pos);
|
||||
case UninitializedVariable(vari, pos):
|
||||
trace(' Uninitialized variable "$vari"');
|
||||
trace(' ERROR '.error() + 'Uninitialized variable "$vari"');
|
||||
printPos(pos);
|
||||
case UnknownVariable(vari, pos):
|
||||
trace(' Unknown variable "$vari"');
|
||||
trace(' ERROR '.error() + 'Unknown variable "$vari"');
|
||||
printPos(pos);
|
||||
case ParserError(message, pos):
|
||||
trace(' Parsing error: ${message}');
|
||||
trace(' ERROR '.error() + 'Parsing error: ${message}');
|
||||
printPos(pos);
|
||||
case CustomFunctionException(e, pos):
|
||||
if (Std.isOfType(e, String))
|
||||
{
|
||||
trace(' ${e}');
|
||||
trace(' ERROR '.error() + '${e}');
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -49,11 +49,11 @@ class DataError
|
||||
switch (Type.typeof(e))
|
||||
{
|
||||
case TClass(c):
|
||||
trace(' [${Type.getClassName(c)}] ${e.toString()}');
|
||||
trace(' ERROR '.error() + '(${Type.getClassName(c)}) ${e.toString()}');
|
||||
case TEnum(c):
|
||||
trace(' [${Type.getEnumName(c)}] ${e.toString()}');
|
||||
trace(' ERROR '.error() + '(${Type.getEnumName(c)}) ${e.toString()}');
|
||||
default:
|
||||
trace(' [${Type.typeof(e)}] ${e.toString()}');
|
||||
trace(' ERROR '.error() + '(${Type.typeof(e)}) ${Std.string(e)}');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -243,8 +243,7 @@ class DataParse
|
||||
switch (json.value)
|
||||
{
|
||||
case JObject(fields):
|
||||
var result:LegacyNoteSection =
|
||||
{
|
||||
var result:LegacyNoteSection = {
|
||||
mustHitSection: false,
|
||||
sectionNotes: [],
|
||||
};
|
||||
|
||||
@@ -6,4 +6,6 @@ package funkin.data;
|
||||
* @see `funkin.data.BaseRegistry`
|
||||
*/
|
||||
@:autoBuild(funkin.util.macro.RegistryMacro.buildRegistry())
|
||||
interface DefaultRegistryImpl {}
|
||||
interface DefaultRegistryImpl
|
||||
{
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ typedef UnnamedAnimationData =
|
||||
|
||||
/**
|
||||
* Optionally specify an asset path to use for this specific animation.
|
||||
* ONLY for use by MultiSparrow characters.
|
||||
* ONLY for use by MultiSparrow and MultiAnimateAtlas characters.
|
||||
* @default The assetPath of the parent sprite
|
||||
*/
|
||||
@:optional
|
||||
@@ -138,4 +138,29 @@ typedef UnnamedAnimationData =
|
||||
@:default([])
|
||||
@:optional
|
||||
var frameIndices:Null<Array<Int>>;
|
||||
|
||||
/**
|
||||
* The type of animation to use.
|
||||
* Only available for texture atlases.
|
||||
* Options: "framelabel", "symbol"
|
||||
*/
|
||||
@:default("framelabel")
|
||||
@:optional
|
||||
var animType:String;
|
||||
|
||||
/**
|
||||
* The render type of the animation.
|
||||
* Only available for multi-sparrow and multi-animateatlas characters.
|
||||
* Options: "sparrow", "animateatlas"
|
||||
*/
|
||||
@:default("")
|
||||
@:optional
|
||||
var renderType:String;
|
||||
|
||||
/**
|
||||
* Various settings for the sprite.
|
||||
* Only available for texture atlases.
|
||||
*/
|
||||
@:optional
|
||||
var atlasSettings:funkin.data.stage.StageData.TextureAtlasData;
|
||||
}
|
||||
|
||||
@@ -6,12 +6,14 @@ import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.play.character.ScriptedCharacter.ScriptedAnimateAtlasCharacter;
|
||||
import funkin.play.character.ScriptedCharacter.ScriptedBaseCharacter;
|
||||
import funkin.play.character.ScriptedCharacter.ScriptedMultiSparrowCharacter;
|
||||
import funkin.play.character.ScriptedCharacter.ScriptedMultiAnimateAtlasCharacter;
|
||||
import funkin.play.character.ScriptedCharacter.ScriptedPackerCharacter;
|
||||
import funkin.play.character.ScriptedCharacter.ScriptedSparrowCharacter;
|
||||
import funkin.play.character.AnimateAtlasCharacter;
|
||||
import funkin.play.character.BaseCharacter;
|
||||
import funkin.play.character.SparrowCharacter;
|
||||
import funkin.play.character.MultiSparrowCharacter;
|
||||
import funkin.play.character.MultiAnimateAtlasCharacter;
|
||||
import funkin.play.character.PackerCharacter;
|
||||
import funkin.util.assets.DataAssets;
|
||||
import funkin.util.VersionUtil;
|
||||
@@ -49,16 +51,17 @@ class CharacterDataParser
|
||||
{
|
||||
// Clear any stages that are cached if there were any.
|
||||
clearCharacterCache();
|
||||
trace('[CHARACTER] Parsing all entries...');
|
||||
log(' INFO '.info() + 'Parsing all entries...');
|
||||
|
||||
//
|
||||
// UNSCRIPTED CHARACTERS
|
||||
//
|
||||
var charIdList:Array<String> = DataAssets.listDataFilesInPath('characters/');
|
||||
var unscriptedCharIds:Array<String> = charIdList.filter(function(charId:String):Bool {
|
||||
var unscriptedCharIds:Array<String> = charIdList.filter(function(charId:String):Bool
|
||||
{
|
||||
return !characterCache.exists(charId);
|
||||
});
|
||||
trace(' Fetching data for ${unscriptedCharIds.length} characters...');
|
||||
log('Fetching data for ${unscriptedCharIds.length} characters...');
|
||||
for (charId in unscriptedCharIds)
|
||||
{
|
||||
try
|
||||
@@ -66,7 +69,7 @@ class CharacterDataParser
|
||||
var charData:Null<CharacterData> = parseCharacterData(charId);
|
||||
if (charData != null)
|
||||
{
|
||||
trace(' Loaded character data: ${charId}');
|
||||
log('Loaded character "${charId}"');
|
||||
characterCache.set(charId, charData);
|
||||
}
|
||||
}
|
||||
@@ -86,19 +89,19 @@ class CharacterDataParser
|
||||
var scriptedCharClassNames1:Array<String> = ScriptedSparrowCharacter.listScriptClasses();
|
||||
if (scriptedCharClassNames1.length > 0)
|
||||
{
|
||||
trace(' Instantiating ${scriptedCharClassNames1.length} (Sparrow) scripted characters...');
|
||||
log('Instantiating ${scriptedCharClassNames1.length} (Sparrow) scripted characters...');
|
||||
for (charCls in scriptedCharClassNames1)
|
||||
{
|
||||
try
|
||||
{
|
||||
var character:SparrowCharacter = ScriptedSparrowCharacter.init(charCls, DEFAULT_CHAR_ID);
|
||||
trace(' Initialized character ${character.characterName}');
|
||||
var character:SparrowCharacter = ScriptedSparrowCharacter.scriptInit(charCls, DEFAULT_CHAR_ID);
|
||||
log('Loaded character ${character.characterName} (scripted: $charCls)');
|
||||
characterScriptedClass.set(character.characterId, charCls);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
trace(' FAILED to instantiate scripted Sparrow character: ${charCls}');
|
||||
trace(e);
|
||||
log(' ERROR '.error() + 'Failed to initialize scripted Sparrow character: $charCls');
|
||||
log(' ERROR '.error() + '$e');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,18 +109,19 @@ class CharacterDataParser
|
||||
var scriptedCharClassNames2:Array<String> = ScriptedPackerCharacter.listScriptClasses();
|
||||
if (scriptedCharClassNames2.length > 0)
|
||||
{
|
||||
trace(' Instantiating ${scriptedCharClassNames2.length} (Packer) scripted characters...');
|
||||
log('Instantiating ${scriptedCharClassNames2.length} (Packer) scripted characters...');
|
||||
for (charCls in scriptedCharClassNames2)
|
||||
{
|
||||
try
|
||||
{
|
||||
var character:PackerCharacter = ScriptedPackerCharacter.init(charCls, DEFAULT_CHAR_ID);
|
||||
var character:PackerCharacter = ScriptedPackerCharacter.scriptInit(charCls, DEFAULT_CHAR_ID);
|
||||
log('Loaded character ${character.characterName} (scripted: $charCls)');
|
||||
characterScriptedClass.set(character.characterId, charCls);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
trace(' FAILED to instantiate scripted Packer character: ${charCls}');
|
||||
trace(e);
|
||||
log(' ERROR '.error() + 'Failed to initialize scripted Packer character: $charCls');
|
||||
log(' ERROR '.error() + '$e');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,18 +129,19 @@ class CharacterDataParser
|
||||
var scriptedCharClassNames3:Array<String> = ScriptedMultiSparrowCharacter.listScriptClasses();
|
||||
if (scriptedCharClassNames3.length > 0)
|
||||
{
|
||||
trace(' Instantiating ${scriptedCharClassNames3.length} (Multi-Sparrow) scripted characters...');
|
||||
log('Instantiating ${scriptedCharClassNames3.length} (Multi-Sparrow) scripted characters...');
|
||||
for (charCls in scriptedCharClassNames3)
|
||||
{
|
||||
try
|
||||
{
|
||||
var character:MultiSparrowCharacter = ScriptedMultiSparrowCharacter.init(charCls, DEFAULT_CHAR_ID);
|
||||
var character:MultiSparrowCharacter = ScriptedMultiSparrowCharacter.scriptInit(charCls, DEFAULT_CHAR_ID);
|
||||
log('Loaded character ${character.characterName} (scripted: $charCls)');
|
||||
characterScriptedClass.set(character.characterId, charCls);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
trace(' FAILED to instantiate scripted Multi-Sparrow character: ${charCls}');
|
||||
trace(e);
|
||||
log(' ERROR '.error() + 'Failed to initialize scripted Multi-Sparrow character: $charCls');
|
||||
log(' ERROR '.error() + '$e');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,18 +149,39 @@ class CharacterDataParser
|
||||
var scriptedCharClassNames4:Array<String> = ScriptedAnimateAtlasCharacter.listScriptClasses();
|
||||
if (scriptedCharClassNames4.length > 0)
|
||||
{
|
||||
trace(' Instantiating ${scriptedCharClassNames4.length} (Animate Atlas) scripted characters...');
|
||||
log('Instantiating ${scriptedCharClassNames4.length} (Animate Atlas) scripted characters...');
|
||||
for (charCls in scriptedCharClassNames4)
|
||||
{
|
||||
try
|
||||
{
|
||||
var character:AnimateAtlasCharacter = ScriptedAnimateAtlasCharacter.init(charCls, DEFAULT_CHAR_ID);
|
||||
var character:AnimateAtlasCharacter = ScriptedAnimateAtlasCharacter.scriptInit(charCls, DEFAULT_CHAR_ID);
|
||||
log('Loaded character ${character.characterName} (scripted: $charCls)');
|
||||
characterScriptedClass.set(character.characterId, charCls);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
trace(' FAILED to instantiate scripted Animate Atlas character: ${charCls}');
|
||||
trace(e);
|
||||
log(' ERROR '.error() + 'Failed to initialize scripted Animate Atlas character: $charCls');
|
||||
log(' ERROR '.error() + '$e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var scriptedCharClassNames5:Array<String> = ScriptedMultiAnimateAtlasCharacter.listScriptClasses();
|
||||
if (scriptedCharClassNames5.length > 0)
|
||||
{
|
||||
log('Instantiating ${scriptedCharClassNames5.length} (Multi-Animate Atlas) scripted characters...');
|
||||
for (charCls in scriptedCharClassNames5)
|
||||
{
|
||||
try
|
||||
{
|
||||
var character:MultiAnimateAtlasCharacter = ScriptedMultiAnimateAtlasCharacter.scriptInit(charCls, DEFAULT_CHAR_ID);
|
||||
log('Loaded character ${character.characterName} (scripted: $charCls)');
|
||||
characterScriptedClass.set(character.characterId, charCls);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
log(' ERROR '.error() + 'Failed to initialize scripted Multi-Animate Atlas character: $charCls');
|
||||
log(' ERROR '.error() + '$e');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,33 +189,35 @@ class CharacterDataParser
|
||||
// NOTE: Only instantiate the ones not populated above.
|
||||
// ScriptedBaseCharacter.listScriptClasses() will pick up scripts extending the other classes.
|
||||
var scriptedCharClassNames:Array<String> = ScriptedBaseCharacter.listScriptClasses();
|
||||
scriptedCharClassNames = scriptedCharClassNames.filter(function(charCls:String):Bool {
|
||||
scriptedCharClassNames = scriptedCharClassNames.filter(function(charCls:String):Bool
|
||||
{
|
||||
return !(scriptedCharClassNames1.contains(charCls)
|
||||
|| scriptedCharClassNames2.contains(charCls)
|
||||
|| scriptedCharClassNames3.contains(charCls)
|
||||
|| scriptedCharClassNames4.contains(charCls));
|
||||
|| scriptedCharClassNames4.contains(charCls)
|
||||
|| scriptedCharClassNames5.contains(charCls));
|
||||
});
|
||||
|
||||
if (scriptedCharClassNames.length > 0)
|
||||
{
|
||||
trace(' Instantiating ${scriptedCharClassNames.length} (Base) scripted characters...');
|
||||
log('Instantiating ${scriptedCharClassNames.length} (Base) scripted characters...');
|
||||
for (charCls in scriptedCharClassNames)
|
||||
{
|
||||
var character:BaseCharacter = ScriptedBaseCharacter.init(charCls, DEFAULT_CHAR_ID, Custom);
|
||||
var character:BaseCharacter = ScriptedBaseCharacter.scriptInit(charCls, DEFAULT_CHAR_ID, Custom);
|
||||
if (character == null)
|
||||
{
|
||||
trace(' Failed to instantiate scripted character: ${charCls}');
|
||||
log(' ERROR '.error() + 'Failed to initialize scripted character: $charCls');
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
trace(' Successfully instantiated scripted character: ${charCls}');
|
||||
log('Loaded character ${character.characterName} (scripted: $charCls)');
|
||||
characterScriptedClass.set(character.characterId, charCls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trace(' Successfully loaded ${Lambda.count(characterCache)} stages.');
|
||||
log(' INFO '.info() + 'Successfully loaded ${characterCache.size()} stages.');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -219,16 +247,18 @@ class CharacterDataParser
|
||||
if (charData != null) switch (charData.renderType)
|
||||
{
|
||||
case CharacterRenderType.AnimateAtlas:
|
||||
char = ScriptedAnimateAtlasCharacter.init(charScriptClass, charId);
|
||||
char = ScriptedAnimateAtlasCharacter.scriptInit(charScriptClass, charId);
|
||||
case CharacterRenderType.MultiSparrow:
|
||||
char = ScriptedMultiSparrowCharacter.init(charScriptClass, charId);
|
||||
char = ScriptedMultiSparrowCharacter.scriptInit(charScriptClass, charId);
|
||||
case CharacterRenderType.Sparrow:
|
||||
char = ScriptedSparrowCharacter.init(charScriptClass, charId);
|
||||
char = ScriptedSparrowCharacter.scriptInit(charScriptClass, charId);
|
||||
case CharacterRenderType.Packer:
|
||||
char = ScriptedPackerCharacter.init(charScriptClass, charId);
|
||||
char = ScriptedPackerCharacter.scriptInit(charScriptClass, charId);
|
||||
case CharacterRenderType.MultiAnimateAtlas:
|
||||
char = ScriptedMultiAnimateAtlasCharacter.scriptInit(charScriptClass, charId);
|
||||
default:
|
||||
// We're going to assume that the script class does the rendering.
|
||||
char = ScriptedBaseCharacter.init(charScriptClass, charId, CharacterRenderType.Custom);
|
||||
char = ScriptedBaseCharacter.scriptInit(charScriptClass, charId, CharacterRenderType.Custom);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -243,8 +273,10 @@ class CharacterDataParser
|
||||
char = new SparrowCharacter(charId);
|
||||
case CharacterRenderType.Packer:
|
||||
char = new PackerCharacter(charId);
|
||||
case CharacterRenderType.MultiAnimateAtlas:
|
||||
char = new MultiAnimateAtlasCharacter(charId);
|
||||
default:
|
||||
trace('[WARN] Creating character with undefined renderType ${charData.renderType}');
|
||||
trace(' WARNING '.warning() + ' Creating character with undefined renderType ${charData.renderType}');
|
||||
char = new BaseCharacter(charId, CharacterRenderType.Custom);
|
||||
}
|
||||
}
|
||||
@@ -312,7 +344,7 @@ class CharacterDataParser
|
||||
|
||||
if (!Assets.exists(Paths.image(charPath)))
|
||||
{
|
||||
trace('[WARN] Character ${char} has no freeplay icon.');
|
||||
trace(' WARNING '.warning() + ' Character ${char} has no freeplay icon.');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -323,13 +355,14 @@ class CharacterDataParser
|
||||
{
|
||||
var frames = Paths.getSparrowAtlas(charPath);
|
||||
|
||||
var idleFrame:Null<FlxFrame> = frames.frames.find(function(frame:FlxFrame):Bool {
|
||||
var idleFrame:Null<FlxFrame> = frames.frames.find(function(frame:FlxFrame):Bool
|
||||
{
|
||||
return frame.name.startsWith('idle');
|
||||
});
|
||||
|
||||
if (idleFrame == null)
|
||||
{
|
||||
trace('[WARN] Character ${char} has no idle in their freeplay icon.');
|
||||
trace(' WARNING '.warning() + ' Character ${char} has no idle in their freeplay icon.');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -431,6 +464,15 @@ class CharacterDataParser
|
||||
public static final DEFAULT_SCALE:Float = 1;
|
||||
public static final DEFAULT_SCROLL:Array<Float> = [0, 0];
|
||||
public static final DEFAULT_STARTINGANIM:String = 'idle';
|
||||
public static final DEFAULT_APPLYSTAGEMATRIX:Bool = false;
|
||||
public static final DEFAULT_ANIMTYPE:String = "framelabel";
|
||||
public static final DEFAULT_ATLASSETTINGS:funkin.data.stage.StageData.TextureAtlasData = {
|
||||
swfMode: true,
|
||||
cacheOnLoad: false,
|
||||
filterQuality: 1,
|
||||
applyStageMatrix: false,
|
||||
useRenderTexture: false
|
||||
};
|
||||
|
||||
/**
|
||||
* Set unspecified parameters to their defaults.
|
||||
@@ -488,8 +530,7 @@ class CharacterDataParser
|
||||
|
||||
if (input.healthIcon == null)
|
||||
{
|
||||
input.healthIcon =
|
||||
{
|
||||
input.healthIcon = {
|
||||
id: null,
|
||||
scale: null,
|
||||
flipX: null,
|
||||
@@ -559,6 +600,16 @@ class CharacterDataParser
|
||||
input.flipX = DEFAULT_FLIPX;
|
||||
}
|
||||
|
||||
if (input.applyStageMatrix == null)
|
||||
{
|
||||
input.applyStageMatrix = DEFAULT_APPLYSTAGEMATRIX;
|
||||
}
|
||||
|
||||
if (input.atlasSettings == null)
|
||||
{
|
||||
input.atlasSettings = DEFAULT_ATLASSETTINGS;
|
||||
}
|
||||
|
||||
if (input.animations.length == 0 && input.startingAnimation != null)
|
||||
{
|
||||
return null;
|
||||
@@ -596,11 +647,21 @@ class CharacterDataParser
|
||||
{
|
||||
inputAnimation.flipY = DEFAULT_FLIPY;
|
||||
}
|
||||
|
||||
if (inputAnimation.animType == null)
|
||||
{
|
||||
inputAnimation.animType = DEFAULT_ANIMTYPE;
|
||||
}
|
||||
}
|
||||
|
||||
// All good!
|
||||
return input;
|
||||
}
|
||||
|
||||
static function log(message:String):Void
|
||||
{
|
||||
trace(' CHARACTER '.bold().bg_note_down() + ' $message');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -624,10 +685,15 @@ enum abstract CharacterRenderType(String) from String to String
|
||||
public var MultiSparrow = 'multisparrow';
|
||||
|
||||
/**
|
||||
* Renders the character using a spritesheet of symbols and JSON data.
|
||||
* Renders the character using a single spritesheet of symbols and JSON data.
|
||||
*/
|
||||
public var AnimateAtlas = 'animateatlas';
|
||||
|
||||
/**
|
||||
* Renders the character using multiple spritesheets of symbols and JSON data.
|
||||
*/
|
||||
public var MultiAnimateAtlas = 'multianimateatlas';
|
||||
|
||||
/**
|
||||
* Renders the character using a custom method.
|
||||
*/
|
||||
@@ -640,7 +706,7 @@ enum abstract CharacterRenderType(String) from String to String
|
||||
typedef CharacterData =
|
||||
{
|
||||
/**
|
||||
* The sematic version number of the character data JSON format.
|
||||
* The semantic version number of the character data JSON format.
|
||||
*/
|
||||
var version:String;
|
||||
|
||||
@@ -658,7 +724,7 @@ typedef CharacterData =
|
||||
/**
|
||||
* Behavior varies by render type:
|
||||
* - SPARROW: Path to retrieve both the spritesheet and the XML data from.
|
||||
* - PACKER: Path to retrieve both the spritsheet and the TXT data from.
|
||||
* - PACKER: Path to retrieve both the spritesheet and the TXT data from.
|
||||
*/
|
||||
var assetPath:String;
|
||||
|
||||
@@ -674,6 +740,9 @@ typedef CharacterData =
|
||||
*/
|
||||
var healthIcon:Null<HealthIconData>;
|
||||
|
||||
/**
|
||||
* Optional data about the death animation for the character.
|
||||
*/
|
||||
var death:Null<DeathData>;
|
||||
|
||||
/**
|
||||
@@ -734,6 +803,23 @@ typedef CharacterData =
|
||||
* @default false
|
||||
*/
|
||||
var flipX:Null<Bool>;
|
||||
|
||||
/**
|
||||
* NOTE: This only applies to animate atlas characters.
|
||||
*
|
||||
* Whether to apply the stage matrix, if it was exported from a symbol instance.
|
||||
* Also positions the Texture Atlas as it displays in Animate.
|
||||
* Turning this on is only recommended if you prepositioned the character in Animate.
|
||||
* For other cases, it should be turned off to act similarly to a normal FlxSprite.
|
||||
*/
|
||||
var applyStageMatrix:Null<Bool>;
|
||||
|
||||
/**
|
||||
* Various settings for the prop.
|
||||
* Only available for texture atlases.
|
||||
*/
|
||||
@:optional
|
||||
var atlasSettings:funkin.data.stage.StageData.TextureAtlasData;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,4 +23,6 @@ class SpeakerRegistry extends BaseRegistry<Speaker, SpeakerData, SpeakerEntryPar
|
||||
}
|
||||
}
|
||||
|
||||
typedef SpeakerEntryParams = {}
|
||||
typedef SpeakerEntryParams =
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package funkin.data.event;
|
||||
|
||||
import funkin.play.event.SongEvent;
|
||||
import flixel.util.FlxSort;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.util.macro.ClassMacro;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.play.event.ScriptedSongEvent;
|
||||
import funkin.play.event.SongEvent;
|
||||
import funkin.util.SortUtil;
|
||||
import funkin.util.macro.ClassMacro;
|
||||
|
||||
/**
|
||||
* This class statically handles the parsing of internal and scripted song event handlers.
|
||||
@@ -13,7 +17,7 @@ class SongEventRegistry
|
||||
{
|
||||
/**
|
||||
* Every built-in event class must be added to this list.
|
||||
* Thankfully, with the power of `SongEventMacro`, this is done automatically.
|
||||
* Thankfully, with the power of `ClassMacro`, this is done automatically.
|
||||
*/
|
||||
static final BUILTIN_EVENTS:List<Class<SongEvent>> = ClassMacro.listSubclassesOf(SongEvent);
|
||||
|
||||
@@ -64,7 +68,7 @@ class SongEventRegistry
|
||||
|
||||
for (eventCls in scriptedEventClassNames)
|
||||
{
|
||||
var event:SongEvent = ScriptedSongEvent.init(eventCls, "UKNOWN");
|
||||
var event:SongEvent = ScriptedSongEvent.scriptInit(eventCls, "UKNOWN");
|
||||
|
||||
if (event != null)
|
||||
{
|
||||
@@ -108,8 +112,7 @@ class SongEventRegistry
|
||||
|
||||
public static function handleEvent(data:SongEventData):Void
|
||||
{
|
||||
var eventKind:String = data.eventKind;
|
||||
var eventHandler:Null<SongEvent> = eventCache.get(eventKind);
|
||||
var eventHandler:Null<SongEvent> = getEvent(data.eventKind);
|
||||
|
||||
if (eventHandler != null)
|
||||
{
|
||||
@@ -117,7 +120,7 @@ class SongEventRegistry
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('WARNING: No event handler for event with kind: ${eventKind}');
|
||||
trace('WARNING: No event handler for event with kind: ${data.eventKind}');
|
||||
}
|
||||
|
||||
data.activated = true;
|
||||
@@ -132,20 +135,40 @@ class SongEventRegistry
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a list of song events and the current timestamp,
|
||||
* return a list of events that should be handled.
|
||||
* Caching the index for the next event to query greatly reduces lag.
|
||||
* Kinda nasty that it's tied to a static class though.
|
||||
*/
|
||||
public static function queryEvents(events:Array<SongEventData>, currentTime:Float):Array<SongEventData>
|
||||
static var nextEventIndex:Int = 0;
|
||||
|
||||
/**
|
||||
* Retrieve the list of events to activate this frame.
|
||||
*
|
||||
* @param events The list of available song events.
|
||||
* @param currentTime The current time in milliseconds.
|
||||
* @param startIndex The index to start querying from.
|
||||
* Defaults to the index of the last event handled.
|
||||
* @return The list of events which haven't been handled yet.
|
||||
*/
|
||||
public static function queryEvents(events:Array<SongEventData>, currentTime:Float, ?startIndex:Int):Array<SongEventData>
|
||||
{
|
||||
return events.filter(function(event:SongEventData):Bool {
|
||||
// If the event is already activated, don't activate it again.
|
||||
if (event.activated) return false;
|
||||
startIndex ??= nextEventIndex;
|
||||
|
||||
// If the event is in the future, don't activate it.
|
||||
if (event.time > currentTime) return false;
|
||||
var result:Array<SongEventData> = [];
|
||||
|
||||
return true;
|
||||
});
|
||||
for (i in startIndex...events.length)
|
||||
{
|
||||
if (events[i].activated) continue;
|
||||
|
||||
if (events[i].time > currentTime)
|
||||
{
|
||||
nextEventIndex = i;
|
||||
return result;
|
||||
}
|
||||
|
||||
result.push(events[i]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,10 +199,26 @@ class SongEventRegistry
|
||||
*/
|
||||
public static function resetEvents(events:Array<SongEventData>):Void
|
||||
{
|
||||
events.sort(SortUtil.eventDataByTime.bind(FlxSort.ASCENDING));
|
||||
nextEventIndex = 0;
|
||||
allEventHandlers.resize(0);
|
||||
|
||||
for (event in events)
|
||||
{
|
||||
event.activated = false;
|
||||
// TODO: Add an onReset() method to SongEvent?
|
||||
|
||||
var handler:Null<SongEvent> = getEvent(event.eventKind);
|
||||
if (handler != null && !allEventHandlers.contains(handler)) allEventHandlers.push(handler);
|
||||
}
|
||||
}
|
||||
|
||||
static var allEventHandlers:Array<SongEvent> = [];
|
||||
|
||||
public static inline function callEvent(scriptEvent:ScriptEvent):Void
|
||||
{
|
||||
for (event in allEventHandlers)
|
||||
{
|
||||
ScriptEventDispatcher.callEvent(event, scriptEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package funkin.data.event;
|
||||
|
||||
@:nullSafety
|
||||
@:forward(name, title, type, keys, min, max, step, units, defaultValue, iterator)
|
||||
abstract SongEventSchema(SongEventSchemaRaw)
|
||||
{
|
||||
@@ -10,13 +11,22 @@ abstract SongEventSchema(SongEventSchemaRaw)
|
||||
|
||||
public function new(?fields:Array<SongEventSchemaField>)
|
||||
{
|
||||
this = fields;
|
||||
this = fields ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a SongEventSchemaField by name. This works even if the field is inside a Frame.
|
||||
* You can use array access to call this function; `schema["field_name"]`
|
||||
*
|
||||
* @param name The name of the field to retreive.
|
||||
* @return The retrieved field, or null if not found.
|
||||
*/
|
||||
@:arrayAccess
|
||||
public function getByName(name:String):SongEventSchemaField
|
||||
public function getByName(name:String):Null<SongEventSchemaField>
|
||||
{
|
||||
for (field in this)
|
||||
var allFields = listAllFields(this);
|
||||
|
||||
for (field in allFields)
|
||||
{
|
||||
if (field.name == name) return field;
|
||||
}
|
||||
@@ -24,26 +34,70 @@ abstract SongEventSchema(SongEventSchemaRaw)
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getFirstField():SongEventSchemaField
|
||||
/**
|
||||
* Return whether the field with the given name exists.
|
||||
* @param name The name of the field to check.
|
||||
* @return Whether the field exists.
|
||||
*/
|
||||
public function hasField(name:String):Bool
|
||||
{
|
||||
return abstract.getByName(name) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the first field in the schema.
|
||||
* @return The first field.
|
||||
*/
|
||||
public function getFirstField():Null<SongEventSchemaField>
|
||||
{
|
||||
return this[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a field from the schema by numeric index.
|
||||
* @param key The index of the field to retrieve.
|
||||
* @return The retrieved field.
|
||||
*/
|
||||
@:arrayAccess
|
||||
public inline function get(key:Int)
|
||||
public inline function get(key:Int):Null<SongEventSchemaField>
|
||||
{
|
||||
return this[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a field to the schema by numeric index.
|
||||
* @param k The index of the field to write.
|
||||
* @param v The new field value to write.
|
||||
* @return The assigned value.
|
||||
*/
|
||||
@:arrayAccess
|
||||
public inline function arrayWrite(k:Int, v:SongEventSchemaField):SongEventSchemaField
|
||||
{
|
||||
return this[k] = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given song event field, retrieve its default value.
|
||||
* @param name The name of the field to retrieve.
|
||||
* @return The default value of the field, or null if not found.
|
||||
*/
|
||||
public function getDefaultFieldValue(name:String):Null<Dynamic>
|
||||
{
|
||||
return getByName(name)?.defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given song event field, convert the value into a string.
|
||||
* This is particularly useful for ENUM fields.
|
||||
*
|
||||
* @param name The name of the field to display.
|
||||
* @param value The value of the field to convert.
|
||||
* @param addUnits Whether to add the units specified by the schema to the resulting string.
|
||||
* @return The resulting string.
|
||||
*/
|
||||
public function stringifyFieldValue(name:String, value:Dynamic, addUnits:Bool = true):String
|
||||
{
|
||||
var field:SongEventSchemaField = getByName(name);
|
||||
var field:Null<SongEventSchemaField> = getByName(name);
|
||||
if (field == null) return 'Unknown';
|
||||
|
||||
switch (field.type)
|
||||
@@ -62,10 +116,12 @@ abstract SongEventSchema(SongEventSchemaRaw)
|
||||
return Std.string(value);
|
||||
case SongEventFieldType.ENUM:
|
||||
var valueString:String = Std.string(value);
|
||||
for (key in field.keys.keys())
|
||||
var fieldKeys:Array<String> = field.keys?.keyValues() ?? [];
|
||||
for (key in fieldKeys)
|
||||
{
|
||||
var value:Null<Dynamic> = field.keys?.get(key) ?? null;
|
||||
// Comparing these values as strings because comparing Dynamic variables is jank.
|
||||
if (Std.string(field.keys.get(key)) == valueString) return key;
|
||||
if (Std.string(value) == valueString) return key;
|
||||
}
|
||||
return valueString;
|
||||
default:
|
||||
@@ -73,7 +129,13 @@ abstract SongEventSchema(SongEventSchemaRaw)
|
||||
}
|
||||
}
|
||||
|
||||
function addUnitsToString(value:String, field:SongEventSchemaField)
|
||||
/**
|
||||
* Apply the song event field's specified units to the value.
|
||||
* @param value The value to add the units to.
|
||||
* @param field The field to get the units from.
|
||||
* @return The resulting string.
|
||||
*/
|
||||
function addUnitsToString(value:String, field:SongEventSchemaField):String
|
||||
{
|
||||
if (field.units == null || field.units == '') return value;
|
||||
|
||||
@@ -81,10 +143,50 @@ abstract SongEventSchema(SongEventSchemaRaw)
|
||||
|
||||
return value + (NO_SPACE_UNITS.contains(unit) ? '' : ' ') + '${unit}';
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a flat list of all the fields in the schema. Frames containing children are parsed recursively.
|
||||
*
|
||||
* @param schema The song event schema schema to parse.
|
||||
* @return The array of fields, parsed recursively from the schema and its child frames.
|
||||
*/
|
||||
function listAllFields(schema:SongEventSchemaRaw):Array<SongEventSchemaField>
|
||||
{
|
||||
var result:Array<SongEventSchemaField> = [];
|
||||
|
||||
for (field in schema)
|
||||
{
|
||||
if (field.children == null)
|
||||
{
|
||||
result.push(field);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = result.concat(field.children);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all the field names in the schema, so they can be iterated over and retrieved.
|
||||
* @return The list of field names.
|
||||
*/
|
||||
public function listAllFieldNames():Array<String>
|
||||
{
|
||||
return listAllFields(this).map((field:SongEventSchemaField) -> field.name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The raw underlying data for a song event schema is an array of fields.
|
||||
*/
|
||||
typedef SongEventSchemaRaw = Array<SongEventSchemaField>;
|
||||
|
||||
/**
|
||||
* The individual fields of a song event schema.
|
||||
*/
|
||||
typedef SongEventSchemaField =
|
||||
{
|
||||
/**
|
||||
@@ -135,37 +237,58 @@ typedef SongEventSchemaField =
|
||||
*/
|
||||
?units:String,
|
||||
|
||||
/**
|
||||
* Used for FRAME values.
|
||||
* The child components that this frame contains.
|
||||
*/
|
||||
?children:SongEventSchemaRaw,
|
||||
|
||||
/**
|
||||
* Used for FRAME values.
|
||||
* Whether to make the frame be collapsible.
|
||||
*/
|
||||
?collapsible:Bool,
|
||||
|
||||
/**
|
||||
* An optional default value for the field.
|
||||
*/
|
||||
?defaultValue:Dynamic,
|
||||
}
|
||||
|
||||
/**
|
||||
* The available field types for a song event schema.
|
||||
*/
|
||||
enum abstract SongEventFieldType(String) from String to String
|
||||
{
|
||||
/**
|
||||
* The STRING type will display as a text field.
|
||||
*/
|
||||
var STRING = "string";
|
||||
public var STRING = "string";
|
||||
|
||||
/**
|
||||
* The INTEGER type will display as a text field that only accepts numbers.
|
||||
*/
|
||||
var INTEGER = "integer";
|
||||
public var INTEGER = "integer";
|
||||
|
||||
/**
|
||||
* The FLOAT type will display as a text field that only accepts numbers.
|
||||
*/
|
||||
var FLOAT = "float";
|
||||
public var FLOAT = "float";
|
||||
|
||||
/**
|
||||
* The BOOL type will display as a checkbox.
|
||||
*/
|
||||
var BOOL = "bool";
|
||||
public var BOOL = "bool";
|
||||
|
||||
/**
|
||||
* The ENUM type will display as a dropdown.
|
||||
* Make sure to specify the `keys` field in the schema.
|
||||
*/
|
||||
var ENUM = "enum";
|
||||
public var ENUM = "enum";
|
||||
|
||||
/**
|
||||
* The FRAME type will display a frame with child components.
|
||||
* Make sure to specify the `children` field in the schema.
|
||||
*/
|
||||
public var FRAME = "frame";
|
||||
}
|
||||
|
||||
@@ -49,4 +49,10 @@ typedef AlbumData =
|
||||
@:optional
|
||||
@:default([])
|
||||
public var albumTitleAnimations:Array<AnimationData>;
|
||||
|
||||
/**
|
||||
* An optional override for the Freeplay OST text.
|
||||
*/
|
||||
@:optional
|
||||
public var albumOSTName:String;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ class AlbumRegistry extends BaseRegistry<Album, AlbumData, AlbumEntryParams> imp
|
||||
* Handle breaking changes by incrementing this value
|
||||
* and adding migration to the `migrateAlbumData()` function.
|
||||
*/
|
||||
public static final ALBUM_DATA_VERSION:thx.semver.Version = '1.0.0';
|
||||
public static final ALBUM_DATA_VERSION:thx.semver.Version = '1.0.3';
|
||||
|
||||
public static final ALBUM_DATA_VERSION_RULE:thx.semver.VersionRule = '1.0.x';
|
||||
|
||||
@@ -24,4 +24,6 @@ class AlbumRegistry extends BaseRegistry<Album, AlbumData, AlbumEntryParams> imp
|
||||
}
|
||||
}
|
||||
|
||||
typedef AlbumEntryParams = {}
|
||||
typedef AlbumEntryParams =
|
||||
{
|
||||
}
|
||||
|
||||
@@ -5,5 +5,17 @@ All notable changes to this project 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).
|
||||
|
||||
## [1.0.3]
|
||||
### Added
|
||||
- Added `albumOSTName` to change the OST at the top right of the Freeplay menu.
|
||||
|
||||
## [1.0.2]
|
||||
### Added
|
||||
- Added `albumTitleOffsets` to change the horizontal and vertical position of the album title.
|
||||
|
||||
## [1.0.1]
|
||||
### Added
|
||||
- Added `albumTitleAnimations` to modify animation data for the album art.
|
||||
|
||||
## [1.0.0]
|
||||
Initial release.
|
||||
|
||||
@@ -103,6 +103,10 @@ class PlayerFreeplayDJData
|
||||
var assetPath:String;
|
||||
var animations:Array<AnimationData>;
|
||||
|
||||
@:optional
|
||||
@:default(false)
|
||||
var applyStageMatrix:Bool;
|
||||
|
||||
@:optional
|
||||
@:default("BOYFRIEND")
|
||||
var text1:String;
|
||||
@@ -125,17 +129,33 @@ class PlayerFreeplayDJData
|
||||
var charSelect:Null<PlayerFreeplayDJCharSelectData>;
|
||||
|
||||
@:optional
|
||||
var cartoon:Null<PlayerFreeplayDJCartoonData>;
|
||||
var fistPump:Null<PlayerFreeplayDJFistPumpData>;
|
||||
|
||||
@:optional
|
||||
var fistPump:Null<PlayerFreeplayDJFistPumpData>;
|
||||
public var atlasSettings:funkin.data.stage.StageData.TextureAtlasData;
|
||||
|
||||
@:optional
|
||||
@:default("animateatlas")
|
||||
public var renderType:Null<String>;
|
||||
|
||||
@:optional
|
||||
@:default("")
|
||||
public var scriptClass:Null<String>;
|
||||
|
||||
@:optional
|
||||
@:default(false)
|
||||
public var useAnimatePosition:Bool;
|
||||
|
||||
@:optional
|
||||
@:default([0, 0])
|
||||
var offsets:Array<Float>;
|
||||
|
||||
public function new()
|
||||
{
|
||||
animationMap = new Map();
|
||||
}
|
||||
|
||||
function mapAnimations()
|
||||
function mapAnimations():Void
|
||||
{
|
||||
if (animationMap == null) animationMap = new Map();
|
||||
if (prefixToOffsetsMap == null) prefixToOffsetsMap = new Map();
|
||||
@@ -149,9 +169,37 @@ class PlayerFreeplayDJData
|
||||
}
|
||||
}
|
||||
|
||||
public function getAtlasPath():String
|
||||
public inline function getAssetPath():String return assetPath; // return assetPath;
|
||||
|
||||
public inline function getAnimationsList():Array<AnimationData> return animations;
|
||||
|
||||
public function useApplyStageMatrix():Bool
|
||||
{
|
||||
return Paths.animateAtlas(assetPath);
|
||||
return applyStageMatrix;
|
||||
}
|
||||
|
||||
public function getGlobalOffsets():Array<Float>
|
||||
{
|
||||
return offsets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normally, we'd let `FunkinSprite` handle the settings validation, but
|
||||
* Freeplay DJs have a special case where the turntable lights use a movieclip
|
||||
* that remains static without SWF mode enabled!
|
||||
* So we have to manually validate the settings to have SWF mode enabled by default.
|
||||
*
|
||||
* @return The configuration for the texture atlas.
|
||||
*/
|
||||
public function getAtlasSettings():funkin.graphics.FunkinSprite.AtlasSpriteSettings
|
||||
{
|
||||
return {
|
||||
swfMode: atlasSettings?.swfMode ?? true,
|
||||
cacheOnLoad: atlasSettings?.cacheOnLoad ?? false,
|
||||
filterQuality: cast atlasSettings?.filterQuality ?? animate.FlxAnimateFrames.FilterQuality.MEDIUM,
|
||||
applyStageMatrix: atlasSettings?.applyStageMatrix ?? false,
|
||||
useRenderTexture: atlasSettings?.useRenderTexture ?? false
|
||||
}
|
||||
}
|
||||
|
||||
public function getFreeplayDJText(index:Int):String
|
||||
@@ -190,33 +238,6 @@ class PlayerFreeplayDJData
|
||||
return getAnimationOffsetsByPrefix(getAnimationPrefix(name));
|
||||
}
|
||||
|
||||
// TODO: These should really be frame labels, ehe.
|
||||
|
||||
public function getCartoonSoundClickFrame():Int
|
||||
{
|
||||
return cartoon?.soundClickFrame ?? 80;
|
||||
}
|
||||
|
||||
public function getCartoonSoundCartoonFrame():Int
|
||||
{
|
||||
return cartoon?.soundCartoonFrame ?? 85;
|
||||
}
|
||||
|
||||
public function getCartoonLoopBlinkFrame():Int
|
||||
{
|
||||
return cartoon?.loopBlinkFrame ?? 112;
|
||||
}
|
||||
|
||||
public function getCartoonLoopFrame():Int
|
||||
{
|
||||
return cartoon?.loopFrame ?? 166;
|
||||
}
|
||||
|
||||
public function getCartoonChannelChangeFrame():Int
|
||||
{
|
||||
return cartoon?.channelChangeFrame ?? 60;
|
||||
}
|
||||
|
||||
public function getFistPumpIntroStartFrame():Int
|
||||
{
|
||||
return fistPump?.introStartFrame ?? 0;
|
||||
@@ -334,6 +355,10 @@ typedef PlayerResultsAnimationData =
|
||||
*/
|
||||
var renderType:String;
|
||||
|
||||
@:optional
|
||||
@:default(false)
|
||||
var applyStageMatrix:Bool;
|
||||
|
||||
@:optional
|
||||
var assetPath:Null<String>;
|
||||
|
||||
@@ -384,15 +409,6 @@ typedef PlayerFreeplayDJCharSelectData =
|
||||
var transitionDelay:Float;
|
||||
}
|
||||
|
||||
typedef PlayerFreeplayDJCartoonData =
|
||||
{
|
||||
var soundClickFrame:Int;
|
||||
var soundCartoonFrame:Int;
|
||||
var loopBlinkFrame:Int;
|
||||
var loopFrame:Int;
|
||||
var channelChangeFrame:Int;
|
||||
}
|
||||
|
||||
typedef PlayerFreeplayDJFistPumpData =
|
||||
{
|
||||
@:default(0)
|
||||
|
||||
@@ -70,7 +70,7 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData, PlayerE
|
||||
public function hasNewCharacter():Bool
|
||||
{
|
||||
#if (!UNLOCK_EVERYTHING)
|
||||
var charactersSeen = Save.instance.charactersSeen.clone();
|
||||
var charactersSeen = Save.instance.charactersSeen.value.clone();
|
||||
|
||||
for (charId in listEntryIds())
|
||||
{
|
||||
@@ -94,7 +94,7 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData, PlayerE
|
||||
var result = [];
|
||||
|
||||
#if (!UNLOCK_EVERYTHING)
|
||||
var charactersSeen = Save.instance.charactersSeen.clone();
|
||||
var charactersSeen = Save.instance.charactersSeen.value.clone();
|
||||
for (charId in listEntryIds())
|
||||
{
|
||||
var player = fetchEntry(charId);
|
||||
@@ -143,9 +143,11 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData, PlayerE
|
||||
#if UNLOCK_EVERYTHING
|
||||
return true;
|
||||
#else
|
||||
return Save.instance.charactersSeen.contains(characterId);
|
||||
return Save.instance.charactersSeen.value.contains(characterId);
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
typedef PlayerEntryParams = {}
|
||||
typedef PlayerEntryParams =
|
||||
{
|
||||
}
|
||||
|
||||
@@ -25,4 +25,6 @@ class FreeplayStyleRegistry extends BaseRegistry<FreeplayStyle, FreeplayStyleDat
|
||||
}
|
||||
}
|
||||
|
||||
typedef FreeplayStyleEntryParams = {}
|
||||
typedef FreeplayStyleEntryParams =
|
||||
{
|
||||
}
|
||||
|
||||
@@ -219,9 +219,17 @@ typedef NoteStyleData_Countdown =
|
||||
var audioPath:String;
|
||||
}
|
||||
|
||||
typedef NoteStyleData_HoldNote = {}
|
||||
typedef NoteStyleData_Judgement = {}
|
||||
typedef NoteStyleData_ComboNum = {}
|
||||
typedef NoteStyleData_HoldNote =
|
||||
{
|
||||
}
|
||||
|
||||
typedef NoteStyleData_Judgement =
|
||||
{
|
||||
}
|
||||
|
||||
typedef NoteStyleData_ComboNum =
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Data on animations for each direction of the strumline.
|
||||
|
||||
@@ -31,4 +31,6 @@ class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData, NoteStyle
|
||||
}
|
||||
}
|
||||
|
||||
typedef NoteStyleEntryParams = {}
|
||||
typedef NoteStyleEntryParams =
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package funkin.data.song;
|
||||
|
||||
import funkin.ui.debug.charting.ChartEditorState;
|
||||
import funkin.data.event.SongEventRegistry;
|
||||
import funkin.play.event.SongEvent;
|
||||
import funkin.data.event.SongEventSchema;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import thx.semver.Version;
|
||||
import funkin.util.tools.ICloneable;
|
||||
import funkin.play.notes.notekind.NoteKind;
|
||||
import funkin.play.notes.notekind.NoteKindManager;
|
||||
|
||||
/**
|
||||
* Data containing information about a song.
|
||||
@@ -19,7 +22,6 @@ class SongMetadata implements ICloneable<SongMetadata>
|
||||
* A semantic versioning string for the song data format.
|
||||
*
|
||||
*/
|
||||
// @:default(funkin.data.song.SongRegistry.SONG_METADATA_VERSION)
|
||||
@:jcustomparse(funkin.data.DataParse.semverVersion)
|
||||
@:jcustomwrite(funkin.data.DataWrite.semverVersion)
|
||||
public var version:Version;
|
||||
@@ -53,7 +55,6 @@ class SongMetadata implements ICloneable<SongMetadata>
|
||||
*/
|
||||
public var playData:SongPlayData;
|
||||
|
||||
@:default(funkin.data.song.SongRegistry.DEFAULT_GENERATEDBY)
|
||||
public var generatedBy:String;
|
||||
|
||||
@:optional
|
||||
@@ -121,6 +122,18 @@ class SongMetadata implements ICloneable<SongMetadata>
|
||||
// Update generatedBy and version before writing.
|
||||
updateVersionToLatest();
|
||||
|
||||
// This might not be a very wise hack, but it works.
|
||||
#if FEATURE_CHART_EDITOR
|
||||
if (Std.isOfType(FlxG.state, ChartEditorState))
|
||||
{
|
||||
var state:ChartEditorState = cast(FlxG.state, ChartEditorState);
|
||||
|
||||
final songLength:Float = @:privateAccess state.songLengthInMs;
|
||||
if (playData.previewStart > 1) playData.previewStart /= songLength;
|
||||
if (playData.previewEnd > 1) playData.previewEnd /= songLength;
|
||||
}
|
||||
#end
|
||||
|
||||
var ignoreNullOptionals = true;
|
||||
var writer = new json2object.JsonWriter<SongMetadata>(ignoreNullOptionals);
|
||||
// I believe @:jignored should be ignored by the writer?
|
||||
@@ -352,7 +365,6 @@ class SongMusicData implements ICloneable<SongMusicData>
|
||||
* A semantic versioning string for the song data format.
|
||||
*
|
||||
*/
|
||||
// @:default(funkin.data.song.SongRegistry.SONG_METADATA_VERSION)
|
||||
@:jcustomparse(funkin.data.DataParse.semverVersion)
|
||||
@:jcustomwrite(funkin.data.DataWrite.semverVersion)
|
||||
public var version:Version;
|
||||
@@ -463,6 +475,7 @@ class SongPlayData implements ICloneable<SongPlayData>
|
||||
*/
|
||||
@:optional
|
||||
@:default(['normal' => 0])
|
||||
@:order(funkin.util.Constants.DEFAULT_DIFFICULTY_LIST_FULL)
|
||||
public var ratings:Map<String, Int>;
|
||||
|
||||
/**
|
||||
@@ -485,17 +498,17 @@ class SongPlayData implements ICloneable<SongPlayData>
|
||||
* @since `2.2.2`
|
||||
*/
|
||||
@:optional
|
||||
@:default(0)
|
||||
public var previewStart:Int;
|
||||
@:default(funkin.util.Constants.DEFAULT_PREVIEW_START_TIME)
|
||||
public var previewStart:Float;
|
||||
|
||||
/**
|
||||
* The end time for the audio preview in Freeplay.
|
||||
* Defaults to 15 seconds in.
|
||||
* Defaults to 20% seconds in.
|
||||
* @since `2.2.2`
|
||||
*/
|
||||
@:optional
|
||||
@:default(15000)
|
||||
public var previewEnd:Int;
|
||||
@:default(funkin.util.Constants.DEFAULT_PREVIEW_END_TIME)
|
||||
public var previewEnd:Float;
|
||||
|
||||
public function new()
|
||||
{
|
||||
@@ -568,7 +581,7 @@ class SongCharacterData implements ICloneable<SongCharacterData>
|
||||
this.opponent = opponent;
|
||||
this.instrumental = instrumental;
|
||||
|
||||
this.altInstrumentals = altInstrumentals;
|
||||
this.altInstrumentals = altInstrumentals ?? [];
|
||||
this.opponentVocals = opponentVocals;
|
||||
this.playerVocals = playerVocals;
|
||||
|
||||
@@ -595,16 +608,16 @@ class SongCharacterData implements ICloneable<SongCharacterData>
|
||||
|
||||
class SongChartData implements ICloneable<SongChartData>
|
||||
{
|
||||
@:default(funkin.data.song.SongRegistry.SONG_CHART_DATA_VERSION)
|
||||
@:jcustomparse(funkin.data.DataParse.semverVersion)
|
||||
@:jcustomwrite(funkin.data.DataWrite.semverVersion)
|
||||
public var version:Version;
|
||||
|
||||
@:order(funkin.util.Constants.DEFAULT_DIFFICULTY_LIST_FULL)
|
||||
public var scrollSpeed:Map<String, Float>;
|
||||
public var events:Array<SongEventData>;
|
||||
@:order(funkin.util.Constants.DEFAULT_DIFFICULTY_LIST_FULL)
|
||||
public var notes:Map<String, Array<SongNoteData>>;
|
||||
|
||||
@:default(funkin.data.song.SongRegistry.DEFAULT_GENERATEDBY)
|
||||
public var generatedBy:String;
|
||||
|
||||
/**
|
||||
@@ -700,6 +713,10 @@ class SongChartData implements ICloneable<SongChartData>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The raw underlying data for a song event.
|
||||
* This value gets wrapped in an `abstract` to allow operators to be overloaded.
|
||||
*/
|
||||
class SongEventDataRaw implements ICloneable<SongEventDataRaw>
|
||||
{
|
||||
/**
|
||||
@@ -735,7 +752,7 @@ class SongEventDataRaw implements ICloneable<SongEventDataRaw>
|
||||
|
||||
/**
|
||||
* Whether this event has been activated.
|
||||
* This is only used internally by the game. It should not be serialized.
|
||||
* This is only used internally by the game during gameplay. It should not be serialized.
|
||||
*/
|
||||
@:jignored
|
||||
public var activated:Bool = false;
|
||||
@@ -747,9 +764,19 @@ class SongEventDataRaw implements ICloneable<SongEventDataRaw>
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The cached step time of the event. Should not be serialized in case the BPM of the song changes.
|
||||
*/
|
||||
@:jignored
|
||||
var _stepTime:Null<Float> = null;
|
||||
|
||||
/**
|
||||
* Get the position of the event in the song, in steps.
|
||||
* This value is cached for performance. Use `force` to require a recalculation.
|
||||
*
|
||||
* @param force Force the value to be recalculated.
|
||||
* @return The position of the event in the song, in steps.
|
||||
*/
|
||||
public function getStepTime(force:Bool = false):Float
|
||||
{
|
||||
if (_stepTime != null && !force) return _stepTime;
|
||||
@@ -757,6 +784,10 @@ class SongEventDataRaw implements ICloneable<SongEventDataRaw>
|
||||
return _stepTime = Conductor.instance.getTimeInSteps(this.time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone this event data, creating an independent instance with identical data.
|
||||
* @return The newly created event data.
|
||||
*/
|
||||
public function clone():SongEventDataRaw
|
||||
{
|
||||
return new SongEventDataRaw(this.time, this.eventKind, this.value);
|
||||
@@ -764,7 +795,9 @@ class SongEventDataRaw implements ICloneable<SongEventDataRaw>
|
||||
|
||||
public function valueAsStruct(?defaultKey:String = "key"):Dynamic
|
||||
{
|
||||
if (this.value == null) return {};
|
||||
if (this.value == null) return
|
||||
{
|
||||
};
|
||||
if (Std.isOfType(this.value, Array))
|
||||
{
|
||||
var result:haxe.DynamicAccess<Dynamic> = {};
|
||||
@@ -784,61 +817,128 @@ class SongEventDataRaw implements ICloneable<SongEventDataRaw>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the SongEvent handler class for this event.
|
||||
* @return The handler class, or `null` if not found.
|
||||
*/
|
||||
public function getHandler():Null<SongEvent>
|
||||
{
|
||||
return SongEventRegistry.getEvent(this.eventKind);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the SongEventSchema for this event.
|
||||
* @return The schema, or `null` if not found.
|
||||
*/
|
||||
public function getSchema():Null<SongEventSchema>
|
||||
{
|
||||
return SongEventRegistry.getEventSchema(this.eventKind);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a field from this event's data, as a `Dynamic` value.
|
||||
* @param key The name of the field to retrieve.
|
||||
* @return The field value, or `null` if not provided.
|
||||
*/
|
||||
public function getDynamic(key:String):Null<Dynamic>
|
||||
{
|
||||
return this.value == null ? null : Reflect.field(this.value, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a field from this event's data, as a `Bool` value.
|
||||
* @param key The name of the field to retrieve.
|
||||
* @return The field value, or `null` if not provided.
|
||||
*/
|
||||
public function getBool(key:String):Null<Bool>
|
||||
{
|
||||
return this.value == null ? null : cast Reflect.field(this.value, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a field from this event's data, as an `Int` value.
|
||||
* @param key The name of the field to retrieve.
|
||||
* @return The field value, or `null` if not provided.
|
||||
*/
|
||||
public function getInt(key:String):Null<Int>
|
||||
{
|
||||
if (this.value == null) return null;
|
||||
var result = Reflect.field(this.value, key);
|
||||
var result:Any = Reflect.field(this.value, key);
|
||||
if (result == null) return null;
|
||||
if (Std.isOfType(result, Int)) return result;
|
||||
if (Std.isOfType(result, String)) return Std.parseInt(cast result);
|
||||
return cast result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a field from this event's data, as a `Float` value.
|
||||
* @param key The name of the field to retrieve.
|
||||
* @return The field value, or `null` if not provided.
|
||||
*/
|
||||
public function getFloat(key:String):Null<Float>
|
||||
{
|
||||
if (this.value == null) return null;
|
||||
var result = Reflect.field(this.value, key);
|
||||
var result:Any = Reflect.field(this.value, key);
|
||||
if (result == null) return null;
|
||||
if (Std.isOfType(result, Float)) return result;
|
||||
if (Std.isOfType(result, String)) return Std.parseFloat(cast result);
|
||||
return cast result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a field from this event's data, as a `String` value.
|
||||
* @param key The name of the field to retrieve.
|
||||
* @return The field value, or `null` if not provided.
|
||||
*/
|
||||
public function getString(key:String):String
|
||||
{
|
||||
return this.value == null ? null : cast Reflect.field(this.value, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a field from this event's data, as an `Array<Dynamic>` value.
|
||||
* @param key The name of the field to retrieve.
|
||||
* @return The field value, or `null` if not provided.
|
||||
*/
|
||||
public function getArray(key:String):Array<Dynamic>
|
||||
{
|
||||
return this.value == null ? null : cast Reflect.field(this.value, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a field from this event's data, as an `Array<Bool>` value.
|
||||
* @param key The name of the field to retrieve.
|
||||
* @return The field value, or `null` if not provided.
|
||||
*/
|
||||
public function getBoolArray(key:String):Array<Bool>
|
||||
{
|
||||
return this.value == null ? null : cast Reflect.field(this.value, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a field from this event's data, as an `Array<Float>` value.
|
||||
* @param key The name of the field to retrieve.
|
||||
* @return The field value, or `null` if not provided.
|
||||
*/
|
||||
public function getFloatArray(key:String):Array<Float>
|
||||
{
|
||||
return this.value == null ? null : cast Reflect.field(this.value, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a field from this event's data, as an `Array<String>` value.
|
||||
* @param key The name of the field to retrieve.
|
||||
* @return The field value, or `null` if not provided.
|
||||
*/
|
||||
public function getStringArray(key:String):Array<Float>
|
||||
{
|
||||
return this.value == null ? null : cast Reflect.field(this.value, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the tooltip string for this particular instance of the song event.
|
||||
* @return A tooltip string, containing readable values for the event's data.
|
||||
*/
|
||||
public function buildTooltip():String
|
||||
{
|
||||
var eventHandler = getHandler();
|
||||
@@ -851,15 +951,26 @@ class SongEventDataRaw implements ICloneable<SongEventDataRaw>
|
||||
var defaultKey = eventSchema.getFirstField()?.name;
|
||||
var valueStruct:haxe.DynamicAccess<Dynamic> = valueAsStruct(defaultKey);
|
||||
|
||||
for (fieldName in eventSchema.listAllFieldNames())
|
||||
{
|
||||
var fieldValue:Dynamic = valueStruct.exists(fieldName) ? valueStruct.get(fieldName) : eventSchema.getDefaultFieldValue(fieldName);
|
||||
|
||||
var title:String = eventSchema.getByName(fieldName)?.title ?? 'UnknownField';
|
||||
var valueStr:String = eventSchema.stringifyFieldValue(fieldName, fieldValue) ?? 'UnknownValue';
|
||||
|
||||
result += '\n- ${title}: ${valueStr}';
|
||||
}
|
||||
|
||||
for (pair in valueStruct.keyValueIterator())
|
||||
{
|
||||
var key = pair.key;
|
||||
var value = pair.value;
|
||||
var fieldName:String = pair.key;
|
||||
var fieldValue:Dynamic = pair.value;
|
||||
|
||||
var title = eventSchema.getByName(key)?.title ?? 'UnknownField';
|
||||
// Already populated above.
|
||||
if (eventSchema.hasField(fieldName)) continue;
|
||||
|
||||
// if (eventSchema.stringifyFieldValue(key, value) != null) trace(eventSchema.stringifyFieldValue(key, value));
|
||||
var valueStr = eventSchema.stringifyFieldValue(key, value) ?? 'UnknownValue';
|
||||
var title:String = eventSchema.getByName(fieldName)?.title ?? 'Unknown Field ($fieldName)';
|
||||
var valueStr:String = eventSchema.stringifyFieldValue(fieldName, fieldValue) ?? 'Unknown Value ($fieldValue)';
|
||||
|
||||
result += '\n- ${title}: ${valueStr}';
|
||||
}
|
||||
@@ -880,41 +991,87 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
|
||||
this = new SongEventDataRaw(time, eventKind, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an independent copy of this event with the same underlying data.
|
||||
* @return The newly created event.
|
||||
*/
|
||||
public function clone():SongEventData
|
||||
{
|
||||
return new SongEventData(this.time, this.eventKind, this.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this event is equal to another event.
|
||||
* Ex. `event1 == event2`
|
||||
*
|
||||
* @param other The other event to compare.
|
||||
* @return Whether the events have the same time, kind, and data.
|
||||
*/
|
||||
@:op(A == B)
|
||||
public function op_equals(other:SongEventData):Bool
|
||||
{
|
||||
return this.time == other.time && this.eventKind == other.eventKind && this.value == other.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this event is not equal to another event.
|
||||
* Ex. `event1 != event2`
|
||||
*
|
||||
* @param other The other event to compare.
|
||||
* @return Whether the events have a different same time, kind, or data.
|
||||
*/
|
||||
@:op(A != B)
|
||||
public function op_notEquals(other:SongEventData):Bool
|
||||
{
|
||||
return this.time != other.time || this.eventKind != other.eventKind || this.value != other.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this event is later than another event.
|
||||
* Ex. `event1 > event2`
|
||||
*
|
||||
* @param other The other event to compare.
|
||||
* @return Bool
|
||||
*/
|
||||
@:op(A > B)
|
||||
public function op_greaterThan(other:SongEventData):Bool
|
||||
{
|
||||
return this.time > other.time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this event is earlier than another event.
|
||||
* Ex. `event1 < event2`
|
||||
*
|
||||
* @param other The other event to compare.
|
||||
* @return Whether this event is earlier than the other event.
|
||||
*/
|
||||
@:op(A < B)
|
||||
public function op_lessThan(other:SongEventData):Bool
|
||||
{
|
||||
return this.time < other.time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this event is later than or at the same time as another event.
|
||||
* Ex. `event1 >= event2`
|
||||
*
|
||||
* @param other The other event to compare.
|
||||
* @return Whether this event is later than or at the same time as the other event.
|
||||
*/
|
||||
@:op(A >= B)
|
||||
public function op_greaterThanOrEquals(other:SongEventData):Bool
|
||||
{
|
||||
return this.time >= other.time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this event is earlier than or at the same time as another event.
|
||||
* Ex. `event1 <= event2`
|
||||
*
|
||||
* @param other The other event to compare.
|
||||
* @return Whether this event is earlier than or at the same time as the other event.
|
||||
*/
|
||||
@:op(A <= B)
|
||||
public function op_lessThanOrEquals(other:SongEventData):Bool
|
||||
{
|
||||
@@ -922,7 +1079,7 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces a string representation suitable for debugging.
|
||||
* Produces a string representation of this event, suitable for debugging.
|
||||
*/
|
||||
public function toString():String
|
||||
{
|
||||
@@ -930,6 +1087,9 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The underlying data for a single note.
|
||||
*/
|
||||
class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
|
||||
{
|
||||
/**
|
||||
@@ -991,6 +1151,10 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
|
||||
return this.kind = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The parameters for the note.
|
||||
* Used for custom behavior on custom note kinds. Defaults to an empty array.
|
||||
*/
|
||||
@:alias("p")
|
||||
@:default([])
|
||||
@:optional
|
||||
@@ -1010,12 +1174,22 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
|
||||
* Strips the strumline index from the data.
|
||||
*
|
||||
* 0 = left, 1 = down, 2 = up, 3 = right
|
||||
*
|
||||
* @param strumlineSize The size of the strumline for the current song. Defaults to 4.
|
||||
* @return The direction of the note as an index.
|
||||
*/
|
||||
public inline function getDirection(strumlineSize:Int = 4):Int
|
||||
{
|
||||
return this.data % strumlineSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the direction of the note, if applicable.
|
||||
* Strips the strumline index from the data.
|
||||
*
|
||||
* @param strumlineSize The size of the strumline for the current song. Defaults to 4.
|
||||
* @return The direction of the note as a name.
|
||||
*/
|
||||
public function getDirectionName(strumlineSize:Int = 4):String
|
||||
{
|
||||
return SongNoteData.buildDirectionName(this.data, strumlineSize);
|
||||
@@ -1026,6 +1200,9 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
|
||||
* Strips the direction from the data.
|
||||
*
|
||||
* 0 = player, 1 = opponent, etc.
|
||||
*
|
||||
* @param strumlineSize The size of the strumline for the current song. Defaults to 4.
|
||||
* @return The strumline index of the note.
|
||||
*/
|
||||
public function getStrumlineIndex(strumlineSize:Int = 4):Int
|
||||
{
|
||||
@@ -1043,6 +1220,11 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
|
||||
return getStrumlineIndex(strumlineSize) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The position of the note in the song, in steps.
|
||||
* Calculated from the time and the BPM.
|
||||
* Cached for performance. Set to `null` to recalculate.
|
||||
*/
|
||||
@:jignored
|
||||
var _stepTime:Null<Float> = null;
|
||||
|
||||
@@ -1078,6 +1260,10 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
|
||||
return _stepLength = Conductor.instance.getTimeInSteps(this.time + this.length) - getStepTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the length of the hold note, in steps.
|
||||
* @param value The desired length of the hold note, in steps. Use `0` to make this a tap note.
|
||||
*/
|
||||
public function setStepLength(value:Float):Void
|
||||
{
|
||||
if (value <= 0)
|
||||
@@ -1097,16 +1283,24 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
|
||||
_stepLength = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone the `params` data for this node, creating a new independent instance.
|
||||
* @return A new array of cloned `NoteParamData` instances.
|
||||
*/
|
||||
public function cloneParams():Array<NoteParamData>
|
||||
{
|
||||
var params:Array<NoteParamData> = [];
|
||||
var newParams:Array<NoteParamData> = [];
|
||||
for (param in this.params)
|
||||
{
|
||||
params.push(param.clone());
|
||||
newParams.push(param.clone());
|
||||
}
|
||||
return params;
|
||||
return newParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone this song note data, creating a new independent instance with identical data.
|
||||
* @return The newly created song note data.
|
||||
*/
|
||||
public function clone():SongNoteDataRaw
|
||||
{
|
||||
return new SongNoteDataRaw(this.time, this.data, this.length, this.kind, cloneParams());
|
||||
@@ -1118,14 +1312,21 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
|
||||
+ (this.kind != '' ? ' [kind: ${this.kind}])' : ')');
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a tooltip string for this note, as seen when hovering in the Chart Editor.
|
||||
* @return The tooltip string to display.
|
||||
*/
|
||||
public function buildTooltip():String
|
||||
{
|
||||
if ((this.kind?.length ?? 0) == 0) return "";
|
||||
if ((this.kind?.length ?? 0) == 0) return '';
|
||||
|
||||
var result:String = 'Kind: ${this.kind}';
|
||||
var noteKind:Null<NoteKind> = NoteKindManager.getNoteKind(this.kind);
|
||||
var noteKindDesc:String = noteKind?.description ?? this.kind;
|
||||
|
||||
var result:String = 'Kind: $noteKindDesc';
|
||||
if (this.params.length == 0) return result;
|
||||
|
||||
result += "\nParams:";
|
||||
result += '\nParams:';
|
||||
|
||||
for (param in params)
|
||||
{
|
||||
@@ -1164,14 +1365,24 @@ abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this note is a hold note.
|
||||
*/
|
||||
@:jignored
|
||||
public var isHoldNote(get, never):Bool;
|
||||
|
||||
public function get_isHoldNote():Bool
|
||||
function get_isHoldNote():Bool
|
||||
{
|
||||
return this.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if two notes are equal.
|
||||
* Ex. `note1 == note2`
|
||||
*
|
||||
* @param other The other note to compare.
|
||||
* @return Whether the two notes have equal data.
|
||||
*/
|
||||
@:op(A == B)
|
||||
public function op_equals(other:SongNoteData):Bool
|
||||
{
|
||||
|
||||
@@ -24,7 +24,8 @@ class SongDataUtils
|
||||
*/
|
||||
public static function offsetSongNoteData(notes:Array<SongNoteData>, offset:Float):Array<SongNoteData>
|
||||
{
|
||||
var offsetNote = function(note:SongNoteData):SongNoteData {
|
||||
var offsetNote = function(note:SongNoteData):SongNoteData
|
||||
{
|
||||
var time:Float = note.time + offset;
|
||||
var data:Int = note.data;
|
||||
var length:Float = note.length;
|
||||
@@ -46,7 +47,8 @@ class SongDataUtils
|
||||
*/
|
||||
public static function offsetSongEventData(events:Array<SongEventData>, offset:Float):Array<SongEventData>
|
||||
{
|
||||
return events.map(function(event:SongEventData):SongEventData {
|
||||
return events.map(function(event:SongEventData):SongEventData
|
||||
{
|
||||
return new SongEventData(event.time + offset, event.eventKind, event.value);
|
||||
});
|
||||
}
|
||||
@@ -61,7 +63,8 @@ class SongDataUtils
|
||||
*/
|
||||
public static function clampSongNoteData(notes:Array<SongNoteData>, startTime:Float, endTime:Float):Array<SongNoteData>
|
||||
{
|
||||
return notes.filter(function(note:SongNoteData):Bool {
|
||||
return notes.filter(function(note:SongNoteData):Bool
|
||||
{
|
||||
return note.time >= startTime && note.time <= endTime;
|
||||
});
|
||||
}
|
||||
@@ -76,7 +79,8 @@ class SongDataUtils
|
||||
*/
|
||||
public static function clampSongEventData(events:Array<SongEventData>, startTime:Float, endTime:Float):Array<SongEventData>
|
||||
{
|
||||
return events.filter(function(event:SongEventData):Bool {
|
||||
return events.filter(function(event:SongEventData):Bool
|
||||
{
|
||||
return event.time >= startTime && event.time <= endTime;
|
||||
});
|
||||
}
|
||||
@@ -92,7 +96,8 @@ class SongDataUtils
|
||||
{
|
||||
if (notes.length == 0 || subtrahend.length == 0) return notes;
|
||||
|
||||
var result = notes.filter(function(note:SongNoteData):Bool {
|
||||
var result = notes.filter(function(note:SongNoteData):Bool
|
||||
{
|
||||
for (x in subtrahend)
|
||||
{
|
||||
// The currently iterated note is in the subtrahend array.
|
||||
@@ -117,7 +122,8 @@ class SongDataUtils
|
||||
{
|
||||
if (events.length == 0 || subtrahend.length == 0) return events;
|
||||
|
||||
return events.filter(function(event:SongEventData):Bool {
|
||||
return events.filter(function(event:SongEventData):Bool
|
||||
{
|
||||
for (x in subtrahend)
|
||||
{
|
||||
// The currently iterated event is in the subtrahend array.
|
||||
@@ -134,7 +140,8 @@ class SongDataUtils
|
||||
*/
|
||||
public static function flipNotes(notes:Array<SongNoteData>, strumlineSize:Int = 4):Array<SongNoteData>
|
||||
{
|
||||
return notes.map(function(note:SongNoteData):SongNoteData {
|
||||
return notes.map(function(note:SongNoteData):SongNoteData
|
||||
{
|
||||
var newData = note.data;
|
||||
|
||||
if (newData < strumlineSize) newData += strumlineSize;
|
||||
@@ -145,6 +152,63 @@ class SongDataUtils
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an array of notes whose note data is mirrored.
|
||||
* Does not mutate the original array.
|
||||
*
|
||||
* @param flip Flip the notes if the notes given are in both strumlines, so that result isn't inverted when mirrored.
|
||||
* @param mirrorX Mirror along the X axis, aka the directions of the notes.
|
||||
* @param mirrorY Mirror along the Y axis, aka the time of the notes.
|
||||
*/
|
||||
public static function mirrorNotes(notes:Array<SongNoteData>, strumlineSize:Int = 4, flip:Bool = false, mirrorX:Bool = true,
|
||||
mirrorY:Bool = true):Array<SongNoteData>
|
||||
{
|
||||
var minTime = notes[0].time;
|
||||
var maxTime = notes[0].time;
|
||||
var minStrumline = notes[0].data;
|
||||
var maxStrumline = notes[0].data;
|
||||
for (note in notes)
|
||||
{
|
||||
// Find the maximum and minimum time and strumline positions
|
||||
// I wish there was a better way of doing this
|
||||
if (flip)
|
||||
{
|
||||
if (note.data < minStrumline) minStrumline = note.data;
|
||||
else if (note.data > maxStrumline) maxStrumline = note.data;
|
||||
}
|
||||
if (note.time < minTime) minTime = note.time;
|
||||
else if (note.time > maxTime) maxTime = note.time;
|
||||
}
|
||||
|
||||
var timeDiff = minTime + (maxTime - minTime) / 2;
|
||||
if (flip && minStrumline < strumlineSize && strumlineSize < maxStrumline)
|
||||
{
|
||||
// Flip the notes if one of the notes is on the other strum
|
||||
// Otherwise they'll be inverted when mirrored
|
||||
notes = flipNotes(notes);
|
||||
}
|
||||
|
||||
return notes.map(function(note:SongNoteData):SongNoteData
|
||||
{
|
||||
var newData = note.data;
|
||||
var newTime = note.time;
|
||||
|
||||
if (mirrorX)
|
||||
{
|
||||
if (newData < strumlineSize) newData = strumlineSize - 1 - newData;
|
||||
else
|
||||
newData = strumlineSize + strumlineSize * 2 - 1 - newData;
|
||||
}
|
||||
if (mirrorY)
|
||||
{
|
||||
if (newTime < timeDiff) newTime += (timeDiff - newTime) * 2;
|
||||
else if (newTime > timeDiff) newTime -= (newTime - timeDiff) * 2;
|
||||
}
|
||||
|
||||
return new SongNoteData(newTime, newData, note.length, note.kind);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare an array of notes to be used as the clipboard data.
|
||||
*
|
||||
@@ -175,7 +239,8 @@ class SongDataUtils
|
||||
public static function sortNotes(notes:Array<SongNoteData>, desc:Bool = false):Array<SongNoteData>
|
||||
{
|
||||
// TODO: Modifies the array in place. Is this okay?
|
||||
notes.sort(function(a:SongNoteData, b:SongNoteData):Int {
|
||||
notes.sort(function(a:SongNoteData, b:SongNoteData):Int
|
||||
{
|
||||
return FlxSort.byValues(desc ? FlxSort.DESCENDING : FlxSort.ASCENDING, a.time, b.time);
|
||||
});
|
||||
return notes;
|
||||
@@ -187,7 +252,8 @@ class SongDataUtils
|
||||
public static function sortEvents(events:Array<SongEventData>, desc:Bool = false):Array<SongEventData>
|
||||
{
|
||||
// TODO: Modifies the array in place. Is this okay?
|
||||
events.sort(function(a:SongEventData, b:SongEventData):Int {
|
||||
events.sort(function(a:SongEventData, b:SongEventData):Int
|
||||
{
|
||||
return FlxSort.byValues(desc ? FlxSort.DESCENDING : FlxSort.ASCENDING, a.time, b.time);
|
||||
});
|
||||
return events;
|
||||
@@ -199,7 +265,8 @@ class SongDataUtils
|
||||
public static function sortTimeChanges(timeChanges:Array<SongTimeChange>, desc:Bool = false):Array<SongTimeChange>
|
||||
{
|
||||
// TODO: Modifies the array in place. Is this okay?
|
||||
timeChanges.sort(function(a:SongTimeChange, b:SongTimeChange):Int {
|
||||
timeChanges.sort(function(a:SongTimeChange, b:SongTimeChange):Int
|
||||
{
|
||||
return FlxSort.byValues(desc ? FlxSort.DESCENDING : FlxSort.ASCENDING, a.timeStamp, b.timeStamp);
|
||||
});
|
||||
return timeChanges;
|
||||
@@ -256,7 +323,8 @@ class SongDataUtils
|
||||
*/
|
||||
public static function getNotesInTimeRange(notes:Array<SongNoteData>, start:Float, end:Float):Array<SongNoteData>
|
||||
{
|
||||
return notes.filter(function(note:SongNoteData):Bool {
|
||||
return notes.filter(function(note:SongNoteData):Bool
|
||||
{
|
||||
return note.time >= start && note.time <= end;
|
||||
});
|
||||
}
|
||||
@@ -266,7 +334,8 @@ class SongDataUtils
|
||||
*/
|
||||
public static function getEventsInTimeRange(events:Array<SongEventData>, start:Float, end:Float):Array<SongEventData>
|
||||
{
|
||||
return events.filter(function(event:SongEventData):Bool {
|
||||
return events.filter(function(event:SongEventData):Bool
|
||||
{
|
||||
return event.time >= start && event.time <= end;
|
||||
});
|
||||
}
|
||||
@@ -276,7 +345,8 @@ class SongDataUtils
|
||||
*/
|
||||
public static function getNotesInDataRange(notes:Array<SongNoteData>, start:Int, end:Int):Array<SongNoteData>
|
||||
{
|
||||
return notes.filter(function(note:SongNoteData):Bool {
|
||||
return notes.filter(function(note:SongNoteData):Bool
|
||||
{
|
||||
return note.data >= start && note.data <= end;
|
||||
});
|
||||
}
|
||||
@@ -286,7 +356,8 @@ class SongDataUtils
|
||||
*/
|
||||
public static function getNotesWithData(notes:Array<SongNoteData>, data:Array<Int>):Array<SongNoteData>
|
||||
{
|
||||
return notes.filter(function(note:SongNoteData):Bool {
|
||||
return notes.filter(function(note:SongNoteData):Bool
|
||||
{
|
||||
return data.indexOf(note.data) != -1;
|
||||
});
|
||||
}
|
||||
@@ -296,7 +367,8 @@ class SongDataUtils
|
||||
*/
|
||||
public static function getEventsWithKind(events:Array<SongEventData>, kinds:Array<String>):Array<SongEventData>
|
||||
{
|
||||
return events.filter(function(event:SongEventData):Bool {
|
||||
return events.filter(function(event:SongEventData):Bool
|
||||
{
|
||||
return kinds.indexOf(event.eventKind) != -1;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ using funkin.data.song.migrator.SongDataMigrator;
|
||||
// SCRIPTED ENTRIES
|
||||
//
|
||||
var scriptedEntryClassNames:Array<String> = getScriptedClassNames();
|
||||
log('Parsing ${scriptedEntryClassNames.length} scripted entries...');
|
||||
log(' INFO '.info() + 'Parsing ${scriptedEntryClassNames.length} scripted entries...');
|
||||
|
||||
for (entryCls in scriptedEntryClassNames)
|
||||
{
|
||||
@@ -85,10 +85,12 @@ using funkin.data.song.migrator.SongDataMigrator;
|
||||
//
|
||||
// UNSCRIPTED ENTRIES
|
||||
//
|
||||
var entryIdList:Array<String> = DataAssets.listDataFilesInPath('songs/', '-metadata.json').map(function(songDataPath:String):String {
|
||||
var entryIdList:Array<String> = DataAssets.listDataFilesInPath('songs/', '-metadata.json').map(function(songDataPath:String):String
|
||||
{
|
||||
return songDataPath.split('/')[0];
|
||||
});
|
||||
var unscriptedEntryIds:Array<String> = entryIdList.filter(function(entryId:String):Bool {
|
||||
var unscriptedEntryIds:Array<String> = entryIdList.filter(function(entryId:String):Bool
|
||||
{
|
||||
return !entries.exists(entryId);
|
||||
});
|
||||
log('Parsing ${unscriptedEntryIds.length} unscripted entries...');
|
||||
@@ -99,15 +101,15 @@ using funkin.data.song.migrator.SongDataMigrator;
|
||||
var entry:Null<Song> = createEntry(entryId);
|
||||
if (entry != null)
|
||||
{
|
||||
trace(' Loaded entry data: ${entry}');
|
||||
log('Loaded entry data: ${entry}');
|
||||
entries.set(entry.id, entry);
|
||||
}
|
||||
}
|
||||
catch (e:Dynamic)
|
||||
{
|
||||
// Print the error.
|
||||
trace(' Failed to load entry data: ${entryId}');
|
||||
trace(e);
|
||||
log(' ERROR '.error() + 'Failed to load entry data: ${entryId}');
|
||||
log(' ERROR '.error() + e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -129,6 +131,29 @@ using funkin.data.song.migrator.SongDataMigrator;
|
||||
return parseEntryMetadataRaw(contents);
|
||||
}
|
||||
|
||||
public override function isScriptedEntry(id:String, ?params:Null<SongEntryParams>)
|
||||
{
|
||||
var variation:String = params?.variation ?? Constants.DEFAULT_VARIATION;
|
||||
if (variation != Constants.DEFAULT_VARIATION)
|
||||
{
|
||||
return scriptedSongVariations.exists('${id}:${variation}');
|
||||
}
|
||||
return super.isScriptedEntry(id, params);
|
||||
}
|
||||
|
||||
public override function getScriptedEntryClassName(id:String, ?params:Null<SongEntryParams>):Null<String>
|
||||
{
|
||||
var variation:String = params?.variation ?? Constants.DEFAULT_VARIATION;
|
||||
if (variation != Constants.DEFAULT_VARIATION)
|
||||
{
|
||||
final variationSongId:ScriptedSong = cast scriptedSongVariations.get('${id}:${variation}');
|
||||
@:privateAccess
|
||||
var path:String = variationSongId._asc._c.name;
|
||||
return path;
|
||||
}
|
||||
return super.getScriptedEntryClassName(id, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* We override `fetchEntry` to handle song variations!
|
||||
*/
|
||||
@@ -446,7 +471,7 @@ using funkin.data.song.migrator.SongDataMigrator;
|
||||
var entryFilePath:String = Paths.json('$dataFilePath/$id/$id-metadata${variation == Constants.DEFAULT_VARIATION ? '' : '-$variation'}');
|
||||
if (!openfl.Assets.exists(entryFilePath))
|
||||
{
|
||||
trace(' [WARN] Could not locate file $entryFilePath');
|
||||
trace(' WARNING '.bold().bg_yellow() + ' Could not locate file $entryFilePath');
|
||||
return null;
|
||||
}
|
||||
var rawJson:Null<String> = openfl.Assets.getText(entryFilePath);
|
||||
@@ -524,7 +549,7 @@ using funkin.data.song.migrator.SongDataMigrator;
|
||||
|
||||
if (character == null)
|
||||
{
|
||||
trace(' [WARN] Could not locate character $characterId');
|
||||
trace(' WARNING '.bold().bg_yellow() + ' Could not locate character $characterId');
|
||||
return allDifficulties;
|
||||
}
|
||||
|
||||
@@ -540,9 +565,11 @@ using funkin.data.song.migrator.SongDataMigrator;
|
||||
}
|
||||
}
|
||||
|
||||
allDifficulties.sort(funkin.util.SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_DIFFICULTY_LIST_FULL));
|
||||
|
||||
if (allDifficulties.length == 0)
|
||||
{
|
||||
trace(' [WARN] No difficulties found. Returning default difficulty list.');
|
||||
trace(' WARNING '.bold().bg_yellow() + ' No difficulties found. Returning default difficulty list.');
|
||||
allDifficulties = Constants.DEFAULT_DIFFICULTY_LIST.copy();
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ class ChartManifestData
|
||||
|
||||
public static final invalidIdRegex:EReg = ~/[\/\\:*?"<>|]/g;
|
||||
|
||||
@:default(funkin.data.song.importer.ChartManifestData.CHART_MANIFEST_DATA_VERSION)
|
||||
@:jcustomparse(funkin.data.DataParse.semverVersion)
|
||||
@:jcustomwrite(funkin.data.DataWrite.semverVersion)
|
||||
public var version:thx.semver.Version;
|
||||
@@ -22,6 +21,7 @@ class ChartManifestData
|
||||
* The metadata and chart data file names are derived from this.
|
||||
*/
|
||||
public var songId(default, set):String;
|
||||
|
||||
public function set_songId(value:String):String
|
||||
{
|
||||
return songId = invalidIdRegex.replace(value.trim(), '');
|
||||
|
||||
@@ -27,7 +27,9 @@ class LegacySongData
|
||||
public var notes:Either<Array<LegacyNoteSection>, LegacyNoteData>;
|
||||
public var song:String; // Song name
|
||||
|
||||
public function new() {}
|
||||
public function new()
|
||||
{
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package funkin.data.song.importer;
|
||||
|
||||
/**
|
||||
* Structure of a parsed Osu!Mania .osu file
|
||||
* Stuctured like a INI file format by CSV for HitObjects and more
|
||||
*/
|
||||
typedef OsuManiaData =
|
||||
{
|
||||
var General:
|
||||
{
|
||||
var PreviewTime:Int;
|
||||
var Mode:Int; // Should only be 3 if you are sure this is a mania beatmap
|
||||
};
|
||||
var Editor:
|
||||
{
|
||||
var DistanceSpacing:Float;
|
||||
var BeatDivisor:Int;
|
||||
var GridSize:Int;
|
||||
};
|
||||
var Metadata:
|
||||
{
|
||||
var Title:String;
|
||||
var TitleUnicode:String;
|
||||
var Artist:String;
|
||||
var ArtistUnicode:String;
|
||||
var Creator:String;
|
||||
var Version:String;
|
||||
};
|
||||
var Difficulty:
|
||||
{
|
||||
var OverallDifficulty:Int;
|
||||
var SliderMultiplier:Float;
|
||||
var SliderTickRate:Float;
|
||||
var CircleSize:Int;
|
||||
};
|
||||
var HitObjects:Array<ManiaHitObject>;
|
||||
var TimingPoints:Array<TimingPoint>;
|
||||
var Events:Array<Any>;
|
||||
}
|
||||
|
||||
class TimingPoint
|
||||
{
|
||||
public var time:Float;
|
||||
public var beatLength:Float;
|
||||
public var meter:Int;
|
||||
public var sampleSet:Int;
|
||||
public var sampleIndex:Int;
|
||||
public var volume:Int;
|
||||
public var uninherited:Int;
|
||||
public var effects:Int;
|
||||
public var bpm:Null<Float>;
|
||||
public var sv:Null<Float>;
|
||||
|
||||
public function new(time:Float, beatLength:Float, meter:Int, sampleSet:Int, sampleIndex:Int, volume:Int, uninherited:Int, effects:Int)
|
||||
{
|
||||
this.time = time;
|
||||
this.beatLength = beatLength;
|
||||
this.meter = meter;
|
||||
this.sampleSet = sampleSet;
|
||||
this.sampleIndex = sampleIndex;
|
||||
this.volume = volume;
|
||||
this.uninherited = uninherited;
|
||||
this.effects = effects;
|
||||
|
||||
// Derived values
|
||||
this.bpm = (uninherited == 1) ? (Math.round((60000 / beatLength) * 10) / 10) : null;
|
||||
this.sv = (uninherited == 0) ? (beatLength / 100) : null; // Just incase someone wants to add Scroll Velocity Support
|
||||
}
|
||||
}
|
||||
|
||||
class ManiaHitObject
|
||||
{
|
||||
public var time:Int;
|
||||
public var column:Int;
|
||||
public var holdDuration:Int;
|
||||
|
||||
public function new(time:Int, column:Int, holdDuration:Int)
|
||||
{
|
||||
this.time = time;
|
||||
this.column = column;
|
||||
this.holdDuration = holdDuration;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user