Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0971a0bb4c | ||
|
|
136e7358b1 | ||
|
|
84ff83759f | ||
|
|
9d3131bc8d | ||
|
|
90f6f80a71 | ||
|
|
e3ed153a7c | ||
|
|
66781181fa | ||
|
|
93039c3d65 | ||
|
|
a1370157a4 | ||
|
|
7ce7d7839e | ||
|
|
042dfb7442 | ||
|
|
7a3d983bba | ||
|
|
ac3e2b2560 | ||
|
|
db0944712d | ||
|
|
fb1b3cc1cd | ||
|
|
007a32dc9c | ||
|
|
9a57762a9c | ||
|
|
06fee5599c | ||
|
|
cd2b458da2 | ||
|
|
8a8160dfe7 | ||
|
|
5630e74584 | ||
|
|
067f52ca91 | ||
|
|
2eb5208c46 | ||
|
|
b5f3996236 | ||
|
|
378179d1f7 | ||
|
|
f1c1e63375 | ||
|
|
ec749c7157 | ||
|
|
e321861b9f | ||
|
|
c4ed31b246 | ||
|
|
2053f0d691 | ||
|
|
ffd76963bd | ||
|
|
4669ac03a9 | ||
|
|
cb16a8d55e | ||
|
|
e1aa25a249 | ||
|
|
67126b33fa | ||
|
|
a84ae6069e | ||
|
|
c3f982b32c | ||
|
|
67bce85343 | ||
|
|
998bbb213a | ||
|
|
8d02a3f984 | ||
|
|
85763813ae | ||
|
|
b29296288e | ||
|
|
5693d626b5 | ||
|
|
84d8c02920 | ||
|
|
ec9b5db210 | ||
|
|
7e75308423 | ||
|
|
073dbaa023 | ||
|
|
f6697f8ddc | ||
|
|
78d0ad9956 | ||
|
|
52f5c24f96 | ||
|
|
252b87c451 | ||
|
|
0389cd6760 | ||
|
|
29c860ed43 | ||
|
|
a86cfded2a | ||
|
|
3d5482660c | ||
|
|
299f4840cf | ||
|
|
8749372725 | ||
|
|
50f65177cb | ||
|
|
1c2a403dea | ||
|
|
84ce43eff7 | ||
|
|
cca997b724 | ||
|
|
298bc9496b | ||
|
|
2edb318901 | ||
|
|
15bce04a50 | ||
|
|
5c6d72d8d5 | ||
|
|
1937529c1f | ||
|
|
0bf1bead03 | ||
|
|
58508755f9 | ||
|
|
bcb7bc4cbc | ||
|
|
28d50a4567 | ||
|
|
6a113addca | ||
|
|
c7238fcfd1 | ||
|
|
29a7005d8a | ||
|
|
d23ae7c55b | ||
|
|
d11acfd83b | ||
|
|
754e2edbeb | ||
|
|
5548fd52f4 | ||
|
|
6c743b3b89 | ||
|
|
e978f5df76 | ||
|
|
3601b50e32 | ||
|
|
e535ae4ca6 | ||
|
|
ec8ecc6163 | ||
|
|
01e9888839 | ||
|
|
2c73b241e8 | ||
|
|
3ef238a2f6 | ||
|
|
690f3090b7 | ||
|
|
a41d3033d5 | ||
|
|
d02e834460 | ||
|
|
2a6eadcbff | ||
|
|
78cde16af2 | ||
|
|
4946e7f4c7 | ||
|
|
8a9b1b4727 | ||
|
|
5f64d89d0e | ||
|
|
143ef88e6f | ||
|
|
cb4ac352e6 | ||
|
|
ccb2e1889f | ||
|
|
9045ae2d9c | ||
|
|
e3a13bee8c | ||
|
|
426bff3bf5 | ||
|
|
249ff69f9f | ||
|
|
4af4c2defc | ||
|
|
76bf5df57f | ||
|
|
c46483722e | ||
|
|
69bcd0834e | ||
|
|
29e89c8acb | ||
|
|
778ce54047 | ||
|
|
47ed9d7fb2 | ||
|
|
00c1428c0f | ||
|
|
0b908b3cab | ||
|
|
50de041251 | ||
|
|
f9186b67a7 | ||
|
|
a8aeae4f7f | ||
|
|
4d73e0cc47 | ||
|
|
b03c8a9cf8 | ||
|
|
3cbba8ba29 | ||
|
|
bfee63f588 | ||
|
|
35aa8731d0 | ||
|
|
8fd84f9d13 | ||
|
|
25cafe7550 | ||
|
|
7d6fa19ff4 | ||
|
|
9f0bc2bd52 | ||
|
|
d81e09b1c3 | ||
|
|
a128afedf7 | ||
|
|
472d41a893 | ||
|
|
215e20cabe | ||
|
|
3c30575727 | ||
|
|
f30c0cff49 | ||
|
|
fc6e194d20 | ||
|
|
35005df32a | ||
|
|
62149aea39 | ||
|
|
fdddfba3be | ||
|
|
ab3b976f3c | ||
|
|
e02bb9b21c | ||
|
|
afcb57aaf0 | ||
|
|
99657da1f9 | ||
|
|
3bb981adb0 | ||
|
|
50a5304ad4 | ||
|
|
a4b59d14bf | ||
|
|
9d5cde0bf1 | ||
|
|
5efbff044b | ||
|
|
a469385cdb | ||
|
|
970816592f | ||
|
|
ba90a6abe0 | ||
|
|
d54dc6179a | ||
|
|
b5ed7f40d4 | ||
|
|
b28af79eb3 | ||
|
|
5f563f0555 | ||
|
|
1beb21e57a | ||
|
|
b6b2248aa3 | ||
|
|
d30fe61ca0 | ||
|
|
7b7d5deed5 | ||
|
|
19e6029fb2 | ||
|
|
2a5269c2a7 | ||
|
|
f7fb640e52 | ||
|
|
119148acf0 | ||
|
|
598a3bea24 | ||
|
|
b03d320c12 | ||
|
|
61d23b9985 | ||
|
|
b70983a0e4 | ||
|
|
de07497ae3 | ||
|
|
ea9dab4b73 | ||
|
|
f2b3f47e73 | ||
|
|
6e33cac9ec | ||
|
|
dab417b391 | ||
|
|
0706192cbe | ||
|
|
7fbbc5cf95 | ||
|
|
4939b09fb0 | ||
|
|
aae7dcdada | ||
|
|
e6525d0129 | ||
|
|
6efdbde8da | ||
|
|
b7c3f5ac9f | ||
|
|
811caf6b70 | ||
|
|
d710318830 | ||
|
|
1fc835494d | ||
|
|
2e9223ec72 | ||
|
|
9624b57e44 | ||
|
|
7d2b946e9c | ||
|
|
5a877a14a0 | ||
|
|
535ac0e528 | ||
|
|
35b5957715 | ||
|
|
e915b637c5 | ||
|
|
f38a95bbf7 | ||
|
|
ebfce9f990 | ||
|
|
359e4b41e5 | ||
|
|
b0c9365c56 | ||
|
|
65fddd95bb | ||
|
|
f1d3ace247 | ||
|
|
9e90e67673 | ||
|
|
3ce7cd6990 | ||
|
|
d4cd2d031c | ||
|
|
081d2d8fd8 | ||
|
|
99f4d13e47 | ||
|
|
8cad10263e | ||
|
|
8470880e4a | ||
|
|
6bde7c34a0 | ||
|
|
3ac10fb45c | ||
|
|
fb0b277a9c | ||
|
|
519a6f3412 | ||
|
|
27f700dd98 | ||
|
|
f015976521 | ||
|
|
265039b025 | ||
|
|
8c6cb72d47 | ||
|
|
b3221c0e3f | ||
|
|
c7cb03dd1d | ||
|
|
a7c0512503 | ||
|
|
74a94d0080 | ||
|
|
d214ae1747 | ||
|
|
6ee1bddacb | ||
|
|
8b96d1bbac | ||
|
|
d4e11f786c | ||
|
|
724c415016 | ||
|
|
055ae5b932 | ||
|
|
85e9dbb959 | ||
|
|
c5060b6eec | ||
|
|
df77d11896 | ||
|
|
aa2d934f22 | ||
|
|
3f6ad6f8ad | ||
|
|
45efd78931 | ||
|
|
b95e228322 | ||
|
|
c9e2e7bef5 | ||
|
|
bab4ac4902 | ||
|
|
b8d1341a21 | ||
|
|
55e0970e4c | ||
|
|
82c2d54079 | ||
|
|
c4b475e86a | ||
|
|
9fddb727b8 | ||
|
|
224d930ae6 | ||
|
|
3b9d400f57 | ||
|
|
f8d0ddef34 | ||
|
|
18edadb712 | ||
|
|
80fc0cd526 | ||
|
|
e7674ec31c | ||
|
|
0b531c5b21 | ||
|
|
8efcd1eae1 | ||
|
|
06485518db | ||
|
|
57523c9439 | ||
|
|
42544ee757 | ||
|
|
4be4570c94 | ||
|
|
d0ec26c44a | ||
|
|
8252b2a6b3 | ||
|
|
a5ac717677 | ||
|
|
ea2e6506c8 | ||
|
|
1e8d3c715b | ||
|
|
4bc2510d3c | ||
|
|
0574f8b07b | ||
|
|
11224c5999 | ||
|
|
367e3e9008 | ||
|
|
a27c4ae24f | ||
|
|
1f21e1114b | ||
|
|
16aff6e7c3 | ||
|
|
9f3af1ab94 | ||
|
|
e23a85682a | ||
|
|
fd6ca6e267 | ||
|
|
d5c7e4ffdb | ||
|
|
688abb5571 | ||
|
|
0a8719088d | ||
|
|
df87d677e1 | ||
|
|
517984d120 | ||
|
|
3fba55a422 | ||
|
|
178389ea0b | ||
|
|
d59594ee45 | ||
|
|
d3d998d994 | ||
|
|
247b86ea1d | ||
|
|
80868ccbba | ||
|
|
0e225c9bae | ||
|
|
c9d6c19420 | ||
|
|
c6bdd36959 | ||
|
|
cae7db6254 | ||
|
|
af31be5436 | ||
|
|
172c90b1bd | ||
|
|
a4cd2eb169 | ||
|
|
0aa2ccd1cd | ||
|
|
e2a51def65 | ||
|
|
47f3c6f00a | ||
|
|
d826f2a7bc | ||
|
|
fdc5bc6b79 | ||
|
|
3103768d91 | ||
|
|
10dfc96cf4 | ||
|
|
2dc4e13b00 | ||
|
|
1e9c6f0c4c | ||
|
|
f96532f104 | ||
|
|
bc86f1f459 | ||
|
|
5f60be6d75 | ||
|
|
703e899288 | ||
|
|
34d1c568ea | ||
|
|
ae6f431b6a | ||
|
|
a4bdeeaf5f | ||
|
|
6dca858fd7 | ||
|
|
2a9814e030 | ||
|
|
f0ac09f949 | ||
|
|
395de4ee95 | ||
|
|
89f4703977 | ||
|
|
dab41091af | ||
|
|
a4e977f60f | ||
|
|
1ee351c83d | ||
|
|
33df8936b1 | ||
|
|
f7f311d6f2 | ||
|
|
a9667c54f3 | ||
|
|
3970189fe9 | ||
|
|
721cecd271 | ||
|
|
e5673549cf | ||
|
|
386dc22d7b | ||
|
|
b2b16ff2d3 | ||
|
|
cdd78818a7 | ||
|
|
bfb19cc67e | ||
|
|
9eb56b4e76 | ||
|
|
07c5c5d5a7 | ||
|
|
7daedd19fc | ||
|
|
260946f44f | ||
|
|
3de7047d22 | ||
|
|
0b13e0fdd5 | ||
|
|
96f306471c | ||
|
|
9f6d29cf66 | ||
|
|
19f332ea7e | ||
|
|
8fdf04796c | ||
|
|
d3e8938888 | ||
|
|
2c80701855 | ||
|
|
b7fb6105a7 | ||
|
|
e12ed47162 | ||
|
|
bfcbc54ced | ||
|
|
3553d008cf | ||
|
|
e32dd0e504 | ||
|
|
fded5892c5 | ||
|
|
70ab000be4 | ||
|
|
cfcfc773da | ||
|
|
9f5c154b1d | ||
|
|
bea23c37a8 | ||
|
|
c81f041695 | ||
|
|
17a48bfc2b | ||
|
|
68d87a0837 | ||
|
|
6349ba3a5a | ||
|
|
5c1766a00f | ||
|
|
0dc286e1d3 | ||
|
|
2aa9e58f67 | ||
|
|
07d9ce2d5a | ||
|
|
c3fcc707f1 | ||
|
|
94856a6c32 | ||
|
|
0956b692d7 | ||
|
|
c63feaafbd | ||
|
|
d1622040b6 | ||
|
|
4cbea7d486 | ||
|
|
22b1a5d9c0 | ||
|
|
9891dc7309 | ||
|
|
e5a788de7d | ||
|
|
59fac5a809 | ||
|
|
9a242c5d00 | ||
|
|
a530d91a76 | ||
|
|
f74365f230 | ||
|
|
0ec22bcc85 | ||
|
|
7aa20223b9 | ||
|
|
c678cabbf2 | ||
|
|
5021b49915 | ||
|
|
7063fafd33 | ||
|
|
9c8142b769 | ||
|
|
96657e9e41 | ||
|
|
ee78178b20 | ||
|
|
03d9416ab1 | ||
|
|
34e93fa95a | ||
|
|
f6bd203786 | ||
|
|
06389cb5cd | ||
|
|
030afe1355 | ||
|
|
adb4bc96ca | ||
|
|
4c1b4859e7 | ||
|
|
877d64c32b | ||
|
|
af8fe0c0d2 | ||
|
|
8d27a45d17 | ||
|
|
e2c287e112 | ||
|
|
591d4a29f4 | ||
|
|
d9a3604880 | ||
|
|
7220fd7e95 | ||
|
|
c2ebd4ead6 | ||
|
|
5a4795eea2 | ||
|
|
e2d25b2721 | ||
|
|
35af057f88 | ||
|
|
d61357b80d | ||
|
|
b9b8490243 | ||
|
|
50d2a8fd9d | ||
|
|
f8a2709482 | ||
|
|
6961e6cc63 | ||
|
|
16905c8210 | ||
|
|
4f1423870c | ||
|
|
51e5eb929c | ||
|
|
77346696f8 | ||
|
|
fbcf553bd9 | ||
|
|
f3722e5da2 | ||
|
|
40b5ad7c2a | ||
|
|
e0a4818e5a | ||
|
|
b1e68c27fd | ||
|
|
a623ec77f7 | ||
|
|
89e1cb8258 | ||
|
|
55c5e01bd5 | ||
|
|
4f666e4855 | ||
|
|
e185eef7ea | ||
|
|
c45fa93786 | ||
|
|
a901677853 | ||
|
|
a61f40571d | ||
|
|
313dba6985 | ||
|
|
1021f5c2ee | ||
|
|
4cad56705a | ||
|
|
677357ba37 | ||
|
|
34d13c61de | ||
|
|
239b9b0419 | ||
|
|
33aaabe190 | ||
|
|
0e37cb9540 | ||
|
|
5c44e61ce1 | ||
|
|
df266e66d3 | ||
|
|
def2b64a41 | ||
|
|
24096bd319 | ||
|
|
3a2ca6b525 | ||
|
|
0a6350113e | ||
|
|
b37ebf1aac | ||
|
|
788f0ee026 | ||
|
|
bd144d6adb | ||
|
|
36505f75c1 | ||
|
|
f16bffb3fc | ||
|
|
b58bd07fd2 | ||
|
|
d049478c65 | ||
|
|
930f5ff5a6 | ||
|
|
2f59363432 | ||
|
|
498cd4a830 | ||
|
|
33d1112095 | ||
|
|
cdc3883173 | ||
|
|
7511de1e7a | ||
|
|
21d5bb6112 | ||
|
|
71da847158 | ||
|
|
4f562e3d6c | ||
|
|
27970e4c2d | ||
|
|
60c10aae99 | ||
|
|
0c809c828e | ||
|
|
de03da7e6f | ||
|
|
e7448cb8f7 | ||
|
|
a4f3c2cb84 | ||
|
|
78fb6e1188 | ||
|
|
ecd6100cf9 | ||
|
|
3e339916d2 | ||
|
|
737a679196 | ||
|
|
825327e1c8 | ||
|
|
364a5d5a55 | ||
|
|
0392eddba6 | ||
|
|
b27aa4c94d | ||
|
|
0a316c815f | ||
|
|
560badd6b5 | ||
|
|
1fbca9ab6a | ||
|
|
5d590654f4 | ||
|
|
2effd977f4 | ||
|
|
8385c2b8ca | ||
|
|
452747139e | ||
|
|
6cff1efc72 | ||
|
|
e7fca119f8 | ||
|
|
1fb5c31c22 | ||
|
|
7e39687994 | ||
|
|
16a6dba9df | ||
|
|
2e7eabc494 | ||
|
|
570e672296 | ||
|
|
152820f2e1 | ||
|
|
89402f8bb8 | ||
|
|
ccabded2cc | ||
|
|
af93da6cd4 | ||
|
|
b0f04779dc | ||
|
|
382af3b485 | ||
|
|
469ff6690f | ||
|
|
353aa42f5b | ||
|
|
9b712ce2aa | ||
|
|
71ecc8f651 | ||
|
|
861b869b2d | ||
|
|
ce62c240fa | ||
|
|
f10ff1d80a | ||
|
|
a7334d8ff9 | ||
|
|
82496299e7 | ||
|
|
4990ac03a4 | ||
|
|
fd5d8aa196 | ||
|
|
d02f632c0e | ||
|
|
df42ac7531 | ||
|
|
5caba54e19 | ||
|
|
dff87babe6 | ||
|
|
91e4043ce9 | ||
|
|
475a20ece8 | ||
|
|
200b3c963b | ||
|
|
6b9bdb5f2a | ||
|
|
269f3ecfbe | ||
|
|
f2433bfdcc | ||
|
|
b51ba61d07 | ||
|
|
3c208ee143 | ||
|
|
a778555a97 | ||
|
|
266bf939a9 | ||
|
|
514aa576ea | ||
|
|
8010441bef | ||
|
|
0193bde700 | ||
|
|
7cac3c95ee | ||
|
|
1089764ab6 | ||
|
|
86f61b4fdd | ||
|
|
a1714711e3 | ||
|
|
0b19b4bc25 | ||
|
|
f4c45f082f | ||
|
|
025c186ddc | ||
|
|
7940b19dbf | ||
|
|
fd06e6800c | ||
|
|
00c7767f8a | ||
|
|
c62b53ebe9 | ||
|
|
9e1d328a0f | ||
|
|
55dd1857c9 | ||
|
|
e37330f288 | ||
|
|
0d3fa41b29 | ||
|
|
7f0f0efe46 | ||
|
|
5756eff7cf | ||
|
|
e1d8e758bb | ||
|
|
cee5d53deb | ||
|
|
df588f86af | ||
|
|
78d299db4e | ||
|
|
76edf2e4e9 | ||
|
|
6e3fba4d3d | ||
|
|
af8c52a489 | ||
|
|
1b309e33ef | ||
|
|
f8b63c9968 | ||
|
|
94a08ff528 | ||
|
|
a6f8cf0141 | ||
|
|
7d3d83b23c | ||
|
|
ec07a476c1 | ||
|
|
0aa9c41c5b | ||
|
|
82eea221d8 | ||
|
|
8a2d8a9941 | ||
|
|
4dad17d241 | ||
|
|
8a03cc7b2a | ||
|
|
9d85d423c6 | ||
|
|
eb5bf449a4 | ||
|
|
0270df6b23 | ||
|
|
0e46f91a60 | ||
|
|
b1fe983c21 | ||
|
|
6679ec5519 | ||
|
|
0d33cd29a9 | ||
|
|
988e819e9b | ||
|
|
c209e0e192 | ||
|
|
f4e42375d1 | ||
|
|
6e1ff4a6b1 | ||
|
|
f5826e3a35 | ||
|
|
ce559106f6 | ||
|
|
a0047a482f | ||
|
|
b47c695cb7 | ||
|
|
78d9d2ad21 | ||
|
|
9ff3e0435a | ||
|
|
21fe5f12be | ||
|
|
e105f933ac | ||
|
|
09fe8c1196 | ||
|
|
cce6434570 | ||
|
|
c3d497a9d9 | ||
|
|
ae648418c2 | ||
|
|
62d0b33689 | ||
|
|
d43042fd36 | ||
|
|
960c151985 | ||
|
|
f4968b0ae5 | ||
|
|
8200a08152 | ||
|
|
270748d108 | ||
|
|
3b7e656793 | ||
|
|
3c814dacf0 | ||
|
|
c9c81fae51 | ||
|
|
f4a2ac4dde | ||
|
|
cb02ea7da5 | ||
|
|
c8322c479c | ||
|
|
4f46a8e4d0 | ||
|
|
e2297af1cd | ||
|
|
eb78b754f8 | ||
|
|
bef2e98222 | ||
|
|
68b7610225 | ||
|
|
9de51fd7d1 | ||
|
|
e4e4c66c6c | ||
|
|
5e04628480 | ||
|
|
195010c7ed | ||
|
|
f1af005cf3 | ||
|
|
ee584d780c | ||
|
|
41bd0246ac | ||
|
|
d07564cdc2 | ||
|
|
82a6ca5916 | ||
|
|
d3209e57b7 | ||
|
|
d5a53855d3 | ||
|
|
dc9dbe84fa | ||
|
|
bf1d4031af | ||
|
|
0e7fad090b | ||
|
|
f3624f7e76 | ||
|
|
3db0cf3e0a | ||
|
|
f07777116d | ||
|
|
1f1d467156 | ||
|
|
4dfb469552 | ||
|
|
a15695c3dc | ||
|
|
35c8b2c818 | ||
|
|
a2838fffd0 | ||
|
|
aaab24850d | ||
|
|
1831daac38 | ||
|
|
6e301bf648 | ||
|
|
964f6878c3 | ||
|
|
8778ab1d0e | ||
|
|
59ad15aee4 | ||
|
|
c8caeb42de | ||
|
|
c60299e51a | ||
|
|
4164329ca9 | ||
|
|
9b8961d4b5 | ||
|
|
04c88baff0 | ||
|
|
a071e90746 | ||
|
|
1515719a0f | ||
|
|
0110936a3e | ||
|
|
bfa0bafc6f | ||
|
|
7547875535 | ||
|
|
729745899e | ||
|
|
558ec53532 | ||
|
|
3f242eb96c | ||
|
|
f90c2bda99 | ||
|
|
b0a842c2c7 | ||
|
|
8d9071d4fd | ||
|
|
e26a99d128 | ||
|
|
ba96b11196 | ||
|
|
563905ebd4 | ||
|
|
a0ab216617 | ||
|
|
4bcfbc957f | ||
|
|
ad93706b1d | ||
|
|
73fc855f8e | ||
|
|
ff1ab1eb42 | ||
|
|
4d81aa0fe6 | ||
|
|
691da783fc | ||
|
|
a93cd05aeb | ||
|
|
ee44981995 | ||
|
|
5e1d25e791 | ||
|
|
f8a0627fd2 | ||
|
|
f8232911cb | ||
|
|
35ba5663a1 | ||
|
|
fbe224731a | ||
|
|
d7794374d9 | ||
|
|
b6a8b62950 | ||
|
|
66ef6a6290 | ||
|
|
894ea5cc6b | ||
|
|
6f66488a04 | ||
|
|
416d60000f | ||
|
|
aed100df47 | ||
|
|
9d3c043f8a | ||
|
|
2547fbb8f4 | ||
|
|
2993e17278 | ||
|
|
0c5815c176 | ||
|
|
6116ec3639 | ||
|
|
492874ce32 | ||
|
|
d4cbe74939 | ||
|
|
ace762413e | ||
|
|
97e69c6343 | ||
|
|
f76309c91e | ||
|
|
1fe44fa368 | ||
|
|
4746c1da0e | ||
|
|
f9bccb057a | ||
|
|
492af8add4 | ||
|
|
44d9785317 | ||
|
|
c41d846df5 | ||
|
|
764cdee63d | ||
|
|
0fe726cefa | ||
|
|
437cc68ba7 | ||
|
|
93475ae8aa | ||
|
|
d9b9854d91 | ||
|
|
9a563ec46b | ||
|
|
94fe4a06b9 | ||
|
|
c8d019da2f | ||
|
|
acd8912d16 | ||
|
|
fbcc73dcee | ||
|
|
7a0c7ade35 | ||
|
|
928de7b8eb | ||
|
|
c4855c0ca8 | ||
|
|
49d302be95 | ||
|
|
bb97549171 | ||
|
|
040fc85a62 | ||
|
|
ca2cbb44f5 | ||
|
|
9d7846a0d5 | ||
|
|
ca69e7b850 | ||
|
|
328e590f92 | ||
|
|
b6eda8e498 | ||
|
|
14771e72de | ||
|
|
3f39d9509c | ||
|
|
e3e4e9fac0 | ||
|
|
c83e505f5b | ||
|
|
606d9d4af4 | ||
|
|
134b467876 | ||
|
|
a7482410b9 | ||
|
|
03d1f858ec | ||
|
|
51c304a6fa | ||
|
|
c3f8075b5f | ||
|
|
e6c9767800 | ||
|
|
e23e6c160d | ||
|
|
17f5a06256 | ||
|
|
90155bcfbf | ||
|
|
4a7545a0db | ||
|
|
305ab3146f | ||
|
|
18b795d4f7 | ||
|
|
50b587e3b8 | ||
|
|
3721fb688f | ||
|
|
5a26ece9d6 | ||
|
|
7cd19a0135 | ||
|
|
966fce3426 | ||
|
|
261d68c410 | ||
|
|
d423753de0 | ||
|
|
cffe41985d | ||
|
|
068a660a91 | ||
|
|
19fd8b5e4e | ||
|
|
71947c581e | ||
|
|
66b103c650 | ||
|
|
411484351a | ||
|
|
ddec0070e3 | ||
|
|
cfb2cf36de | ||
|
|
93148e97e8 | ||
|
|
dd3f41ab1b | ||
|
|
69859b80ee | ||
|
|
47a2cf4e5d | ||
|
|
a12b0e6ec3 | ||
|
|
2be22055cc | ||
|
|
1a44853c88 | ||
|
|
9b483de1da | ||
|
|
7b9e4a0542 | ||
|
|
3f63bc35a4 | ||
|
|
6e65996180 | ||
|
|
562136ed55 | ||
|
|
4044a7d936 | ||
|
|
2fb55ba3ed | ||
|
|
25a90d8419 | ||
|
|
6698ee6770 | ||
|
|
ffadce45e0 | ||
|
|
ca5e08f967 | ||
|
|
0f35618a15 | ||
|
|
ce24a6f7a3 | ||
|
|
ac11e5238d | ||
|
|
88b35ce8c2 | ||
|
|
7353747ca5 | ||
|
|
fd37e2121c | ||
|
|
712792c385 | ||
|
|
2915fd7717 | ||
|
|
738a21eeb6 | ||
|
|
a27e743c5f | ||
|
|
dd763c4b64 | ||
|
|
8107d1a290 | ||
|
|
49b7876b5a | ||
|
|
abf898ff01 | ||
|
|
33bfe34f09 | ||
|
|
2d5be92882 | ||
|
|
db0bb4d9c2 | ||
|
|
56948507e8 | ||
|
|
5a534e210b | ||
|
|
fd7cdca333 | ||
|
|
2ba71e78bb | ||
|
|
6b2ab64e97 | ||
|
|
2a5a24af70 | ||
|
|
f1e1b99b97 | ||
|
|
34a19ef583 | ||
|
|
242ab6e922 | ||
|
|
87d8d4043e | ||
|
|
9f17478a3a | ||
|
|
81680694e3 | ||
|
|
2ae6882241 | ||
|
|
d8572cd323 | ||
|
|
a93af43d63 | ||
|
|
9ad2bb35f9 | ||
|
|
6c077cad5f | ||
|
|
481f7ab61a | ||
|
|
ff770d6948 | ||
|
|
2d1d6eb19e | ||
|
|
717894300d | ||
|
|
39a774709e | ||
|
|
98ed4bd843 | ||
|
|
cb4620d33a | ||
|
|
ae03537676 | ||
|
|
7a653707a8 | ||
|
|
fdf52efdd1 | ||
|
|
b4acfe8d95 | ||
|
|
51a156eec9 | ||
|
|
d9d0facfca | ||
|
|
6e5c6a8f6d | ||
|
|
96aa0b3a59 | ||
|
|
b338dfe8f0 | ||
|
|
3a37b79a55 | ||
|
|
57c995e19d | ||
|
|
a92334d5ae | ||
|
|
d7105e8b8e | ||
|
|
f82f0d3b05 | ||
|
|
d1a1f2a32f | ||
|
|
a9aa142b8d | ||
|
|
c9c94a5fda | ||
|
|
6593ddbbc8 | ||
|
|
4d9b506380 | ||
|
|
214f68c4c1 | ||
|
|
67b1c3864e | ||
|
|
130440fd6e | ||
|
|
eddfa23955 | ||
|
|
11eefc4f6d | ||
|
|
1eb12a3e3f | ||
|
|
a7cfae8545 | ||
|
|
7fbc765403 | ||
|
|
e32fcee77c | ||
|
|
f31d2f7bfc | ||
|
|
cd9400e898 | ||
|
|
05b57c88d3 | ||
|
|
bd17d965f5 | ||
|
|
ad57e64994 | ||
|
|
5c2bad888d | ||
|
|
72a00b9ae2 | ||
|
|
b1021530d8 | ||
|
|
837efcea36 | ||
|
|
105e3dccba | ||
|
|
927e37b35b | ||
|
|
3fa9b2a635 | ||
|
|
8dfd36d227 | ||
|
|
b1b7a032be | ||
|
|
28068b1a86 | ||
|
|
be4ca0f47d | ||
|
|
d686890f76 | ||
|
|
bcaeae27f1 | ||
|
|
043c8e8ade | ||
|
|
5134547071 | ||
|
|
bb1fc64754 | ||
|
|
88263b9ffd | ||
|
|
61fad2325d | ||
|
|
432c709075 | ||
|
|
cd08116aed | ||
|
|
fbc78adb65 | ||
|
|
8a4f19d603 | ||
|
|
841a61408c | ||
|
|
9fb4a8719a | ||
|
|
2916bd6ff7 | ||
|
|
be5e0aafeb | ||
|
|
488fc6888f | ||
|
|
8e81f6bdf4 | ||
|
|
09c8a2600e | ||
|
|
0a7902c4b4 | ||
|
|
a9c6b9ff8a | ||
|
|
90f3a8f285 | ||
|
|
55d02d49c2 | ||
|
|
7e6ef61169 | ||
|
|
32deb8d190 | ||
|
|
62501a1ded | ||
|
|
03eab140f1 | ||
|
|
b022581e23 | ||
|
|
6bf28a5416 | ||
|
|
9d9d7a853c | ||
|
|
84dd1cd3b9 | ||
|
|
02dd7e7264 | ||
|
|
91bc63004a | ||
|
|
fbfd8259a6 | ||
|
|
16d64991ed | ||
|
|
6f26fbba5f | ||
|
|
a7bdf23832 | ||
|
|
27bf88c11e | ||
|
|
dd30012d80 | ||
|
|
dad8b0815d | ||
|
|
4a73d7e1ce | ||
|
|
08d35eb00d | ||
|
|
69a9a45131 | ||
|
|
e7f39ea081 | ||
|
|
ef7612d1c2 | ||
|
|
e7dd724be6 | ||
|
|
8a144352a0 | ||
|
|
1757532097 | ||
|
|
ab2f949d7e | ||
|
|
5751430261 | ||
|
|
39c3af58f2 | ||
|
|
9b4f9db73e | ||
|
|
458e51c327 | ||
|
|
4bd78f4696 | ||
|
|
9b64d08944 | ||
|
|
153590b8fc | ||
|
|
e862963c16 | ||
|
|
da834dab8d | ||
|
|
bdbc136497 | ||
|
|
f3e04114eb | ||
|
|
a5442b407e | ||
|
|
47b7aa702a | ||
|
|
de6ba0c741 | ||
|
|
93aa02868e | ||
|
|
92917e17c6 | ||
|
|
0f972cb11c | ||
|
|
4e65c49e0e | ||
|
|
983d241e47 | ||
|
|
d44275c2c7 | ||
|
|
81cada675c | ||
|
|
aa3e3eb9b6 | ||
|
|
aa24dc4aa0 | ||
|
|
ef2885ed0e | ||
|
|
3b3b9d97ba | ||
|
|
be38ae6c00 | ||
|
|
0589624d11 | ||
|
|
dd7a894b51 | ||
|
|
9b3a748f37 | ||
|
|
73efd963b9 | ||
|
|
dd413d42ab | ||
|
|
263039f52c | ||
|
|
60e741434c | ||
|
|
6f2f2a9aa4 | ||
|
|
78bbf5a7aa | ||
|
|
cf44774b21 | ||
|
|
0ed103b2f0 | ||
|
|
e2be19039c | ||
|
|
42192dfe97 | ||
|
|
24bdf2db64 | ||
|
|
250ec840ef | ||
|
|
37b8c881a8 | ||
|
|
b5c4a8589a | ||
|
|
2cc41c23a8 | ||
|
|
3c81f93ca2 | ||
|
|
60f6e5ec50 | ||
|
|
ac08f5518d | ||
|
|
4b354f6f32 | ||
|
|
5cde06bc86 | ||
|
|
9bd09e3791 | ||
|
|
504766a081 | ||
|
|
5b40900066 | ||
|
|
ba4677857a | ||
|
|
32ad9d159e | ||
|
|
b8c9717864 | ||
|
|
1f1fe62a06 | ||
|
|
e3159745fb | ||
|
|
49155b06b3 | ||
|
|
a63b60d458 | ||
|
|
1c45c9b03b | ||
|
|
80981eee37 | ||
|
|
7a2b023f30 | ||
|
|
c4d6e18885 | ||
|
|
b7eaa238e0 | ||
|
|
f48a65e9ce | ||
|
|
fb71f9087d | ||
|
|
9fa603363c | ||
|
|
9618cd2128 | ||
|
|
9468d95506 | ||
|
|
6962088d16 | ||
|
|
db10257993 | ||
|
|
4997bc27fd | ||
|
|
075989ce9c | ||
|
|
19ef15dbc8 | ||
|
|
347f487358 | ||
|
|
0808469a62 | ||
|
|
3f77d1d7ca | ||
|
|
c1da4d336c | ||
|
|
6b74befb5f | ||
|
|
3334cc3563 | ||
|
|
73982fbd60 | ||
|
|
7904a6a20f | ||
|
|
ade4aeb3f7 | ||
|
|
e46108fa15 | ||
|
|
7346a46975 | ||
|
|
5baa4c7ef7 | ||
|
|
4ce79cc386 | ||
|
|
11fee0f200 | ||
|
|
26b357df72 | ||
|
|
4a531888b5 | ||
|
|
262bcc4d8b | ||
|
|
088ff3744a | ||
|
|
01b1ce679c | ||
|
|
267fd0eadc | ||
|
|
814d72cb3f | ||
|
|
b75f6e5853 | ||
|
|
dd6d12de23 | ||
|
|
f86a163692 | ||
|
|
e0fe7cb1eb | ||
|
|
47cbd4b620 | ||
|
|
0a6f1abd33 | ||
|
|
92b84168e1 | ||
|
|
fae625a795 | ||
|
|
c49b66f0e4 | ||
|
|
6a52efe8a4 | ||
|
|
f6334fb30b | ||
|
|
3cc7f7407d | ||
|
|
dd86934712 | ||
|
|
7ce3eabd17 |
+3
-1
@@ -1 +1,3 @@
|
||||
* text=auto eol=lf
|
||||
* text=auto eol=lf
|
||||
*.hxc linguist-language=Haxe
|
||||
*.hxp linguist-language=Haxe
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Report a bug or critical performance issue
|
||||
title: 'Bug Report: [DESCRIBE YOUR BUG IN DETAIL HERE]'
|
||||
labels: bug
|
||||
---
|
||||
|
||||
<!-- FILL THIS ISSUE THING OUT AS MUCH AS POSSIBLE
|
||||
OR ELSE YOUR ISSUE WILL BE LESS LIKELY TO BE SOLVED!
|
||||
|
||||
Do not post about issues from other FNF mod engines!
|
||||
We cannot and probably won't solve those!
|
||||
You can hopefully go to their respective GitHub issues and report them there, thank you :)
|
||||
|
||||
Please check for duplicates or similar issues, as well performing simple troubleshooting steps (such as clearing cookies, clearing AppData, trying another browser) before submitting an issue.
|
||||
|
||||
From Joel On Software:
|
||||
|
||||
"It’s pretty easy to remember the rule for a good bug report. Every good bug report needs exactly three things.
|
||||
|
||||
1. Steps to reproduce,
|
||||
2. What you expected to see, and
|
||||
3. What you saw instead."
|
||||
|
||||
-->
|
||||
|
||||
## Describe the bug
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
## To Reproduce
|
||||
<!-- Describe in DETAIL how to reproduce the bug/issue you are running into. -->
|
||||
## Expected behavior
|
||||
<!-- A clear and concise description of what you expected to happen. -->
|
||||
|
||||
## Screenshots/Video
|
||||
<!-- If applicable, add screenshots/video to help explain your problem.
|
||||
Remember to mark the area in the application thats impacted. -->
|
||||
|
||||
## Desktop
|
||||
- OS:
|
||||
<!-- [e.g. Windows 10, 11, Mac, Linux Mint, Ubuntu, Arch (btw)] -->
|
||||
- Browser
|
||||
<!-- [e.g. chrome, safari, firefox, edge, operaGX] -->
|
||||
- Version:
|
||||
<!-- [e.g. 0.4.0, 0.3.3, this can be found in the bottom left corner of the main menu!] -->
|
||||
|
||||
## Additional context
|
||||
<!-- Add any other context about the problem here. -->
|
||||
|
||||
<!-- If you're game is FROZEN and you're playing a web version, press F12 to open up browser dev window, and go to console, and copy-paste whatever red error you're getting -->
|
||||
@@ -0,0 +1,62 @@
|
||||
name: Bug Report
|
||||
description: Report a bug or an issue in the game.
|
||||
labels: ["type: minor bug", "status: pending triage"]
|
||||
title: "Bug Report: "
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Issue Checklist
|
||||
options:
|
||||
- label: I have properly named the issue
|
||||
- label: I have checked the issues/discussions pages to see if the issue has been previously reported
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: What platform are you using?
|
||||
options:
|
||||
- Newgrounds (Web)
|
||||
- Itch.io (Web)
|
||||
- Itch.io (Downloadable Build) - Windows
|
||||
- Itch.io (Downloadable Build) - MacOS
|
||||
- Itch.io (Downloadable Build) - Linux
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: If you are playing on a browser, which one are you using?
|
||||
options:
|
||||
- Google Chrome
|
||||
- Microsoft Edge
|
||||
- Firefox
|
||||
- Opera
|
||||
- Safari
|
||||
- Other (Specify below)
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: Version
|
||||
description: What version are you using?
|
||||
placeholder: ex. 0.4.1
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "## Describe your bug."
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "### Please do not report issues from other engines. These must be reported in their respective repositories."
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "#### Provide as many details as you can."
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Context (Provide images, videos, etc.)
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce (or crash logs, errors, etc.)
|
||||
@@ -0,0 +1 @@
|
||||
blank_issues_enabled: false
|
||||
@@ -0,0 +1,70 @@
|
||||
name: Crash Report
|
||||
description: Report a crash that occurred while playing the game.
|
||||
labels: ["type: major bug", "status: pending triage"]
|
||||
title: "Crash Report: "
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Issue Checklist
|
||||
options:
|
||||
- label: I have properly named the issue
|
||||
- label: I have checked the issues/discussions pages to see if the issue has been previously reported
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: What platform are you using?
|
||||
options:
|
||||
- Newgrounds (Web)
|
||||
- Itch.io (Web)
|
||||
- Itch.io (Downloadable Build) - Windows
|
||||
- Itch.io (Downloadable Build) - MacOS
|
||||
- Itch.io (Downloadable Build) - Linux
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: If you are playing on a browser, which one are you using?
|
||||
options:
|
||||
- Google Chrome
|
||||
- Microsoft Edge
|
||||
- Firefox
|
||||
- Opera
|
||||
- Safari
|
||||
- Other (Specify below)
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: Version
|
||||
description: What version are you using?
|
||||
placeholder: ex. 0.4.1
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "## Describe your issue."
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "### Please do not report issues from other engines. These must be reported in their respective repositories."
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "#### Provide as many details as you can."
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Context (Provide screenshots or videos of the crash happening)
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Crash logs (can be found in the logs folder where Funkin.exe is)
|
||||
validations:
|
||||
required: true
|
||||
@@ -1,8 +0,0 @@
|
||||
---
|
||||
name: Enhancement
|
||||
about: Suggest a new feature
|
||||
title: 'Enhancement: '
|
||||
labels: enhancement
|
||||
---
|
||||
#### Please check for duplicates or similar issues before creating this issue.
|
||||
## What is your suggestion, and why should it be implemented?
|
||||
@@ -0,0 +1,15 @@
|
||||
name: Enhancement
|
||||
description: Suggest a new feature.
|
||||
labels: ["type: enhancement", "status: pending triage"]
|
||||
title: "Enhancement: "
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Issue Checklist
|
||||
options:
|
||||
- label: I have properly named the enhancement
|
||||
- label: I have checked the issues/discussions pages to see if the enhancement has been previously suggested
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: What is your suggestion, and why should it be implemented?
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
name: Bug Fix
|
||||
about: Fix a bug or critical performance issue
|
||||
title: 'Bug Fix: '
|
||||
labels: bug
|
||||
---
|
||||
#### Please check for duplicates or similar PRs before creating this issue.
|
||||
## Does this PR close any issue(s)? If so, link them below.
|
||||
|
||||
## Briefly describe the issue(s) fixed.
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
name: Enhancement
|
||||
about: Add a new feature
|
||||
title: 'Enhancement: '
|
||||
labels: enhancement
|
||||
---
|
||||
#### Please check for duplicates or similar PRs before creating this issue.
|
||||
## Does this PR close any issue(s)? If so, link them below.
|
||||
|
||||
## What do your change(s) add, and why should they be implemented?
|
||||
@@ -44,7 +44,7 @@ runs:
|
||||
g++ \
|
||||
libx11-dev libxi-dev libxext-dev libxinerama-dev libxrandr-dev \
|
||||
libgl-dev libgl1-mesa-dev \
|
||||
libasound2-dev
|
||||
libasound2-dev libpulse-dev
|
||||
ln -s /usr/lib/x86_64-linux-gnu/libffi.so.8 /usr/lib/x86_64-linux-gnu/libffi.so.6 || true
|
||||
- name: Install linux-specific dependencies
|
||||
if: ${{ runner.os == 'Linux' && contains(inputs.targets, 'linux') }}
|
||||
@@ -56,12 +56,17 @@ runs:
|
||||
shell: bash
|
||||
run: |
|
||||
echo "TIMER_HAXELIB=$(date +%s)" >> "$GITHUB_ENV"
|
||||
haxelib --debug --never install haxelib 4.1.0 --global
|
||||
haxelib --debug --never deleterepo || true
|
||||
haxelib fixrepo --global || true
|
||||
haxelib --debug --never --global install haxelib 4.1.0
|
||||
haxelib --debug --global set haxelib 4.1.0
|
||||
haxelib --global remove haxelib git || true
|
||||
haxelib --global remove hmm || true
|
||||
rm -rf .haxelib
|
||||
haxelib --debug --never --global git haxelib https://github.com/FunkinCrew/haxelib.git funkin-patches --skip-dependencies
|
||||
haxelib --debug --never --global git hmm https://github.com/FunkinCrew/hmm funkin-patches
|
||||
haxelib --debug --never newrepo
|
||||
haxelib version
|
||||
echo "HAXEPATH=$(haxelib config)" >> "$GITHUB_ENV"
|
||||
haxelib --debug --never git haxelib https://github.com/HaxeFoundation/haxelib.git master
|
||||
haxelib --debug --global install hmm
|
||||
echo "TIMER_DEPS=$(date +%s)" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Restore cached dependencies
|
||||
@@ -75,7 +80,12 @@ runs:
|
||||
name: Prep git for dependency install
|
||||
uses: gacts/run-and-post-run@v1
|
||||
with:
|
||||
run: git config --global 'url.https://x-access-token:${{ inputs.gh-token }}@github.com/.insteadOf' https://github.com/
|
||||
run: |
|
||||
git config --global --name-only --get-regexp 'url\.https\:\/\/x-access-token:.+@github\.com\/\.insteadOf' \
|
||||
| xargs -I {} git config --global --unset {}
|
||||
|
||||
git config -l --show-scope --show-origin
|
||||
git config --global 'url.https://x-access-token:${{ inputs.gh-token }}@github.com/.insteadOf' https://github.com/
|
||||
post: git config --global --unset 'url.https://x-access-token:${{ inputs.gh-token }}@github.com/.insteadOf'
|
||||
|
||||
- if: ${{ steps.cache-hmm.outputs.cache-hit != 'true' }}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# Add 'small' to any changes below 10 lines
|
||||
small:
|
||||
max: 9
|
||||
|
||||
# Add 'medium' to any changes between 10 and 100 lines
|
||||
medium:
|
||||
min: 10
|
||||
max: 99
|
||||
|
||||
# Add 'large' to any changes for more than 100 lines
|
||||
large:
|
||||
min: 100
|
||||
@@ -0,0 +1,44 @@
|
||||
# Configuration for Label Actions - https://github.com/dessant/label-actions
|
||||
|
||||
# Automatically close issues and pull requests when the `status: duplicate` label is applied
|
||||
'status: duplicate':
|
||||
issues:
|
||||
# Post a comment
|
||||
comment: >
|
||||
This issue is a duplicate. Please direct all discussion to the original issue.
|
||||
# Close the issue
|
||||
close: true
|
||||
# Remove other status labels
|
||||
unlabel:
|
||||
- 'status: pending triage'
|
||||
# Set a close reason
|
||||
close-reason: 'not planned'
|
||||
prs:
|
||||
# Post a comment
|
||||
comment: >
|
||||
This pull request is a duplicate. Please direct all discussion to the original pull request.
|
||||
# Remove other status labels
|
||||
unlabel:
|
||||
- 'status: pending triage'
|
||||
# Close the pull request
|
||||
close: true
|
||||
# Set a close reason
|
||||
close-reason: 'not planned'
|
||||
|
||||
'status: rejected':
|
||||
issues:
|
||||
# Close the issue
|
||||
close: true
|
||||
# Remove other status labels
|
||||
unlabel:
|
||||
- 'status: pending triage'
|
||||
# Set a close reason
|
||||
close-reason: 'not planned'
|
||||
prs:
|
||||
# Close the pull request
|
||||
close: true
|
||||
# Remove other status labels
|
||||
unlabel:
|
||||
- 'status: pending triage'
|
||||
# Set a close reason
|
||||
close-reason: 'not planned'
|
||||
@@ -1,7 +1,6 @@
|
||||
# Add Documentation tag to PR's changing markdown files, or anyhting in the docs folder
|
||||
Documentation:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- any-glob-to-any-file:
|
||||
- docs/*
|
||||
- '**/*.md'
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<!-- Please check for duplicates or similar PRs before submitting this PR. -->
|
||||
## Does this PR close any issues? If so, link them below.
|
||||
|
||||
## Briefly describe the issue(s) fixed.
|
||||
|
||||
## Include any relevant screenshots or videos.
|
||||
@@ -2,6 +2,15 @@ name: Build and Upload nightly game builds
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
build-defines:
|
||||
type: string
|
||||
description: Build defines to use
|
||||
default: '-DGITHUB_BUILD'
|
||||
save-artifact:
|
||||
type: boolean
|
||||
description: Save the build artifact to Github Actions (sends to itch otherwise)
|
||||
default: false
|
||||
push:
|
||||
paths-ignore:
|
||||
- '**/Dockerfile'
|
||||
@@ -45,17 +54,28 @@ jobs:
|
||||
uses: ./.github/actions/setup-haxe
|
||||
with:
|
||||
gh-token: ${{ steps.app_token.outputs.token }}
|
||||
|
||||
- name: Setup HXCPP dev commit
|
||||
run: |
|
||||
cd .haxelib/hxcpp/git/tools/hxcpp
|
||||
haxe compile.hxml
|
||||
cd ../../../../..
|
||||
- name: Build game
|
||||
if: ${{ matrix.target == 'windows' }}
|
||||
run: |
|
||||
haxelib run lime build windows -v -release -DGITHUB_BUILD
|
||||
haxelib run lime build windows -v -release ${{ github.event.inputs.build-defines }}
|
||||
timeout-minutes: 120
|
||||
- name: Build game
|
||||
if: ${{ matrix.target != 'windows' }}
|
||||
run: |
|
||||
haxelib run lime build ${{ matrix.target }} -v -release --times -DGITHUB_BUILD
|
||||
haxelib run lime build ${{ matrix.target }} -v -release --times ${{ github.event.inputs.build-defines }}
|
||||
timeout-minutes: 120
|
||||
- name: Save build artifact to Github Actions
|
||||
if: ${{ github.event.inputs.save-artifact }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-${{ matrix.target }}
|
||||
path: export/release/${{matrix.target}}/bin/
|
||||
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: ./.github/actions/upload-itch
|
||||
@@ -107,7 +127,9 @@ jobs:
|
||||
name: Install dependencies
|
||||
run: |
|
||||
git config --global 'url.https://x-access-token:${{ steps.app_token.outputs.token }}@github.com/.insteadOf' https://github.com/
|
||||
git config --global advice.detachedHead false
|
||||
haxelib --global run hmm install -q
|
||||
cd .haxelib/hxcpp/git/tools/hxcpp && haxe compile.hxml
|
||||
|
||||
- if: ${{ matrix.target != 'html5' }}
|
||||
name: Restore hxcpp cache
|
||||
@@ -119,9 +141,15 @@ jobs:
|
||||
|
||||
- name: Build game
|
||||
run: |
|
||||
haxelib run lime build ${{ matrix.target }} -v -release --times -DGITHUB_BUILD
|
||||
haxelib run lime build ${{ matrix.target }} -v -release --times ${{ github.event.inputs.build-defines }}
|
||||
timeout-minutes: 120
|
||||
|
||||
- name: Save build artifact to Github Actions
|
||||
if: ${{ github.event.inputs.save-artifact }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-${{ matrix.target }}
|
||||
path: export/release/${{matrix.target}}/bin/
|
||||
- name: Upload build artifacts
|
||||
uses: ./.github/actions/upload-itch
|
||||
with:
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# Perform actions when labels are applied to issues, discussions, or pull requests
|
||||
# See .github/label-actions.yml
|
||||
name: 'Label Actions'
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- labeled
|
||||
- unlabeled
|
||||
pull_request_target:
|
||||
types:
|
||||
- labeled
|
||||
- unlabeled
|
||||
discussion:
|
||||
types:
|
||||
- labeled
|
||||
- unlabeled
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
discussions: write
|
||||
|
||||
jobs:
|
||||
action:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/label-actions@v4
|
||||
@@ -0,0 +1,52 @@
|
||||
name: "Issue Labeler"
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- edited
|
||||
|
||||
jobs:
|
||||
# When an issue is opened, perform a similarity check for potential duplicates.
|
||||
# If some are found, add a label and comment listing the potential duplicate issues.
|
||||
potential-duplicate:
|
||||
name: Detect potential duplicate issues
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: wow-actions/potential-duplicates@v1
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Issue title filter work with anymatch https://www.npmjs.com/package/anymatch.
|
||||
# Any matched issue will stop detection immediately.
|
||||
# You can specify multi filters in each line.
|
||||
filter: ''
|
||||
# Exclude keywords in title before detecting.
|
||||
exclude: ''
|
||||
# Label to set, when potential duplicates are detected.
|
||||
label: 'potential duplicate'
|
||||
# Get issues with state to compare. Supported state: 'all', 'closed', 'open'.
|
||||
state: all
|
||||
# If similarity is higher than this threshold([0,1]), issue will be marked as duplicate.
|
||||
# Turn this up if the detection is too sensitive
|
||||
threshold: 0.6
|
||||
# Reactions to be add to comment when potential duplicates are detected.
|
||||
# Available reactions: "-1", "+1", "confused", "laugh", "heart", "hooray", "rocket", "eyes"
|
||||
# reactions: '-1'
|
||||
# Comment to post when potential duplicates are detected.
|
||||
comment: >
|
||||
Potential duplicates: {{#issues}}
|
||||
- [#{{ number }}] {{ title }} ({{ accuracy }}%)
|
||||
{{/issues}}
|
||||
# When an issue is opened, detect if it has an empty body or incomplete issue form.
|
||||
# If it does, close the issue immediately.
|
||||
empty-issues:
|
||||
name: Close empty issues
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Run empty issues closer action
|
||||
uses: rickstaa/empty-issues-closer-action@v1
|
||||
env:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
close_comment: Closing this issue because it appears to be empty. Please update the issue for it to be reopened.
|
||||
open_comment: Reopening this issue because the author provided more information.
|
||||
@@ -0,0 +1,29 @@
|
||||
name: "Pull Request Labeler"
|
||||
on:
|
||||
- pull_request_target
|
||||
|
||||
jobs:
|
||||
# Apply labels to pull requests based on which files were edited
|
||||
labeler:
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set basic labels
|
||||
uses: actions/labeler@v5
|
||||
with:
|
||||
sync-labels: true
|
||||
# Apply labels to pull requests based on how many lines were edited
|
||||
changed-lines-count-labeler:
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
name: An action for automatically labelling pull requests based on the changed lines count
|
||||
steps:
|
||||
- name: Set change count labels
|
||||
uses: vkirilichev/changed-lines-count-labeler@v0.2
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
configuration-path: .github/changed-lines-count-labeler.yml
|
||||
@@ -1,14 +0,0 @@
|
||||
name: "Pull Request Labeler"
|
||||
on:
|
||||
- pull_request_target
|
||||
|
||||
jobs:
|
||||
labeler:
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/labeler@v5
|
||||
with:
|
||||
sync-labels: true
|
||||
@@ -13,3 +13,4 @@ shitAudio/
|
||||
node_modules/
|
||||
package.json
|
||||
package-lock.json
|
||||
.aider*
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
[submodule "assets"]
|
||||
path = assets
|
||||
url = https://github.com/FunkinCrew/funkin.assets
|
||||
url = https://github.com/FunkinCrew/Funkin-assets-secret
|
||||
[submodule "art"]
|
||||
path = art
|
||||
url = https://github.com/FunkinCrew/funkin.art
|
||||
url = https://github.com/FunkinCrew/Funkin-art-secret
|
||||
|
||||
Vendored
+25
-15
@@ -94,22 +94,32 @@
|
||||
{
|
||||
"label": "Windows / Debug",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DFORCE_DEBUG_VERSION"]
|
||||
"args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (Tracy)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DFEATURE_DEBUG_TRACY", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "Linux / Debug",
|
||||
"target": "linux",
|
||||
"args": ["-debug", "-DFORCE_DEBUG_VERSION"]
|
||||
"args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug",
|
||||
"target": "hl",
|
||||
"args": ["-debug"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (Discord)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS", "-DFEATURE_DISCORD_RPC"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (FlxAnimate Test)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DANIMATE", "-DFORCE_DEBUG_VERSION"]
|
||||
"args": ["-debug", "-DANIMATE", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug (FlxAnimate Test)",
|
||||
@@ -119,7 +129,7 @@
|
||||
{
|
||||
"label": "Windows / Debug (Straight to Freeplay)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DFREEPLAY", "-DFORCE_DEBUG_VERSION"]
|
||||
"args": ["-debug", "-DFREEPLAY", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug (Straight to Freeplay)",
|
||||
@@ -132,13 +142,13 @@
|
||||
"args": [
|
||||
"-debug",
|
||||
"-DSONG=bopeebo -DDIFFICULTY=normal",
|
||||
"-DFORCE_DEBUG_VERSION"
|
||||
"-DFEATURE_DEBUG_FUNCTIONS"
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (Straight to Play - 2hot)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DSONG=2hot", "-DFORCE_DEBUG_VERSION"]
|
||||
"args": ["-debug", "-DSONG=2hot", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug (Straight to Play - Bopeebo Normal)",
|
||||
@@ -148,7 +158,7 @@
|
||||
{
|
||||
"label": "Windows / Debug (Conversation Test)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DDIALOGUE", "-DFORCE_DEBUG_VERSION"]
|
||||
"args": ["-debug", "-DDIALOGUE", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug (Conversation Test)",
|
||||
@@ -163,7 +173,7 @@
|
||||
{
|
||||
"label": "Windows / Debug (Straight to Chart Editor)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DCHARTING", "-DFORCE_DEBUG_VERSION"]
|
||||
"args": ["-debug", "-DCHARTING", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug (Straight to Chart Editor)",
|
||||
@@ -173,12 +183,12 @@
|
||||
{
|
||||
"label": "Windows / Debug (Straight to Animation Editor)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DANIMDEBUG", "-DFORCE_DEBUG_VERSION"]
|
||||
"args": ["-debug", "-DANIMDEBUG", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (Debug hxCodec)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DHXC_LIBVLC_LOGGING", "-DFORCE_DEBUG_VERSION"]
|
||||
"args": ["-debug", "-DHXC_LIBVLC_LOGGING", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug (Straight to Animation Editor)",
|
||||
@@ -188,7 +198,7 @@
|
||||
{
|
||||
"label": "Windows / Debug (Latency Test)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DLATENCY", "-DFORCE_DEBUG_VERSION"]
|
||||
"args": ["-debug", "-DLATENCY", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug (Latency Test)",
|
||||
@@ -198,7 +208,7 @@
|
||||
{
|
||||
"label": "Windows / Debug (Waveform Test)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DWAVEFORM", "-DFORCE_DEBUG_VERSION"]
|
||||
"args": ["-debug", "-DWAVEFORM", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Release",
|
||||
@@ -218,17 +228,17 @@
|
||||
{
|
||||
"label": "HTML5 / Debug",
|
||||
"target": "html5",
|
||||
"args": ["-debug", "-DFORCE_DEBUG_VERSION"]
|
||||
"args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "HTML5 / Debug (Watch)",
|
||||
"target": "html5",
|
||||
"args": ["-debug", "-watch", "-DFORCE_DEBUG_VERSION"]
|
||||
"args": ["-debug", "-watch", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "macOS / Debug",
|
||||
"target": "mac",
|
||||
"args": ["-debug", "-DFORCE_DEBUG_VERSION"]
|
||||
"args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "macOS / Release",
|
||||
|
||||
+79
-10
@@ -4,6 +4,85 @@ 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.5.0] - 2024-09-12
|
||||
### Added
|
||||
- Added a new Character Select screen to switch between playable characters in Freeplay
|
||||
- Modding isn't 100% there but we're working on it!
|
||||
- Added Pico as a playable character! Unlock him by completing Weekend 1 (if you haven't already done that)
|
||||
- The songs from Weekend 1 have moved; you must now switch to Pico via Character Select screen in Freeplay to access them
|
||||
- Added 11 new Pico remixes! Access them by selecting Pico from in the Character Select screen
|
||||
- Bopeebo (Pico Mix)
|
||||
- Fresh (Pico Mix)
|
||||
- DadBattle (Pico Mix)
|
||||
- Spookeez (Pico Mix)
|
||||
- South (Pico Mix)
|
||||
- Pico (Pico Mix)
|
||||
- Philly Nice (Pico Mix)
|
||||
- Blammed (Pico Mix)
|
||||
- Eggnog (Pico Mix)
|
||||
- Ugh (Pico Mix)
|
||||
- Guns (Pico Mix)
|
||||
- Added 1 new Boyfriend remix! Access it by completing Weekend 1 as Pico and then selecting Boyfriend from in the Character Select screen
|
||||
- Darnell (BF Mix)
|
||||
- Added 2 new Erect remixes! Access them by switching difficulty on the song
|
||||
- Cocoa Erect
|
||||
- Ugh Erect
|
||||
- Implemented support for a new Instrumental Selector in Freeplay
|
||||
- Beating a Pico remix lets you use that instrumental when playing as Boyfriend
|
||||
- Added the first batch of Erect Stages! These graphical overhauls of the original stages will be used when playing Erect remixes and Pico remixes
|
||||
- Week 1 Erect Stage
|
||||
- Week 2 Erect Stage
|
||||
- Week 3 Erect Stage
|
||||
- Week 4 Erect Stage
|
||||
- Week 5 Erect Stage
|
||||
- Weekend 1 Erect Stage
|
||||
- Implemented alternate animations and music for Pico in the results screen.
|
||||
- These display on Pico remixes, as well as when playing Weekend 1.
|
||||
- Implemented support for scripted Note Kinds. You can use HScript define a different note style to display for these notes as well as custom behavior. (community feature by lemz1)
|
||||
- Implemented support for Numeric and Selector options in the Options menu. (community feature by FlooferLand)
|
||||
- Implemented new animations for Tankman and Pico
|
||||
## Changed
|
||||
- Girlfriend and Nene now perform previously unused animations when you achieve a large combo, or drop a large combo.
|
||||
- The pixel character icons in the Freeplay menu now display an animation!
|
||||
- Altered how Week 6 displays sprites to make things look more retro.
|
||||
- Character offsets are now independent of the character's scale.
|
||||
- This should resolve issues with offsets when porting characters from older mods.
|
||||
- Pixel character offsets have been modified to compensate.
|
||||
- Note style data can now specify custom combo count graphics, judgement graphics, countdown graphics, and countdown audio. (community feature by anysad)
|
||||
- These were previously using hardcoded values based on whether the stage was `school` or `schoolEvil`.
|
||||
- The `danceEvery` property of characters and stage props can now use values with a precision of `0.25`, to play their idle animation up to four times per beat.
|
||||
- Reworked the JSON merging system in Polymod; you can now include JSONPatch files under `_merge` in your mod folder to add, modify, or remove values in a JSON without replacing it entirely!
|
||||
- Cutscenes now automatically pause when tabbing out (community fix by AbnormalPoof)
|
||||
- Characters will now respect the `danceEvery` property (community fix by gamerbross)
|
||||
- The F5 function now reloads the current song's chart data from disc (community feature by gamerbross)
|
||||
- Refactored the compilation guide and added common troubleshooting steps (community fix by Hundrec)
|
||||
- Made several layout improvements and fixes to the Animation Offsets editor in the Debug menu (community fix by gamerbross)
|
||||
- Fixed a bug where the Back sound would be not played when leaving the Story menu and Options menu (community fix by AppleHair)
|
||||
- Animation offsets no longer directly modify the `x` and `y` position of props, which makes props work better with tweens (community fix by Sword352)
|
||||
- The YEAH! events in Tutorial now use chart events rather than being hard-coded (community fix by anysad)
|
||||
- The player's Score now displays commas in it (community fix by loggo)
|
||||
## Fixed
|
||||
- Fixed an issue where songs with no notes would crash on the Results screen.
|
||||
- Fixed an issue where the old icon easter egg would not work properly on pixel levels.
|
||||
- Fixed an issue where you could play notes during the Thorns cutscene.
|
||||
- Fixed an issue where the Heart icon when favoriting a song in Freeplay would be malformed.
|
||||
- Fixed an issue where Pico's death animation displays a faint blue background (community fix by doggogit)
|
||||
- Fixed an issue where mod songs would not play a preview in the Freeplay menu (community fix by KarimAkra)
|
||||
- Fixed an issue where the Memory Usage counter could overflow and display a negative number (community fix by KarimAkra)
|
||||
- Fixed an issue where pressing the Chart Editor keybind while playtesting a chart would reset the chart editor (community fix by gamerbross)
|
||||
- Fixed a crash bug when pressing F5 after seeing the sticker transition (community fix by gamerbross)
|
||||
- Fixed an issue where the Story Mode menu couldn't be scrolled with a mouse (community fix by JVNpixels)
|
||||
- Fixed an issue causing the song to majorly desync sometimes (community fix by Burgerballs)
|
||||
- Fixed an issue where the Freeplay song preview would not respect the instrumental ID specified in the song metadata (community fix by AppleHair)
|
||||
- Fixed an issue where Tankman's icon wouldn't display in the Chart Editor (community fix by hundrec)
|
||||
- Fixed an issue where pausing the game during a camera zoom would zoom the pause menu. (community fix by gamerbros)
|
||||
- Fixed an issue where certain UI elements would not flash at a consistent rate (community fix by cyn0x8)
|
||||
- Fixed an issue where the game would not use the placeholder health icon as a fallback (community fix by gamerbross)
|
||||
- Fixed an issue where the chart editor could get stuck creating a hold note when using Live Inputs (community fix by gamerbross)
|
||||
- Fixed an issue where character graphics could not be placed in week folders (community fix by 7oltan)
|
||||
- Fixed a crash issue when a Freeplay song has no `Normal` difficulty (community fix by Applehair and gamerbross)
|
||||
- Fixed an issue in Story Mode where a song that isn't valid for the current variation could be selected (community fix by Applehair)
|
||||
|
||||
## [0.4.1] - 2024-06-12
|
||||
### Added
|
||||
- Pressing ESCAPE on the title screen on desktop now exits the game, allowing you to exit the game while in fullscreen on desktop
|
||||
@@ -81,19 +160,9 @@ which would remove their rank if they had a lower one.
|
||||
- Fixed a crash on Linux caused by an old version of hxCodec (thanks Noobz4Life!)
|
||||
- Optimized animation handling for characters (thanks richTrash21!)
|
||||
- Made improvements to compiling documentation (thanks gedehari!)
|
||||
- Fixed a bug where pressing the volume keys would stop the Toy commercial (thanks gamerbross!)
|
||||
- Fixed a bug where the Chart Editor Playtest would crash when losing (thanks gamerbross!)
|
||||
- Removed a large number of unused imports to optimize builds (thanks Ethan-makes-music!)
|
||||
- Fixed a bug where hold notes would be positioned wrong on downscroll (thanks MaybeMaru!)
|
||||
- Additional fixes to the Loading bar on HTML5 (thanks lemz1!)
|
||||
- Fixed a crash in Freeplay caused by a level referencing an invalid song (thanks gamerbross!)
|
||||
- Improved debug logging for unscripted stages (thanks gamerbross!)
|
||||
- Fixed a bug where changing difficulties in Story mode wouldn't update the score (thanks sectorA!)
|
||||
- Fixed an issue where the Chart Editor would use an incorrect instrumental on imported Legacy songs (thanks gamerbross!)
|
||||
- Fixed a camera bug in the Main Menu (thanks richTrash21!)
|
||||
- Fixed several bugs with the TitleState, including missing music when returning from the Main Menu (thanks gamerbross!)
|
||||
- Fixed a bug where opening the game from the command line would crash the preloader (thanks NotHyper474!)
|
||||
- Fixed a bug where hold notes would display improperly in the Chart Editor when downscroll was enabled for gameplay (thanks gamerbross!)
|
||||
- Fixed a bug where characters would sometimes use the wrong scale value (thanks PurSnake!)
|
||||
- Additional bug fixes and optimizations.
|
||||
|
||||
|
||||
-269
@@ -1,269 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<project xmlns="http://lime.openfl.org/project/1.0.4" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://lime.openfl.org/project/1.0.4 http://lime.openfl.org/xsd/project-1.0.4.xsd">
|
||||
<!-- _________________________ Application Settings _________________________ -->
|
||||
<app title="Friday Night Funkin'" file="Funkin" packageName="com.funkin.fnf" package="com.funkin.fnf" main="Main" version="0.4.1" company="ninjamuffin99" />
|
||||
<!--Switch Export with Unique ApplicationID and Icon-->
|
||||
<set name="APP_ID" value="0x0100f6c013bbc000" />
|
||||
|
||||
<!--
|
||||
Define the OpenFL sprite which displays the preloader.
|
||||
You can't replace the preloader's logic here, sadly, but you can extend it.
|
||||
Basic preloading logic is done by `openfl.display.Preloader`.
|
||||
-->
|
||||
<app preloader="funkin.ui.transition.preload.FunkinPreloader" />
|
||||
|
||||
<!--Minimum without FLX_NO_GAMEPAD: 11.8, without FLX_NO_NATIVE_CURSOR: 11.2-->
|
||||
<set name="SWF_VERSION" value="11.8" />
|
||||
<!-- ____________________________ Window Settings ___________________________ -->
|
||||
<!--These window settings apply to all targets-->
|
||||
<window width="1280" height="720" fps="60" background="#000000" hardware="true" vsync="false" />
|
||||
<!--HTML5-specific-->
|
||||
<window if="html5" resizable="true" />
|
||||
<!--Desktop-specific-->
|
||||
<window if="desktop" orientation="landscape" fullscreen="false" resizable="true" vsync="false" />
|
||||
<!--Mobile-specific-->
|
||||
<window if="mobile" orientation="landscape" fullscreen="true" width="0" height="0" resizable="false" />
|
||||
<!-- _____________________________ Path Settings ____________________________ -->
|
||||
|
||||
<set name="BUILD_DIR" value="export/debug" if="debug" />
|
||||
<set name="BUILD_DIR" value="export/release" unless="debug" />
|
||||
<set name="BUILD_DIR" value="export/32bit" if="32bit" />
|
||||
<source path="source" />
|
||||
<assets path="assets/preload" rename="assets" exclude="*.ogg|*.wav" if="web" />
|
||||
<assets path="assets/preload" rename="assets" exclude="*.mp3|*.wav" unless="web" />
|
||||
<define name="PRELOAD_ALL" unless="web" />
|
||||
<define name="NO_PRELOAD_ALL" unless="PRELOAD_ALL" />
|
||||
<section if="PRELOAD_ALL">
|
||||
<library name="songs" preload="true" />
|
||||
<library name="shared" preload="true" />
|
||||
<library name="tutorial" preload="true" />
|
||||
<library name="week1" preload="true" />
|
||||
<library name="week2" preload="true" />
|
||||
<library name="week3" preload="true" />
|
||||
<library name="week4" preload="true" />
|
||||
<library name="week5" preload="true" />
|
||||
<library name="week6" preload="true" />
|
||||
<library name="week7" preload="true" />
|
||||
<library name="weekend1" preload="true" />
|
||||
<library name="videos" preload="true" />
|
||||
</section>
|
||||
<section if="NO_PRELOAD_ALL">
|
||||
<library name="songs" preload="false" />
|
||||
<library name="shared" preload="false" />
|
||||
<library name="tutorial" preload="false" />
|
||||
<library name="week1" preload="false" />
|
||||
<library name="week2" preload="false" />
|
||||
<library name="week3" preload="false" />
|
||||
<library name="week4" preload="false" />
|
||||
<library name="week5" preload="false" />
|
||||
<library name="week6" preload="false" />
|
||||
<library name="week7" preload="false" />
|
||||
<library name="weekend1" preload="false" />
|
||||
<library name="videos" preload="false" />
|
||||
</section>
|
||||
<library name="art" preload="false" />
|
||||
<assets path="assets/songs" library="songs" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||
<assets path="assets/songs" library="songs" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||
<!-- Videos go in their own library because web never needs to preload them, they can just be streamed. -->
|
||||
<assets path="assets/videos" library="videos" />
|
||||
<assets path="assets/shared" library="shared" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||
<assets path="assets/shared" library="shared" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||
<assets path="assets/tutorial" library="tutorial" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||
<assets path="assets/tutorial" library="tutorial" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||
<assets path="assets/week1" library="week1" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||
<assets path="assets/week1" library="week1" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||
<assets path="assets/week2" library="week2" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||
<assets path="assets/week2" library="week2" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||
<assets path="assets/week3" library="week3" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||
<assets path="assets/week3" library="week3" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||
<assets path="assets/week4" library="week4" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||
<assets path="assets/week4" library="week4" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||
<assets path="assets/week5" library="week5" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||
<assets path="assets/week5" library="week5" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||
<assets path="assets/week6" library="week6" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||
<assets path="assets/week6" library="week6" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||
<assets path="assets/week7" library="week7" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||
<assets path="assets/week7" library="week7" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||
<assets path="assets/weekend1" library="weekend1" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||
<assets path="assets/weekend1" library="weekend1" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||
<!-- <assets path='example_mods' rename='mods' embed='false'/> -->
|
||||
<!--
|
||||
AUTOMATICALLY MOVING EXAMPLE MODS INTO THE BUILD CAUSES ISSUES
|
||||
Currently, this line will add the mod files to the library manifest,
|
||||
which causes issues if the mod is not enabled.
|
||||
If we can exclude the `mods` folder from the manifest, we can re-enable this line.
|
||||
<assets path='example_mods' rename='mods' embed='false' exclude="*.md" />
|
||||
-->
|
||||
<assets path="art/readme.txt" rename="do NOT readme.txt" library="art"/>
|
||||
<assets path="CHANGELOG.md" rename="changelog.txt" library="art"/>
|
||||
<!-- NOTE FOR FUTURE SELF SINCE FONTS ARE ALWAYS FUCKY
|
||||
TO FIX ONE OF THEM, I CONVERTED IT TO OTF. DUNNO IF YOU NEED TO
|
||||
THEN UHHH I USED THE NAME OF THE FONT WITH SETFORMAT() ON THE TEXT!!!
|
||||
NOT USING A DIRECT THING TO THE ASSET!!!
|
||||
-->
|
||||
<assets path="assets/fonts" embed="true" />
|
||||
|
||||
<!-- If compiled via github actions, show debug version number. -->
|
||||
<define name="FORCE_DEBUG_VERSION" if="GITHUB_BUILD" />
|
||||
<define name="NO_REDIRECT_ASSETS_FOLDER" if="GITHUB_BUILD" />
|
||||
<define name="TOUCH_HERE_TO_PLAY" if="web" />
|
||||
|
||||
<!-- _______________________________ Libraries ______________________________ -->
|
||||
<haxelib name="lime" /> <!-- Game engine backend -->
|
||||
<haxelib name="openfl" /> <!-- Game engine backend -->
|
||||
<haxelib name="flixel" /> <!-- Game engine -->
|
||||
|
||||
<haxedev set="webgl" />
|
||||
|
||||
<haxelib name="flixel-addons" /> <!-- Additional utilities for Flixel -->
|
||||
<haxelib name="hscript" /> <!-- Scripting -->
|
||||
<haxelib name="flixel-ui" /> <!-- UI framework (DEPRECATED) -->
|
||||
<haxelib name="haxeui-core" /> <!-- UI framework -->
|
||||
<haxelib name="haxeui-flixel" /> <!-- Integrate HaxeUI with Flixel -->
|
||||
<haxelib name="flixel-text-input" /> <!-- Improved text field rendering for HaxeUI -->
|
||||
<haxelib name="polymod" /> <!-- Modding framework -->
|
||||
<haxelib name="flxanimate" /> <!-- Texture atlas rendering -->
|
||||
<haxelib name="hxCodec" if="desktop" unless="hl" /> <!-- Video playback -->
|
||||
<haxelib name="funkin.vis"/>
|
||||
<haxelib name="grig.audio" />
|
||||
|
||||
<haxelib name="FlxPartialSound" /> <!-- Loading partial sound data -->
|
||||
|
||||
<haxelib name="json2object" /> <!-- JSON parsing -->
|
||||
<haxelib name="thx.core" /> <!-- General utility library, "the lodash of Haxe" -->
|
||||
<haxelib name="thx.semver" /> <!-- Version string handling -->
|
||||
|
||||
<haxelib name="hxcpp-debug-server" if="desktop debug" /> <!-- VSCode debug support -->
|
||||
|
||||
<!--Disable the Flixel core focus lost screen-->
|
||||
<haxedef name="FLX_NO_FOCUS_LOST_SCREEN" />
|
||||
<!--Disable the Flixel core debugger. Automatically gets set whenever you compile in release mode!-->
|
||||
<haxedef name="FLX_NO_DEBUG" unless="debug || FORCE_DEBUG_VERSION" />
|
||||
<!--Enable this for Nape release builds for a serious peformance improvement-->
|
||||
<haxedef name="NAPE_RELEASE_BUILD" unless="debug" />
|
||||
|
||||
<!--
|
||||
Hide deprecation warnings until they're fixed.
|
||||
TODO: REMOVE THIS!!!!
|
||||
<haxeflag name="-w" value="-WDeprecated" />
|
||||
-->
|
||||
|
||||
<!-- Haxe 4.3.0+: Enable pretty syntax errors and stuff. -->
|
||||
<haxedef name="message.reporting" value="pretty" />
|
||||
|
||||
<!-- _________________________________ Custom _______________________________ -->
|
||||
<!-- Disable trace() calls in release builds to bump up performance.
|
||||
<haxeflag name="- -no-traces" unless="debug" />-->
|
||||
<!-- HScript relies heavily on Reflection, which means we can't use DCE. -->
|
||||
<haxeflag name="-dce no" />
|
||||
<!-- Ensure all Funkin' classes are available at runtime. -->
|
||||
<haxeflag name="--macro" value="include('funkin')" />
|
||||
<!-- Ensure all UI components are available at runtime. -->
|
||||
<haxeflag name="--macro" value="include('haxe.ui.backend.flixel.components')" />
|
||||
<haxeflag name="--macro" value="include('haxe.ui.containers.dialogs')" />
|
||||
<haxeflag name="--macro" value="include('haxe.ui.containers.menus')" />
|
||||
<haxeflag name="--macro" value="include('haxe.ui.containers.properties')" />
|
||||
<haxeflag name="--macro" value="include('haxe.ui.core')" />
|
||||
<haxeflag name="--macro" value="include('haxe.ui.components')" />
|
||||
<haxeflag name="--macro" value="include('haxe.ui.containers')" />
|
||||
<!--
|
||||
Ensure additional class packages are available at runtime (some only really used by scripts).
|
||||
Ignore packages we can't include.
|
||||
-->
|
||||
<haxeflag name="--macro" value="include('flixel', true, [ 'flixel.addons.editors.spine.*', 'flixel.addons.nape.*', 'flixel.system.macros.*' ])" />
|
||||
<!-- Necessary to provide stack traces for HScript. -->
|
||||
<haxedef name="hscriptPos" />
|
||||
<haxedef name="safeMode"/>
|
||||
<haxedef name="HXCPP_CHECK_POINTER" />
|
||||
<haxedef name="HXCPP_STACK_LINE" />
|
||||
<haxedef name="HXCPP_STACK_TRACE" />
|
||||
<!-- This macro allows addition of new functionality to existing Flixel. -->
|
||||
<haxeflag name="--macro" value="addMetadata('@:build(funkin.util.macro.FlxMacro.buildFlxBasic())', 'flixel.FlxBasic')" />
|
||||
<!--Place custom nodes like icons here (higher priority to override the HaxeFlixel icon)-->
|
||||
<icon path="art/icon16.png" size="16" />
|
||||
<icon path="art/icon32.png" size="32" />
|
||||
<icon path="art/icon64.png" size="64" />
|
||||
<icon path="art/iconOG.png" />
|
||||
<haxedef name="CAN_OPEN_LINKS" unless="switch" />
|
||||
<haxedef name="CAN_CHEAT" if="switch debug" />
|
||||
<!-- I don't remember what this is for. -->
|
||||
<haxedef name="haxeui_no_mouse_reset" />
|
||||
<!-- Clicking outside a dialog should deselect the current focused component. -->
|
||||
<haxedef name="haxeui_focus_out_on_click" />
|
||||
<!-- Required to use haxe.ui.backend.flixel.UIState with build macros. -->
|
||||
<haxedef name="haxeui_dont_impose_base_class" />
|
||||
<haxedef name="HARDCODED_CREDITS" />
|
||||
|
||||
<!-- Skip the Intro -->
|
||||
<section if="debug">
|
||||
<!-- Starts the game at the specified week, at the first song -->
|
||||
<!-- <haxedef name="week" value="1" if="debug"/> -->
|
||||
<!-- Starts the game at the specified song -->
|
||||
<!-- <haxedef name="song" value="bopeebo" if="debug"/> -->
|
||||
<!-- Difficulty, only used for week or song, defaults to 1 -->
|
||||
<!-- <haxedef name="dif" value="2" if="debug"/> -->
|
||||
</section>
|
||||
<section if="newgrounds">
|
||||
<!-- Enables Ng.core.verbose -->
|
||||
<!-- <haxedef name="NG_VERBOSE" /> -->
|
||||
<!-- Enables a NG debug session, so medals don't permently unlock -->
|
||||
<!-- <haxedef name="NG_DEBUG" /> -->
|
||||
<!-- pretends that the saved session Id was expired, forcing the reconnect prompt -->
|
||||
<!-- <haxedef name="NG_FORCE_EXPIRED_SESSION" if="debug" /> -->
|
||||
</section>
|
||||
|
||||
<!-- Uncomment this to wipe your input settings. -->
|
||||
<!-- <haxedef name="CLEAR_INPUT_SAVE"/> -->
|
||||
|
||||
<section if="debug" unless="NO_REDIRECT_ASSETS_FOLDER || html5 || GITHUB_BUILD">
|
||||
<!--
|
||||
Use the parent assets folder rather than the exported one
|
||||
No more will we accidentally undo our changes!
|
||||
-->
|
||||
<haxedef name="REDIRECT_ASSETS_FOLDER" />
|
||||
</section>
|
||||
|
||||
|
||||
<section>
|
||||
<!--
|
||||
This flag enables the popup/crashlog error handler.
|
||||
However, it also messes with breakpoints on some platforms.
|
||||
-->
|
||||
<haxedef name="openfl-enable-handle-error" />
|
||||
</section>
|
||||
|
||||
<!-- Run a script before and after building. -->
|
||||
<prebuild haxe="source/Prebuild.hx"/> -->
|
||||
<postbuild haxe="source/Postbuild.hx"/> -->
|
||||
|
||||
<!-- Enable this on platforms which do not support dropping files onto the window. -->
|
||||
<haxedef name="FILE_DROP_UNSUPPORTED" if="mac" />
|
||||
<section unless="FILE_DROP_UNSUPPORTED">
|
||||
<haxedef name="FILE_DROP_SUPPORTED" />
|
||||
</section>
|
||||
|
||||
<!-- Enable this on platforms which do not support the edsior views. -->
|
||||
<haxedef name="CHART_EDITOR_UNSUPPORTED" if="web" />
|
||||
<haxedef name="CHART_EDITOR_SUPPORTED" unless="web"/>
|
||||
|
||||
<!-- Options for Polymod -->
|
||||
<section if="polymod">
|
||||
<!-- Turns on additional debug logging. -->
|
||||
<haxedef name="POLYMOD_DEBUG" value="true" if="debug" />
|
||||
<!-- The file extension to use for script files. -->
|
||||
<haxedef name="POLYMOD_SCRIPT_EXT" value=".hscript" />
|
||||
<!-- Which asset library to use for scripts. -->
|
||||
<haxedef name="POLYMOD_SCRIPT_LIBRARY" value="scripts" />
|
||||
<!-- The base path from which scripts should be accessed. -->
|
||||
<haxedef name="POLYMOD_ROOT_PATH" value="scripts/" />
|
||||
<!-- Determines the subdirectory of the mod folder used for file appending. -->
|
||||
<haxedef name="POLYMOD_APPEND_FOLDER" value="_append" />
|
||||
<!-- Determines the subdirectory of the mod folder used for file merges. -->
|
||||
<haxedef name="POLYMOD_MERGE_FOLDER" value="_merge" />
|
||||
<!-- Determines the file in the mod folder used for metadata. -->
|
||||
<haxedef name="POLYMOD_MOD_METADATA_FILE" value="_polymod_meta.json" />
|
||||
<!-- Determines the file in the mod folder used for the icon. -->
|
||||
<haxedef name="POLYMOD_MOD_ICON_FILE" value="_polymod_icon.png" />
|
||||
</section>
|
||||
</project>
|
||||
@@ -19,7 +19,7 @@ Please check out our [Contributor's guide](./CONTRIBUTORS.md) on how you can act
|
||||
|
||||
# Credits and Special Thanks
|
||||
|
||||
Full credits can be found in-game, or wherever the credits.json file is.
|
||||
Full credits can be found in-game, or in the `credits.json` file which is located [here](https://github.com/FunkinCrew/funkin.assets/blob/main/exclude/data/credits.json).
|
||||
|
||||
## Programming
|
||||
- [ninjamuffin99](https://twitter.com/ninja_muffin99) - Lead Programmer
|
||||
|
||||
+1
-1
Submodule art updated: faeba700c5...fbd3e3df77
+1
-1
Submodule assets updated: 2e1594ee4c...b2404b6b1c
+3
-3
@@ -83,7 +83,7 @@ apt-fast install -y --no-install-recommends \
|
||||
libc6-dev libffi-dev \
|
||||
libx11-dev libxi-dev libxext-dev libxinerama-dev libxrandr-dev \
|
||||
libgl-dev libgl1-mesa-dev \
|
||||
libasound2-dev \
|
||||
libasound2-dev libpulse-dev \
|
||||
libvlc-dev libvlccore-dev
|
||||
EOF
|
||||
|
||||
@@ -137,8 +137,8 @@ ENV PATH="$HAXEPATH:$PATH"
|
||||
RUN <<EOF
|
||||
HOME=/etc haxelib setup "$HAXEPATH/lib"
|
||||
haxelib --global --never install haxelib $haxelib_version
|
||||
haxelib --global --never git haxelib https://github.com/HaxeFoundation/haxelib.git master
|
||||
haxelib --global --never install hmm
|
||||
haxelib --global --never git haxelib https://github.com/FunkinCrew/haxelib.git funkin-patches --skip-dependencies
|
||||
haxelib --global --never git hmm https://github.com/FunkinCrew/hmm funkin-patches
|
||||
EOF
|
||||
|
||||
# hxcpp
|
||||
|
||||
+2
-1
@@ -327,7 +327,8 @@
|
||||
"INLINE",
|
||||
"DYNAMIC",
|
||||
"FINAL"
|
||||
]
|
||||
],
|
||||
"severity": "IGNORE"
|
||||
},
|
||||
"type": "ModifierOrder"
|
||||
},
|
||||
|
||||
+32
-12
@@ -2,14 +2,19 @@
|
||||
|
||||
0. Setup
|
||||
- Download Haxe from [Haxe.org](https://haxe.org)
|
||||
1. Cloning the Repository: Make sure when you clone, you clone the submodules to get the assets repo:
|
||||
- `git clone --recurse-submodules https://github.com/FunkinCrew/funkin.git`
|
||||
- If you accidentally cloned without the `assets` submodule (aka didn't follow the step above), you can run `git submodule update --init --recursive` to get the assets in a foolproof way.
|
||||
2. Install `hmm` (run `haxelib --global install hmm` and then `haxelib --global run hmm setup`)
|
||||
3. Download Git from [git-scm.com](https://www.git-scm.com)
|
||||
4. Install all haxelibs of the current branch by running `hmm install`
|
||||
5. Setup lime: `haxelib run lime setup`
|
||||
6. Platform setup
|
||||
- Download Git from [git-scm.com](https://www.git-scm.com)
|
||||
- Do NOT download the repository using the Download ZIP button on GitHub or you may run into errors!
|
||||
- Instead, open a command prompt and do the following steps...
|
||||
1. Run `cd the\directory\you\want\the\source\code\in` to specify which folder the command prompt is working in.
|
||||
- For example, `cd C:\Users\YOURNAME\Documents` would instruct the command prompt to perform the next steps in your Documents folder.
|
||||
2. Run `git clone https://github.com/FunkinCrew/funkin.git` to clone the base repository.
|
||||
3. Run `cd funkin` to enter the cloned repository's directory.
|
||||
4. Run `git submodule update --init --recursive` to download the game's assets.
|
||||
- NOTE: By performing this operation, you are downloading Content which is proprietary and protected by national and international copyright and trademark laws. See [the LICENSE.md file for the Funkin.assets](https://github.com/FunkinCrew/funkin.assets/blob/main/LICENSE.md) repo for more information.
|
||||
5. Run `haxelib --global install hmm` and then `haxelib --global run hmm setup` to install hmm.json
|
||||
6. Run `hmm install` to install all haxelibs of the current branch
|
||||
7. Run `haxelib run lime setup` to set up lime
|
||||
8. Perform additional platform setup
|
||||
- For Windows, download the [Visual Studio Build Tools](https://aka.ms/vs/17/release/vs_BuildTools.exe)
|
||||
- When prompted, select "Individual Components" and make sure to download the following:
|
||||
- MSVC v143 VS 2022 C++ x64/x86 build tools
|
||||
@@ -17,10 +22,25 @@
|
||||
- Mac: [`lime setup mac` Documentation](https://lime.openfl.org/docs/advanced-setup/macos/)
|
||||
- Linux: [`lime setup linux` Documentation](https://lime.openfl.org/docs/advanced-setup/linux/)
|
||||
- HTML5: Compiles without any extra setup
|
||||
7. If you are targeting for native, you may need to run `lime rebuild PLATFORM` and `lime rebuild PLATFORM -debug`
|
||||
8. `lime test PLATFORM` ! Add `-debug` to enable several debug features such as time travel (`PgUp`/`PgDn` in Play State).
|
||||
9. If you are targeting for native, you may need to run `lime rebuild <PLATFORM>` and `lime rebuild <PLATFORM> -debug`
|
||||
10. `lime test <PLATFORM>` to build and launch the game for your platform (for example, `lime test windows`)
|
||||
|
||||
## Build Flags
|
||||
|
||||
There are several useful build flags you can add to a build to affect how it works. A full list can be found in `project.hxp`, but here's information on some of them:
|
||||
|
||||
- `-debug` to build the game in debug mode. This automatically enables several useful debug features.
|
||||
- This includes enabling in-game debug functions, disables compile-time optimizations, enabling asset redirection (see below), and enabling the VSCode debug server (which can slow the game on some machines but allows for powerful debugging through breakpoints).
|
||||
- `-DGITHUB_BUILD` will enable in-game debug functions (such as the ability to time travel in a song by pressing `PgUp`/`PgDn`), without enabling the other stuff
|
||||
- `-DFEATURE_POLYMOD_MODS` or `-DNO_FEATURE_POLYMOD_MODS` to forcibly enable or disable modding support.
|
||||
- `-DREDIRECT_ASSETS_FOLDER` or `-DNO_REDIRECT_ASSETS_FOLDER` to forcibly enable or disable asset redirection.
|
||||
- This feature causes the game to load exported assets from the project's assets folder rather than the exported one. Great for fast iteration, but the game
|
||||
- `-DFEATURE_DISCORD_RPC` or `-DNO_FEATURE_DISCORD_RPC` to forcibly enable or disable support for Discord Rich Presence.
|
||||
- `-DFEATURE_VIDEO_PLAYBACK` or `-DNO_FEATURE_VIDEO_PLAYBACK` to forcibly enable or disable video cutscene support.
|
||||
- `-DFEATURE_CHART_EDITOR` or `-DNO_FEATURE_CHART_EDITOR` to forcibly enable or disable the chart editor in the Debug menu.
|
||||
- `-DFEATURE_STAGE_EDITOR` to forcibly enable the experimental stage editor.
|
||||
- `-DFEATURE_GHOST_TAPPING` to forcibly enable an experimental gameplay change to the anti-mash system.
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
- During the cloning process, you may experience an error along the lines of `error: RPC failed; curl 92 HTTP/2 stream 0 was not closed cleanly: PROTOCOL_ERROR (err 1)` due to poor connectivity. A common fix is to run ` git config --global http.postBuffer 4096M`.
|
||||
|
||||
If you experience any issues during the compilation process, DO NOT open an issue on GitHub. Instead, check the [Troubleshooting Guide](TROUBLESHOOTING.md) for steps on how to resolve common problems.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Troubleshooting Common Issues
|
||||
# Troubleshooting Common Compilation Issues
|
||||
|
||||
- Weird macro error with a very tall call stack: Restart Visual Studio Code
|
||||
- NOTE: This is caused by Polymod somewhere, and seems to only occur when there is another compile error somewhere in the program. There is a bounty up for it.
|
||||
@@ -13,3 +13,11 @@
|
||||
|
||||
- `LINK : fatal error LNK1201: error writing to program database ''; check for insufficient disk space, invalid path, or insufficient privilege`
|
||||
- This error occurs if the PDB file located in your `export` folder is in use or exceeds 4 GB. Try deleting the `export` folder and building again from scratch.
|
||||
|
||||
- `error: RPC failed; curl 92 HTTP/2 stream 0 was not closed cleanly: PROTOCOL_ERROR (err 1)`
|
||||
- This error can happen during cloning as a result of poor network connectivity. A common fix is to run ` git config --global http.postBuffer 4096M` in your terminal.
|
||||
|
||||
- Repository is missing an `assets` folder, or `assets` folder is empty.
|
||||
- You did not clone the repository correctly! Copy the path to your `funkin` folder and run `cd the\path\you\copied`. Then follow the compilation guide starting from **Step 4**.
|
||||
|
||||
- Other compilation issues may be caused by installing bad library versions. Try deleting the `.haxelib` folder and following the guide starting from **Step 5**.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"name": "EliteMasterEric"
|
||||
}
|
||||
],
|
||||
"api_version": "0.1.0",
|
||||
"api_version": "0.5.0",
|
||||
"mod_version": "1.0.0",
|
||||
"license": "Apache-2.0"
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"name": "EliteMasterEric"
|
||||
}
|
||||
],
|
||||
"api_version": "0.1.0",
|
||||
"api_version": "0.5.0",
|
||||
"mod_version": "1.0.0",
|
||||
"license": "Apache-2.0"
|
||||
}
|
||||
|
||||
@@ -1,51 +1,39 @@
|
||||
{
|
||||
"dependencies": [
|
||||
{
|
||||
"name": "discord_rpc",
|
||||
"name": "FlxPartialSound",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "2d83fa863ef0c1eace5f1cf67c3ac315d1a3a8a5",
|
||||
"url": "https://github.com/Aidan63/linc_discord-rpc"
|
||||
"ref": "a1eab7b9bf507b87200a3341719054fe427f3b15",
|
||||
"url": "https://github.com/FunkinCrew/FlxPartialSound.git"
|
||||
},
|
||||
{
|
||||
"name": "flixel",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "a7d8e3bad89a0a3506a4714121f73d8e34522c49",
|
||||
"ref": "f2b090d6c608471e730b051c8ee22b8b378964b1",
|
||||
"url": "https://github.com/FunkinCrew/flixel"
|
||||
},
|
||||
{
|
||||
"name": "flixel-addons",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "a523c3b56622f0640933944171efed46929e360e",
|
||||
"ref": "7424db4e9164ff46f224a7c47de1b732d2542dd7",
|
||||
"url": "https://github.com/FunkinCrew/flixel-addons"
|
||||
},
|
||||
{
|
||||
"name": "flixel-text-input",
|
||||
"type": "haxelib",
|
||||
"version": "1.1.0"
|
||||
},
|
||||
{
|
||||
"name": "flixel-ui",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "719b4f10d94186ed55f6fef1b6618d32abec8c15",
|
||||
"url": "https://github.com/HaxeFlixel/flixel-ui"
|
||||
"ref": "951a0103a17bfa55eed86703ce50b4fb0d7590bc",
|
||||
"url": "https://github.com/FunkinCrew/flixel-text-input"
|
||||
},
|
||||
{
|
||||
"name": "flxanimate",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "17e0d59fdbc2b6283a5c0e4df41f1c7f27b71c49",
|
||||
"url": "https://github.com/FunkinCrew/flxanimate"
|
||||
},
|
||||
{
|
||||
"name": "FlxPartialSound",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "f986332ba5ab02abd386ce662578baf04904604a",
|
||||
"url": "https://github.com/FunkinCrew/FlxPartialSound.git"
|
||||
"ref": "0654797e5eb7cd7de0c1b2dbaa1efe5a1e1d9412",
|
||||
"url": "https://github.com/Dot-Stuff/flxanimate"
|
||||
},
|
||||
{
|
||||
"name": "format",
|
||||
@@ -56,7 +44,7 @@
|
||||
"name": "funkin.vis",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "38261833590773cb1de34ac5d11e0825696fc340",
|
||||
"ref": "22b1ce089dd924f15cdc4632397ef3504d464e90",
|
||||
"url": "https://github.com/FunkinCrew/funkVis"
|
||||
},
|
||||
{
|
||||
@@ -75,20 +63,22 @@
|
||||
"name": "haxeui-core",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "0212d8fdfcafeb5f0d5a41e1ddba8ff21d0e183b",
|
||||
"ref": "c9d96b168ea2a19274ad7c766ab1a34b57baa793",
|
||||
"url": "https://github.com/haxeui/haxeui-core"
|
||||
},
|
||||
{
|
||||
"name": "haxeui-flixel",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "63a906a6148958dbfde8c7b48d90b0693767fd95",
|
||||
"ref": "013b9d4e56bfe9a034e028a8d685f0b274cb73c4",
|
||||
"url": "https://github.com/haxeui/haxeui-flixel"
|
||||
},
|
||||
{
|
||||
"name": "hscript",
|
||||
"type": "haxelib",
|
||||
"version": "2.5.0"
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "12785398e2f07082f05034cb580682e5671442a2",
|
||||
"url": "https://github.com/FunkinCrew/hscript"
|
||||
},
|
||||
{
|
||||
"name": "hxCodec",
|
||||
@@ -99,8 +89,10 @@
|
||||
},
|
||||
{
|
||||
"name": "hxcpp",
|
||||
"type": "haxelib",
|
||||
"version": "4.3.2"
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "c6bac3d6c7d683f25104296b2f4c50f8c90b8349",
|
||||
"url": "https://github.com/cortex-engine/hxcpp"
|
||||
},
|
||||
{
|
||||
"name": "hxcpp-debug-server",
|
||||
@@ -109,10 +101,24 @@
|
||||
"ref": "147294123f983e35f50a966741474438069a7a8f",
|
||||
"url": "https://github.com/FunkinCrew/hxcpp-debugger"
|
||||
},
|
||||
{
|
||||
"name": "hxdiscord_rpc",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "82c47ecc1a454b7dd644e4fcac7e91155f176dec",
|
||||
"url": "https://github.com/FunkinCrew/hxdiscord_rpc"
|
||||
},
|
||||
{
|
||||
"name": "hxjsonast",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "20e72cc68c823496359775ac1f06500e67f189d5",
|
||||
"url": "https://github.com/nadako/hxjsonast/"
|
||||
},
|
||||
{
|
||||
"name": "hxp",
|
||||
"type": "haxelib",
|
||||
"version": "1.2.2"
|
||||
"version": "1.3.0"
|
||||
},
|
||||
{
|
||||
"name": "json2object",
|
||||
@@ -121,11 +127,25 @@
|
||||
"ref": "a8c26f18463c98da32f744c214fe02273e1823fa",
|
||||
"url": "https://github.com/FunkinCrew/json2object"
|
||||
},
|
||||
{
|
||||
"name": "jsonpatch",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "f9b83215acd586dc28754b4ae7f69d4c06c3b4d3",
|
||||
"url": "https://github.com/EliteMasterEric/jsonpatch"
|
||||
},
|
||||
{
|
||||
"name": "jsonpath",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "7a24193717b36393458c15c0435bb7c4470ecdda",
|
||||
"url": "https://github.com/EliteMasterEric/jsonpath"
|
||||
},
|
||||
{
|
||||
"name": "lime",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "872ff6db2f2d27c0243d4ff76802121ded550dd7",
|
||||
"ref": "fe3368f611a84a19afc03011353945ae4da8fffd",
|
||||
"url": "https://github.com/FunkinCrew/lime"
|
||||
},
|
||||
{
|
||||
@@ -160,29 +180,29 @@
|
||||
"name": "openfl",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "228c1b5063911e2ad75cef6e3168ef0a4b9f9134",
|
||||
"ref": "8306425c497766739510ab29e876059c96f77bd2",
|
||||
"url": "https://github.com/FunkinCrew/openfl"
|
||||
},
|
||||
{
|
||||
"name": "polymod",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "bfbe30d81601b3543d80dce580108ad6b7e182c7",
|
||||
"ref": "0fbdf27fe124549730accd540cec8a183f8652c0",
|
||||
"url": "https://github.com/larsiusprime/polymod"
|
||||
},
|
||||
{
|
||||
"name": "thx.core",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "22605ff44f01971d599641790d6bae4869f7d9f4",
|
||||
"url": "https://github.com/FunkinCrew/thx.core"
|
||||
"ref": "76d87418fadd92eb8e1b61f004cff27d656e53dd",
|
||||
"url": "https://github.com/fponticelli/thx.core"
|
||||
},
|
||||
{
|
||||
"name": "thx.semver",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "cf8d213589a2c7ce4a59b0fdba9e8ff36bc029fa",
|
||||
"url": "https://github.com/FunkinCrew/thx.semver"
|
||||
"ref": "bdb191fe7cf745c02a980749906dbf22719e200b",
|
||||
"url": "https://github.com/fponticelli/thx.semver"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+1150
File diff suppressed because it is too large
Load Diff
+9
-10
@@ -2,6 +2,7 @@ package;
|
||||
|
||||
import flixel.FlxGame;
|
||||
import flixel.FlxState;
|
||||
import funkin.Preferences;
|
||||
import funkin.util.logging.CrashHandler;
|
||||
import funkin.ui.debug.MemoryCounter;
|
||||
import funkin.save.Save;
|
||||
@@ -22,12 +23,6 @@ class Main extends Sprite
|
||||
var gameHeight:Int = 720; // Height of the game in pixels (might be less / more in actual pixels depending on your zoom).
|
||||
var initialState:Class<FlxState> = funkin.InitState; // The FlxState the game starts with.
|
||||
var zoom:Float = -1; // If -1, zoom is automatically calculated to fit the window dimensions.
|
||||
#if web
|
||||
var framerate:Int = 60; // How many frames per second the game should run at.
|
||||
#else
|
||||
// TODO: This should probably be in the options menu?
|
||||
var framerate:Int = 144; // How many frames per second the game should run at.
|
||||
#end
|
||||
var skipSplash:Bool = true; // Whether to skip the flixel splash screen that appears in release mode.
|
||||
var startFullscreen:Bool = false; // Whether to start the game in fullscreen on desktop targets
|
||||
|
||||
@@ -66,6 +61,12 @@ class Main extends Sprite
|
||||
|
||||
function init(?event:Event):Void
|
||||
{
|
||||
#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
|
||||
|
||||
if (hasEventListener(Event.ADDED_TO_STAGE))
|
||||
{
|
||||
removeEventListener(Event.ADDED_TO_STAGE, init);
|
||||
@@ -103,7 +104,7 @@ class Main extends Sprite
|
||||
|
||||
// George recommends binding the save before FlxGame is created.
|
||||
Save.load();
|
||||
var game:FlxGame = new FlxGame(gameWidth, gameHeight, initialState, framerate, framerate, skipSplash, startFullscreen);
|
||||
var game:FlxGame = new FlxGame(gameWidth, gameHeight, initialState, Preferences.framerate, Preferences.framerate, skipSplash, startFullscreen);
|
||||
|
||||
// FlxG.game._customSoundTray wants just the class, it calls new from
|
||||
// create() in there, which gets called when it's added to stage
|
||||
@@ -113,12 +114,10 @@ class Main extends Sprite
|
||||
|
||||
addChild(game);
|
||||
|
||||
#if debug
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
game.debugger.interaction.addTool(new funkin.util.TrackerToolButtonUtil());
|
||||
#end
|
||||
|
||||
addChild(fpsCounter);
|
||||
|
||||
#if hxcpp_debug_server
|
||||
trace('hxcpp_debug_server is enabled! You can now connect to the game with a debugger.');
|
||||
#else
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Pulled from Tracey profiler PR
|
||||
* @see https://github.com/HaxeFoundation/haxe/pull/11772
|
||||
*/
|
||||
|
||||
package cpp.vm.tracy;
|
||||
|
||||
#if (!HXCPP_TRACY)
|
||||
#error "This class cannot be used without -D HXCPP_TRACY"
|
||||
#end
|
||||
enum abstract PlotFormatType(cpp.UInt8) from cpp.UInt8 to cpp.UInt8
|
||||
{
|
||||
var Number = 0;
|
||||
var Memory = 1;
|
||||
var Percentage = 2;
|
||||
}
|
||||
|
||||
@:include('hx/TelemetryTracy.h')
|
||||
extern class Native_TracyProfiler
|
||||
{
|
||||
/**
|
||||
Mark a frame. Call this at the end of each frame loop.
|
||||
**/
|
||||
@:native('::__hxcpp_tracy_framemark')
|
||||
public static function frameMark():Void;
|
||||
|
||||
/**
|
||||
Print a message into Tracy's log.
|
||||
**/
|
||||
@:native('::__hxcpp_tracy_message')
|
||||
public static function message(_msg:String, ?_color:Int = 0x000000):Void;
|
||||
|
||||
/**
|
||||
Tracy can collect additional information about the profiled application,
|
||||
which will be available in the trace description.
|
||||
This can include data such as the source repository revision,
|
||||
the application’s environment (dev/prod), etc.
|
||||
**/
|
||||
@:native('::__hxcpp_tracy_message_app_info')
|
||||
public static function messageAppInfo(_info:String):Void;
|
||||
|
||||
/**
|
||||
Plot a named value to tracy. This will generate a graph in the profiler for you.
|
||||
**/
|
||||
@:native('::__hxcpp_tracy_plot')
|
||||
public static function plot(_name:String, _val:cpp.Float32):Void;
|
||||
|
||||
/**
|
||||
Configure how values are plotted and displayed.
|
||||
**/
|
||||
@:native('::__hxcpp_tracy_plot_config')
|
||||
public static function plotConfig(_name:String, _format:PlotFormatType, ?_step:Bool = false, ?_fill:Bool = false, ?_color:Int = 0x000000):Void;
|
||||
|
||||
/**
|
||||
Set a name for the current thread this function is called in. Supply an optional groupHint so threads become grouped in Tracy's UI.
|
||||
**/
|
||||
@:native('::__hxcpp_tracy_set_thread_name_and_group')
|
||||
public static function setThreadName(_name:String, ?_groupHint:Int = 1):Void;
|
||||
|
||||
/**
|
||||
Create a custom named scoped zone in your code.
|
||||
**/
|
||||
@:native('HXCPP_TRACY_ZONE')
|
||||
public static function zoneScoped(_name:String):Void;
|
||||
}
|
||||
|
||||
#if (scriptable || cppia)
|
||||
class Cppia_TracyProfiler
|
||||
{
|
||||
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.frameMark)
|
||||
public static function frameMark()
|
||||
Native_TracyProfiler.frameMark();
|
||||
|
||||
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.message)
|
||||
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);
|
||||
|
||||
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.plot)
|
||||
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);
|
||||
|
||||
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.setThreadName)
|
||||
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);
|
||||
}
|
||||
|
||||
typedef TracyProfiler = Cppia_TracyProfiler;
|
||||
#else
|
||||
typedef TracyProfiler = Native_TracyProfiler;
|
||||
#end
|
||||
@@ -0,0 +1,161 @@
|
||||
package funkin;
|
||||
|
||||
import openfl.utils.Future;
|
||||
|
||||
/**
|
||||
* A wrapper around `openfl.utils.Assets` which disallows access to the harmful functions.
|
||||
* Later we'll add Funkin-specific caching to this.
|
||||
*/
|
||||
class Assets
|
||||
{
|
||||
/**
|
||||
* Get the file system path for an asset
|
||||
* @param path The asset path to load from, relative to the assets folder
|
||||
* @return The path to the asset on the file system
|
||||
*/
|
||||
public static function getPath(path:String):String
|
||||
{
|
||||
return openfl.utils.Assets.getPath(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load bytes from an asset
|
||||
* May cause stutters or throw an error if the asset is not cached
|
||||
* @param path The asset path to load from
|
||||
* @return The byte contents of the file
|
||||
*/
|
||||
public static function getBytes(path:String):haxe.io.Bytes
|
||||
{
|
||||
return openfl.utils.Assets.getBytes(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load bytes from an asset asynchronously
|
||||
* @param path The asset path to load from
|
||||
* @return A future which promises to return the byte contents of the file
|
||||
*/
|
||||
public static function loadBytes(path:String):Future<openfl.utils.ByteArray>
|
||||
{
|
||||
return openfl.utils.Assets.loadBytes(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load text from an asset.
|
||||
* May cause stutters or throw an error if the asset is not cached
|
||||
* @param path The asset path to load from
|
||||
* @return The text contents of the file
|
||||
*/
|
||||
public static function getText(path:String):String
|
||||
{
|
||||
return openfl.utils.Assets.getText(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load text from an asset asynchronously
|
||||
* @param path The asset path to load from
|
||||
* @return A future which promises to return the text contents of the file
|
||||
*/
|
||||
public static function loadText(path:String):Future<String>
|
||||
{
|
||||
return openfl.utils.Assets.loadText(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a Sound file from an asset
|
||||
* May cause stutters or throw an error if the asset is not cached
|
||||
* @param path The asset path to load from
|
||||
* @return The loaded sound
|
||||
*/
|
||||
public static function getSound(path:String):openfl.media.Sound
|
||||
{
|
||||
return openfl.utils.Assets.getSound(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a Sound file from an asset asynchronously
|
||||
* @param path The asset path to load from
|
||||
* @return A future which promises to return the loaded sound
|
||||
*/
|
||||
public static function loadSound(path:String):Future<openfl.media.Sound>
|
||||
{
|
||||
return openfl.utils.Assets.loadSound(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a Sound file from an asset, with optimizations specific to long-duration music
|
||||
* May cause stutters or throw an error if the asset is not cached
|
||||
* @param path The asset path to load from
|
||||
* @return The loaded sound
|
||||
*/
|
||||
public static function getMusic(path:String):openfl.media.Sound
|
||||
{
|
||||
return openfl.utils.Assets.getMusic(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a Sound file from an asset asynchronously, with optimizations specific to long-duration music
|
||||
* @param path The asset path to load from
|
||||
* @return A future which promises to return the loaded sound
|
||||
*/
|
||||
public static function loadMusic(path:String):Future<openfl.media.Sound>
|
||||
{
|
||||
return openfl.utils.Assets.loadMusic(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a Bitmap from an asset
|
||||
* May cause stutters or throw an error if the asset is not cached
|
||||
* @param path The asset path to load from
|
||||
* @return The loaded Bitmap image
|
||||
*/
|
||||
public static function getBitmapData(path:String):openfl.display.BitmapData
|
||||
{
|
||||
return openfl.utils.Assets.getBitmapData(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a Bitmap from an asset asynchronously
|
||||
* @param path The asset path to load from
|
||||
* @return The future which promises to return the loaded Bitmap image
|
||||
*/
|
||||
public static function loadBitmapData(path:String):Future<openfl.display.BitmapData>
|
||||
{
|
||||
return openfl.utils.Assets.loadBitmapData(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the given asset of the given type exists.
|
||||
* @param path The asset path to check
|
||||
* @param type The asset type to check
|
||||
* @return Whether the asset exists
|
||||
*/
|
||||
public static function exists(path:String, ?type:openfl.utils.AssetType):Bool
|
||||
{
|
||||
return openfl.utils.Assets.exists(path, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a list of all assets of the given type
|
||||
* @param type The asset type to check
|
||||
* @return A list of asset paths
|
||||
*/
|
||||
public static function list(?type:openfl.utils.AssetType):Array<String>
|
||||
{
|
||||
return openfl.utils.Assets.list(type);
|
||||
}
|
||||
|
||||
public static function hasLibrary(name:String):Bool
|
||||
{
|
||||
return openfl.utils.Assets.hasLibrary(name);
|
||||
}
|
||||
|
||||
public static function getLibrary(name:String):lime.utils.AssetLibrary
|
||||
{
|
||||
return openfl.utils.Assets.getLibrary(name);
|
||||
}
|
||||
|
||||
public static function loadLibrary(name:String):Future<openfl.utils.AssetLibrary>
|
||||
{
|
||||
return openfl.utils.Assets.loadLibrary(name);
|
||||
}
|
||||
}
|
||||
@@ -275,6 +275,13 @@ class Conductor
|
||||
return Save.instance.options.audioVisualOffset;
|
||||
}
|
||||
|
||||
public var combinedOffset(get, never):Float;
|
||||
|
||||
function get_combinedOffset():Float
|
||||
{
|
||||
return instrumentalOffset + audioVisualOffset + inputOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of beats in a measure. May be fractional depending on the time signature.
|
||||
*/
|
||||
@@ -397,9 +404,12 @@ class Conductor
|
||||
*/
|
||||
public function update(?songPos:Float, applyOffsets:Bool = true, forceDispatch:Bool = false)
|
||||
{
|
||||
var currentTime:Float = (FlxG.sound.music != null) ? FlxG.sound.music.time : 0.0;
|
||||
var currentLength:Float = (FlxG.sound.music != null) ? FlxG.sound.music.length : 0.0;
|
||||
|
||||
if (songPos == null)
|
||||
{
|
||||
songPos = (FlxG.sound.music != null) ? FlxG.sound.music.time : 0.0;
|
||||
songPos = currentTime;
|
||||
}
|
||||
|
||||
// Take into account instrumental and file format song offsets.
|
||||
@@ -409,8 +419,17 @@ class Conductor
|
||||
var oldBeat:Float = this.currentBeat;
|
||||
var oldStep:Float = this.currentStep;
|
||||
|
||||
// If the song is playing, limit the song position to the length of the song or beginning of the song.
|
||||
if (FlxG.sound.music != null && FlxG.sound.music.playing)
|
||||
{
|
||||
this.songPosition = Math.min(currentLength, Math.max(0, songPos));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.songPosition = songPos;
|
||||
}
|
||||
|
||||
// Set the song position we are at (for purposes of calculating note positions, etc).
|
||||
this.songPosition = songPos;
|
||||
|
||||
currentTimeChange = timeChanges[0];
|
||||
if (this.songPosition > 0.0)
|
||||
@@ -430,7 +449,8 @@ class Conductor
|
||||
else if (currentTimeChange != null && this.songPosition > 0.0)
|
||||
{
|
||||
// roundDecimal prevents representing 8 as 7.9999999
|
||||
this.currentStepTime = FlxMath.roundDecimal((currentTimeChange.beatTime * Constants.STEPS_PER_BEAT) + (this.songPosition - currentTimeChange.timeStamp) / stepLengthMs, 6);
|
||||
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.currentStep = Math.floor(currentStepTime);
|
||||
|
||||
+46
-26
@@ -1,39 +1,42 @@
|
||||
package funkin;
|
||||
|
||||
import funkin.ui.debug.charting.ChartEditorState;
|
||||
import funkin.ui.transition.LoadingState;
|
||||
import flixel.FlxState;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
import flixel.addons.transition.FlxTransitionSprite.GraphicTransTileDiamond;
|
||||
import flixel.addons.transition.TransitionData;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.FlxState;
|
||||
import flixel.graphics.FlxGraphic;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.math.FlxRect;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.system.debug.log.LogStyle;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.util.macro.MacroUtil;
|
||||
import funkin.util.WindowUtil;
|
||||
import funkin.play.PlayStatePlaylist;
|
||||
import openfl.display.BitmapData;
|
||||
import funkin.data.story.level.LevelRegistry;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.data.event.SongEventRegistry;
|
||||
import funkin.data.stage.StageRegistry;
|
||||
import funkin.data.dialogue.conversation.ConversationRegistry;
|
||||
import funkin.data.dialogue.dialoguebox.DialogueBoxRegistry;
|
||||
import funkin.data.dialogue.speaker.SpeakerRegistry;
|
||||
import funkin.data.freeplay.album.AlbumRegistry;
|
||||
import funkin.data.freeplay.player.PlayerRegistry;
|
||||
import funkin.data.freeplay.style.FreeplayStyleRegistry;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import funkin.data.event.SongEventRegistry;
|
||||
import funkin.data.stage.StageRegistry;
|
||||
import funkin.data.story.level.LevelRegistry;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import funkin.play.notes.notekind.NoteKindManager;
|
||||
import funkin.play.PlayStatePlaylist;
|
||||
import funkin.ui.debug.charting.ChartEditorState;
|
||||
import funkin.ui.title.TitleState;
|
||||
import funkin.ui.transition.LoadingState;
|
||||
import funkin.util.CLIUtil;
|
||||
import funkin.util.CLIUtil.CLIParams;
|
||||
import funkin.util.macro.MacroUtil;
|
||||
import funkin.util.TimerUtil;
|
||||
import funkin.util.TrackerUtil;
|
||||
#if discord_rpc
|
||||
import Discord.DiscordClient;
|
||||
import funkin.util.WindowUtil;
|
||||
import openfl.display.BitmapData;
|
||||
#if FEATURE_DISCORD_RPC
|
||||
import funkin.api.discord.DiscordClient;
|
||||
#end
|
||||
|
||||
/**
|
||||
@@ -121,11 +124,11 @@ class InitState extends FlxState
|
||||
//
|
||||
// DISCORD API SETUP
|
||||
//
|
||||
#if discord_rpc
|
||||
DiscordClient.initialize();
|
||||
#if FEATURE_DISCORD_RPC
|
||||
DiscordClient.instance.init();
|
||||
|
||||
Application.current.onExit.add(function(exitCode) {
|
||||
DiscordClient.shutdown();
|
||||
lime.app.Application.current.onExit.add(function(exitCode) {
|
||||
DiscordClient.instance.shutdown();
|
||||
});
|
||||
#end
|
||||
|
||||
@@ -142,13 +145,15 @@ class InitState extends FlxState
|
||||
// Plugins provide a useful interface for globally active Flixel objects,
|
||||
// that receive update events regardless of the current state.
|
||||
// TODO: Move scripted Module behavior to a Flixel plugin.
|
||||
#if debug
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
funkin.util.plugins.MemoryGCPlugin.initialize();
|
||||
#end
|
||||
#if FEATURE_SCREENSHOTS
|
||||
funkin.util.plugins.ScreenshotPlugin.initialize();
|
||||
#end
|
||||
funkin.util.plugins.EvacuateDebugPlugin.initialize();
|
||||
funkin.util.plugins.ForceCrashPlugin.initialize();
|
||||
funkin.util.plugins.ReloadAssetsDebugPlugin.initialize();
|
||||
funkin.util.plugins.ScreenshotPlugin.initialize();
|
||||
funkin.util.plugins.VolumePlugin.initialize();
|
||||
funkin.util.plugins.WatchPlugin.initialize();
|
||||
|
||||
@@ -164,9 +169,11 @@ class InitState extends FlxState
|
||||
SongRegistry.instance.loadEntries();
|
||||
LevelRegistry.instance.loadEntries();
|
||||
NoteStyleRegistry.instance.loadEntries();
|
||||
PlayerRegistry.instance.loadEntries();
|
||||
ConversationRegistry.instance.loadEntries();
|
||||
DialogueBoxRegistry.instance.loadEntries();
|
||||
SpeakerRegistry.instance.loadEntries();
|
||||
FreeplayStyleRegistry.instance.loadEntries();
|
||||
AlbumRegistry.instance.loadEntries();
|
||||
StageRegistry.instance.loadEntries();
|
||||
|
||||
@@ -174,6 +181,8 @@ class InitState extends FlxState
|
||||
// Move it to use a BaseRegistry.
|
||||
CharacterDataParser.loadCharacterCache();
|
||||
|
||||
NoteKindManager.loadScripts();
|
||||
|
||||
ModuleHandler.buildModuleCallbacks();
|
||||
ModuleHandler.loadModuleCache();
|
||||
ModuleHandler.callOnCreate();
|
||||
@@ -218,9 +227,10 @@ class InitState extends FlxState
|
||||
// -DRESULTS
|
||||
FlxG.switchState(() -> new funkin.play.ResultState(
|
||||
{
|
||||
storyMode: false,
|
||||
storyMode: true,
|
||||
title: "Cum Song Erect by Kawai Sprite",
|
||||
songId: "cum",
|
||||
characterId: "pico-playable",
|
||||
difficultyId: "nightmare",
|
||||
isNewHighscore: true,
|
||||
scoreData:
|
||||
@@ -236,8 +246,13 @@ class InitState extends FlxState
|
||||
combo: 69,
|
||||
maxCombo: 69,
|
||||
totalNotesHit: 140,
|
||||
totalNotes: 200 // 0,
|
||||
totalNotes: 190
|
||||
}
|
||||
// 2400 total notes = 7% = LOSS
|
||||
// 240 total notes = 79% = GOOD
|
||||
// 230 total notes = 82% = GREAT
|
||||
// 210 total notes = 91% = EXCELLENT
|
||||
// 190 total notes = PERFECT
|
||||
},
|
||||
}));
|
||||
#elseif ANIMDEBUG
|
||||
@@ -363,11 +378,16 @@ class InitState extends FlxState
|
||||
//
|
||||
// FLIXEL DEBUG SETUP
|
||||
//
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
// Make errors and warnings less annoying.
|
||||
// Forcing this always since I have never been happy to have the debugger to pop up
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
trace('Initializing Flixel debugger...');
|
||||
|
||||
#if !debug
|
||||
// Make errors less annoying on release builds.
|
||||
LogStyle.ERROR.openConsole = false;
|
||||
LogStyle.ERROR.errorSound = null;
|
||||
#end
|
||||
|
||||
// Make errors and warnings less annoying.
|
||||
LogStyle.WARNING.openConsole = false;
|
||||
LogStyle.WARNING.errorSound = null;
|
||||
|
||||
|
||||
+11
-5
@@ -2,7 +2,6 @@ package funkin;
|
||||
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import openfl.utils.AssetType;
|
||||
import openfl.utils.Assets as OpenFlAssets;
|
||||
|
||||
/**
|
||||
* A core class which handles determining asset paths.
|
||||
@@ -11,9 +10,16 @@ class Paths
|
||||
{
|
||||
static var currentLevel:Null<String> = null;
|
||||
|
||||
public static function setCurrentLevel(name:String):Void
|
||||
public static function setCurrentLevel(name:Null<String>):Void
|
||||
{
|
||||
currentLevel = name.toLowerCase();
|
||||
if (name == null)
|
||||
{
|
||||
currentLevel = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentLevel = name.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
public static function stripLibrary(path:String):String
|
||||
@@ -37,11 +43,11 @@ class Paths
|
||||
if (currentLevel != null)
|
||||
{
|
||||
var levelPath:String = getLibraryPathForce(file, currentLevel);
|
||||
if (OpenFlAssets.exists(levelPath, type)) return levelPath;
|
||||
if (Assets.exists(levelPath, type)) return levelPath;
|
||||
}
|
||||
|
||||
var levelPath:String = getLibraryPathForce(file, 'shared');
|
||||
if (OpenFlAssets.exists(levelPath, type)) return levelPath;
|
||||
if (Assets.exists(levelPath, type)) return levelPath;
|
||||
|
||||
return getPreloadPath(file);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,35 @@ import funkin.save.Save;
|
||||
*/
|
||||
class Preferences
|
||||
{
|
||||
/**
|
||||
* FPS
|
||||
* @default `60`
|
||||
*/
|
||||
public static var framerate(get, set):Int;
|
||||
|
||||
static function get_framerate():Int
|
||||
{
|
||||
#if web
|
||||
return 60;
|
||||
#else
|
||||
return Save?.instance?.options?.framerate ?? 60;
|
||||
#end
|
||||
}
|
||||
|
||||
static function set_framerate(value:Int):Int
|
||||
{
|
||||
#if web
|
||||
return 60;
|
||||
#else
|
||||
var save:Save = Save.instance;
|
||||
save.options.framerate = value;
|
||||
save.flush();
|
||||
FlxG.updateFramerate = value;
|
||||
FlxG.drawFramerate = value;
|
||||
return value;
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether some particularly fowl language is displayed.
|
||||
* @default `true`
|
||||
@@ -128,6 +157,48 @@ class Preferences
|
||||
return value;
|
||||
}
|
||||
|
||||
public static var unlockedFramerate(get, set):Bool;
|
||||
|
||||
static function get_unlockedFramerate():Bool
|
||||
{
|
||||
return Save?.instance?.options?.unlockedFramerate;
|
||||
}
|
||||
|
||||
static function set_unlockedFramerate(value:Bool):Bool
|
||||
{
|
||||
if (value != Save.instance.options.unlockedFramerate)
|
||||
{
|
||||
#if web
|
||||
toggleFramerateCap(value);
|
||||
#end
|
||||
}
|
||||
|
||||
var save:Save = Save.instance;
|
||||
save.options.unlockedFramerate = value;
|
||||
save.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
|
||||
|
||||
/**
|
||||
* Loads the user's preferences from the save data and apply them.
|
||||
*/
|
||||
@@ -137,6 +208,17 @@ class Preferences
|
||||
FlxG.autoPause = Preferences.autoPause;
|
||||
// Apply the debugDisplay setting (enables the FPS and RAM display).
|
||||
toggleDebugDisplay(Preferences.debugDisplay);
|
||||
#if web
|
||||
toggleFramerateCap(Preferences.unlockedFramerate);
|
||||
#end
|
||||
}
|
||||
|
||||
static function toggleFramerateCap(unlocked:Bool):Void
|
||||
{
|
||||
#if web
|
||||
var framerateFunction = unlocked ? unlockedFramerateFunction : lockedFramerateFunction;
|
||||
untyped js.Syntax.code("window.requestAnimationFrame = framerateFunction;");
|
||||
#end
|
||||
}
|
||||
|
||||
static function toggleDebugDisplay(show:Bool):Void
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
package funkin.api.discord;
|
||||
|
||||
import Sys.sleep;
|
||||
#if discord_rpc
|
||||
import discord_rpc.DiscordRpc;
|
||||
#end
|
||||
|
||||
class DiscordClient
|
||||
{
|
||||
#if discord_rpc
|
||||
public function new()
|
||||
{
|
||||
trace("Discord Client starting...");
|
||||
DiscordRpc.start(
|
||||
{
|
||||
clientID: "814588678700924999",
|
||||
onReady: onReady,
|
||||
onError: onError,
|
||||
onDisconnected: onDisconnected
|
||||
});
|
||||
trace("Discord Client started.");
|
||||
|
||||
while (true)
|
||||
{
|
||||
DiscordRpc.process();
|
||||
sleep(2);
|
||||
// trace("Discord Client Update");
|
||||
}
|
||||
|
||||
DiscordRpc.shutdown();
|
||||
}
|
||||
|
||||
public static function shutdown()
|
||||
{
|
||||
DiscordRpc.shutdown();
|
||||
}
|
||||
|
||||
static function onReady()
|
||||
{
|
||||
DiscordRpc.presence(
|
||||
{
|
||||
details: "In the Menus",
|
||||
state: null,
|
||||
largeImageKey: 'icon',
|
||||
largeImageText: "Friday Night Funkin'"
|
||||
});
|
||||
}
|
||||
|
||||
static function onError(_code:Int, _message:String)
|
||||
{
|
||||
trace('Error! $_code : $_message');
|
||||
}
|
||||
|
||||
static function onDisconnected(_code:Int, _message:String)
|
||||
{
|
||||
trace('Disconnected! $_code : $_message');
|
||||
}
|
||||
|
||||
public static function initialize()
|
||||
{
|
||||
var DiscordDaemon = sys.thread.Thread.create(() -> {
|
||||
new DiscordClient();
|
||||
});
|
||||
trace("Discord Client initialized");
|
||||
}
|
||||
|
||||
public static function changePresence(details:String, ?state:String, ?smallImageKey:String, ?hasStartTimestamp:Bool, ?endTimestamp:Float)
|
||||
{
|
||||
var startTimestamp:Float = if (hasStartTimestamp) Date.now().getTime() else 0;
|
||||
|
||||
if (endTimestamp > 0)
|
||||
{
|
||||
endTimestamp = startTimestamp + endTimestamp;
|
||||
}
|
||||
|
||||
DiscordRpc.presence(
|
||||
{
|
||||
details: details,
|
||||
state: state,
|
||||
largeImageKey: 'icon',
|
||||
largeImageText: "Friday Night Funkin'",
|
||||
smallImageKey: smallImageKey,
|
||||
// Obtained times are in milliseconds so they are divided so Discord can use it
|
||||
startTimestamp: Std.int(startTimestamp / 1000),
|
||||
endTimestamp: Std.int(endTimestamp / 1000)
|
||||
});
|
||||
|
||||
// trace('Discord RPC Updated. Arguments: $details, $state, $smallImageKey, $hasStartTimestamp, $endTimestamp');
|
||||
}
|
||||
#end
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
package funkin.api.discord;
|
||||
|
||||
#if FEATURE_DISCORD_RPC
|
||||
import hxdiscord_rpc.Discord;
|
||||
import hxdiscord_rpc.Types;
|
||||
import sys.thread.Thread;
|
||||
|
||||
class DiscordClient
|
||||
{
|
||||
static final CLIENT_ID:String = "816168432860790794";
|
||||
|
||||
public static var instance(get, never):DiscordClient;
|
||||
static var _instance:Null<DiscordClient> = null;
|
||||
|
||||
static function get_instance():DiscordClient
|
||||
{
|
||||
if (DiscordClient._instance == null) _instance = new DiscordClient();
|
||||
if (DiscordClient._instance == null) throw "Could not initialize singleton DiscordClient!";
|
||||
return DiscordClient._instance;
|
||||
}
|
||||
|
||||
var handlers:DiscordEventHandlers;
|
||||
|
||||
private function new()
|
||||
{
|
||||
trace('[DISCORD] Initializing event handlers...');
|
||||
|
||||
handlers = DiscordEventHandlers.create();
|
||||
|
||||
handlers.ready = cpp.Function.fromStaticFunction(onReady);
|
||||
handlers.disconnected = cpp.Function.fromStaticFunction(onDisconnected);
|
||||
handlers.errored = cpp.Function.fromStaticFunction(onError);
|
||||
}
|
||||
|
||||
public function init():Void
|
||||
{
|
||||
trace('[DISCORD] Initializing connection...');
|
||||
|
||||
// Discord.initialize(CLIENT_ID, handlers, true, null);
|
||||
Discord.Initialize(CLIENT_ID, cpp.RawPointer.addressOf(handlers), 1, null);
|
||||
|
||||
createDaemon();
|
||||
}
|
||||
|
||||
var daemon:Thread = null;
|
||||
|
||||
function createDaemon():Void
|
||||
{
|
||||
daemon = Thread.create(doDaemonWork);
|
||||
}
|
||||
|
||||
function doDaemonWork():Void
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
trace('[DISCORD] Performing client update...');
|
||||
|
||||
#if DISCORD_DISABLE_IO_THREAD
|
||||
Discord.updateConnection();
|
||||
#end
|
||||
|
||||
Discord.runCallbacks();
|
||||
Sys.sleep(2);
|
||||
}
|
||||
}
|
||||
|
||||
public function shutdown():Void
|
||||
{
|
||||
trace('[DISCORD] Shutting down...');
|
||||
|
||||
Discord.shutdown();
|
||||
}
|
||||
|
||||
public function setPresence(params:DiscordClientPresenceParams):Void
|
||||
{
|
||||
trace('[DISCORD] Updating presence... (${params})');
|
||||
|
||||
Discord.updatePresence(buildPresence(params));
|
||||
}
|
||||
|
||||
function buildPresence(params:DiscordClientPresenceParams):DiscordRichPresence
|
||||
{
|
||||
var presence = DiscordRichPresence.create();
|
||||
|
||||
// Presence should always be playing the game.
|
||||
presence.type = DiscordActivityType_Playing;
|
||||
|
||||
// Text when hovering over the large image. We just leave this as the game name.
|
||||
presence.largeImageText = "Friday Night Funkin'";
|
||||
|
||||
// State should be generally what the person is doing, like "In the Menus" or "Pico (Pico Mix) [Freeplay Hard]"
|
||||
presence.state = cast(params.state, Null<String>);
|
||||
// Details should be what the person is specifically doing, including stuff like timestamps (maybe something like "03:24 elapsed").
|
||||
presence.details = cast(params.details, Null<String>);
|
||||
|
||||
// The large image displaying what the user is doing.
|
||||
// This should probably be album art.
|
||||
// IMPORTANT NOTE: This can be an asset key uploaded to Discord's developer panel OR any URL you like.
|
||||
presence.largeImageKey = cast(params.largeImageKey, Null<String>) ?? "album-volume1";
|
||||
|
||||
trace('[DISCORD] largeImageKey: ${presence.largeImageKey}');
|
||||
|
||||
// TODO: Make this use the song's album art.
|
||||
// presence.largeImageKey = "icon";
|
||||
// presence.largeImageKey = "https://f4.bcbits.com/img/a0746694746_16.jpg";
|
||||
|
||||
// The small inset image for what the user is doing.
|
||||
// This can be the opponent's health icon?
|
||||
// NOTE: Like largeImageKey, this can be a URL, or an asset key.
|
||||
presence.smallImageKey = cast(params.smallImageKey, Null<String>);
|
||||
|
||||
// NOTE: In previous versions, this showed as "Elapsed", but now shows as playtime and doesn't look good
|
||||
// presence.startTimestamp = time - 10;
|
||||
// presence.endTimestamp = time + 30;
|
||||
|
||||
final button1:DiscordButton = DiscordButton.create();
|
||||
button1.label = "Play on Web";
|
||||
button1.url = Constants.URL_NEWGROUNDS;
|
||||
presence.buttons[0] = button1;
|
||||
|
||||
final button2:DiscordButton = DiscordButton.create();
|
||||
button2.label = "Download";
|
||||
button2.url = Constants.URL_ITCH;
|
||||
presence.buttons[1] = button2;
|
||||
|
||||
return 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!');
|
||||
|
||||
final username:String = request[0].username;
|
||||
final globalName:String = request[0].username;
|
||||
final discriminator:Int = Std.parseInt(request[0].discriminator);
|
||||
|
||||
if (discriminator != 0)
|
||||
{
|
||||
trace('[DISCORD] User: ${username}#${discriminator} (${globalName})');
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[DISCORD] User: @${username} (${globalName})');
|
||||
}
|
||||
}
|
||||
|
||||
private static function onDisconnected(errorCode:Int, message:cpp.ConstCharStar):Void
|
||||
{
|
||||
trace('[DISCORD] 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)}"');
|
||||
}
|
||||
|
||||
// public var type(get, set):DiscordActivityType;
|
||||
// public var state(get, set):String;
|
||||
// public var details(get, set):String;
|
||||
// public var startTimestamp(get, set):Int;
|
||||
// public var endTimestamp(get, set):Int;
|
||||
// public var largeImageKey(get, set):String;
|
||||
// public var largeImageText(get, set):String;
|
||||
// public var smallImageKey(get, set):String;
|
||||
// public var smallImageText(get, set):String;
|
||||
//
|
||||
//
|
||||
// public var partyId(get, set)
|
||||
// public var partySize(get, set)
|
||||
// public var partyMax(get, set)
|
||||
// public var partyPrivacy(get, set)
|
||||
//
|
||||
// public var buttons(get, set)
|
||||
//
|
||||
// public var matchSecret(get, set)
|
||||
// public var joinSecret(get, set)
|
||||
// public var spectateSecret(get, set)
|
||||
}
|
||||
|
||||
typedef DiscordClientPresenceParams =
|
||||
{
|
||||
/**
|
||||
* The first row of text below the game title.
|
||||
*/
|
||||
var state:String;
|
||||
|
||||
/**
|
||||
* The second row of text below the game title.
|
||||
* Use `null` to display no text.
|
||||
*/
|
||||
var details:Null<String>;
|
||||
|
||||
/**
|
||||
* A large, 4-row high image to the left of the content.
|
||||
*/
|
||||
var ?largeImageKey:String;
|
||||
|
||||
/**
|
||||
* A small, inset image to the bottom right of `largeImageKey`.
|
||||
*/
|
||||
var ?smallImageKey:String;
|
||||
}
|
||||
#end
|
||||
@@ -24,7 +24,7 @@ class NGUnsafe
|
||||
NG.core.calls.event.logEvent(event).send();
|
||||
trace('should have logged: ' + event);
|
||||
#else
|
||||
#if debug
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
trace('event:$event - not logged, missing NG.io lib');
|
||||
#end
|
||||
#end
|
||||
@@ -39,7 +39,7 @@ class NGUnsafe
|
||||
if (!medal.unlocked) medal.sendUnlock();
|
||||
}
|
||||
#else
|
||||
#if debug
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
trace('medal:$id - not unlocked, missing NG.io lib');
|
||||
#end
|
||||
#end
|
||||
@@ -63,7 +63,7 @@ class NGUnsafe
|
||||
}
|
||||
}
|
||||
#else
|
||||
#if debug
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
trace('Song:$song, Score:$score - not posted, missing NG.io lib');
|
||||
#end
|
||||
#end
|
||||
|
||||
@@ -239,7 +239,7 @@ class NGio
|
||||
NG.core.calls.event.logEvent(event).send();
|
||||
trace('should have logged: ' + event);
|
||||
#else
|
||||
#if debug
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
trace('event:$event - not logged, missing NG.io lib');
|
||||
#end
|
||||
#end
|
||||
@@ -254,7 +254,7 @@ class NGio
|
||||
if (!medal.unlocked) medal.sendUnlock();
|
||||
}
|
||||
#else
|
||||
#if debug
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
trace('medal:$id - not unlocked, missing NG.io lib');
|
||||
#end
|
||||
#end
|
||||
@@ -278,7 +278,7 @@ class NGio
|
||||
}
|
||||
}
|
||||
#else
|
||||
#if debug
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
trace('Song:$song, Score:$score - not posted, missing NG.io lib');
|
||||
#end
|
||||
#end
|
||||
|
||||
@@ -340,6 +340,8 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
if (songMusicData != null)
|
||||
{
|
||||
Conductor.instance.mapTimeChanges(songMusicData.timeChanges);
|
||||
|
||||
if (songMusicData.looped != null && params.loop == null) params.loop = songMusicData.looped;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -388,7 +390,7 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
}
|
||||
else
|
||||
{
|
||||
var music = FunkinSound.load(pathToUse, params?.startingVolume ?? 1.0, params.loop ?? true, false, true);
|
||||
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;
|
||||
@@ -396,6 +398,8 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
// Prevent repeat update() and onFocus() calls.
|
||||
FlxG.sound.list.remove(FlxG.sound.music);
|
||||
|
||||
if (FlxG.sound.music != null && params.onLoad != null) params.onLoad();
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@@ -426,12 +430,13 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
* @param autoDestroy Whether to destroy this sound when it finishes playing.
|
||||
* Leave this value set to `false` if you want to re-use this `FunkinSound` instance.
|
||||
* @param autoPlay Whether to play the sound immediately or wait for a `play()` call.
|
||||
* @param persist Whether to keep this `FunkinSound` between states, or destroy it.
|
||||
* @param onComplete Called when the sound finished playing.
|
||||
* @param onLoad Called when the sound finished loading. Called immediately for succesfully loaded embedded sounds.
|
||||
* @return A `FunkinSound` object, or `null` if the sound could not be loaded.
|
||||
*/
|
||||
public static function load(embeddedSound:FlxSoundAsset, volume:Float = 1.0, looped:Bool = false, autoDestroy:Bool = false, autoPlay:Bool = false,
|
||||
?onComplete:Void->Void, ?onLoad:Void->Void):Null<FunkinSound>
|
||||
persist:Bool = false, ?onComplete:Void->Void, ?onLoad:Void->Void):Null<FunkinSound>
|
||||
{
|
||||
@:privateAccess
|
||||
if (SoundMixer.__soundChannels.length >= SoundMixer.MAX_ACTIVE_CHANNELS)
|
||||
@@ -458,7 +463,7 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
if (autoPlay) sound.play();
|
||||
sound.volume = volume;
|
||||
sound.group = FlxG.sound.defaultSoundGroup;
|
||||
sound.persist = true;
|
||||
sound.persist = persist;
|
||||
|
||||
// Make sure to add the sound to the list.
|
||||
// If it's already in, it won't get re-added.
|
||||
@@ -491,8 +496,10 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
var promise:lime.app.Promise<Null<FunkinSound>> = new lime.app.Promise<Null<FunkinSound>>();
|
||||
|
||||
// split the path and get only after first :
|
||||
// we are bypassing the openfl/lime asset library fuss
|
||||
// we are bypassing the openfl/lime asset library fuss on web only
|
||||
#if web
|
||||
path = Paths.stripLibrary(path);
|
||||
#end
|
||||
|
||||
var soundRequest = FlxPartialSound.partialLoadFromFile(path, start, end);
|
||||
|
||||
@@ -507,7 +514,7 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
});
|
||||
|
||||
soundRequest.future.onComplete(function(partialSound) {
|
||||
var snd = FunkinSound.load(partialSound, volume, looped, autoDestroy, autoPlay, onComplete, onLoad);
|
||||
var snd = FunkinSound.load(partialSound, volume, looped, autoDestroy, autoPlay, false, onComplete, onLoad);
|
||||
promise.complete(snd);
|
||||
});
|
||||
}
|
||||
@@ -533,21 +540,23 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
* Play a sound effect once, then destroy it.
|
||||
* @param key
|
||||
* @param volume
|
||||
* @return static function construct():FunkinSound
|
||||
* @return A `FunkinSound` object, or `null` if the sound could not be loaded.
|
||||
*/
|
||||
public static function playOnce(key:String, volume:Float = 1.0, ?onComplete:Void->Void, ?onLoad:Void->Void):Void
|
||||
public static function playOnce(key:String, volume:Float = 1.0, ?onComplete:Void->Void, ?onLoad:Void->Void):Null<FunkinSound>
|
||||
{
|
||||
var result = FunkinSound.load(key, volume, false, true, true, onComplete, onLoad);
|
||||
var result:Null<FunkinSound> = FunkinSound.load(key, volume, false, true, true, false, onComplete, onLoad);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop all sounds in the pool and allow them to be recycled.
|
||||
*/
|
||||
public static function stopAllAudio(musicToo:Bool = false):Void
|
||||
public static function stopAllAudio(musicToo:Bool = false, persistToo:Bool = false):Void
|
||||
{
|
||||
for (sound in pool)
|
||||
{
|
||||
if (sound == null) continue;
|
||||
if (!persistToo && sound.persist) continue;
|
||||
if (!musicToo && sound == FlxG.sound.music) continue;
|
||||
sound.destroy();
|
||||
}
|
||||
@@ -562,6 +571,14 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
|
||||
return sound;
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces a string representation suitable for debugging.
|
||||
*/
|
||||
public override function toString():String
|
||||
{
|
||||
return 'FunkinSound(${this._label})';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -613,6 +630,11 @@ typedef FunkinSoundPlayMusicParams =
|
||||
|
||||
var ?partialParams:PartialSoundParams;
|
||||
|
||||
/**
|
||||
* Whether the sound should be destroyed on state switches
|
||||
*/
|
||||
var ?persist:Bool;
|
||||
|
||||
var ?onComplete:Void->Void;
|
||||
var ?onLoad:Void->Void;
|
||||
}
|
||||
|
||||
@@ -113,6 +113,11 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
|
||||
public function play(forceRestart:Bool = false, startTime:Float = 0.0, ?endTime:Float)
|
||||
{
|
||||
forEachAlive(function(sound:FunkinSound) {
|
||||
if (sound.length < startTime)
|
||||
{
|
||||
// trace('Queuing sound (${sound.toString()} past its length! Skipping...)');
|
||||
return;
|
||||
}
|
||||
sound.play(forceRestart, startTime, endTime);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
||||
public function initAnalyzer()
|
||||
{
|
||||
@:privateAccess
|
||||
analyzer = new SpectralAnalyzer(snd._channel.__source, 7, 0.1, 40);
|
||||
analyzer = new SpectralAnalyzer(snd._channel.__audioSource, 7, 0.1, 40);
|
||||
|
||||
#if desktop
|
||||
// On desktop it uses FFT stuff that isn't as optimized as the direct browser stuff we use on HTML5
|
||||
@@ -102,7 +102,9 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
||||
var animFrame:Int = Math.round(levels[i].value * 5);
|
||||
|
||||
#if desktop
|
||||
animFrame = Math.round(animFrame * FlxG.sound.volume);
|
||||
// Web version scales with the Flixel volume level.
|
||||
// This line brings platform parity but looks worse.
|
||||
// animFrame = Math.round(animFrame * FlxG.sound.volume);
|
||||
#end
|
||||
|
||||
animFrame = Math.floor(Math.min(5, animFrame));
|
||||
|
||||
@@ -117,7 +117,7 @@ class VisShit
|
||||
{
|
||||
// Math.pow3
|
||||
@:privateAccess
|
||||
var buf = snd._channel.__source.buffer;
|
||||
var buf = snd._channel.__audioSource.buffer;
|
||||
|
||||
// @:privateAccess
|
||||
audioData = cast buf.data; // jank and hacky lol! kinda busted on HTML5 also!!
|
||||
|
||||
@@ -16,7 +16,7 @@ class WaveformDataParser
|
||||
|
||||
// Method 1. This only works if the sound has been played before.
|
||||
@:privateAccess
|
||||
var soundBuffer:Null<lime.media.AudioBuffer> = sound?._channel?.__source?.buffer;
|
||||
var soundBuffer:Null<lime.media.AudioBuffer> = sound?._channel?.__audioSource?.buffer;
|
||||
|
||||
if (soundBuffer == null)
|
||||
{
|
||||
|
||||
@@ -263,7 +263,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||
* @param version The entry's version (use `fetchEntryVersion(id)`).
|
||||
* @return The created entry.
|
||||
*/
|
||||
public function parseEntryDataWithMigration(id:String, version:thx.semver.Version):Null<J>
|
||||
public function parseEntryDataWithMigration(id:String, version:Null<thx.semver.Version>):Null<J>
|
||||
{
|
||||
if (version == null)
|
||||
{
|
||||
|
||||
@@ -46,7 +46,7 @@ class SongEventRegistry
|
||||
|
||||
if (event != null)
|
||||
{
|
||||
trace(' Loaded built-in song event: (${event.id})');
|
||||
trace(' Loaded built-in song event: ${event.id}');
|
||||
eventCache.set(event.id, event);
|
||||
}
|
||||
else
|
||||
@@ -59,9 +59,9 @@ class SongEventRegistry
|
||||
static function registerScriptedEvents()
|
||||
{
|
||||
var scriptedEventClassNames:Array<String> = ScriptedSongEvent.listScriptClasses();
|
||||
trace('Instantiating ${scriptedEventClassNames.length} scripted song events...');
|
||||
if (scriptedEventClassNames == null || scriptedEventClassNames.length == 0) return;
|
||||
|
||||
trace('Instantiating ${scriptedEventClassNames.length} scripted song events...');
|
||||
for (eventCls in scriptedEventClassNames)
|
||||
{
|
||||
var event:SongEvent = ScriptedSongEvent.init(eventCls, "UKNOWN");
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# Freeplay Playable Character Data Schema Changelog
|
||||
|
||||
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.0]
|
||||
Initial release.
|
||||
@@ -0,0 +1,400 @@
|
||||
package funkin.data.freeplay.player;
|
||||
|
||||
import funkin.data.animation.AnimationData;
|
||||
|
||||
@:nullSafety
|
||||
class PlayerData
|
||||
{
|
||||
/**
|
||||
* The sematic version number of the player data JSON format.
|
||||
* Supports fancy comparisons like NPM does it's neat.
|
||||
*/
|
||||
@:default(funkin.data.freeplay.player.PlayerRegistry.PLAYER_DATA_VERSION)
|
||||
public var version:String;
|
||||
|
||||
/**
|
||||
* A readable name for this playable character.
|
||||
*/
|
||||
public var name:String = 'Unknown';
|
||||
|
||||
/**
|
||||
* The character IDs this character is associated with.
|
||||
* Only songs that use these characters will show up in Freeplay.
|
||||
*/
|
||||
@:default([])
|
||||
public var ownedChars:Array<String> = [];
|
||||
|
||||
/**
|
||||
* Whether to show songs with character IDs that aren't associated with any specific character.
|
||||
*/
|
||||
@:optional
|
||||
@:default(false)
|
||||
public var showUnownedChars:Bool = false;
|
||||
|
||||
/**
|
||||
* Which freeplay style to use for this character.
|
||||
*/
|
||||
@:optional
|
||||
@:default("bf")
|
||||
public var freeplayStyle:String = Constants.DEFAULT_FREEPLAY_STYLE;
|
||||
|
||||
/**
|
||||
* Data for displaying this character in the Freeplay menu.
|
||||
* If null, display no DJ.
|
||||
*/
|
||||
@:optional
|
||||
public var freeplayDJ:Null<PlayerFreeplayDJData> = null;
|
||||
|
||||
/**
|
||||
* Data for displaying this character in the Character Select menu.
|
||||
* If null, exclude from Character Select.
|
||||
*/
|
||||
@:optional
|
||||
public var charSelect:Null<PlayerCharSelectData> = null;
|
||||
|
||||
/**
|
||||
* Data for displaying this character in the results screen.
|
||||
*/
|
||||
@:optional
|
||||
public var results:Null<PlayerResultsData> = null;
|
||||
|
||||
/**
|
||||
* Whether this character is unlocked by default.
|
||||
* Use a ScriptedPlayableCharacter to add custom logic.
|
||||
*/
|
||||
@:optional
|
||||
@:default(true)
|
||||
public var unlocked:Bool = true;
|
||||
|
||||
public function new()
|
||||
{
|
||||
this.version = PlayerRegistry.PLAYER_DATA_VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert this StageData into a JSON string.
|
||||
*/
|
||||
public function serialize(pretty:Bool = true):String
|
||||
{
|
||||
// Update generatedBy and version before writing.
|
||||
updateVersionToLatest();
|
||||
|
||||
var writer = new json2object.JsonWriter<PlayerData>();
|
||||
return writer.write(this, pretty ? ' ' : null);
|
||||
}
|
||||
|
||||
public function updateVersionToLatest():Void
|
||||
{
|
||||
this.version = PlayerRegistry.PLAYER_DATA_VERSION;
|
||||
}
|
||||
}
|
||||
|
||||
class PlayerFreeplayDJData
|
||||
{
|
||||
var assetPath:String;
|
||||
var animations:Array<AnimationData>;
|
||||
|
||||
@:optional
|
||||
@:default("BOYFRIEND")
|
||||
var text1:String;
|
||||
|
||||
@:optional
|
||||
@:default("HOT BLOODED IN MORE WAYS THAN ONE")
|
||||
var text2:String;
|
||||
|
||||
@:optional
|
||||
@:default("PROTECT YO NUTS")
|
||||
var text3:String;
|
||||
|
||||
@:jignored
|
||||
var animationMap:Map<String, AnimationData>;
|
||||
|
||||
@:jignored
|
||||
var prefixToOffsetsMap:Map<String, Array<Float>>;
|
||||
|
||||
@:optional
|
||||
var charSelect:Null<PlayerFreeplayDJCharSelectData>;
|
||||
|
||||
@:optional
|
||||
var cartoon:Null<PlayerFreeplayDJCartoonData>;
|
||||
|
||||
@:optional
|
||||
var fistPump:Null<PlayerFreeplayDJFistPumpData>;
|
||||
|
||||
public function new()
|
||||
{
|
||||
animationMap = new Map();
|
||||
}
|
||||
|
||||
function mapAnimations()
|
||||
{
|
||||
if (animationMap == null) animationMap = new Map();
|
||||
if (prefixToOffsetsMap == null) prefixToOffsetsMap = new Map();
|
||||
|
||||
animationMap.clear();
|
||||
prefixToOffsetsMap.clear();
|
||||
for (anim in animations)
|
||||
{
|
||||
animationMap.set(anim.name, anim);
|
||||
prefixToOffsetsMap.set(anim.prefix, anim.offsets);
|
||||
}
|
||||
}
|
||||
|
||||
public function getAtlasPath():String
|
||||
{
|
||||
return Paths.animateAtlas(assetPath);
|
||||
}
|
||||
|
||||
public function getFreeplayDJText(index:Int):String
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 1:
|
||||
return text1;
|
||||
case 2:
|
||||
return text2;
|
||||
case 3:
|
||||
return text3;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
public function getAnimationPrefix(name:String):Null<String>
|
||||
{
|
||||
if (animationMap.size() == 0) mapAnimations();
|
||||
|
||||
var anim = animationMap.get(name);
|
||||
if (anim == null) return null;
|
||||
return anim.prefix;
|
||||
}
|
||||
|
||||
public function getAnimationOffsetsByPrefix(?prefix:String):Array<Float>
|
||||
{
|
||||
if (prefixToOffsetsMap.size() == 0) mapAnimations();
|
||||
if (prefix == null) return [0, 0];
|
||||
return prefixToOffsetsMap.get(prefix);
|
||||
}
|
||||
|
||||
public function getAnimationOffsets(name:String):Array<Float>
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
public function getFistPumpIntroEndFrame():Int
|
||||
{
|
||||
return fistPump?.introEndFrame ?? 0;
|
||||
}
|
||||
|
||||
public function getFistPumpLoopStartFrame():Int
|
||||
{
|
||||
return fistPump?.loopStartFrame ?? 0;
|
||||
}
|
||||
|
||||
public function getFistPumpLoopEndFrame():Int
|
||||
{
|
||||
return fistPump?.loopEndFrame ?? 0;
|
||||
}
|
||||
|
||||
public function getFistPumpIntroBadStartFrame():Int
|
||||
{
|
||||
return fistPump?.introBadStartFrame ?? 0;
|
||||
}
|
||||
|
||||
public function getFistPumpIntroBadEndFrame():Int
|
||||
{
|
||||
return fistPump?.introBadEndFrame ?? 0;
|
||||
}
|
||||
|
||||
public function getFistPumpLoopBadStartFrame():Int
|
||||
{
|
||||
return fistPump?.loopBadStartFrame ?? 0;
|
||||
}
|
||||
|
||||
public function getFistPumpLoopBadEndFrame():Int
|
||||
{
|
||||
return fistPump?.loopBadEndFrame ?? 0;
|
||||
}
|
||||
|
||||
public function getCharSelectTransitionDelay():Float
|
||||
{
|
||||
return charSelect?.transitionDelay ?? 0.25;
|
||||
}
|
||||
}
|
||||
|
||||
class PlayerCharSelectData
|
||||
{
|
||||
/**
|
||||
* A zero-indexed number for the character's preferred position in the grid.
|
||||
* 0 = top left, 4 = center, 8 = bottom right
|
||||
* In the event of a conflict, the first character alphabetically gets it,
|
||||
* and others get shifted over.
|
||||
*/
|
||||
@:optional
|
||||
public var position:Null<Int>;
|
||||
|
||||
/**
|
||||
* The GF name to assign for this character.
|
||||
*/
|
||||
@:optional
|
||||
public var gf:PlayerCharSelectGFData;
|
||||
}
|
||||
|
||||
typedef PlayerCharSelectGFData =
|
||||
{
|
||||
@:optional
|
||||
public var assetPath:String;
|
||||
|
||||
@:optional
|
||||
public var animInfoPath:String;
|
||||
|
||||
@:optional
|
||||
@:default(false)
|
||||
public var visualizer:Bool;
|
||||
}
|
||||
|
||||
typedef PlayerResultsData =
|
||||
{
|
||||
var music:PlayerResultsMusicData;
|
||||
|
||||
var perfectGold:Array<PlayerResultsAnimationData>;
|
||||
var perfect:Array<PlayerResultsAnimationData>;
|
||||
var excellent:Array<PlayerResultsAnimationData>;
|
||||
var great:Array<PlayerResultsAnimationData>;
|
||||
var good:Array<PlayerResultsAnimationData>;
|
||||
var loss:Array<PlayerResultsAnimationData>;
|
||||
};
|
||||
|
||||
typedef PlayerResultsMusicData =
|
||||
{
|
||||
@:optional
|
||||
var PERFECT_GOLD:String;
|
||||
|
||||
@:optional
|
||||
var PERFECT:String;
|
||||
|
||||
@:optional
|
||||
var EXCELLENT:String;
|
||||
|
||||
@:optional
|
||||
var GREAT:String;
|
||||
|
||||
@:optional
|
||||
var GOOD:String;
|
||||
|
||||
@:optional
|
||||
var SHIT:String;
|
||||
}
|
||||
|
||||
typedef PlayerResultsAnimationData =
|
||||
{
|
||||
/**
|
||||
* `sparrow` or `animate` or whatever
|
||||
*/
|
||||
var renderType:String;
|
||||
|
||||
var assetPath:String;
|
||||
|
||||
@:optional
|
||||
@:default([0, 0])
|
||||
var offsets:Array<Float>;
|
||||
|
||||
@:optional
|
||||
@:default(500)
|
||||
var zIndex:Int;
|
||||
|
||||
@:optional
|
||||
@:default(0.0)
|
||||
var delay:Float;
|
||||
|
||||
@:optional
|
||||
@:default(1.0)
|
||||
var scale:Float;
|
||||
|
||||
@:optional
|
||||
@:default('')
|
||||
var startFrameLabel:Null<String>;
|
||||
|
||||
@:optional
|
||||
@:default(true)
|
||||
var looped:Bool;
|
||||
|
||||
@:optional
|
||||
var loopFrame:Null<Int>;
|
||||
|
||||
@:optional
|
||||
var loopFrameLabel:Null<String>;
|
||||
};
|
||||
|
||||
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)
|
||||
var introStartFrame:Int;
|
||||
|
||||
@:default(4)
|
||||
var introEndFrame:Int;
|
||||
|
||||
@:default(4)
|
||||
var loopStartFrame:Int;
|
||||
|
||||
@:default(-1)
|
||||
var loopEndFrame:Int;
|
||||
|
||||
@:default(0)
|
||||
var introBadStartFrame:Int;
|
||||
|
||||
@:default(4)
|
||||
var introBadEndFrame:Int;
|
||||
|
||||
@:default(4)
|
||||
var loopBadStartFrame:Int;
|
||||
|
||||
@:default(-1)
|
||||
var loopBadEndFrame:Int;
|
||||
};
|
||||
@@ -0,0 +1,208 @@
|
||||
package funkin.data.freeplay.player;
|
||||
|
||||
import funkin.data.freeplay.player.PlayerData;
|
||||
import funkin.ui.freeplay.charselect.PlayableCharacter;
|
||||
import funkin.ui.freeplay.charselect.ScriptedPlayableCharacter;
|
||||
import funkin.save.Save;
|
||||
|
||||
class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData>
|
||||
{
|
||||
/**
|
||||
* The current version string for the stage data format.
|
||||
* Handle breaking changes by incrementing this value
|
||||
* and adding migration to the `migratePlayerData()` function.
|
||||
*/
|
||||
public static final PLAYER_DATA_VERSION:thx.semver.Version = "1.0.0";
|
||||
|
||||
public static final PLAYER_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
|
||||
|
||||
public static var instance(get, never):PlayerRegistry;
|
||||
static var _instance:Null<PlayerRegistry> = null;
|
||||
|
||||
static function get_instance():PlayerRegistry
|
||||
{
|
||||
if (_instance == null) _instance = new PlayerRegistry();
|
||||
return _instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* A mapping between stage character IDs and Freeplay playable character IDs.
|
||||
*/
|
||||
var ownedCharacterIds:Map<String, String> = [];
|
||||
|
||||
public function new()
|
||||
{
|
||||
super('PLAYER', 'players', PLAYER_DATA_VERSION_RULE);
|
||||
}
|
||||
|
||||
public override function loadEntries():Void
|
||||
{
|
||||
super.loadEntries();
|
||||
|
||||
for (playerId in listEntryIds())
|
||||
{
|
||||
var player = fetchEntry(playerId);
|
||||
if (player == null) continue;
|
||||
|
||||
var currentPlayerCharIds = player.getOwnedCharacterIds();
|
||||
for (characterId in currentPlayerCharIds)
|
||||
{
|
||||
ownedCharacterIds.set(characterId, playerId);
|
||||
}
|
||||
}
|
||||
|
||||
log('Loaded ${countEntries()} playable characters with ${ownedCharacterIds.size()} associations.');
|
||||
}
|
||||
|
||||
public function countUnlockedCharacters():Int
|
||||
{
|
||||
var count = 0;
|
||||
|
||||
for (charId in listEntryIds())
|
||||
{
|
||||
var player = fetchEntry(charId);
|
||||
if (player == null) continue;
|
||||
|
||||
if (player.isUnlocked()) count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public function hasNewCharacter():Bool
|
||||
{
|
||||
var charactersSeen = Save.instance.charactersSeen.clone();
|
||||
|
||||
for (charId in listEntryIds())
|
||||
{
|
||||
var player = fetchEntry(charId);
|
||||
if (player == null) continue;
|
||||
|
||||
if (!player.isUnlocked()) continue;
|
||||
if (charactersSeen.contains(charId)) continue;
|
||||
|
||||
// This character is unlocked but we haven't seen them in Freeplay yet.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fallthrough case.
|
||||
return false;
|
||||
}
|
||||
|
||||
public function listNewCharacters():Array<String>
|
||||
{
|
||||
var charactersSeen = Save.instance.charactersSeen.clone();
|
||||
var result = [];
|
||||
|
||||
for (charId in listEntryIds())
|
||||
{
|
||||
var player = fetchEntry(charId);
|
||||
if (player == null) continue;
|
||||
|
||||
if (!player.isUnlocked()) continue;
|
||||
if (charactersSeen.contains(charId)) continue;
|
||||
|
||||
// This character is unlocked but we haven't seen them in Freeplay yet.
|
||||
result.push(charId);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the playable character associated with a given stage character.
|
||||
* @param characterId The stage character ID.
|
||||
* @return The playable character.
|
||||
*/
|
||||
public function getCharacterOwnerId(characterId:Null<String>):Null<String>
|
||||
{
|
||||
if (characterId == null) return null;
|
||||
return ownedCharacterIds[characterId];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the given stage character is associated with a specific playable character.
|
||||
* If so, the level should only appear if that character is selected in Freeplay.
|
||||
* @param characterId The stage character ID.
|
||||
* @return Whether the character is owned by any one character.
|
||||
*/
|
||||
public function isCharacterOwned(characterId:String):Bool
|
||||
{
|
||||
return ownedCharacterIds.exists(characterId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
||||
*/
|
||||
public function parseEntryData(id:String):Null<PlayerData>
|
||||
{
|
||||
// JsonParser does not take type parameters,
|
||||
// otherwise this function would be in BaseRegistry.
|
||||
var parser = new json2object.JsonParser<PlayerData>();
|
||||
parser.ignoreUnknownVariables = false;
|
||||
|
||||
switch (loadEntryFile(id))
|
||||
{
|
||||
case {fileName: fileName, contents: contents}:
|
||||
parser.fromJson(contents, fileName);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
if (parser.errors.length > 0)
|
||||
{
|
||||
printErrors(parser.errors, id);
|
||||
return null;
|
||||
}
|
||||
return parser.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and validate the JSON data and produce the corresponding data object.
|
||||
*
|
||||
* NOTE: Must be implemented on the implementation class.
|
||||
* @param contents The JSON as a string.
|
||||
* @param fileName An optional file name for error reporting.
|
||||
*/
|
||||
public function parseEntryDataRaw(contents:String, ?fileName:String):Null<PlayerData>
|
||||
{
|
||||
var parser = new json2object.JsonParser<PlayerData>();
|
||||
parser.ignoreUnknownVariables = false;
|
||||
parser.fromJson(contents, fileName);
|
||||
|
||||
if (parser.errors.length > 0)
|
||||
{
|
||||
printErrors(parser.errors, fileName);
|
||||
return null;
|
||||
}
|
||||
return parser.value;
|
||||
}
|
||||
|
||||
function createScriptedEntry(clsName:String):PlayableCharacter
|
||||
{
|
||||
return ScriptedPlayableCharacter.init(clsName, "unknown");
|
||||
}
|
||||
|
||||
function getScriptedClassNames():Array<String>
|
||||
{
|
||||
return ScriptedPlayableCharacter.listScriptClasses();
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of all the playable characters from the base game, in order.
|
||||
*/
|
||||
public function listBaseGamePlayerIds():Array<String>
|
||||
{
|
||||
return ["bf", "pico"];
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of all installed playable characters that are not from the base game.
|
||||
*/
|
||||
public function listModdedPlayerIds():Array<String>
|
||||
{
|
||||
return listEntryIds().filter(function(id:String):Bool {
|
||||
return listBaseGamePlayerIds().indexOf(id) == -1;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
# Freeplay Style Data Schema Changelog
|
||||
|
||||
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.0]
|
||||
Initial release.
|
||||
@@ -0,0 +1,48 @@
|
||||
package funkin.data.freeplay.style;
|
||||
|
||||
import funkin.data.animation.AnimationData;
|
||||
|
||||
/**
|
||||
* A type definition for the data for an album of songs.
|
||||
* It includes things like what graphics to display in Freeplay.
|
||||
* @see https://lib.haxe.org/p/json2object/
|
||||
*/
|
||||
typedef FreeplayStyleData =
|
||||
{
|
||||
/**
|
||||
* Semantic version for style data.
|
||||
*/
|
||||
public var version:String;
|
||||
|
||||
/**
|
||||
* Asset key for the background image.
|
||||
*/
|
||||
public var bgAsset:String;
|
||||
|
||||
/**
|
||||
* Asset key for the difficulty selector image.
|
||||
*/
|
||||
public var selectorAsset:String;
|
||||
|
||||
/**
|
||||
* Asset key for the numbers shown at the top right of the screen.
|
||||
*/
|
||||
public var numbersAsset:String;
|
||||
|
||||
/**
|
||||
* Asset key for the freeplay capsules.
|
||||
*/
|
||||
public var capsuleAsset:String;
|
||||
|
||||
/**
|
||||
* Color data for the capsule text outline.
|
||||
* the order of this array goes as follows: [DESELECTED, SELECTED]
|
||||
*/
|
||||
public var capsuleTextColors:Array<String>;
|
||||
|
||||
/**
|
||||
* Delay time after confirming a song selection, before entering PlayState.
|
||||
* Useful for letting longer animations play out.
|
||||
*/
|
||||
public var startDelay:Float;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package funkin.data.freeplay.style;
|
||||
|
||||
import funkin.ui.freeplay.FreeplayStyle;
|
||||
import funkin.data.freeplay.style.FreeplayStyleData;
|
||||
import funkin.ui.freeplay.ScriptedFreeplayStyle;
|
||||
|
||||
class FreeplayStyleRegistry extends BaseRegistry<FreeplayStyle, FreeplayStyleData>
|
||||
{
|
||||
/**
|
||||
* The current version string for the style data format.
|
||||
* Handle breaking changes by incrementing this value
|
||||
* and adding migration to the `migrateStyleData()` function.
|
||||
*/
|
||||
public static final FREEPLAYSTYLE_DATA_VERSION:thx.semver.Version = '1.0.0';
|
||||
|
||||
public static final FREEPLAYSTYLE_DATA_VERSION_RULE:thx.semver.VersionRule = '1.0.x';
|
||||
|
||||
public static final instance:FreeplayStyleRegistry = new FreeplayStyleRegistry();
|
||||
|
||||
public function new()
|
||||
{
|
||||
super('FREEPLAYSTYLE', 'ui/freeplay/styles', FREEPLAYSTYLE_DATA_VERSION_RULE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
||||
* @param id The ID of the entry to load.
|
||||
* @return The parsed data object.
|
||||
*/
|
||||
public function parseEntryData(id:String):Null<FreeplayStyleData>
|
||||
{
|
||||
// JsonParser does not take type parameters,
|
||||
// otherwise this function would be in BaseRegistry.
|
||||
var parser:json2object.JsonParser<FreeplayStyleData> = new json2object.JsonParser<FreeplayStyleData>();
|
||||
parser.ignoreUnknownVariables = false;
|
||||
|
||||
switch (loadEntryFile(id))
|
||||
{
|
||||
case {fileName: fileName, contents: contents}:
|
||||
parser.fromJson(contents, fileName);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
if (parser.errors.length > 0)
|
||||
{
|
||||
printErrors(parser.errors, id);
|
||||
return null;
|
||||
}
|
||||
return parser.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and validate the JSON data and produce the corresponding data object.
|
||||
*
|
||||
* NOTE: Must be implemented on the implementation class.
|
||||
* @param contents The JSON as a string.
|
||||
* @param fileName An optional file name for error reporting.
|
||||
* @return The parsed data object.
|
||||
*/
|
||||
public function parseEntryDataRaw(contents:String, ?fileName:String):Null<FreeplayStyleData>
|
||||
{
|
||||
var parser:json2object.JsonParser<FreeplayStyleData> = new json2object.JsonParser<FreeplayStyleData>();
|
||||
parser.ignoreUnknownVariables = false;
|
||||
parser.fromJson(contents, fileName);
|
||||
|
||||
if (parser.errors.length > 0)
|
||||
{
|
||||
printErrors(parser.errors, fileName);
|
||||
return null;
|
||||
}
|
||||
return parser.value;
|
||||
}
|
||||
|
||||
function createScriptedEntry(clsName:String):FreeplayStyle
|
||||
{
|
||||
return ScriptedFreeplayStyle.init(clsName, 'unknown');
|
||||
}
|
||||
|
||||
function getScriptedClassNames():Array<String>
|
||||
{
|
||||
return ScriptedFreeplayStyle.listScriptClasses();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
# Note Style Data Schema Changelog
|
||||
|
||||
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.1.0]
|
||||
### Added
|
||||
- Added several new `assets`:
|
||||
- `countdownThree`
|
||||
- `countdownTwo`
|
||||
- `countdownOne`
|
||||
- `countdownGo`
|
||||
- `judgementSick`
|
||||
- `judgementGood`
|
||||
- `judgementBad`
|
||||
- `judgementShit`
|
||||
- `comboNumber0`
|
||||
- `comboNumber1`
|
||||
- `comboNumber2`
|
||||
- `comboNumber3`
|
||||
- `comboNumber4`
|
||||
- `comboNumber5`
|
||||
- `comboNumber6`
|
||||
- `comboNumber7`
|
||||
- `comboNumber8`
|
||||
- `comboNumber9`
|
||||
|
||||
## [1.0.0]
|
||||
Initial version.
|
||||
@@ -74,6 +74,84 @@ typedef NoteStyleAssetsData =
|
||||
*/
|
||||
@:optional
|
||||
var holdNoteCover:NoteStyleAssetData<NoteStyleData_HoldNoteCover>;
|
||||
|
||||
/**
|
||||
* The THREE sound (and an optional pre-READY graphic).
|
||||
*/
|
||||
@:optional
|
||||
var countdownThree:NoteStyleAssetData<NoteStyleData_Countdown>;
|
||||
|
||||
/**
|
||||
* The TWO sound and READY graphic.
|
||||
*/
|
||||
@:optional
|
||||
var countdownTwo:NoteStyleAssetData<NoteStyleData_Countdown>;
|
||||
|
||||
/**
|
||||
* The ONE sound and SET graphic.
|
||||
*/
|
||||
@:optional
|
||||
var countdownOne:NoteStyleAssetData<NoteStyleData_Countdown>;
|
||||
|
||||
/**
|
||||
* The GO sound and GO! graphic.
|
||||
*/
|
||||
@:optional
|
||||
var countdownGo:NoteStyleAssetData<NoteStyleData_Countdown>;
|
||||
|
||||
/**
|
||||
* The SICK! judgement.
|
||||
*/
|
||||
@:optional
|
||||
var judgementSick:NoteStyleAssetData<NoteStyleData_Judgement>;
|
||||
|
||||
/**
|
||||
* The GOOD! judgement.
|
||||
*/
|
||||
@:optional
|
||||
var judgementGood:NoteStyleAssetData<NoteStyleData_Judgement>;
|
||||
|
||||
/**
|
||||
* The BAD! judgement.
|
||||
*/
|
||||
@:optional
|
||||
var judgementBad:NoteStyleAssetData<NoteStyleData_Judgement>;
|
||||
|
||||
/**
|
||||
* The SHIT! judgement.
|
||||
*/
|
||||
@:optional
|
||||
var judgementShit:NoteStyleAssetData<NoteStyleData_Judgement>;
|
||||
|
||||
@:optional
|
||||
var comboNumber0:NoteStyleAssetData<NoteStyleData_ComboNum>;
|
||||
|
||||
@:optional
|
||||
var comboNumber1:NoteStyleAssetData<NoteStyleData_ComboNum>;
|
||||
|
||||
@:optional
|
||||
var comboNumber2:NoteStyleAssetData<NoteStyleData_ComboNum>;
|
||||
|
||||
@:optional
|
||||
var comboNumber3:NoteStyleAssetData<NoteStyleData_ComboNum>;
|
||||
|
||||
@:optional
|
||||
var comboNumber4:NoteStyleAssetData<NoteStyleData_ComboNum>;
|
||||
|
||||
@:optional
|
||||
var comboNumber5:NoteStyleAssetData<NoteStyleData_ComboNum>;
|
||||
|
||||
@:optional
|
||||
var comboNumber6:NoteStyleAssetData<NoteStyleData_ComboNum>;
|
||||
|
||||
@:optional
|
||||
var comboNumber7:NoteStyleAssetData<NoteStyleData_ComboNum>;
|
||||
|
||||
@:optional
|
||||
var comboNumber8:NoteStyleAssetData<NoteStyleData_ComboNum>;
|
||||
|
||||
@:optional
|
||||
var comboNumber9:NoteStyleAssetData<NoteStyleData_ComboNum>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,10 +187,19 @@ typedef NoteStyleAssetData<T> =
|
||||
@:optional
|
||||
var isPixel:Bool;
|
||||
|
||||
/**
|
||||
* If true, animations will be played on the graphic.
|
||||
* @default `false` to save performance.
|
||||
*/
|
||||
@:default(false)
|
||||
@:optional
|
||||
var animated:Bool;
|
||||
|
||||
/**
|
||||
* The structure of this data depends on the asset.
|
||||
*/
|
||||
var data:T;
|
||||
@:optional
|
||||
var data:Null<T>;
|
||||
}
|
||||
|
||||
typedef NoteStyleData_Note =
|
||||
@@ -123,7 +210,14 @@ typedef NoteStyleData_Note =
|
||||
var right:UnnamedAnimationData;
|
||||
}
|
||||
|
||||
typedef NoteStyleData_Countdown =
|
||||
{
|
||||
var audioPath:String;
|
||||
}
|
||||
|
||||
typedef NoteStyleData_HoldNote = {}
|
||||
typedef NoteStyleData_Judgement = {}
|
||||
typedef NoteStyleData_ComboNum = {}
|
||||
|
||||
/**
|
||||
* Data on animations for each direction of the strumline.
|
||||
|
||||
@@ -11,9 +11,9 @@ class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData>
|
||||
* Handle breaking changes by incrementing this value
|
||||
* and adding migration to the `migrateNoteStyleData()` function.
|
||||
*/
|
||||
public static final NOTE_STYLE_DATA_VERSION:thx.semver.Version = "1.0.0";
|
||||
public static final NOTE_STYLE_DATA_VERSION:thx.semver.Version = "1.1.0";
|
||||
|
||||
public static final NOTE_STYLE_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
|
||||
public static final NOTE_STYLE_DATA_VERSION_RULE:thx.semver.VersionRule = "1.1.x";
|
||||
|
||||
public static var instance(get, never):NoteStyleRegistry;
|
||||
static var _instance:Null<NoteStyleRegistry> = null;
|
||||
|
||||
@@ -5,6 +5,15 @@ 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).
|
||||
|
||||
## [2.2.4]
|
||||
### Added
|
||||
- Added `playData.characters.opponentVocals` to specify which vocal track(s) to play for the opponent.
|
||||
- If the value isn't present, it will use the `playData.characters.opponent`, but if it is present, it will be used (even if it's empty, in which case no vocals will be used for the opponent)
|
||||
- Added `playData.characters.playerVocals` to specify which vocal track(s) to play for the player.
|
||||
- If the value isn't present, it will use the `playData.characters.player`, but if it is present, it will be used (even if it's empty, in which case no vocals will be used for the player)
|
||||
- Added `offsets.altVocals` field to apply vocal offsets when alternate instrumentals are used.
|
||||
|
||||
|
||||
## [2.2.3]
|
||||
### Added
|
||||
- Added `charter` field to denote authorship of a chart.
|
||||
|
||||
@@ -257,18 +257,27 @@ class SongOffsets implements ICloneable<SongOffsets>
|
||||
public var altInstrumentals:Map<String, Float>;
|
||||
|
||||
/**
|
||||
* The offset, in milliseconds, to apply to the song's vocals, relative to the chart.
|
||||
* The offset, in milliseconds, to apply to the song's vocals, relative to the song's base instrumental.
|
||||
* These are applied ON TOP OF the instrumental offset.
|
||||
*/
|
||||
@:optional
|
||||
@:default([])
|
||||
public var vocals:Map<String, Float>;
|
||||
|
||||
public function new(instrumental:Float = 0.0, ?altInstrumentals:Map<String, Float>, ?vocals:Map<String, Float>)
|
||||
/**
|
||||
* The offset, in milliseconds, to apply to the songs vocals, relative to each alternate instrumental.
|
||||
* This is useful for the circumstance where, for example, an alt instrumental has a few seconds of lead in before the song starts.
|
||||
*/
|
||||
@:optional
|
||||
@:default([])
|
||||
public var altVocals:Map<String, Map<String, Float>>;
|
||||
|
||||
public function new(instrumental:Float = 0.0, ?altInstrumentals:Map<String, Float>, ?vocals:Map<String, Float>, ?altVocals:Map<String, Map<String, Float>>)
|
||||
{
|
||||
this.instrumental = instrumental;
|
||||
this.altInstrumentals = altInstrumentals == null ? new Map<String, Float>() : altInstrumentals;
|
||||
this.vocals = vocals == null ? new Map<String, Float>() : vocals;
|
||||
this.altVocals = altVocals == null ? new Map<String, Map<String, Float>>() : altVocals;
|
||||
}
|
||||
|
||||
public function getInstrumentalOffset(?instrumental:String):Float
|
||||
@@ -293,11 +302,19 @@ class SongOffsets implements ICloneable<SongOffsets>
|
||||
return value;
|
||||
}
|
||||
|
||||
public function getVocalOffset(charId:String):Float
|
||||
public function getVocalOffset(charId:String, ?instrumental:String):Float
|
||||
{
|
||||
if (!this.vocals.exists(charId)) return 0.0;
|
||||
|
||||
return this.vocals.get(charId);
|
||||
if (instrumental == null)
|
||||
{
|
||||
if (!this.vocals.exists(charId)) return 0.0;
|
||||
return this.vocals.get(charId);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!this.altVocals.exists(instrumental)) return 0.0;
|
||||
if (!this.altVocals.get(instrumental).exists(charId)) return 0.0;
|
||||
return this.altVocals.get(instrumental).get(charId);
|
||||
}
|
||||
}
|
||||
|
||||
public function setVocalOffset(charId:String, value:Float):Float
|
||||
@@ -320,7 +337,7 @@ class SongOffsets implements ICloneable<SongOffsets>
|
||||
*/
|
||||
public function toString():String
|
||||
{
|
||||
return 'SongOffsets(${this.instrumental}ms, ${this.altInstrumentals}, ${this.vocals})';
|
||||
return 'SongOffsets(${this.instrumental}ms, ${this.altInstrumentals}, ${this.vocals}, ${this.altVocals})';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -529,12 +546,26 @@ class SongCharacterData implements ICloneable<SongCharacterData>
|
||||
@:default([])
|
||||
public var altInstrumentals:Array<String> = [];
|
||||
|
||||
public function new(player:String = '', girlfriend:String = '', opponent:String = '', instrumental:String = '')
|
||||
@:optional
|
||||
public var opponentVocals:Null<Array<String>> = null;
|
||||
|
||||
@:optional
|
||||
public var playerVocals:Null<Array<String>> = null;
|
||||
|
||||
public function new(player:String = '', girlfriend:String = '', opponent:String = '', instrumental:String = '', ?altInstrumentals:Array<String>,
|
||||
?opponentVocals:Array<String>, ?playerVocals:Array<String>)
|
||||
{
|
||||
this.player = player;
|
||||
this.girlfriend = girlfriend;
|
||||
this.opponent = opponent;
|
||||
this.instrumental = instrumental;
|
||||
|
||||
this.altInstrumentals = altInstrumentals;
|
||||
this.opponentVocals = opponentVocals;
|
||||
this.playerVocals = playerVocals;
|
||||
|
||||
if (opponentVocals == null) this.opponentVocals = [opponent];
|
||||
if (playerVocals == null) this.playerVocals = [player];
|
||||
}
|
||||
|
||||
public function clone():SongCharacterData
|
||||
@@ -722,18 +753,6 @@ class SongEventDataRaw implements ICloneable<SongEventDataRaw>
|
||||
{
|
||||
return new SongEventDataRaw(this.time, this.eventKind, this.value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap SongEventData in an abstract so we can overload operators.
|
||||
*/
|
||||
@:forward(time, eventKind, value, activated, getStepTime, clone)
|
||||
abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataRaw
|
||||
{
|
||||
public function new(time:Float, eventKind:String, value:Dynamic = null)
|
||||
{
|
||||
this = new SongEventDataRaw(time, eventKind, value);
|
||||
}
|
||||
|
||||
public function valueAsStruct(?defaultKey:String = "key"):Dynamic
|
||||
{
|
||||
@@ -757,27 +776,27 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
|
||||
}
|
||||
}
|
||||
|
||||
public inline function getHandler():Null<SongEvent>
|
||||
public function getHandler():Null<SongEvent>
|
||||
{
|
||||
return SongEventRegistry.getEvent(this.eventKind);
|
||||
}
|
||||
|
||||
public inline function getSchema():Null<SongEventSchema>
|
||||
public function getSchema():Null<SongEventSchema>
|
||||
{
|
||||
return SongEventRegistry.getEventSchema(this.eventKind);
|
||||
}
|
||||
|
||||
public inline function getDynamic(key:String):Null<Dynamic>
|
||||
public function getDynamic(key:String):Null<Dynamic>
|
||||
{
|
||||
return this.value == null ? null : Reflect.field(this.value, key);
|
||||
}
|
||||
|
||||
public inline function getBool(key:String):Null<Bool>
|
||||
public function getBool(key:String):Null<Bool>
|
||||
{
|
||||
return this.value == null ? null : cast Reflect.field(this.value, key);
|
||||
}
|
||||
|
||||
public inline function getInt(key:String):Null<Int>
|
||||
public function getInt(key:String):Null<Int>
|
||||
{
|
||||
if (this.value == null) return null;
|
||||
var result = Reflect.field(this.value, key);
|
||||
@@ -787,7 +806,7 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
|
||||
return cast result;
|
||||
}
|
||||
|
||||
public inline function getFloat(key:String):Null<Float>
|
||||
public function getFloat(key:String):Null<Float>
|
||||
{
|
||||
if (this.value == null) return null;
|
||||
var result = Reflect.field(this.value, key);
|
||||
@@ -797,17 +816,17 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
|
||||
return cast result;
|
||||
}
|
||||
|
||||
public inline function getString(key:String):String
|
||||
public function getString(key:String):String
|
||||
{
|
||||
return this.value == null ? null : cast Reflect.field(this.value, key);
|
||||
}
|
||||
|
||||
public inline function getArray(key:String):Array<Dynamic>
|
||||
public function getArray(key:String):Array<Dynamic>
|
||||
{
|
||||
return this.value == null ? null : cast Reflect.field(this.value, key);
|
||||
}
|
||||
|
||||
public inline function getBoolArray(key:String):Array<Bool>
|
||||
public function getBoolArray(key:String):Array<Bool>
|
||||
{
|
||||
return this.value == null ? null : cast Reflect.field(this.value, key);
|
||||
}
|
||||
@@ -839,6 +858,19 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap SongEventData in an abstract so we can overload operators.
|
||||
*/
|
||||
@:forward(time, eventKind, value, activated, getStepTime, clone, getHandler, getSchema, getDynamic, getBool, getInt, getFloat, getString, getArray,
|
||||
getBoolArray, buildTooltip, valueAsStruct)
|
||||
abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataRaw
|
||||
{
|
||||
public function new(time:Float, eventKind:String, value:Dynamic = null)
|
||||
{
|
||||
this = new SongEventDataRaw(time, eventKind, value);
|
||||
}
|
||||
|
||||
public function clone():SongEventData
|
||||
{
|
||||
@@ -951,12 +983,18 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
|
||||
return this.kind = value;
|
||||
}
|
||||
|
||||
public function new(time:Float, data:Int, length:Float = 0, kind:String = '')
|
||||
@:alias("p")
|
||||
@:default([])
|
||||
@:optional
|
||||
public var params:Array<NoteParamData>;
|
||||
|
||||
public function new(time:Float, data:Int, length:Float = 0, kind:String = '', ?params:Array<NoteParamData>)
|
||||
{
|
||||
this.time = time;
|
||||
this.data = data;
|
||||
this.length = length;
|
||||
this.kind = kind;
|
||||
this.params = params ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1051,9 +1089,19 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
|
||||
_stepLength = null;
|
||||
}
|
||||
|
||||
public function cloneParams():Array<NoteParamData>
|
||||
{
|
||||
var params:Array<NoteParamData> = [];
|
||||
for (param in this.params)
|
||||
{
|
||||
params.push(param.clone());
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
public function clone():SongNoteDataRaw
|
||||
{
|
||||
return new SongNoteDataRaw(this.time, this.data, this.length, this.kind);
|
||||
return new SongNoteDataRaw(this.time, this.data, this.length, this.kind, cloneParams());
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
@@ -1069,9 +1117,9 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
|
||||
@:forward
|
||||
abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw
|
||||
{
|
||||
public function new(time:Float, data:Int, length:Float = 0, kind:String = '')
|
||||
public function new(time:Float, data:Int, length:Float = 0, kind:String = '', ?params:Array<NoteParamData>)
|
||||
{
|
||||
this = new SongNoteDataRaw(time, data, length, kind);
|
||||
this = new SongNoteDataRaw(time, data, length, kind, params);
|
||||
}
|
||||
|
||||
public static function buildDirectionName(data:Int, strumlineSize:Int = 4):String
|
||||
@@ -1115,7 +1163,7 @@ abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw
|
||||
if (other.kind == '' || this.kind == null) return false;
|
||||
}
|
||||
|
||||
return this.time == other.time && this.data == other.data && this.length == other.length;
|
||||
return this.time == other.time && this.data == other.data && this.length == other.length && this.params == other.params;
|
||||
}
|
||||
|
||||
@:op(A != B)
|
||||
@@ -1134,7 +1182,7 @@ abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw
|
||||
if (other.kind == '') return true;
|
||||
}
|
||||
|
||||
return this.time != other.time || this.data != other.data || this.length != other.length;
|
||||
return this.time != other.time || this.data != other.data || this.length != other.length || this.params != other.params;
|
||||
}
|
||||
|
||||
@:op(A > B)
|
||||
@@ -1171,7 +1219,7 @@ abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw
|
||||
|
||||
public function clone():SongNoteData
|
||||
{
|
||||
return new SongNoteData(this.time, this.data, this.length, this.kind);
|
||||
return new SongNoteData(this.time, this.data, this.length, this.kind, this.params);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1183,3 +1231,30 @@ abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw
|
||||
+ (this.kind != '' ? ' [kind: ${this.kind}])' : ')');
|
||||
}
|
||||
}
|
||||
|
||||
class NoteParamData implements ICloneable<NoteParamData>
|
||||
{
|
||||
@:alias("n")
|
||||
public var name:String;
|
||||
|
||||
@:alias("v")
|
||||
@:jcustomparse(funkin.data.DataParse.dynamicValue)
|
||||
@:jcustomwrite(funkin.data.DataWrite.dynamicValue)
|
||||
public var value:Dynamic;
|
||||
|
||||
public function new(name:String, value:Dynamic)
|
||||
{
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public function clone():NoteParamData
|
||||
{
|
||||
return new NoteParamData(this.name, this.value);
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
return 'NoteParamData(${this.name}, ${this.value})';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
||||
* Handle breaking changes by incrementing this value
|
||||
* and adding migration to the `migrateStageData()` function.
|
||||
*/
|
||||
public static final SONG_METADATA_VERSION:thx.semver.Version = "2.2.3";
|
||||
public static final SONG_METADATA_VERSION:thx.semver.Version = "2.2.4";
|
||||
|
||||
public static final SONG_METADATA_VERSION_RULE:thx.semver.VersionRule = "2.2.x";
|
||||
|
||||
|
||||
@@ -199,6 +199,8 @@ class FNFLegacyImporter
|
||||
{
|
||||
// Handle the dumb logic for mustHitSection.
|
||||
var noteData = note.data;
|
||||
if (noteData < 0) continue; // Exclude Psych event notes.
|
||||
if (noteData > (STRUMLINE_SIZE * 2)) noteData = noteData % (2 * STRUMLINE_SIZE); // Handle other engine event notes.
|
||||
|
||||
// Flip notes if mustHitSection is FALSE (not true lol).
|
||||
if (!mustHitSection)
|
||||
|
||||
@@ -5,6 +5,10 @@ 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.2]
|
||||
### Added
|
||||
- Added the ability to specify `flipX` and `flipY` on stage props to horizontally or vertically flip, respectively.
|
||||
|
||||
## [1.0.1]
|
||||
### Added
|
||||
- Added the ability to specify a hexadecimal color in the `assetPath` field instead of a texture key.
|
||||
|
||||
@@ -118,6 +118,22 @@ typedef StageDataProp =
|
||||
@:default(false)
|
||||
var isPixel:Bool;
|
||||
|
||||
/**
|
||||
* If set to true, the prop will be flipped horizontally.
|
||||
* @default false
|
||||
*/
|
||||
@:optional
|
||||
@:default(false)
|
||||
var flipX:Bool;
|
||||
|
||||
/**
|
||||
* If set to true, the prop will be flipped vertically.
|
||||
* @default false
|
||||
*/
|
||||
@:optional
|
||||
@:default(false)
|
||||
var flipY:Bool;
|
||||
|
||||
/**
|
||||
* Either the scale of the prop as a float, or the [w, h] scale as an array of two floats.
|
||||
* Pro tip: On pixel-art levels, save the sprite small and set this value to 6 or so to save memory.
|
||||
@@ -140,12 +156,12 @@ typedef StageDataProp =
|
||||
* If not zero, this prop will play an animation every X beats of the song.
|
||||
* This requires animations to be defined. If `danceLeft` and `danceRight` are defined,
|
||||
* they will alternated between, otherwise the `idle` animation will be used.
|
||||
*
|
||||
* @default 0
|
||||
* Supports up to 0.25 precision.
|
||||
* @default 0.0
|
||||
*/
|
||||
@:default(0)
|
||||
@:default(0.0)
|
||||
@:optional
|
||||
var danceEvery:Int;
|
||||
var danceEvery:Float;
|
||||
|
||||
/**
|
||||
* How much the prop scrolls relative to the camera. Used to create a parallax effect.
|
||||
|
||||
@@ -11,9 +11,9 @@ class StageRegistry extends BaseRegistry<Stage, StageData>
|
||||
* Handle breaking changes by incrementing this value
|
||||
* and adding migration to the `migrateStageData()` function.
|
||||
*/
|
||||
public static final STAGE_DATA_VERSION:thx.semver.Version = "1.0.0";
|
||||
public static final STAGE_DATA_VERSION:thx.semver.Version = "1.0.2";
|
||||
|
||||
public static final STAGE_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
|
||||
public static final STAGE_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <=1.0.2";
|
||||
|
||||
public static var instance(get, never):StageRegistry;
|
||||
static var _instance:Null<StageRegistry> = null;
|
||||
@@ -93,8 +93,8 @@ class StageRegistry extends BaseRegistry<Stage, StageData>
|
||||
public function listBaseGameStageIds():Array<String>
|
||||
{
|
||||
return [
|
||||
"mainStage", "spookyMansion", "phillyTrain", "limoRide", "mallXmas", "mallEvil", "school", "schoolEvil", "tankmanBattlefield", "phillyStreets",
|
||||
"phillyBlazin",
|
||||
"mainStage", "mainStageErect", "spookyMansion", "phillyTrain", "phillyTrainErect", "limoRide", "limoRideErect", "mallXmas", "mallXmasErect", "mallEvil",
|
||||
"school", "schoolEvil", "tankmanBattlefield", "phillyStreets", "phillyStreetsErect", "phillyBlazin",
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -91,11 +91,13 @@ typedef LevelPropData =
|
||||
|
||||
/**
|
||||
* The frequency to bop at, in beats.
|
||||
* @default 1 = every beat, 2 = every other beat, etc.
|
||||
* 1 = every beat, 2 = every other beat, etc.
|
||||
* Supports up to 0.25 precision.
|
||||
* @default 1.0
|
||||
*/
|
||||
@:default(1)
|
||||
@:default(1.0)
|
||||
@:optional
|
||||
var danceEvery:Int;
|
||||
var danceEvery:Float;
|
||||
|
||||
/**
|
||||
* The offset on the position to render the prop at.
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package funkin.effects;
|
||||
|
||||
import flixel.addons.effects.FlxTrail;
|
||||
import funkin.play.stage.Bopper;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.system.FlxAssets;
|
||||
|
||||
/**
|
||||
* An offshoot of FlxTrail, but accomodates the way Funkin
|
||||
* does offsets for characters. example, fixes Spirits trail
|
||||
*/
|
||||
class FunkTrail extends FlxTrail
|
||||
{
|
||||
/**
|
||||
* Creates a new FunkTrail effect for a specific FlxSprite.
|
||||
*
|
||||
* @param Target The FlxSprite the trail is attached to.
|
||||
* @param Graphic The image to use for the trailsprites. Optional, uses the sprite's graphic if null.
|
||||
* @param Length The amount of trailsprites to create.
|
||||
* @param Delay How often to update the trail. 0 updates every frame.
|
||||
* @param Alpha The alpha value for the very first trailsprite.
|
||||
* @param Diff How much lower the alpha of the next trailsprite is.
|
||||
*/
|
||||
public function new(Target:FlxSprite, ?Graphic:FlxGraphicAsset, Length:Int = 10, Delay:Int = 3, Alpha:Float = 0.4, Diff:Float = 0.05)
|
||||
{
|
||||
super(Target, Graphic, Length, Delay, Alpha, Diff);
|
||||
}
|
||||
|
||||
override public function update(elapsed:Float):Void
|
||||
{
|
||||
if (target is Bopper)
|
||||
{
|
||||
var targ:Bopper = cast target;
|
||||
@:privateAccess effectOffset.set((targ.animOffsets[0] - targ.globalOffsets[0]) * targ.scale.x,
|
||||
(targ.animOffsets[1] - targ.globalOffsets[1]) * targ.scale.y);
|
||||
}
|
||||
|
||||
super.update(elapsed);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package funkin.effects;
|
||||
|
||||
import flixel.util.FlxTimer;
|
||||
import flixel.FlxCamera;
|
||||
import openfl.filters.ColorMatrixFilter;
|
||||
|
||||
class RetroCameraFade
|
||||
{
|
||||
// im lazy, but we only use this for week 6
|
||||
// and also sorta yoinked for djflixel, lol !
|
||||
public static function fadeWhite(camera:FlxCamera, camSteps:Int = 5, time:Float = 1):Void
|
||||
{
|
||||
var steps:Int = 0;
|
||||
var stepsTotal:Int = camSteps;
|
||||
|
||||
new FlxTimer().start(time / stepsTotal, _ -> {
|
||||
var V:Float = (1 / stepsTotal) * steps;
|
||||
if (steps == stepsTotal) V = 1;
|
||||
|
||||
var matrix = [
|
||||
1, 0, 0, 0, V * 255,
|
||||
0, 1, 0, 0, V * 255,
|
||||
0, 0, 1, 0, V * 255,
|
||||
0, 0, 0, 1, 0
|
||||
];
|
||||
camera.filters = [new ColorMatrixFilter(matrix)];
|
||||
steps++;
|
||||
}, stepsTotal + 1);
|
||||
}
|
||||
|
||||
public static function fadeFromWhite(camera:FlxCamera, camSteps:Int = 5, time:Float = 1):Void
|
||||
{
|
||||
var steps:Int = camSteps;
|
||||
var stepsTotal:Int = camSteps;
|
||||
|
||||
var matrixDerp = [
|
||||
1, 0, 0, 0, 1.0 * 255,
|
||||
0, 1, 0, 0, 1.0 * 255,
|
||||
0, 0, 1, 0, 1.0 * 255,
|
||||
0, 0, 0, 1, 0
|
||||
];
|
||||
camera.filters = [new ColorMatrixFilter(matrixDerp)];
|
||||
|
||||
new FlxTimer().start(time / stepsTotal, _ -> {
|
||||
var V:Float = (1 / stepsTotal) * steps;
|
||||
if (steps == stepsTotal) V = 1;
|
||||
|
||||
var matrix = [
|
||||
1, 0, 0, 0, V * 255,
|
||||
0, 1, 0, 0, V * 255,
|
||||
0, 0, 1, 0, V * 255,
|
||||
0, 0, 0, 1, 0
|
||||
];
|
||||
camera.filters = [new ColorMatrixFilter(matrix)];
|
||||
steps--;
|
||||
}, camSteps);
|
||||
}
|
||||
|
||||
public static function fadeToBlack(camera:FlxCamera, camSteps:Int = 5, time:Float = 1):Void
|
||||
{
|
||||
var steps:Int = 0;
|
||||
var stepsTotal:Int = camSteps;
|
||||
|
||||
new FlxTimer().start(time / stepsTotal, _ -> {
|
||||
var V:Float = (1 / stepsTotal) * steps;
|
||||
if (steps == stepsTotal) V = 1;
|
||||
|
||||
var matrix = [
|
||||
1, 0, 0, 0, -V * 255,
|
||||
0, 1, 0, 0, -V * 255,
|
||||
0, 0, 1, 0, -V * 255,
|
||||
0, 0, 0, 1, 0
|
||||
];
|
||||
camera.filters = [new ColorMatrixFilter(matrix)];
|
||||
steps++;
|
||||
}, camSteps);
|
||||
}
|
||||
|
||||
public static function fadeBlack(camera:FlxCamera, camSteps:Int = 5, time:Float = 1):Void
|
||||
{
|
||||
var steps:Int = camSteps;
|
||||
var stepsTotal:Int = camSteps;
|
||||
|
||||
var matrixDerp = [
|
||||
1, 0, 0, 0, -1.0 * 255,
|
||||
0, 1, 0, 0, -1.0 * 255,
|
||||
0, 0, 1, 0, -1.0 * 255,
|
||||
0, 0, 0, 1, 0
|
||||
];
|
||||
camera.filters = [new ColorMatrixFilter(matrixDerp)];
|
||||
|
||||
new FlxTimer().start(time / stepsTotal, _ -> {
|
||||
var V:Float = (1 / stepsTotal) * steps;
|
||||
if (steps == stepsTotal) V = 1;
|
||||
|
||||
var matrix = [
|
||||
1, 0, 0, 0, -V * 255,
|
||||
0, 1, 0, 0, -V * 255,
|
||||
0, 0, 1, 0, -V * 255,
|
||||
0, 0, 0, 1, 0
|
||||
];
|
||||
camera.filters = [new ColorMatrixFilter(matrix)];
|
||||
steps--;
|
||||
}, camSteps + 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
package funkin.graphics;
|
||||
|
||||
import flixel.FlxBasic;
|
||||
import flixel.FlxCamera;
|
||||
import flixel.FlxG;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.graphics.FlxGraphic;
|
||||
import flixel.graphics.frames.FlxFrame;
|
||||
import flixel.math.FlxMatrix;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.math.FlxRect;
|
||||
import flixel.util.FlxColor;
|
||||
import lime.graphics.cairo.Cairo;
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.display.BlendMode;
|
||||
import openfl.display.DisplayObjectRenderer;
|
||||
import openfl.display.Graphics;
|
||||
import openfl.display.OpenGLRenderer;
|
||||
import openfl.display._internal.Context3DGraphics;
|
||||
import openfl.display3D.Context3D;
|
||||
import openfl.display3D.Context3DClearMask;
|
||||
import openfl.filters.BitmapFilter;
|
||||
import openfl.filters.BlurFilter;
|
||||
import openfl.geom.ColorTransform;
|
||||
import openfl.geom.Matrix;
|
||||
import openfl.geom.Point;
|
||||
import openfl.geom.Rectangle;
|
||||
#if (js && html5)
|
||||
import lime._internal.graphics.ImageCanvasUtil;
|
||||
import openfl.display.CanvasRenderer;
|
||||
import openfl.display._internal.CanvasGraphics as GfxRenderer;
|
||||
#else
|
||||
import openfl.display.CairoRenderer;
|
||||
import openfl.display._internal.CairoGraphics as GfxRenderer;
|
||||
#end
|
||||
|
||||
/**
|
||||
* A modified `FlxSprite` that supports filters.
|
||||
* The name's pretty much self-explanatory.
|
||||
*/
|
||||
@:access(openfl.geom.Rectangle)
|
||||
@:access(openfl.filters.BitmapFilter)
|
||||
@:access(flixel.graphics.frames.FlxFrame)
|
||||
class FlxFilteredSprite extends FlxSprite
|
||||
{
|
||||
@:noCompletion var _renderer:FlxAnimateFilterRenderer = new FlxAnimateFilterRenderer();
|
||||
|
||||
@:noCompletion var _filterMatrix:FlxMatrix;
|
||||
|
||||
/**
|
||||
* An `Array` of shader filters (aka `BitmapFilter`).
|
||||
*/
|
||||
public var filters(default, set):Array<BitmapFilter>;
|
||||
|
||||
/**
|
||||
* a flag to update the image with the filters.
|
||||
* Useful when trying to render a shader at all times.
|
||||
*/
|
||||
public var filterDirty:Bool = false;
|
||||
|
||||
@:noCompletion var filtered:Bool;
|
||||
|
||||
@:noCompletion var _blankFrame:FlxFrame;
|
||||
|
||||
var _filterBmp1:BitmapData;
|
||||
var _filterBmp2:BitmapData;
|
||||
|
||||
override public function update(elapsed:Float)
|
||||
{
|
||||
super.update(elapsed);
|
||||
if (!filterDirty && filters != null)
|
||||
{
|
||||
for (filter in filters)
|
||||
{
|
||||
if (filter.__renderDirty)
|
||||
{
|
||||
filterDirty = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@:noCompletion
|
||||
override function initVars():Void
|
||||
{
|
||||
super.initVars();
|
||||
_filterMatrix = new FlxMatrix();
|
||||
filters = null;
|
||||
filtered = false;
|
||||
}
|
||||
|
||||
override public function draw():Void
|
||||
{
|
||||
checkEmptyFrame();
|
||||
|
||||
if (alpha == 0 || _frame.type == FlxFrameType.EMPTY) return;
|
||||
|
||||
if (dirty) // rarely
|
||||
calcFrame(useFramePixels);
|
||||
|
||||
if (filterDirty) filterFrame();
|
||||
|
||||
for (camera in cameras)
|
||||
{
|
||||
if (!camera.visible || !camera.exists || !isOnScreen(camera)) continue;
|
||||
|
||||
getScreenPosition(_point, camera).subtractPoint(offset);
|
||||
|
||||
if (isSimpleRender(camera)) drawSimple(camera);
|
||||
else
|
||||
drawComplex(camera);
|
||||
|
||||
#if FLX_DEBUG
|
||||
FlxBasic.visibleCount++;
|
||||
#end
|
||||
}
|
||||
|
||||
#if FLX_DEBUG
|
||||
if (FlxG.debugger.drawDebug) drawDebug();
|
||||
#end
|
||||
}
|
||||
|
||||
@:noCompletion
|
||||
override function drawComplex(camera:FlxCamera):Void
|
||||
{
|
||||
_frame.prepareMatrix(_matrix, FlxFrameAngle.ANGLE_0, checkFlipX(), checkFlipY());
|
||||
_matrix.concat(_filterMatrix);
|
||||
_matrix.translate(-origin.x, -origin.y);
|
||||
_matrix.scale(scale.x, scale.y);
|
||||
|
||||
if (bakedRotationAngle <= 0)
|
||||
{
|
||||
updateTrig();
|
||||
|
||||
if (angle != 0) _matrix.rotateWithTrig(_cosAngle, _sinAngle);
|
||||
}
|
||||
|
||||
_point.add(origin.x, origin.y);
|
||||
_matrix.translate(_point.x, _point.y);
|
||||
|
||||
if (isPixelPerfectRender(camera))
|
||||
{
|
||||
_matrix.tx = Math.floor(_matrix.tx);
|
||||
_matrix.ty = Math.floor(_matrix.ty);
|
||||
}
|
||||
|
||||
camera.drawPixels((filtered) ? _blankFrame : _frame, framePixels, _matrix, colorTransform, blend, antialiasing, shader);
|
||||
}
|
||||
|
||||
@:noCompletion
|
||||
function filterFrame()
|
||||
{
|
||||
filterDirty = false;
|
||||
_filterMatrix.identity();
|
||||
|
||||
if (filters != null && filters.length > 0)
|
||||
{
|
||||
_flashRect.setEmpty();
|
||||
|
||||
for (filter in filters)
|
||||
{
|
||||
_flashRect.__expand(-filter.__leftExtension,
|
||||
-filter.__topExtension, filter.__leftExtension
|
||||
+ filter.__rightExtension,
|
||||
filter.__topExtension
|
||||
+ filter.__bottomExtension);
|
||||
}
|
||||
_flashRect.width += frameWidth;
|
||||
_flashRect.height += frameHeight;
|
||||
if (_blankFrame == null) _blankFrame = new FlxFrame(null);
|
||||
|
||||
if (_blankFrame.parent == null || _flashRect.width > _blankFrame.parent.width || _flashRect.height > _blankFrame.parent.height)
|
||||
{
|
||||
if (_blankFrame.parent != null)
|
||||
{
|
||||
_blankFrame.parent.destroy();
|
||||
_filterBmp1.dispose();
|
||||
_filterBmp2.dispose();
|
||||
}
|
||||
|
||||
_blankFrame.parent = FlxGraphic.fromRectangle(Math.ceil(_flashRect.width * 1.25), Math.ceil(_flashRect.height * 1.25), 0, true);
|
||||
_filterBmp1 = new BitmapData(_blankFrame.parent.width, _blankFrame.parent.height, 0);
|
||||
_filterBmp2 = new BitmapData(_blankFrame.parent.width, _blankFrame.parent.height, 0);
|
||||
}
|
||||
_blankFrame.offset.copyFrom(_frame.offset);
|
||||
_blankFrame.parent.bitmap = _renderer.applyFilter(_blankFrame.parent.bitmap, _filterBmp1, _filterBmp2, frame.parent.bitmap, filters, _flashRect,
|
||||
frame.frame.copyToFlash());
|
||||
_blankFrame.frame = FlxRect.get(0, 0, _blankFrame.parent.bitmap.width, _blankFrame.parent.bitmap.height);
|
||||
_filterMatrix.translate(_flashRect.x, _flashRect.y);
|
||||
_frame = _blankFrame.copyTo();
|
||||
filtered = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
resetFrame();
|
||||
filtered = false;
|
||||
}
|
||||
}
|
||||
|
||||
@:noCompletion
|
||||
function set_filters(value:Array<BitmapFilter>)
|
||||
{
|
||||
if (filters != value) filterDirty = true;
|
||||
|
||||
return filters = value;
|
||||
}
|
||||
|
||||
@:noCompletion
|
||||
override function set_frame(value:FlxFrame)
|
||||
{
|
||||
if (value != frame) filterDirty = true;
|
||||
|
||||
return super.set_frame(value);
|
||||
}
|
||||
|
||||
override public function destroy()
|
||||
{
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@:noCompletion
|
||||
@:access(openfl.display.OpenGLRenderer)
|
||||
@:access(openfl.filters.BitmapFilter)
|
||||
@:access(openfl.geom.Rectangle)
|
||||
@:access(openfl.display.Stage)
|
||||
@:access(openfl.display.Graphics)
|
||||
@:access(openfl.display.Shader)
|
||||
@:access(openfl.display.BitmapData)
|
||||
@:access(openfl.geom.ColorTransform)
|
||||
@:access(openfl.display.DisplayObject)
|
||||
@:access(openfl.display3D.Context3D)
|
||||
@:access(openfl.display.CanvasRenderer)
|
||||
@:access(openfl.display.CairoRenderer)
|
||||
@:access(openfl.display3D.Context3D)
|
||||
class FlxAnimateFilterRenderer
|
||||
{
|
||||
var renderer:OpenGLRenderer;
|
||||
var context:Context3D;
|
||||
|
||||
public function new()
|
||||
{
|
||||
// context = new openfl.display3D.Context3D(null);
|
||||
renderer = new OpenGLRenderer(FlxG.game.stage.context3D);
|
||||
renderer.__worldTransform = new Matrix();
|
||||
renderer.__worldColorTransform = new ColorTransform();
|
||||
}
|
||||
|
||||
@:noCompletion function setRenderer(renderer:DisplayObjectRenderer, rect:Rectangle)
|
||||
{
|
||||
@:privateAccess
|
||||
if (true)
|
||||
{
|
||||
var displayObject = FlxG.game;
|
||||
var pixelRatio = FlxG.game.stage.__renderer.__pixelRatio;
|
||||
|
||||
var offsetX = rect.x > 0 ? Math.ceil(rect.x) : Math.floor(rect.x);
|
||||
var offsetY = rect.y > 0 ? Math.ceil(rect.y) : Math.floor(rect.y);
|
||||
if (renderer.__worldTransform == null)
|
||||
{
|
||||
renderer.__worldTransform = new Matrix();
|
||||
renderer.__worldColorTransform = new ColorTransform();
|
||||
}
|
||||
if (displayObject.__cacheBitmapColorTransform == null) displayObject.__cacheBitmapColorTransform = new ColorTransform();
|
||||
|
||||
renderer.__stage = displayObject.stage;
|
||||
|
||||
renderer.__allowSmoothing = true;
|
||||
renderer.__setBlendMode(NORMAL);
|
||||
renderer.__worldAlpha = 1 / displayObject.__worldAlpha;
|
||||
|
||||
renderer.__worldTransform.identity();
|
||||
renderer.__worldTransform.invert();
|
||||
renderer.__worldTransform.concat(new Matrix());
|
||||
renderer.__worldTransform.tx -= offsetX;
|
||||
renderer.__worldTransform.ty -= offsetY;
|
||||
renderer.__worldTransform.scale(pixelRatio, pixelRatio);
|
||||
|
||||
renderer.__pixelRatio = pixelRatio;
|
||||
}
|
||||
}
|
||||
|
||||
public function applyFilter(target:BitmapData = null, target1:BitmapData = null, target2:BitmapData = null, bmp:BitmapData, filters:Array<BitmapFilter>,
|
||||
rect:Rectangle, bmpRect:Rectangle)
|
||||
{
|
||||
if (filters == null || filters.length == 0) return bmp;
|
||||
|
||||
renderer.__setBlendMode(NORMAL);
|
||||
renderer.__worldAlpha = 1;
|
||||
|
||||
if (renderer.__worldTransform == null)
|
||||
{
|
||||
renderer.__worldTransform = new Matrix();
|
||||
renderer.__worldColorTransform = new ColorTransform();
|
||||
}
|
||||
renderer.__worldTransform.identity();
|
||||
renderer.__worldColorTransform.__identity();
|
||||
|
||||
var bitmap:BitmapData = (target == null) ? new BitmapData(Math.ceil(rect.width * 1.25), Math.ceil(rect.height * 1.25), true, 0) : target;
|
||||
|
||||
var bitmap2 = (target1 == null) ? new BitmapData(Math.ceil(rect.width * 1.25), Math.ceil(rect.height * 1.25), true, 0) : target1,
|
||||
bitmap3 = (target2 == null) ? bitmap2.clone() : target2;
|
||||
renderer.__setRenderTarget(bitmap);
|
||||
|
||||
bmp.__renderTransform.translate(Math.abs(rect.x) - bmpRect.x, Math.abs(rect.y) - bmpRect.y);
|
||||
bmpRect.x = Math.abs(rect.x);
|
||||
bmpRect.y = Math.abs(rect.y);
|
||||
|
||||
var bestResolution = renderer.__context3D.__backBufferWantsBestResolution;
|
||||
renderer.__context3D.__backBufferWantsBestResolution = false;
|
||||
renderer.__scissorRect(bmpRect);
|
||||
renderer.__renderFilterPass(bmp, renderer.__defaultDisplayShader, true);
|
||||
renderer.__scissorRect();
|
||||
|
||||
renderer.__context3D.__backBufferWantsBestResolution = bestResolution;
|
||||
|
||||
bmp.__renderTransform.identity();
|
||||
|
||||
var shader, cacheBitmap = null;
|
||||
for (filter in filters)
|
||||
{
|
||||
if (filter.__preserveObject)
|
||||
{
|
||||
renderer.__setRenderTarget(bitmap3);
|
||||
renderer.__renderFilterPass(bitmap, renderer.__defaultDisplayShader, filter.__smooth);
|
||||
}
|
||||
|
||||
for (i in 0...filter.__numShaderPasses)
|
||||
{
|
||||
shader = filter.__initShader(renderer, i, (filter.__preserveObject) ? bitmap3 : null);
|
||||
renderer.__setBlendMode(filter.__shaderBlendMode);
|
||||
renderer.__setRenderTarget(bitmap2);
|
||||
renderer.__renderFilterPass(bitmap, shader, filter.__smooth);
|
||||
|
||||
cacheBitmap = bitmap;
|
||||
bitmap = bitmap2;
|
||||
bitmap2 = cacheBitmap;
|
||||
}
|
||||
filter.__renderDirty = false;
|
||||
}
|
||||
if (target1 == null) bitmap2.dispose();
|
||||
if (target2 == null) bitmap3.dispose();
|
||||
|
||||
// var gl = renderer.__gl;
|
||||
|
||||
// var renderBuffer = bitmap.getTexture(renderer.__context3D);
|
||||
// @:privateAccess
|
||||
// gl.readPixels(0, 0, bitmap.width, bitmap.height, renderBuffer.__format, gl.UNSIGNED_BYTE, bitmap.image.data);
|
||||
// bitmap.image.version = 0;
|
||||
// @:privateAccess
|
||||
// bitmap.__textureVersion = -1;
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public function applyBlend(blend:BlendMode, bitmap:BitmapData)
|
||||
{
|
||||
bitmap.__update(false, true);
|
||||
var bmp = new BitmapData(bitmap.width, bitmap.height, 0);
|
||||
|
||||
#if (js && html5)
|
||||
ImageCanvasUtil.convertToCanvas(bmp.image);
|
||||
@:privateAccess
|
||||
var renderer = new CanvasRenderer(bmp.image.buffer.__srcContext);
|
||||
#else
|
||||
var renderer = new CairoRenderer(new Cairo(bmp.getSurface()));
|
||||
#end
|
||||
|
||||
// setRenderer(renderer, bmp.rect);
|
||||
|
||||
var m = new Matrix();
|
||||
var c = new ColorTransform();
|
||||
renderer.__allowSmoothing = true;
|
||||
renderer.__overrideBlendMode = blend;
|
||||
renderer.__worldTransform = m;
|
||||
renderer.__worldAlpha = 1;
|
||||
renderer.__worldColorTransform = c;
|
||||
|
||||
renderer.__setBlendMode(blend);
|
||||
#if (js && html5)
|
||||
bmp.__drawCanvas(bitmap, renderer);
|
||||
#else
|
||||
bmp.__drawCairo(bitmap, renderer);
|
||||
#end
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public function graphicstoBitmapData(gfx:Graphics)
|
||||
{
|
||||
if (gfx.__bounds == null) return null;
|
||||
// var cacheRTT = renderer.__context3D.__state.renderToTexture;
|
||||
// var cacheRTTDepthStencil = renderer.__context3D.__state.renderToTextureDepthStencil;
|
||||
// var cacheRTTAntiAlias = renderer.__context3D.__state.renderToTextureAntiAlias;
|
||||
// var cacheRTTSurfaceSelector = renderer.__context3D.__state.renderToTextureSurfaceSelector;
|
||||
|
||||
// var bmp = new BitmapData(Math.ceil(gfx.__width), Math.ceil(gfx.__height), 0);
|
||||
// renderer.__context3D.setRenderToTexture(bmp.getTexture(renderer.__context3D));
|
||||
// gfx.__owner.__renderTransform.identity();
|
||||
// gfx.__renderTransform.identity();
|
||||
// Context3DGraphics.render(gfx, renderer);
|
||||
GfxRenderer.render(gfx, cast renderer.__softwareRenderer);
|
||||
var bmp = gfx.__bitmap;
|
||||
|
||||
gfx.__bitmap = null;
|
||||
|
||||
// if (cacheRTT != null)
|
||||
// {
|
||||
// renderer.__context3D.setRenderToTexture(cacheRTT, cacheRTTDepthStencil, cacheRTTAntiAlias, cacheRTTSurfaceSelector);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// renderer.__context3D.setRenderToBackBuffer();
|
||||
// }
|
||||
|
||||
return bmp;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,10 @@ import flixel.tweens.FlxTween;
|
||||
import openfl.display3D.textures.TextureBase;
|
||||
import funkin.graphics.framebuffer.FixedBitmapData;
|
||||
import openfl.display.BitmapData;
|
||||
import flixel.math.FlxRect;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.graphics.frames.FlxFrame;
|
||||
import flixel.FlxCamera;
|
||||
|
||||
/**
|
||||
* An FlxSprite with additional functionality.
|
||||
@@ -269,6 +273,103 @@ class FunkinSprite extends FlxSprite
|
||||
return result;
|
||||
}
|
||||
|
||||
@:access(flixel.FlxCamera)
|
||||
override function getBoundingBox(camera:FlxCamera):FlxRect
|
||||
{
|
||||
getScreenPosition(_point, camera);
|
||||
|
||||
_rect.set(_point.x, _point.y, width, height);
|
||||
_rect = camera.transformRect(_rect);
|
||||
|
||||
if (isPixelPerfectRender(camera))
|
||||
{
|
||||
_rect.width = _rect.width / this.scale.x;
|
||||
_rect.height = _rect.height / this.scale.y;
|
||||
_rect.x = _rect.x / this.scale.x;
|
||||
_rect.y = _rect.y / this.scale.y;
|
||||
_rect.floor();
|
||||
_rect.x = _rect.x * this.scale.x;
|
||||
_rect.y = _rect.y * this.scale.y;
|
||||
_rect.width = _rect.width * this.scale.x;
|
||||
_rect.height = _rect.height * this.scale.y;
|
||||
}
|
||||
|
||||
return _rect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the screen position of this object.
|
||||
*
|
||||
* @param result Optional arg for the returning point
|
||||
* @param camera The desired "screen" coordinate space. If `null`, `FlxG.camera` is used.
|
||||
* @return The screen position of this object.
|
||||
*/
|
||||
public override function getScreenPosition(?result:FlxPoint, ?camera:FlxCamera):FlxPoint
|
||||
{
|
||||
if (result == null) result = FlxPoint.get();
|
||||
|
||||
if (camera == null) camera = FlxG.camera;
|
||||
|
||||
result.set(x, y);
|
||||
if (pixelPerfectPosition)
|
||||
{
|
||||
_rect.width = _rect.width / this.scale.x;
|
||||
_rect.height = _rect.height / this.scale.y;
|
||||
_rect.x = _rect.x / this.scale.x;
|
||||
_rect.y = _rect.y / this.scale.y;
|
||||
_rect.round();
|
||||
_rect.x = _rect.x * this.scale.x;
|
||||
_rect.y = _rect.y * this.scale.y;
|
||||
_rect.width = _rect.width * this.scale.x;
|
||||
_rect.height = _rect.height * this.scale.y;
|
||||
}
|
||||
|
||||
return result.subtract(camera.scroll.x * scrollFactor.x, camera.scroll.y * scrollFactor.y);
|
||||
}
|
||||
|
||||
override function drawSimple(camera:FlxCamera):Void
|
||||
{
|
||||
getScreenPosition(_point, camera).subtractPoint(offset);
|
||||
if (isPixelPerfectRender(camera))
|
||||
{
|
||||
_point.x = _point.x / this.scale.x;
|
||||
_point.y = _point.y / this.scale.y;
|
||||
_point.round();
|
||||
|
||||
_point.x = _point.x * this.scale.x;
|
||||
_point.y = _point.y * this.scale.y;
|
||||
}
|
||||
|
||||
_point.copyToFlash(_flashPoint);
|
||||
camera.copyPixels(_frame, framePixels, _flashRect, _flashPoint, colorTransform, blend, antialiasing);
|
||||
}
|
||||
|
||||
override function drawComplex(camera:FlxCamera):Void
|
||||
{
|
||||
_frame.prepareMatrix(_matrix, FlxFrameAngle.ANGLE_0, checkFlipX(), checkFlipY());
|
||||
_matrix.translate(-origin.x, -origin.y);
|
||||
_matrix.scale(scale.x, scale.y);
|
||||
|
||||
if (bakedRotationAngle <= 0)
|
||||
{
|
||||
updateTrig();
|
||||
|
||||
if (angle != 0) _matrix.rotateWithTrig(_cosAngle, _sinAngle);
|
||||
}
|
||||
|
||||
getScreenPosition(_point, camera).subtractPoint(offset);
|
||||
_point.add(origin.x, origin.y);
|
||||
_matrix.translate(_point.x, _point.y);
|
||||
|
||||
if (isPixelPerfectRender(camera))
|
||||
{
|
||||
_matrix.tx = Math.round(_matrix.tx / this.scale.x) * this.scale.x;
|
||||
_matrix.ty = Math.round(_matrix.ty / this.scale.y) * this.scale.y;
|
||||
}
|
||||
|
||||
camera.drawPixels(_frame, framePixels, _matrix, colorTransform, blend, antialiasing, shader);
|
||||
}
|
||||
|
||||
public override function destroy():Void
|
||||
{
|
||||
frames = null;
|
||||
|
||||
@@ -4,8 +4,11 @@ import flixel.util.FlxSignal.FlxTypedSignal;
|
||||
import flxanimate.FlxAnimate;
|
||||
import flxanimate.FlxAnimate.Settings;
|
||||
import flxanimate.frames.FlxAnimateFrames;
|
||||
import flixel.graphics.frames.FlxFrame;
|
||||
import flixel.system.FlxAssets.FlxGraphicAsset;
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.utils.Assets;
|
||||
import flixel.math.FlxPoint;
|
||||
import flxanimate.animate.FlxKeyFrame;
|
||||
|
||||
/**
|
||||
* A sprite which provides convenience functions for rendering a texture atlas with animations.
|
||||
@@ -18,16 +21,21 @@ class FlxAtlasSprite extends FlxAnimate
|
||||
FrameRate: 24.0,
|
||||
Reversed: false,
|
||||
// ?OnComplete:Void -> Void,
|
||||
ShowPivot: #if debug false #else false #end,
|
||||
ShowPivot: false,
|
||||
Antialiasing: true,
|
||||
ScrollFactor: null,
|
||||
// Offset: new FlxPoint(0, 0), // This is just FlxSprite.offset
|
||||
};
|
||||
|
||||
/**
|
||||
* Signal dispatched when an animation finishes playing.
|
||||
* Signal dispatched when an animation advances to the next frame.
|
||||
*/
|
||||
public var onAnimationFinish:FlxTypedSignal<String->Void> = new FlxTypedSignal<String->Void>();
|
||||
public var onAnimationFrame:FlxTypedSignal<String->Int->Void> = new FlxTypedSignal();
|
||||
|
||||
/**
|
||||
* Signal dispatched when a non-looping animation finishes playing.
|
||||
*/
|
||||
public var onAnimationComplete:FlxTypedSignal<String->Void> = new FlxTypedSignal();
|
||||
|
||||
var currentAnimation:String;
|
||||
|
||||
@@ -42,19 +50,28 @@ class FlxAtlasSprite extends FlxAnimate
|
||||
throw 'Null path specified for FlxAtlasSprite!';
|
||||
}
|
||||
|
||||
// Validate asset path.
|
||||
if (!Assets.exists('${path}/Animation.json'))
|
||||
{
|
||||
throw 'FlxAtlasSprite does not have an Animation.json file at the specified path (${path})';
|
||||
}
|
||||
|
||||
super(x, y, path, settings);
|
||||
|
||||
if (this.anim.curInstance == null)
|
||||
if (this.anim.stageInstance == null)
|
||||
{
|
||||
throw 'FlxAtlasSprite not initialized properly. Are you sure the path (${path}) exists?';
|
||||
}
|
||||
|
||||
onAnimationFinish.add(cleanupAnimation);
|
||||
onAnimationComplete.add(cleanupAnimation);
|
||||
|
||||
// This defaults the sprite to play the first animation in the atlas,
|
||||
// then pauses it. This ensures symbols are intialized properly.
|
||||
this.anim.play('');
|
||||
this.anim.pause();
|
||||
|
||||
this.anim.onComplete.add(_onAnimationComplete);
|
||||
this.anim.onFrame.add(_onAnimationFrame);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,9 +79,13 @@ class FlxAtlasSprite extends FlxAnimate
|
||||
*/
|
||||
public function listAnimations():Array<String>
|
||||
{
|
||||
if (this.anim == null) return [];
|
||||
return this.anim.getFrameLabels();
|
||||
// return [""];
|
||||
var mainSymbol = this.anim.symbolDictionary[this.anim.stageInstance.symbol.name];
|
||||
if (mainSymbol == null)
|
||||
{
|
||||
FlxG.log.error('FlxAtlasSprite does not have its main symbol!');
|
||||
return [];
|
||||
}
|
||||
return mainSymbol.getFrameLabels().map(keyFrame -> keyFrame.name).filterNull();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,7 +94,7 @@ class FlxAtlasSprite extends FlxAnimate
|
||||
*/
|
||||
public function hasAnimation(id:String):Bool
|
||||
{
|
||||
return getLabelIndex(id) != -1;
|
||||
return getLabelIndex(id) != -1 || anim.symbolDictionary.exists(id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,22 +105,13 @@ class FlxAtlasSprite extends FlxAnimate
|
||||
return this.currentAnimation;
|
||||
}
|
||||
|
||||
/**
|
||||
* `anim.finished` always returns false on looping animations,
|
||||
* but this function will return true if we are on the last frame of the looping animation.
|
||||
*/
|
||||
public function isLoopFinished():Bool
|
||||
{
|
||||
if (this.anim == null) return false;
|
||||
if (!this.anim.isPlaying) return false;
|
||||
var _completeAnim:Bool = false;
|
||||
|
||||
// Reverse animation finished.
|
||||
if (this.anim.reversed && this.anim.curFrame == 0) return true;
|
||||
// Forward animation finished.
|
||||
if (!this.anim.reversed && this.anim.curFrame >= (this.anim.length - 1)) return true;
|
||||
var fr:FlxKeyFrame = null;
|
||||
|
||||
return false;
|
||||
}
|
||||
var looping:Bool = false;
|
||||
|
||||
public var ignoreExclusionPref:Array<String> = [];
|
||||
|
||||
/**
|
||||
* Plays an animation.
|
||||
@@ -107,61 +119,86 @@ class FlxAtlasSprite extends FlxAnimate
|
||||
* @param restart Whether to restart the animation if it is already playing.
|
||||
* @param ignoreOther Whether to ignore all other animation inputs, until this one is done playing
|
||||
* @param loop Whether to loop the animation
|
||||
* @param startFrame The frame to start the animation on
|
||||
* NOTE: `loop` and `ignoreOther` are not compatible with each other!
|
||||
*/
|
||||
public function playAnimation(id:String, restart:Bool = false, ignoreOther:Bool = false, ?loop:Bool = false):Void
|
||||
public function playAnimation(id:String, restart:Bool = false, ignoreOther:Bool = false, loop:Bool = false, startFrame:Int = 0):Void
|
||||
{
|
||||
if (loop == null) loop = false;
|
||||
|
||||
// Skip if not allowed to play animations.
|
||||
if ((!canPlayOtherAnims && !ignoreOther)) return;
|
||||
if ((!canPlayOtherAnims))
|
||||
{
|
||||
if (this.currentAnimation == id && restart) {}
|
||||
else if (ignoreExclusionPref != null && ignoreExclusionPref.length > 0)
|
||||
{
|
||||
var detected:Bool = false;
|
||||
for (entry in ignoreExclusionPref)
|
||||
{
|
||||
if (StringTools.startsWith(id, entry))
|
||||
{
|
||||
detected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!detected) return;
|
||||
}
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
if (anim == null) return;
|
||||
|
||||
if (id == null || id == '') id = this.currentAnimation;
|
||||
|
||||
if (this.currentAnimation == id && !restart)
|
||||
{
|
||||
if (anim.isPlaying)
|
||||
if (!anim.isPlaying)
|
||||
{
|
||||
// Skip if animation is already playing.
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Resume animation if it's paused.
|
||||
anim.play('', false, false);
|
||||
}
|
||||
}
|
||||
if (fr != null) anim.curFrame = fr.index + startFrame;
|
||||
else
|
||||
anim.curFrame = startFrame;
|
||||
|
||||
// Skip if the animation doesn't exist
|
||||
if (!hasAnimation(id))
|
||||
// Resume animation if it's paused.
|
||||
anim.resume();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else if (!hasAnimation(id))
|
||||
{
|
||||
// Skip if the animation doesn't exist
|
||||
trace('Animation ' + id + ' not found');
|
||||
return;
|
||||
}
|
||||
|
||||
anim.callback = function(_, frame:Int) {
|
||||
var offset = loop ? 0 : -1;
|
||||
this.currentAnimation = id;
|
||||
anim.onComplete.removeAll();
|
||||
anim.onComplete.add(function() {
|
||||
_onAnimationComplete();
|
||||
});
|
||||
|
||||
var frameLabel = anim.getFrameLabel(id);
|
||||
if (frame == (frameLabel.duration + offset) + frameLabel.index)
|
||||
{
|
||||
if (loop)
|
||||
{
|
||||
playAnimation(id, true, false, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
onAnimationFinish.dispatch(id);
|
||||
}
|
||||
}
|
||||
};
|
||||
looping = loop;
|
||||
|
||||
// Prevent other animations from playing if `ignoreOther` is true.
|
||||
if (ignoreOther) canPlayOtherAnims = false;
|
||||
|
||||
// Move to the first frame of the animation.
|
||||
goToFrameLabel(id);
|
||||
this.currentAnimation = id;
|
||||
// goToFrameLabel(id);
|
||||
// trace('Playing animation $id');
|
||||
if ((id == null || id == "") || this.anim.symbolDictionary.exists(id) || (this.anim.getByName(id) != null))
|
||||
{
|
||||
this.anim.play(id, restart, false, startFrame);
|
||||
|
||||
this.currentAnimation = anim.curSymbol.name;
|
||||
|
||||
fr = null;
|
||||
}
|
||||
// Only call goToFrameLabel if there is a frame label with that name. This prevents annoying warnings!
|
||||
if (getFrameLabelNames().indexOf(id) != -1)
|
||||
{
|
||||
goToFrameLabel(id);
|
||||
fr = anim.getFrameLabel(id);
|
||||
anim.curFrame += startFrame;
|
||||
}
|
||||
}
|
||||
|
||||
override public function update(elapsed:Float)
|
||||
@@ -169,6 +206,29 @@ class FlxAtlasSprite extends FlxAnimate
|
||||
super.update(elapsed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the animation has finished playing.
|
||||
* Never true if animation is configured to loop.
|
||||
*/
|
||||
public function isAnimationFinished():Bool
|
||||
{
|
||||
return this.anim.finished;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the animation has reached the last frame.
|
||||
* Can be true even if animation is configured to loop.
|
||||
*/
|
||||
public function isLoopComplete():Bool
|
||||
{
|
||||
if (this.anim == null) return false;
|
||||
if (!this.anim.isPlaying) return false;
|
||||
|
||||
if (fr != null) return (anim.reversed && anim.curFrame < fr.index || !anim.reversed && anim.curFrame >= (fr.index + fr.duration));
|
||||
|
||||
return (anim.reversed && anim.curFrame == 0 || !(anim.reversed) && (anim.curFrame) >= (anim.length - 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the current animation.
|
||||
*/
|
||||
@@ -192,6 +252,18 @@ class FlxAtlasSprite extends FlxAnimate
|
||||
this.anim.goToFrameLabel(label);
|
||||
}
|
||||
|
||||
function getFrameLabelNames(?layer:haxe.extern.EitherType<Int, String> = null)
|
||||
{
|
||||
var labels = this.anim.getFrameLabels(layer);
|
||||
var array = [];
|
||||
for (label in labels)
|
||||
{
|
||||
array.push(label.name);
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
function getNextFrameLabel(label:String):String
|
||||
{
|
||||
return listAnimations()[(getLabelIndex(label) + 1) % listAnimations().length];
|
||||
@@ -213,4 +285,95 @@ class FlxAtlasSprite extends FlxAnimate
|
||||
// this.currentAnimation = null;
|
||||
this.anim.pause();
|
||||
}
|
||||
|
||||
function _onAnimationFrame(frame:Int):Void
|
||||
{
|
||||
if (currentAnimation != null)
|
||||
{
|
||||
onAnimationFrame.dispatch(currentAnimation, frame);
|
||||
|
||||
if (isLoopComplete())
|
||||
{
|
||||
anim.pause();
|
||||
_onAnimationComplete();
|
||||
|
||||
if (looping)
|
||||
{
|
||||
anim.curFrame = (fr != null) ? fr.index : 0;
|
||||
anim.resume();
|
||||
}
|
||||
else if (fr != null && anim.curFrame != anim.length - 1)
|
||||
{
|
||||
anim.curFrame--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _onAnimationComplete():Void
|
||||
{
|
||||
if (currentAnimation != null)
|
||||
{
|
||||
onAnimationComplete.dispatch(currentAnimation);
|
||||
}
|
||||
else
|
||||
{
|
||||
onAnimationComplete.dispatch('');
|
||||
}
|
||||
}
|
||||
|
||||
var prevFrames:Map<Int, FlxFrame> = [];
|
||||
|
||||
public function replaceFrameGraphic(index:Int, ?graphic:FlxGraphicAsset):Void
|
||||
{
|
||||
if (graphic == null || !Assets.exists(graphic))
|
||||
{
|
||||
var prevFrame:Null<FlxFrame> = prevFrames.get(index);
|
||||
if (prevFrame == null) return;
|
||||
|
||||
prevFrame.copyTo(frames.getByIndex(index));
|
||||
return;
|
||||
}
|
||||
|
||||
var prevFrame:FlxFrame = prevFrames.get(index) ?? frames.getByIndex(index).copyTo();
|
||||
prevFrames.set(index, prevFrame);
|
||||
|
||||
var frame = FlxG.bitmap.add(graphic).imageFrame.frame;
|
||||
frame.copyTo(frames.getByIndex(index));
|
||||
|
||||
// Additional sizing fix.
|
||||
@:privateAccess
|
||||
if (true)
|
||||
{
|
||||
var frame = frames.getByIndex(index);
|
||||
frame.tileMatrix[0] = prevFrame.frame.width / frame.frame.width;
|
||||
frame.tileMatrix[3] = prevFrame.frame.height / frame.frame.height;
|
||||
}
|
||||
}
|
||||
|
||||
public function getBasePosition():Null<FlxPoint>
|
||||
{
|
||||
// var stagePos = new FlxPoint(anim.stageInstance.matrix.tx, anim.stageInstance.matrix.ty);
|
||||
var instancePos = new FlxPoint(anim.curInstance.matrix.tx, anim.curInstance.matrix.ty);
|
||||
var firstElement = anim.curSymbol.timeline?.get(0)?.get(0)?.get(0);
|
||||
if (firstElement == null) return instancePos;
|
||||
var firstElementPos = new FlxPoint(firstElement.matrix.tx, firstElement.matrix.ty);
|
||||
|
||||
return instancePos + firstElementPos;
|
||||
}
|
||||
|
||||
public function getPivotPosition():Null<FlxPoint>
|
||||
{
|
||||
return anim.curInstance.symbol.transformationPoint;
|
||||
}
|
||||
|
||||
public override function destroy():Void
|
||||
{
|
||||
for (prevFrameId in prevFrames.keys())
|
||||
{
|
||||
replaceFrameGraphic(prevFrameId, null);
|
||||
}
|
||||
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.addons.display.FlxRuntimeShader;
|
||||
|
||||
class AdjustColorShader extends FlxRuntimeShader
|
||||
{
|
||||
public var hue(default, set):Float;
|
||||
public var saturation(default, set):Float;
|
||||
public var brightness(default, set):Float;
|
||||
public var contrast(default, set):Float;
|
||||
|
||||
public function new()
|
||||
{
|
||||
super(Assets.getText(Paths.frag('adjustColor')));
|
||||
// FlxG.debugger.addTrackerProfile(new TrackerProfile(HSVShader, ['hue', 'saturation', 'brightness', 'contrast']));
|
||||
hue = 0;
|
||||
saturation = 0;
|
||||
brightness = 0;
|
||||
contrast = 0;
|
||||
}
|
||||
|
||||
function set_hue(value:Float):Float
|
||||
{
|
||||
this.setFloat('hue', value);
|
||||
this.hue = value;
|
||||
|
||||
return this.hue;
|
||||
}
|
||||
|
||||
function set_saturation(value:Float):Float
|
||||
{
|
||||
this.setFloat('saturation', value);
|
||||
this.saturation = value;
|
||||
|
||||
return this.saturation;
|
||||
}
|
||||
|
||||
function set_brightness(value:Float):Float
|
||||
{
|
||||
this.setFloat('brightness', value);
|
||||
this.brightness = value;
|
||||
|
||||
return this.brightness;
|
||||
}
|
||||
|
||||
function set_contrast(value:Float):Float
|
||||
{
|
||||
this.setFloat('contrast', value);
|
||||
this.contrast = value;
|
||||
|
||||
return this.contrast;
|
||||
}
|
||||
|
||||
public override function toString():String
|
||||
{
|
||||
return 'AdjustColorShader(${this.hue}, ${this.saturation}, ${this.brightness}, ${this.contrast})';
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,25 @@
|
||||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.system.FlxAssets.FlxShader;
|
||||
import flixel.util.FlxColor;
|
||||
|
||||
class AngleMask extends FlxShader
|
||||
{
|
||||
public var extraColor(default, set):FlxColor = 0xFFFFFFFF;
|
||||
|
||||
function set_extraColor(value:FlxColor):FlxColor
|
||||
{
|
||||
extraTint.value = [value.redFloat, value.greenFloat, value.blueFloat];
|
||||
this.extraColor = value;
|
||||
|
||||
return this.extraColor;
|
||||
}
|
||||
|
||||
@:glFragmentSource('
|
||||
#pragma header
|
||||
|
||||
uniform vec3 extraTint;
|
||||
|
||||
uniform vec2 endPosition;
|
||||
vec2 hash22(vec2 p) {
|
||||
vec3 p3 = fract(vec3(p.xyx) * vec3(.1031, .1030, .0973));
|
||||
@@ -69,6 +82,7 @@ class AngleMask extends FlxShader
|
||||
|
||||
void main() {
|
||||
vec4 col = antialias(openfl_TextureCoordv);
|
||||
col.xyz = col.xyz * extraTint.xyz;
|
||||
// col.xyz = gamma(col.xyz);
|
||||
gl_FragColor = col;
|
||||
}')
|
||||
@@ -77,5 +91,6 @@ class AngleMask extends FlxShader
|
||||
super();
|
||||
|
||||
endPosition.value = [90, 100]; // 100 AS DEFAULT WORKS NICELY FOR FREEPLAY?
|
||||
extraTint.value = [1, 1, 1];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.addons.display.FlxRuntimeShader;
|
||||
import funkin.Paths;
|
||||
import openfl.utils.Assets;
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.display.ShaderInput;
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.system.FlxAssets.FlxShader;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
|
||||
class BlueFade extends FlxShader
|
||||
{
|
||||
public var fadeVal(default, set):Float;
|
||||
|
||||
function set_fadeVal(val:Float):Float
|
||||
{
|
||||
fadeAmt.value = [val];
|
||||
fadeVal = val;
|
||||
// trace(fadeVal);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
public function fade(startAmt:Float = 0, targetAmt:Float = 1, duration:Float, _options:TweenOptions):Void
|
||||
{
|
||||
fadeVal = startAmt;
|
||||
FlxTween.tween(this, {fadeVal: targetAmt}, duration, _options);
|
||||
}
|
||||
|
||||
@:glFragmentSource('
|
||||
#pragma header
|
||||
|
||||
// Value from (0, 1)
|
||||
uniform float fadeAmt;
|
||||
|
||||
// fade the image to blue as it fades to black
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 tex = flixel_texture2D(bitmap, openfl_TextureCoordv);
|
||||
|
||||
vec4 finalColor = mix(vec4(vec4(0.0, 0.0, tex.b, tex.a) * fadeAmt), vec4(tex * fadeAmt), fadeAmt);
|
||||
|
||||
// Output to screen
|
||||
gl_FragColor = finalColor;
|
||||
}
|
||||
|
||||
')
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
|
||||
this.fadeVal = 1;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.addons.display.FlxRuntimeShader;
|
||||
import funkin.Paths;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
/**
|
||||
* Note... not actually gaussian!
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.addons.display.FlxRuntimeShader;
|
||||
import funkin.Paths;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
class Grayscale extends FlxRuntimeShader
|
||||
{
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.addons.display.FlxRuntimeShader;
|
||||
import funkin.Paths;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
class HSVShader extends FlxRuntimeShader
|
||||
{
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.addons.display.FlxRuntimeShader;
|
||||
import flixel.math.FlxPoint;
|
||||
|
||||
class MosaicEffect extends FlxRuntimeShader
|
||||
{
|
||||
public var blockSize:FlxPoint = FlxPoint.get(1.0, 1.0);
|
||||
|
||||
public function new()
|
||||
{
|
||||
super(Assets.getText(Paths.frag('mosaic')));
|
||||
setBlockSize(1.0, 1.0);
|
||||
}
|
||||
|
||||
public function setBlockSize(w:Float, h:Float)
|
||||
{
|
||||
blockSize.set(w, h);
|
||||
setFloatArray("uBlocksize", [w, h]);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package funkin.graphics.shaders;
|
||||
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.display.BlendMode;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
class RuntimeCustomBlendShader extends RuntimePostEffectShader
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@ package funkin.graphics.shaders;
|
||||
|
||||
import flixel.FlxCamera;
|
||||
import flixel.FlxG;
|
||||
import flixel.graphics.frames.FlxFrame;
|
||||
import flixel.addons.display.FlxRuntimeShader;
|
||||
import lime.graphics.opengl.GLProgram;
|
||||
import lime.utils.Log;
|
||||
@@ -32,6 +33,9 @@ class RuntimePostEffectShader extends FlxRuntimeShader
|
||||
// equals (camera.viewLeft, camera.viewTop, camera.viewRight, camera.viewBottom)
|
||||
uniform vec4 uCameraBounds;
|
||||
|
||||
// equals (frame.left, frame.top, frame.right, frame.bottom)
|
||||
uniform vec4 uFrameBounds;
|
||||
|
||||
// screen coord -> world coord conversion
|
||||
// returns world coord in px
|
||||
vec2 screenToWorld(vec2 screenCoord) {
|
||||
@@ -56,6 +60,25 @@ class RuntimePostEffectShader extends FlxRuntimeShader
|
||||
return (worldCoord - offset) / scale;
|
||||
}
|
||||
|
||||
// screen coord -> frame coord conversion
|
||||
// returns normalized frame coord
|
||||
vec2 screenToFrame(vec2 screenCoord) {
|
||||
float left = uFrameBounds.x;
|
||||
float top = uFrameBounds.y;
|
||||
float right = uFrameBounds.z;
|
||||
float bottom = uFrameBounds.w;
|
||||
float width = right - left;
|
||||
float height = bottom - top;
|
||||
|
||||
float clampedX = clamp(screenCoord.x, left, right);
|
||||
float clampedY = clamp(screenCoord.y, top, bottom);
|
||||
|
||||
return vec2(
|
||||
(clampedX - left) / (width),
|
||||
(clampedY - top) / (height)
|
||||
);
|
||||
}
|
||||
|
||||
// internally used to get the maximum `openfl_TextureCoordv`
|
||||
vec2 bitmapCoordScale() {
|
||||
return openfl_TextureCoordv / screenCoord;
|
||||
@@ -80,6 +103,8 @@ class RuntimePostEffectShader extends FlxRuntimeShader
|
||||
{
|
||||
super(fragmentSource, null, glVersion);
|
||||
uScreenResolution.value = [FlxG.width, FlxG.height];
|
||||
uCameraBounds.value = [0, 0, FlxG.width, FlxG.height];
|
||||
uFrameBounds.value = [0, 0, FlxG.width, FlxG.height];
|
||||
}
|
||||
|
||||
// basically `updateViewInfo(FlxG.width, FlxG.height, FlxG.camera)` is good
|
||||
@@ -89,6 +114,12 @@ class RuntimePostEffectShader extends FlxRuntimeShader
|
||||
uCameraBounds.value = [camera.viewLeft, camera.viewTop, camera.viewRight, camera.viewBottom];
|
||||
}
|
||||
|
||||
public function updateFrameInfo(frame:FlxFrame)
|
||||
{
|
||||
// NOTE: uv.width is actually the right pos and uv.height is the bottom pos
|
||||
uFrameBounds.value = [frame.uv.x, frame.uv.y, frame.uv.width, frame.uv.height];
|
||||
}
|
||||
|
||||
override function __createGLProgram(vertexSource:String, fragmentSource:String):GLProgram
|
||||
{
|
||||
try
|
||||
|
||||
@@ -4,7 +4,7 @@ import flixel.system.FlxAssets.FlxShader;
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.display.ShaderParameter;
|
||||
import openfl.display.ShaderParameterType;
|
||||
import openfl.utils.Assets;
|
||||
import flixel.util.FlxColor;
|
||||
|
||||
typedef Light =
|
||||
{
|
||||
@@ -32,6 +32,14 @@ class RuntimeRainShader extends RuntimePostEffectShader
|
||||
return time = value;
|
||||
}
|
||||
|
||||
public var spriteMode(default, set):Bool = false;
|
||||
|
||||
function set_spriteMode(value:Bool):Bool
|
||||
{
|
||||
this.setBool('uSpriteMode', value);
|
||||
return spriteMode = value;
|
||||
}
|
||||
|
||||
// The scale of the rain depends on the world coordinate system, so higher resolution makes
|
||||
// the raindrops smaller. This parameter can be used to adjust the total scale of the scene.
|
||||
// The size of the raindrops is proportional to the value of this parameter.
|
||||
@@ -86,6 +94,14 @@ class RuntimeRainShader extends RuntimePostEffectShader
|
||||
return mask = value;
|
||||
}
|
||||
|
||||
public var rainColor(default, set):FlxColor;
|
||||
|
||||
function set_rainColor(color:FlxColor):FlxColor
|
||||
{
|
||||
this.setFloatArray("uRainColor", [color.red / 255, color.green / 255, color.blue / 255]);
|
||||
return rainColor = color;
|
||||
}
|
||||
|
||||
public var lightMap(default, set):BitmapData;
|
||||
|
||||
function set_lightMap(value:BitmapData):BitmapData
|
||||
@@ -105,6 +121,7 @@ class RuntimeRainShader extends RuntimePostEffectShader
|
||||
public function new()
|
||||
{
|
||||
super(Assets.getText(Paths.frag('rain')));
|
||||
this.rainColor = 0xFF6680cc;
|
||||
}
|
||||
|
||||
public function update(elapsed:Float):Void
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.system.FlxAssets.FlxShader;
|
||||
import flixel.util.FlxColor;
|
||||
import openfl.display.BitmapData;
|
||||
|
||||
class TextureSwap extends FlxShader
|
||||
{
|
||||
public var swappedImage(default, set):BitmapData;
|
||||
public var amount(default, set):Float;
|
||||
|
||||
function set_swappedImage(_bitmapData:BitmapData):BitmapData
|
||||
{
|
||||
image.input = _bitmapData;
|
||||
|
||||
return _bitmapData;
|
||||
}
|
||||
|
||||
function set_amount(val:Float):Float
|
||||
{
|
||||
fadeAmount.value = [val];
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
@:glFragmentSource('
|
||||
#pragma header
|
||||
|
||||
uniform sampler2D image;
|
||||
uniform float fadeAmount;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 tex = flixel_texture2D(bitmap, openfl_TextureCoordv);
|
||||
vec4 tex2 = flixel_texture2D(image, openfl_TextureCoordv);
|
||||
|
||||
vec4 finalColor = mix(tex, vec4(tex2.rgb, tex.a), fadeAmount);
|
||||
|
||||
gl_FragColor = finalColor;
|
||||
}
|
||||
')
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
|
||||
this.amount = 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package funkin.graphics.video;
|
||||
|
||||
#if hxCodec
|
||||
import hxcodec.flixel.FlxVideoSprite;
|
||||
|
||||
/**
|
||||
* Not to be confused with FlxVideo, this is a hxcodec based video class
|
||||
* We override it simply to correct/control our volume easier.
|
||||
*/
|
||||
class FunkinVideoSprite extends FlxVideoSprite
|
||||
{
|
||||
public var volume(default, set):Float = 1;
|
||||
|
||||
public function new(x:Float = 0, y:Float = 0)
|
||||
{
|
||||
super(x, y);
|
||||
|
||||
set_volume(1);
|
||||
}
|
||||
|
||||
override public function update(elapsed:Float):Void
|
||||
{
|
||||
super.update(elapsed);
|
||||
set_volume(volume);
|
||||
}
|
||||
|
||||
function set_volume(value:Float):Float
|
||||
{
|
||||
volume = value;
|
||||
bitmap.volume = Std.int((FlxG.sound.muted ? 0 : 1) * (FlxG.sound.logToLinear(FlxG.sound.volume) * 100) * volume);
|
||||
return volume;
|
||||
}
|
||||
}
|
||||
#end
|
||||
@@ -3,6 +3,7 @@ package;
|
||||
#if !macro
|
||||
// Only import these when we aren't in a macro.
|
||||
import funkin.util.Constants;
|
||||
import funkin.Assets;
|
||||
import funkin.Paths;
|
||||
import funkin.Preferences;
|
||||
import flixel.FlxG; // This one in particular causes a compile error if you're using macros.
|
||||
|
||||
+322
-180
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,6 @@
|
||||
package funkin.input;
|
||||
|
||||
import haxe.ui.backend.flixel.CursorHelper;
|
||||
import openfl.utils.Assets;
|
||||
import lime.app.Future;
|
||||
import openfl.display.BitmapData;
|
||||
|
||||
|
||||
@@ -73,6 +73,22 @@ interface INoteScriptedClass extends IScriptedClass
|
||||
public function onNoteMiss(event:NoteScriptEvent):Void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a set of callbacks available to scripted classes which represent sprites synced with the BPM.
|
||||
*/
|
||||
interface IBPMSyncedScriptedClass extends IScriptedClass
|
||||
{
|
||||
/**
|
||||
* Called once every step of the song.
|
||||
*/
|
||||
public function onStepHit(event:SongTimeScriptEvent):Void;
|
||||
|
||||
/**
|
||||
* Called once every beat of the song.
|
||||
*/
|
||||
public function onBeatHit(event:SongTimeScriptEvent):Void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Developer note:
|
||||
*
|
||||
@@ -86,7 +102,7 @@ interface INoteScriptedClass extends IScriptedClass
|
||||
/**
|
||||
* Defines a set of callbacks available to scripted classes that involve the lifecycle of the Play State.
|
||||
*/
|
||||
interface IPlayStateScriptedClass extends INoteScriptedClass
|
||||
interface IPlayStateScriptedClass extends INoteScriptedClass extends IBPMSyncedScriptedClass
|
||||
{
|
||||
/**
|
||||
* Called when the game is paused.
|
||||
@@ -136,16 +152,6 @@ interface IPlayStateScriptedClass extends INoteScriptedClass
|
||||
*/
|
||||
public function onSongEvent(event:SongEventScriptEvent):Void;
|
||||
|
||||
/**
|
||||
* Called once every step of the song.
|
||||
*/
|
||||
public function onStepHit(event:SongTimeScriptEvent):Void;
|
||||
|
||||
/**
|
||||
* Called once every beat of the song.
|
||||
*/
|
||||
public function onBeatHit(event:SongTimeScriptEvent):Void;
|
||||
|
||||
/**
|
||||
* Called when the countdown of the song starts.
|
||||
*/
|
||||
|
||||
@@ -7,7 +7,9 @@ import funkin.data.dialogue.speaker.SpeakerRegistry;
|
||||
import funkin.data.event.SongEventRegistry;
|
||||
import funkin.data.story.level.LevelRegistry;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.play.notes.notekind.NoteKindManager;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.data.freeplay.player.PlayerRegistry;
|
||||
import funkin.data.stage.StageRegistry;
|
||||
import funkin.data.freeplay.album.AlbumRegistry;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
@@ -25,12 +27,18 @@ import polymod.Polymod;
|
||||
class PolymodHandler
|
||||
{
|
||||
/**
|
||||
* The API version that mods should comply with.
|
||||
* Format this with Semantic Versioning; <MAJOR>.<MINOR>.<PATCH>.
|
||||
* Bug fixes increment the patch version, new features increment the minor version.
|
||||
* Changes that break old mods increment the major version.
|
||||
* The API version for the current version of the game. Since 0.5.0, we've just made this the game version!
|
||||
* Minor updates rarely impact mods but major versions often do.
|
||||
*/
|
||||
static final API_VERSION:String = '0.1.0';
|
||||
// static final API_VERSION:String = Constants.VERSION;
|
||||
|
||||
/**
|
||||
* The Semantic Versioning rule
|
||||
* Indicates which mods are compatible with this version of the game.
|
||||
* Using more complex rules allows mods from older compatible versions to stay functioning,
|
||||
* while preventing mods made for future versions from being installed.
|
||||
*/
|
||||
static final API_VERSION_RULE:String = ">=0.5.0 <0.6.0";
|
||||
|
||||
/**
|
||||
* Where relative to the executable that mods are located.
|
||||
@@ -130,7 +138,7 @@ class PolymodHandler
|
||||
// Framework being used to load assets.
|
||||
framework: OPENFL,
|
||||
// The current version of our API.
|
||||
apiVersionRule: API_VERSION,
|
||||
apiVersionRule: API_VERSION_RULE,
|
||||
// Call this function any time an error occurs.
|
||||
errorCallback: PolymodErrorHandler.onPolymodError,
|
||||
// Enforce semantic version patterns for each mod.
|
||||
@@ -176,7 +184,7 @@ class PolymodHandler
|
||||
loadedModIds.push(mod.id);
|
||||
}
|
||||
|
||||
#if debug
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
var fileList:Array<String> = Polymod.listModFiles(PolymodAssetType.IMAGE);
|
||||
trace('Installed mods have replaced ${fileList.length} images.');
|
||||
for (item in fileList)
|
||||
@@ -227,11 +235,19 @@ class PolymodHandler
|
||||
static function buildImports():Void
|
||||
{
|
||||
// Add default imports for common classes.
|
||||
Polymod.addDefaultImport(funkin.Assets);
|
||||
Polymod.addDefaultImport(funkin.Paths);
|
||||
|
||||
// Add import aliases for certain classes.
|
||||
// NOTE: Scripted classes are automatically aliased to their parent class.
|
||||
Polymod.addImportAlias('flixel.math.FlxPoint', flixel.math.FlxPoint.FlxBasePoint);
|
||||
|
||||
Polymod.addImportAlias('funkin.data.event.SongEventSchema', funkin.data.event.SongEventSchema.SongEventSchemaRaw);
|
||||
|
||||
// `lime.utils.Assets` literally just has a private `resolveClass` function for some reason? so we replace it with our own.
|
||||
Polymod.addImportAlias('lime.utils.Assets', funkin.Assets);
|
||||
Polymod.addImportAlias('openfl.utils.Assets', funkin.Assets);
|
||||
|
||||
// Add blacklisting for prohibited classes and packages.
|
||||
|
||||
// `Sys`
|
||||
@@ -250,8 +266,28 @@ class PolymodHandler
|
||||
// Lib.load() can load malicious DLLs
|
||||
Polymod.blacklistImport('cpp.Lib');
|
||||
|
||||
// `Unserializer`
|
||||
// Unserializer.DEFAULT_RESOLVER.resolveClass() can access blacklisted packages
|
||||
Polymod.blacklistImport('Unserializer');
|
||||
|
||||
// `lime.system.CFFI`
|
||||
// Can load and execute compiled binaries.
|
||||
Polymod.blacklistImport('lime.system.CFFI');
|
||||
|
||||
// `lime.system.JNI`
|
||||
// Can load and execute compiled binaries.
|
||||
Polymod.blacklistImport('lime.system.JNI');
|
||||
|
||||
// `lime.system.System`
|
||||
// System.load() can load malicious DLLs
|
||||
Polymod.blacklistImport('lime.system.System');
|
||||
|
||||
// `openfl.desktop.NativeProcess`
|
||||
// Can load native processes on the host operating system.
|
||||
Polymod.blacklistImport('openfl.desktop.NativeProcess');
|
||||
|
||||
// `polymod.*`
|
||||
// You can probably unblacklist a module
|
||||
// Contains functions which may allow for un-blacklisting other modules.
|
||||
for (cls in ClassMacro.listClassesInPackage('polymod'))
|
||||
{
|
||||
if (cls == null) continue;
|
||||
@@ -260,6 +296,7 @@ class PolymodHandler
|
||||
}
|
||||
|
||||
// `sys.*`
|
||||
// Access to system utilities such as the file system.
|
||||
for (cls in ClassMacro.listClassesInPackage('sys'))
|
||||
{
|
||||
if (cls == null) continue;
|
||||
@@ -308,7 +345,7 @@ class PolymodHandler
|
||||
var modMetadata:Array<ModMetadata> = Polymod.scan(
|
||||
{
|
||||
modRoot: MOD_FOLDER,
|
||||
apiVersionRule: API_VERSION,
|
||||
apiVersionRule: API_VERSION_RULE,
|
||||
fileSystem: modFileSystem,
|
||||
errorCallback: PolymodErrorHandler.onPolymodError
|
||||
});
|
||||
@@ -369,16 +406,20 @@ class PolymodHandler
|
||||
|
||||
// These MUST be imported at the top of the file and not referred to by fully qualified name,
|
||||
// to ensure build macros work properly.
|
||||
SongEventRegistry.loadEventCache();
|
||||
|
||||
SongRegistry.instance.loadEntries();
|
||||
LevelRegistry.instance.loadEntries();
|
||||
NoteStyleRegistry.instance.loadEntries();
|
||||
SongEventRegistry.loadEventCache();
|
||||
PlayerRegistry.instance.loadEntries();
|
||||
ConversationRegistry.instance.loadEntries();
|
||||
DialogueBoxRegistry.instance.loadEntries();
|
||||
SpeakerRegistry.instance.loadEntries();
|
||||
AlbumRegistry.instance.loadEntries();
|
||||
StageRegistry.instance.loadEntries();
|
||||
|
||||
CharacterDataParser.loadCharacterCache(); // TODO: Migrate characters to BaseRegistry.
|
||||
NoteKindManager.loadScripts();
|
||||
ModuleHandler.loadModuleCache();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package funkin.modding.base;
|
||||
|
||||
/**
|
||||
* A script that can be tied to an FlxUIState.
|
||||
* Create a scripted class that extends FlxUIState to use this.
|
||||
*/
|
||||
@:hscriptClass
|
||||
class ScriptedFlxUIState extends flixel.addons.ui.FlxUIState implements HScriptedClass {}
|
||||
@@ -1,8 +1,8 @@
|
||||
package funkin.modding.base;
|
||||
|
||||
/**
|
||||
* A script that can be tied to an FlxSprite.
|
||||
* Create a scripted class that extends FlxSprite to use this.
|
||||
* A script that can be tied to a FunkinSprite.
|
||||
* Create a scripted class that extends FunkinSprite to use this.
|
||||
*/
|
||||
@:hscriptClass
|
||||
class ScriptedFunkinSprite extends funkin.graphics.FunkinSprite implements HScriptedClass {}
|
||||
|
||||
@@ -103,7 +103,7 @@ class NoteScriptEvent extends ScriptEvent
|
||||
public var comboCount(default, null):Int;
|
||||
|
||||
/**
|
||||
* Whether to play the record scratch sound (if this eventn type is `NOTE_MISS`).
|
||||
* Whether to play the record scratch sound (if this event type is `NOTE_MISS`).
|
||||
*/
|
||||
public var playSound(default, default):Bool;
|
||||
|
||||
@@ -151,7 +151,8 @@ class HitNoteScriptEvent extends NoteScriptEvent
|
||||
public var hitDiff:Float = 0;
|
||||
|
||||
/**
|
||||
* If the hit causes a notesplash
|
||||
* Whether this note hit causes a note splash to display.
|
||||
* Defaults to true only on "sick" notes.
|
||||
*/
|
||||
public var doesNotesplash:Bool = false;
|
||||
|
||||
|
||||
@@ -94,6 +94,21 @@ class ScriptEventDispatcher
|
||||
}
|
||||
}
|
||||
|
||||
if (Std.isOfType(target, IBPMSyncedScriptedClass))
|
||||
{
|
||||
var t:IBPMSyncedScriptedClass = cast(target, IBPMSyncedScriptedClass);
|
||||
switch (event.type)
|
||||
{
|
||||
case SONG_BEAT_HIT:
|
||||
t.onBeatHit(cast event);
|
||||
return;
|
||||
case SONG_STEP_HIT:
|
||||
t.onStepHit(cast event);
|
||||
return;
|
||||
default: // Continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (Std.isOfType(target, IPlayStateScriptedClass))
|
||||
{
|
||||
var t:IPlayStateScriptedClass = cast(target, IPlayStateScriptedClass);
|
||||
@@ -102,12 +117,6 @@ class ScriptEventDispatcher
|
||||
case NOTE_GHOST_MISS:
|
||||
t.onNoteGhostMiss(cast event);
|
||||
return;
|
||||
case SONG_BEAT_HIT:
|
||||
t.onBeatHit(cast event);
|
||||
return;
|
||||
case SONG_STEP_HIT:
|
||||
t.onStepHit(cast event);
|
||||
return;
|
||||
case SONG_START:
|
||||
t.onSongStart(event);
|
||||
return;
|
||||
|
||||
@@ -20,7 +20,7 @@ enum abstract ScriptEventType(String) from String to String
|
||||
var DESTROY = 'DESTROY';
|
||||
|
||||
/**
|
||||
* Called when the relevent object is added to the game state.
|
||||
* Called when the relevant object is added to the game state.
|
||||
* This assumes all data is loaded and ready to go.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
|
||||
@@ -125,7 +125,6 @@ class ModuleHandler
|
||||
for (key => value in moduleCache)
|
||||
{
|
||||
ScriptEventDispatcher.callEvent(value, event);
|
||||
moduleCache.remove(key);
|
||||
}
|
||||
|
||||
moduleCache.clear();
|
||||
|
||||
@@ -9,7 +9,10 @@ import funkin.modding.module.ModuleHandler;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.events.ScriptEvent.CountdownScriptEvent;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.util.EaseUtil;
|
||||
import funkin.audio.FunkinSound;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
|
||||
class Countdown
|
||||
{
|
||||
@@ -18,6 +21,24 @@ class Countdown
|
||||
*/
|
||||
public static var countdownStep(default, null):CountdownStep = BEFORE;
|
||||
|
||||
/**
|
||||
* Which alternate graphic/sound on countdown to use.
|
||||
* This is set via the current notestyle.
|
||||
* For example, in Week 6 it is `pixel`.
|
||||
*/
|
||||
public static var soundSuffix:String = '';
|
||||
|
||||
/**
|
||||
* Which alternate graphic on countdown to use.
|
||||
* You can set this via script.
|
||||
* For example, in Week 6 it is `-pixel`.
|
||||
*/
|
||||
public static var graphicSuffix:String = '';
|
||||
|
||||
static var noteStyle:NoteStyle;
|
||||
|
||||
static var fallbackNoteStyle:Null<NoteStyle>;
|
||||
|
||||
/**
|
||||
* The currently running countdown. This will be null if there is no countdown running.
|
||||
*/
|
||||
@@ -29,7 +50,7 @@ class Countdown
|
||||
* This will automatically stop and restart the countdown if it is already running.
|
||||
* @returns `false` if the countdown was cancelled by a script.
|
||||
*/
|
||||
public static function performCountdown(isPixelStyle:Bool):Bool
|
||||
public static function performCountdown():Bool
|
||||
{
|
||||
countdownStep = BEFORE;
|
||||
var cancelled:Bool = propagateCountdownEvent(countdownStep);
|
||||
@@ -64,10 +85,10 @@ class Countdown
|
||||
// PlayState.instance.dispatchEvent(new SongTimeScriptEvent(SONG_BEAT_HIT, 0, 0));
|
||||
|
||||
// Countdown graphic.
|
||||
showCountdownGraphic(countdownStep, isPixelStyle);
|
||||
showCountdownGraphic(countdownStep);
|
||||
|
||||
// Countdown sound.
|
||||
playCountdownSound(countdownStep, isPixelStyle);
|
||||
playCountdownSound(countdownStep);
|
||||
|
||||
// Event handling bullshit.
|
||||
var cancelled:Bool = propagateCountdownEvent(countdownStep);
|
||||
@@ -117,7 +138,7 @@ class Countdown
|
||||
*
|
||||
* If you want to call this from a module, it's better to use the event system and cancel the onCountdownStep event.
|
||||
*/
|
||||
public static function pauseCountdown()
|
||||
public static function pauseCountdown():Void
|
||||
{
|
||||
if (countdownTimer != null && !countdownTimer.finished)
|
||||
{
|
||||
@@ -130,7 +151,7 @@ class Countdown
|
||||
*
|
||||
* If you want to call this from a module, it's better to use the event system and cancel the onCountdownStep event.
|
||||
*/
|
||||
public static function resumeCountdown()
|
||||
public static function resumeCountdown():Void
|
||||
{
|
||||
if (countdownTimer != null && !countdownTimer.finished)
|
||||
{
|
||||
@@ -143,7 +164,7 @@ class Countdown
|
||||
*
|
||||
* If you want to call this from a module, it's better to use the event system and cancel the onCountdownStart event.
|
||||
*/
|
||||
public static function stopCountdown()
|
||||
public static function stopCountdown():Void
|
||||
{
|
||||
if (countdownTimer != null)
|
||||
{
|
||||
@@ -156,7 +177,7 @@ class Countdown
|
||||
/**
|
||||
* Stops the current countdown, then starts the song for you.
|
||||
*/
|
||||
public static function skipCountdown()
|
||||
public static function skipCountdown():Void
|
||||
{
|
||||
stopCountdown();
|
||||
// This will trigger PlayState.startSong()
|
||||
@@ -176,114 +197,69 @@ class Countdown
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the graphic to use for this step of the countdown.
|
||||
* TODO: Make this less dumb. Unhardcode it? Use modules? Use notestyles?
|
||||
*
|
||||
* This is public so modules can do lol funny shit.
|
||||
* Reset the countdown configuration to the default.
|
||||
*/
|
||||
public static function showCountdownGraphic(index:CountdownStep, isPixelStyle:Bool):Void
|
||||
public static function reset()
|
||||
{
|
||||
var spritePath:String = null;
|
||||
noteStyle = null;
|
||||
}
|
||||
|
||||
if (isPixelStyle)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case TWO:
|
||||
spritePath = 'weeb/pixelUI/ready-pixel';
|
||||
case ONE:
|
||||
spritePath = 'weeb/pixelUI/set-pixel';
|
||||
case GO:
|
||||
spritePath = 'weeb/pixelUI/date-pixel';
|
||||
default:
|
||||
// null
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case TWO:
|
||||
spritePath = 'ready';
|
||||
case ONE:
|
||||
spritePath = 'set';
|
||||
case GO:
|
||||
spritePath = 'go';
|
||||
default:
|
||||
// null
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Retrieve the note style data (if we haven't already)
|
||||
* @param noteStyleId The id of the note style to fetch. Defaults to the one used by the current PlayState.
|
||||
* @param force Fetch the note style from the registry even if we've already fetched it.
|
||||
*/
|
||||
static function fetchNoteStyle(?noteStyleId:String, force:Bool = false):Void
|
||||
{
|
||||
if (noteStyle != null && !force) return;
|
||||
|
||||
if (spritePath == null) return;
|
||||
if (noteStyleId == null) noteStyleId = PlayState.instance?.currentChart?.noteStyle;
|
||||
|
||||
var countdownSprite:FunkinSprite = FunkinSprite.create(spritePath);
|
||||
countdownSprite.scrollFactor.set(0, 0);
|
||||
noteStyle = NoteStyleRegistry.instance.fetchEntry(noteStyleId);
|
||||
if (noteStyle == null) noteStyle = NoteStyleRegistry.instance.fetchDefault();
|
||||
}
|
||||
|
||||
if (isPixelStyle) countdownSprite.setGraphicSize(Std.int(countdownSprite.width * Constants.PIXEL_ART_SCALE));
|
||||
/**
|
||||
* Retrieves the graphic to use for this step of the countdown.
|
||||
*/
|
||||
public static function showCountdownGraphic(index:CountdownStep):Void
|
||||
{
|
||||
fetchNoteStyle();
|
||||
|
||||
countdownSprite.antialiasing = !isPixelStyle;
|
||||
var countdownSprite = noteStyle.buildCountdownSprite(index);
|
||||
if (countdownSprite == null) return;
|
||||
|
||||
countdownSprite.updateHitbox();
|
||||
countdownSprite.screenCenter();
|
||||
var fadeEase = FlxEase.cubeInOut;
|
||||
if (noteStyle.isCountdownSpritePixel(index)) fadeEase = EaseUtil.stepped(8);
|
||||
|
||||
// Fade sprite in, then out, then destroy it.
|
||||
FlxTween.tween(countdownSprite, {y: countdownSprite.y += 100, alpha: 0}, Conductor.instance.beatLengthMs / 1000,
|
||||
FlxTween.tween(countdownSprite, {alpha: 0}, Conductor.instance.beatLengthMs / 1000,
|
||||
{
|
||||
ease: FlxEase.cubeInOut,
|
||||
ease: fadeEase,
|
||||
onComplete: function(twn:FlxTween) {
|
||||
countdownSprite.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
countdownSprite.cameras = [PlayState.instance.camHUD];
|
||||
PlayState.instance.add(countdownSprite);
|
||||
countdownSprite.screenCenter();
|
||||
|
||||
var offsets = noteStyle.getCountdownSpriteOffsets(index);
|
||||
countdownSprite.x += offsets[0];
|
||||
countdownSprite.y += offsets[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the sound file to use for this step of the countdown.
|
||||
* TODO: Make this less dumb. Unhardcode it? Use modules? Use notestyles?
|
||||
*
|
||||
* This is public so modules can do lol funny shit.
|
||||
*/
|
||||
public static function playCountdownSound(index:CountdownStep, isPixelStyle:Bool):Void
|
||||
public static function playCountdownSound(step:CountdownStep):FunkinSound
|
||||
{
|
||||
var soundPath:String = null;
|
||||
fetchNoteStyle();
|
||||
var path = noteStyle.getCountdownSoundPath(step);
|
||||
if (path == null) return null;
|
||||
|
||||
if (isPixelStyle)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case THREE:
|
||||
soundPath = 'intro3-pixel';
|
||||
case TWO:
|
||||
soundPath = 'intro2-pixel';
|
||||
case ONE:
|
||||
soundPath = 'intro1-pixel';
|
||||
case GO:
|
||||
soundPath = 'introGo-pixel';
|
||||
default:
|
||||
// null
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case THREE:
|
||||
soundPath = 'intro3';
|
||||
case TWO:
|
||||
soundPath = 'intro2';
|
||||
case ONE:
|
||||
soundPath = 'intro1';
|
||||
case GO:
|
||||
soundPath = 'introGo';
|
||||
default:
|
||||
// null
|
||||
}
|
||||
}
|
||||
|
||||
if (soundPath == null) return;
|
||||
|
||||
FunkinSound.playOnce(Paths.sound(soundPath), Constants.COUNTDOWN_VOLUME);
|
||||
return FunkinSound.playOnce(path, Constants.COUNTDOWN_VOLUME);
|
||||
}
|
||||
|
||||
public static function decrement(step:CountdownStep):CountdownStep
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user