Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b976b0d71 | ||
|
|
ae1239c57d | ||
|
|
bfbbf9cb01 | ||
|
|
d29bb47d22 | ||
|
|
d29ecfb7c4 | ||
|
|
cdd17ce033 | ||
|
|
8e8b26f029 | ||
|
|
6f43438cdb | ||
|
|
a74369c77e | ||
|
|
def1b74eb6 | ||
|
|
c70fa7c94a | ||
|
|
c0353bca2b | ||
|
|
f385cf9fd3 | ||
|
|
301f71b428 | ||
|
|
189e028442 | ||
|
|
a9647cf9fc | ||
|
|
05b9d68645 | ||
|
|
45f0f14ed9 | ||
|
|
a8262a8d3b | ||
|
|
264c2d8e77 | ||
|
|
b2f231e889 | ||
|
|
34bb201104 | ||
|
|
d9fcaf0e63 | ||
|
|
56259b9143 | ||
|
|
dc2acac152 | ||
|
|
bf5a669592 | ||
|
|
8b9775d91f | ||
|
|
6caeda9a9d | ||
|
|
9983556748 | ||
|
|
651298e908 | ||
|
|
142ea6db0c | ||
|
|
5397924edd | ||
|
|
94bd18fe17 | ||
|
|
664d9e0fb3 | ||
|
|
edf6889af9 | ||
|
|
c65dddacc0 | ||
|
|
659ea41206 | ||
|
|
5cd47a4414 | ||
|
|
65699ee865 | ||
|
|
d423eb3ddd | ||
|
|
c67085a46a | ||
|
|
a83a4a599c | ||
|
|
f6ac4cad43 | ||
|
|
0101bae7ec | ||
|
|
4c5335d7a3 | ||
|
|
b811ca91a6 | ||
|
|
c5f2a74494 | ||
|
|
3e0dbe2758 | ||
|
|
bbc0546c74 | ||
|
|
617d5ffa06 | ||
|
|
73114e2da6 | ||
|
|
808698ace7 | ||
|
|
832bc5bd83 | ||
|
|
b15e809c63 | ||
|
|
8074d57186 | ||
|
|
31d3718e5c | ||
|
|
5fdbd23a17 | ||
|
|
23a9e7f944 | ||
|
|
0397a41a7f | ||
|
|
afbb335993 | ||
|
|
2bc1a31427 | ||
|
|
dafbb43532 | ||
|
|
4978c89ac3 | ||
|
|
29b541af38 | ||
|
|
fc09350be7 | ||
|
|
e1ca3c69f0 | ||
|
|
a15bd36943 | ||
|
|
51605e50f8 | ||
|
|
3a1b573ea3 | ||
|
|
ef6b214872 | ||
|
|
8f2a8c8335 | ||
|
|
7f5b4974c3 | ||
|
|
d63782ee9d | ||
|
|
19b1c32124 | ||
|
|
d183876514 | ||
|
|
db0ac48192 | ||
|
|
ba37405e4b | ||
|
|
66197b34f6 | ||
|
|
a7da71c8a8 | ||
|
|
5bd081c2af | ||
|
|
aefc4db277 | ||
|
|
adc14cc15d | ||
|
|
a8294a9cce | ||
|
|
80569c806c | ||
|
|
d6837eba2e | ||
|
|
924b1cf386 | ||
|
|
0839c16535 | ||
|
|
db3e012cb0 | ||
|
|
6c2ebff680 | ||
|
|
df0f915be5 | ||
|
|
104d3cffcb | ||
|
|
584b1ece07 | ||
|
|
4555b6736e | ||
|
|
0c674ac23b | ||
|
|
c5e5ab16df | ||
|
|
adefa902e7 | ||
|
|
63f216ea71 | ||
|
|
03d46084b4 | ||
|
|
19eb138666 | ||
|
|
e21b01aa66 | ||
|
|
c41c800998 | ||
|
|
b7675aa440 | ||
|
|
745f49ca6d | ||
|
|
78646c4635 | ||
|
|
f1cc092820 | ||
|
|
7e200241ef | ||
|
|
1c4b8751b7 | ||
|
|
926d8c0517 | ||
|
|
e290e0c702 | ||
|
|
53dec057be | ||
|
|
b3f1798320 | ||
|
|
f20b938f3f | ||
|
|
85915eae8c | ||
|
|
83bb3bb507 | ||
|
|
642b2142ca | ||
|
|
0c6b730647 | ||
|
|
83c367e23b | ||
|
|
fe7bf3dd55 | ||
|
|
273d950899 | ||
|
|
9e61481cfe | ||
|
|
e572c7c2fb | ||
|
|
95ade2a08b | ||
|
|
51324e9c28 | ||
|
|
e6e3a30c0c | ||
|
|
2b7254fbd2 | ||
|
|
d54df57b95 | ||
|
|
b295e31266 | ||
|
|
695c708cb0 | ||
|
|
4a7f56a7ad | ||
|
|
030abed207 | ||
|
|
0d8cfaf104 | ||
|
|
caf708fe4a | ||
|
|
e7cf5fbbdc | ||
|
|
ccaa3d1070 | ||
|
|
4b883d4339 | ||
|
|
b40b4b0369 | ||
|
|
aa37745bbd | ||
|
|
df6ffca01f | ||
|
|
9dd8a257df | ||
|
|
2984ce4852 | ||
|
|
8fb35ac822 | ||
|
|
e8c42c281f | ||
|
|
68892b92ab | ||
|
|
12ee60f776 | ||
|
|
945314d271 | ||
|
|
93d4f30cad | ||
|
|
dda91cf770 | ||
|
|
6a1160feab | ||
|
|
b48b5ae1aa | ||
|
|
5bf8154e0b | ||
|
|
6de51848fd | ||
|
|
ee906ae8c0 | ||
|
|
7eebce432d | ||
|
|
750779a3fb | ||
|
|
095bc9006d | ||
|
|
09fdd0c91e | ||
|
|
be5d6227a7 | ||
|
|
855deb5828 | ||
|
|
963e2ecd63 | ||
|
|
14975212a9 | ||
|
|
3ad14bac32 | ||
|
|
e672c0978f | ||
|
|
0d5b7d4ed4 | ||
|
|
7e90423495 | ||
|
|
c7c2620217 | ||
|
|
3944816021 | ||
|
|
db00feff93 | ||
|
|
6e2a077d6b | ||
|
|
1ee8246b48 | ||
|
|
20c3228d8c | ||
|
|
01bf3bee11 | ||
|
|
4d9638f0ac | ||
|
|
b00589367c | ||
|
|
577421a782 | ||
|
|
72e9fb641e | ||
|
|
429f6f9bc0 | ||
|
|
550bf95c76 | ||
|
|
da686ed0c4 | ||
|
|
9f3711bf43 | ||
|
|
6390725ef0 | ||
|
|
cdc468ba15 | ||
|
|
deb4e1921d | ||
|
|
068c9fb43d | ||
|
|
5be4f3bbb9 | ||
|
|
b83ae65a65 | ||
|
|
dc876be57c | ||
|
|
c08847f48d | ||
|
|
19912b5f89 | ||
|
|
019473dec2 | ||
|
|
f001562270 | ||
|
|
34bb58015d | ||
|
|
d86fe68d80 | ||
|
|
1434820d10 | ||
|
|
08fb5b8459 | ||
|
|
8e8dcaa7f9 | ||
|
|
b35059ec99 | ||
|
|
b28ba8f465 | ||
|
|
dcc8085691 | ||
|
|
c186666402 | ||
|
|
6f93960cb5 | ||
|
|
a756ceac22 | ||
|
|
c394520019 | ||
|
|
2d93baf968 | ||
|
|
4a98e6999f | ||
|
|
9ff14aed42 | ||
|
|
0a7bd3111f | ||
|
|
1f88a3b6e1 | ||
|
|
fcb47ff4fc | ||
|
|
3218ceeecd | ||
|
|
1823373d7e | ||
|
|
af14d42939 | ||
|
|
3dd3338ab2 | ||
|
|
26f2578bf2 | ||
|
|
c32ade49b9 | ||
|
|
d4eee38fd2 | ||
|
|
6543c58363 | ||
|
|
158c015682 | ||
|
|
278bac7b6d | ||
|
|
29cddfb528 | ||
|
|
da9f17cf87 | ||
|
|
02dc353032 | ||
|
|
d31ef12363 | ||
|
|
c7d4406a34 | ||
|
|
68e9daffe8 | ||
|
|
c157338783 | ||
|
|
eb9c2c2c99 | ||
|
|
afb4a3dd68 | ||
|
|
9b5032ee84 | ||
|
|
62621881c0 | ||
|
|
52be941b45 | ||
|
|
c94b3a3b65 | ||
|
|
8291fe0407 | ||
|
|
f935526baa | ||
|
|
102ac4bfc8 | ||
|
|
f8b212a318 | ||
|
|
3bd79b822e | ||
|
|
17a4be66b9 | ||
|
|
54cd52db76 | ||
|
|
4fa0334e7b | ||
|
|
b7761d45b7 | ||
|
|
3c6466bec7 | ||
|
|
1d5d0ddd52 | ||
|
|
0818bd9c93 | ||
|
|
43f6cf6267 | ||
|
|
cfd380b243 | ||
|
|
dd1b09a293 | ||
|
|
3ccd1b04da | ||
|
|
0526eb7a64 | ||
|
|
6b321b72ae | ||
|
|
9580875c23 | ||
|
|
d6d8df9f67 | ||
|
|
4eb4b00271 | ||
|
|
e7634c90e7 | ||
|
|
439be3cb26 | ||
|
|
5e93920797 | ||
|
|
a4069eba2a | ||
|
|
30168a1160 | ||
|
|
a2fcaec9ff | ||
|
|
e5e3453c1f | ||
|
|
0d8b04dd56 | ||
|
|
3b997d7c0f | ||
|
|
3ff93f1923 | ||
|
|
64d8e81ed1 | ||
|
|
a741b4eed3 | ||
|
|
81e7c6b84a | ||
|
|
77e06bc9d4 | ||
|
|
c6966c2b63 | ||
|
|
a9652544f5 | ||
|
|
042d29309f | ||
|
|
05d88ba0bf | ||
|
|
d07d173631 | ||
|
|
d02d86edcd | ||
|
|
e99526d195 | ||
|
|
14e5c1c655 | ||
|
|
f7a48bcb3b | ||
|
|
db43704fe3 | ||
|
|
35780ec9f3 | ||
|
|
36997e8b30 | ||
|
|
abe4ac8485 | ||
|
|
f61dfa1f66 | ||
|
|
0bc938d0fa | ||
|
|
668b2cdfb7 | ||
|
|
96e29b7921 | ||
|
|
00893768d1 | ||
|
|
8e43a7ae0c | ||
|
|
251e96b426 | ||
|
|
94331bd938 | ||
|
|
c4b9097868 | ||
|
|
64eb80e8b7 | ||
|
|
d84c2e7875 | ||
|
|
d08c8a541e | ||
|
|
ba7c263d23 | ||
|
|
293cd7d50a | ||
|
|
57b2ae9f37 | ||
|
|
431241bf19 | ||
|
|
703eae8c62 | ||
|
|
34c07811d1 | ||
|
|
871709bb7d | ||
|
|
91a594cc85 | ||
|
|
43bdd38a44 | ||
|
|
6e463e0188 | ||
|
|
319419c541 | ||
|
|
e8c07c9ec6 | ||
|
|
fa5a80df08 | ||
|
|
e51b6147b5 | ||
|
|
2a9712466e | ||
|
|
e9891c4d00 | ||
|
|
5c47ed4a8c | ||
|
|
6e4e7008f2 | ||
|
|
79e31636e2 | ||
|
|
8fcd145d53 | ||
|
|
e30e6ee4ed | ||
|
|
bab920dc82 | ||
|
|
ab2dd75986 | ||
|
|
a8440062b8 | ||
|
|
824cf7cf1b | ||
|
|
65786d8f1c | ||
|
|
bcd29b5d67 | ||
|
|
95847440b5 | ||
|
|
c776900df0 | ||
|
|
fc9b395138 | ||
|
|
7b54292bf7 | ||
|
|
4ef0c18212 | ||
|
|
8e4f9fe66b | ||
|
|
92b24b91e7 | ||
|
|
0db0d88256 | ||
|
|
535ff2211a | ||
|
|
f7329e0687 | ||
|
|
868932cd13 | ||
|
|
9c2ef02368 | ||
|
|
4187244305 | ||
|
|
65b9466ce3 | ||
|
|
a5e5c1dee2 | ||
|
|
a90b911653 | ||
|
|
06940573ca | ||
|
|
01693eb4ce | ||
|
|
1adcc67acb | ||
|
|
1506225100 | ||
|
|
43a09f668e | ||
|
|
07c433e6d4 | ||
|
|
febe5e84a2 | ||
|
|
d7c3b64e02 | ||
|
|
0f6e6c4701 | ||
|
|
b70946f3c3 | ||
|
|
099c309f9b | ||
|
|
02dcef4727 | ||
|
|
bf36a86877 | ||
|
|
a72fce14d1 | ||
|
|
2f339e8113 | ||
|
|
9829103671 | ||
|
|
e83b88d320 | ||
|
|
3667c51c1e | ||
|
|
a55bf60fcf | ||
|
|
90c5eea0af | ||
|
|
63883a1f07 | ||
|
|
5ecad9ea9b | ||
|
|
128642264d | ||
|
|
fa772bdec1 | ||
|
|
0a8d7a23bf | ||
|
|
bf1a238f6a | ||
|
|
3a5b625f05 | ||
|
|
f94a202f9c | ||
|
|
5e3dfbb1f5 | ||
|
|
dd82bb8ed5 | ||
|
|
8fa3384c1e | ||
|
|
d51622f563 | ||
|
|
2041b6969c | ||
|
|
e1fde6ddd9 | ||
|
|
b8f2524fa2 | ||
|
|
4adb3005ae | ||
|
|
b0151da0e7 | ||
|
|
b74df06f48 | ||
|
|
055c4d9b75 | ||
|
|
ea93ec90cb | ||
|
|
4383fcf32c | ||
|
|
cef0cbf9d4 | ||
|
|
3158e5a5da | ||
|
|
20403eb80f | ||
|
|
6b9e6c9ebc | ||
|
|
7d80e6657b | ||
|
|
bb8d3ae3dc | ||
|
|
56033b3288 | ||
|
|
6e44484a1a | ||
|
|
26de08c005 | ||
|
|
d5e3fbb384 | ||
|
|
b46e61a650 | ||
|
|
82a316c5b3 | ||
|
|
44d41a848c | ||
|
|
99657f1c79 | ||
|
|
fb26aeb71a | ||
|
|
6b404c4fcf | ||
|
|
4e05d16cb6 | ||
|
|
7ab3818344 | ||
|
|
93e6d0685b | ||
|
|
ea3f1be425 | ||
|
|
f9a40a2bf7 | ||
|
|
f8c546b82c | ||
|
|
8d3772fdfb | ||
|
|
436b735d48 | ||
|
|
fc7e518373 | ||
|
|
3fcb8ec3f2 | ||
|
|
fccfe3432b | ||
|
|
9708e20dd4 | ||
|
|
df353a1ee8 | ||
|
|
6c9c3e418a | ||
|
|
2a1766fd7b | ||
|
|
63a8403e7b | ||
|
|
7231b3f645 | ||
|
|
96bdb0d4b2 | ||
|
|
7c9f048776 | ||
|
|
6b835931c5 | ||
|
|
f4da1ced3a | ||
|
|
2633538596 | ||
|
|
6e55bcfc77 | ||
|
|
9bf83f9159 | ||
|
|
9e7b43ef4f | ||
|
|
3370e0186d | ||
|
|
85b225eeb7 | ||
|
|
9121e98428 | ||
|
|
47dda7041d | ||
|
|
2983c7b5bc | ||
|
|
6e7d511faa | ||
|
|
edb270d15e | ||
|
|
17bf4924bc | ||
|
|
7a624bc3a3 | ||
|
|
869726fc6b | ||
|
|
66ed3f2644 | ||
|
|
3168814328 | ||
|
|
e077603806 | ||
|
|
99337a7123 | ||
|
|
be0ad00913 | ||
|
|
c61a943f80 | ||
|
|
29dcf4f59a | ||
|
|
b35abffd1b | ||
|
|
07c872c775 | ||
|
|
4891e54586 | ||
|
|
a31257f10f | ||
|
|
5329a17439 | ||
|
|
3975c4fa78 | ||
|
|
04185e150c | ||
|
|
5e02db372d | ||
|
|
bd3acc319c | ||
|
|
cde7382713 | ||
|
|
44337a6b81 | ||
|
|
e610b7624c | ||
|
|
e6aadf9a31 | ||
|
|
f6caa9b67b | ||
|
|
838f7b0125 | ||
|
|
a411ce13c1 | ||
|
|
e07c4487b9 | ||
|
|
6061c7762e | ||
|
|
bda3ab1192 | ||
|
|
5054c58dca | ||
|
|
de02137d7c | ||
|
|
128afcaad1 | ||
|
|
f6d9d29181 | ||
|
|
ff56b1948a | ||
|
|
87b1fc92f8 | ||
|
|
bb974c2642 | ||
|
|
0e0c4aeb77 | ||
|
|
3492d412c6 | ||
|
|
b13bf05d16 | ||
|
|
36df61ba9e | ||
|
|
ee53ccd327 | ||
|
|
7058126e99 | ||
|
|
06c12e36c6 | ||
|
|
c06d79441d | ||
|
|
0a911ae607 | ||
|
|
13fb02599b | ||
|
|
2a3cf4a211 | ||
|
|
2a2e36a713 | ||
|
|
43d109df55 | ||
|
|
4223b1d5ec | ||
|
|
72730b67f1 | ||
|
|
3cdaf2c543 | ||
|
|
eaff62baf5 | ||
|
|
103a705e24 | ||
|
|
894d0743c9 | ||
|
|
482530c569 | ||
|
|
6795c33980 | ||
|
|
6466b88d88 | ||
|
|
9b774e3264 | ||
|
|
33f943a672 | ||
|
|
f5efc16611 | ||
|
|
7e820f9497 | ||
|
|
f3ea19ec31 | ||
|
|
40d2a892a9 | ||
|
|
b4a477e800 | ||
|
|
54ddb652b6 | ||
|
|
7cb9eb797a | ||
|
|
422feb2c6d | ||
|
|
f7df118b7b | ||
|
|
02a1af6fd9 | ||
|
|
87fe63af5e | ||
|
|
7ebf4a4558 | ||
|
|
74cd7c3353 | ||
|
|
5f80641d5f | ||
|
|
ba6461e249 | ||
|
|
24a97524ac | ||
|
|
09c2d21111 | ||
|
|
07b6d8f86f | ||
|
|
8126cca29b | ||
|
|
ba78ded457 | ||
|
|
bda5d90bbc | ||
|
|
a9d0c1cb88 | ||
|
|
f8f533ad37 | ||
|
|
0bc0ab4eec | ||
|
|
d11027fb08 | ||
|
|
7b9e375fea | ||
|
|
dd215687d4 | ||
|
|
e3eddc892d | ||
|
|
36fd05b1f9 | ||
|
|
c6ef9b17a2 | ||
|
|
943c38767b | ||
|
|
c83d2bbc69 | ||
|
|
cbcf3e2754 | ||
|
|
34c67fdb54 | ||
|
|
731f38a2bb | ||
|
|
a8b854a807 | ||
|
|
9bc217d3f5 | ||
|
|
11d6c0bc88 | ||
|
|
072264dce5 | ||
|
|
265d3808e5 | ||
|
|
609812a4a1 | ||
|
|
f48deb1ade | ||
|
|
06f6a4a42c | ||
|
|
da4af6198d | ||
|
|
557f9800f2 | ||
|
|
0a7a73388e | ||
|
|
9923e29901 | ||
|
|
58758b1d6a | ||
|
|
a07cb7ba32 | ||
|
|
58ea0287d3 | ||
|
|
60b1b9aa1f | ||
|
|
6f40aeac1c | ||
|
|
ca16d41592 | ||
|
|
72b06b4a81 | ||
|
|
e59ca3080c | ||
|
|
4f12cc4efa | ||
|
|
796cbb3812 | ||
|
|
246de95e46 | ||
|
|
fdb4ad11ab | ||
|
|
d1c87febae | ||
|
|
7100ff0ba3 | ||
|
|
b5bb09a2f7 | ||
|
|
ea2d45e6c9 | ||
|
|
d9fd814645 | ||
|
|
019a6ed25b | ||
|
|
db39b19e79 | ||
|
|
a6388962dd | ||
|
|
df142a9144 | ||
|
|
47e320e96f | ||
|
|
0c06ba942e | ||
|
|
5e939bd769 | ||
|
|
0b309403e0 | ||
|
|
c232224f04 | ||
|
|
526f326323 | ||
|
|
0b0556a8b0 | ||
|
|
62166554e7 | ||
|
|
a01bcc3da8 | ||
|
|
7e0f4d9c84 | ||
|
|
2adae38805 | ||
|
|
981aad214f | ||
|
|
48a72ce32c | ||
|
|
dfe02ec668 | ||
|
|
a3e23733db | ||
|
|
1c12b8467e | ||
|
|
ad45b72b1a | ||
|
|
785c4be88b | ||
|
|
22d41d21b8 | ||
|
|
69d8570a9e | ||
|
|
a17b0e8b3c | ||
|
|
e73be4a2b3 | ||
|
|
c38967b501 | ||
|
|
95f46d01ce | ||
|
|
8188b19622 | ||
|
|
250f01f207 | ||
|
|
8014302d58 | ||
|
|
19023645df | ||
|
|
a98f680f8f | ||
|
|
4b127b6413 | ||
|
|
2b7f62edd3 | ||
|
|
b5d95d59ff | ||
|
|
9f23a7c54c | ||
|
|
5d5d12dbfd | ||
|
|
c3a188b357 | ||
|
|
bc521b7e7f | ||
|
|
a08da8793b | ||
|
|
cb131d8c9d | ||
|
|
3aad825f86 | ||
|
|
1c2fb43ae1 | ||
|
|
3305e3099b | ||
|
|
4554487ded | ||
|
|
a0ce19f1e0 | ||
|
|
12ed07b074 | ||
|
|
6aa57a22de | ||
|
|
d294eb170d | ||
|
|
04a7007f6e | ||
|
|
bdcd7b8a91 | ||
|
|
1f5b1ad025 | ||
|
|
c4749636d3 | ||
|
|
533876680b | ||
|
|
43e55ccee0 | ||
|
|
17bb2d23ba | ||
|
|
f3ff8279c0 | ||
|
|
1dd97ff9e2 | ||
|
|
9863206fad | ||
|
|
4813e6f476 | ||
|
|
69cca7e97b | ||
|
|
f4d4b67df4 | ||
|
|
a5a5382a60 | ||
|
|
69b7bcb58b | ||
|
|
e0738e0674 | ||
|
|
8e7fc52712 | ||
|
|
bb24821f13 | ||
|
|
57870bf8c5 | ||
|
|
1274986f7d | ||
|
|
6e1eb2c8dd | ||
|
|
e69d58d0fd | ||
|
|
61a7978020 | ||
|
|
b5c5693d4d | ||
|
|
d6db842efb | ||
|
|
ec9ec2031a | ||
|
|
2383fdfade | ||
|
|
36d7de5692 | ||
|
|
7f8937d540 | ||
|
|
4ce83b2ac5 | ||
|
|
517359914b | ||
|
|
0ea4320dfb | ||
|
|
eafa3db1b5 | ||
|
|
e0911b2861 | ||
|
|
6efd8b122f | ||
|
|
f8cf32e0a9 | ||
|
|
02de1c43e2 | ||
|
|
5df14f76fe | ||
|
|
0513ac60cf | ||
|
|
83b1333191 | ||
|
|
56c94bee31 | ||
|
|
0eb1223202 | ||
|
|
da558ddaa2 | ||
|
|
be3fe9028a | ||
|
|
71ed154b81 | ||
|
|
be08bbec58 | ||
|
|
c0c5fa4617 | ||
|
|
769707e54a | ||
|
|
76bedb8079 | ||
|
|
1612005656 | ||
|
|
a5cee7c5ad | ||
|
|
c6d5bda802 | ||
|
|
a9f6521b0e | ||
|
|
e3b1a7d850 | ||
|
|
2143792369 | ||
|
|
7456b5c4fd | ||
|
|
cba2caa78b | ||
|
|
490760caa0 | ||
|
|
b28b1d8810 | ||
|
|
ef44bda9da | ||
|
|
90f6eac3fa | ||
|
|
6851cfc8aa | ||
|
|
a1cf76e89c | ||
|
|
1ac91b35ca | ||
|
|
6d48de2107 | ||
|
|
fcb48ea52f | ||
|
|
3d5e91f455 | ||
|
|
61639e5b51 | ||
|
|
d3c001c673 | ||
|
|
65dc9d487d | ||
|
|
1212c694e8 | ||
|
|
b7f2112c60 | ||
|
|
a356e6e520 | ||
|
|
71d0d38190 | ||
|
|
673b4f8a6b | ||
|
|
2ade41f152 | ||
|
|
d64cf595d6 | ||
|
|
e974718947 | ||
|
|
97da5f0122 | ||
|
|
00be427794 | ||
|
|
775904a7c0 | ||
|
|
aa08dcfbbc | ||
|
|
b75a550c5b | ||
|
|
df094f15cf | ||
|
|
e9e639500a | ||
|
|
9e13ea0b2c | ||
|
|
79188b06f4 | ||
|
|
8cad041035 | ||
|
|
0fd8624483 | ||
|
|
9cf808b5d3 | ||
|
|
3362f0bdd0 | ||
|
|
97c3114200 | ||
|
|
0d8e4a5330 | ||
|
|
6321983eba | ||
|
|
101b9f59b3 | ||
|
|
e570dfb8e7 | ||
|
|
e6b6b41766 | ||
|
|
090ddd1f1c | ||
|
|
76c8c8b520 | ||
|
|
20d9016984 | ||
|
|
144ba00377 | ||
|
|
7159cad2c1 | ||
|
|
c0b15ec066 | ||
|
|
62de2a0345 | ||
|
|
8cf346c3b0 | ||
|
|
a0cefba9fd | ||
|
|
f46f57e23d | ||
|
|
5819a43522 | ||
|
|
dff4135fc9 | ||
|
|
24ad7f4a39 | ||
|
|
4dbb0baa17 | ||
|
|
ce85ee20a7 | ||
|
|
4effd709eb | ||
|
|
09b42f3f08 | ||
|
|
55c2dabe84 |
@@ -3,13 +3,17 @@ description: Report a bug or an issue in the game.
|
|||||||
labels: ["type: minor bug", "status: pending triage"]
|
labels: ["type: minor bug", "status: pending triage"]
|
||||||
title: "Bug Report: "
|
title: "Bug Report: "
|
||||||
body:
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: "# PLEASE READ THE [CONTRIBUTING GUIDE](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md) BEFORE OPENING ISSUES!"
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
attributes:
|
attributes:
|
||||||
label: Issue Checklist
|
label: Issue Checklist
|
||||||
description: Be sure to complete these steps to increase the chances of your issue being addressed!
|
description: Be sure to complete these steps to increase the chances of your issue being addressed!
|
||||||
options:
|
options:
|
||||||
- label: I have properly named my issue
|
- label: I have read the [Contributing Guide](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md)
|
||||||
- label: I have checked the Issues/Discussions pages to see if my issue has already been reported
|
- label: I have checked the Issues/Discussions pages to see if my issue has already been reported
|
||||||
|
- label: I have properly titled my issue
|
||||||
|
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
attributes:
|
attributes:
|
||||||
@@ -40,11 +44,11 @@ body:
|
|||||||
- type: input
|
- type: input
|
||||||
attributes:
|
attributes:
|
||||||
label: Version
|
label: Version
|
||||||
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
|
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
|
||||||
placeholder: ex. 0.5.0
|
placeholder: ex. 0.6.3
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: "## Describe your bug."
|
value: "## Describe your bug."
|
||||||
@@ -56,11 +60,11 @@ body:
|
|||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Description (include any images, videos, errors, or crash logs)
|
label: Description (include any images, videos, errors, or crash logs)
|
||||||
description: Provide as much detail as you can. The better others understand your issue, the more they can help you!
|
description: Provide as much detail as you can. The better others understand your issue, the more they can help you!
|
||||||
placeholder: Describe your issue here...
|
placeholder: Describe your issue here...
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Steps to Reproduce
|
label: Steps to Reproduce
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
name: Charting Issue
|
||||||
|
description: Report an issue with the placement of notes in the game.
|
||||||
|
labels: ["type: charting issue", "status: pending triage"]
|
||||||
|
title: "Charting Issue: "
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: "# PLEASE READ THE [CONTRIBUTING GUIDE](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md) BEFORE OPENING ISSUES!"
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Issue Checklist
|
||||||
|
description: Be sure to complete these steps to increase the chances of your issue being addressed!
|
||||||
|
options:
|
||||||
|
- label: I have read the [Contributing Guide](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md)
|
||||||
|
- label: I have checked the Issues/Discussions pages to see if my issue has already been reported
|
||||||
|
- label: I have properly titled my issue
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: Platform
|
||||||
|
description: Which platform are you playing on?
|
||||||
|
options:
|
||||||
|
- Newgrounds (Web/HTML5)
|
||||||
|
- Itch.io (Web/HTML5)
|
||||||
|
- Itch.io (Downloadable Build) - Windows
|
||||||
|
- Itch.io (Downloadable Build) - MacOS
|
||||||
|
- Itch.io (Downloadable Build) - Linux
|
||||||
|
- Compiled from GitHub Source Code
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: Browser
|
||||||
|
description: (Web/HTML5 users only) Which browser are you playing on?
|
||||||
|
options:
|
||||||
|
- Google Chrome
|
||||||
|
- Microsoft Edge
|
||||||
|
- Firefox
|
||||||
|
- Opera
|
||||||
|
- Safari
|
||||||
|
- Other (Specify in Description field)
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Version
|
||||||
|
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
|
||||||
|
placeholder: ex. 0.6.3
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: "## Describe the charting issue(s)."
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: "### Please do not report issues from other engines. These must be reported in their respective repositories."
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Location
|
||||||
|
description: Where did you find the issue(s)? Include the name of the song, the variation, the difficulty, the time/section of the song, and any images or videos.
|
||||||
|
placeholder: ex. Cocoa Erect on Erect/Nightmare difficulties at Section 30
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Description
|
||||||
|
description: Why do you believe this is an issue? Is a note at the wrong time, a hold note too short/long, or something else?
|
||||||
|
placeholder: Describe the charting issue(s) here...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
name: Compiling Help
|
||||||
|
description: Report an issue with compiling the game.
|
||||||
|
labels: ["type: compilation help", "status: pending triage"]
|
||||||
|
title: "Compiling Help: "
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: "# PLEASE READ THE [CONTRIBUTING GUIDE](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md) BEFORE OPENING ISSUES!"
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Issue Checklist
|
||||||
|
description: Be sure to complete these steps to increase the chances of your issue being addressed!
|
||||||
|
options:
|
||||||
|
- label: I have followed the [Compiling Guide](https://github.com/FunkinCrew/Funkin/blob/main/docs/COMPILING.md) and the [Troubleshooting Guide](https://github.com/FunkinCrew/Funkin/blob/main/docs/TROUBLESHOOTING.md)
|
||||||
|
- label: I have read the [Contributing Guide](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md)
|
||||||
|
- label: I have checked the Issues/Discussions pages to see if my issue has already been reported
|
||||||
|
- label: I have properly titled my issue
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: Platform
|
||||||
|
description: Which platform are you compiling for?
|
||||||
|
options:
|
||||||
|
- Web/HTML5
|
||||||
|
- Desktop (Windows)
|
||||||
|
- Desktop (Mac)
|
||||||
|
- Desktop (Linux)
|
||||||
|
- Other
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Version
|
||||||
|
description: Which version are you compiling? The game version is in the bottom left corner of the main menu or in the project.hxp file.
|
||||||
|
placeholder: ex. 0.6.3
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: "## Describe your compiling issue."
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: "### Please do not report issues from other engines. These must be reported in their respective repositories."
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Console Output
|
||||||
|
description: Paste any errors or warnings from the console here.
|
||||||
|
placeholder: Paste output here...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Context
|
||||||
|
description: Provide as much detail as you can. Did you modify any code? Provide any relevant images or videos.
|
||||||
|
placeholder: Describe the context here...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
@@ -3,13 +3,18 @@ description: Report a crash that occurred while playing the game.
|
|||||||
labels: ["type: major bug", "status: pending triage"]
|
labels: ["type: major bug", "status: pending triage"]
|
||||||
title: "Crash Report: "
|
title: "Crash Report: "
|
||||||
body:
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: "# PLEASE READ THE [CONTRIBUTING GUIDE](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md) BEFORE OPENING ISSUES!"
|
||||||
|
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
attributes:
|
attributes:
|
||||||
label: Issue Checklist
|
label: Issue Checklist
|
||||||
description: Be sure to complete these steps to increase the chances of your issue being addressed!
|
description: Be sure to complete these steps to increase the chances of your issue being addressed!
|
||||||
options:
|
options:
|
||||||
- label: I have properly named my issue
|
- label: I have read the [Contributing Guide](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md)
|
||||||
- label: I have checked the Issues/Discussions pages to see if my issue has already been reported
|
- label: I have checked the Issues/Discussions pages to see if my issue has already been reported
|
||||||
|
- label: I have properly titled my issue
|
||||||
|
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
attributes:
|
attributes:
|
||||||
@@ -41,7 +46,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: Version
|
label: Version
|
||||||
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
|
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
|
||||||
placeholder: ex. 0.5.0
|
placeholder: ex. 0.6.3
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,18 @@ description: Suggest a new feature.
|
|||||||
labels: ["type: enhancement", "status: pending triage"]
|
labels: ["type: enhancement", "status: pending triage"]
|
||||||
title: "Enhancement: "
|
title: "Enhancement: "
|
||||||
body:
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: "# PLEASE READ THE [CONTRIBUTING GUIDE](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md) BEFORE OPENING ISSUES!"
|
||||||
|
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
attributes:
|
attributes:
|
||||||
label: Issue Checklist
|
label: Issue Checklist
|
||||||
description: Be sure to complete these steps to increase the chances of your suggestion being considered!
|
description: Be sure to complete these steps to increase the chances of your suggestion being considered!
|
||||||
options:
|
options:
|
||||||
- label: I have properly named my enhancement
|
- label: I have read the [Contributing Guide](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md)
|
||||||
- label: I have checked the Issues/Discussions pages to see if my enhancement has already been suggested
|
- label: I have checked the Issues/Discussions pages to see if my enhancement has already been suggested
|
||||||
|
- label: I have properly titled my enhancement
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ inputs:
|
|||||||
haxe:
|
haxe:
|
||||||
description: 'Version of haxe to install'
|
description: 'Version of haxe to install'
|
||||||
required: true
|
required: true
|
||||||
default: '4.3.4'
|
default: '4.3.6'
|
||||||
hxcpp-cache:
|
hxcpp-cache:
|
||||||
description: 'Whether to use a shared hxcpp compile cache'
|
description: 'Whether to use a shared hxcpp compile cache'
|
||||||
required: true
|
required: true
|
||||||
@@ -30,7 +30,7 @@ runs:
|
|||||||
echo "TIMER_HAXE=$(date +%s)" >> "$GITHUB_ENV"
|
echo "TIMER_HAXE=$(date +%s)" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
- name: Install Haxe
|
- name: Install Haxe
|
||||||
uses: funkincrew/ci-haxe@v3.1.0
|
uses: funkincrew/ci-haxe@v3.2.7
|
||||||
with:
|
with:
|
||||||
haxe-version: ${{ inputs.haxe }}
|
haxe-version: ${{ inputs.haxe }}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
# Add 'small' to any changes below 10 lines
|
# Add 'size: small' to any changes below 10 lines
|
||||||
small:
|
'size: small':
|
||||||
max: 9
|
max: 9
|
||||||
|
|
||||||
# Add 'medium' to any changes between 10 and 100 lines
|
# Add 'size: medium' to any changes between 10 and 100 lines
|
||||||
medium:
|
'size: medium':
|
||||||
min: 10
|
min: 10
|
||||||
max: 99
|
max: 99
|
||||||
|
|
||||||
# Add 'large' to any changes of at least 100 lines
|
# Add 'size: large' to any changes of at least 100 lines
|
||||||
large:
|
'size: large':
|
||||||
min: 100
|
min: 100
|
||||||
|
|||||||
+121
-8
@@ -7,38 +7,151 @@
|
|||||||
comment: >
|
comment: >
|
||||||
This issue is a duplicate. Please direct all discussion to the original issue.
|
This issue is a duplicate. Please direct all discussion to the original issue.
|
||||||
# Close the issue
|
# Close the issue
|
||||||
close: true
|
close: false
|
||||||
|
# Set a close reason
|
||||||
|
# close-reason: 'not planned'
|
||||||
# Remove other status labels
|
# Remove other status labels
|
||||||
unlabel:
|
unlabel:
|
||||||
|
- 'status: accepted'
|
||||||
|
- 'status: bug reproduced'
|
||||||
|
- 'status: cannot reproduce'
|
||||||
|
- 'status: needs clarification'
|
||||||
|
- 'status: needs r&d'
|
||||||
|
- 'status: needs revision'
|
||||||
|
- 'status: pending pull request'
|
||||||
- 'status: pending triage'
|
- 'status: pending triage'
|
||||||
# Set a close reason
|
- 'status: planned'
|
||||||
close-reason: 'not planned'
|
- 'status: rejected'
|
||||||
|
- 'status: resolved'
|
||||||
|
- 'status: resolved internally'
|
||||||
|
- 'status: reviewing internally'
|
||||||
|
- 'status: stale'
|
||||||
prs:
|
prs:
|
||||||
# Post a comment
|
# Post a comment
|
||||||
comment: >
|
comment: >
|
||||||
This pull request is a duplicate. Please direct all discussion to the original pull request.
|
This pull request is a duplicate. Please direct all discussion to the original pull request.
|
||||||
|
# Close the pull request
|
||||||
|
close: false
|
||||||
# Remove other status labels
|
# Remove other status labels
|
||||||
unlabel:
|
unlabel:
|
||||||
|
- 'status: accepted'
|
||||||
|
- 'status: bug reproduced'
|
||||||
|
- 'status: cannot reproduce'
|
||||||
|
- 'status: needs clarification'
|
||||||
|
- 'status: needs r&d'
|
||||||
|
- 'status: needs revision'
|
||||||
|
- 'status: pending pull request'
|
||||||
- 'status: pending triage'
|
- 'status: pending triage'
|
||||||
# Close the pull request
|
- 'status: planned'
|
||||||
|
- 'status: rejected'
|
||||||
|
- 'status: resolved'
|
||||||
|
- 'status: resolved internally'
|
||||||
|
- 'status: reviewing internally'
|
||||||
|
- 'status: stale'
|
||||||
|
|
||||||
|
'status: stale':
|
||||||
|
issues:
|
||||||
|
# Close the issue
|
||||||
close: true
|
close: true
|
||||||
# Set a close reason
|
# Set a close reason
|
||||||
close-reason: 'not planned'
|
close-reason: 'not planned'
|
||||||
|
# Remove other status labels
|
||||||
|
unlabel:
|
||||||
|
- 'status: accepted'
|
||||||
|
- 'status: bug reproduced'
|
||||||
|
- 'status: cannot reproduce'
|
||||||
|
- 'status: duplicate'
|
||||||
|
- 'status: needs clarification'
|
||||||
|
- 'status: needs r&d'
|
||||||
|
- 'status: needs revision'
|
||||||
|
- 'status: pending pull request'
|
||||||
|
- 'status: pending triage'
|
||||||
|
- 'status: planned'
|
||||||
|
- 'status: rejected'
|
||||||
|
- 'status: resolved'
|
||||||
|
- 'status: resolved internally'
|
||||||
|
- 'status: reviewing internally'
|
||||||
|
prs:
|
||||||
|
# Close the pull request
|
||||||
|
close: true
|
||||||
|
# Remove other status labels
|
||||||
|
unlabel:
|
||||||
|
- 'status: accepted'
|
||||||
|
- 'status: bug reproduced'
|
||||||
|
- 'status: cannot reproduce'
|
||||||
|
- 'status: duplicate'
|
||||||
|
- 'status: needs clarification'
|
||||||
|
- 'status: needs r&d'
|
||||||
|
- 'status: needs revision'
|
||||||
|
- 'status: pending pull request'
|
||||||
|
- 'status: pending triage'
|
||||||
|
- 'status: planned'
|
||||||
|
- 'status: rejected'
|
||||||
|
- 'status: resolved'
|
||||||
|
- 'status: resolved internally'
|
||||||
|
- 'status: reviewing internally'
|
||||||
|
|
||||||
'status: rejected':
|
'status: rejected':
|
||||||
issues:
|
issues:
|
||||||
# Close the issue
|
# Close the issue
|
||||||
close: true
|
close: true
|
||||||
# Remove other status labels
|
|
||||||
unlabel:
|
|
||||||
- 'status: pending triage'
|
|
||||||
# Set a close reason
|
# Set a close reason
|
||||||
close-reason: 'not planned'
|
close-reason: 'not planned'
|
||||||
|
# Remove other status labels
|
||||||
|
unlabel:
|
||||||
|
- 'status: accepted'
|
||||||
|
- 'status: bug reproduced'
|
||||||
|
- 'status: cannot reproduce'
|
||||||
|
- 'status: duplicate'
|
||||||
|
- 'status: needs clarification'
|
||||||
|
- 'status: needs r&d'
|
||||||
|
- 'status: needs revision'
|
||||||
|
- 'status: pending pull request'
|
||||||
|
- 'status: pending triage'
|
||||||
|
- 'status: planned'
|
||||||
|
- 'status: resolved'
|
||||||
|
- 'status: resolved internally'
|
||||||
|
- 'status: reviewing internally'
|
||||||
|
- 'status: stale'
|
||||||
prs:
|
prs:
|
||||||
# Close the pull request
|
# Close the pull request
|
||||||
close: true
|
close: true
|
||||||
# Remove other status labels
|
# Remove other status labels
|
||||||
unlabel:
|
unlabel:
|
||||||
|
- 'status: accepted'
|
||||||
|
- 'status: bug reproduced'
|
||||||
|
- 'status: cannot reproduce'
|
||||||
|
- 'status: duplicate'
|
||||||
|
- 'status: needs clarification'
|
||||||
|
- 'status: needs r&d'
|
||||||
|
- 'status: needs revision'
|
||||||
|
- 'status: pending pull request'
|
||||||
- 'status: pending triage'
|
- 'status: pending triage'
|
||||||
|
- 'status: planned'
|
||||||
|
- 'status: resolved'
|
||||||
|
- 'status: resolved internally'
|
||||||
|
- 'status: reviewing internally'
|
||||||
|
- 'status: stale'
|
||||||
|
|
||||||
|
'status: resolved':
|
||||||
|
issues:
|
||||||
|
# Close the issue
|
||||||
|
close: true
|
||||||
# Set a close reason
|
# Set a close reason
|
||||||
close-reason: 'not planned'
|
close-reason: 'completed'
|
||||||
|
# Remove other status labels
|
||||||
|
unlabel:
|
||||||
|
- 'status: accepted'
|
||||||
|
- 'status: bug reproduced'
|
||||||
|
- 'status: cannot reproduce'
|
||||||
|
- 'status: duplicate'
|
||||||
|
- 'status: needs clarification'
|
||||||
|
- 'status: needs r&d'
|
||||||
|
- 'status: needs revision'
|
||||||
|
- 'status: pending pull request'
|
||||||
|
- 'status: pending triage'
|
||||||
|
- 'status: planned'
|
||||||
|
- 'status: rejected'
|
||||||
|
- 'status: resolved internally'
|
||||||
|
- 'status: reviewing internally'
|
||||||
|
- 'status: stale'
|
||||||
|
|||||||
+9
-2
@@ -1,11 +1,18 @@
|
|||||||
# Add Documentation tag to PR's changing markdown files, or anything in the docs folder
|
# Add Documentation tag to PR's changing markdown files, or anything in the docs folder
|
||||||
Documentation:
|
'pr: documentation':
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- docs/*
|
- docs/*
|
||||||
- '**/*.md'
|
- '**/*.md'
|
||||||
|
|
||||||
# Add Haxe tag to PR's changing haxe code files
|
# Add Haxe tag to PR's changing haxe code files
|
||||||
Haxe:
|
'pr: haxe':
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file: '**/*.hx'
|
- any-glob-to-any-file: '**/*.hx'
|
||||||
|
|
||||||
|
# Add GitHub tag to PR's changing yml files, or anything in the .github folder
|
||||||
|
'pr: github':
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- github/*
|
||||||
|
- '**/*.yml'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!-- Please check for duplicates or similar PRs before submitting this PR. -->
|
<!-- Please read the Contributing Guide (https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md) before submitting this PR. -->
|
||||||
## Does this PR close any issues? If so, link them below.
|
## Does this PR close any issues? If so, link them below.
|
||||||
|
|
||||||
## Briefly describe the issue(s) fixed.
|
## Briefly describe the issue(s) fixed.
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
name: Create and publish Docker image
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
|
||||||
paths:
|
|
||||||
- '**/Dockerfile'
|
|
||||||
- '.github/workflows/build-docker-image.yml'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-and-push-image:
|
|
||||||
runs-on: build-set
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Get checkout token
|
|
||||||
uses: actions/create-github-app-token@v1
|
|
||||||
id: app_token
|
|
||||||
with:
|
|
||||||
app-id: ${{ vars.APP_ID }}
|
|
||||||
private-key: ${{ secrets.APP_PEM }}
|
|
||||||
owner: ${{ github.repository_owner }}
|
|
||||||
|
|
||||||
- name: Checkout repo
|
|
||||||
uses: funkincrew/ci-checkout@v6
|
|
||||||
with:
|
|
||||||
submodules: false
|
|
||||||
token: ${{ steps.app_token.outputs.token }}
|
|
||||||
|
|
||||||
- name: Log into GitHub Container Registry
|
|
||||||
uses: docker/login-action@v3.1.0
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Build and push Docker image
|
|
||||||
uses: docker/build-push-action@v5.3.0
|
|
||||||
with:
|
|
||||||
context: ./build
|
|
||||||
push: true
|
|
||||||
tags: |
|
|
||||||
ghcr.io/funkincrew/build-dependencies:latest
|
|
||||||
ghcr.io/funkincrew/build-dependencies:${{ github.sha }}
|
|
||||||
labels: |
|
|
||||||
org.opencontainers.image.description=precooked haxe build-dependencies
|
|
||||||
org.opencontainers.image.revision=${{ github.sha }}
|
|
||||||
org.opencontainers.image.source=https://github.com/${{ github.repository }}
|
|
||||||
org.opencontainers.image.title=${{ github.repository_owner }}/build-dependencies
|
|
||||||
org.opencontainers.image.url=https://github.com/${{ github.repository }}
|
|
||||||
org.opencontainers.image.version=${{ github.sha }}
|
|
||||||
+214
-121
@@ -12,146 +12,239 @@ on:
|
|||||||
description: Save the build artifact to Github Actions (sends to itch otherwise)
|
description: Save the build artifact to Github Actions (sends to itch otherwise)
|
||||||
default: false
|
default: false
|
||||||
push:
|
push:
|
||||||
paths-ignore:
|
|
||||||
- '**/Dockerfile'
|
|
||||||
- '.github/workflows/build-docker-image.yml'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
|
gather-changes:
|
||||||
|
runs-on: build-set
|
||||||
|
outputs:
|
||||||
|
trigger-build: ${{ steps.should-trigger.outputs.result }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: funkincrew/ci-checkout@v6
|
||||||
|
with:
|
||||||
|
submodules: false
|
||||||
|
- uses: dorny/paths-filter@v3
|
||||||
|
id: filter
|
||||||
|
with:
|
||||||
|
base: ${{ github.ref }}
|
||||||
|
filters: |
|
||||||
|
docker:
|
||||||
|
- '.github/workflows/build-game.yml'
|
||||||
|
- '**/Dockerfile'
|
||||||
|
- uses: actions/github-script@v7
|
||||||
|
id: should-trigger
|
||||||
|
with:
|
||||||
|
result-encoding: string
|
||||||
|
script: |
|
||||||
|
const { payload } = context
|
||||||
|
const changes = ${{ steps.filter.outputs.changes }}
|
||||||
|
const manual = payload.commits
|
||||||
|
.some(c => c.message.toLowerCase().replace(/\W/g, " ").includes("docker rebuild"))
|
||||||
|
|
||||||
|
console.log({ payload, changes, manual, commits: payload.commits })
|
||||||
|
|
||||||
|
return payload.created
|
||||||
|
|| payload.forced
|
||||||
|
|| changes.includes("docker")
|
||||||
|
|| manual
|
||||||
|
|
||||||
|
gather-tags:
|
||||||
|
runs-on: build-set
|
||||||
|
outputs:
|
||||||
|
primary: ${{ steps.build-tags.outputs.primary }}
|
||||||
|
list: ${{ steps.build-tags.outputs.list }}
|
||||||
|
steps:
|
||||||
|
- name: Gather build tags
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
id: build-tags
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const base = "ghcr.io/funkincrew/build-dependencies"
|
||||||
|
const default_branch = "rewrite/master"
|
||||||
|
|
||||||
|
const ref_path = context.ref.split("/").slice(2)
|
||||||
|
const ref = ref_path.join("/")
|
||||||
|
const ref_tag = ref_path.join("-")
|
||||||
|
|
||||||
|
const tags = [ref_tag, context.sha]
|
||||||
|
|
||||||
|
if (ref === default_branch) tags.push("latest")
|
||||||
|
|
||||||
|
console.log([
|
||||||
|
`ref: ${ref}`,
|
||||||
|
`default_branch: ${default_branch}`,
|
||||||
|
`tags: ${tags.join(", ")}`
|
||||||
|
].join('\n'))
|
||||||
|
|
||||||
|
const tag_list = tags
|
||||||
|
.map(tag => `${base}:${tag}`)
|
||||||
|
.join("\n")
|
||||||
|
|
||||||
|
core.setOutput("primary", ref_tag)
|
||||||
|
core.setOutput("list", tag_list)
|
||||||
|
|
||||||
|
docker-image:
|
||||||
|
needs: [gather-changes, gather-tags]
|
||||||
|
if: needs.gather-changes.outputs.trigger-build == 'true'
|
||||||
|
runs-on: build-set
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: funkincrew/ci-checkout@v6
|
||||||
|
with:
|
||||||
|
submodules: false
|
||||||
|
- name: Log into GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Build and push Docker image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: ./build
|
||||||
|
push: true
|
||||||
|
tags: ${{ needs.gather-tags.outputs.list }}
|
||||||
|
labels: |
|
||||||
|
org.opencontainers.image.description=precooked haxe build dependencies
|
||||||
|
org.opencontainers.image.revision=${{ github.sha }}
|
||||||
|
org.opencontainers.image.source=https://github.com/${{ github.repository }}
|
||||||
|
org.opencontainers.image.title=${{ github.repository_owner }}/build-dependencies
|
||||||
|
org.opencontainers.image.url=https://github.com/${{ github.repository }}
|
||||||
|
org.opencontainers.image.version=${{ github.sha }}
|
||||||
|
|
||||||
build-game-on-host:
|
build-game-on-host:
|
||||||
strategy:
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- target: windows
|
- target: windows
|
||||||
- target: macos
|
runs-on: windows
|
||||||
|
- target: macos
|
||||||
|
runs-on: macos
|
||||||
runs-on:
|
runs-on:
|
||||||
- ${{ matrix.target }}
|
- ${{ matrix.runs-on }}
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
|
env:
|
||||||
|
BUILD_DEFINES: ${{ github.event.inputs.build-defines || '-DGITHUB_BUILD' }}
|
||||||
steps:
|
steps:
|
||||||
- name: Make git happy
|
- name: Make git happy
|
||||||
run: |
|
run: |
|
||||||
git config --global --replace-all safe.directory $GITHUB_WORKSPACE
|
git config --global --replace-all safe.directory $GITHUB_WORKSPACE
|
||||||
|
- name: Get checkout token
|
||||||
- name: Get checkout token
|
uses: actions/create-github-app-token@v1
|
||||||
uses: actions/create-github-app-token@v1
|
id: app_token
|
||||||
id: app_token
|
with:
|
||||||
with:
|
app-id: ${{ vars.APP_ID }}
|
||||||
app-id: ${{ vars.APP_ID }}
|
private-key: ${{ secrets.APP_PEM }}
|
||||||
private-key: ${{ secrets.APP_PEM }}
|
owner: ${{ github.repository_owner }}
|
||||||
owner: ${{ github.repository_owner }}
|
- name: Checkout repo
|
||||||
|
uses: funkincrew/ci-checkout@v6
|
||||||
- name: Checkout repo
|
with:
|
||||||
uses: funkincrew/ci-checkout@v6
|
submodules: 'recursive'
|
||||||
with:
|
token: ${{ steps.app_token.outputs.token }}
|
||||||
submodules: 'recursive'
|
persist-credentials: false
|
||||||
token: ${{ steps.app_token.outputs.token }}
|
- name: Setup build environment
|
||||||
persist-credentials: false
|
uses: ./.github/actions/setup-haxe
|
||||||
|
with:
|
||||||
- name: Setup build environment
|
gh-token: ${{ steps.app_token.outputs.token }}
|
||||||
uses: ./.github/actions/setup-haxe
|
- name: Setup HXCPP dev commit
|
||||||
with:
|
run: |
|
||||||
gh-token: ${{ steps.app_token.outputs.token }}
|
cd .haxelib/hxcpp/git/tools/hxcpp
|
||||||
- name: Setup HXCPP dev commit
|
haxe compile.hxml
|
||||||
run: |
|
cd ../../../../..
|
||||||
cd .haxelib/hxcpp/git/tools/hxcpp
|
- name: Build game (windows)
|
||||||
haxe compile.hxml
|
if: ${{ matrix.target == 'windows' }}
|
||||||
cd ../../../../..
|
run: |
|
||||||
- name: Build game
|
haxelib run lime build windows -v -release $BUILD_DEFINES
|
||||||
if: ${{ matrix.target == 'windows' }}
|
timeout-minutes: 120
|
||||||
run: |
|
- name: Build game (unix)
|
||||||
haxelib run lime build windows -v -release ${{ github.event.inputs.build-defines }}
|
if: ${{ matrix.target != 'windows' }}
|
||||||
timeout-minutes: 120
|
run: |
|
||||||
- name: Build game
|
haxelib run lime build ${{ matrix.target }} -v -release --times $BUILD_DEFINES
|
||||||
if: ${{ matrix.target != 'windows' }}
|
timeout-minutes: 120
|
||||||
run: |
|
- name: Save build artifact to Github Actions
|
||||||
haxelib run lime build ${{ matrix.target }} -v -release --times ${{ github.event.inputs.build-defines }}
|
if: ${{ github.event.inputs.save-artifact }}
|
||||||
timeout-minutes: 120
|
uses: actions/upload-artifact@v4
|
||||||
- name: Save build artifact to Github Actions
|
with:
|
||||||
if: ${{ github.event.inputs.save-artifact }}
|
name: build-${{ matrix.target }}
|
||||||
uses: actions/upload-artifact@v4
|
path: export/release/${{matrix.target}}/bin/
|
||||||
with:
|
- name: Upload build artifacts
|
||||||
name: build-${{ matrix.target }}
|
uses: ./.github/actions/upload-itch
|
||||||
path: export/release/${{matrix.target}}/bin/
|
with:
|
||||||
|
butler-key: ${{ secrets.BUTLER_API_KEY}}
|
||||||
|
target: ${{ matrix.target }}
|
||||||
- name: Upload build artifacts
|
|
||||||
uses: ./.github/actions/upload-itch
|
|
||||||
with:
|
|
||||||
butler-key: ${{ secrets.BUTLER_API_KEY}}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
|
|
||||||
build-game-in-container:
|
build-game-in-container:
|
||||||
|
needs: [gather-tags, docker-image]
|
||||||
|
if: ${{ ! cancelled() }}
|
||||||
runs-on: build-set
|
runs-on: build-set
|
||||||
container: ghcr.io/funkincrew/build-dependencies:latest
|
container: ghcr.io/funkincrew/build-dependencies:${{ needs.gather-tags.outputs.primary }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- target: linux
|
- target: linux
|
||||||
- target: html5
|
- target: html5
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
|
env:
|
||||||
|
BUILD_DEFINES: ${{ github.event.inputs.build-defines || '-DGITHUB_BUILD' }}
|
||||||
steps:
|
steps:
|
||||||
- name: Get checkout token
|
- name: Get checkout token
|
||||||
uses: actions/create-github-app-token@v1
|
uses: actions/create-github-app-token@v1
|
||||||
id: app_token
|
id: app_token
|
||||||
with:
|
with:
|
||||||
app-id: ${{ vars.APP_ID }}
|
app-id: ${{ vars.APP_ID }}
|
||||||
private-key: ${{ secrets.APP_PEM }}
|
private-key: ${{ secrets.APP_PEM }}
|
||||||
owner: ${{ github.repository_owner }}
|
owner: ${{ github.repository_owner }}
|
||||||
|
- name: Checkout repo
|
||||||
- name: Checkout repo
|
uses: funkincrew/ci-checkout@v6
|
||||||
uses: funkincrew/ci-checkout@v6
|
with:
|
||||||
with:
|
submodules: 'recursive'
|
||||||
submodules: 'recursive'
|
token: ${{ steps.app_token.outputs.token }}
|
||||||
token: ${{ steps.app_token.outputs.token }}
|
persist-credentials: false
|
||||||
persist-credentials: false
|
- name: Config haxelib
|
||||||
|
run: |
|
||||||
- name: Config haxelib
|
haxelib --never newrepo
|
||||||
run: |
|
echo "HAXEPATH=$(haxelib config)" >> "$GITHUB_ENV"
|
||||||
haxelib --never newrepo
|
- name: Restore cached dependencies
|
||||||
echo "HAXEPATH=$(haxelib config)" >> "$GITHUB_ENV"
|
id: cache-hmm
|
||||||
|
uses: actions/cache@v4
|
||||||
- name: Restore cached dependencies
|
with:
|
||||||
id: cache-hmm
|
path: .haxelib
|
||||||
uses: actions/cache@v4
|
key: haxe-hmm-${{ runner.os }}-${{ hashFiles('**/hmm.json') }}
|
||||||
with:
|
- if: ${{ steps.cache-hmm.outputs.cache-hit != 'true' }}
|
||||||
path: .haxelib
|
name: Install dependencies
|
||||||
key: haxe-hmm-${{ runner.os }}-${{ hashFiles('**/hmm.json') }}
|
run: |
|
||||||
|
git config --global 'url.https://x-access-token:${{ steps.app_token.outputs.token }}@github.com/.insteadOf' https://github.com/
|
||||||
- if: ${{ steps.cache-hmm.outputs.cache-hit != 'true' }}
|
git config --global advice.detachedHead false
|
||||||
name: Install dependencies
|
haxelib --global run hmm install -q
|
||||||
run: |
|
cd .haxelib/hxcpp/git/tools/hxcpp && haxe compile.hxml
|
||||||
git config --global 'url.https://x-access-token:${{ steps.app_token.outputs.token }}@github.com/.insteadOf' https://github.com/
|
- if: ${{ matrix.target != 'html5' }}
|
||||||
git config --global advice.detachedHead false
|
name: Restore hxcpp cache
|
||||||
haxelib --global run hmm install -q
|
uses: actions/cache@v4
|
||||||
cd .haxelib/hxcpp/git/tools/hxcpp && haxe compile.hxml
|
with:
|
||||||
|
path: /usr/share/hxcpp
|
||||||
- if: ${{ matrix.target != 'html5' }}
|
key: haxe-hxcpp-${{ runner.os }}-${{ github.ref_name }}-${{ github.sha }}
|
||||||
name: Restore hxcpp cache
|
restore-keys: haxe-hxcpp-${{ runner.os }}-${{ github.ref_name }}
|
||||||
uses: actions/cache@v4
|
- name: Build game
|
||||||
with:
|
run: |
|
||||||
path: /usr/share/hxcpp
|
haxelib run lime build ${{ matrix.target }} -v -release --times $BUILD_DEFINES
|
||||||
key: haxe-hxcpp-${{ runner.os }}-${{ github.ref_name }}-${{ github.sha }}
|
timeout-minutes: 120
|
||||||
restore-keys: haxe-hxcpp-${{ runner.os }}-${{ github.ref_name }}
|
- name: Save build artifact to Github Actions
|
||||||
|
if: ${{ github.event.inputs.save-artifact }}
|
||||||
- name: Build game
|
uses: actions/upload-artifact@v4
|
||||||
run: |
|
with:
|
||||||
haxelib run lime build ${{ matrix.target }} -v -release --times ${{ github.event.inputs.build-defines }}
|
name: build-${{ matrix.target }}
|
||||||
timeout-minutes: 120
|
path: export/release/${{matrix.target}}/bin/
|
||||||
|
- name: Upload build artifacts
|
||||||
- name: Save build artifact to Github Actions
|
uses: ./.github/actions/upload-itch
|
||||||
if: ${{ github.event.inputs.save-artifact }}
|
with:
|
||||||
uses: actions/upload-artifact@v4
|
butler-key: ${{ secrets.BUTLER_API_KEY}}
|
||||||
with:
|
target: ${{ matrix.target }}
|
||||||
name: build-${{ matrix.target }}
|
|
||||||
path: export/release/${{matrix.target}}/bin/
|
|
||||||
- name: Upload build artifacts
|
|
||||||
uses: ./.github/actions/upload-itch
|
|
||||||
with:
|
|
||||||
butler-key: ${{ secrets.BUTLER_API_KEY}}
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
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,19 @@
|
|||||||
|
name: "Pull Request Labeler 2 (Runs on PR creation)"
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# Apply `status: pending triage` to newly created pull requests
|
||||||
|
apply-pending-triage:
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Apply "status pending triage" to new pull requests
|
||||||
|
uses: actions-ecosystem/action-add-labels@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
labels: "status: pending triage"
|
||||||
@@ -4,10 +4,12 @@
|
|||||||
APIStuff.hx
|
APIStuff.hx
|
||||||
dump/
|
dump/
|
||||||
export/
|
export/
|
||||||
|
project/
|
||||||
RECOVER_*.fla
|
RECOVER_*.fla
|
||||||
shitAudio/
|
shitAudio/
|
||||||
.build_time
|
.build_time
|
||||||
.swp
|
.swp
|
||||||
|
NewgroundsCredentials.hx
|
||||||
|
|
||||||
# Exclude JS stuff
|
# Exclude JS stuff
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|||||||
Vendored
+1
-2
@@ -6,7 +6,6 @@
|
|||||||
"vshaxe.hxcpp-debugger", // CPP debugging
|
"vshaxe.hxcpp-debugger", // CPP debugging
|
||||||
"openfl.lime-vscode-extension", // Lime integration
|
"openfl.lime-vscode-extension", // Lime integration
|
||||||
"esbenp.prettier-vscode", // JSON formatting
|
"esbenp.prettier-vscode", // JSON formatting
|
||||||
"redhat.vscode-xml", // XML formatting
|
"redhat.vscode-xml" // XML formatting
|
||||||
"ryanluker.vscode-coverage-gutters" // Highlight code coverage
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+16
-49
@@ -70,7 +70,7 @@
|
|||||||
"files.eol": "\n",
|
"files.eol": "\n",
|
||||||
|
|
||||||
"haxe.displayPort": "auto",
|
"haxe.displayPort": "auto",
|
||||||
"haxe.enableCompilationServer": false,
|
"haxe.enableCompilationServer": true,
|
||||||
"haxe.enableServerView": true,
|
"haxe.enableServerView": true,
|
||||||
"haxe.displayServer": {
|
"haxe.displayServer": {
|
||||||
"arguments": ["-v"]
|
"arguments": ["-v"]
|
||||||
@@ -91,26 +91,6 @@
|
|||||||
"haxecheckstyle.codeSimilarityBufferSize": 100,
|
"haxecheckstyle.codeSimilarityBufferSize": 100,
|
||||||
|
|
||||||
"lime.targetConfigurations": [
|
"lime.targetConfigurations": [
|
||||||
{
|
|
||||||
"label": "Windows / Debug",
|
|
||||||
"target": "windows",
|
|
||||||
"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", "-DFEATURE_DEBUG_FUNCTIONS"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "HashLink / Debug",
|
|
||||||
"target": "hl",
|
|
||||||
"args": ["-debug"]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"label": "Windows / Debug (Discord)",
|
"label": "Windows / Debug (Discord)",
|
||||||
"target": "windows",
|
"target": "windows",
|
||||||
@@ -170,23 +150,13 @@
|
|||||||
"target": "windows",
|
"target": "windows",
|
||||||
"args": ["-debug", "-DRESULTS"]
|
"args": ["-debug", "-DRESULTS"]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"label": "Windows / Debug (Straight to Chart Editor)",
|
|
||||||
"target": "windows",
|
|
||||||
"args": ["-debug", "-DCHARTING", "-DFEATURE_DEBUG_FUNCTIONS"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "HashLink / Debug (Straight to Chart Editor)",
|
|
||||||
"target": "hl",
|
|
||||||
"args": ["-debug", "-DCHARTING"]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"label": "Windows / Debug (Straight to Animation Editor)",
|
"label": "Windows / Debug (Straight to Animation Editor)",
|
||||||
"target": "windows",
|
"target": "windows",
|
||||||
"args": ["-debug", "-DANIMDEBUG", "-DFEATURE_DEBUG_FUNCTIONS"]
|
"args": ["-debug", "-DANIMDEBUG", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Windows / Debug (Debug hxCodec)",
|
"label": "Windows / Debug (Debug hxvlc)",
|
||||||
"target": "windows",
|
"target": "windows",
|
||||||
"args": ["-debug", "-DHXC_LIBVLC_LOGGING", "-DFEATURE_DEBUG_FUNCTIONS"]
|
"args": ["-debug", "-DHXC_LIBVLC_LOGGING", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||||
},
|
},
|
||||||
@@ -210,11 +180,6 @@
|
|||||||
"target": "windows",
|
"target": "windows",
|
||||||
"args": ["-debug", "-DWAVEFORM", "-DFEATURE_DEBUG_FUNCTIONS"]
|
"args": ["-debug", "-DWAVEFORM", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"label": "Windows / Release",
|
|
||||||
"target": "windows",
|
|
||||||
"args": ["-release"]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"label": "Windows / Release (GitHub Actions)",
|
"label": "Windows / Release (GitHub Actions)",
|
||||||
"target": "windows",
|
"target": "windows",
|
||||||
@@ -225,29 +190,31 @@
|
|||||||
"target": "hl",
|
"target": "hl",
|
||||||
"args": ["-debug", "-DWAVEFORM"]
|
"args": ["-debug", "-DWAVEFORM"]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"label": "HTML5 / Debug",
|
|
||||||
"target": "html5",
|
|
||||||
"args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS"]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"label": "HTML5 / Debug (Watch)",
|
"label": "HTML5 / Debug (Watch)",
|
||||||
"target": "html5",
|
"target": "html5",
|
||||||
"args": ["-debug", "-watch", "-DFEATURE_DEBUG_FUNCTIONS"]
|
"args": ["-debug", "-watch", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||||
},
|
}
|
||||||
|
],
|
||||||
|
"lime.buildTypes": [
|
||||||
{
|
{
|
||||||
"label": "macOS / Debug",
|
"label": "Debug",
|
||||||
"target": "mac",
|
|
||||||
"args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS"]
|
"args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "macOS / Release",
|
"label": "Debug (Tracy)",
|
||||||
"target": "mac",
|
"args": ["-debug", "-DFEATURE_DEBUG_TRACY", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Debug (Straight to Chart Editor)",
|
||||||
|
"args": ["-debug", "-DCHARTING", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Release",
|
||||||
"args": ["-release"]
|
"args": ["-release"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "macOS / Release (GitHub Actions)",
|
"label": "Release (GitHub Actions)",
|
||||||
"target": "mac",
|
|
||||||
"args": ["-release", "-DGITHUB_BUILD"]
|
"args": ["-release", "-DGITHUB_BUILD"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
+689
-237
File diff suppressed because it is too large
Load Diff
+3
-3
@@ -14,8 +14,8 @@ Code Quality is handled by the `vshaxe.haxe-checkstyle` extension, which include
|
|||||||
* `IndentationCharacter` checks what is used to indent, `Indentation` checks how deep the indentation is.
|
* `IndentationCharacter` checks what is used to indent, `Indentation` checks how deep the indentation is.
|
||||||
* `CommentedOutCode` check is in place because old code should be retrieved via Git history.
|
* `CommentedOutCode` check is in place because old code should be retrieved via Git history.
|
||||||
* TODO items: Enable these one-by-one and fix them to improve the overall code quality.
|
* TODO items: Enable these one-by-one and fix them to improve the overall code quality.
|
||||||
- Reconfigure `MethodLength`
|
- Re-configure `MethodLength`
|
||||||
- Reconfigure `CyclomaticComplexity`
|
- Re-configure `CyclomaticComplexity`
|
||||||
- Re-enable `MagicNumber`
|
- Re-enable `MagicNumber`
|
||||||
- Re-configure `NestedControlFlow`
|
- Re-configure `NestedControlFlow`
|
||||||
- Re-configure `NestedIfDepth`
|
- Re-configure `NestedIfDepth`
|
||||||
@@ -28,5 +28,5 @@ Prettier automatically handles formatting of JSON files, and can be configured i
|
|||||||
|
|
||||||
### Prettier Notes
|
### Prettier Notes
|
||||||
* Prettier will automatically attempt to place expressions on a single line if they fit, but will keep them multi-line if they are manually made multi-line.
|
* Prettier will automatically attempt to place expressions on a single line if they fit, but will keep them multi-line if they are manually made multi-line.
|
||||||
* This means that long singleline objects are automatically expanded, and short multiline objects aren't automatically collapsed.
|
* This means that long single-line objects are automatically expanded, and short multi-line objects aren't automatically collapsed.
|
||||||
* You may want to use regex replacement to manually remove the first newline in short multi-line objects to convince Prettier to collapse them.
|
* You may want to use regex replacement to manually remove the first newline in short multi-line objects to convince Prettier to collapse them.
|
||||||
|
|||||||
+2
-1
@@ -8,7 +8,7 @@ All Rights Reserved. "Friday Night Funkin'" and the "Friday Night Funkin'" logo
|
|||||||
You can view the `funkin-assets` license here: (https://github.com/FunkinCrew/funkin.assets/blob/main/LICENSE.md)
|
You can view the `funkin-assets` license here: (https://github.com/FunkinCrew/funkin.assets/blob/main/LICENSE.md)
|
||||||
|
|
||||||
## Apache 2.0 License
|
## Apache 2.0 License
|
||||||
|
```
|
||||||
Apache License
|
Apache License
|
||||||
Version 2.0, January 2004
|
Version 2.0, January 2004
|
||||||
http://www.apache.org/licenses/
|
http://www.apache.org/licenses/
|
||||||
@@ -210,3 +210,4 @@ You can view the `funkin-assets` license here: (https://github.com/FunkinCrew/fu
|
|||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
```
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ This game was made with love to Newgrounds and its community. Extra love to Tom
|
|||||||
|
|
||||||
**PLEASE USE THE LINKS ABOVE IF YOU JUST WANT TO PLAY THE GAME**
|
**PLEASE USE THE LINKS ABOVE IF YOU JUST WANT TO PLAY THE GAME**
|
||||||
|
|
||||||
To learn how to install the necessary dependencies and compile the game from source, please check out our [building the game](/docs/COMPILING.md) guide.
|
To learn how to install the necessary dependencies and compile the game from source, please follow our [Compiling Guide](/docs/COMPILING.md).
|
||||||
|
|
||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
Please check out our [Contributor's guide](./CONTRIBUTORS.md) on how you can actively participate in the development of Friday Night Funkin'.
|
Check out our [Contributing Guide](/docs/CONTRIBUTING.md) to learn how you can actively contribute to the development of Friday Night Funkin'!
|
||||||
|
|
||||||
# Modding
|
# Modding
|
||||||
|
|
||||||
|
|||||||
+1
-1
Submodule art updated: fbd3e3df77...8402339e2e
+1
-1
Submodule assets updated: 4abd6cc06e...c108a7ff0d
+15
-13
@@ -1,8 +1,8 @@
|
|||||||
FROM ubuntu:mantic
|
FROM ubuntu:noble
|
||||||
|
|
||||||
ARG haxe_version=4.3.4
|
ARG haxe_version=4.3.6
|
||||||
ARG haxelib_version=4.1.0
|
ARG haxelib_version=4.1.0
|
||||||
ARG neko_version=2.3.0
|
ARG neko_version=2.4.0
|
||||||
|
|
||||||
# prepare runner
|
# prepare runner
|
||||||
ENV GITHUB_HOME="/github/home"
|
ENV GITHUB_HOME="/github/home"
|
||||||
@@ -98,11 +98,12 @@ EOF
|
|||||||
# neko
|
# neko
|
||||||
# https://github.com/HaxeFoundation/neko/releases/download/v2-3-0/neko-2.3.0-linux64.tar.gz
|
# https://github.com/HaxeFoundation/neko/releases/download/v2-3-0/neko-2.3.0-linux64.tar.gz
|
||||||
RUN <<EOF
|
RUN <<EOF
|
||||||
neko_url=$(curl https://api.github.com/repos/HaxeFoundation/neko/releases -sfL \
|
#neko_url=$(curl https://api.github.com/repos/HaxeFoundation/neko/releases -fL \
|
||||||
| jq '.[] | select(.name == "'"$neko_version"'")' \
|
# | jq '.[] | select(.name == "'"$neko_version"'")' \
|
||||||
| jq '.assets[] | select(.name | endswith("linux64.tar.gz"))' \
|
# | jq '.assets[] | select(.name | endswith("linux64.tar.gz"))' \
|
||||||
| jq -r '.browser_download_url')
|
# | jq -r '.browser_download_url')
|
||||||
curl -sfL "$neko_url" | tar -xz -C /usr/local
|
neko_url="https://geo.thei.rs/funkin/neko-2.4.1-linux64.tar.gz"
|
||||||
|
curl -fL "$neko_url" | tar -xz -C /usr/local
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
RUN <<EOF
|
RUN <<EOF
|
||||||
@@ -117,11 +118,12 @@ ENV PATH="$NEKOPATH:$PATH"
|
|||||||
# haxe
|
# haxe
|
||||||
# https://github.com/HaxeFoundation/haxe/releases/download/4.0.5/haxe-4.0.5-linux64.tar.gz
|
# https://github.com/HaxeFoundation/haxe/releases/download/4.0.5/haxe-4.0.5-linux64.tar.gz
|
||||||
RUN <<EOF
|
RUN <<EOF
|
||||||
haxe_url=$(curl https://api.github.com/repos/HaxeFoundation/haxe/releases -sfL \
|
#haxe_url=$(curl https://api.github.com/repos/HaxeFoundation/haxe/releases -fL \
|
||||||
| jq '.[] | select(.name == "'"$haxe_version"'")' \
|
# | jq '.[] | select(.name == "'"$haxe_version"'")' \
|
||||||
| jq '.assets[] | select(.name | endswith("linux64.tar.gz"))' \
|
# | jq '.assets[] | select(.name | endswith("linux64.tar.gz"))' \
|
||||||
| jq -r '.browser_download_url')
|
# | jq -r '.browser_download_url')
|
||||||
curl -sfL "$haxe_url" | tar -xz -C /usr/local
|
haxe_url="https://geo.thei.rs/funkin/haxe-4.3.6-linux64.tar.gz"
|
||||||
|
curl -fL "$haxe_url" | tar -xz -C /usr/local
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
RUN <<EOF
|
RUN <<EOF
|
||||||
|
|||||||
+1
-1
@@ -347,7 +347,7 @@
|
|||||||
{
|
{
|
||||||
"props": {
|
"props": {
|
||||||
"allowSingleLineStatement": true,
|
"allowSingleLineStatement": true,
|
||||||
"tokens": ["FOR", "IF", "ELSE_IF", "WHILE", "DO_WHILE"]
|
"tokens": ["FOR", "ELSE_IF", "WHILE", "DO_WHILE"]
|
||||||
},
|
},
|
||||||
"type": "NeedBraces"
|
"type": "NeedBraces"
|
||||||
},
|
},
|
||||||
|
|||||||
+7
-4
@@ -13,17 +13,20 @@
|
|||||||
- 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.
|
- 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
|
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
|
6. Run `hmm install` to install all haxelibs of the current branch
|
||||||
7. Run `haxelib run lime setup` to set up lime
|
- If you have issues installing Lime, try utilizing Funkin's patched libraries for hmm and haxelib: `haxelib --global git haxelib https://github.com/FunkinCrew/haxelib.git` and `haxelib --global git hmm https://github.com/FunkinCrew/hmm.git`
|
||||||
8. Perform additional platform setup
|
8. Run `haxelib run lime setup` to set up Lime
|
||||||
|
9. Perform additional platform setup
|
||||||
- For Windows, download the [Visual Studio Build Tools](https://aka.ms/vs/17/release/vs_BuildTools.exe)
|
- 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:
|
- When prompted, select "Individual Components" and make sure to download the following:
|
||||||
- MSVC v143 VS 2022 C++ x64/x86 build tools
|
- MSVC v143 VS 2022 C++ x64/x86 build tools
|
||||||
- Windows 10/11 SDK
|
- Windows 10/11 SDK
|
||||||
- Mac: [`lime setup mac` Documentation](https://lime.openfl.org/docs/advanced-setup/macos/)
|
- 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/)
|
- Linux: [`lime setup linux` Documentation](https://lime.openfl.org/docs/advanced-setup/linux/)
|
||||||
|
- One of Funkin's dependencies uses libVLC, which requires you to install some development packages to be able to compile.
|
||||||
|
Command for Ubuntu/Debian based systems: `sudo apt install libvlc-dev libvlccore-dev libvlccore9`
|
||||||
- HTML5: Compiles without any extra setup
|
- HTML5: Compiles without any extra setup
|
||||||
9. If you are targeting for native, you may need to run `lime rebuild <PLATFORM>` and `lime rebuild <PLATFORM> -debug`
|
10. 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`)
|
11. `lime test <PLATFORM>` to build and launch the game for your platform (for example, `lime test windows`)
|
||||||
|
|
||||||
## Build Flags
|
## Build Flags
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# Mac Compiling Guide + Considerations
|
||||||
|
|
||||||
|
There's a few extra considerations when compiling FNF for Mac that *we* have to handle when creating a wider release.
|
||||||
|
- [Creating a Universal Binary](#creating-a-universal-binary)
|
||||||
|
- Code-signing
|
||||||
|
- Notarizing
|
||||||
|
|
||||||
|
## Creating a Universal Binary
|
||||||
|
|
||||||
|
Run the `art/macos-universal.sh` script, which automatically compiles release versions of both arm64 and x86 of Funkin. You can also see there for reference of how it's done.
|
||||||
@@ -0,0 +1,421 @@
|
|||||||
|
# Contributing
|
||||||
|
Welcome to the Contributing Guide!
|
||||||
|
You can contribute to the Funkin' repository by opening issues or pull requests.
|
||||||
|
|
||||||
|
This guide will cover best practices for each type of contribution.
|
||||||
|
|
||||||
|
# Table of Contents
|
||||||
|
<details open>
|
||||||
|
<summary><b>Contents</b></summary>
|
||||||
|
|
||||||
|
[Part 1: Etiquette](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#part-1-etiquette)
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><a href="https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#part-2-issues">Part 2: Issues</a></summary>
|
||||||
|
|
||||||
|
* [Requirements](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#requirements)
|
||||||
|
|
||||||
|
* [Rejected Features](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#rejected-features)
|
||||||
|
|
||||||
|
* [Issue Types](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#issue-types)
|
||||||
|
|
||||||
|
* [Before You Submit...](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#before-you-submit)
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
|
||||||
|
<summary><a href="https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#part-3-pull-requests">Part 3: Pull Requests</a></summary>
|
||||||
|
|
||||||
|
* [Choosing a base branch](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#choosing-a-base-branch)
|
||||||
|
|
||||||
|
* [Merge conflicts and rebasing](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#merge-conflicts-and-rebasing)
|
||||||
|
|
||||||
|
* [Code PRs](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#code-prs)
|
||||||
|
|
||||||
|
* [Documentation PRs](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#documentation-prs)
|
||||||
|
|
||||||
|
* [GitHub PRs](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#github-prs)
|
||||||
|
|
||||||
|
* [funkin.assets PRs](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#funkinassets-prs)
|
||||||
|
|
||||||
|
* [Charting PRs](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#charting-prs)
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
[Closing](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#closing)
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
# Part 1: Etiquette
|
||||||
|
- Be respectful to one another. We're here to help each other out!
|
||||||
|
- Keep your titles clear and concise. Do not type the whole description into the title.
|
||||||
|
- Do not spam by creating unnecessary issues and pull requests.
|
||||||
|
- Use common sense!
|
||||||
|
|
||||||
|
# Part 2: Issues
|
||||||
|
Issues serve many purposes, from reporting bugs to suggesting new features.
|
||||||
|
This section provides guidelines to follow when [opening an issue](https://github.com/FunkinCrew/Funkin/issues).
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
Make sure you're playing:
|
||||||
|
- the latest version of the game (currently v0.6.3)
|
||||||
|
- without any mods
|
||||||
|
- on [Newgrounds](https://www.newgrounds.com/portal/view/770371) or downloaded from [itch.io](https://ninja-muffin24.itch.io/funkin)
|
||||||
|
|
||||||
|
## Rejected Features
|
||||||
|
If you want to **suggest a feature**, make sure it hasn't already been rejected.
|
||||||
|
Here's a list of commonly suggested features and the reasons why they won't be added:
|
||||||
|
|
||||||
|
| Feature | Reason |
|
||||||
|
| ------- | ------- |
|
||||||
|
| Combo Break + Accuracy Displays | https://github.com/FunkinCrew/Funkin/pull/2681#issuecomment-2156308982 |
|
||||||
|
| Toggleable Ghost Tapping | https://github.com/FunkinCrew/Funkin/pull/2564#issuecomment-2119701802 |
|
||||||
|
| Perfectly Centered Strumlines | _same as above^_ |
|
||||||
|
| MultiKey, 9k, More than 4 keys, etc. | https://github.com/FunkinCrew/Funkin/issues/4243#issuecomment-2692371969 |
|
||||||
|
| Flashy Combo Milestones | https://github.com/FunkinCrew/Funkin/pull/4700#issuecomment-2798916579 |
|
||||||
|
| Losing Icons for DD and Parents | https://github.com/FunkinCrew/Funkin/issues/3048#issuecomment-2243491536 |
|
||||||
|
| Playable GF / Speaker BF / Speaker Pico | https://github.com/FunkinCrew/Funkin/issues/2953#issuecomment-2216985230 |
|
||||||
|
| Fresh (Chill Mix) as Title Screen Music | https://github.com/FunkinCrew/Funkin/pull/4282#issuecomment-2709334718 |
|
||||||
|
| Adjusted Difficulty Ratings | https://github.com/FunkinCrew/Funkin/issues/2781#issuecomment-2172053144 |
|
||||||
|
| Difficulty Ratings above 20 | https://github.com/FunkinCrew/Funkin/issues/3075#issuecomment-2368984497 |
|
||||||
|
| Ability to Reset a Song's Score | https://github.com/FunkinCrew/Funkin/issues/3916#issuecomment-2525408261 |
|
||||||
|
| Quick Restart Keybind (not R) | https://github.com/FunkinCrew/Funkin/issues/3268#issuecomment-2351095232 |
|
||||||
|
| Countdown after Unpausing Song | https://github.com/FunkinCrew/Funkin/issues/2721#issuecomment-2159330106 |
|
||||||
|
| 4:3 Aspect Ratio for Week 6 | https://github.com/FunkinCrew/Funkin/issues/3840#issuecomment-2689158438 |
|
||||||
|
| "Philly Glow" Effect from Psych Engine | https://github.com/FunkinCrew/Funkin/issues/3788#issuecomment-2688966982 |
|
||||||
|
| Importing Charts from Psych Engine (and other mod content) | https://github.com/FunkinCrew/Funkin/issues/2586#issuecomment-2125733327 |
|
||||||
|
| Backwards Compatibility for Modding | https://github.com/FunkinCrew/Funkin/issues/3949#issuecomment-2608391329 |
|
||||||
|
| Lua Support | https://github.com/FunkinCrew/Funkin/issues/2643#issuecomment-2143718093 |
|
||||||
|
|
||||||
|
|
||||||
|
## Issue Types
|
||||||
|
Choose the issue template that best suits your needs!
|
||||||
|
Here's what each template is designed for:
|
||||||
|
|
||||||
|
### Bug Report ([view list](https://github.com/FunkinCrew/Funkin/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22type%3A%20minor%20bug%22))
|
||||||
|
For minor bugs and general issues with the game. Choose this one if none of the others fit your needs.
|
||||||
|
|
||||||
|
### Crash Report ([view list](https://github.com/FunkinCrew/Funkin/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22type%3A%20major%20bug%22))
|
||||||
|
For crashes and freezes like the [Null Object Reference](https://github.com/FunkinCrew/Funkin/issues/2209) problem from v0.3.0.
|
||||||
|
|
||||||
|
### Charting Issue ([view list](https://github.com/FunkinCrew/Funkin/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22type%3A%20charting%20issue%22))
|
||||||
|
For misplaced notes, wonky camera movements, broken song events, and everything related to the game's charts.
|
||||||
|
|
||||||
|
### Enhancement ([view list](https://github.com/FunkinCrew/Funkin/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22type%3A%20enhancement%22))
|
||||||
|
For suggestions to add new features or improve existing ones. We'd love to hear your ideas!
|
||||||
|
|
||||||
|
### Compiling Help (only after reading the [Troubleshooting Guide](https://github.com/FunkinCrew/Funkin/blob/main/docs/TROUBLESHOOTING.md))
|
||||||
|
For issues with compiling the game. Legacy versions (before v0.3.0) are not supported.
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> If none of the above Issue templates suit your inquiry (e.g. Questions or Coding Help), please [open a discussion](https://github.com/FunkinCrew/Funkin/discussions).
|
||||||
|
|
||||||
|
## Before You Submit...
|
||||||
|
Complete the Issue Checklist at the top of your template!
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> If you do not complete each step of the Issue Checklist, **your issue may be closed.**
|
||||||
|
|
||||||
|
Be sure to use the search bar on the Issues page to check that your issue hasn't already been reported by someone else.
|
||||||
|
Duplicate issues make it harder to keep track of important issues with the game.
|
||||||
|
|
||||||
|
Also only report one issue or enhancement at a time! If you have multiple bug reports or suggestions, split them up into separate submissions so they can be checked off one by one.
|
||||||
|
|
||||||
|
Once you're sure your issue is unique and specific, feel free to submit it.
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> **DO NOT CLOSE YOUR ISSUE FOR ANY REASON!** Your issue will be taken care of by a moderator!
|
||||||
|
|
||||||
|
**Thank you for opening issues!**
|
||||||
|
|
||||||
|
# Part 3: Pull Requests
|
||||||
|
Community members are welcome to contribute their changes by [opening pull requests](https://github.com/FunkinCrew/Funkin/pulls).
|
||||||
|
This section covers guidelines for opening and managing pull requests (PRs).
|
||||||
|
|
||||||
|
## Choosing a base branch
|
||||||
|
When creating a branch in your fork, base your branch on either the `main` or `develop` branch depending on the types of changes you want to make.
|
||||||
|
|
||||||
|
> [!CAUTION]
|
||||||
|
> Avoid using your fork's default branch (`main` in this case) for your PR. This is considered an [anti-pattern](https://jmeridth.com/posts/do-not-issue-pull-requests-from-your-master-branch/) by GitHub themselves!
|
||||||
|
> Instead, make a separate branch for your additions (ex. `docs/fix-typo` or `minor-bugfix`).
|
||||||
|
|
||||||
|
Choose the `main` branch if you modify:
|
||||||
|
- Documentation (`.md` files)
|
||||||
|
- GitHub files (`.yml` files or anything in the `.github` folder)
|
||||||
|
|
||||||
|
Choose the `develop` branch if you modify:
|
||||||
|
- Game code (`.hx` files)
|
||||||
|
- Any other type of file
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> When in doubt, base your branch on the `develop` branch.
|
||||||
|
|
||||||
|
Choosing the right base branch helps keep your commit history clean and avoid merge conflicts.
|
||||||
|
Once you’re satisfied with the changes you’ve made, open a PR and base it on the same branch you previously chose.
|
||||||
|
|
||||||
|
## Maintaining your pull request
|
||||||
|
Keeping your pull request clean and easy to review increases the chance that it will be accepted!
|
||||||
|
|
||||||
|
Our maintenance policy is as follows:
|
||||||
|
- If we require changes to your PR, we will label your PR `status: needs revision`.
|
||||||
|
- We may also leave a comment under your PR specifying what changes you should make.
|
||||||
|
- If you receive a comment, you have 90 days to implement the requested changes.
|
||||||
|
- After this period, your PR will be closed due to inactivity and labeled `status: stale`.
|
||||||
|
- Even after your PR is closed, you may request for us to reopen it. Just be sure to address the issues!
|
||||||
|
|
||||||
|
This policy ensures that PRs awaiting review are up to date and ready to merge.
|
||||||
|
|
||||||
|
### Merge conflicts and rebasing
|
||||||
|
Some game updates introduce significant breaking changes that may create merge conflicts in your PR. To resolve them, you will need to update or rebase your PR.
|
||||||
|
|
||||||
|
Most merge conflicts are small and will only require you to modify a few files to resolve them.
|
||||||
|
However, some changes are so big that your commit history will be a total mess!
|
||||||
|
In this case, you will have to perform a [**rebase**](https://docs.github.com/en/get-started/using-git/about-git-rebase).
|
||||||
|
This process reapplies your changes on top of the updated branch and cleanly resolves the merge conflicts.
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> If your commit history becomes too long, you can use rebase to `squash` your PR's commits into a single commit.
|
||||||
|
|
||||||
|
## Code PRs
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> This guide does not cover compiling. If you have trouble compiling the game, refer to the [Compilation Guide](https://github.com/FunkinCrew/Funkin/blob/main/docs/COMPILING.md).
|
||||||
|
|
||||||
|
Code-based PRs make changes such as **fixing bugs** or **implementing new features** in the game.
|
||||||
|
|
||||||
|
This involves modifying one or several of the repository’s `.hx` files, found within the `source/` folder.
|
||||||
|
|
||||||
|
### Codestyle
|
||||||
|
Before submitting your PR, check that your code follows the [Style Guide](https://github.com/FunkinCrew/Funkin/blob/main/docs/style-guide.md). This keeps your code consistent with the rest of the codebase!
|
||||||
|
|
||||||
|
### Code comments
|
||||||
|
Code comments help others understand your changes, so the way you write them is important!
|
||||||
|
Here are some guidelines for writing comments in your code:
|
||||||
|
- Leave comments only when you believe a piece of code warrants explanation. If a piece of code is self-explanatory, it does not need a comment.
|
||||||
|
- Ensure that your comments provide meaningful insight into the function or purpose of the code.
|
||||||
|
- Write your comments in a clear and concise manner.
|
||||||
|
- Only sign your comments with your name when your changes are complex and may require further explanation.
|
||||||
|
|
||||||
|
### Example code comments
|
||||||
|
#### DO NOT:
|
||||||
|
Below are examples of what you SHOULD NOT do when writing code comments.
|
||||||
|
```haxe
|
||||||
|
/**
|
||||||
|
* jumps around the song
|
||||||
|
* works with bpm changes but skipped notes still hurt
|
||||||
|
* @param sections how many sections to jump, negative = backwards
|
||||||
|
*/
|
||||||
|
function changeSection(sections:Int):Void
|
||||||
|
{
|
||||||
|
// Pause the music, as you probably guessed
|
||||||
|
// FlxG.sound.music.pause();
|
||||||
|
|
||||||
|
// Set the target time in steps, I don’t really get how this works though lol - [GitHub username]
|
||||||
|
var targetTimeSteps:Float = Conductor.instance.currentStepTime + (Conductor.instance.stepsPerMeasure * sections);
|
||||||
|
var targetTimeMs:Float = Conductor.instance.getStepTimeInMs(targetTimeSteps);
|
||||||
|
|
||||||
|
// Don't go back in time to before the song started, that would probably break a lot of things and cause a bunch of problems!
|
||||||
|
targetTimeMs = Math.max(0, targetTimeMs);
|
||||||
|
|
||||||
|
if (FlxG.sound.music != null) // If the music is not null, set the time to the target time
|
||||||
|
{
|
||||||
|
FlxG.sound.music.time = targetTimeMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle skipped notes and events and all that jazz
|
||||||
|
handleSkippedNotes();
|
||||||
|
SongEventRegistry.handleSkippedEvents(songEvents, Conductor.instance.songPosition);
|
||||||
|
// regenNoteData(FlxG.sound.music.time);
|
||||||
|
|
||||||
|
Conductor.instance.update(FlxG.sound?.music?.time ?? 0.0);
|
||||||
|
|
||||||
|
// I hate this function - [GitHub username]
|
||||||
|
resyncVocals();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```haxe
|
||||||
|
// End the song when the music is complete.
|
||||||
|
FlxG.sound.music.onComplete = function() {
|
||||||
|
endSong(skipEndingTransition);
|
||||||
|
};
|
||||||
|
// A negative instrumental offset means the song skips the first few milliseconds of the track.
|
||||||
|
// This just gets added into the startTimestamp behavior so we don't need to do anything extra.
|
||||||
|
FlxG.sound.music.play(true, Math.max(0, startTimestamp - Conductor.instance.combinedOffset));
|
||||||
|
FlxG.sound.music.pitch = playbackRate;
|
||||||
|
|
||||||
|
// Prevent the volume from being wrong.
|
||||||
|
FlxG.sound.music.volume = 1.0;
|
||||||
|
// IF fadetween is not null we cancel it
|
||||||
|
if (FlxG.sound.music.fadeTween != null) FlxG.sound.music.fadeTween.cancel();
|
||||||
|
|
||||||
|
// Play the vocals
|
||||||
|
trace('Playing vocals...');
|
||||||
|
// Add the vocals
|
||||||
|
add(vocals);
|
||||||
|
// Play the vocals for real this time lol
|
||||||
|
vocals.play();
|
||||||
|
// Set the vocals volume
|
||||||
|
vocals.volume = 1.0;
|
||||||
|
// Set the vocals pitch
|
||||||
|
vocals.pitch = playbackRate;
|
||||||
|
// Set the vocals time to the music time
|
||||||
|
vocals.time = FlxG.sound.music.time;
|
||||||
|
// trace('${FlxG.sound.music.time}');
|
||||||
|
// trace('${vocals.time}');
|
||||||
|
// functionThatWasntHereBeforeThisPRorSomethingIdKLOL();
|
||||||
|
/* testsprite = new FlxSprite(0, 0);
|
||||||
|
testsprite.loadGraphic(Paths.image('test'));
|
||||||
|
testsprite.screenCenter();
|
||||||
|
add(testsprite); */
|
||||||
|
|
||||||
|
// Me too [GitHub username] I hate this function it gave me pain and suffering - Girlfriend
|
||||||
|
resyncVocals();
|
||||||
|
```
|
||||||
|
|
||||||
|
```haxe
|
||||||
|
#if FEATURE_DEBUG_FUNCTIONS
|
||||||
|
// PAGEUP: who knows what this does
|
||||||
|
// SHIFT+PAGEUP: There will be dire consequences.
|
||||||
|
if (FlxG.keys.justPressed.PAGEUP) changeSection(FlxG.keys.pressed.SHIFT ? 20 : 2);
|
||||||
|
// PAGEUP: who knows what this does
|
||||||
|
// SHIFT+PAGEUP: There will be dire consequences.
|
||||||
|
if (FlxG.keys.justPressed.PAGEDOWN) changeSection(FlxG.keys.pressed.SHIFT ? -20 : -2);
|
||||||
|
#end
|
||||||
|
```
|
||||||
|
|
||||||
|
#### DO:
|
||||||
|
Below are examples on what you SHOULD do when writing code comments.
|
||||||
|
```haxe
|
||||||
|
/**
|
||||||
|
* Jumps forward or backward a number of sections in the song.
|
||||||
|
* Accounts for BPM changes, does not prevent death from skipped notes.
|
||||||
|
* @param sections The number of sections to jump, negative to go backwards.
|
||||||
|
*/
|
||||||
|
function changeSection(sections:Int):Void
|
||||||
|
{
|
||||||
|
var targetTimeSteps:Float = Conductor.instance.currentStepTime + (Conductor.instance.stepsPerMeasure * sections);
|
||||||
|
var targetTimeMs:Float = Conductor.instance.getStepTimeInMs(targetTimeSteps);
|
||||||
|
|
||||||
|
// Don't go back in time to before the song started.
|
||||||
|
targetTimeMs = Math.max(0, targetTimeMs);
|
||||||
|
|
||||||
|
if (FlxG.sound.music != null)
|
||||||
|
{
|
||||||
|
FlxG.sound.music.time = targetTimeMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSkippedNotes();
|
||||||
|
SongEventRegistry.handleSkippedEvents(songEvents, Conductor.instance.songPosition);
|
||||||
|
|
||||||
|
Conductor.instance.update(FlxG.sound?.music?.time ?? 0.0);
|
||||||
|
|
||||||
|
resyncVocals();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```haxe
|
||||||
|
FlxG.sound.music.onComplete = function() {
|
||||||
|
endSong(skipEndingTransition);
|
||||||
|
};
|
||||||
|
// A negative instrumental offset means the song skips the first few milliseconds of the track.
|
||||||
|
// This just gets added into the startTimestamp behavior so we don't need to do anything extra.
|
||||||
|
FlxG.sound.music.play(true, Math.max(0, startTimestamp - Conductor.instance.combinedOffset));
|
||||||
|
FlxG.sound.music.pitch = playbackRate;
|
||||||
|
|
||||||
|
// Prevent the volume from being wrong.
|
||||||
|
FlxG.sound.music.volume = 1.0;
|
||||||
|
if (FlxG.sound.music.fadeTween != null) FlxG.sound.music.fadeTween.cancel();
|
||||||
|
|
||||||
|
trace('Playing vocals...');
|
||||||
|
add(vocals);
|
||||||
|
vocals.play();
|
||||||
|
vocals.volume = 1.0;
|
||||||
|
vocals.pitch = playbackRate;
|
||||||
|
vocals.time = FlxG.sound.music.time;
|
||||||
|
resyncVocals();
|
||||||
|
```
|
||||||
|
|
||||||
|
```haxe
|
||||||
|
#if FEATURE_DEBUG_FUNCTIONS
|
||||||
|
// PAGEUP: Skip forward two sections.
|
||||||
|
// SHIFT+PAGEUP: Skip forward twenty sections.
|
||||||
|
if (FlxG.keys.justPressed.PAGEUP) changeSection(FlxG.keys.pressed.SHIFT ? 20 : 2);
|
||||||
|
// PAGEDOWN: Skip backward two section. Doesn't replace notes.
|
||||||
|
// SHIFT+PAGEDOWN: Skip backward twenty sections.
|
||||||
|
if (FlxG.keys.justPressed.PAGEDOWN) changeSection(FlxG.keys.pressed.SHIFT ? -20 : -2);
|
||||||
|
#end
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation PRs
|
||||||
|
Documentation-based PRs make changes such as **fixing typos** or **adding new information** in documentation files.
|
||||||
|
|
||||||
|
This involves modifying one or several of the repository’s `.md` files, found throughout the repository.
|
||||||
|
|
||||||
|
Make sure your changes are easy to understand and formatted consistently to maximize clarity and readability.
|
||||||
|
|
||||||
|
> [!CAUTION]
|
||||||
|
> DO NOT TOUCH THE `LICENSE.md` FILE, EVEN TO MAKE SMALL CHANGES!
|
||||||
|
|
||||||
|
### Example Documentation PR
|
||||||
|
#### DO NOT:
|
||||||
|
```
|
||||||
|
// The original documentation
|
||||||
|
- `F2`: ***OVERLAY***: Enables the Flixel debug overlay, which has partial support for scripting.
|
||||||
|
- `F3`: ***SCREENSHOT***: Takes a screenshot of the game and saves it to the local `screenshots` directory. Works outside of debug builds too!
|
||||||
|
- `F4`: ***EJECT***: Forcibly switch state to the Main Menu (with no extra transition). Useful if you're stuck in a level and you need to get out!
|
||||||
|
- `F5`: ***HOT RELOAD***: Forcibly reload the game's scripts and data files, then restart the current state. If any files in the `assets` folder have been modified, the game should process the changes for you! NOTE: Known bug, this does not reset song charts or song scripts, but it should reset everything else (such as stage layout data and character animation data).
|
||||||
|
- `CTRL-SHIFT-L`: ***FORCE CRASH***: Immediately crash the game with a detailed crash log and a stack trace.
|
||||||
|
|
||||||
|
// The new PR additions
|
||||||
|
- `ctrl alt shift e`: No idea what this does
|
||||||
|
- `alt-f4`: closes the game
|
||||||
|
```
|
||||||
|
|
||||||
|
#### DO:
|
||||||
|
```
|
||||||
|
// The original documentation
|
||||||
|
- `F2`: ***OVERLAY***: Enables the Flixel debug overlay, which has partial support for scripting.
|
||||||
|
- `F3`: ***SCREENSHOT***: Takes a screenshot of the game and saves it to the local `screenshots` directory. Works outside of debug builds too!
|
||||||
|
- `F4`: ***EJECT***: Forcibly switch state to the Main Menu (with no extra transition). Useful if you're stuck in a level and you need to get out!
|
||||||
|
- `F5`: ***HOT RELOAD***: Forcibly reload the game's scripts and data files, then restart the current state. If any files in the `assets` folder have been modified, the game should process the changes for you! NOTE: Known bug, this does not reset song charts or song scripts, but it should reset everything else (such as stage layout data and character animation data).
|
||||||
|
- `CTRL-SHIFT-L`: ***FORCE CRASH***: Immediately crash the game with a detailed crash log and a stack trace.
|
||||||
|
|
||||||
|
// The new PR additions
|
||||||
|
- `CTRL-ALT-SHIFT-E`: ***DUMP SAVE DATA***: Prompts the user to save their save data as a JSON file, so its contents can be viewed. Only available on debug builds.
|
||||||
|
- `ALT-F4`: ***CLOSE GAME***: Closes the game forcibly on Windows.
|
||||||
|
```
|
||||||
|
|
||||||
|
## GitHub PRs
|
||||||
|
GitHub-related PRs make changes such as **tweaking Issue Templates** or **updating the repository’s workflows**.
|
||||||
|
|
||||||
|
This involves modifying one or several of the repository’s `.yml` files, or any other file in the `.github` folder.
|
||||||
|
|
||||||
|
Please test these changes on your fork’s main branch to avoid breaking anything in this repository (e.g. GitHub Actions, issue templates, etc.)!
|
||||||
|
|
||||||
|
## funkin.assets PRs
|
||||||
|
The `assets` submodule has its own repository called [funkin.assets](https://github.com/FunkinCrew/funkin.assets).
|
||||||
|
|
||||||
|
If you only modify files in the `assets` folder, open a PR in the `funkin.assets` repository instead of the main repository.
|
||||||
|
|
||||||
|
If you simultaneously modify files from both repositories, then open two separate PRs and explain the connection in your PR descriptions.
|
||||||
|
|
||||||
|
Be sure to choose `main` as the base branch for `funkin.assets` PRs, as no `develop` branch exists for that repository.
|
||||||
|
|
||||||
|
### Charting PRs
|
||||||
|
Charting PRs make changes such as **adjusting chart metadata**.
|
||||||
|
|
||||||
|
This involves modifying one or several of the `funkin.assets` repository's `*-metadata.json` files, found in the `preload/data/songs/` directory.
|
||||||
|
|
||||||
|
> [!CAUTION]
|
||||||
|
> We **do not** accept changes to the chart `.json` file itself.
|
||||||
|
> If you find an error in the game's charts, please open an issue in the `Funkin` repo, and the Funkin' Crew will review it and make tweaks themselves as necessary.
|
||||||
|
|
||||||
|
These PRs should only be opened in the `funkin.assets` repository.
|
||||||
|
|
||||||
|
# Closing
|
||||||
|
Thank you for reading the Contributing Guide.
|
||||||
|
|
||||||
|
We look forward to seeing your contributions to the game!
|
||||||
@@ -3,10 +3,13 @@
|
|||||||
Most of this functionality is only available on debug builds of the game!
|
Most of this functionality is only available on debug builds of the game!
|
||||||
|
|
||||||
## Any State
|
## Any State
|
||||||
|
- `INSERT`: ***MEMORY GARBAGE COLLECTION***: Immediately perform memory garbage collection.
|
||||||
- `F2`: ***OVERLAY***: Enables the Flixel debug overlay, which has partial support for scripting.
|
- `F2`: ***OVERLAY***: Enables the Flixel debug overlay, which has partial support for scripting.
|
||||||
- `F3`: ***SCREENSHOT***: Takes a screenshot of the game and saves it to the local `screenshots` directory. Works outside of debug builds too!
|
- `F3`: ***SCREENSHOT***: Takes a screenshot of the game and saves it to the local `screenshots` directory. Works outside of debug builds too!
|
||||||
- `F4`: ***EJECT***: Forcibly switch state to the Main Menu (with no extra transition). Useful if you're stuck in a level and you need to get out!
|
- `F4`: ***EJECT***: Forcibly switch state to the Main Menu (with no extra transition). Useful if you're stuck in a level and you need to get out!
|
||||||
- `F5`: ***HOT RELOAD***: Forcibly reload the game's scripts and data files, then restart the current state. If any files in the `assets` folder have been modified, the game should process the changes for you! NOTE: Known bug, this does not reset song charts or song scripts, but it should reset everything else (such as stage layout data and character animation data).
|
- `F5`: ***HOT RELOAD***: Forcibly reload the game's scripts and data files, then restart the current state. If any files in the `assets` folder have been modified, the game should process the changes for you! NOTE: Known bug, this does not reset song charts or song scripts, but it should reset everything else (such as stage layout data and character animation data).
|
||||||
|
- `CTRL-ALT-SHIFT-L`: ***FORCE CRASH***: Immediately crash the game with a detailed crash log and a stack trace. (Only works in the Main Menu on debug builds).
|
||||||
|
|
||||||
- `CTRL-SHIFT-L`: ***FORCE CRASH***: Immediately crash the game with a detailed crash log and a stack trace.
|
- `CTRL-SHIFT-L`: ***FORCE CRASH***: Immediately crash the game with a detailed crash log and a stack trace.
|
||||||
|
|
||||||
## **Play State**
|
## **Play State**
|
||||||
@@ -17,15 +20,27 @@ Most of this functionality is only available on debug builds of the game!
|
|||||||
- `9`: NEATO!
|
- `9`: NEATO!
|
||||||
- `PAGEUP` (MacOS: `Fn-Up`): ***FORWARDS TIME TRAVEL***: Move forward by 2 sections. Hold SHIFT to move forward by 20 sections instead.
|
- `PAGEUP` (MacOS: `Fn-Up`): ***FORWARDS TIME TRAVEL***: Move forward by 2 sections. Hold SHIFT to move forward by 20 sections instead.
|
||||||
- `PAGEDOWN` (MacOS: `Fn-Down`): ***BACKWARDS TIME TRAVEL***: Move backward by 2 sections. Hold SHIFT to move backward by 20 sections instead.
|
- `PAGEDOWN` (MacOS: `Fn-Down`): ***BACKWARDS TIME TRAVEL***: Move backward by 2 sections. Hold SHIFT to move backward by 20 sections instead.
|
||||||
|
- `CHART Hotkey`: ***OPEN CHART EDITOR***: Open the Chart Editor with the current song loaded.
|
||||||
|
- `STAGE Hotkey`: ***OPEN STAGE EDITOR***: Open the old Stage Editor overlay.
|
||||||
|
|
||||||
## **Freeplay State**
|
## **Freeplay State**
|
||||||
- `F` (Freeplay Menu) - Move to Favorites
|
- `P`: ***SWITCH TO PICO/BF***: Switch to Pico or BF (reopens Freeplay as the other character).
|
||||||
- `Q` (Freeplay Menu) - Back one category
|
- `T`: ***TEST RANK ANIMATION***: Play the Gold Perfect rank animation on the currently selected song.
|
||||||
- `E` (Freeplay Menu) - Forward one category
|
- `SHIFT-ACCEPT`: ***BOTPLAY MODE***: Start a song with Botplay mode enabled.
|
||||||
|
- `CTRL-Switch Song`: Play the first unlocked alternate instrumental as the song preview.
|
||||||
|
|
||||||
## **Title State**
|
## **Title State**
|
||||||
- `Y` - WOAH
|
- `ESCAPE`: ***QUIT***: Quit the game.
|
||||||
|
- `D`: COOL
|
||||||
|
- `UI LEFT`/`UI RIGHT Hotkey`: 2COOL
|
||||||
|
- `Y`: WOAH
|
||||||
|
|
||||||
## **Main Menu**
|
## **Main Menu**
|
||||||
- `~`: ***DEBUG***: Opens a menu to access the Chart Editor and other work-in-progress editors. Rebindable in the options menu.
|
- `~`: ***DEBUG***: Opens a menu to access the Chart Editor and other work-in-progress editors. Rebindable in the options menu.
|
||||||
|
- `SHIFT-ACCEPT`: ***OPEN FREEPLAY AS PICO***: When selecting Freeplay, open the Freeplay menu with the playable character set to Pico.
|
||||||
- `CTRL-ALT-SHIFT-W`: ***ALL ACCESS***: Unlocks all songs in Freeplay. Only available on debug builds.
|
- `CTRL-ALT-SHIFT-W`: ***ALL ACCESS***: Unlocks all songs in Freeplay. Only available on debug builds.
|
||||||
|
- `CTRL-ALT-SHIFT-M`: ***NO MORE ACCESS***: Re-locks all songs in Freeplay except those unlocked by default. Only available on debug builds.
|
||||||
|
- `CTRL-ALT-SHIFT-R`: ***GREAT SCORE?***: Give the user a hypothetical overridden score, and see if we can maintain that golden P rank. Only available on debug builds.
|
||||||
|
- `CTRL-ALT-SHIFT-P`: ***CHARACTER UNLOCK SCREEN***: Forces the Character Select screen to play Pico's unlocking animation. Only available on debug builds.
|
||||||
|
- `CTRL-ALT-SHIFT-N`: ***CHARACTER NOT SEEN***: Marks all characters as not seen and enables BF's new character unlocked animation in Freeplay. Only available on debug builds.
|
||||||
|
- `CTRL-ALT-SHIFT-E`: ***DUMP SAVE DATA***: Prompts the user to save their save data as a JSON file, so its contents can be viewed. Only available on debug builds.
|
||||||
|
|||||||
+33
-3
@@ -1,11 +1,19 @@
|
|||||||
# Troubleshooting Common Compilation Issues
|
# Troubleshooting Common Compilation Issues
|
||||||
|
|
||||||
|
- Any output containing `WARNING` or `(WDeprecated)`
|
||||||
|
- Will not disrupt compilation and can be safely ignored.
|
||||||
|
|
||||||
|
- `This version of hxcpp` ... `Would you like to do this now [y/n]`
|
||||||
|
- Type "y" into the console and press Enter.
|
||||||
|
|
||||||
- Weird macro error with a very tall call stack: Restart Visual Studio Code
|
- 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.
|
- 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.
|
||||||
|
|
||||||
- `Get Thread Context Failed`: Turn off other expensive applications while building
|
- `Get Thread Context Failed`
|
||||||
|
- Turn off other expensive applications while building.
|
||||||
|
|
||||||
- `Type not found: T1`: This is thrown by `json2object`, make sure the data type of `@:default` is correct.
|
- `Type not found: T1`
|
||||||
|
- This is thrown by `json2object`, make sure the data type of `@:default` is correct.
|
||||||
- NOTE: `flixel.util.typeLimit.OneOfTwo` isn't supported.
|
- NOTE: `flixel.util.typeLimit.OneOfTwo` isn't supported.
|
||||||
|
|
||||||
- `Class lists not properly generated. Try cleaning out your export folder, restarting your IDE, and rebuilding your project.`
|
- `Class lists not properly generated. Try cleaning out your export folder, restarting your IDE, and rebuilding your project.`
|
||||||
@@ -15,9 +23,31 @@
|
|||||||
- 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.
|
- 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)`
|
- `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.
|
- 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.
|
- 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**.
|
- 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**.
|
- Other compilation issues may be caused by installing bad library versions. Try deleting the `.haxelib` folder and following the guide starting from **Step 5**.
|
||||||
|
|
||||||
|
## Lime Related Issues
|
||||||
|
- Segmentation fault and/or crash after `Done mapping time changes: [SongTimeChange(0ms,102bpm)]`
|
||||||
|
- Caused by using official Lime instead of Funkin's fork. Reinstalling Lime should fix it.
|
||||||
|
(NOTE: Make sure you do this via `hmm` (e.g `hmm reinstall -f lime`) to guarantee you get Funkin's version of Lime.)
|
||||||
|
|
||||||
|
- `Uncaught exception - Could not find lime.ndll.` ... `Advanced users may run "lime rebuild cpp" instead.`
|
||||||
|
- If on Linux:
|
||||||
|
- The binaries GLibC version might more recent than the one your system supports. Running the commands below should fix it.
|
||||||
|
```
|
||||||
|
cd .haxelib/lime/git
|
||||||
|
git submodule init
|
||||||
|
git submodule sync
|
||||||
|
git submodule update
|
||||||
|
cd ../../..
|
||||||
|
# Note: The command and packages here might be different depending on your distro.
|
||||||
|
sudo apt install libgl1-mesa-dev libglu1-mesa-dev g++ g++-multilib gcc-multilib libasound2-dev libx11-dev libxext-dev libxi-dev libxrandr-dev libxinerama-dev libpulse-dev
|
||||||
|
lime rebuild cpp -64 -release -clean
|
||||||
|
```
|
||||||
|
|
||||||
|
- The binaries are missing for some reason. You can download pre-built binaries from [Funkin's Lime](https://github.com/FunkinCrew/lime/tree/dev-funkin/ndll).
|
||||||
|
You should copy them to `.haxelib/lime/git/ndll/<PLATFORM>64/`, where `<PLATFORM>` is the current platform you're on, which can be either Windows, Linux or MacOS.
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ This short document is designed to give a run-down on how code should be formatt
|
|||||||
|
|
||||||
## Notes on IDEs and Extensions
|
## Notes on IDEs and Extensions
|
||||||
|
|
||||||
The Visual Studio Code IDE is highly recommended, as this repo contains various configuration that works with VSCode extensions to automatically style certain things for you.
|
The Visual Studio Code IDE is highly recommended, as this repo contains various configurations that work with VSCode extensions to automatically style certain things for you.
|
||||||
|
|
||||||
VSCode is also the only IDE that has any good tools for Haxe so yeah.
|
VSCode is also the only IDE that has any good tools for Haxe so yeah.
|
||||||
|
|
||||||
|
|||||||
@@ -4,21 +4,21 @@
|
|||||||
"name": "FlxPartialSound",
|
"name": "FlxPartialSound",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "a1eab7b9bf507b87200a3341719054fe427f3b15",
|
"ref": "41f35ddb1eb9d10bc742e6f8b5bcc62f9ef8ad84",
|
||||||
"url": "https://github.com/FunkinCrew/FlxPartialSound.git"
|
"url": "https://github.com/FunkinCrew/FlxPartialSound.git"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "flixel",
|
"name": "flixel",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "f2b090d6c608471e730b051c8ee22b8b378964b1",
|
"ref": "fffb1a74cf08f63dacc2ab09976340563f5b6e6d",
|
||||||
"url": "https://github.com/FunkinCrew/flixel"
|
"url": "https://github.com/FunkinCrew/flixel"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "flixel-addons",
|
"name": "flixel-addons",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "7424db4e9164ff46f224a7c47de1b732d2542dd7",
|
"ref": "b9118f47f43a66bc0e5fbfcfd9903f0425e918ee",
|
||||||
"url": "https://github.com/FunkinCrew/flixel-addons"
|
"url": "https://github.com/FunkinCrew/flixel-addons"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -32,8 +32,8 @@
|
|||||||
"name": "flxanimate",
|
"name": "flxanimate",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "0654797e5eb7cd7de0c1b2dbaa1efe5a1e1d9412",
|
"ref": "f8842cea9883d6112a2c854cf93fa72856171428",
|
||||||
"url": "https://github.com/Dot-Stuff/flxanimate"
|
"url": "https://github.com/FunkinCrew/flxanimate"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "format",
|
"name": "format",
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
"name": "funkin.vis",
|
"name": "funkin.vis",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "22b1ce089dd924f15cdc4632397ef3504d464e90",
|
"ref": "1966f8fbbbc509ed90d4b520f3c49c084fc92fd6",
|
||||||
"url": "https://github.com/FunkinCrew/funkVis"
|
"url": "https://github.com/FunkinCrew/funkVis"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -63,36 +63,29 @@
|
|||||||
"name": "haxeui-core",
|
"name": "haxeui-core",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "51c23588614397089a5ce182cddea729f0be6fa0",
|
"ref": "07fc7aa8098deaea633b0726d01f83eb4ef8a832",
|
||||||
"url": "https://github.com/haxeui/haxeui-core"
|
"url": "https://github.com/haxeui/haxeui-core"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "haxeui-flixel",
|
"name": "haxeui-flixel",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "da27e833947f32ef007ed11f523aa5524f5a5d54",
|
"ref": "b899a4c7d7318c5ff2b1bb645fbc73728fad1ac9",
|
||||||
"url": "https://github.com/haxeui/haxeui-flixel"
|
"url": "https://github.com/haxeui/haxeui-flixel"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "hscript",
|
"name": "hscript",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "12785398e2f07082f05034cb580682e5671442a2",
|
"ref": "27c86f9a761c1d16d4433c4cf252eccb7b2e18de",
|
||||||
"url": "https://github.com/FunkinCrew/hscript"
|
"url": "https://github.com/FunkinCrew/hscript"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "hxCodec",
|
|
||||||
"type": "git",
|
|
||||||
"dir": null,
|
|
||||||
"ref": "61b98a7a353b7f529a8fec84ed9afc919a2dffdd",
|
|
||||||
"url": "https://github.com/FunkinCrew/hxCodec"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "hxcpp",
|
"name": "hxcpp",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "c6bac3d6c7d683f25104296b2f4c50f8c90b8349",
|
"ref": "v4.3.75",
|
||||||
"url": "https://github.com/cortex-engine/hxcpp"
|
"url": "https://github.com/HaxeFoundation/hxcpp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "hxcpp-debug-server",
|
"name": "hxcpp-debug-server",
|
||||||
@@ -120,6 +113,11 @@
|
|||||||
"type": "haxelib",
|
"type": "haxelib",
|
||||||
"version": "1.3.0"
|
"version": "1.3.0"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "hxvlc",
|
||||||
|
"type": "haxelib",
|
||||||
|
"version": "2.1.2"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "json2object",
|
"name": "json2object",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -145,7 +143,7 @@
|
|||||||
"name": "lime",
|
"name": "lime",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "fe3368f611a84a19afc03011353945ae4da8fffd",
|
"ref": "be81ad7e4e1a92c3482bcc009648a4ac892cfa35",
|
||||||
"url": "https://github.com/FunkinCrew/lime"
|
"url": "https://github.com/FunkinCrew/lime"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -176,11 +174,18 @@
|
|||||||
"ref": "f61be7f7ba796595f45023ca65164a485aba0e7e",
|
"ref": "f61be7f7ba796595f45023ca65164a485aba0e7e",
|
||||||
"url": "https://github.com/FunkinCrew/MassiveUnit"
|
"url": "https://github.com/FunkinCrew/MassiveUnit"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "newgrounds",
|
||||||
|
"type": "git",
|
||||||
|
"dir": null,
|
||||||
|
"ref": "c8b30832027fa04f48fe6a45fa7903d5b265d56e",
|
||||||
|
"url": "https://github.com/Geokureli/Newgrounds/"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "openfl",
|
"name": "openfl",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "8306425c497766739510ab29e876059c96f77bd2",
|
"ref": "d061c936b462f040304ec2bd42d9f59d2e59e285",
|
||||||
"url": "https://github.com/FunkinCrew/openfl"
|
"url": "https://github.com/FunkinCrew/openfl"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
+63
-20
@@ -25,7 +25,7 @@ class Project extends HXProject {
|
|||||||
* REMEMBER TO CHANGE THIS WHEN THE GAME UPDATES!
|
* REMEMBER TO CHANGE THIS WHEN THE GAME UPDATES!
|
||||||
* You only have to change it here, the rest of the game will query this value.
|
* You only have to change it here, the rest of the game will query this value.
|
||||||
*/
|
*/
|
||||||
static final VERSION:String = "0.5.2";
|
static final VERSION:String = "0.6.4";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The game's name. Used as the default window title.
|
* The game's name. Used as the default window title.
|
||||||
@@ -178,6 +178,33 @@ class Project extends HXProject {
|
|||||||
*/
|
*/
|
||||||
static final FEATURE_NEWGROUNDS:FeatureFlag = "FEATURE_NEWGROUNDS";
|
static final FEATURE_NEWGROUNDS:FeatureFlag = "FEATURE_NEWGROUNDS";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `-DFEATURE_NEWGROUNDS_DEBUG`
|
||||||
|
* If this flag is enabled, the game will enable Newgrounds.io's debug functions.
|
||||||
|
* This provides additional information in requests, as well as "faking" medal and leaderboard submissions.
|
||||||
|
*/
|
||||||
|
static final FEATURE_NEWGROUNDS_DEBUG:FeatureFlag = "FEATURE_NEWGROUNDS_DEBUG";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `-DFEATURE_NEWGROUNDS_AUTOLOGIN`
|
||||||
|
* If this flag is enabled, the game will attempt to automatically login to Newgrounds on startup.
|
||||||
|
*/
|
||||||
|
static final FEATURE_NEWGROUNDS_AUTOLOGIN:FeatureFlag = "FEATURE_NEWGROUNDS_AUTOLOGIN";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `-DFEATURE_NEWGROUNDS_TESTING_MEDALS`
|
||||||
|
* If this flag is enabled, use the medal IDs from the debug test bench.
|
||||||
|
* If disabled, use the actual medal IDs from the release project on Newgrounds.
|
||||||
|
*/
|
||||||
|
static final FEATURE_NEWGROUNDS_TESTING_MEDALS:FeatureFlag = "FEATURE_NEWGROUNDS_TESTING_MEDALS";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `-DFEATURE_NEWGROUNDS_EVENTS`
|
||||||
|
* If this flag is enabled, the game will attempt to send events to Newgrounds when the user does stuff.
|
||||||
|
* This lets us see cool anonymized stats! It only works if the user is logged in.
|
||||||
|
*/
|
||||||
|
static final FEATURE_NEWGROUNDS_EVENTS:FeatureFlag = "FEATURE_NEWGROUNDS_EVENTS";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `-DFEATURE_FUNKVIS`
|
* `-DFEATURE_FUNKVIS`
|
||||||
* If this flag is enabled, the game will enable the Funkin Visualizer library.
|
* If this flag is enabled, the game will enable the Funkin Visualizer library.
|
||||||
@@ -197,7 +224,7 @@ class Project extends HXProject {
|
|||||||
/**
|
/**
|
||||||
* `-DFEATURE_VIDEO_PLAYBACK`
|
* `-DFEATURE_VIDEO_PLAYBACK`
|
||||||
* If this flag is enabled, the game will enable support for video playback.
|
* If this flag is enabled, the game will enable support for video playback.
|
||||||
* This requires the hxCodec library on desktop platforms.
|
* This requires the hxvlc library on desktop platforms.
|
||||||
*/
|
*/
|
||||||
static final FEATURE_VIDEO_PLAYBACK:FeatureFlag = "FEATURE_VIDEO_PLAYBACK";
|
static final FEATURE_VIDEO_PLAYBACK:FeatureFlag = "FEATURE_VIDEO_PLAYBACK";
|
||||||
|
|
||||||
@@ -232,6 +259,12 @@ class Project extends HXProject {
|
|||||||
*/
|
*/
|
||||||
static final FEATURE_STAGE_EDITOR:FeatureFlag = "FEATURE_STAGE_EDITOR";
|
static final FEATURE_STAGE_EDITOR:FeatureFlag = "FEATURE_STAGE_EDITOR";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `-DFEATURE_RESULTS_DEBUG
|
||||||
|
* If this flag is enabled, a debug menu for Results screen will be accessible from the debug menu.
|
||||||
|
*/
|
||||||
|
static final FEATURE_RESULTS_DEBUG:FeatureFlag = "FEATURE_RESULTS_DEBUG";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `-DFEATURE_POLYMOD_MODS`
|
* `-DFEATURE_POLYMOD_MODS`
|
||||||
* If this flag is enabled, the game will enable the Polymod library's support for atomic mod loading from the `./mods` folder.
|
* If this flag is enabled, the game will enable the Polymod library's support for atomic mod loading from the `./mods` folder.
|
||||||
@@ -330,6 +363,8 @@ class Project extends HXProject {
|
|||||||
|
|
||||||
this.window.hardware = true;
|
this.window.hardware = true;
|
||||||
this.window.vsync = false;
|
this.window.vsync = false;
|
||||||
|
// force / allow high DPI
|
||||||
|
this.window.allowHighDPI = true;
|
||||||
|
|
||||||
if (isWeb()) {
|
if (isWeb()) {
|
||||||
this.window.resizable = true;
|
this.window.resizable = true;
|
||||||
@@ -460,8 +495,6 @@ class Project extends HXProject {
|
|||||||
|
|
||||||
// Should be false unless explicitly requested.
|
// Should be false unless explicitly requested.
|
||||||
GITHUB_BUILD.apply(this, false);
|
GITHUB_BUILD.apply(this, false);
|
||||||
FEATURE_STAGE_EDITOR.apply(this, false);
|
|
||||||
FEATURE_NEWGROUNDS.apply(this, false);
|
|
||||||
FEATURE_GHOST_TAPPING.apply(this, false);
|
FEATURE_GHOST_TAPPING.apply(this, false);
|
||||||
|
|
||||||
// Should be true unless explicitly requested.
|
// Should be true unless explicitly requested.
|
||||||
@@ -471,10 +504,21 @@ class Project extends HXProject {
|
|||||||
FEATURE_FUNKVIS.apply(this, true);
|
FEATURE_FUNKVIS.apply(this, true);
|
||||||
FEATURE_PARTIAL_SOUNDS.apply(this, true);
|
FEATURE_PARTIAL_SOUNDS.apply(this, true);
|
||||||
FEATURE_VIDEO_PLAYBACK.apply(this, true);
|
FEATURE_VIDEO_PLAYBACK.apply(this, true);
|
||||||
|
FEATURE_STAGE_EDITOR.apply(this, true);
|
||||||
|
|
||||||
// Should be true on debug builds or if GITHUB_BUILD is enabled.
|
// Should be true on debug builds or if GITHUB_BUILD is enabled.
|
||||||
FEATURE_DEBUG_FUNCTIONS.apply(this, isDebug() || GITHUB_BUILD.isEnabled(this));
|
FEATURE_DEBUG_FUNCTIONS.apply(this, isDebug() || GITHUB_BUILD.isEnabled(this));
|
||||||
FEATURE_LOG_TRACE.apply(this, isDebug());
|
FEATURE_RESULTS_DEBUG.apply(this, isDebug() || GITHUB_BUILD.isEnabled(this));
|
||||||
|
|
||||||
|
// Got a lot of complains about this being turned off by default on some builds.
|
||||||
|
// TODO: Look into ways to optimize logging (maybe by using a thread pool?)
|
||||||
|
FEATURE_LOG_TRACE.apply(this, true);
|
||||||
|
|
||||||
|
FEATURE_NEWGROUNDS.apply(this, true);
|
||||||
|
FEATURE_NEWGROUNDS_DEBUG.apply(this, false);
|
||||||
|
FEATURE_NEWGROUNDS_TESTING_MEDALS.apply(this, FEATURE_NEWGROUNDS.isEnabled(this) && FEATURE_DEBUG_FUNCTIONS.isEnabled(this));
|
||||||
|
FEATURE_NEWGROUNDS_AUTOLOGIN.apply(this, FEATURE_NEWGROUNDS.isEnabled(this) && isWeb());
|
||||||
|
FEATURE_NEWGROUNDS_EVENTS.apply(this, FEATURE_NEWGROUNDS.isEnabled(this));
|
||||||
|
|
||||||
// Should default to true on workspace builds and false on release builds.
|
// Should default to true on workspace builds and false on release builds.
|
||||||
REDIRECT_ASSETS_FOLDER.apply(this, isDebug() && isDesktop());
|
REDIRECT_ASSETS_FOLDER.apply(this, isDebug() && isDesktop());
|
||||||
@@ -576,7 +620,7 @@ class Project extends HXProject {
|
|||||||
|
|
||||||
// Ensure all Flixel classes are available at runtime.
|
// Ensure all Flixel classes are available at runtime.
|
||||||
// Explicitly ignore packages which require additional dependencies.
|
// Explicitly ignore packages which require additional dependencies.
|
||||||
addHaxeMacro("include('flixel', true, [ 'flixel.addons.editors.spine.*', 'flixel.addons.nape.*', 'flixel.system.macros.*' ])");
|
addHaxeMacro("include('flixel', true, [ 'flixel.addons.editors.spine.*', 'flixel.addons.nape.*', 'flixel.system.macros.*', 'flixel.addons.tile.FlxRayCastTilemap' ])");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -590,7 +634,15 @@ class Project extends HXProject {
|
|||||||
function configureOutputDir() {
|
function configureOutputDir() {
|
||||||
// Set the output directory. Depends on the target platform and build type.
|
// Set the output directory. Depends on the target platform and build type.
|
||||||
|
|
||||||
var buildDir = 'export/${isDebug() ? 'debug' : 'release'}/';
|
var buildDir = 'export/${isDebug() ? 'debug' : 'release'}';
|
||||||
|
|
||||||
|
// we use a dedicated 'tracy' folder, since it generally needs a recompile when in use
|
||||||
|
if (FEATURE_DEBUG_TRACY.isEnabled(this))
|
||||||
|
buildDir += "-tracy";
|
||||||
|
|
||||||
|
// trailing slash might not be needed, works fine on macOS without it, but I haven't tested on Windows!
|
||||||
|
buildDir += "/";
|
||||||
|
|
||||||
|
|
||||||
info('Output directory: $buildDir');
|
info('Output directory: $buildDir');
|
||||||
// setenv('BUILD_DIR', buildDir);
|
// setenv('BUILD_DIR', buildDir);
|
||||||
@@ -647,9 +699,9 @@ class Project extends HXProject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isDesktop() && !isHashLink() && FEATURE_VIDEO_PLAYBACK.isEnabled(this)) {
|
if (isDesktop() && !isHashLink() && FEATURE_VIDEO_PLAYBACK.isEnabled(this)) {
|
||||||
// hxCodec doesn't function on HashLink or non-desktop platforms
|
// hxvlc doesn't function on HashLink or non-desktop platforms
|
||||||
// It's also unnecessary if video playback is disabled
|
// It's also unnecessary if video playback is disabled
|
||||||
addHaxelib('hxCodec'); // Video playback
|
addHaxelib('hxvlc'); // Video playback
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FEATURE_DISCORD_RPC.isEnabled(this)) {
|
if (FEATURE_DISCORD_RPC.isEnabled(this)) {
|
||||||
@@ -721,13 +773,12 @@ class Project extends HXProject {
|
|||||||
addAssetLibrary("weekend1", shouldEmbed, shouldPreload);
|
addAssetLibrary("weekend1", shouldEmbed, shouldPreload);
|
||||||
addAssetPath("assets/weekend1", "assets/weekend1", "weekend1", ["*"], exclude, shouldEmbed);
|
addAssetPath("assets/weekend1", "assets/weekend1", "weekend1", ["*"], exclude, shouldEmbed);
|
||||||
|
|
||||||
// Art asset library (where README and CHANGELOG pull from)
|
// Art asset library (where README pulls from)
|
||||||
var shouldEmbedArt = false;
|
var shouldEmbedArt = false;
|
||||||
var shouldPreloadArt = false;
|
var shouldPreloadArt = false;
|
||||||
addAssetLibrary("art", shouldEmbedArt, shouldPreloadArt);
|
addAssetLibrary("art", shouldEmbedArt, shouldPreloadArt);
|
||||||
addAsset("art/readme.txt", "do NOT readme.txt", "art", shouldEmbedArt);
|
addAsset("art/readme.txt", "do NOT readme.txt", "art", shouldEmbedArt);
|
||||||
addAsset("LICENSE.md", "LICENSE.md", "art", shouldEmbedArt);
|
addAsset("LICENSE.md", "LICENSE.md", "art", shouldEmbedArt);
|
||||||
addAsset("CHANGELOG.md", "CHANGELOG.md", "art", shouldEmbedArt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1038,15 +1089,7 @@ class Project extends HXProject {
|
|||||||
* Display an info message. This should not interfere with the build process.
|
* Display an info message. This should not interfere with the build process.
|
||||||
*/
|
*/
|
||||||
public function info(message:String):Void {
|
public function info(message:String):Void {
|
||||||
// CURSED: We have to disable info() log calls because of a bug.
|
if(command != "display") { Log.info('[INFO] ${message}'); }
|
||||||
// https://github.com/haxelime/lime-vscode-extension/issues/88
|
|
||||||
|
|
||||||
// Log.info('[INFO] ${message}');
|
|
||||||
|
|
||||||
// trace(message);
|
|
||||||
// Sys.println(message);
|
|
||||||
// Sys.stdout().writeString(message);
|
|
||||||
// Sys.stderr().writeString(message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+13
-7
@@ -13,6 +13,7 @@ import openfl.events.Event;
|
|||||||
import openfl.Lib;
|
import openfl.Lib;
|
||||||
import openfl.media.Video;
|
import openfl.media.Video;
|
||||||
import openfl.net.NetStream;
|
import openfl.net.NetStream;
|
||||||
|
import funkin.util.WindowUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The main class which initializes HaxeFlixel and starts the game in its initial state.
|
* The main class which initializes HaxeFlixel and starts the game in its initial state.
|
||||||
@@ -61,12 +62,6 @@ class Main extends Sprite
|
|||||||
|
|
||||||
function init(?event:Event):Void
|
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))
|
if (hasEventListener(Event.ADDED_TO_STAGE))
|
||||||
{
|
{
|
||||||
removeEventListener(Event.ADDED_TO_STAGE, init);
|
removeEventListener(Event.ADDED_TO_STAGE, init);
|
||||||
@@ -104,10 +99,21 @@ class Main extends Sprite
|
|||||||
|
|
||||||
// George recommends binding the save before FlxGame is created.
|
// George recommends binding the save before FlxGame is created.
|
||||||
Save.load();
|
Save.load();
|
||||||
|
|
||||||
|
// Don't call anything from the preferences until the save is loaded!
|
||||||
|
#if web
|
||||||
|
// set this variable (which is a function) from the lime version at lime/_internal/backend/html5/HTML5Application.hx
|
||||||
|
// The framerate cap will more thoroughly initialize via Preferences in InitState.hx
|
||||||
|
funkin.Preferences.lockedFramerateFunction = untyped js.Syntax.code("window.requestAnimationFrame");
|
||||||
|
#end
|
||||||
|
|
||||||
|
WindowUtil.setVSyncMode(funkin.Preferences.vsyncMode);
|
||||||
|
|
||||||
|
|
||||||
var game:FlxGame = new FlxGame(gameWidth, gameHeight, initialState, Preferences.framerate, Preferences.framerate, skipSplash, 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
|
// FlxG.game._customSoundTray wants just the class, it calls new from
|
||||||
// create() in there, which gets called when it's added to stage
|
// create() in there, which gets called when it's added to the stage
|
||||||
// which is why it needs to be added before addChild(game) here
|
// which is why it needs to be added before addChild(game) here
|
||||||
@:privateAccess
|
@:privateAccess
|
||||||
game._customSoundTray = funkin.ui.options.FunkinSoundTray;
|
game._customSoundTray = funkin.ui.options.FunkinSoundTray;
|
||||||
|
|||||||
+32
-1
@@ -9,10 +9,23 @@ class Prebuild
|
|||||||
{
|
{
|
||||||
static inline final BUILD_TIME_FILE:String = '.build_time';
|
static inline final BUILD_TIME_FILE:String = '.build_time';
|
||||||
|
|
||||||
|
static final NG_CREDS_PATH:String = './source/funkin/api/newgrounds/NewgroundsCredentials.hx';
|
||||||
|
|
||||||
|
static final NG_CREDS_TEMPLATE:String = "package funkin.api.newgrounds;
|
||||||
|
|
||||||
|
class NewgroundsCredentials
|
||||||
|
{
|
||||||
|
public static final APP_ID:String = #if API_NG_APP_ID haxe.macro.Compiler.getDefine(\"API_NG_APP_ID\") #else 'INSERT APP ID HERE' #end;
|
||||||
|
public static final ENCRYPTION_KEY:String = #if API_NG_ENC_KEY haxe.macro.Compiler.getDefine(\"API_NG_ENC_KEY\") #else 'INSERT ENCRYPTION KEY HERE' #end;
|
||||||
|
}";
|
||||||
|
|
||||||
static function main():Void
|
static function main():Void
|
||||||
{
|
{
|
||||||
saveBuildTime();
|
|
||||||
trace('Building...');
|
trace('Building...');
|
||||||
|
|
||||||
|
saveBuildTime();
|
||||||
|
|
||||||
|
buildCredsFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
static function saveBuildTime():Void
|
static function saveBuildTime():Void
|
||||||
@@ -22,4 +35,22 @@ class Prebuild
|
|||||||
fo.writeDouble(now);
|
fo.writeDouble(now);
|
||||||
fo.close();
|
fo.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static function buildCredsFile():Void
|
||||||
|
{
|
||||||
|
#if sys
|
||||||
|
if (sys.FileSystem.exists(NG_CREDS_PATH))
|
||||||
|
{
|
||||||
|
trace('NewgroundsCredentials.hx already exists, skipping.');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trace('Creating NewgroundsCredentials.hx...');
|
||||||
|
|
||||||
|
var fileContents:String = NG_CREDS_TEMPLATE;
|
||||||
|
|
||||||
|
sys.io.File.saveContent(NG_CREDS_PATH, fileContents);
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import openfl.utils.Future;
|
|||||||
* A wrapper around `openfl.utils.Assets` which disallows access to the harmful functions.
|
* A wrapper around `openfl.utils.Assets` which disallows access to the harmful functions.
|
||||||
* Later we'll add Funkin-specific caching to this.
|
* Later we'll add Funkin-specific caching to this.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class Assets
|
class Assets
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@@ -144,16 +145,31 @@ class Assets
|
|||||||
return openfl.utils.Assets.list(type);
|
return openfl.utils.Assets.list(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a library with the given name exists.
|
||||||
|
* @param name The name to check.
|
||||||
|
* @return Whether or not the library exists.
|
||||||
|
*/
|
||||||
public static function hasLibrary(name:String):Bool
|
public static function hasLibrary(name:String):Bool
|
||||||
{
|
{
|
||||||
return openfl.utils.Assets.hasLibrary(name);
|
return openfl.utils.Assets.hasLibrary(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a library with the given name.
|
||||||
|
* @param name The name of the library to get.
|
||||||
|
* @return The library with the given name.
|
||||||
|
*/
|
||||||
public static function getLibrary(name:String):lime.utils.AssetLibrary
|
public static function getLibrary(name:String):lime.utils.AssetLibrary
|
||||||
{
|
{
|
||||||
return openfl.utils.Assets.getLibrary(name);
|
return openfl.utils.Assets.getLibrary(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a library with the given name.
|
||||||
|
* @param name The name of the library to load.
|
||||||
|
* @return An `AssetLibary` object.
|
||||||
|
*/
|
||||||
public static function loadLibrary(name:String):Future<openfl.utils.AssetLibrary>
|
public static function loadLibrary(name:String):Future<openfl.utils.AssetLibrary>
|
||||||
{
|
{
|
||||||
return openfl.utils.Assets.loadLibrary(name);
|
return openfl.utils.Assets.loadLibrary(name);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import funkin.data.freeplay.player.PlayerRegistry;
|
|||||||
import funkin.data.freeplay.style.FreeplayStyleRegistry;
|
import funkin.data.freeplay.style.FreeplayStyleRegistry;
|
||||||
import funkin.data.notestyle.NoteStyleRegistry;
|
import funkin.data.notestyle.NoteStyleRegistry;
|
||||||
import funkin.data.song.SongRegistry;
|
import funkin.data.song.SongRegistry;
|
||||||
|
import funkin.data.stickers.StickerRegistry;
|
||||||
import funkin.data.event.SongEventRegistry;
|
import funkin.data.event.SongEventRegistry;
|
||||||
import funkin.data.stage.StageRegistry;
|
import funkin.data.stage.StageRegistry;
|
||||||
import funkin.data.story.level.LevelRegistry;
|
import funkin.data.story.level.LevelRegistry;
|
||||||
@@ -38,6 +39,9 @@ import openfl.display.BitmapData;
|
|||||||
#if FEATURE_DISCORD_RPC
|
#if FEATURE_DISCORD_RPC
|
||||||
import funkin.api.discord.DiscordClient;
|
import funkin.api.discord.DiscordClient;
|
||||||
#end
|
#end
|
||||||
|
#if FEATURE_NEWGROUNDS
|
||||||
|
import funkin.api.newgrounds.NewgroundsClient;
|
||||||
|
#end
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A core class which performs initialization of the game.
|
* A core class which performs initialization of the game.
|
||||||
@@ -82,6 +86,10 @@ class InitState extends FlxState
|
|||||||
// Disable the thing on Windows where it tries to send a bug report to Microsoft because why do they care?
|
// Disable the thing on Windows where it tries to send a bug report to Microsoft because why do they care?
|
||||||
WindowUtil.disableCrashHandler();
|
WindowUtil.disableCrashHandler();
|
||||||
|
|
||||||
|
#if FEATURE_DEBUG_TRACY
|
||||||
|
funkin.util.WindowUtil.initTracy();
|
||||||
|
#end
|
||||||
|
|
||||||
// This ain't a pixel art game! (most of the time)
|
// This ain't a pixel art game! (most of the time)
|
||||||
FlxSprite.defaultAntialiasing = true;
|
FlxSprite.defaultAntialiasing = true;
|
||||||
|
|
||||||
@@ -90,9 +98,18 @@ class InitState extends FlxState
|
|||||||
FlxG.sound.volumeDownKeys = [];
|
FlxG.sound.volumeDownKeys = [];
|
||||||
FlxG.sound.muteKeys = [];
|
FlxG.sound.muteKeys = [];
|
||||||
|
|
||||||
|
// A small jumpstart to the soundtray, it usually sets itself to inactive (somewhere...)
|
||||||
|
// but that makes our soundtray not show up on init if we have the game muted.
|
||||||
|
// We set it to active so it at least calls it's update function once (see FlxGame.onEnterFrame(), it's called there)
|
||||||
|
// and also see FunkinSoundTray.update() to see what we do and how we check if we are muted or not
|
||||||
|
FlxG.game.soundTray.active = true;
|
||||||
|
|
||||||
// Set the game to a lower frame rate while it is in the background.
|
// Set the game to a lower frame rate while it is in the background.
|
||||||
FlxG.game.focusLostFramerate = 30;
|
FlxG.game.focusLostFramerate = 30;
|
||||||
|
|
||||||
|
// Makes Flixel use frame times instead of locked movements per frame for things like tweens
|
||||||
|
FlxG.fixedTimestep = false;
|
||||||
|
|
||||||
setupFlixelDebug();
|
setupFlixelDebug();
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -117,8 +134,8 @@ class InitState extends FlxState
|
|||||||
//
|
//
|
||||||
// NEWGROUNDS API SETUP
|
// NEWGROUNDS API SETUP
|
||||||
//
|
//
|
||||||
#if newgrounds
|
#if FEATURE_NEWGROUNDS
|
||||||
NGio.init();
|
NewgroundsClient.instance.init();
|
||||||
#end
|
#end
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -151,6 +168,7 @@ class InitState extends FlxState
|
|||||||
#if FEATURE_SCREENSHOTS
|
#if FEATURE_SCREENSHOTS
|
||||||
funkin.util.plugins.ScreenshotPlugin.initialize();
|
funkin.util.plugins.ScreenshotPlugin.initialize();
|
||||||
#end
|
#end
|
||||||
|
funkin.util.plugins.NewgroundsMedalPlugin.initialize();
|
||||||
funkin.util.plugins.EvacuateDebugPlugin.initialize();
|
funkin.util.plugins.EvacuateDebugPlugin.initialize();
|
||||||
funkin.util.plugins.ForceCrashPlugin.initialize();
|
funkin.util.plugins.ForceCrashPlugin.initialize();
|
||||||
funkin.util.plugins.ReloadAssetsDebugPlugin.initialize();
|
funkin.util.plugins.ReloadAssetsDebugPlugin.initialize();
|
||||||
@@ -176,6 +194,7 @@ class InitState extends FlxState
|
|||||||
FreeplayStyleRegistry.instance.loadEntries();
|
FreeplayStyleRegistry.instance.loadEntries();
|
||||||
AlbumRegistry.instance.loadEntries();
|
AlbumRegistry.instance.loadEntries();
|
||||||
StageRegistry.instance.loadEntries();
|
StageRegistry.instance.loadEntries();
|
||||||
|
StickerRegistry.instance.loadEntries();
|
||||||
|
|
||||||
// TODO: CharacterDataParser doesn't use json2object, so it's way slower than the other parsers and more prone to syntax errors.
|
// TODO: CharacterDataParser doesn't use json2object, so it's way slower than the other parsers and more prone to syntax errors.
|
||||||
// Move it to use a BaseRegistry.
|
// Move it to use a BaseRegistry.
|
||||||
@@ -187,6 +206,8 @@ class InitState extends FlxState
|
|||||||
ModuleHandler.loadModuleCache();
|
ModuleHandler.loadModuleCache();
|
||||||
ModuleHandler.callOnCreate();
|
ModuleHandler.callOnCreate();
|
||||||
|
|
||||||
|
funkin.input.Cursor.hide();
|
||||||
|
|
||||||
trace('Parsing game data took: ${TimerUtil.ms(perfStart)}');
|
trace('Parsing game data took: ${TimerUtil.ms(perfStart)}');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,8 +251,8 @@ class InitState extends FlxState
|
|||||||
storyMode: true,
|
storyMode: true,
|
||||||
title: "Cum Song Erect by Kawai Sprite",
|
title: "Cum Song Erect by Kawai Sprite",
|
||||||
songId: "cum",
|
songId: "cum",
|
||||||
characterId: "pico-playable",
|
characterId: "pico",
|
||||||
difficultyId: "nightmare",
|
difficultyId: "hard",
|
||||||
isNewHighscore: true,
|
isNewHighscore: true,
|
||||||
scoreData:
|
scoreData:
|
||||||
{
|
{
|
||||||
@@ -246,9 +267,10 @@ class InitState extends FlxState
|
|||||||
combo: 69,
|
combo: 69,
|
||||||
maxCombo: 69,
|
maxCombo: 69,
|
||||||
totalNotesHit: 140,
|
totalNotesHit: 140,
|
||||||
totalNotes: 190
|
totalNotes: 240
|
||||||
}
|
}
|
||||||
// 2400 total notes = 7% = LOSS
|
// 2400 total notes = 7% = LOSS
|
||||||
|
// 275 total notes = 69% = NICE
|
||||||
// 240 total notes = 79% = GOOD
|
// 240 total notes = 79% = GOOD
|
||||||
// 230 total notes = 82% = GREAT
|
// 230 total notes = 82% = GREAT
|
||||||
// 210 total notes = 91% = EXCELLENT
|
// 210 total notes = 91% = EXCELLENT
|
||||||
|
|||||||
@@ -2,11 +2,9 @@ package funkin;
|
|||||||
|
|
||||||
import funkin.save.Save;
|
import funkin.save.Save;
|
||||||
import funkin.input.Controls;
|
import funkin.input.Controls;
|
||||||
import flixel.FlxCamera;
|
|
||||||
import funkin.input.PreciseInputManager;
|
import funkin.input.PreciseInputManager;
|
||||||
import flixel.input.actions.FlxActionInput;
|
|
||||||
import flixel.input.gamepad.FlxGamepad;
|
import flixel.input.gamepad.FlxGamepad;
|
||||||
import flixel.util.FlxSignal;
|
import flixel.util.FlxSignal.FlxTypedSignal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A core class which represents the current player(s) and their controls and other configuration.
|
* A core class which represents the current player(s) and their controls and other configuration.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package funkin;
|
package funkin;
|
||||||
|
|
||||||
import funkin.save.Save;
|
import funkin.save.Save;
|
||||||
|
import funkin.util.WindowUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A core class which provides a store of user-configurable, globally relevant values.
|
* A core class which provides a store of user-configurable, globally relevant values.
|
||||||
@@ -157,6 +158,72 @@ class Preferences
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If enabled, the game will automatically launch in fullscreen on startup.
|
||||||
|
* @default `true`
|
||||||
|
*/
|
||||||
|
public static var autoFullscreen(get, set):Bool;
|
||||||
|
|
||||||
|
static function get_autoFullscreen():Bool
|
||||||
|
{
|
||||||
|
return Save?.instance?.options?.autoFullscreen ?? true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function set_autoFullscreen(value:Bool):Bool
|
||||||
|
{
|
||||||
|
var save:Save = Save.instance;
|
||||||
|
save.options.autoFullscreen = value;
|
||||||
|
save.flush();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If enabled, the game will utilize VSync (or adaptive VSync) on startup.
|
||||||
|
* @default `OFF`
|
||||||
|
*/
|
||||||
|
public static var vsyncMode(get, set):lime.ui.WindowVSyncMode;
|
||||||
|
|
||||||
|
static function get_vsyncMode():lime.ui.WindowVSyncMode
|
||||||
|
{
|
||||||
|
var value = Save?.instance?.options?.vsyncMode ?? "Off";
|
||||||
|
|
||||||
|
return switch (value)
|
||||||
|
{
|
||||||
|
case "Off":
|
||||||
|
lime.ui.WindowVSyncMode.OFF;
|
||||||
|
case "On":
|
||||||
|
lime.ui.WindowVSyncMode.ON;
|
||||||
|
case "Adaptive":
|
||||||
|
lime.ui.WindowVSyncMode.ADAPTIVE;
|
||||||
|
default:
|
||||||
|
lime.ui.WindowVSyncMode.OFF;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static function set_vsyncMode(value:lime.ui.WindowVSyncMode):lime.ui.WindowVSyncMode
|
||||||
|
{
|
||||||
|
var string;
|
||||||
|
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case lime.ui.WindowVSyncMode.OFF:
|
||||||
|
string = "Off";
|
||||||
|
case lime.ui.WindowVSyncMode.ON:
|
||||||
|
string = "On";
|
||||||
|
case lime.ui.WindowVSyncMode.ADAPTIVE:
|
||||||
|
string = "Adaptive";
|
||||||
|
default:
|
||||||
|
string = "Off";
|
||||||
|
};
|
||||||
|
|
||||||
|
WindowUtil.setVSyncMode(value);
|
||||||
|
|
||||||
|
var save:Save = Save.instance;
|
||||||
|
save.options.vsyncMode = string;
|
||||||
|
save.flush();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
public static var unlockedFramerate(get, set):Bool;
|
public static var unlockedFramerate(get, set):Bool;
|
||||||
|
|
||||||
static function get_unlockedFramerate():Bool
|
static function get_unlockedFramerate():Bool
|
||||||
@@ -199,6 +266,121 @@ class Preferences
|
|||||||
public static var lockedFramerateFunction = untyped js.Syntax.code("window.requestAnimationFrame");
|
public static var lockedFramerateFunction = untyped js.Syntax.code("window.requestAnimationFrame");
|
||||||
#end
|
#end
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If >0, the game will display a semi-opaque background under the notes.
|
||||||
|
* `0` for no background, `100` for solid black if you're freaky like that
|
||||||
|
* @default `0`
|
||||||
|
*/
|
||||||
|
public static var strumlineBackgroundOpacity(get, set):Int;
|
||||||
|
|
||||||
|
static function get_strumlineBackgroundOpacity():Int
|
||||||
|
{
|
||||||
|
return (Save?.instance?.options?.strumlineBackgroundOpacity ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function set_strumlineBackgroundOpacity(value:Int):Int
|
||||||
|
{
|
||||||
|
var save:Save = Save.instance;
|
||||||
|
save.options.strumlineBackgroundOpacity = value;
|
||||||
|
save.flush();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If enabled, the game will hide the mouse when taking a screenshot.
|
||||||
|
* @default `true`
|
||||||
|
*/
|
||||||
|
public static var shouldHideMouse(get, set):Bool;
|
||||||
|
|
||||||
|
static function get_shouldHideMouse():Bool
|
||||||
|
{
|
||||||
|
return Save?.instance?.options?.screenshot?.shouldHideMouse ?? true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function set_shouldHideMouse(value:Bool):Bool
|
||||||
|
{
|
||||||
|
var save:Save = Save.instance;
|
||||||
|
save.options.screenshot.shouldHideMouse = value;
|
||||||
|
save.flush();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If enabled, the game will show a preview after taking a screenshot.
|
||||||
|
* @default `true`
|
||||||
|
*/
|
||||||
|
public static var fancyPreview(get, set):Bool;
|
||||||
|
|
||||||
|
static function get_fancyPreview():Bool
|
||||||
|
{
|
||||||
|
return Save?.instance?.options?.screenshot?.fancyPreview ?? true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function set_fancyPreview(value:Bool):Bool
|
||||||
|
{
|
||||||
|
var save:Save = Save.instance;
|
||||||
|
save.options.screenshot.fancyPreview = value;
|
||||||
|
save.flush();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If enabled, the game will show the preview only after a screenshot is saved.
|
||||||
|
* @default `true`
|
||||||
|
*/
|
||||||
|
public static var previewOnSave(get, set):Bool;
|
||||||
|
|
||||||
|
static function get_previewOnSave():Bool
|
||||||
|
{
|
||||||
|
return Save?.instance?.options?.screenshot?.previewOnSave ?? true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function set_previewOnSave(value:Bool):Bool
|
||||||
|
{
|
||||||
|
var save:Save = Save.instance;
|
||||||
|
save.options.screenshot.previewOnSave = value;
|
||||||
|
save.flush();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The game will save any screenshots taken to this format.
|
||||||
|
* @default `PNG`
|
||||||
|
*/
|
||||||
|
public static var saveFormat(get, set):Any;
|
||||||
|
|
||||||
|
static function get_saveFormat():Any
|
||||||
|
{
|
||||||
|
return Save?.instance?.options?.screenshot?.saveFormat ?? 'PNG';
|
||||||
|
}
|
||||||
|
|
||||||
|
static function set_saveFormat(value):Any
|
||||||
|
{
|
||||||
|
var save:Save = Save.instance;
|
||||||
|
save.options.screenshot.saveFormat = value;
|
||||||
|
save.flush();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The game will save JPEG screenshots with this quality percentage.
|
||||||
|
* @default `80`
|
||||||
|
*/
|
||||||
|
public static var jpegQuality(get, set):Int;
|
||||||
|
|
||||||
|
static function get_jpegQuality():Int
|
||||||
|
{
|
||||||
|
return Save?.instance?.options?.screenshot?.jpegQuality ?? 80;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function set_jpegQuality(value:Int):Int
|
||||||
|
{
|
||||||
|
var save:Save = Save.instance;
|
||||||
|
save.options.screenshot.jpegQuality = value;
|
||||||
|
save.flush();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the user's preferences from the save data and apply them.
|
* Loads the user's preferences from the save data and apply them.
|
||||||
*/
|
*/
|
||||||
@@ -206,11 +388,14 @@ class Preferences
|
|||||||
{
|
{
|
||||||
// Apply the autoPause setting (enables automatic pausing on focus lost).
|
// Apply the autoPause setting (enables automatic pausing on focus lost).
|
||||||
FlxG.autoPause = Preferences.autoPause;
|
FlxG.autoPause = Preferences.autoPause;
|
||||||
|
// WindowUtil.setVSyncMode(Preferences.vsyncMode);
|
||||||
// Apply the debugDisplay setting (enables the FPS and RAM display).
|
// Apply the debugDisplay setting (enables the FPS and RAM display).
|
||||||
toggleDebugDisplay(Preferences.debugDisplay);
|
toggleDebugDisplay(Preferences.debugDisplay);
|
||||||
#if web
|
#if web
|
||||||
toggleFramerateCap(Preferences.unlockedFramerate);
|
toggleFramerateCap(Preferences.unlockedFramerate);
|
||||||
#end
|
#end
|
||||||
|
// Apply the autoFullscreen setting (launches the game in fullscreen automatically)
|
||||||
|
FlxG.fullscreen = Preferences.autoFullscreen;
|
||||||
}
|
}
|
||||||
|
|
||||||
static function toggleFramerateCap(unlocked:Bool):Void
|
static function toggleFramerateCap(unlocked:Bool):Void
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ package funkin.api.discord;
|
|||||||
|
|
||||||
#if FEATURE_DISCORD_RPC
|
#if FEATURE_DISCORD_RPC
|
||||||
import hxdiscord_rpc.Discord;
|
import hxdiscord_rpc.Discord;
|
||||||
import hxdiscord_rpc.Types;
|
import hxdiscord_rpc.Types.DiscordButton;
|
||||||
|
import hxdiscord_rpc.Types.DiscordEventHandlers;
|
||||||
|
import hxdiscord_rpc.Types.DiscordRichPresence;
|
||||||
|
import hxdiscord_rpc.Types.DiscordUser;
|
||||||
import sys.thread.Thread;
|
import sys.thread.Thread;
|
||||||
|
|
||||||
class DiscordClient
|
class DiscordClient
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
package funkin.api.newgrounds;
|
||||||
|
|
||||||
|
import io.newgrounds.Call.CallOutcome;
|
||||||
|
import io.newgrounds.NG;
|
||||||
|
import io.newgrounds.objects.events.Outcome;
|
||||||
|
import io.newgrounds.objects.events.Result;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use Newgrounds to perform basic telemetry. Ignore if not logged in to Newgrounds.
|
||||||
|
*/
|
||||||
|
@:nullSafety
|
||||||
|
class Events
|
||||||
|
{
|
||||||
|
// Only allow letters, numbers, spaces, dashes, and underscores.
|
||||||
|
static final EVENT_NAME_REGEX:EReg = ~/[^a-zA-Z0-9 -_]/g;
|
||||||
|
|
||||||
|
public static function logEvent(eventName:String):Void
|
||||||
|
{
|
||||||
|
#if (FEATURE_NEWGROUNDS && FEATURE_NEWGROUNDS_EVENTS)
|
||||||
|
if (NewgroundsClient.instance.isLoggedIn())
|
||||||
|
{
|
||||||
|
var eventHandler = NG.core.calls.event;
|
||||||
|
|
||||||
|
if (eventHandler != null)
|
||||||
|
{
|
||||||
|
var sanitizedEventName = EVENT_NAME_REGEX.replace(eventName, '');
|
||||||
|
var outcomeHandler = onEventLogged.bind(sanitizedEventName, _);
|
||||||
|
eventHandler.logEvent(sanitizedEventName).addOutcomeHandler(outcomeHandler).send();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
|
static function onEventLogged(eventName:String, outcome:CallOutcome<LogEventData>)
|
||||||
|
{
|
||||||
|
switch (outcome)
|
||||||
|
{
|
||||||
|
case SUCCESS(data):
|
||||||
|
trace('[NEWGROUNDS] Logged event: ${data.eventName}');
|
||||||
|
case FAIL(outcome):
|
||||||
|
switch (outcome)
|
||||||
|
{
|
||||||
|
case HTTP(error):
|
||||||
|
trace('[NEWGROUNDS] HTTP error while logging event: ${error}');
|
||||||
|
case RESPONSE(error):
|
||||||
|
trace('[NEWGROUNDS] Response error (${error.code}) while logging event: ${error.message}');
|
||||||
|
case RESULT(error):
|
||||||
|
switch (error.code)
|
||||||
|
{
|
||||||
|
case 103: // Invalid custom event name
|
||||||
|
trace('[NEWGROUNDS] Invalid custom event name: ${eventName}');
|
||||||
|
default:
|
||||||
|
trace('[NEWGROUNDS] Result error (${error.code}) while logging event: ${error.message}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static inline function logStartGame():Void
|
||||||
|
{
|
||||||
|
logEvent('start-game');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static inline function logStartSong(songId:String, variation:String):Void
|
||||||
|
{
|
||||||
|
logEvent('start-song_${songId}-${variation}');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static inline function logFailSong(songId:String, variation:String):Void
|
||||||
|
{
|
||||||
|
logEvent('blueballs_${songId}-${variation}');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static inline function logCompleteSong(songId:String, variation:String):Void
|
||||||
|
{
|
||||||
|
logEvent('complete-song_${songId}-${variation}');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static inline function logStartLevel(levelId:String):Void
|
||||||
|
{
|
||||||
|
logEvent('start-level_${levelId}');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static inline function logCompleteLevel(levelId:String):Void
|
||||||
|
{
|
||||||
|
logEvent('complete-level_${levelId}');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static inline function logEarnRank(rankName:String):Void
|
||||||
|
{
|
||||||
|
logEvent('earn-rank_${rankName}');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static inline function logWatchCartoon():Void
|
||||||
|
{
|
||||||
|
logEvent('watch-cartoon');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note there is already a loadReferral call for the merch link
|
||||||
|
// and that gets logged as an event!
|
||||||
|
|
||||||
|
public static inline function logOpenCredits():Void
|
||||||
|
{
|
||||||
|
logEvent('open-credits');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,395 @@
|
|||||||
|
package funkin.api.newgrounds;
|
||||||
|
|
||||||
|
#if FEATURE_NEWGROUNDS
|
||||||
|
import io.newgrounds.Call.CallError;
|
||||||
|
import io.newgrounds.objects.ScoreBoard as LeaderboardData;
|
||||||
|
import io.newgrounds.objects.events.Outcome;
|
||||||
|
import io.newgrounds.utils.ScoreBoardList;
|
||||||
|
|
||||||
|
class Leaderboards
|
||||||
|
{
|
||||||
|
public static function listLeaderboardData():Map<Leaderboard, LeaderboardData>
|
||||||
|
{
|
||||||
|
var leaderboardList:Null<ScoreBoardList> = NewgroundsClient.instance.leaderboards;
|
||||||
|
if (leaderboardList == null)
|
||||||
|
{
|
||||||
|
trace('[NEWGROUNDS] Not logged in, cannot fetch medal data!');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var result:Map<Leaderboard, LeaderboardData> = [];
|
||||||
|
|
||||||
|
for (leaderboardId in leaderboardList.keys())
|
||||||
|
{
|
||||||
|
var leaderboardData = leaderboardList.get(leaderboardId);
|
||||||
|
if (leaderboardData == null) continue;
|
||||||
|
|
||||||
|
// A little hacky, but it works.
|
||||||
|
result.set(cast leaderboardId, leaderboardData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit a score to Newgrounds.
|
||||||
|
* @param leaderboard The leaderboard to submit to.
|
||||||
|
* @param score The score to submit.
|
||||||
|
* @param tag An optional tag to attach to the score.
|
||||||
|
*/
|
||||||
|
public static function submitScore(leaderboard:Leaderboard, score:Int, ?tag:String):Void
|
||||||
|
{
|
||||||
|
// Silently reject submissions for unknown leaderboards.
|
||||||
|
if (leaderboard == Leaderboard.Unknown) return;
|
||||||
|
|
||||||
|
if (NewgroundsClient.instance.isLoggedIn())
|
||||||
|
{
|
||||||
|
var leaderboardList = NewgroundsClient.instance.leaderboards;
|
||||||
|
if (leaderboardList == null) return;
|
||||||
|
|
||||||
|
var leaderboardData:Null<LeaderboardData> = leaderboardList.get(leaderboard.getId());
|
||||||
|
if (leaderboardData != null)
|
||||||
|
{
|
||||||
|
leaderboardData.postScore(score, function(outcome:Outcome<CallError>):Void {
|
||||||
|
switch (outcome)
|
||||||
|
{
|
||||||
|
case SUCCESS:
|
||||||
|
trace('[NEWGROUNDS] Submitted score!');
|
||||||
|
case FAIL(error):
|
||||||
|
trace('[NEWGROUNDS] Failed to submit score!');
|
||||||
|
trace(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit a score for a Story Level to Newgrounds.
|
||||||
|
*/
|
||||||
|
public static function submitLevelScore(levelId:String, difficultyId:String, score:Int):Void
|
||||||
|
{
|
||||||
|
var tag = '${difficultyId}';
|
||||||
|
Leaderboards.submitScore(Leaderboard.getLeaderboardByLevel(levelId), score, tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit a score for a song to Newgrounds.
|
||||||
|
*/
|
||||||
|
public static function submitSongScore(songId:String, difficultyId:String, score:Int):Void
|
||||||
|
{
|
||||||
|
var tag = '${difficultyId}';
|
||||||
|
Leaderboards.submitScore(Leaderboard.getLeaderboardBySong(songId, difficultyId), score, tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
|
||||||
|
enum abstract Leaderboard(Int)
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Represents an undefined or invalid leaderboard.
|
||||||
|
*/
|
||||||
|
var Unknown = -1;
|
||||||
|
|
||||||
|
//
|
||||||
|
// STORY LEVELS
|
||||||
|
//
|
||||||
|
var StoryWeek1 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14239 #else 9615 #end;
|
||||||
|
var StoryWeek2 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14240 #else 9616 #end;
|
||||||
|
var StoryWeek3 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14242 #else 9767 #end;
|
||||||
|
var StoryWeek4 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14241 #else 9866 #end;
|
||||||
|
var StoryWeek5 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14243 #else 9956 #end;
|
||||||
|
var StoryWeek6 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14244 #else 9957 #end;
|
||||||
|
var StoryWeek7 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14245 #else 14682 #end;
|
||||||
|
var StoryWeekend1 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14237 #else 14683 #end;
|
||||||
|
|
||||||
|
//
|
||||||
|
// SONGS
|
||||||
|
//
|
||||||
|
// Tutorial
|
||||||
|
var Tutorial = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14249 #else 14684 #end;
|
||||||
|
|
||||||
|
// Week 1
|
||||||
|
var Bopeebo = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14246 #else 9603 #end;
|
||||||
|
var BopeeboErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14685 #end;
|
||||||
|
var BopeeboPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14686 #end;
|
||||||
|
var Fresh = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14247 #else 9602 #end;
|
||||||
|
var FreshErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14687 #end;
|
||||||
|
var FreshPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14688 #end;
|
||||||
|
var DadBattle = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14248 #else 9605 #end;
|
||||||
|
var DadBattleErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14689 #end;
|
||||||
|
var DadBattlePicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14690 #end;
|
||||||
|
|
||||||
|
// Week 2
|
||||||
|
var Spookeez = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9604 #end;
|
||||||
|
var SpookeezErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14691 #end;
|
||||||
|
var SpookeezPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14692 #end;
|
||||||
|
var South = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9606 #end;
|
||||||
|
var SouthErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14693 #end;
|
||||||
|
var SouthPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14694 #end;
|
||||||
|
var Monster = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14703 #end;
|
||||||
|
|
||||||
|
// Week 3
|
||||||
|
var Pico = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9766 #end;
|
||||||
|
var PicoErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14695 #end;
|
||||||
|
var PicoPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14696 #end;
|
||||||
|
var PhillyNice = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9769 #end;
|
||||||
|
var PhillyNiceErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14697 #end;
|
||||||
|
var PhillyNicePicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14698 #end;
|
||||||
|
var Blammed = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9768 #end;
|
||||||
|
var BlammedErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14704 #end;
|
||||||
|
var BlammedPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14705 #end;
|
||||||
|
|
||||||
|
// Week 4
|
||||||
|
var SatinPanties = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9869 #end;
|
||||||
|
var SatinPantiesErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14701 #end;
|
||||||
|
var High = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9867 #end;
|
||||||
|
var HighErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14699 #end;
|
||||||
|
var MILF = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9868 #end;
|
||||||
|
|
||||||
|
// Week 5
|
||||||
|
var Cocoa = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14706 #end;
|
||||||
|
var CocoaErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14707 #end;
|
||||||
|
var CocoaPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14708 #end;
|
||||||
|
var Eggnog = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14709 #end;
|
||||||
|
var EggnogErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14711 #end;
|
||||||
|
var EggnogPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14710 #end;
|
||||||
|
var WinterHorrorland = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14712 #end;
|
||||||
|
|
||||||
|
// Week 6
|
||||||
|
var Senpai = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9958 #end;
|
||||||
|
var SenpaiErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14713 #end;
|
||||||
|
var SenpaiPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14716 #end;
|
||||||
|
var Roses = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9959 #end;
|
||||||
|
var RosesErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14714 #end;
|
||||||
|
var RosesPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14717 #end;
|
||||||
|
var Thorns = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9960 #end;
|
||||||
|
var ThornsErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14715 #end;
|
||||||
|
|
||||||
|
// Week 7
|
||||||
|
var Ugh = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14718 #end;
|
||||||
|
var UghErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14722 #end;
|
||||||
|
var UghPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14721 #end;
|
||||||
|
var Guns = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14719 #end;
|
||||||
|
var GunsPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14723 #end;
|
||||||
|
var Stress = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14720 #end;
|
||||||
|
var StressPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14724 #end;
|
||||||
|
|
||||||
|
// Weekend 1
|
||||||
|
var Darnell = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14725 #end;
|
||||||
|
var DarnellErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14727 #end;
|
||||||
|
var DarnellBFMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14726 #end;
|
||||||
|
var LitUp = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14728 #end;
|
||||||
|
var LitUpBFMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14729 #end;
|
||||||
|
var TwoHot = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14730 #end; // Variable names can't start with a number!
|
||||||
|
var Blazin = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14731 #end;
|
||||||
|
|
||||||
|
public function getId():Int
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the leaderboard for a given level and difficulty.
|
||||||
|
* @param levelId The ID for the story level.
|
||||||
|
* @param difficulty The current difficulty.
|
||||||
|
* @return The Leaderboard ID for the given level and difficulty.
|
||||||
|
*/
|
||||||
|
public static function getLeaderboardByLevel(levelId:String):Leaderboard
|
||||||
|
{
|
||||||
|
switch (levelId)
|
||||||
|
{
|
||||||
|
case "week1":
|
||||||
|
return StoryWeek1;
|
||||||
|
case "week2":
|
||||||
|
return StoryWeek2;
|
||||||
|
case "week3":
|
||||||
|
return StoryWeek3;
|
||||||
|
case "week4":
|
||||||
|
return StoryWeek4;
|
||||||
|
case "week5":
|
||||||
|
return StoryWeek5;
|
||||||
|
case "week6":
|
||||||
|
return StoryWeek6;
|
||||||
|
case "week7":
|
||||||
|
return StoryWeek7;
|
||||||
|
case "weekend1":
|
||||||
|
return StoryWeekend1;
|
||||||
|
default:
|
||||||
|
return Unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the leaderboard for a given level and difficulty.
|
||||||
|
* @param levelId The ID for the story level.
|
||||||
|
* @param difficulty The current difficulty, suffixed with the variation, like `easy-pico` or `nightmare`.
|
||||||
|
* @return The Leaderboard ID for the given level and difficulty.
|
||||||
|
*/
|
||||||
|
public static function getLeaderboardBySong(songId:String, difficulty:String):Leaderboard
|
||||||
|
{
|
||||||
|
var variation = Constants.DEFAULT_VARIATION;
|
||||||
|
var difficultyParts = difficulty.split('-');
|
||||||
|
|
||||||
|
if (difficultyParts.length >= 2)
|
||||||
|
{
|
||||||
|
variation = difficultyParts[difficultyParts.length - 1];
|
||||||
|
}
|
||||||
|
else if (Constants.DEFAULT_DIFFICULTY_LIST_ERECT.contains(difficulty))
|
||||||
|
{
|
||||||
|
variation = "erect";
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (variation)
|
||||||
|
{
|
||||||
|
case "pico":
|
||||||
|
switch (songId)
|
||||||
|
{
|
||||||
|
case "bopeebo":
|
||||||
|
return BopeeboPicoMix;
|
||||||
|
case "fresh":
|
||||||
|
return FreshPicoMix;
|
||||||
|
case "dadbattle":
|
||||||
|
return DadBattlePicoMix;
|
||||||
|
case "spookeez":
|
||||||
|
return SpookeezPicoMix;
|
||||||
|
case "south":
|
||||||
|
return SouthPicoMix;
|
||||||
|
case "pico":
|
||||||
|
return PicoPicoMix;
|
||||||
|
case "philly-nice":
|
||||||
|
return PhillyNicePicoMix;
|
||||||
|
case "blammed":
|
||||||
|
return BlammedPicoMix;
|
||||||
|
case "cocoa":
|
||||||
|
return CocoaPicoMix;
|
||||||
|
case "eggnog":
|
||||||
|
return EggnogPicoMix;
|
||||||
|
case "senpai":
|
||||||
|
return SenpaiPicoMix;
|
||||||
|
case "roses":
|
||||||
|
return RosesPicoMix;
|
||||||
|
case "ugh":
|
||||||
|
return UghPicoMix;
|
||||||
|
case "guns":
|
||||||
|
return GunsPicoMix;
|
||||||
|
case "stress":
|
||||||
|
return StressPicoMix;
|
||||||
|
default:
|
||||||
|
return Unknown;
|
||||||
|
}
|
||||||
|
case "bf":
|
||||||
|
switch (songId)
|
||||||
|
{
|
||||||
|
case "darnell":
|
||||||
|
return DarnellBFMix;
|
||||||
|
case "litup":
|
||||||
|
return LitUpBFMix;
|
||||||
|
default:
|
||||||
|
return Unknown;
|
||||||
|
}
|
||||||
|
case "erect":
|
||||||
|
switch (songId)
|
||||||
|
{
|
||||||
|
case "bopeebo":
|
||||||
|
return BopeeboErect;
|
||||||
|
case "fresh":
|
||||||
|
return FreshErect;
|
||||||
|
case "dadbattle":
|
||||||
|
return DadBattleErect;
|
||||||
|
case "spookeez":
|
||||||
|
return SpookeezErect;
|
||||||
|
case "south":
|
||||||
|
return SouthErect;
|
||||||
|
case "pico":
|
||||||
|
return PicoErect;
|
||||||
|
case "philly-nice":
|
||||||
|
return PhillyNiceErect;
|
||||||
|
case "blammed":
|
||||||
|
return BlammedErect;
|
||||||
|
case "satin-panties":
|
||||||
|
return SatinPantiesErect;
|
||||||
|
case "high":
|
||||||
|
return HighErect;
|
||||||
|
case "cocoa":
|
||||||
|
return CocoaErect;
|
||||||
|
case "eggnog":
|
||||||
|
return EggnogErect;
|
||||||
|
case "senpai":
|
||||||
|
return SenpaiErect;
|
||||||
|
case "roses":
|
||||||
|
return RosesErect;
|
||||||
|
case "thorns":
|
||||||
|
return ThornsErect;
|
||||||
|
case "ugh":
|
||||||
|
return UghErect;
|
||||||
|
case "darnell":
|
||||||
|
return DarnellErect;
|
||||||
|
default:
|
||||||
|
return Unknown;
|
||||||
|
}
|
||||||
|
case "default":
|
||||||
|
switch (songId)
|
||||||
|
{
|
||||||
|
case "tutorial":
|
||||||
|
return Tutorial;
|
||||||
|
case "bopeebo":
|
||||||
|
return Bopeebo;
|
||||||
|
case "fresh":
|
||||||
|
return Fresh;
|
||||||
|
case "dadbattle":
|
||||||
|
return DadBattle;
|
||||||
|
case "spookeez":
|
||||||
|
return Spookeez;
|
||||||
|
case "south":
|
||||||
|
return South;
|
||||||
|
case "monster":
|
||||||
|
return Monster;
|
||||||
|
case "pico":
|
||||||
|
return Pico;
|
||||||
|
case "philly-nice":
|
||||||
|
return PhillyNice;
|
||||||
|
case "blammed":
|
||||||
|
return Blammed;
|
||||||
|
case "satin-panties":
|
||||||
|
return SatinPanties;
|
||||||
|
case "high":
|
||||||
|
return High;
|
||||||
|
case "milf":
|
||||||
|
return MILF;
|
||||||
|
case "cocoa":
|
||||||
|
return Cocoa;
|
||||||
|
case "eggnog":
|
||||||
|
return Eggnog;
|
||||||
|
case "winter-horrorland":
|
||||||
|
return WinterHorrorland;
|
||||||
|
case "senpai":
|
||||||
|
return Senpai;
|
||||||
|
case "roses":
|
||||||
|
return Roses;
|
||||||
|
case "thorns":
|
||||||
|
return Thorns;
|
||||||
|
case "ugh":
|
||||||
|
return Ugh;
|
||||||
|
case "guns":
|
||||||
|
return Guns;
|
||||||
|
case "stress":
|
||||||
|
return Stress;
|
||||||
|
case "darnell":
|
||||||
|
return Darnell;
|
||||||
|
case "litup":
|
||||||
|
return LitUp;
|
||||||
|
case "2hot":
|
||||||
|
return TwoHot;
|
||||||
|
case "blazin":
|
||||||
|
return Blazin;
|
||||||
|
default:
|
||||||
|
return Unknown;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return Unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,347 @@
|
|||||||
|
package funkin.api.newgrounds;
|
||||||
|
|
||||||
|
#if FEATURE_NEWGROUNDS
|
||||||
|
import io.newgrounds.objects.Medal as MedalData;
|
||||||
|
import funkin.util.plugins.NewgroundsMedalPlugin;
|
||||||
|
import flixel.graphics.FlxGraphic;
|
||||||
|
import openfl.display.BitmapData;
|
||||||
|
import io.newgrounds.utils.MedalList;
|
||||||
|
import haxe.Json;
|
||||||
|
|
||||||
|
class Medals
|
||||||
|
{
|
||||||
|
public static var medalJSON:Array<MedalJSON> = [];
|
||||||
|
|
||||||
|
public static function listMedalData():Map<Medal, MedalData>
|
||||||
|
{
|
||||||
|
var medalList = NewgroundsClient.instance.medals;
|
||||||
|
|
||||||
|
if (medalList == null)
|
||||||
|
{
|
||||||
|
trace('[NEWGROUNDS] Not logged in, cannot fetch medal data!');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: Why do I have to do this, @:nullSafety is fucked up
|
||||||
|
var result:Map<Medal, MedalData> = [];
|
||||||
|
|
||||||
|
for (medalId in medalList.keys())
|
||||||
|
{
|
||||||
|
var medalData = medalList.get(medalId);
|
||||||
|
if (medalData == null) continue;
|
||||||
|
|
||||||
|
// A little hacky, but it works.
|
||||||
|
result.set(cast medalId, medalData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function award(medal:Medal):Void
|
||||||
|
{
|
||||||
|
if (NewgroundsClient.instance.isLoggedIn())
|
||||||
|
{
|
||||||
|
var medalList = NewgroundsClient.instance.medals;
|
||||||
|
@:privateAccess
|
||||||
|
if (medalList == null || medalList._map == null) return;
|
||||||
|
|
||||||
|
var medalData:Null<MedalData> = medalList.get(medal.getId());
|
||||||
|
@:privateAccess
|
||||||
|
if (medalData == null || medalData._data == null)
|
||||||
|
{
|
||||||
|
trace('[NEWGROUNDS] Could not retrieve data for medal: ${medal}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (!medalData.unlocked)
|
||||||
|
{
|
||||||
|
trace('[NEWGROUNDS] Awarding medal (${medal}).');
|
||||||
|
medalData.sendUnlock();
|
||||||
|
|
||||||
|
// Play the medal unlock animation, but only if the user has not already unlocked it.
|
||||||
|
#if html5
|
||||||
|
// Web builds support parsing the bitmap data from the URL directly.
|
||||||
|
BitmapData.loadFromFile("https:" + medalData.icon).onComplete(function(bmp:BitmapData) {
|
||||||
|
var medalGraphic = FlxGraphic.fromBitmapData(bmp);
|
||||||
|
medalGraphic.persist = true;
|
||||||
|
NewgroundsMedalPlugin.play(medalData.value, medalData.name, medalGraphic);
|
||||||
|
});
|
||||||
|
#else
|
||||||
|
if ((medalJSON?.length ?? 0) == 0) loadMedalJSON();
|
||||||
|
// We have to use a medal image from the game files. We use a Base64 encoded image that NG spits out.
|
||||||
|
// TODO: Wait, don't they give us the medal icon?
|
||||||
|
|
||||||
|
var localMedalData:Null<MedalJSON> = medalJSON.filter(function(jsonMedal) {
|
||||||
|
#if FEATURE_NEWGROUNDS_TESTING_MEDALS
|
||||||
|
return medal == jsonMedal.idTest;
|
||||||
|
#else
|
||||||
|
return medal == jsonMedal.id;
|
||||||
|
#end
|
||||||
|
})[0];
|
||||||
|
|
||||||
|
if (localMedalData == null) throw "You forgot to encode a Base64 image for medal: " + medal;
|
||||||
|
|
||||||
|
var str:String = localMedalData.icon;
|
||||||
|
// Lime/OpenFL parses it without the included prefix stuff, so we remove it.
|
||||||
|
str = str.replace("data:image/png;base64,", "").trim();
|
||||||
|
var bitmapData = BitmapData.fromBase64(str, "image/png");
|
||||||
|
var medalGraphic:Null<FlxGraphic> = null;
|
||||||
|
if (str != null)
|
||||||
|
{
|
||||||
|
medalGraphic = FlxGraphic.fromBitmapData(bitmapData);
|
||||||
|
medalGraphic.persist = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
NewgroundsMedalPlugin.play(medalData.value, medalData.name, medalGraphic);
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trace('[NEWGROUNDS] User already has medal (${medal}).');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trace('[NEWGROUNDS] Attempted to award medal (${medal}), but not logged into Newgrounds.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function loadMedalJSON():Void
|
||||||
|
{
|
||||||
|
var jsonPath = Paths.json('medals');
|
||||||
|
|
||||||
|
var jsonString = Assets.getText(jsonPath);
|
||||||
|
|
||||||
|
var parser = new json2object.JsonParser<Array<MedalJSON>>();
|
||||||
|
parser.ignoreUnknownVariables = false;
|
||||||
|
trace('[NEWGROUNDS] Parsing local medal data...');
|
||||||
|
parser.fromJson(jsonString, jsonPath);
|
||||||
|
|
||||||
|
if (parser.errors.length > 0)
|
||||||
|
{
|
||||||
|
trace('[NEWGROUNDS] Failed to parse local medal data!');
|
||||||
|
for (error in parser.errors)
|
||||||
|
funkin.data.DataError.printError(error);
|
||||||
|
medalJSON = [];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
medalJSON = parser.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function awardStoryLevel(id:String):Void
|
||||||
|
{
|
||||||
|
switch (id)
|
||||||
|
{
|
||||||
|
case 'tutorial':
|
||||||
|
Medals.award(Medal.StoryTutorial);
|
||||||
|
case 'week1':
|
||||||
|
Medals.award(Medal.StoryWeek1);
|
||||||
|
case 'week2':
|
||||||
|
Medals.award(Medal.StoryWeek2);
|
||||||
|
case 'week3':
|
||||||
|
Medals.award(Medal.StoryWeek3);
|
||||||
|
case 'week4':
|
||||||
|
Medals.award(Medal.StoryWeek4);
|
||||||
|
case 'week5':
|
||||||
|
Medals.award(Medal.StoryWeek5);
|
||||||
|
case 'week6':
|
||||||
|
Medals.award(Medal.StoryWeek6);
|
||||||
|
case 'week7':
|
||||||
|
Medals.award(Medal.StoryWeek7);
|
||||||
|
case 'weekend1':
|
||||||
|
Medals.award(Medal.StoryWeekend1);
|
||||||
|
default:
|
||||||
|
trace('[NEWGROUNDS] Story level does not have a medal! (${id}).');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents NG Medal data in a JSON format.
|
||||||
|
*/
|
||||||
|
typedef MedalJSON =
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Medal ID to use for release builds
|
||||||
|
*/
|
||||||
|
var id:Int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Medal ID to use for testing builds
|
||||||
|
*/
|
||||||
|
var idTest:Int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The English name for the medal
|
||||||
|
*/
|
||||||
|
var name:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The English name for the medal
|
||||||
|
*/
|
||||||
|
var icon:String;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum abstract Medal(Int) from Int to Int
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Represents an undefined or invalid medal.
|
||||||
|
*/
|
||||||
|
var Unknown = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* I Said Funkin'!
|
||||||
|
* Start the game for the first time.
|
||||||
|
*/
|
||||||
|
var StartGame = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80894 #else 60960 #end;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* That's How You Do It!
|
||||||
|
* Beat Tutoria l in Story Mode (on any difficulty).
|
||||||
|
*/
|
||||||
|
var StoryTutorial = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80906 #else 83647 #end;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* More Like Daddy Queerest
|
||||||
|
* Beat Week 1 in Story Mode (on any difficulty).
|
||||||
|
*/
|
||||||
|
var StoryWeek1 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80899 #else 60961 #end;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IT IS THE SPOOKY MONTH
|
||||||
|
* Beat Week 2 in Story Mode (on any difficulty).
|
||||||
|
*/
|
||||||
|
var StoryWeek2 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80900 #else 83648 #end;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pico Funny
|
||||||
|
* Beat Week 3 in Story Mode (on any difficulty).
|
||||||
|
*/
|
||||||
|
var StoryWeek3 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80901 #else 83649 #end;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mommy Must Murder
|
||||||
|
* Beat Week 4 in Story Mode (on any difficulty).
|
||||||
|
*/
|
||||||
|
var StoryWeek4 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80902 #else 83650 #end;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Yule Tide Joy
|
||||||
|
* Beat Week 5 in Story Mode (on any difficulty).
|
||||||
|
*/
|
||||||
|
var StoryWeek5 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80903 #else 83651 #end;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Visual Novelty
|
||||||
|
* Beat Week 6 in Story Mode (on any difficulty).
|
||||||
|
*/
|
||||||
|
var StoryWeek6 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80904 #else 83652 #end;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* I <3 JohnnyUtah
|
||||||
|
* Beat Week 7 in Story Mode (on any difficulty).
|
||||||
|
*/
|
||||||
|
var StoryWeek7 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80905 #else 83653 #end;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Yo, Really Think So?
|
||||||
|
* Beat Weekend 1 in Story Mode (on any difficulty).
|
||||||
|
*/
|
||||||
|
var StoryWeekend1 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80907 #else 83654 #end;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stay Funky
|
||||||
|
* Press TAB in Freeplay and unlock your first character.
|
||||||
|
*/
|
||||||
|
var CharSelect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 83633 #else 83655 #end;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Challenger Appears
|
||||||
|
* Beat any Pico remix in Freeplay (on any difficulty).
|
||||||
|
*/
|
||||||
|
var FreeplayPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80910 #else 83656 #end;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* De-Stressing
|
||||||
|
* Beat Stress (Pico Mix) in Freeplay (on Normal difficulty or higher).
|
||||||
|
*/
|
||||||
|
var FreeplayStressPico = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 83629 #else 83657 #end;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* L
|
||||||
|
* Earn a Loss rating on any song (on any difficulty).
|
||||||
|
*/
|
||||||
|
var LossRating = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80915 #else 83658 #end;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getting Freaky
|
||||||
|
* Earn a Perfect rating on any song on Hard difficulty or higher.
|
||||||
|
* NOTE: Should also be awarded for a Gold Perfect because otherwise that would be annoying.
|
||||||
|
*/
|
||||||
|
var PerfectRatingHard = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80908 #else 83659 #end;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* You Should Drink More Water
|
||||||
|
* Earn a Golden Perfect rating on any song on Hard difficulty or higher.
|
||||||
|
*/
|
||||||
|
var GoldPerfectRatingHard = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80909 #else 83660 #end;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Harder Than Hard
|
||||||
|
* Beat any Erect remix in Freeplay on Erect or Nightmare difficulty.
|
||||||
|
*/
|
||||||
|
var ErectDifficulty = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80911 #else 83661 #end;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Rap God
|
||||||
|
* Earn a Gold Perfect rating on any song on Nightmare difficulty.
|
||||||
|
*/
|
||||||
|
var GoldPerfectRatingNightmare = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80912 #else 83662 #end;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Just like the game!
|
||||||
|
* Get freaky on a Friday.
|
||||||
|
* NOTE: You must beat at least one song on any difficulty.
|
||||||
|
*/
|
||||||
|
var FridayNight = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80913 #else 61034 #end;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nice
|
||||||
|
* Earn a rating of EXACTLY 69% (good luck).
|
||||||
|
*/
|
||||||
|
var Nice = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80914 #else 83646 #end;
|
||||||
|
|
||||||
|
public function getId():Int
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getMedalByStoryLevel(levelId:String):Medal
|
||||||
|
{
|
||||||
|
switch (levelId)
|
||||||
|
{
|
||||||
|
case "week1":
|
||||||
|
return StoryWeek1;
|
||||||
|
case "week2":
|
||||||
|
return StoryWeek2;
|
||||||
|
case "week3":
|
||||||
|
return StoryWeek3;
|
||||||
|
case "week4":
|
||||||
|
return StoryWeek4;
|
||||||
|
case "week5":
|
||||||
|
return StoryWeek5;
|
||||||
|
case "week6":
|
||||||
|
return StoryWeek6;
|
||||||
|
case "week7":
|
||||||
|
return StoryWeek7;
|
||||||
|
case "weekend1":
|
||||||
|
return StoryWeekend1;
|
||||||
|
default:
|
||||||
|
return Unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
package funkin.api.newgrounds;
|
|
||||||
|
|
||||||
#if newgrounds
|
|
||||||
import io.newgrounds.NG;
|
|
||||||
import io.newgrounds.NGLite;
|
|
||||||
import io.newgrounds.components.ScoreBoardComponent.Period;
|
|
||||||
import io.newgrounds.objects.Error;
|
|
||||||
import io.newgrounds.objects.Medal;
|
|
||||||
import io.newgrounds.objects.Score;
|
|
||||||
import io.newgrounds.objects.ScoreBoard;
|
|
||||||
import io.newgrounds.objects.events.Response;
|
|
||||||
import io.newgrounds.objects.events.Result.GetCurrentVersionResult;
|
|
||||||
import io.newgrounds.objects.events.Result.GetVersionResult;
|
|
||||||
#end
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains any script functions which should be BLOCKED from use by modded scripts.
|
|
||||||
*/
|
|
||||||
class NGUnsafe
|
|
||||||
{
|
|
||||||
static public function logEvent(event:String)
|
|
||||||
{
|
|
||||||
#if newgrounds
|
|
||||||
NG.core.calls.event.logEvent(event).send();
|
|
||||||
trace('should have logged: ' + event);
|
|
||||||
#else
|
|
||||||
#if FEATURE_DEBUG_FUNCTIONS
|
|
||||||
trace('event:$event - not logged, missing NG.io lib');
|
|
||||||
#end
|
|
||||||
#end
|
|
||||||
}
|
|
||||||
|
|
||||||
static public function unlockMedal(id:Int)
|
|
||||||
{
|
|
||||||
#if newgrounds
|
|
||||||
if (isLoggedIn)
|
|
||||||
{
|
|
||||||
var medal = NG.core.medals.get(id);
|
|
||||||
if (!medal.unlocked) medal.sendUnlock();
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
#if FEATURE_DEBUG_FUNCTIONS
|
|
||||||
trace('medal:$id - not unlocked, missing NG.io lib');
|
|
||||||
#end
|
|
||||||
#end
|
|
||||||
}
|
|
||||||
|
|
||||||
static public function postScore(score:Int = 0, song:String)
|
|
||||||
{
|
|
||||||
#if newgrounds
|
|
||||||
if (isLoggedIn)
|
|
||||||
{
|
|
||||||
for (id in NG.core.scoreBoards.keys())
|
|
||||||
{
|
|
||||||
var board = NG.core.scoreBoards.get(id);
|
|
||||||
|
|
||||||
if (song == board.name)
|
|
||||||
{
|
|
||||||
board.postScore(score, "Uhh meow?");
|
|
||||||
}
|
|
||||||
|
|
||||||
// trace('loaded scoreboard id:$id, name:${board.name}');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
#if FEATURE_DEBUG_FUNCTIONS
|
|
||||||
trace('Song:$song, Score:$score - not posted, missing NG.io lib');
|
|
||||||
#end
|
|
||||||
#end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,243 +0,0 @@
|
|||||||
package funkin.api.newgrounds;
|
|
||||||
|
|
||||||
import flixel.util.FlxSignal;
|
|
||||||
import flixel.util.FlxTimer;
|
|
||||||
import lime.app.Application;
|
|
||||||
import openfl.display.Stage;
|
|
||||||
#if newgrounds
|
|
||||||
import io.newgrounds.NG;
|
|
||||||
import io.newgrounds.NGLite;
|
|
||||||
import io.newgrounds.components.ScoreBoardComponent.Period;
|
|
||||||
import io.newgrounds.objects.Error;
|
|
||||||
import io.newgrounds.objects.Medal;
|
|
||||||
import io.newgrounds.objects.Score;
|
|
||||||
import io.newgrounds.objects.ScoreBoard;
|
|
||||||
import io.newgrounds.objects.events.Response;
|
|
||||||
import io.newgrounds.objects.events.Result.GetCurrentVersionResult;
|
|
||||||
import io.newgrounds.objects.events.Result.GetVersionResult;
|
|
||||||
#end
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains any script functions which should be ALLOWD for use by modded scripts.
|
|
||||||
*/
|
|
||||||
class NGUtil
|
|
||||||
{
|
|
||||||
#if newgrounds
|
|
||||||
/**
|
|
||||||
* True, if the saved sessionId was used in the initial login, and failed to connect.
|
|
||||||
* Used in MainMenuState to show a popup to establish a new connection
|
|
||||||
*/
|
|
||||||
public static var savedSessionFailed(default, null):Bool = false;
|
|
||||||
|
|
||||||
public static var scoreboardsLoaded:Bool = false;
|
|
||||||
public static var isLoggedIn(get, never):Bool;
|
|
||||||
|
|
||||||
inline static function get_isLoggedIn()
|
|
||||||
{
|
|
||||||
return NG.core != null && NG.core.loggedIn;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static var scoreboardArray:Array<Score> = [];
|
|
||||||
|
|
||||||
public static var ngDataLoaded(default, null):FlxSignal = new FlxSignal();
|
|
||||||
public static var ngScoresLoaded(default, null):FlxSignal = new FlxSignal();
|
|
||||||
|
|
||||||
public static var GAME_VER:String = "";
|
|
||||||
|
|
||||||
static public function checkVersion(callback:String->Void)
|
|
||||||
{
|
|
||||||
trace('checking NG.io version');
|
|
||||||
GAME_VER = "v" + Application.current.meta.get('version');
|
|
||||||
|
|
||||||
NG.core.calls.app.getCurrentVersion(GAME_VER).addDataHandler(function(response) {
|
|
||||||
GAME_VER = response.result.data.currentVersion;
|
|
||||||
trace('CURRENT NG VERSION: ' + GAME_VER);
|
|
||||||
callback(GAME_VER);
|
|
||||||
}).send();
|
|
||||||
}
|
|
||||||
|
|
||||||
static public function init()
|
|
||||||
{
|
|
||||||
var api = APIStuff.API;
|
|
||||||
if (api == null || api.length == 0)
|
|
||||||
{
|
|
||||||
trace("Missing Newgrounds API key, aborting connection");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
trace("connecting to newgrounds");
|
|
||||||
|
|
||||||
#if NG_FORCE_EXPIRED_SESSION
|
|
||||||
var sessionId:String = "fake_session_id";
|
|
||||||
function onSessionFail(error:Error)
|
|
||||||
{
|
|
||||||
trace("Forcing an expired saved session. " + "To disable, comment out NG_FORCE_EXPIRED_SESSION in Project.xml");
|
|
||||||
savedSessionFailed = true;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
var sessionId:String = NGLite.getSessionId();
|
|
||||||
if (sessionId != null) trace("found web session id");
|
|
||||||
|
|
||||||
#if (debug)
|
|
||||||
if (sessionId == null && APIStuff.SESSION != null)
|
|
||||||
{
|
|
||||||
trace("using debug session id");
|
|
||||||
sessionId = APIStuff.SESSION;
|
|
||||||
}
|
|
||||||
#end
|
|
||||||
|
|
||||||
var onSessionFail:Error->Void = null;
|
|
||||||
if (sessionId == null && Save.instance.ngSessionId != null)
|
|
||||||
{
|
|
||||||
trace("using stored session id");
|
|
||||||
sessionId = Save.instance.ngSessionId;
|
|
||||||
onSessionFail = function(error) savedSessionFailed = true;
|
|
||||||
}
|
|
||||||
#end
|
|
||||||
|
|
||||||
NG.create(api, sessionId, #if NG_DEBUG true #else false #end, onSessionFail);
|
|
||||||
|
|
||||||
#if NG_VERBOSE
|
|
||||||
NG.core.verbose = true;
|
|
||||||
#end
|
|
||||||
// Set the encryption cipher/format to RC4/Base64. AES128 and Hex are not implemented yet
|
|
||||||
NG.core.initEncryption(APIStuff.EncKey); // Found in you NG project view
|
|
||||||
|
|
||||||
if (NG.core.attemptingLogin)
|
|
||||||
{
|
|
||||||
/* a session_id was found in the loadervars, this means the user is playing on newgrounds.com
|
|
||||||
* and we should login shortly. lets wait for that to happen
|
|
||||||
*/
|
|
||||||
trace("attempting login");
|
|
||||||
NG.core.onLogin.add(onNGLogin);
|
|
||||||
}
|
|
||||||
// GK: taking out auto login, adding a login button to the main menu
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// /* They are NOT playing on newgrounds.com, no session id was found. We must start one manually, if we want to.
|
|
||||||
// * Note: This will cause a new browser window to pop up where they can log in to newgrounds
|
|
||||||
// */
|
|
||||||
// NG.core.requestLogin(onNGLogin);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to log in to newgrounds by requesting a new session ID, only call if no session ID was found automatically
|
|
||||||
* @param popupLauncher The function to call to open the login url, must be inside
|
|
||||||
* a user input event or the popup blocker will block it.
|
|
||||||
* @param onComplete A callback with the result of the connection.
|
|
||||||
*/
|
|
||||||
static public function login(?popupLauncher:(Void->Void)->Void, onComplete:ConnectionResult->Void)
|
|
||||||
{
|
|
||||||
trace("Logging in manually");
|
|
||||||
var onPending:Void->Void = null;
|
|
||||||
if (popupLauncher != null)
|
|
||||||
{
|
|
||||||
onPending = function() popupLauncher(NG.core.openPassportUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
var onSuccess:Void->Void = onNGLogin;
|
|
||||||
var onFail:Error->Void = null;
|
|
||||||
var onCancel:Void->Void = null;
|
|
||||||
if (onComplete != null)
|
|
||||||
{
|
|
||||||
onSuccess = function() {
|
|
||||||
onNGLogin();
|
|
||||||
onComplete(Success);
|
|
||||||
}
|
|
||||||
onFail = function(e) onComplete(Fail(e.message));
|
|
||||||
onCancel = function() onComplete(Cancelled);
|
|
||||||
}
|
|
||||||
|
|
||||||
NG.core.requestLogin(onSuccess, onPending, onFail, onCancel);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline static public function cancelLogin():Void
|
|
||||||
{
|
|
||||||
NG.core.cancelLoginRequest();
|
|
||||||
}
|
|
||||||
|
|
||||||
static function onNGLogin():Void
|
|
||||||
{
|
|
||||||
trace('logged in! user:${NG.core.user.name}');
|
|
||||||
Save.instance.ngSessionId = NG.core.sessionId;
|
|
||||||
Save.instance.flush();
|
|
||||||
// Load medals then call onNGMedalFetch()
|
|
||||||
NG.core.requestMedals(onNGMedalFetch);
|
|
||||||
|
|
||||||
// Load Scoreboards hten call onNGBoardsFetch()
|
|
||||||
NG.core.requestScoreBoards(onNGBoardsFetch);
|
|
||||||
|
|
||||||
ngDataLoaded.dispatch();
|
|
||||||
}
|
|
||||||
|
|
||||||
static public function logout()
|
|
||||||
{
|
|
||||||
NG.core.logOut();
|
|
||||||
|
|
||||||
Save.instance.ngSessionId = null;
|
|
||||||
Save.instance.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- MEDALS
|
|
||||||
static function onNGMedalFetch():Void
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
// Reading medal info
|
|
||||||
for (id in NG.core.medals.keys())
|
|
||||||
{
|
|
||||||
var medal = NG.core.medals.get(id);
|
|
||||||
trace('loaded medal id:$id, name:${medal.name}, description:${medal.description}');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unlocking medals
|
|
||||||
var unlockingMedal = NG.core.medals.get(54352);// medal ids are listed in your NG project viewer
|
|
||||||
if (!unlockingMedal.unlocked)
|
|
||||||
unlockingMedal.sendUnlock();
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- SCOREBOARDS
|
|
||||||
static function onNGBoardsFetch():Void
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
// Reading medal info
|
|
||||||
for (id in NG.core.scoreBoards.keys())
|
|
||||||
{
|
|
||||||
var board = NG.core.scoreBoards.get(id);
|
|
||||||
trace('loaded scoreboard id:$id, name:${board.name}');
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
// var board = NG.core.scoreBoards.get(8004);// ID found in NG project view
|
|
||||||
|
|
||||||
// Posting a score thats OVER 9000!
|
|
||||||
// board.postScore(FlxG.random.int(0, 1000));
|
|
||||||
|
|
||||||
// --- To view the scores you first need to select the range of scores you want to see ---
|
|
||||||
|
|
||||||
// add an update listener so we know when we get the new scores
|
|
||||||
// board.onUpdate.add(onNGScoresFetch);
|
|
||||||
trace("shoulda got score by NOW!");
|
|
||||||
// board.requestScores(20);// get the best 10 scores ever logged
|
|
||||||
// more info on scores --- http://www.newgrounds.io/help/components/#scoreboard-getscores
|
|
||||||
}
|
|
||||||
|
|
||||||
static function onNGScoresFetch():Void
|
|
||||||
{
|
|
||||||
scoreboardsLoaded = true;
|
|
||||||
|
|
||||||
ngScoresLoaded.dispatch();
|
|
||||||
/*
|
|
||||||
for (score in NG.core.scoreBoards.get(8737).scores)
|
|
||||||
{
|
|
||||||
trace('score loaded user:${score.user.name}, score:${score.formatted_value}');
|
|
||||||
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// var board = NG.core.scoreBoards.get(8004);// ID found in NG project view
|
|
||||||
// board.postScore(HighScore.score);
|
|
||||||
|
|
||||||
// NGUtil.scoreboardArray = NG.core.scoreBoards.get(8004).scores;
|
|
||||||
}
|
|
||||||
#end
|
|
||||||
}
|
|
||||||
@@ -1,298 +0,0 @@
|
|||||||
package funkin.api.newgrounds;
|
|
||||||
|
|
||||||
#if newgrounds
|
|
||||||
import flixel.util.FlxSignal;
|
|
||||||
import io.newgrounds.NG;
|
|
||||||
import io.newgrounds.NGLite;
|
|
||||||
import io.newgrounds.objects.Error;
|
|
||||||
import io.newgrounds.objects.Score;
|
|
||||||
import lime.app.Application;
|
|
||||||
#end
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MADE BY GEOKURELI THE LEGENED GOD HERO MVP
|
|
||||||
*/
|
|
||||||
class NGio
|
|
||||||
{
|
|
||||||
#if newgrounds
|
|
||||||
/**
|
|
||||||
* True, if the saved sessionId was used in the initial login, and failed to connect.
|
|
||||||
* Used in MainMenuState to show a popup to establish a new connection
|
|
||||||
*/
|
|
||||||
public static var savedSessionFailed(default, null):Bool = false;
|
|
||||||
|
|
||||||
public static var scoreboardsLoaded:Bool = false;
|
|
||||||
public static var isLoggedIn(get, never):Bool;
|
|
||||||
|
|
||||||
inline static function get_isLoggedIn()
|
|
||||||
{
|
|
||||||
return NG.core != null && NG.core.loggedIn;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static var scoreboardArray:Array<Score> = [];
|
|
||||||
|
|
||||||
public static var ngDataLoaded(default, null):FlxSignal = new FlxSignal();
|
|
||||||
public static var ngScoresLoaded(default, null):FlxSignal = new FlxSignal();
|
|
||||||
|
|
||||||
public static var GAME_VER:String = "";
|
|
||||||
|
|
||||||
static public function checkVersion(callback:String->Void)
|
|
||||||
{
|
|
||||||
trace('checking NG.io version');
|
|
||||||
GAME_VER = "v" + Application.current.meta.get('version');
|
|
||||||
|
|
||||||
NG.core.calls.app.getCurrentVersion(GAME_VER).addDataHandler(function(response) {
|
|
||||||
GAME_VER = response.result.data.currentVersion;
|
|
||||||
trace('CURRENT NG VERSION: ' + GAME_VER);
|
|
||||||
callback(GAME_VER);
|
|
||||||
}).send();
|
|
||||||
}
|
|
||||||
|
|
||||||
static public function init()
|
|
||||||
{
|
|
||||||
var api = APIStuff.API;
|
|
||||||
if (api == null || api.length == 0)
|
|
||||||
{
|
|
||||||
trace("Missing Newgrounds API key, aborting connection");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
trace("connecting to newgrounds");
|
|
||||||
|
|
||||||
#if NG_FORCE_EXPIRED_SESSION
|
|
||||||
var sessionId:String = "fake_session_id";
|
|
||||||
function onSessionFail(error:Error)
|
|
||||||
{
|
|
||||||
trace("Forcing an expired saved session. " + "To disable, comment out NG_FORCE_EXPIRED_SESSION in Project.xml");
|
|
||||||
savedSessionFailed = true;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
var sessionId:String = NGLite.getSessionId();
|
|
||||||
if (sessionId != null) trace("found web session id");
|
|
||||||
|
|
||||||
#if (debug)
|
|
||||||
if (sessionId == null && APIStuff.SESSION != null)
|
|
||||||
{
|
|
||||||
trace("using debug session id");
|
|
||||||
sessionId = APIStuff.SESSION;
|
|
||||||
}
|
|
||||||
#end
|
|
||||||
|
|
||||||
var onSessionFail:Error->Void = null;
|
|
||||||
if (sessionId == null && Save.instance.ngSessionId != null)
|
|
||||||
{
|
|
||||||
trace("using stored session id");
|
|
||||||
sessionId = Save.instance.ngSessionId;
|
|
||||||
onSessionFail = function(error) savedSessionFailed = true;
|
|
||||||
}
|
|
||||||
#end
|
|
||||||
|
|
||||||
NG.create(api, sessionId, #if NG_DEBUG true #else false #end, onSessionFail);
|
|
||||||
|
|
||||||
#if NG_VERBOSE
|
|
||||||
NG.core.verbose = true;
|
|
||||||
#end
|
|
||||||
// Set the encryption cipher/format to RC4/Base64. AES128 and Hex are not implemented yet
|
|
||||||
NG.core.initEncryption(APIStuff.EncKey); // Found in you NG project view
|
|
||||||
|
|
||||||
if (NG.core.attemptingLogin)
|
|
||||||
{
|
|
||||||
/* a session_id was found in the loadervars, this means the user is playing on newgrounds.com
|
|
||||||
* and we should login shortly. lets wait for that to happen
|
|
||||||
*/
|
|
||||||
trace("attempting login");
|
|
||||||
NG.core.onLogin.add(onNGLogin);
|
|
||||||
}
|
|
||||||
// GK: taking out auto login, adding a login button to the main menu
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// /* They are NOT playing on newgrounds.com, no session id was found. We must start one manually, if we want to.
|
|
||||||
// * Note: This will cause a new browser window to pop up where they can log in to newgrounds
|
|
||||||
// */
|
|
||||||
// NG.core.requestLogin(onNGLogin);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to log in to newgrounds by requesting a new session ID, only call if no session ID was found automatically
|
|
||||||
* @param popupLauncher The function to call to open the login url, must be inside
|
|
||||||
* a user input event or the popup blocker will block it.
|
|
||||||
* @param onComplete A callback with the result of the connection.
|
|
||||||
*/
|
|
||||||
static public function login(?popupLauncher:(Void->Void)->Void, onComplete:ConnectionResult->Void)
|
|
||||||
{
|
|
||||||
trace("Logging in manually");
|
|
||||||
var onPending:Void->Void = null;
|
|
||||||
if (popupLauncher != null)
|
|
||||||
{
|
|
||||||
onPending = function() popupLauncher(NG.core.openPassportUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
var onSuccess:Void->Void = onNGLogin;
|
|
||||||
var onFail:Error->Void = null;
|
|
||||||
var onCancel:Void->Void = null;
|
|
||||||
if (onComplete != null)
|
|
||||||
{
|
|
||||||
onSuccess = function() {
|
|
||||||
onNGLogin();
|
|
||||||
onComplete(Success);
|
|
||||||
}
|
|
||||||
onFail = function(e) onComplete(Fail(e.message));
|
|
||||||
onCancel = function() onComplete(Cancelled);
|
|
||||||
}
|
|
||||||
|
|
||||||
NG.core.requestLogin(onSuccess, onPending, onFail, onCancel);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline static public function cancelLogin():Void
|
|
||||||
{
|
|
||||||
NG.core.cancelLoginRequest();
|
|
||||||
}
|
|
||||||
|
|
||||||
static function onNGLogin():Void
|
|
||||||
{
|
|
||||||
trace('logged in! user:${NG.core.user.name}');
|
|
||||||
Save.instance.ngSessionId = NG.core.sessionId;
|
|
||||||
Save.instance.flush();
|
|
||||||
// Load medals then call onNGMedalFetch()
|
|
||||||
NG.core.requestMedals(onNGMedalFetch);
|
|
||||||
|
|
||||||
// Load Scoreboards hten call onNGBoardsFetch()
|
|
||||||
NG.core.requestScoreBoards(onNGBoardsFetch);
|
|
||||||
|
|
||||||
ngDataLoaded.dispatch();
|
|
||||||
}
|
|
||||||
|
|
||||||
static public function logout()
|
|
||||||
{
|
|
||||||
NG.core.logOut();
|
|
||||||
|
|
||||||
Save.instance.ngSessionId = null;
|
|
||||||
Save.instance.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- MEDALS
|
|
||||||
static function onNGMedalFetch():Void
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
// Reading medal info
|
|
||||||
for (id in NG.core.medals.keys())
|
|
||||||
{
|
|
||||||
var medal = NG.core.medals.get(id);
|
|
||||||
trace('loaded medal id:$id, name:${medal.name}, description:${medal.description}');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unlocking medals
|
|
||||||
var unlockingMedal = NG.core.medals.get(54352);// medal ids are listed in your NG project viewer
|
|
||||||
if (!unlockingMedal.unlocked)
|
|
||||||
unlockingMedal.sendUnlock();
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- SCOREBOARDS
|
|
||||||
static function onNGBoardsFetch():Void
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
// Reading medal info
|
|
||||||
for (id in NG.core.scoreBoards.keys())
|
|
||||||
{
|
|
||||||
var board = NG.core.scoreBoards.get(id);
|
|
||||||
trace('loaded scoreboard id:$id, name:${board.name}');
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
// var board = NG.core.scoreBoards.get(8004);// ID found in NG project view
|
|
||||||
|
|
||||||
// Posting a score thats OVER 9000!
|
|
||||||
// board.postScore(FlxG.random.int(0, 1000));
|
|
||||||
|
|
||||||
// --- To view the scores you first need to select the range of scores you want to see ---
|
|
||||||
|
|
||||||
// add an update listener so we know when we get the new scores
|
|
||||||
// board.onUpdate.add(onNGScoresFetch);
|
|
||||||
trace("shoulda got score by NOW!");
|
|
||||||
// board.requestScores(20);// get the best 10 scores ever logged
|
|
||||||
// more info on scores --- http://www.newgrounds.io/help/components/#scoreboard-getscores
|
|
||||||
}
|
|
||||||
|
|
||||||
static function onNGScoresFetch():Void
|
|
||||||
{
|
|
||||||
scoreboardsLoaded = true;
|
|
||||||
|
|
||||||
ngScoresLoaded.dispatch();
|
|
||||||
/*
|
|
||||||
for (score in NG.core.scoreBoards.get(8737).scores)
|
|
||||||
{
|
|
||||||
trace('score loaded user:${score.user.name}, score:${score.formatted_value}');
|
|
||||||
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// var board = NG.core.scoreBoards.get(8004);// ID found in NG project view
|
|
||||||
// board.postScore(HighScore.score);
|
|
||||||
|
|
||||||
// NGio.scoreboardArray = NG.core.scoreBoards.get(8004).scores;
|
|
||||||
}
|
|
||||||
#end
|
|
||||||
|
|
||||||
static public function logEvent(event:String)
|
|
||||||
{
|
|
||||||
#if newgrounds
|
|
||||||
NG.core.calls.event.logEvent(event).send();
|
|
||||||
trace('should have logged: ' + event);
|
|
||||||
#else
|
|
||||||
#if FEATURE_DEBUG_FUNCTIONS
|
|
||||||
trace('event:$event - not logged, missing NG.io lib');
|
|
||||||
#end
|
|
||||||
#end
|
|
||||||
}
|
|
||||||
|
|
||||||
static public function unlockMedal(id:Int)
|
|
||||||
{
|
|
||||||
#if newgrounds
|
|
||||||
if (isLoggedIn)
|
|
||||||
{
|
|
||||||
var medal = NG.core.medals.get(id);
|
|
||||||
if (!medal.unlocked) medal.sendUnlock();
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
#if FEATURE_DEBUG_FUNCTIONS
|
|
||||||
trace('medal:$id - not unlocked, missing NG.io lib');
|
|
||||||
#end
|
|
||||||
#end
|
|
||||||
}
|
|
||||||
|
|
||||||
static public function postScore(score:Int = 0, song:String)
|
|
||||||
{
|
|
||||||
#if newgrounds
|
|
||||||
if (isLoggedIn)
|
|
||||||
{
|
|
||||||
for (id in NG.core.scoreBoards.keys())
|
|
||||||
{
|
|
||||||
var board = NG.core.scoreBoards.get(id);
|
|
||||||
|
|
||||||
if (song == board.name)
|
|
||||||
{
|
|
||||||
board.postScore(score, "Uhh meow?");
|
|
||||||
}
|
|
||||||
|
|
||||||
// trace('loaded scoreboard id:$id, name:${board.name}');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
#if FEATURE_DEBUG_FUNCTIONS
|
|
||||||
trace('Song:$song, Score:$score - not posted, missing NG.io lib');
|
|
||||||
#end
|
|
||||||
#end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ConnectionResult
|
|
||||||
{
|
|
||||||
/** Log in successful */
|
|
||||||
Success;
|
|
||||||
|
|
||||||
/** Could not login */
|
|
||||||
Fail(msg:String);
|
|
||||||
|
|
||||||
/** User cancelled the login */
|
|
||||||
Cancelled;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,334 @@
|
|||||||
|
package funkin.api.newgrounds;
|
||||||
|
|
||||||
|
import funkin.save.Save;
|
||||||
|
import funkin.api.newgrounds.Medals.Medal;
|
||||||
|
#if FEATURE_NEWGROUNDS
|
||||||
|
import io.newgrounds.Call.CallError;
|
||||||
|
import io.newgrounds.NG;
|
||||||
|
import io.newgrounds.NGLite;
|
||||||
|
import io.newgrounds.NGLite.LoginOutcome;
|
||||||
|
import io.newgrounds.NGLite.LoginFail;
|
||||||
|
import io.newgrounds.objects.events.Outcome;
|
||||||
|
import io.newgrounds.utils.MedalList;
|
||||||
|
import io.newgrounds.utils.ScoreBoardList;
|
||||||
|
import io.newgrounds.objects.User;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
|
class NewgroundsClient
|
||||||
|
{
|
||||||
|
public static var instance(get, never):NewgroundsClient;
|
||||||
|
static var _instance:Null<NewgroundsClient> = null;
|
||||||
|
|
||||||
|
static function get_instance():NewgroundsClient
|
||||||
|
{
|
||||||
|
if (NewgroundsClient._instance == null) _instance = new NewgroundsClient();
|
||||||
|
if (NewgroundsClient._instance == null) throw "Could not initialize singleton NewgroundsClient!";
|
||||||
|
return NewgroundsClient._instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var user(get, never):Null<User>;
|
||||||
|
public var medals(get, never):Null<MedalList>;
|
||||||
|
public var leaderboards(get, never):Null<ScoreBoardList>;
|
||||||
|
|
||||||
|
private function new()
|
||||||
|
{
|
||||||
|
trace('[NEWGROUNDS] Initializing client...');
|
||||||
|
|
||||||
|
#if FEATURE_NEWGROUNDS_DEBUG
|
||||||
|
trace('[NEWGROUNDS] App ID: ${NewgroundsCredentials.APP_ID}');
|
||||||
|
trace('[NEWGROUNDS] Encryption Key: ${NewgroundsCredentials.ENCRYPTION_KEY}');
|
||||||
|
#end
|
||||||
|
|
||||||
|
if (!hasValidCredentials())
|
||||||
|
{
|
||||||
|
FlxG.log.warn("Tried to initialize Newgrounds client, but credentials are invalid!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var debug = #if FEATURE_NEWGROUNDS_DEBUG true #else false #end;
|
||||||
|
NG.create(NewgroundsCredentials.APP_ID, getSessionId(), debug, onLoginResolved);
|
||||||
|
NG.core.setupEncryption(NewgroundsCredentials.ENCRYPTION_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function init()
|
||||||
|
{
|
||||||
|
if (NG.core == null) return;
|
||||||
|
|
||||||
|
trace('[NEWGROUNDS] Setting up connection...');
|
||||||
|
|
||||||
|
#if FEATURE_NEWGROUNDS_DEBUG
|
||||||
|
NG.core.verbose = true;
|
||||||
|
#end
|
||||||
|
|
||||||
|
NG.core.onLogin.add(onLoginSuccessful);
|
||||||
|
|
||||||
|
if (NG.core.attemptingLogin)
|
||||||
|
{
|
||||||
|
// Session ID was valid and we should be logged in soon.
|
||||||
|
trace('[NEWGROUNDS] Waiting for existing login!');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#if FEATURE_NEWGROUNDS_AUTOLOGIN
|
||||||
|
// Attempt an automatic login.
|
||||||
|
trace('[NEWGROUNDS] Attempting new login immediately!');
|
||||||
|
this.autoLogin();
|
||||||
|
#else
|
||||||
|
trace('[NEWGROUNDS] Not logged in, you have to login manually!');
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to log into Newgrounds and create a session ID.
|
||||||
|
* @param onSuccess An optional callback for when the login is successful.
|
||||||
|
* @param onError An optional callback for when the login fails.
|
||||||
|
*/
|
||||||
|
public function login(?onSuccess:Void->Void, ?onError:Void->Void):Void
|
||||||
|
{
|
||||||
|
if (NG.core == null)
|
||||||
|
{
|
||||||
|
FlxG.log.warn("No Newgrounds client initialized! Are your credentials invalid?");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onSuccess != null && onError != null)
|
||||||
|
{
|
||||||
|
NG.core.requestLogin(onLoginResolvedWithCallbacks.bind(_, onSuccess, onError));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NG.core.requestLogin(onLoginResolved);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function autoLogin(?onSuccess:Void->Void, ?onError:Void->Void):Void
|
||||||
|
{
|
||||||
|
if (NG.core == null)
|
||||||
|
{
|
||||||
|
FlxG.log.warn("No Newgrounds client initialized! Are your credentials invalid?");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dummyPassport:String->Void = function(_) {
|
||||||
|
// just a dummy passport, so we don't create a popup
|
||||||
|
// otherwise `NG.core.requestLogin()` will automatically attempt to open a tab at the beginning of the game
|
||||||
|
// users should go to the Options Menu to login to NG
|
||||||
|
// we cancel the request, so we can call it later
|
||||||
|
NG.core.cancelLoginRequest();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (onSuccess != null && onError != null)
|
||||||
|
{
|
||||||
|
NG.core.requestLogin(onLoginResolvedWithCallbacks.bind(_, onSuccess, onError), dummyPassport);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NG.core.requestLogin(onLoginResolved, dummyPassport);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log out of Newgrounds and invalidate the current session.
|
||||||
|
* @param onSuccess An optional callback for when the logout is successful.
|
||||||
|
*/
|
||||||
|
public function logout(?onSuccess:Void->Void, ?onError:Void->Void):Void
|
||||||
|
{
|
||||||
|
if (NG.core != null)
|
||||||
|
{
|
||||||
|
if (onSuccess != null && onError != null)
|
||||||
|
{
|
||||||
|
NG.core.logOut(onLogoutResolvedWithCallbacks.bind(_, onSuccess, onError));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NG.core.logOut(onLogoutResolved);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Save.instance.ngSessionId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return `true` if the user is logged in to Newgrounds.
|
||||||
|
*/
|
||||||
|
public function isLoggedIn():Bool
|
||||||
|
{
|
||||||
|
#if FEATURE_NEWGROUNDS
|
||||||
|
return NG.core != null && NG.core.loggedIn;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns `false` if either the app ID or the encryption key is invalid.
|
||||||
|
*/
|
||||||
|
static function hasValidCredentials():Bool
|
||||||
|
{
|
||||||
|
return !(NewgroundsCredentials.APP_ID == null
|
||||||
|
|| NewgroundsCredentials.APP_ID == ""
|
||||||
|
|| NewgroundsCredentials.APP_ID.contains(" ")
|
||||||
|
|| NewgroundsCredentials.ENCRYPTION_KEY == null
|
||||||
|
|| NewgroundsCredentials.ENCRYPTION_KEY == ""
|
||||||
|
|| NewgroundsCredentials.ENCRYPTION_KEY.contains(" "));
|
||||||
|
}
|
||||||
|
|
||||||
|
function onLoginResolved(outcome:LoginOutcome):Void
|
||||||
|
{
|
||||||
|
switch (outcome)
|
||||||
|
{
|
||||||
|
case SUCCESS:
|
||||||
|
onLoginSuccessful();
|
||||||
|
case FAIL(result):
|
||||||
|
onLoginFailed(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onLoginResolvedWithCallbacks(outcome:LoginOutcome, onSuccess:Void->Void, onError:Void->Void):Void
|
||||||
|
{
|
||||||
|
onLoginResolved(outcome);
|
||||||
|
|
||||||
|
switch (outcome)
|
||||||
|
{
|
||||||
|
case SUCCESS:
|
||||||
|
onSuccess();
|
||||||
|
case FAIL(result):
|
||||||
|
onError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onLogoutResolved(outcome:Outcome<CallError>):Void
|
||||||
|
{
|
||||||
|
switch (outcome)
|
||||||
|
{
|
||||||
|
case SUCCESS:
|
||||||
|
onLogoutSuccessful();
|
||||||
|
case FAIL(result):
|
||||||
|
onLogoutFailed(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onLogoutResolvedWithCallbacks(outcome:Outcome<CallError>, onSuccess:Void->Void, onError:Void->Void):Void
|
||||||
|
{
|
||||||
|
onLogoutResolved(outcome);
|
||||||
|
|
||||||
|
switch (outcome)
|
||||||
|
{
|
||||||
|
case SUCCESS:
|
||||||
|
onSuccess();
|
||||||
|
case FAIL(result):
|
||||||
|
onError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onLoginSuccessful():Void
|
||||||
|
{
|
||||||
|
if (NG.core == null) return;
|
||||||
|
|
||||||
|
trace('[NEWGROUNDS] Login successful!');
|
||||||
|
|
||||||
|
// Persist the session ID.
|
||||||
|
Save.instance.ngSessionId = NG.core.sessionId;
|
||||||
|
|
||||||
|
trace('[NEWGROUNDS] Submitting medal request...');
|
||||||
|
NG.core.requestMedals(onFetchedMedals);
|
||||||
|
|
||||||
|
trace('[NEWGROUNDS] Submitting leaderboard request...');
|
||||||
|
NG.core.scoreBoards.loadList(onFetchedLeaderboards);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onLoginFailed(result:LoginFail):Void
|
||||||
|
{
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case CANCELLED(type):
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case PASSPORT:
|
||||||
|
trace('[NEWGROUNDS] Login cancelled by passport website.');
|
||||||
|
case MANUAL:
|
||||||
|
trace('[NEWGROUNDS] Login cancelled by application.');
|
||||||
|
default:
|
||||||
|
trace('[NEWGROUNDS] Login cancelled by unknown source.');
|
||||||
|
}
|
||||||
|
case ERROR(error):
|
||||||
|
switch (error)
|
||||||
|
{
|
||||||
|
case HTTP(error):
|
||||||
|
trace('[NEWGROUNDS] Login failed due to HTTP error: ${error}');
|
||||||
|
case RESPONSE(error):
|
||||||
|
trace('[NEWGROUNDS] Login failed due to response error: ${error.message} (${error.code})');
|
||||||
|
case RESULT(error):
|
||||||
|
trace('[NEWGROUNDS] Login failed due to result error: ${error.message} (${error.code})');
|
||||||
|
default:
|
||||||
|
trace('[NEWGROUNDS] Login failed due to unknown error: ${error}');
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
trace('[NEWGROUNDS] Login failed due to unknown reason.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onLogoutSuccessful():Void
|
||||||
|
{
|
||||||
|
trace('[NEWGROUNDS] Logout successful!');
|
||||||
|
}
|
||||||
|
|
||||||
|
function onLogoutFailed(result:CallError):Void
|
||||||
|
{
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case HTTP(error):
|
||||||
|
trace('[NEWGROUNDS] Logout failed due to HTTP error: ${error}');
|
||||||
|
case RESPONSE(error):
|
||||||
|
trace('[NEWGROUNDS] Logout failed due to response error: ${error.message} (${error.code})');
|
||||||
|
case RESULT(error):
|
||||||
|
trace('[NEWGROUNDS] Logout failed due to result error: ${error.message} (${error.code})');
|
||||||
|
default:
|
||||||
|
trace('[NEWGROUNDS] Logout failed due to unknown error: ${result}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFetchedMedals(outcome:Outcome<CallError>):Void
|
||||||
|
{
|
||||||
|
trace('[NEWGROUNDS] Fetched medals!');
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFetchedLeaderboards(outcome:Outcome<CallError>):Void
|
||||||
|
{
|
||||||
|
trace('[NEWGROUNDS] Fetched leaderboards!');
|
||||||
|
|
||||||
|
// trace(funkin.api.newgrounds.Leaderboards.listLeaderboardData());
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_user():Null<User>
|
||||||
|
{
|
||||||
|
if (NG.core == null || !this.isLoggedIn()) return null;
|
||||||
|
return NG.core.user;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_medals():Null<MedalList>
|
||||||
|
{
|
||||||
|
if (NG.core == null || !this.isLoggedIn()) return null;
|
||||||
|
return NG.core.medals;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_leaderboards():Null<ScoreBoardList>
|
||||||
|
{
|
||||||
|
if (NG.core == null || !this.isLoggedIn()) return null;
|
||||||
|
return NG.core.scoreBoards;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function getSessionId():Null<String>
|
||||||
|
{
|
||||||
|
#if js
|
||||||
|
// We can fetch the session ID from the URL.
|
||||||
|
var result:Null<String> = NGLite.getSessionId();
|
||||||
|
if (result != null) return result;
|
||||||
|
#end
|
||||||
|
|
||||||
|
// We have to fetch the session ID from the save file.
|
||||||
|
return Save.instance.ngSessionId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#end
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
package funkin.api.newgrounds;
|
|
||||||
|
|
||||||
#if newgrounds
|
|
||||||
import funkin.NGio;
|
|
||||||
import funkin.ui.Prompt;
|
|
||||||
|
|
||||||
class NgPrompt extends Prompt
|
|
||||||
{
|
|
||||||
public function new(text:String, style:ButtonStyle = Yes_No)
|
|
||||||
{
|
|
||||||
super(text, style);
|
|
||||||
}
|
|
||||||
|
|
||||||
static public function showLogin()
|
|
||||||
{
|
|
||||||
return showLoginPrompt(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
static public function showSavedSessionFailed()
|
|
||||||
{
|
|
||||||
return showLoginPrompt(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
static function showLoginPrompt(fromUi:Bool)
|
|
||||||
{
|
|
||||||
var prompt = new NgPrompt("Talking to server...", None);
|
|
||||||
prompt.openCallback = NGUtil.login.bind(function popupLauncher(openPassportUrl)
|
|
||||||
{
|
|
||||||
var choiceMsg = fromUi ? #if web "Log in to Newgrounds?" #else null #end // User-input needed to allow popups
|
|
||||||
: "Your session has expired.\n Please login again.";
|
|
||||||
|
|
||||||
if (choiceMsg != null)
|
|
||||||
{
|
|
||||||
prompt.setText(choiceMsg);
|
|
||||||
prompt.setButtons(Yes_No);
|
|
||||||
#if web
|
|
||||||
prompt.buttons.getItem("yes").fireInstantly = true;
|
|
||||||
#end
|
|
||||||
prompt.onYes = function() {
|
|
||||||
prompt.setText("Connecting..." #if web + "\n(check your popup blocker)" #end);
|
|
||||||
prompt.setButtons(None);
|
|
||||||
openPassportUrl();
|
|
||||||
};
|
|
||||||
prompt.onNo = function() {
|
|
||||||
prompt.close();
|
|
||||||
prompt = null;
|
|
||||||
NGio.cancelLogin();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
prompt.setText("Connecting...");
|
|
||||||
openPassportUrl();
|
|
||||||
}
|
|
||||||
}, function onLoginComplete(result:ConnectionResult)
|
|
||||||
{
|
|
||||||
switch (result)
|
|
||||||
{
|
|
||||||
case Success:
|
|
||||||
{
|
|
||||||
prompt.setText("Login Successful");
|
|
||||||
prompt.setButtons(Ok);
|
|
||||||
prompt.onYes = prompt.close;
|
|
||||||
}
|
|
||||||
case Fail(msg):
|
|
||||||
{
|
|
||||||
trace("Login Error:" + msg);
|
|
||||||
prompt.setText("Login failed");
|
|
||||||
prompt.setButtons(Ok);
|
|
||||||
prompt.onYes = prompt.close;
|
|
||||||
}
|
|
||||||
case Cancelled:
|
|
||||||
{
|
|
||||||
if (prompt != null)
|
|
||||||
{
|
|
||||||
prompt.setText("Login cancelled by user");
|
|
||||||
prompt.setButtons(Ok);
|
|
||||||
prompt.onYes = prompt.close;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
trace("Login cancelled via prompt");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return prompt;
|
|
||||||
}
|
|
||||||
|
|
||||||
static public function showLogout()
|
|
||||||
{
|
|
||||||
var user = io.newgrounds.NG.core.user.name;
|
|
||||||
var prompt = new NgPrompt('Log out of $user?', Yes_No);
|
|
||||||
prompt.onYes = function() {
|
|
||||||
NGio.logout();
|
|
||||||
prompt.close();
|
|
||||||
};
|
|
||||||
prompt.onNo = prompt.close;
|
|
||||||
return prompt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#end
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
# funkin.api.newgrounds
|
|
||||||
|
|
||||||
This package contains two main classes:
|
|
||||||
- `NGUtil` contains utility functions for interacting with the Newgrounds API.
|
|
||||||
- This includes any functions which scripts should be able to use,
|
|
||||||
such as retrieving achievement status.
|
|
||||||
- `NGUnsafe` contains sensitive utility functions for interacting with the Newgrounds API.
|
|
||||||
- This includes any functions which scripts should not be able to use,
|
|
||||||
such as writing high scores or posting achievements.
|
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package funkin.api.newgrounds;
|
||||||
|
|
||||||
|
#if FEATURE_NEWGROUNDS
|
||||||
|
import io.newgrounds.NG;
|
||||||
|
#end
|
||||||
|
import funkin.api.newgrounds.NewgroundsClient;
|
||||||
|
import funkin.util.WindowUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles referrals to links retrieved from the Newgrounds API.
|
||||||
|
*/
|
||||||
|
class Referral
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Opens the referral page with the given name.
|
||||||
|
* @param referralName The name of the referral.
|
||||||
|
* @param fallbackUrl The URL to open if the referral fails or is not available.
|
||||||
|
*/
|
||||||
|
static function doReferral(referralName:String, fallbackUrl:String):Void
|
||||||
|
{
|
||||||
|
#if FEATURE_NEWGROUNDS
|
||||||
|
if (NewgroundsClient.instance.isLoggedIn())
|
||||||
|
{
|
||||||
|
NG.core?.calls.loader.loadReferral(false)
|
||||||
|
.addComponentParameter("referral_name", referralName)
|
||||||
|
.addResponseHandler(response -> {
|
||||||
|
trace(response);
|
||||||
|
if (response.success)
|
||||||
|
{
|
||||||
|
WindowUtil.openURL(response.result.data.url);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WindowUtil.openURL(fallbackUrl);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.send();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WindowUtil.openURL(fallbackUrl);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
WindowUtil.openURL(fallbackUrl);
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the merch referral page.
|
||||||
|
*/
|
||||||
|
public static function doMerchReferral():Void
|
||||||
|
{
|
||||||
|
doReferral("merch_link", Constants.URL_MERCH_FALLBACK);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
package funkin.audio;
|
package funkin.audio;
|
||||||
|
|
||||||
import flash.media.Sound;
|
import flash.media.Sound;
|
||||||
#if flash11
|
|
||||||
import flash.utils.ByteArray;
|
|
||||||
#end
|
|
||||||
import flixel.sound.FlxSound;
|
import flixel.sound.FlxSound;
|
||||||
import flixel.system.FlxAssets.FlxSoundAsset;
|
import flixel.system.FlxAssets.FlxSoundAsset;
|
||||||
import openfl.Assets;
|
import openfl.Assets;
|
||||||
|
|||||||
@@ -13,9 +13,11 @@ import funkin.data.song.SongRegistry;
|
|||||||
import funkin.util.tools.ICloneable;
|
import funkin.util.tools.ICloneable;
|
||||||
import funkin.util.flixel.sound.FlxPartialSound;
|
import funkin.util.flixel.sound.FlxPartialSound;
|
||||||
import funkin.Paths.PathsFunction;
|
import funkin.Paths.PathsFunction;
|
||||||
import openfl.Assets;
|
|
||||||
import lime.app.Future;
|
|
||||||
import lime.app.Promise;
|
import lime.app.Promise;
|
||||||
|
import lime.media.AudioSource;
|
||||||
|
import openfl.events.Event;
|
||||||
|
import openfl.media.Sound;
|
||||||
|
import openfl.media.SoundChannel;
|
||||||
import openfl.media.SoundMixer;
|
import openfl.media.SoundMixer;
|
||||||
|
|
||||||
#if (openfl >= "8.0.0")
|
#if (openfl >= "8.0.0")
|
||||||
@@ -110,6 +112,11 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
|||||||
return _waveformData;
|
return _waveformData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, the game will forcefully add this sound's channel to the list of playing sounds.
|
||||||
|
*/
|
||||||
|
public var important:Bool = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Are we in a state where the song should play but time is negative?
|
* Are we in a state where the song should play but time is negative?
|
||||||
*/
|
*/
|
||||||
@@ -147,6 +154,14 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
super.update(elapsedSec);
|
super.update(elapsedSec);
|
||||||
|
|
||||||
|
@:privateAccess
|
||||||
|
{
|
||||||
|
if (important && _channel != null && !SoundMixer.__soundChannels.contains(_channel))
|
||||||
|
{
|
||||||
|
SoundMixer.__soundChannels.push(_channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,13 +448,14 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
|||||||
* @param persist Whether to keep this `FunkinSound` between states, or destroy it.
|
* @param persist Whether to keep this `FunkinSound` between states, or destroy it.
|
||||||
* @param onComplete Called when the sound finished playing.
|
* @param onComplete Called when the sound finished playing.
|
||||||
* @param onLoad Called when the sound finished loading. Called immediately for succesfully loaded embedded sounds.
|
* @param onLoad Called when the sound finished loading. Called immediately for succesfully loaded embedded sounds.
|
||||||
|
* @param important If `true`, the sound channel will forcefully be added onto the channel array, even if full. Use sparingly!
|
||||||
* @return A `FunkinSound` object, or `null` if the sound could not be loaded.
|
* @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,
|
public static function load(embeddedSound:FlxSoundAsset, volume:Float = 1.0, looped:Bool = false, autoDestroy:Bool = false, autoPlay:Bool = false,
|
||||||
persist:Bool = false, ?onComplete:Void->Void, ?onLoad:Void->Void):Null<FunkinSound>
|
persist:Bool = false, ?onComplete:Void->Void, ?onLoad:Void->Void, important:Bool = false):Null<FunkinSound>
|
||||||
{
|
{
|
||||||
@:privateAccess
|
@:privateAccess
|
||||||
if (SoundMixer.__soundChannels.length >= SoundMixer.MAX_ACTIVE_CHANNELS)
|
if (SoundMixer.__soundChannels.length >= SoundMixer.MAX_ACTIVE_CHANNELS && !important)
|
||||||
{
|
{
|
||||||
FlxG.log.error('FunkinSound could not play sound, channels exhausted! Found ${SoundMixer.__soundChannels.length} active sound channels.');
|
FlxG.log.error('FunkinSound could not play sound, channels exhausted! Found ${SoundMixer.__soundChannels.length} active sound channels.');
|
||||||
return null;
|
return null;
|
||||||
@@ -464,6 +480,7 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
|||||||
sound.volume = volume;
|
sound.volume = volume;
|
||||||
sound.group = FlxG.sound.defaultSoundGroup;
|
sound.group = FlxG.sound.defaultSoundGroup;
|
||||||
sound.persist = persist;
|
sound.persist = persist;
|
||||||
|
sound.important = important;
|
||||||
|
|
||||||
// Make sure to add the sound to the list.
|
// Make sure to add the sound to the list.
|
||||||
// If it's already in, it won't get re-added.
|
// If it's already in, it won't get re-added.
|
||||||
@@ -536,15 +553,50 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
|||||||
this._label = 'unknown';
|
this._label = 'unknown';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@:access(openfl.media.Sound)
|
||||||
|
@:access(openfl.media.SoundChannel)
|
||||||
|
@:access(openfl.media.SoundMixer)
|
||||||
|
override function startSound(startTime:Float)
|
||||||
|
{
|
||||||
|
if (!important)
|
||||||
|
{
|
||||||
|
super.startSound(startTime);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_time = startTime;
|
||||||
|
_paused = false;
|
||||||
|
|
||||||
|
if (_sound == null) return;
|
||||||
|
|
||||||
|
// Create a channel manually if the sound is considered important.
|
||||||
|
var pan:Float = FlxMath.bound(SoundMixer.__soundTransform.pan + _transform.pan, -1, 1);
|
||||||
|
var volume:Float = FlxMath.bound(SoundMixer.__soundTransform.volume * _transform.volume, 0, MAX_VOLUME);
|
||||||
|
|
||||||
|
var audioSource:AudioSource = new AudioSource(_sound.__buffer);
|
||||||
|
audioSource.offset = Std.int(startTime);
|
||||||
|
audioSource.gain = volume;
|
||||||
|
|
||||||
|
var position:lime.math.Vector4 = audioSource.position;
|
||||||
|
position.x = pan;
|
||||||
|
position.z = -1 * Math.sqrt(1 - Math.pow(pan, 2));
|
||||||
|
audioSource.position = position;
|
||||||
|
|
||||||
|
_channel = new SoundChannel(_sound, audioSource, _transform);
|
||||||
|
_channel.addEventListener(Event.SOUND_COMPLETE, stopped);
|
||||||
|
pitch = _pitch;
|
||||||
|
active = true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Play a sound effect once, then destroy it.
|
* Play a sound effect once, then destroy it.
|
||||||
* @param key
|
* @param key
|
||||||
* @param volume
|
* @param volume
|
||||||
* @return A `FunkinSound` object, or `null` if the sound could not be loaded.
|
* @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):Null<FunkinSound>
|
public static function playOnce(key:String, volume:Float = 1.0, ?onComplete:Void->Void, ?onLoad:Void->Void, important:Bool = false):Null<FunkinSound>
|
||||||
{
|
{
|
||||||
var result:Null<FunkinSound> = FunkinSound.load(key, volume, false, true, true, false, onComplete, onLoad);
|
var result:Null<FunkinSound> = FunkinSound.load(key, volume, false, true, true, false, onComplete, onLoad, important);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package funkin.audio;
|
package funkin.audio;
|
||||||
|
|
||||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||||
import funkin.audio.FunkinSound;
|
|
||||||
import flixel.tweens.FlxTween;
|
import flixel.tweens.FlxTween;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,60 +1,76 @@
|
|||||||
package funkin.audio.visualize;
|
package funkin.audio.visualize;
|
||||||
|
|
||||||
|
import funkin.graphics.FunkinSprite;
|
||||||
import flixel.FlxSprite;
|
import flixel.FlxSprite;
|
||||||
import flixel.graphics.frames.FlxAtlasFrames;
|
import flixel.graphics.frames.FlxAtlasFrames;
|
||||||
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
||||||
import flixel.sound.FlxSound;
|
import flixel.sound.FlxSound;
|
||||||
import funkin.vis.dsp.SpectralAnalyzer;
|
import funkin.vis.dsp.SpectralAnalyzer;
|
||||||
import funkin.vis.audioclip.frontends.LimeAudioClip;
|
|
||||||
|
|
||||||
using Lambda;
|
using Lambda;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
||||||
{
|
{
|
||||||
// public var vis:VisShit;
|
// public var vis:VisShit;
|
||||||
var analyzer:SpectralAnalyzer;
|
var analyzer:Null<SpectralAnalyzer> = null;
|
||||||
|
|
||||||
var volumes:Array<Float> = [];
|
var volumes:Array<Float> = [];
|
||||||
|
|
||||||
public var snd:FlxSound;
|
public var snd:Null<FlxSound> = null;
|
||||||
|
|
||||||
public function new(snd:FlxSound)
|
static final BAR_COUNT:Int = 7;
|
||||||
|
|
||||||
|
// TODO: Make the sprites easier to soft code.
|
||||||
|
public function new(snd:FlxSound, pixel:Bool)
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.snd = snd;
|
this.snd = snd;
|
||||||
|
|
||||||
// vis = new VisShit(snd);
|
var visCount = pixel ? (BAR_COUNT + 1) : (BAR_COUNT + 1);
|
||||||
// vis.snd = snd;
|
var visScale = pixel ? 6 : 1;
|
||||||
|
|
||||||
var visFrms:FlxAtlasFrames = Paths.getSparrowAtlas('aBotViz');
|
var visFrms:FlxAtlasFrames = Paths.getSparrowAtlas(pixel ? 'characters/abotPixel/aBotVizPixel' : 'characters/abot/aBotViz');
|
||||||
|
|
||||||
// these are the differences in X position, from left to right
|
// these are the differences in X position, from left to right
|
||||||
var positionX:Array<Float> = [0, 59, 56, 66, 54, 52, 51];
|
var positionX:Array<Float> = pixel ? [0, 7 * 6, 8 * 6, 9 * 6, 10 * 6, 6 * 6, 7 * 6] : [0, 59, 56, 66, 54, 52, 51];
|
||||||
var positionY:Array<Float> = [0, -8, -3.5, -0.4, 0.5, 4.7, 7];
|
var positionY:Array<Float> = pixel ? [0, -2 * 6, -1 * 6, 0, 0, 1 * 6, 2 * 6] : [0, -8, -3.5, -0.4, 0.5, 4.7, 7];
|
||||||
|
|
||||||
for (lol in 1...8)
|
for (index in 1...visCount)
|
||||||
{
|
{
|
||||||
// pushes initial value
|
// pushes initial value
|
||||||
volumes.push(0.0);
|
volumes.push(0.0);
|
||||||
var sum = function(num:Float, total:Float) return total += num;
|
|
||||||
var posX:Float = positionX.slice(0, lol).fold(sum, 0);
|
|
||||||
var posY:Float = positionY.slice(0, lol).fold(sum, 0);
|
|
||||||
|
|
||||||
var viz:FlxSprite = new FlxSprite(posX, posY);
|
// Sum the offsets up to the current index
|
||||||
|
var sum = function(num:Float, total:Float) return total += num;
|
||||||
|
var posX:Float = positionX.slice(0, index).fold(sum, 0);
|
||||||
|
var posY:Float = positionY.slice(0, index).fold(sum, 0);
|
||||||
|
|
||||||
|
var viz:FunkinSprite = new FunkinSprite(posX, posY);
|
||||||
viz.frames = visFrms;
|
viz.frames = visFrms;
|
||||||
|
viz.antialiasing = pixel ? false : true;
|
||||||
|
viz.scale.set(visScale, visScale);
|
||||||
add(viz);
|
add(viz);
|
||||||
|
|
||||||
var visStr = 'viz';
|
var visStr = 'viz';
|
||||||
viz.animation.addByPrefix('VIZ', visStr + lol, 0);
|
viz.animation.addByPrefix('VIZ', '$visStr${index}0', 0);
|
||||||
viz.animation.play('VIZ', false, false, 6);
|
viz.animation.play('VIZ', false, false, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function initAnalyzer()
|
public function initAnalyzer():Void
|
||||||
{
|
{
|
||||||
|
if (snd == null) return;
|
||||||
|
|
||||||
@:privateAccess
|
@:privateAccess
|
||||||
analyzer = new SpectralAnalyzer(snd._channel.__audioSource, 7, 0.1, 40);
|
analyzer = new SpectralAnalyzer(snd._channel.__audioSource, BAR_COUNT, 0.1, 40);
|
||||||
|
// A-Bot tuning...
|
||||||
|
analyzer.minDb = -65;
|
||||||
|
analyzer.maxDb = -25;
|
||||||
|
analyzer.maxFreq = 22000;
|
||||||
|
// we use a very low minFreq since some songs use low low subbass like a boss
|
||||||
|
analyzer.minFreq = 10;
|
||||||
|
|
||||||
#if desktop
|
#if desktop
|
||||||
// On desktop it uses FFT stuff that isn't as optimized as the direct browser stuff we use on HTML5
|
// On desktop it uses FFT stuff that isn't as optimized as the direct browser stuff we use on HTML5
|
||||||
@@ -66,16 +82,17 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
|||||||
// analyzer.fftN = 2048;
|
// analyzer.fftN = 2048;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function dumpSound():Void
|
||||||
|
{
|
||||||
|
snd = null;
|
||||||
|
analyzer = null;
|
||||||
|
}
|
||||||
|
|
||||||
var visTimer:Float = -1;
|
var visTimer:Float = -1;
|
||||||
var visTimeMax:Float = 1 / 30;
|
var visTimeMax:Float = 1 / 30;
|
||||||
|
|
||||||
override function update(elapsed:Float)
|
override function update(elapsed:Float)
|
||||||
{
|
{
|
||||||
// updateViz();
|
|
||||||
|
|
||||||
// updateFFT(elapsed);
|
|
||||||
|
|
||||||
//
|
|
||||||
super.update(elapsed);
|
super.update(elapsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,8 +103,8 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
|||||||
|
|
||||||
override function draw()
|
override function draw()
|
||||||
{
|
{
|
||||||
if (analyzer != null) drawFFT();
|
|
||||||
super.draw();
|
super.draw();
|
||||||
|
drawFFT();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -95,17 +112,17 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
|||||||
*/
|
*/
|
||||||
function drawFFT():Void
|
function drawFFT():Void
|
||||||
{
|
{
|
||||||
var levels = analyzer.getLevels();
|
var levels = (analyzer != null) ? analyzer.getLevels() : getDefaultLevels();
|
||||||
|
|
||||||
for (i in 0...min(group.members.length, levels.length))
|
for (i in 0...min(group.members.length, levels.length))
|
||||||
{
|
{
|
||||||
var animFrame:Int = Math.round(levels[i].value * 5);
|
var animFrame:Int = Math.round(levels[i].value * 6);
|
||||||
|
|
||||||
#if desktop
|
// don't display if we're at 0 volume from the level
|
||||||
// Web version scales with the Flixel volume level.
|
group.members[i].visible = animFrame > 0;
|
||||||
// This line brings platform parity but looks worse.
|
|
||||||
// animFrame = Math.round(animFrame * FlxG.sound.volume);
|
// decrement our animFrame, so we can get a value from 0-5 for animation frames
|
||||||
#end
|
animFrame -= 1;
|
||||||
|
|
||||||
animFrame = Math.floor(Math.min(5, animFrame));
|
animFrame = Math.floor(Math.min(5, animFrame));
|
||||||
animFrame = Math.floor(Math.max(0, animFrame));
|
animFrame = Math.floor(Math.max(0, animFrame));
|
||||||
@@ -116,79 +133,19 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// function updateFFT(elapsed:Float)
|
/**
|
||||||
// {
|
* Explicitly define the default levels to draw when the analyzer is not available.
|
||||||
// if (vis.snd != null)
|
* @return Array<Bar>
|
||||||
// {
|
*/
|
||||||
// vis.checkAndSetBuffer();
|
static function getDefaultLevels():Array<Bar>
|
||||||
// if (vis.setBuffer)
|
{
|
||||||
// {
|
var result:Array<Bar> = [];
|
||||||
// var remappedShit:Int = 0;
|
|
||||||
// if (vis.snd.playing) remappedShit = Std.int(FlxMath.remapToRange(vis.snd.time, 0, vis.snd.length, 0, vis.numSamples));
|
for (i in 0...BAR_COUNT)
|
||||||
// else
|
{
|
||||||
// remappedShit = Std.int(FlxMath.remapToRange(Conductor.instance.songPosition, 0, vis.snd.length, 0, vis.numSamples));
|
result.push({value: 0, peak: 0.0});
|
||||||
// var fftSamples:Array<Float> = [];
|
}
|
||||||
// var swagBucks = remappedShit;
|
|
||||||
// for (i in remappedShit...remappedShit + (Std.int((44100 * (1 / 144)))))
|
return result;
|
||||||
// {
|
}
|
||||||
// var left = vis.audioData[swagBucks] / 32767;
|
|
||||||
// var right = vis.audioData[swagBucks + 1] / 32767;
|
|
||||||
// var balanced = (left + right) / 2;
|
|
||||||
// swagBucks += 2;
|
|
||||||
// fftSamples.push(balanced);
|
|
||||||
// }
|
|
||||||
// var freqShit = vis.funnyFFT(fftSamples);
|
|
||||||
// for (i in 0...group.members.length)
|
|
||||||
// {
|
|
||||||
// var getSliceShit = function(s:Int) {
|
|
||||||
// var powShit = FlxMath.remapToRange(s, 0, group.members.length, 0, MathUtil.logBase(10, freqShit[0].length));
|
|
||||||
// return Math.round(Math.pow(10, powShit));
|
|
||||||
// };
|
|
||||||
// // var powShit:Float = getSliceShit(i);
|
|
||||||
// var hzSliced:Int = getSliceShit(i);
|
|
||||||
// var sliceLength:Int = Std.int(freqShit[0].length / group.members.length);
|
|
||||||
// var volSlice = freqShit[0].slice(hzSliced, getSliceShit(i + 1));
|
|
||||||
// var avgVel:Float = 0;
|
|
||||||
// for (slice in volSlice)
|
|
||||||
// {
|
|
||||||
// avgVel += slice;
|
|
||||||
// }
|
|
||||||
// avgVel /= volSlice.length;
|
|
||||||
// avgVel *= 10000000;
|
|
||||||
// volumes[i] += avgVel - (elapsed * (volumes[i] * 50));
|
|
||||||
// var animFrame:Int = Std.int(volumes[i]);
|
|
||||||
// animFrame = Math.floor(Math.min(5, animFrame));
|
|
||||||
// animFrame = Math.floor(Math.max(0, animFrame));
|
|
||||||
// animFrame = Std.int(Math.abs(animFrame - 5)); // shitty dumbass flip, cuz dave got da shit backwards lol!
|
|
||||||
// group.members[i].animation.curAnim.curFrame = animFrame;
|
|
||||||
// if (FlxG.keys.justPressed.U)
|
|
||||||
// {
|
|
||||||
// trace(avgVel);
|
|
||||||
// trace(group.members[i].animation.curAnim.curFrame);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// // group.members[0].animation.curAnim.curFrame =
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// public function updateViz()
|
|
||||||
// {
|
|
||||||
// if (vis.snd != null)
|
|
||||||
// {
|
|
||||||
// var remappedShit:Int = 0;
|
|
||||||
// vis.checkAndSetBuffer();
|
|
||||||
// if (vis.setBuffer)
|
|
||||||
// {
|
|
||||||
// // var startingSample:Int = Std.int(FlxMath.remapToRange)
|
|
||||||
// if (vis.snd.playing) remappedShit = Std.int(FlxMath.remapToRange(vis.snd.time, 0, vis.snd.length, 0, vis.numSamples));
|
|
||||||
// for (i in 0...group.members.length)
|
|
||||||
// {
|
|
||||||
// var sampleApprox:Int = Std.int(FlxMath.remapToRange(i, 0, group.members.length, remappedShit, remappedShit + 500));
|
|
||||||
// var left = vis.audioData[sampleApprox] / 32767;
|
|
||||||
// var animFrame:Int = Std.int(FlxMath.remapToRange(left, -1, 1, 0, 6));
|
|
||||||
// group.members[i].animation.curAnim.curFrame = animFrame;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import flixel.math.FlxMath;
|
|||||||
import flixel.math.FlxPoint;
|
import flixel.math.FlxPoint;
|
||||||
import flixel.sound.FlxSound;
|
import flixel.sound.FlxSound;
|
||||||
import flixel.util.FlxColor;
|
import flixel.util.FlxColor;
|
||||||
import funkin.audio.visualize.VisShit;
|
import funkin.audio.visualize.VisShit.CurAudioInfo;
|
||||||
import funkin.graphics.rendering.MeshRender;
|
import funkin.graphics.rendering.MeshRender;
|
||||||
import lime.utils.Int16Array;
|
import lime.utils.Int16Array;
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ typedef EntryConstructorFunction = String->Void;
|
|||||||
* @param J The type of the JSON data used when constructing.
|
* @param J The type of the JSON data used when constructing.
|
||||||
*/
|
*/
|
||||||
@:generic
|
@:generic
|
||||||
|
@:autoBuild(funkin.util.macro.DataRegistryMacro.buildRegistry())
|
||||||
abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructorFunction>), J>
|
abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructorFunction>), J>
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@@ -189,6 +190,19 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
|||||||
return entries.get(id);
|
return entries.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of all entries included in the base game.
|
||||||
|
* The actual function exists and is auto-generated on each registry at build time.
|
||||||
|
* @return Array<String>
|
||||||
|
*/
|
||||||
|
// public function listBaseGameEntryIds():Array<String> {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of all entries that are not included in the base game.
|
||||||
|
* @return Array<String>
|
||||||
|
*/
|
||||||
|
// public function listModdedEntryIds():Array<String> {}
|
||||||
|
|
||||||
public function toString():String
|
public function toString():String
|
||||||
{
|
{
|
||||||
return 'Registry(' + registryId + ', ${countEntries()} entries)';
|
return 'Registry(' + registryId + ', ${countEntries()} entries)';
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package funkin.data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface which automatically implements essential fields for a class extending `BaseRegistry`.
|
||||||
|
*
|
||||||
|
* @see `funkin.data.BaseRegistry`
|
||||||
|
*/
|
||||||
|
@:autoBuild(funkin.util.macro.RegistryMacro.buildRegistry())
|
||||||
|
interface DefaultRegistryImpl {}
|
||||||
@@ -5,6 +5,7 @@ package funkin.data;
|
|||||||
* A `String->Void` constructor is also mandatory, but enforced elsewhere.
|
* A `String->Void` constructor is also mandatory, but enforced elsewhere.
|
||||||
* @param T The JSON data type of the registry entry.
|
* @param T The JSON data type of the registry entry.
|
||||||
*/
|
*/
|
||||||
|
@:autoBuild(funkin.util.macro.RegistryMacro.buildEntry())
|
||||||
interface IRegistryEntry<T>
|
interface IRegistryEntry<T>
|
||||||
{
|
{
|
||||||
public final id:String;
|
public final id:String;
|
||||||
|
|||||||
@@ -17,6 +17,16 @@ class AnimationDataUtil
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param data
|
||||||
|
* @param name (adds index to name)
|
||||||
|
* @return Array<AnimationData>
|
||||||
|
*/
|
||||||
|
public static function toNamedArray(data:Array<UnnamedAnimationData>, ?name:String = ""):Array<AnimationData>
|
||||||
|
{
|
||||||
|
return data.mapi(function(animItem, ind) return toNamed(animItem, '$name$ind'));
|
||||||
|
}
|
||||||
|
|
||||||
public static function toUnnamed(data:AnimationData):UnnamedAnimationData
|
public static function toUnnamed(data:AnimationData):UnnamedAnimationData
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
@@ -30,6 +40,11 @@ class AnimationDataUtil
|
|||||||
frameIndices: data.frameIndices
|
frameIndices: data.frameIndices
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function toUnnamedArray(data:Array<AnimationData>):Array<UnnamedAnimationData>
|
||||||
|
{
|
||||||
|
return data.map(toUnnamed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ package funkin.data.dialogue.conversation;
|
|||||||
|
|
||||||
import funkin.play.cutscene.dialogue.Conversation;
|
import funkin.play.cutscene.dialogue.Conversation;
|
||||||
import funkin.play.cutscene.dialogue.ScriptedConversation;
|
import funkin.play.cutscene.dialogue.ScriptedConversation;
|
||||||
|
import funkin.util.tools.ISingleton;
|
||||||
|
import funkin.data.DefaultRegistryImpl;
|
||||||
|
|
||||||
class ConversationRegistry extends BaseRegistry<Conversation, ConversationData>
|
class ConversationRegistry extends BaseRegistry<Conversation, ConversationData> implements ISingleton implements DefaultRegistryImpl
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The current version string for the dialogue box data format.
|
* The current version string for the dialogue box data format.
|
||||||
@@ -14,74 +16,8 @@ class ConversationRegistry extends BaseRegistry<Conversation, ConversationData>
|
|||||||
|
|
||||||
public static final CONVERSATION_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
|
public static final CONVERSATION_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
|
||||||
|
|
||||||
public static var instance(get, never):ConversationRegistry;
|
|
||||||
static var _instance:Null<ConversationRegistry> = null;
|
|
||||||
|
|
||||||
static function get_instance():ConversationRegistry
|
|
||||||
{
|
|
||||||
if (_instance == null) _instance = new ConversationRegistry();
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function new()
|
public function new()
|
||||||
{
|
{
|
||||||
super('CONVERSATION', 'dialogue/conversations', CONVERSATION_DATA_VERSION_RULE);
|
super('CONVERSATION', 'dialogue/conversations', CONVERSATION_DATA_VERSION_RULE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
|
||||||
*/
|
|
||||||
public function parseEntryData(id:String):Null<ConversationData>
|
|
||||||
{
|
|
||||||
// JsonParser does not take type parameters,
|
|
||||||
// otherwise this function would be in BaseRegistry.
|
|
||||||
var parser = new json2object.JsonParser<ConversationData>();
|
|
||||||
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<ConversationData>
|
|
||||||
{
|
|
||||||
var parser = new json2object.JsonParser<ConversationData>();
|
|
||||||
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):Conversation
|
|
||||||
{
|
|
||||||
return ScriptedConversation.init(clsName, "unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
function getScriptedClassNames():Array<String>
|
|
||||||
{
|
|
||||||
return ScriptedConversation.listScriptClasses();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ package funkin.data.dialogue.dialoguebox;
|
|||||||
import funkin.play.cutscene.dialogue.DialogueBox;
|
import funkin.play.cutscene.dialogue.DialogueBox;
|
||||||
import funkin.data.dialogue.dialoguebox.DialogueBoxData;
|
import funkin.data.dialogue.dialoguebox.DialogueBoxData;
|
||||||
import funkin.play.cutscene.dialogue.ScriptedDialogueBox;
|
import funkin.play.cutscene.dialogue.ScriptedDialogueBox;
|
||||||
|
import funkin.util.tools.ISingleton;
|
||||||
|
import funkin.data.DefaultRegistryImpl;
|
||||||
|
|
||||||
class DialogueBoxRegistry extends BaseRegistry<DialogueBox, DialogueBoxData>
|
class DialogueBoxRegistry extends BaseRegistry<DialogueBox, DialogueBoxData> implements ISingleton implements DefaultRegistryImpl
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The current version string for the dialogue box data format.
|
* The current version string for the dialogue box data format.
|
||||||
@@ -13,76 +15,10 @@ class DialogueBoxRegistry extends BaseRegistry<DialogueBox, DialogueBoxData>
|
|||||||
*/
|
*/
|
||||||
public static final DIALOGUEBOX_DATA_VERSION:thx.semver.Version = "1.1.0";
|
public static final DIALOGUEBOX_DATA_VERSION:thx.semver.Version = "1.1.0";
|
||||||
|
|
||||||
public static final DIALOGUEBOX_DATA_VERSION_RULE:thx.semver.VersionRule = "1.1.x";
|
public static final DIALOGUEBOX_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <1.2.0";
|
||||||
|
|
||||||
public static var instance(get, never):DialogueBoxRegistry;
|
|
||||||
static var _instance:Null<DialogueBoxRegistry> = null;
|
|
||||||
|
|
||||||
static function get_instance():DialogueBoxRegistry
|
|
||||||
{
|
|
||||||
if (_instance == null) _instance = new DialogueBoxRegistry();
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function new()
|
public function new()
|
||||||
{
|
{
|
||||||
super('DIALOGUEBOX', 'dialogue/boxes', DIALOGUEBOX_DATA_VERSION_RULE);
|
super('DIALOGUEBOX', 'dialogue/boxes', DIALOGUEBOX_DATA_VERSION_RULE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
|
||||||
*/
|
|
||||||
public function parseEntryData(id:String):Null<DialogueBoxData>
|
|
||||||
{
|
|
||||||
// JsonParser does not take type parameters,
|
|
||||||
// otherwise this function would be in BaseRegistry.
|
|
||||||
var parser = new json2object.JsonParser<DialogueBoxData>();
|
|
||||||
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<DialogueBoxData>
|
|
||||||
{
|
|
||||||
var parser = new json2object.JsonParser<DialogueBoxData>();
|
|
||||||
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):DialogueBox
|
|
||||||
{
|
|
||||||
return ScriptedDialogueBox.init(clsName, "unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
function getScriptedClassNames():Array<String>
|
|
||||||
{
|
|
||||||
return ScriptedDialogueBox.listScriptClasses();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
package funkin.data.dialogue.speaker;
|
package funkin.data.dialogue.speaker;
|
||||||
|
|
||||||
import funkin.play.cutscene.dialogue.Speaker;
|
import funkin.play.cutscene.dialogue.Speaker;
|
||||||
import funkin.data.dialogue.speaker.SpeakerData;
|
|
||||||
import funkin.play.cutscene.dialogue.ScriptedSpeaker;
|
import funkin.play.cutscene.dialogue.ScriptedSpeaker;
|
||||||
|
import funkin.util.tools.ISingleton;
|
||||||
|
import funkin.data.DefaultRegistryImpl;
|
||||||
|
|
||||||
class SpeakerRegistry extends BaseRegistry<Speaker, SpeakerData>
|
class SpeakerRegistry extends BaseRegistry<Speaker, SpeakerData> implements ISingleton implements DefaultRegistryImpl
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The current version string for the speaker data format.
|
* The current version string for the speaker data format.
|
||||||
@@ -15,74 +16,8 @@ class SpeakerRegistry extends BaseRegistry<Speaker, SpeakerData>
|
|||||||
|
|
||||||
public static final SPEAKER_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
|
public static final SPEAKER_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
|
||||||
|
|
||||||
public static var instance(get, never):SpeakerRegistry;
|
|
||||||
static var _instance:Null<SpeakerRegistry> = null;
|
|
||||||
|
|
||||||
static function get_instance():SpeakerRegistry
|
|
||||||
{
|
|
||||||
if (_instance == null) _instance = new SpeakerRegistry();
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function new()
|
public function new()
|
||||||
{
|
{
|
||||||
super('SPEAKER', 'dialogue/speakers', SPEAKER_DATA_VERSION_RULE);
|
super('SPEAKER', 'dialogue/speakers', SPEAKER_DATA_VERSION_RULE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
|
||||||
*/
|
|
||||||
public function parseEntryData(id:String):Null<SpeakerData>
|
|
||||||
{
|
|
||||||
// JsonParser does not take type parameters,
|
|
||||||
// otherwise this function would be in BaseRegistry.
|
|
||||||
var parser = new json2object.JsonParser<SpeakerData>();
|
|
||||||
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<SpeakerData>
|
|
||||||
{
|
|
||||||
var parser = new json2object.JsonParser<SpeakerData>();
|
|
||||||
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):Speaker
|
|
||||||
{
|
|
||||||
return ScriptedSpeaker.init(clsName, "unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
function getScriptedClassNames():Array<String>
|
|
||||||
{
|
|
||||||
return ScriptedSpeaker.listScriptClasses();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package funkin.data.event;
|
package funkin.data.event;
|
||||||
|
|
||||||
import funkin.play.event.SongEvent;
|
import funkin.play.event.SongEvent;
|
||||||
import funkin.data.event.SongEventSchema;
|
|
||||||
import funkin.data.song.SongData.SongEventData;
|
import funkin.data.song.SongData.SongEventData;
|
||||||
import funkin.util.macro.ClassMacro;
|
import funkin.util.macro.ClassMacro;
|
||||||
import funkin.play.event.ScriptedSongEvent;
|
import funkin.play.event.ScriptedSongEvent;
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
package funkin.data.event;
|
package funkin.data.event;
|
||||||
|
|
||||||
import funkin.play.event.SongEvent;
|
|
||||||
import funkin.data.event.SongEventSchema;
|
|
||||||
import funkin.data.song.SongData.SongEventData;
|
|
||||||
import funkin.util.macro.ClassMacro;
|
|
||||||
import funkin.play.event.ScriptedSongEvent;
|
|
||||||
|
|
||||||
@:forward(name, title, type, keys, min, max, step, units, defaultValue, iterator)
|
@:forward(name, title, type, keys, min, max, step, units, defaultValue, iterator)
|
||||||
abstract SongEventSchema(SongEventSchemaRaw)
|
abstract SongEventSchema(SongEventSchemaRaw)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -36,6 +36,13 @@ typedef AlbumData =
|
|||||||
*/
|
*/
|
||||||
public var albumTitleAsset:String;
|
public var albumTitleAsset:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Offsets for the album title.
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
@:default([0, 0])
|
||||||
|
public var albumTitleOffsets:Null<Array<Float>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An optional array of animations for the album title.
|
* An optional array of animations for the album title.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ package funkin.data.freeplay.album;
|
|||||||
import funkin.ui.freeplay.Album;
|
import funkin.ui.freeplay.Album;
|
||||||
import funkin.data.freeplay.album.AlbumData;
|
import funkin.data.freeplay.album.AlbumData;
|
||||||
import funkin.ui.freeplay.ScriptedAlbum;
|
import funkin.ui.freeplay.ScriptedAlbum;
|
||||||
|
import funkin.util.tools.ISingleton;
|
||||||
|
import funkin.data.DefaultRegistryImpl;
|
||||||
|
|
||||||
class AlbumRegistry extends BaseRegistry<Album, AlbumData>
|
class AlbumRegistry extends BaseRegistry<Album, AlbumData> implements ISingleton implements DefaultRegistryImpl
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The current version string for the album data format.
|
* The current version string for the album data format.
|
||||||
@@ -15,70 +17,8 @@ class AlbumRegistry extends BaseRegistry<Album, AlbumData>
|
|||||||
|
|
||||||
public static final ALBUM_DATA_VERSION_RULE:thx.semver.VersionRule = '1.0.x';
|
public static final ALBUM_DATA_VERSION_RULE:thx.semver.VersionRule = '1.0.x';
|
||||||
|
|
||||||
public static final instance:AlbumRegistry = new AlbumRegistry();
|
|
||||||
|
|
||||||
public function new()
|
public function new()
|
||||||
{
|
{
|
||||||
super('ALBUM', 'ui/freeplay/albums', ALBUM_DATA_VERSION_RULE);
|
super('ALBUM', 'ui/freeplay/albums', ALBUM_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<AlbumData>
|
|
||||||
{
|
|
||||||
// JsonParser does not take type parameters,
|
|
||||||
// otherwise this function would be in BaseRegistry.
|
|
||||||
var parser:json2object.JsonParser<AlbumData> = new json2object.JsonParser<AlbumData>();
|
|
||||||
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<AlbumData>
|
|
||||||
{
|
|
||||||
var parser:json2object.JsonParser<AlbumData> = new json2object.JsonParser<AlbumData>();
|
|
||||||
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):Album
|
|
||||||
{
|
|
||||||
return ScriptedAlbum.init(clsName, 'unknown');
|
|
||||||
}
|
|
||||||
|
|
||||||
function getScriptedClassNames():Array<String>
|
|
||||||
{
|
|
||||||
return ScriptedAlbum.listScriptClasses();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,5 +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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.0.1]
|
||||||
|
### Added
|
||||||
|
- Added `stickerPack` to specify which sticker pack to use when transitioning into and out of song featuring this character.
|
||||||
|
- If the value isn't present, it will use a default sticker pack instead.
|
||||||
|
|
||||||
## [1.0.0]
|
## [1.0.0]
|
||||||
Initial release.
|
Initial release.
|
||||||
|
|||||||
@@ -31,6 +31,15 @@ class PlayerData
|
|||||||
@:default(false)
|
@:default(false)
|
||||||
public var showUnownedChars:Bool = false;
|
public var showUnownedChars:Bool = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default sticker pack to use for songs featuring this playable character.
|
||||||
|
* Can be overridden by specific songs.
|
||||||
|
* @default `default`
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
@:default(funkin.util.Constants.DEFAULT_STICKER_PACK)
|
||||||
|
public var stickerPack:String = Constants.DEFAULT_STICKER_PACK;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Which freeplay style to use for this character.
|
* Which freeplay style to use for this character.
|
||||||
*/
|
*/
|
||||||
@@ -325,12 +334,20 @@ typedef PlayerResultsAnimationData =
|
|||||||
*/
|
*/
|
||||||
var renderType:String;
|
var renderType:String;
|
||||||
|
|
||||||
var assetPath:String;
|
@:optional
|
||||||
|
var assetPath:Null<String>;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
var scriptClass:Null<String>;
|
||||||
|
|
||||||
@:optional
|
@:optional
|
||||||
@:default([0, 0])
|
@:default([0, 0])
|
||||||
var offsets:Array<Float>;
|
var offsets:Array<Float>;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
@:default("both")
|
||||||
|
var filter:String;
|
||||||
|
|
||||||
@:optional
|
@:optional
|
||||||
@:default(500)
|
@:default(500)
|
||||||
var zIndex:Int;
|
var zIndex:Int;
|
||||||
@@ -347,6 +364,10 @@ typedef PlayerResultsAnimationData =
|
|||||||
@:default('')
|
@:default('')
|
||||||
var startFrameLabel:Null<String>;
|
var startFrameLabel:Null<String>;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
@:default('')
|
||||||
|
var sound:Null<String>;
|
||||||
|
|
||||||
@:optional
|
@:optional
|
||||||
@:default(true)
|
@:default(true)
|
||||||
var looped:Bool;
|
var looped:Bool;
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import funkin.data.freeplay.player.PlayerData;
|
|||||||
import funkin.ui.freeplay.charselect.PlayableCharacter;
|
import funkin.ui.freeplay.charselect.PlayableCharacter;
|
||||||
import funkin.ui.freeplay.charselect.ScriptedPlayableCharacter;
|
import funkin.ui.freeplay.charselect.ScriptedPlayableCharacter;
|
||||||
import funkin.save.Save;
|
import funkin.save.Save;
|
||||||
|
import funkin.util.tools.ISingleton;
|
||||||
|
import funkin.data.DefaultRegistryImpl;
|
||||||
|
|
||||||
class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData>
|
class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData> implements ISingleton implements DefaultRegistryImpl
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The current version string for the stage data format.
|
* The current version string for the stage data format.
|
||||||
@@ -16,15 +18,6 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData>
|
|||||||
|
|
||||||
public static final PLAYER_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
|
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.
|
* A mapping between stage character IDs and Freeplay playable character IDs.
|
||||||
*/
|
*/
|
||||||
@@ -130,79 +123,4 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData>
|
|||||||
{
|
{
|
||||||
return ownedCharacterIds.exists(characterId);
|
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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ package funkin.data.freeplay.style;
|
|||||||
import funkin.ui.freeplay.FreeplayStyle;
|
import funkin.ui.freeplay.FreeplayStyle;
|
||||||
import funkin.data.freeplay.style.FreeplayStyleData;
|
import funkin.data.freeplay.style.FreeplayStyleData;
|
||||||
import funkin.ui.freeplay.ScriptedFreeplayStyle;
|
import funkin.ui.freeplay.ScriptedFreeplayStyle;
|
||||||
|
import funkin.util.tools.ISingleton;
|
||||||
|
import funkin.data.DefaultRegistryImpl;
|
||||||
|
|
||||||
class FreeplayStyleRegistry extends BaseRegistry<FreeplayStyle, FreeplayStyleData>
|
class FreeplayStyleRegistry extends BaseRegistry<FreeplayStyle, FreeplayStyleData> implements ISingleton implements DefaultRegistryImpl
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The current version string for the style data format.
|
* The current version string for the style data format.
|
||||||
@@ -15,70 +17,8 @@ class FreeplayStyleRegistry extends BaseRegistry<FreeplayStyle, FreeplayStyleDat
|
|||||||
|
|
||||||
public static final FREEPLAYSTYLE_DATA_VERSION_RULE:thx.semver.VersionRule = '1.0.x';
|
public static final FREEPLAYSTYLE_DATA_VERSION_RULE:thx.semver.VersionRule = '1.0.x';
|
||||||
|
|
||||||
public static final instance:FreeplayStyleRegistry = new FreeplayStyleRegistry();
|
|
||||||
|
|
||||||
public function new()
|
public function new()
|
||||||
{
|
{
|
||||||
super('FREEPLAYSTYLE', 'ui/freeplay/styles', FREEPLAYSTYLE_DATA_VERSION_RULE);
|
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
<!-- TODO: Include NoteSplash and NoteStyle changes made to accomodate week 6 note splash/hold stuff -->
|
||||||
|
|
||||||
## [1.1.0]
|
## [1.1.0]
|
||||||
### Added
|
### Added
|
||||||
- Added several new `assets`:
|
- Added several new `assets`:
|
||||||
|
|||||||
@@ -187,6 +187,10 @@ typedef NoteStyleAssetData<T> =
|
|||||||
@:optional
|
@:optional
|
||||||
var isPixel:Bool;
|
var isPixel:Bool;
|
||||||
|
|
||||||
|
@:default(1.0)
|
||||||
|
@:optional
|
||||||
|
var alpha:Float;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If true, animations will be played on the graphic.
|
* If true, animations will be played on the graphic.
|
||||||
* @default `false` to save performance.
|
* @default `false` to save performance.
|
||||||
@@ -251,6 +255,30 @@ typedef NoteStyleData_NoteSplash =
|
|||||||
@:optional
|
@:optional
|
||||||
@:default(true)
|
@:default(true)
|
||||||
var enabled:Bool;
|
var enabled:Bool;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
@:default(24)
|
||||||
|
var framerateDefault:Int;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
@:default(2)
|
||||||
|
var framerateVariance:Int;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
@:default("normal")
|
||||||
|
var blendMode:String;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
var leftSplashes:Array<UnnamedAnimationData>;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
var downSplashes:Array<UnnamedAnimationData>;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
var upSplashes:Array<UnnamedAnimationData>;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
var rightSplashes:Array<UnnamedAnimationData>;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef NoteStyleData_HoldNoteCover =
|
typedef NoteStyleData_HoldNoteCover =
|
||||||
@@ -262,4 +290,33 @@ typedef NoteStyleData_HoldNoteCover =
|
|||||||
@:optional
|
@:optional
|
||||||
@:default(true)
|
@:default(true)
|
||||||
var enabled:Bool;
|
var enabled:Bool;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
var left:NoteStyleData_HoldNoteCoverDirectionData;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
var down:NoteStyleData_HoldNoteCoverDirectionData;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
var up:NoteStyleData_HoldNoteCoverDirectionData;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
var right:NoteStyleData_HoldNoteCoverDirectionData;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef NoteStyleData_HoldNoteCoverDirectionData =
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Optionally specify an asset path to use for this specific animation.
|
||||||
|
* @:default The assetPath of the main holdNoteCover asset
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
var assetPath:String;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
var start:UnnamedAnimationData;
|
||||||
|
@:optional
|
||||||
|
var hold:UnnamedAnimationData;
|
||||||
|
@:optional
|
||||||
|
var end:UnnamedAnimationData;
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ package funkin.data.notestyle;
|
|||||||
import funkin.play.notes.notestyle.NoteStyle;
|
import funkin.play.notes.notestyle.NoteStyle;
|
||||||
import funkin.play.notes.notestyle.ScriptedNoteStyle;
|
import funkin.play.notes.notestyle.ScriptedNoteStyle;
|
||||||
import funkin.data.notestyle.NoteStyleData;
|
import funkin.data.notestyle.NoteStyleData;
|
||||||
|
import funkin.util.tools.ISingleton;
|
||||||
|
import funkin.data.DefaultRegistryImpl;
|
||||||
|
|
||||||
class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData>
|
class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData> implements ISingleton implements DefaultRegistryImpl
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The current version string for the note style data format.
|
* The current version string for the note style data format.
|
||||||
@@ -13,16 +15,7 @@ class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData>
|
|||||||
*/
|
*/
|
||||||
public static final NOTE_STYLE_DATA_VERSION:thx.semver.Version = "1.1.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.1.x";
|
public static final NOTE_STYLE_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <1.2.0";
|
||||||
|
|
||||||
public static var instance(get, never):NoteStyleRegistry;
|
|
||||||
static var _instance:Null<NoteStyleRegistry> = null;
|
|
||||||
|
|
||||||
static function get_instance():NoteStyleRegistry
|
|
||||||
{
|
|
||||||
if (_instance == null) _instance = new NoteStyleRegistry();
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function new()
|
public function new()
|
||||||
{
|
{
|
||||||
@@ -33,61 +26,4 @@ class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData>
|
|||||||
{
|
{
|
||||||
return fetchEntry(Constants.DEFAULT_NOTE_STYLE);
|
return fetchEntry(Constants.DEFAULT_NOTE_STYLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
|
||||||
*/
|
|
||||||
public function parseEntryData(id:String):Null<NoteStyleData>
|
|
||||||
{
|
|
||||||
// JsonParser does not take type parameters,
|
|
||||||
// otherwise this function would be in BaseRegistry.
|
|
||||||
var parser = new json2object.JsonParser<NoteStyleData>();
|
|
||||||
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<NoteStyleData>
|
|
||||||
{
|
|
||||||
var parser = new json2object.JsonParser<NoteStyleData>();
|
|
||||||
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):NoteStyle
|
|
||||||
{
|
|
||||||
return ScriptedNoteStyle.init(clsName, "unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
function getScriptedClassNames():Array<String>
|
|
||||||
{
|
|
||||||
return ScriptedNoteStyle.listScriptClasses();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,11 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [2.2.5]
|
||||||
|
### Added
|
||||||
|
- Added `playData.stickerPack` to specify which sticker pack to use when transitioning into and out of a song.
|
||||||
|
- If the value isn't present, it will use the character's sticker pack instead.
|
||||||
|
|
||||||
## [2.2.4]
|
## [2.2.4]
|
||||||
### Added
|
### Added
|
||||||
- Added `playData.characters.opponentVocals` to specify which vocal track(s) to play for the opponent.
|
- Added `playData.characters.opponentVocals` to specify which vocal track(s) to play for the opponent.
|
||||||
|
|||||||
@@ -471,6 +471,13 @@ class SongPlayData implements ICloneable<SongPlayData>
|
|||||||
@:optional
|
@:optional
|
||||||
public var album:Null<String>;
|
public var album:Null<String>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The sticker pack for the song to use during transitions.
|
||||||
|
* If `null`, display the character's sticker pack.
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
public var stickerPack:Null<String>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The start time for the audio preview in Freeplay.
|
* The start time for the audio preview in Freeplay.
|
||||||
* Defaults to 0 seconds in.
|
* Defaults to 0 seconds in.
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package funkin.data.song;
|
package funkin.data.song;
|
||||||
|
|
||||||
|
import funkin.data.freeplay.player.PlayerRegistry;
|
||||||
import funkin.data.song.SongData;
|
import funkin.data.song.SongData;
|
||||||
import funkin.data.song.migrator.SongData_v2_0_0.SongMetadata_v2_0_0;
|
import funkin.data.song.migrator.SongData_v2_0_0.SongMetadata_v2_0_0;
|
||||||
import funkin.data.song.migrator.SongData_v2_1_0.SongMetadata_v2_1_0;
|
import funkin.data.song.migrator.SongData_v2_1_0.SongMetadata_v2_1_0;
|
||||||
@@ -9,11 +10,13 @@ import funkin.play.song.ScriptedSong;
|
|||||||
import funkin.play.song.Song;
|
import funkin.play.song.Song;
|
||||||
import funkin.util.assets.DataAssets;
|
import funkin.util.assets.DataAssets;
|
||||||
import funkin.util.VersionUtil;
|
import funkin.util.VersionUtil;
|
||||||
|
import funkin.util.tools.ISingleton;
|
||||||
|
import funkin.data.DefaultRegistryImpl;
|
||||||
|
|
||||||
using funkin.data.song.migrator.SongDataMigrator;
|
using funkin.data.song.migrator.SongDataMigrator;
|
||||||
|
|
||||||
@:nullSafety
|
@:nullSafety
|
||||||
class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
class SongRegistry extends BaseRegistry<Song, SongMetadata> implements ISingleton implements DefaultRegistryImpl
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The current version string for the stage data format.
|
* The current version string for the stage data format.
|
||||||
@@ -39,19 +42,6 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||||||
return '${Constants.TITLE} - ${Constants.VERSION}';
|
return '${Constants.TITLE} - ${Constants.VERSION}';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: What if there was a Singleton macro which automatically created the property for us?
|
|
||||||
*/
|
|
||||||
public static var instance(get, never):SongRegistry;
|
|
||||||
|
|
||||||
static var _instance:Null<SongRegistry> = null;
|
|
||||||
|
|
||||||
static function get_instance():SongRegistry
|
|
||||||
{
|
|
||||||
if (_instance == null) _instance = new SongRegistry();
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function new()
|
public function new()
|
||||||
{
|
{
|
||||||
super('SONG', 'songs', SONG_METADATA_VERSION_RULE);
|
super('SONG', 'songs', SONG_METADATA_VERSION_RULE);
|
||||||
@@ -171,7 +161,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||||||
|
|
||||||
public function parseEntryMetadataWithMigration(id:String, variation:String, version:thx.semver.Version):Null<SongMetadata>
|
public function parseEntryMetadataWithMigration(id:String, variation:String, version:thx.semver.Version):Null<SongMetadata>
|
||||||
{
|
{
|
||||||
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
variation = variation ?? Constants.DEFAULT_VARIATION;
|
||||||
|
|
||||||
// If a version rule is not specified, do not check against it.
|
// If a version rule is not specified, do not check against it.
|
||||||
if (SONG_METADATA_VERSION_RULE == null || VersionUtil.validateVersion(version, SONG_METADATA_VERSION_RULE))
|
if (SONG_METADATA_VERSION_RULE == null || VersionUtil.validateVersion(version, SONG_METADATA_VERSION_RULE))
|
||||||
@@ -192,12 +182,13 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function parseEntryMetadataRawWithMigration(contents:String, ?fileName:String = 'raw', version:thx.semver.Version):Null<SongMetadata>
|
public function parseEntryMetadataRawWithMigration(contents:String, ?fileName:String = 'raw', version:thx.semver.Version,
|
||||||
|
?variation:String):Null<SongMetadata>
|
||||||
{
|
{
|
||||||
// If a version rule is not specified, do not check against it.
|
// If a version rule is not specified, do not check against it.
|
||||||
if (SONG_METADATA_VERSION_RULE == null || VersionUtil.validateVersion(version, SONG_METADATA_VERSION_RULE))
|
if (SONG_METADATA_VERSION_RULE == null || VersionUtil.validateVersion(version, SONG_METADATA_VERSION_RULE))
|
||||||
{
|
{
|
||||||
return parseEntryMetadataRaw(contents, fileName);
|
return parseEntryMetadataRaw(contents, fileName, variation);
|
||||||
}
|
}
|
||||||
else if (VersionUtil.validateVersion(version, "2.1.x"))
|
else if (VersionUtil.validateVersion(version, "2.1.x"))
|
||||||
{
|
{
|
||||||
@@ -417,16 +408,6 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createScriptedEntry(clsName:String):Song
|
|
||||||
{
|
|
||||||
return ScriptedSong.init(clsName, "unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
function getScriptedClassNames():Array<String>
|
|
||||||
{
|
|
||||||
return ScriptedSong.listScriptClasses();
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadEntryMetadataFile(id:String, ?variation:String):Null<JsonFile>
|
function loadEntryMetadataFile(id:String, ?variation:String):Null<JsonFile>
|
||||||
{
|
{
|
||||||
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
||||||
@@ -502,24 +483,37 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list of all the story weeks from the base game, in order.
|
* A list of all difficulties for a specific character.
|
||||||
* TODO: Should this be hardcoded?
|
|
||||||
*/
|
*/
|
||||||
public function listBaseGameSongIds():Array<String>
|
public function listAllDifficulties(characterId:String):Array<String>
|
||||||
{
|
{
|
||||||
return [
|
var allDifficulties:Array<String> = Constants.DEFAULT_DIFFICULTY_LIST.copy();
|
||||||
"tutorial", "bopeebo", "fresh", "dadbattle", "spookeez", "south", "monster", "pico", "philly-nice", "blammed", "satin-panties", "high", "milf", "cocoa",
|
var character = PlayerRegistry.instance.fetchEntry(characterId);
|
||||||
"eggnog", "winter-horrorland", "senpai", "roses", "thorns", "ugh", "guns", "stress", "darnell", "lit-up", "2hot", "blazin"
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
if (character == null)
|
||||||
* A list of all installed story weeks that are not from the base game.
|
{
|
||||||
*/
|
trace(' [WARN] Could not locate character $characterId');
|
||||||
public function listModdedSongIds():Array<String>
|
return allDifficulties;
|
||||||
{
|
}
|
||||||
return listEntryIds().filter(function(id:String):Bool {
|
|
||||||
return listBaseGameSongIds().indexOf(id) == -1;
|
allDifficulties = [];
|
||||||
});
|
for (songId in listEntryIds())
|
||||||
|
{
|
||||||
|
var song = fetchEntry(songId);
|
||||||
|
if (song == null) continue;
|
||||||
|
|
||||||
|
for (diff in song.listDifficulties(null, song.getVariationsByCharacter(character)))
|
||||||
|
{
|
||||||
|
if (!allDifficulties.contains(diff)) allDifficulties.push(diff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allDifficulties.length == 0)
|
||||||
|
{
|
||||||
|
trace(' [WARN] No difficulties found. Returning default difficulty list.');
|
||||||
|
allDifficulties = Constants.DEFAULT_DIFFICULTY_LIST.copy();
|
||||||
|
}
|
||||||
|
|
||||||
|
return allDifficulties;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,12 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.0.3]
|
||||||
|
### Added
|
||||||
|
- Added the `color` attribute on stage props to tint them.
|
||||||
|
- Added the `blend` attribute on stage props to apply blend modes.
|
||||||
|
- Added the `angle` attribute on stage props to apply a rotation to them.
|
||||||
|
|
||||||
## [1.0.2]
|
## [1.0.2]
|
||||||
### Added
|
### Added
|
||||||
- Added the ability to specify `flipX` and `flipY` on stage props to horizontally or vertically flip, respectively.
|
- Added the ability to specify `flipX` and `flipY` on stage props to horizontally or vertically flip, respectively.
|
||||||
|
|||||||
@@ -171,12 +171,12 @@ typedef StageDataProp =
|
|||||||
* How much the prop scrolls relative to the camera. Used to create a parallax effect.
|
* How much the prop scrolls relative to the camera. Used to create a parallax effect.
|
||||||
* Represented as an [x, y] array of two floats.
|
* Represented as an [x, y] array of two floats.
|
||||||
* [1, 1] means the prop moves 1:1 with the camera.
|
* [1, 1] means the prop moves 1:1 with the camera.
|
||||||
* [0.5, 0.5] means the prop half as much as the camera.
|
* [0.5, 0.5] means the prop moves half as much as the camera.
|
||||||
* [0, 0] means the prop is not moved.
|
* [0, 0] means the prop is not moved.
|
||||||
* @default [0, 0]
|
* @default [1, 1]
|
||||||
*/
|
*/
|
||||||
@:optional
|
@:optional
|
||||||
@:default([0, 0])
|
@:default([1, 1])
|
||||||
var scroll:Array<Float>;
|
var scroll:Array<Float>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -261,4 +261,32 @@ typedef StageDataCharacter =
|
|||||||
*/
|
*/
|
||||||
@:optional
|
@:optional
|
||||||
var cameraOffsets:Array<Float>;
|
var cameraOffsets:Array<Float>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How much the character scrolls relative to the camera. Used to create a parallax effect.
|
||||||
|
* Represented as an [x, y] array of two floats.
|
||||||
|
* [1, 1] means the character moves 1:1 with the camera.
|
||||||
|
* [0.5, 0.5] means the character moves half as much as the camera.
|
||||||
|
* [0, 0] means the character is not moved.
|
||||||
|
* @default [1, 1]
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
@:default([1, 1])
|
||||||
|
var scroll:Array<Float>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The alpha of the character, as a float.
|
||||||
|
* @default 1.0
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
@:default(1.0)
|
||||||
|
var alpha:Float;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The angle of the character, as a float.
|
||||||
|
* @default 1.0
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
@:default(0.0)
|
||||||
|
var angle:Float;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,110 +1,23 @@
|
|||||||
package funkin.data.stage;
|
package funkin.data.stage;
|
||||||
|
|
||||||
import funkin.data.stage.StageData;
|
|
||||||
import funkin.play.stage.Stage;
|
import funkin.play.stage.Stage;
|
||||||
import funkin.play.stage.ScriptedStage;
|
import funkin.play.stage.ScriptedStage;
|
||||||
|
import funkin.util.tools.ISingleton;
|
||||||
|
import funkin.data.DefaultRegistryImpl;
|
||||||
|
|
||||||
class StageRegistry extends BaseRegistry<Stage, StageData>
|
class StageRegistry extends BaseRegistry<Stage, StageData> implements ISingleton implements DefaultRegistryImpl
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The current version string for the stage data format.
|
* The current version string for the stage data format.
|
||||||
* Handle breaking changes by incrementing this value
|
* Handle breaking changes by incrementing this value
|
||||||
* and adding migration to the `migrateStageData()` function.
|
* and adding migration to the `migrateStageData()` function.
|
||||||
*/
|
*/
|
||||||
public static final STAGE_DATA_VERSION:thx.semver.Version = "1.0.2";
|
public static final STAGE_DATA_VERSION:thx.semver.Version = "1.0.3";
|
||||||
|
|
||||||
public static final STAGE_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <=1.0.2";
|
public static final STAGE_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <1.1.0";
|
||||||
|
|
||||||
public static var instance(get, never):StageRegistry;
|
|
||||||
static var _instance:Null<StageRegistry> = null;
|
|
||||||
|
|
||||||
static function get_instance():StageRegistry
|
|
||||||
{
|
|
||||||
if (_instance == null) _instance = new StageRegistry();
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function new()
|
public function new()
|
||||||
{
|
{
|
||||||
super('STAGE', 'stages', STAGE_DATA_VERSION_RULE);
|
super('STAGE', 'stages', STAGE_DATA_VERSION_RULE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
|
||||||
*/
|
|
||||||
public function parseEntryData(id:String):Null<StageData>
|
|
||||||
{
|
|
||||||
// JsonParser does not take type parameters,
|
|
||||||
// otherwise this function would be in BaseRegistry.
|
|
||||||
var parser = new json2object.JsonParser<StageData>();
|
|
||||||
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<StageData>
|
|
||||||
{
|
|
||||||
var parser = new json2object.JsonParser<StageData>();
|
|
||||||
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):Stage
|
|
||||||
{
|
|
||||||
return ScriptedStage.init(clsName, "unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
function getScriptedClassNames():Array<String>
|
|
||||||
{
|
|
||||||
return ScriptedStage.listScriptClasses();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A list of all the stages from the base game, in order.
|
|
||||||
* TODO: Should this be hardcoded?
|
|
||||||
*/
|
|
||||||
public function listBaseGameStageIds():Array<String>
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
"mainStage", "mainStageErect", "spookyMansion", "phillyTrain", "phillyTrainErect", "limoRide", "limoRideErect", "mallXmas", "mallXmasErect", "mallEvil",
|
|
||||||
"school", "schoolEvil", "tankmanBattlefield", "phillyStreets", "phillyStreetsErect", "phillyBlazin",
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A list of all installed story weeks that are not from the base game.
|
|
||||||
*/
|
|
||||||
public function listModdedStageIds():Array<String>
|
|
||||||
{
|
|
||||||
return listEntryIds().filter(function(id:String):Bool {
|
|
||||||
return listBaseGameStageIds().indexOf(id) == -1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Sticker Pack 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. Sticker packs have been restructured compared to previous versions to be less complicated.
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package funkin.data.stickers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A type definition for a sticker pack.
|
||||||
|
* It includes things like its name, the artist, and what sticker graphics to use.
|
||||||
|
* @see https://lib.haxe.org/p/json2object/
|
||||||
|
*/
|
||||||
|
typedef StickerData =
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Semantic version of the sticker set data.
|
||||||
|
*/
|
||||||
|
public var version:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Readable name of the sticker set.
|
||||||
|
*/
|
||||||
|
public var name:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The artist of the sticker set.
|
||||||
|
*/
|
||||||
|
public var artist:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The stickers in the set.
|
||||||
|
* This is simply a list of asset files to use.
|
||||||
|
*/
|
||||||
|
public var stickers:Array<String>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package funkin.data.stickers;
|
||||||
|
|
||||||
|
import funkin.data.stickers.StickerData;
|
||||||
|
import funkin.ui.transition.stickers.StickerPack;
|
||||||
|
import funkin.ui.transition.stickers.ScriptedStickerPack;
|
||||||
|
|
||||||
|
class StickerRegistry extends BaseRegistry<StickerPack, StickerData>
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The current version string for the sticker pack data format.
|
||||||
|
* Handle breaking changes by incrementing this value
|
||||||
|
* and adding migration to the `migrateStickerData()` function.
|
||||||
|
*/
|
||||||
|
public static final STICKER_DATA_VERSION:thx.semver.Version = '1.0.0';
|
||||||
|
|
||||||
|
public static final STICKER_DATA_VERSION_RULE:thx.semver.VersionRule = '1.0.x';
|
||||||
|
|
||||||
|
public static final instance:StickerRegistry = new StickerRegistry();
|
||||||
|
|
||||||
|
public function new()
|
||||||
|
{
|
||||||
|
super('STICKER', 'stickerpacks', STICKER_DATA_VERSION_RULE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fetchDefault():StickerPack
|
||||||
|
{
|
||||||
|
return fetchEntry(Constants.DEFAULT_STICKER_PACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<StickerData>
|
||||||
|
{
|
||||||
|
// JsonParser does not take type parameters,
|
||||||
|
// otherwise this function would be in BaseRegistry.
|
||||||
|
var parser:json2object.JsonParser<StickerData> = new json2object.JsonParser<StickerData>();
|
||||||
|
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<StickerData>
|
||||||
|
{
|
||||||
|
var parser:json2object.JsonParser<StickerData> = new json2object.JsonParser<StickerData>();
|
||||||
|
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):StickerPack
|
||||||
|
{
|
||||||
|
return ScriptedStickerPack.init(clsName, 'unknown');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getScriptedClassNames():Array<String>
|
||||||
|
{
|
||||||
|
return ScriptedStickerPack.listScriptClasses();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [1.1.0]
|
## [1.0.1]
|
||||||
- Added `visible` attribute.
|
- Added `visible` attribute.
|
||||||
|
|
||||||
## [1.0.0]
|
## [1.0.0]
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ package funkin.data.story.level;
|
|||||||
|
|
||||||
import funkin.util.SortUtil;
|
import funkin.util.SortUtil;
|
||||||
import funkin.ui.story.Level;
|
import funkin.ui.story.Level;
|
||||||
import funkin.data.story.level.LevelData;
|
|
||||||
import funkin.ui.story.ScriptedLevel;
|
import funkin.ui.story.ScriptedLevel;
|
||||||
|
import funkin.util.tools.ISingleton;
|
||||||
|
import funkin.data.DefaultRegistryImpl;
|
||||||
|
|
||||||
class LevelRegistry extends BaseRegistry<Level, LevelData>
|
class LevelRegistry extends BaseRegistry<Level, LevelData> implements ISingleton implements DefaultRegistryImpl
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The current version string for the level data format.
|
* The current version string for the level data format.
|
||||||
@@ -14,112 +15,43 @@ class LevelRegistry extends BaseRegistry<Level, LevelData>
|
|||||||
*/
|
*/
|
||||||
public static final LEVEL_DATA_VERSION:thx.semver.Version = "1.0.1";
|
public static final LEVEL_DATA_VERSION:thx.semver.Version = "1.0.1";
|
||||||
|
|
||||||
public static final LEVEL_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
|
public static final LEVEL_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <1.1.0";
|
||||||
|
|
||||||
public static var instance(get, never):LevelRegistry;
|
|
||||||
static var _instance:Null<LevelRegistry> = null;
|
|
||||||
|
|
||||||
static function get_instance():LevelRegistry
|
|
||||||
{
|
|
||||||
if (_instance == null) _instance = new LevelRegistry();
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function new()
|
public function new()
|
||||||
{
|
{
|
||||||
super('LEVEL', 'levels', LEVEL_DATA_VERSION_RULE);
|
super('LEVEL', 'levels', LEVEL_DATA_VERSION_RULE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
|
||||||
*/
|
|
||||||
public function parseEntryData(id:String):Null<LevelData>
|
|
||||||
{
|
|
||||||
// JsonParser does not take type parameters,
|
|
||||||
// otherwise this function would be in BaseRegistry.
|
|
||||||
var parser = new json2object.JsonParser<LevelData>();
|
|
||||||
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<LevelData>
|
|
||||||
{
|
|
||||||
var parser = new json2object.JsonParser<LevelData>();
|
|
||||||
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):Level
|
|
||||||
{
|
|
||||||
return ScriptedLevel.init(clsName, "unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
function getScriptedClassNames():Array<String>
|
|
||||||
{
|
|
||||||
return ScriptedLevel.listScriptClasses();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list of all the story weeks from the base game, in order.
|
* A list of all the story weeks from the base game, in order.
|
||||||
* TODO: Should this be hardcoded?
|
* @return Array<String>
|
||||||
*/
|
*/
|
||||||
public function listBaseGameLevelIds():Array<String>
|
public function listBaseGameEntryIds():Array<String>
|
||||||
{
|
{
|
||||||
|
// This MUST be hard-coded (overriding the auto-generated method)
|
||||||
|
// because the auto-generated method spits out values in alphabetical order.
|
||||||
return [
|
return [
|
||||||
"tutorial",
|
'tutorial',
|
||||||
"week1",
|
'week1',
|
||||||
"week2",
|
'week2',
|
||||||
"week3",
|
'week3',
|
||||||
"week4",
|
'week4',
|
||||||
"week5",
|
'week5',
|
||||||
"week6",
|
'week6',
|
||||||
"week7",
|
'week7',
|
||||||
"weekend1"
|
'weekend1'
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of all the story weeks in the game, in order.
|
||||||
|
* Modded levels are in alphabetical order at the end of the list.
|
||||||
|
* @return Array<String>
|
||||||
|
*/
|
||||||
public function listSortedLevelIds():Array<String>
|
public function listSortedLevelIds():Array<String>
|
||||||
{
|
{
|
||||||
var result = listEntryIds();
|
var result:Array<String> = listEntryIds();
|
||||||
result.sort(SortUtil.defaultsThenAlphabetically.bind(listBaseGameLevelIds()));
|
result.sort(SortUtil.defaultsThenAlphabetically.bind(listBaseGameEntryIds()));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A list of all installed story weeks that are not from the base game.
|
|
||||||
*/
|
|
||||||
public function listModdedLevelIds():Array<String>
|
|
||||||
{
|
|
||||||
return listEntryIds().filter(function(id:String):Bool {
|
|
||||||
return listBaseGameLevelIds().indexOf(id) == -1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,38 +3,54 @@ package funkin.effects;
|
|||||||
import flixel.addons.effects.FlxTrail;
|
import flixel.addons.effects.FlxTrail;
|
||||||
import funkin.play.stage.Bopper;
|
import funkin.play.stage.Bopper;
|
||||||
import flixel.FlxSprite;
|
import flixel.FlxSprite;
|
||||||
import flixel.system.FlxAssets;
|
import flixel.system.FlxAssets.FlxGraphicAsset;
|
||||||
|
import flixel.math.FlxPoint;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An offshoot of FlxTrail, but accomodates the way Funkin
|
* An offshoot of FlxTrail, but accomodates the way Funkin
|
||||||
* does offsets for characters. example, fixes Spirits trail
|
* does offsets for characters. example, fixes Spirits trail
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class FunkTrail extends FlxTrail
|
class FunkTrail extends FlxTrail
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Creates a new FunkTrail effect for a specific FlxSprite.
|
* Creates a new FunkTrail effect for a specific FlxSprite.
|
||||||
*
|
*
|
||||||
* @param Target The FlxSprite the trail is attached to.
|
* @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 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 length The amount of trailsprites to create.
|
||||||
* @param Delay How often to update the trail. 0 updates every frame.
|
* @param delay How often to update the trail. 0 updates every frame.
|
||||||
* @param Alpha The alpha value for the very first trailsprite.
|
* @param alpha The alpha value for the very first trailsprite.
|
||||||
* @param Diff How much lower the alpha of the next trailsprite is.
|
* @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)
|
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);
|
super(target, graphic, length, delay, alpha, diff);
|
||||||
}
|
}
|
||||||
|
|
||||||
override public function update(elapsed:Float):Void
|
/**
|
||||||
|
* An offset applied to the target position whenever a new frame is saved.
|
||||||
|
*/
|
||||||
|
public final frameOffset:FlxPoint = FlxPoint.get();
|
||||||
|
|
||||||
|
override function destroy():Void
|
||||||
{
|
{
|
||||||
|
super.destroy();
|
||||||
|
|
||||||
|
frameOffset.put();
|
||||||
|
}
|
||||||
|
|
||||||
|
override function addTrailFrame():Void
|
||||||
|
{
|
||||||
|
super.addTrailFrame();
|
||||||
|
|
||||||
if (target is Bopper)
|
if (target is Bopper)
|
||||||
{
|
{
|
||||||
var targ:Bopper = cast target;
|
var targ:Bopper = cast target;
|
||||||
@:privateAccess effectOffset.set((targ.animOffsets[0] - targ.globalOffsets[0]) * targ.scale.x,
|
@:privateAccess
|
||||||
(targ.animOffsets[1] - targ.globalOffsets[1]) * targ.scale.y);
|
frameOffset.set((targ.animOffsets[0] - targ.globalOffsets[0]) * targ.scale.x, (targ.animOffsets[1] - targ.globalOffsets[1]) * targ.scale.y);
|
||||||
}
|
|
||||||
|
|
||||||
super.update(elapsed);
|
_recentPositions[0]?.subtract(frameOffset.x, frameOffset.y);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import flixel.util.FlxTimer;
|
|||||||
import flixel.FlxCamera;
|
import flixel.FlxCamera;
|
||||||
import openfl.filters.ColorMatrixFilter;
|
import openfl.filters.ColorMatrixFilter;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class RetroCameraFade
|
class RetroCameraFade
|
||||||
{
|
{
|
||||||
// im lazy, but we only use this for week 6
|
// im lazy, but we only use this for week 6
|
||||||
|
|||||||
@@ -7,23 +7,17 @@ import flixel.FlxSprite;
|
|||||||
import flixel.graphics.FlxGraphic;
|
import flixel.graphics.FlxGraphic;
|
||||||
import flixel.graphics.frames.FlxFrame;
|
import flixel.graphics.frames.FlxFrame;
|
||||||
import flixel.math.FlxMatrix;
|
import flixel.math.FlxMatrix;
|
||||||
import flixel.math.FlxPoint;
|
|
||||||
import flixel.math.FlxRect;
|
import flixel.math.FlxRect;
|
||||||
import flixel.util.FlxColor;
|
|
||||||
import lime.graphics.cairo.Cairo;
|
import lime.graphics.cairo.Cairo;
|
||||||
import openfl.display.BitmapData;
|
import openfl.display.BitmapData;
|
||||||
import openfl.display.BlendMode;
|
import openfl.display.BlendMode;
|
||||||
import openfl.display.DisplayObjectRenderer;
|
import openfl.display.DisplayObjectRenderer;
|
||||||
import openfl.display.Graphics;
|
import openfl.display.Graphics;
|
||||||
import openfl.display.OpenGLRenderer;
|
import openfl.display.OpenGLRenderer;
|
||||||
import openfl.display._internal.Context3DGraphics;
|
|
||||||
import openfl.display3D.Context3D;
|
import openfl.display3D.Context3D;
|
||||||
import openfl.display3D.Context3DClearMask;
|
|
||||||
import openfl.filters.BitmapFilter;
|
import openfl.filters.BitmapFilter;
|
||||||
import openfl.filters.BlurFilter;
|
|
||||||
import openfl.geom.ColorTransform;
|
import openfl.geom.ColorTransform;
|
||||||
import openfl.geom.Matrix;
|
import openfl.geom.Matrix;
|
||||||
import openfl.geom.Point;
|
|
||||||
import openfl.geom.Rectangle;
|
import openfl.geom.Rectangle;
|
||||||
#if (js && html5)
|
#if (js && html5)
|
||||||
import lime._internal.graphics.ImageCanvasUtil;
|
import lime._internal.graphics.ImageCanvasUtil;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import funkin.graphics.framebuffer.FixedBitmapData;
|
|||||||
import openfl.display.BitmapData;
|
import openfl.display.BitmapData;
|
||||||
import flixel.math.FlxRect;
|
import flixel.math.FlxRect;
|
||||||
import flixel.math.FlxPoint;
|
import flixel.math.FlxPoint;
|
||||||
import flixel.graphics.frames.FlxFrame;
|
import flixel.graphics.frames.FlxFrame.FlxFrameAngle;
|
||||||
import flixel.FlxCamera;
|
import flixel.FlxCamera;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -97,14 +97,58 @@ class FunkinSprite extends FlxSprite
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function loadTextureAsync(key:String, fade:Bool = false):Void
|
||||||
|
{
|
||||||
|
var fadeTween:Null<FlxTween> = null;
|
||||||
|
if (fade)
|
||||||
|
{
|
||||||
|
fadeTween = FlxTween.tween(this, {alpha: 0}, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
trace('[ASYNC] Start loading image (${key})');
|
||||||
|
graphic.persist = true;
|
||||||
|
openfl.Assets.loadBitmapData(key)
|
||||||
|
.onComplete(function(bitmapData:openfl.display.BitmapData) {
|
||||||
|
trace('[ASYNC] Finished loading image');
|
||||||
|
var cache:Bool = false;
|
||||||
|
loadBitmapData(bitmapData, cache);
|
||||||
|
|
||||||
|
if (fadeTween != null)
|
||||||
|
{
|
||||||
|
fadeTween.cancel();
|
||||||
|
FlxTween.tween(this, {alpha: 1.0}, 0.25);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.onError(function(error:Dynamic) {
|
||||||
|
trace('[ASYNC] Failed to load image: ${error}');
|
||||||
|
if (fadeTween != null)
|
||||||
|
{
|
||||||
|
fadeTween.cancel();
|
||||||
|
this.alpha = 1.0;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.onProgress(function(progress:Int, total:Int) {
|
||||||
|
trace('[ASYNC] Loading image progress: ${progress}/${total}');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply an OpenFL `BitmapData` to this sprite.
|
* Apply an OpenFL `BitmapData` to this sprite.
|
||||||
* @param input The OpenFL `BitmapData` to apply
|
* @param input The OpenFL `BitmapData` to apply
|
||||||
* @return This sprite, for chaining
|
* @return This sprite, for chaining
|
||||||
*/
|
*/
|
||||||
public function loadBitmapData(input:BitmapData):FunkinSprite
|
public function loadBitmapData(input:BitmapData, cache:Bool = true):FunkinSprite
|
||||||
{
|
{
|
||||||
loadGraphic(input);
|
if (cache)
|
||||||
|
{
|
||||||
|
loadGraphic(input);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var graphic:FlxGraphic = FlxGraphic.fromBitmapData(input, false, null, false);
|
||||||
|
this.graphic = graphic;
|
||||||
|
this.frames = this.graphic.imageFrame;
|
||||||
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,8 @@ package funkin.graphics.adobeanimate;
|
|||||||
import flixel.util.FlxSignal.FlxTypedSignal;
|
import flixel.util.FlxSignal.FlxTypedSignal;
|
||||||
import flxanimate.FlxAnimate;
|
import flxanimate.FlxAnimate;
|
||||||
import flxanimate.FlxAnimate.Settings;
|
import flxanimate.FlxAnimate.Settings;
|
||||||
import flxanimate.frames.FlxAnimateFrames;
|
|
||||||
import flixel.graphics.frames.FlxFrame;
|
import flixel.graphics.frames.FlxFrame;
|
||||||
import flixel.system.FlxAssets.FlxGraphicAsset;
|
import flixel.system.FlxAssets.FlxGraphicAsset;
|
||||||
import openfl.display.BitmapData;
|
|
||||||
import flixel.math.FlxPoint;
|
import flixel.math.FlxPoint;
|
||||||
import flxanimate.animate.FlxKeyFrame;
|
import flxanimate.animate.FlxKeyFrame;
|
||||||
|
|
||||||
@@ -37,6 +35,11 @@ class FlxAtlasSprite extends FlxAnimate
|
|||||||
*/
|
*/
|
||||||
public var onAnimationComplete:FlxTypedSignal<String->Void> = new FlxTypedSignal();
|
public var onAnimationComplete:FlxTypedSignal<String->Void> = new FlxTypedSignal();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal dispatched when a looping animation finishes playing.
|
||||||
|
*/
|
||||||
|
public var onAnimationLoop:FlxTypedSignal<String->Void> = new FlxTypedSignal();
|
||||||
|
|
||||||
var currentAnimation:String;
|
var currentAnimation:String;
|
||||||
|
|
||||||
var canPlayOtherAnims:Bool = true;
|
var canPlayOtherAnims:Bool = true;
|
||||||
@@ -198,33 +201,44 @@ class FlxAtlasSprite extends FlxAnimate
|
|||||||
goToFrameLabel(id);
|
goToFrameLabel(id);
|
||||||
fr = anim.getFrameLabel(id);
|
fr = anim.getFrameLabel(id);
|
||||||
anim.curFrame += startFrame;
|
anim.curFrame += startFrame;
|
||||||
|
// Resume animation if it's paused.
|
||||||
|
anim.resume();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override public function update(elapsed:Float)
|
override public function update(elapsed:Float):Void
|
||||||
{
|
{
|
||||||
super.update(elapsed);
|
super.update(elapsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the animation has finished playing.
|
* Returns true if the animation has finished playing.
|
||||||
* Never true if animation is configured to loop.
|
* @return Whether the animation has finished playing.
|
||||||
*/
|
*/
|
||||||
public function isAnimationFinished():Bool
|
public function isAnimationFinished():Bool
|
||||||
{
|
{
|
||||||
return this.anim.finished;
|
return isLoopComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the animation has reached the last frame.
|
* Returns true if the animation has reached the last frame.
|
||||||
* Can be true even if animation is configured to loop.
|
* Can be true even if animation is configured to loop.
|
||||||
|
* @return Whether the animation has reached the last frame.
|
||||||
*/
|
*/
|
||||||
public function isLoopComplete():Bool
|
public function isLoopComplete():Bool
|
||||||
{
|
{
|
||||||
if (this.anim == null) return false;
|
if (this.anim == null) return false;
|
||||||
if (!this.anim.isPlaying) 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));
|
if (fr != null)
|
||||||
|
{
|
||||||
|
var curFrame = anim.curFrame;
|
||||||
|
|
||||||
|
var startFrame = fr.index;
|
||||||
|
var endFrame = (fr.index + fr.duration);
|
||||||
|
|
||||||
|
return (anim.reversed) ? (curFrame < startFrame) : (curFrame >= endFrame);
|
||||||
|
}
|
||||||
|
|
||||||
return (anim.reversed && anim.curFrame == 0 || !(anim.reversed) && (anim.curFrame) >= (anim.length - 1));
|
return (anim.reversed && anim.curFrame == 0 || !(anim.reversed) && (anim.curFrame) >= (anim.length - 1));
|
||||||
}
|
}
|
||||||
@@ -252,7 +266,7 @@ class FlxAtlasSprite extends FlxAnimate
|
|||||||
this.anim.goToFrameLabel(label);
|
this.anim.goToFrameLabel(label);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFrameLabelNames(?layer:haxe.extern.EitherType<Int, String> = null)
|
function getFrameLabelNames(?layer:haxe.extern.EitherType<Int, String>):Array<String>
|
||||||
{
|
{
|
||||||
var labels = this.anim.getFrameLabels(layer);
|
var labels = this.anim.getFrameLabels(layer);
|
||||||
var array = [];
|
var array = [];
|
||||||
@@ -295,16 +309,17 @@ class FlxAtlasSprite extends FlxAnimate
|
|||||||
if (isLoopComplete())
|
if (isLoopComplete())
|
||||||
{
|
{
|
||||||
anim.pause();
|
anim.pause();
|
||||||
_onAnimationComplete();
|
|
||||||
|
|
||||||
if (looping)
|
if (looping)
|
||||||
{
|
{
|
||||||
anim.curFrame = (fr != null) ? fr.index : 0;
|
anim.curFrame = (fr != null) ? fr.index : 0;
|
||||||
anim.resume();
|
anim.resume();
|
||||||
|
_onAnimationLoop();
|
||||||
}
|
}
|
||||||
else if (fr != null && anim.curFrame != anim.length - 1)
|
else if (fr != null && anim.curFrame != anim.length - 1)
|
||||||
{
|
{
|
||||||
anim.curFrame--;
|
anim.curFrame--;
|
||||||
|
_onAnimationComplete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -322,11 +337,32 @@ class FlxAtlasSprite extends FlxAnimate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _onAnimationLoop():Void
|
||||||
|
{
|
||||||
|
if (currentAnimation != null)
|
||||||
|
{
|
||||||
|
onAnimationLoop.dispatch(currentAnimation);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
onAnimationLoop.dispatch('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var prevFrames:Map<Int, FlxFrame> = [];
|
var prevFrames:Map<Int, FlxFrame> = [];
|
||||||
|
|
||||||
public function replaceFrameGraphic(index:Int, ?graphic:FlxGraphicAsset):Void
|
public function replaceFrameGraphic(index:Int, ?graphic:FlxGraphicAsset):Void
|
||||||
{
|
{
|
||||||
if (graphic == null || !Assets.exists(graphic))
|
var cond = false;
|
||||||
|
|
||||||
|
if (graphic == null) cond = true;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if ((graphic is String)) cond = !Assets.exists(graphic)
|
||||||
|
else
|
||||||
|
cond = false;
|
||||||
|
}
|
||||||
|
if (cond)
|
||||||
{
|
{
|
||||||
var prevFrame:Null<FlxFrame> = prevFrames.get(index);
|
var prevFrame:Null<FlxFrame> = prevFrames.get(index);
|
||||||
if (prevFrame == null) return;
|
if (prevFrame == null) return;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package funkin.graphics.shaders;
|
package funkin.graphics.shaders;
|
||||||
|
|
||||||
import flixel.system.FlxAssets.FlxShader;
|
import flixel.system.FlxAssets.FlxShader;
|
||||||
import flixel.tweens.FlxEase;
|
|
||||||
import flixel.tweens.FlxTween;
|
import flixel.tweens.FlxTween;
|
||||||
|
|
||||||
class BlueFade extends FlxShader
|
class BlueFade extends FlxShader
|
||||||
|
|||||||
@@ -0,0 +1,227 @@
|
|||||||
|
package funkin.graphics.shaders;
|
||||||
|
|
||||||
|
import flixel.system.FlxAssets.FlxShader;
|
||||||
|
import flixel.util.FlxColor;
|
||||||
|
import flixel.FlxSprite;
|
||||||
|
import flixel.math.FlxAngle;
|
||||||
|
import flixel.graphics.frames.FlxFrame;
|
||||||
|
import openfl.display.BitmapData;
|
||||||
|
|
||||||
|
/*
|
||||||
|
A screenspace version of the DropShadowShader.. currently the only way to use this effect with
|
||||||
|
FlxAnimate :(
|
||||||
|
*/
|
||||||
|
class DropShadowScreenspace extends DropShadowShader
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
The current zoom of the camera. Needed to figure out how much to multiply the drop shadow size.
|
||||||
|
*/
|
||||||
|
public var curZoom(default, set):Float;
|
||||||
|
|
||||||
|
function set_curZoom(val:Float):Float
|
||||||
|
{
|
||||||
|
curZoom = val;
|
||||||
|
zoom.value = [val];
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
@:glFragmentSource('
|
||||||
|
#pragma header
|
||||||
|
|
||||||
|
// This shader aims to mostly recreate how Adobe Animate/Flash handles drop shadows, but its main use here is for rim lighting.
|
||||||
|
|
||||||
|
// this shader also includes a recreation of the Animate/Flash "Adjust Color" filter,
|
||||||
|
// which was kindly provided and written by Rozebud https://github.com/ThatRozebudDude ( thank u rozebud :) )
|
||||||
|
// Adapted from Andrey-Postelzhuks shader found here: https://forum.unity.com/threads/hue-saturation-brightness-contrast-shader.260649/
|
||||||
|
// Hue rotation stuff is from here: https://www.w3.org/TR/filter-effects/#feColorMatrixElement
|
||||||
|
|
||||||
|
// equals (frame.left, frame.top, frame.right, frame.bottom)
|
||||||
|
uniform vec4 uFrameBounds;
|
||||||
|
|
||||||
|
uniform float ang;
|
||||||
|
uniform float dist;
|
||||||
|
uniform float str;
|
||||||
|
uniform float thr;
|
||||||
|
|
||||||
|
// need to account for rotated frames... oops
|
||||||
|
uniform float angOffset;
|
||||||
|
|
||||||
|
uniform sampler2D altMask;
|
||||||
|
uniform bool useMask;
|
||||||
|
uniform float thr2;
|
||||||
|
|
||||||
|
uniform vec3 dropColor;
|
||||||
|
|
||||||
|
uniform float hue;
|
||||||
|
uniform float saturation;
|
||||||
|
uniform float brightness;
|
||||||
|
uniform float contrast;
|
||||||
|
|
||||||
|
uniform float zoom;
|
||||||
|
|
||||||
|
uniform float AA_STAGES;
|
||||||
|
|
||||||
|
const vec3 grayscaleValues = vec3(0.3098039215686275, 0.607843137254902, 0.0823529411764706);
|
||||||
|
const float e = 2.718281828459045;
|
||||||
|
|
||||||
|
vec3 applyHueRotate(vec3 aColor, float aHue){
|
||||||
|
float angle = radians(aHue);
|
||||||
|
|
||||||
|
mat3 m1 = mat3(0.213, 0.213, 0.213, 0.715, 0.715, 0.715, 0.072, 0.072, 0.072);
|
||||||
|
mat3 m2 = mat3(0.787, -0.213, -0.213, -0.715, 0.285, -0.715, -0.072, -0.072, 0.928);
|
||||||
|
mat3 m3 = mat3(-0.213, 0.143, -0.787, -0.715, 0.140, 0.715, 0.928, -0.283, 0.072);
|
||||||
|
mat3 m = m1 + cos(angle) * m2 + sin(angle) * m3;
|
||||||
|
|
||||||
|
return m * aColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 applySaturation(vec3 aColor, float value){
|
||||||
|
if(value > 0.0){ value = value * 3.0; }
|
||||||
|
value = (1.0 + (value / 100.0));
|
||||||
|
vec3 grayscale = vec3(dot(aColor, grayscaleValues));
|
||||||
|
return clamp(mix(grayscale, aColor, value), 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 applyContrast(vec3 aColor, float value){
|
||||||
|
value = (1.0 + (value / 100.0));
|
||||||
|
if(value > 1.0){
|
||||||
|
value = (((0.00852259 * pow(e, 4.76454 * (value - 1.0))) * 1.01) - 0.0086078159) * 10.0; //Just roll with it...
|
||||||
|
value += 1.0;
|
||||||
|
}
|
||||||
|
return clamp((aColor - 0.25) * value + 0.25, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 applyHSBCEffect(vec3 color){
|
||||||
|
|
||||||
|
//Brightness
|
||||||
|
color = color + ((brightness) / 255.0);
|
||||||
|
|
||||||
|
//Hue
|
||||||
|
color = applyHueRotate(color, hue);
|
||||||
|
|
||||||
|
//Contrast
|
||||||
|
color = applyContrast(color, contrast);
|
||||||
|
|
||||||
|
//Saturation
|
||||||
|
color = applySaturation(color, saturation);
|
||||||
|
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 hash22(vec2 p) {
|
||||||
|
vec3 p3 = fract(vec3(p.xyx) * vec3(.1031, .1030, .0973));
|
||||||
|
p3 += dot(p3, p3.yzx + 33.33);
|
||||||
|
return fract((p3.xx + p3.yz) * p3.zy);
|
||||||
|
}
|
||||||
|
|
||||||
|
float intensityPass(vec2 fragCoord, float curThreshold, bool useMask) {
|
||||||
|
vec4 col = texture2D(bitmap, fragCoord);
|
||||||
|
|
||||||
|
float maskIntensity = 0.0;
|
||||||
|
if(useMask == true){
|
||||||
|
maskIntensity = mix(0.0, 1.0, texture2D(altMask, fragCoord).b);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(col.a == 0.0){
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
float intensity = dot(col.rgb, vec3(0.3098, 0.6078, 0.0823));
|
||||||
|
|
||||||
|
intensity = maskIntensity > 0.0 ? float(intensity > thr2) : float(intensity > thr);
|
||||||
|
|
||||||
|
return intensity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// essentially just stole this from the AngleMask shader but repurposed it to smooth
|
||||||
|
// the threshold because without any sort of smoothing it produces horrible edges
|
||||||
|
float antialias(vec2 fragCoord, float curThreshold, bool useMask) {
|
||||||
|
|
||||||
|
// In GLSL 100, we need to use constant loop bounds
|
||||||
|
// Well assume a reasonable maximum for AA_STAGES and use a fixed loop
|
||||||
|
// The actual number of iterations will be controlled by a condition inside
|
||||||
|
const int MAX_AA = 8; // This should be large enough for most uses
|
||||||
|
|
||||||
|
float AA_TOTAL_PASSES = AA_STAGES * AA_STAGES + 1.0;
|
||||||
|
const float AA_JITTER = 0.5;
|
||||||
|
|
||||||
|
// Run the shader multiple times with a random subpixel offset each time and average the results
|
||||||
|
float color = intensityPass(fragCoord, curThreshold, useMask);
|
||||||
|
for (int i = 0; i < MAX_AA * MAX_AA; i++) {
|
||||||
|
// Calculate x and y from i
|
||||||
|
int x = i / MAX_AA;
|
||||||
|
int y = i - (MAX_AA * int(i/MAX_AA)); // poor mans modulus
|
||||||
|
|
||||||
|
// Skip iterations beyond our desired AA_STAGES
|
||||||
|
if (float(x) >= AA_STAGES || float(y) >= AA_STAGES) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 offset = AA_JITTER * (2.0 * hash22(vec2(float(x), float(y))) - 1.0) / openfl_TextureSize.xy;
|
||||||
|
color += intensityPass(fragCoord + offset, curThreshold, useMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
return color / AA_TOTAL_PASSES;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 createDropShadow(vec3 col, float curThreshold, bool useMask) {
|
||||||
|
|
||||||
|
// essentially a mask so that areas under the threshold dont show the rimlight (mainly the outlines)
|
||||||
|
float intensity = antialias(openfl_TextureCoordv, curThreshold, useMask);
|
||||||
|
|
||||||
|
// the distance the dropshadow moves needs to be correctly scaled based on the texture size
|
||||||
|
vec2 imageRatio = vec2(1.0/openfl_TextureSize.x, 1.0/openfl_TextureSize.y);
|
||||||
|
|
||||||
|
// check the pixel in the direction and distance specified
|
||||||
|
vec2 checkedPixel = vec2(openfl_TextureCoordv.x + ((dist*zoom) * cos(ang + angOffset) * imageRatio.x), openfl_TextureCoordv.y - ((dist*zoom) * sin(ang + angOffset) * imageRatio.y));
|
||||||
|
|
||||||
|
// multiplier for the intensity of the drop shadow
|
||||||
|
float dropShadowAmount = 0.0;
|
||||||
|
|
||||||
|
dropShadowAmount = texture2D(bitmap, checkedPixel).a;
|
||||||
|
|
||||||
|
// add the dropshadow color based on the amount, strength, and intensity
|
||||||
|
col.rgb += dropColor.rgb * ((1.0 - (dropShadowAmount * str))*intensity);
|
||||||
|
|
||||||
|
return col;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec4 col = texture2D(bitmap, openfl_TextureCoordv);
|
||||||
|
|
||||||
|
vec3 unpremultipliedColor = col.a > 0.0 ? col.rgb / col.a : col.rgb;
|
||||||
|
|
||||||
|
vec3 outColor = applyHSBCEffect(unpremultipliedColor);
|
||||||
|
|
||||||
|
outColor = createDropShadow(outColor, thr, useMask);
|
||||||
|
|
||||||
|
gl_FragColor = vec4(outColor.rgb * col.a, col.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
')
|
||||||
|
override public function new()
|
||||||
|
{
|
||||||
|
super();
|
||||||
|
|
||||||
|
angle = 90;
|
||||||
|
strength = 1;
|
||||||
|
distance = 15;
|
||||||
|
threshold = 0.1;
|
||||||
|
|
||||||
|
baseHue = 0;
|
||||||
|
baseSaturation = 0;
|
||||||
|
baseBrightness = 0;
|
||||||
|
baseContrast = 0;
|
||||||
|
|
||||||
|
useAltMask = false;
|
||||||
|
|
||||||
|
color = 0xFFDFEF3C;
|
||||||
|
|
||||||
|
antialiasAmt = 2;
|
||||||
|
|
||||||
|
curZoom = 1;
|
||||||
|
|
||||||
|
angOffset.value = [0];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,451 @@
|
|||||||
|
package funkin.graphics.shaders;
|
||||||
|
|
||||||
|
import flixel.system.FlxAssets.FlxShader;
|
||||||
|
import flixel.util.FlxColor;
|
||||||
|
import flixel.FlxSprite;
|
||||||
|
import flixel.math.FlxAngle;
|
||||||
|
import flixel.graphics.frames.FlxFrame;
|
||||||
|
import openfl.display.BitmapData;
|
||||||
|
|
||||||
|
/*
|
||||||
|
A shader that aims to *mostly recreate how Adobe Animate/Flash handles drop shadows, but its main use here is for rim lighting.
|
||||||
|
|
||||||
|
Has options for color, angle, distance, and a threshold to not cast the shadow on parts like outlines.
|
||||||
|
Can also be supplied a secondary mask which can then have an alternate threshold, for when sprites have too many conflicting colors
|
||||||
|
for the drop shadow to look right (e.g. the tankmen on GF's speakers).
|
||||||
|
|
||||||
|
Also has an Adjust Color shader in here so they can work together when needed.
|
||||||
|
*/
|
||||||
|
class DropShadowShader extends FlxShader
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
The color of the drop shadow.
|
||||||
|
*/
|
||||||
|
public var color(default, set):FlxColor;
|
||||||
|
|
||||||
|
/*
|
||||||
|
The angle of the drop shadow.
|
||||||
|
|
||||||
|
for reference, depending on the angle, the affected side will be:
|
||||||
|
0 = RIGHT
|
||||||
|
90 = UP
|
||||||
|
180 = LEFT
|
||||||
|
270 = DOWN
|
||||||
|
*/
|
||||||
|
public var angle(default, set):Float;
|
||||||
|
|
||||||
|
/*
|
||||||
|
The distance or size of the drop shadow, in pixels,
|
||||||
|
relative to the texture itself... NOT the camera.
|
||||||
|
*/
|
||||||
|
public var distance(default, set):Float;
|
||||||
|
|
||||||
|
/*
|
||||||
|
The strength of the drop shadow.
|
||||||
|
Effectively just an alpha multiplier.
|
||||||
|
*/
|
||||||
|
public var strength(default, set):Float;
|
||||||
|
|
||||||
|
/*
|
||||||
|
The brightness threshold for the drop shadow.
|
||||||
|
Anything below this number will NOT be affected by the drop shadow shader.
|
||||||
|
A value of 0 effectively means theres no threshold, and vice versa.
|
||||||
|
*/
|
||||||
|
public var threshold(default, set):Float;
|
||||||
|
|
||||||
|
/*
|
||||||
|
The amount of antialias samples per-pixel,
|
||||||
|
used to smooth out any hard edges the brightness thresholding creates.
|
||||||
|
Defaults to 2, and 0 will remove any smoothing.
|
||||||
|
*/
|
||||||
|
public var antialiasAmt(default, set):Float;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Whether the shader should try and use the alternate mask.
|
||||||
|
False by default.
|
||||||
|
*/
|
||||||
|
public var useAltMask(default, set):Bool;
|
||||||
|
|
||||||
|
/*
|
||||||
|
The image for the alternate mask.
|
||||||
|
At the moment, it uses the blue channel to specify what is or isnt going to use the alternate threshold.
|
||||||
|
(its kinda sloppy rn i need to make it work a little nicer)
|
||||||
|
TODO: maybe have a sort of "threshold intensity texture" as well? where higher/lower values indicate threshold strength..
|
||||||
|
*/
|
||||||
|
public var altMaskImage(default, set):BitmapData;
|
||||||
|
|
||||||
|
/*
|
||||||
|
An alternate brightness threshold for the drop shadow.
|
||||||
|
Anything below this number will NOT be affected by the drop shadow shader,
|
||||||
|
but ONLY when the pixel is within the mask.
|
||||||
|
*/
|
||||||
|
public var maskThreshold(default, set):Float;
|
||||||
|
|
||||||
|
/*
|
||||||
|
The FlxSprite that the shader should get the frame data from.
|
||||||
|
Needed to keep the drop shadow shader in the correct bounds and rotation.
|
||||||
|
*/
|
||||||
|
public var attachedSprite(default, set):FlxSprite;
|
||||||
|
|
||||||
|
/*
|
||||||
|
The hue component of the Adjust Color part of the shader.
|
||||||
|
*/
|
||||||
|
public var baseHue(default, set):Float;
|
||||||
|
|
||||||
|
/*
|
||||||
|
The saturation component of the Adjust Color part of the shader.
|
||||||
|
*/
|
||||||
|
public var baseSaturation(default, set):Float;
|
||||||
|
|
||||||
|
/*
|
||||||
|
The brightness component of the Adjust Color part of the shader.
|
||||||
|
*/
|
||||||
|
public var baseBrightness(default, set):Float;
|
||||||
|
|
||||||
|
/*
|
||||||
|
The contrast component of the Adjust Color part of the shader.
|
||||||
|
*/
|
||||||
|
public var baseContrast(default, set):Float;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Sets all 4 adjust color values.
|
||||||
|
*/
|
||||||
|
public function setAdjustColor(b:Float, h:Float, c:Float, s:Float)
|
||||||
|
{
|
||||||
|
baseBrightness = b;
|
||||||
|
baseHue = h;
|
||||||
|
baseContrast = c;
|
||||||
|
baseSaturation = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_baseHue(val:Float):Float
|
||||||
|
{
|
||||||
|
baseHue = val;
|
||||||
|
hue.value = [val];
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_baseSaturation(val:Float):Float
|
||||||
|
{
|
||||||
|
baseSaturation = val;
|
||||||
|
saturation.value = [val];
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_baseBrightness(val:Float):Float
|
||||||
|
{
|
||||||
|
baseBrightness = val;
|
||||||
|
brightness.value = [val];
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_baseContrast(val:Float):Float
|
||||||
|
{
|
||||||
|
baseContrast = val;
|
||||||
|
contrast.value = [val];
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_threshold(val:Float):Float
|
||||||
|
{
|
||||||
|
threshold = val;
|
||||||
|
thr.value = [val];
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_antialiasAmt(val:Float):Float
|
||||||
|
{
|
||||||
|
antialiasAmt = val;
|
||||||
|
AA_STAGES.value = [val];
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_color(col:FlxColor):FlxColor
|
||||||
|
{
|
||||||
|
color = col;
|
||||||
|
dropColor.value = [color.red / 255, color.green / 255, color.blue / 255];
|
||||||
|
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_angle(val:Float):Float
|
||||||
|
{
|
||||||
|
angle = val;
|
||||||
|
ang.value = [angle * FlxAngle.TO_RAD];
|
||||||
|
return angle;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_distance(val:Float):Float
|
||||||
|
{
|
||||||
|
distance = val;
|
||||||
|
dist.value = [val];
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_strength(val:Float):Float
|
||||||
|
{
|
||||||
|
strength = val;
|
||||||
|
str.value = [val];
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_attachedSprite(spr:FlxSprite):FlxSprite
|
||||||
|
{
|
||||||
|
attachedSprite = spr;
|
||||||
|
updateFrameInfo(attachedSprite.frame);
|
||||||
|
return spr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Loads an image for the mask.
|
||||||
|
While you *could* directly set the value of the mask, this function works for both HTML5 and desktop targets.
|
||||||
|
*/
|
||||||
|
public function loadAltMask(path:String)
|
||||||
|
{
|
||||||
|
#if html5
|
||||||
|
BitmapData.loadFromFile(path).onComplete(function(bmp:BitmapData) {
|
||||||
|
altMaskImage = bmp;
|
||||||
|
});
|
||||||
|
#else
|
||||||
|
altMaskImage = BitmapData.fromFile(path);
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Should be called on the animation.callback of the attached sprite.
|
||||||
|
TODO: figure out why the reference to the attachedSprite breaks on web??
|
||||||
|
*/
|
||||||
|
public function onAttachedFrame(name, frameNum, frameIndex)
|
||||||
|
{
|
||||||
|
if (attachedSprite != null) updateFrameInfo(attachedSprite.frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Updates the frame bounds and angle offset of the sprite for the shader.
|
||||||
|
*/
|
||||||
|
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];
|
||||||
|
|
||||||
|
// if a frame is rotated the shader will look completely wrong lol
|
||||||
|
angOffset.value = [frame.angle * FlxAngle.TO_RAD];
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_altMaskImage(_bitmapData:BitmapData):BitmapData
|
||||||
|
{
|
||||||
|
altMask.input = _bitmapData;
|
||||||
|
|
||||||
|
return _bitmapData;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_maskThreshold(val:Float):Float
|
||||||
|
{
|
||||||
|
maskThreshold = val;
|
||||||
|
thr2.value = [val];
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_useAltMask(val:Bool):Bool
|
||||||
|
{
|
||||||
|
useAltMask = val;
|
||||||
|
useMask.value = [val];
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
@:glFragmentSource('
|
||||||
|
#pragma header
|
||||||
|
|
||||||
|
// This shader aims to mostly recreate how Adobe Animate/Flash handles drop shadows, but its main use here is for rim lighting.
|
||||||
|
|
||||||
|
// this shader also includes a recreation of the Animate/Flash "Adjust Color" filter,
|
||||||
|
// which was kindly provided and written by Rozebud https://github.com/ThatRozebudDude ( thank u rozebud :) )
|
||||||
|
// Adapted from Andrey-Postelzhuks shader found here: https://forum.unity.com/threads/hue-saturation-brightness-contrast-shader.260649/
|
||||||
|
// Hue rotation stuff is from here: https://www.w3.org/TR/filter-effects/#feColorMatrixElement
|
||||||
|
|
||||||
|
// equals (frame.left, frame.top, frame.right, frame.bottom)
|
||||||
|
uniform vec4 uFrameBounds;
|
||||||
|
|
||||||
|
uniform float ang;
|
||||||
|
uniform float dist;
|
||||||
|
uniform float str;
|
||||||
|
uniform float thr;
|
||||||
|
|
||||||
|
// need to account for rotated frames... oops
|
||||||
|
uniform float angOffset;
|
||||||
|
|
||||||
|
uniform sampler2D altMask;
|
||||||
|
uniform bool useMask;
|
||||||
|
uniform float thr2;
|
||||||
|
|
||||||
|
uniform vec3 dropColor;
|
||||||
|
|
||||||
|
uniform float hue;
|
||||||
|
uniform float saturation;
|
||||||
|
uniform float brightness;
|
||||||
|
uniform float contrast;
|
||||||
|
|
||||||
|
uniform float AA_STAGES;
|
||||||
|
|
||||||
|
const vec3 grayscaleValues = vec3(0.3098039215686275, 0.607843137254902, 0.0823529411764706);
|
||||||
|
const float e = 2.718281828459045;
|
||||||
|
|
||||||
|
vec3 applyHueRotate(vec3 aColor, float aHue){
|
||||||
|
float angle = radians(aHue);
|
||||||
|
|
||||||
|
mat3 m1 = mat3(0.213, 0.213, 0.213, 0.715, 0.715, 0.715, 0.072, 0.072, 0.072);
|
||||||
|
mat3 m2 = mat3(0.787, -0.213, -0.213, -0.715, 0.285, -0.715, -0.072, -0.072, 0.928);
|
||||||
|
mat3 m3 = mat3(-0.213, 0.143, -0.787, -0.715, 0.140, 0.715, 0.928, -0.283, 0.072);
|
||||||
|
mat3 m = m1 + cos(angle) * m2 + sin(angle) * m3;
|
||||||
|
|
||||||
|
return m * aColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 applySaturation(vec3 aColor, float value){
|
||||||
|
if(value > 0.0){ value = value * 3.0; }
|
||||||
|
value = (1.0 + (value / 100.0));
|
||||||
|
vec3 grayscale = vec3(dot(aColor, grayscaleValues));
|
||||||
|
return clamp(mix(grayscale, aColor, value), 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 applyContrast(vec3 aColor, float value){
|
||||||
|
value = (1.0 + (value / 100.0));
|
||||||
|
if(value > 1.0){
|
||||||
|
value = (((0.00852259 * pow(e, 4.76454 * (value - 1.0))) * 1.01) - 0.0086078159) * 10.0; //Just roll with it...
|
||||||
|
value += 1.0;
|
||||||
|
}
|
||||||
|
return clamp((aColor - 0.25) * value + 0.25, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 applyHSBCEffect(vec3 color){
|
||||||
|
|
||||||
|
//Brightness
|
||||||
|
color = color + ((brightness) / 255.0);
|
||||||
|
|
||||||
|
//Hue
|
||||||
|
color = applyHueRotate(color, hue);
|
||||||
|
|
||||||
|
//Contrast
|
||||||
|
color = applyContrast(color, contrast);
|
||||||
|
|
||||||
|
//Saturation
|
||||||
|
color = applySaturation(color, saturation);
|
||||||
|
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 hash22(vec2 p) {
|
||||||
|
vec3 p3 = fract(vec3(p.xyx) * vec3(.1031, .1030, .0973));
|
||||||
|
p3 += dot(p3, p3.yzx + 33.33);
|
||||||
|
return fract((p3.xx + p3.yz) * p3.zy);
|
||||||
|
}
|
||||||
|
|
||||||
|
float intensityPass(vec2 fragCoord, float curThreshold, bool useMask) {
|
||||||
|
vec4 col = texture2D(bitmap, fragCoord);
|
||||||
|
|
||||||
|
float maskIntensity = 0.0;
|
||||||
|
if(useMask == true){
|
||||||
|
maskIntensity = mix(0.0, 1.0, texture2D(altMask, fragCoord).b);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(col.a == 0.0){
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
float intensity = dot(col.rgb, vec3(0.3098, 0.6078, 0.0823));
|
||||||
|
|
||||||
|
intensity = maskIntensity > 0.0 ? float(intensity > thr2) : float(intensity > thr);
|
||||||
|
|
||||||
|
return intensity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// essentially just stole this from the AngleMask shader but repurposed it to smooth
|
||||||
|
// the threshold because without any sort of smoothing it produces horrible edges
|
||||||
|
float antialias(vec2 fragCoord, float curThreshold, bool useMask) {
|
||||||
|
|
||||||
|
// In GLSL 100, we need to use constant loop bounds
|
||||||
|
// Well assume a reasonable maximum for AA_STAGES and use a fixed loop
|
||||||
|
// The actual number of iterations will be controlled by a condition inside
|
||||||
|
const int MAX_AA = 8; // This should be large enough for most uses
|
||||||
|
|
||||||
|
float AA_TOTAL_PASSES = AA_STAGES * AA_STAGES + 1.0;
|
||||||
|
const float AA_JITTER = 0.5;
|
||||||
|
|
||||||
|
// Run the shader multiple times with a random subpixel offset each time and average the results
|
||||||
|
float color = intensityPass(fragCoord, curThreshold, useMask);
|
||||||
|
for (int i = 0; i < MAX_AA * MAX_AA; i++) {
|
||||||
|
// Calculate x and y from i
|
||||||
|
int x = i / MAX_AA;
|
||||||
|
int y = i - (MAX_AA * int(i/MAX_AA)); // poor mans modulus
|
||||||
|
|
||||||
|
// Skip iterations beyond our desired AA_STAGES
|
||||||
|
if (float(x) >= AA_STAGES || float(y) >= AA_STAGES) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 offset = AA_JITTER * (2.0 * hash22(vec2(float(x), float(y))) - 1.0) / openfl_TextureSize.xy;
|
||||||
|
color += intensityPass(fragCoord + offset, curThreshold, useMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
return color / AA_TOTAL_PASSES;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 createDropShadow(vec3 col, float curThreshold, bool useMask) {
|
||||||
|
|
||||||
|
// essentially a mask so that areas under the threshold dont show the rimlight (mainly the outlines)
|
||||||
|
float intensity = antialias(openfl_TextureCoordv, curThreshold, useMask);
|
||||||
|
|
||||||
|
// the distance the dropshadow moves needs to be correctly scaled based on the texture size
|
||||||
|
vec2 imageRatio = vec2(1.0/openfl_TextureSize.x, 1.0/openfl_TextureSize.y);
|
||||||
|
|
||||||
|
// check the pixel in the direction and distance specified
|
||||||
|
vec2 checkedPixel = vec2(openfl_TextureCoordv.x + (dist * cos(ang + angOffset) * imageRatio.x), openfl_TextureCoordv.y - (dist * sin(ang + angOffset) * imageRatio.y));
|
||||||
|
|
||||||
|
// multiplier for the intensity of the drop shadow
|
||||||
|
float dropShadowAmount = 0.0;
|
||||||
|
|
||||||
|
if(checkedPixel.x > uFrameBounds.x && checkedPixel.y > uFrameBounds.y && checkedPixel.x < uFrameBounds.z && checkedPixel.y < uFrameBounds.w){
|
||||||
|
dropShadowAmount = texture2D(bitmap, checkedPixel).a;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the dropshadow color based on the amount, strength, and intensity
|
||||||
|
col.rgb += dropColor.rgb * ((1.0 - (dropShadowAmount * str))*intensity);
|
||||||
|
|
||||||
|
return col;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec4 col = texture2D(bitmap, openfl_TextureCoordv);
|
||||||
|
|
||||||
|
vec3 unpremultipliedColor = col.a > 0.0 ? col.rgb / col.a : col.rgb;
|
||||||
|
|
||||||
|
vec3 outColor = applyHSBCEffect(unpremultipliedColor);
|
||||||
|
|
||||||
|
outColor = createDropShadow(outColor, thr, useMask);
|
||||||
|
|
||||||
|
gl_FragColor = vec4(outColor.rgb * col.a, col.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
')
|
||||||
|
public function new()
|
||||||
|
{
|
||||||
|
super();
|
||||||
|
|
||||||
|
angle = 0;
|
||||||
|
strength = 1;
|
||||||
|
distance = 15;
|
||||||
|
threshold = 0.1;
|
||||||
|
|
||||||
|
baseHue = 0;
|
||||||
|
baseSaturation = 0;
|
||||||
|
baseBrightness = 0;
|
||||||
|
baseContrast = 0;
|
||||||
|
|
||||||
|
antialiasAmt = 2;
|
||||||
|
|
||||||
|
useAltMask = false;
|
||||||
|
|
||||||
|
angOffset.value = [0];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,13 +8,13 @@ class HSVShader extends FlxRuntimeShader
|
|||||||
public var saturation(default, set):Float;
|
public var saturation(default, set):Float;
|
||||||
public var value(default, set):Float;
|
public var value(default, set):Float;
|
||||||
|
|
||||||
public function new()
|
public function new(h:Float = 1, s:Float = 1, v:Float = 1)
|
||||||
{
|
{
|
||||||
super(Assets.getText(Paths.frag('hsv')));
|
super(Assets.getText(Paths.frag('hsv')));
|
||||||
FlxG.debugger.addTrackerProfile(new TrackerProfile(HSVShader, ['hue', 'saturation', 'value']));
|
FlxG.debugger.addTrackerProfile(new TrackerProfile(HSVShader, ['hue', 'saturation', 'value']));
|
||||||
hue = 1;
|
hue = h;
|
||||||
saturation = 1;
|
saturation = s;
|
||||||
value = 1;
|
value = v;
|
||||||
}
|
}
|
||||||
|
|
||||||
function set_hue(value:Float):Float
|
function set_hue(value:Float):Float
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package funkin.graphics.shaders;
|
package funkin.graphics.shaders;
|
||||||
|
|
||||||
import flixel.system.FlxAssets.FlxShader;
|
|
||||||
import openfl.display.BitmapData;
|
import openfl.display.BitmapData;
|
||||||
import openfl.display.ShaderParameter;
|
import openfl.display.ShaderParameter;
|
||||||
import openfl.display.ShaderParameterType;
|
import openfl.display.ShaderParameterType;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package funkin.graphics.shaders;
|
package funkin.graphics.shaders;
|
||||||
|
|
||||||
import flixel.system.FlxAssets.FlxShader;
|
import flixel.system.FlxAssets.FlxShader;
|
||||||
import flixel.util.FlxColor;
|
|
||||||
import openfl.display.BitmapData;
|
import openfl.display.BitmapData;
|
||||||
|
|
||||||
class TextureSwap extends FlxShader
|
class TextureSwap extends FlxShader
|
||||||
@@ -9,6 +8,17 @@ class TextureSwap extends FlxShader
|
|||||||
public var swappedImage(default, set):BitmapData;
|
public var swappedImage(default, set):BitmapData;
|
||||||
public var amount(default, set):Float;
|
public var amount(default, set):Float;
|
||||||
|
|
||||||
|
public function loadSwapImage(path:String)
|
||||||
|
{
|
||||||
|
#if html5
|
||||||
|
BitmapData.loadFromFile(path).onComplete(function(bmp:BitmapData) {
|
||||||
|
swappedImage = bmp;
|
||||||
|
});
|
||||||
|
#else
|
||||||
|
swappedImage = BitmapData.fromFile(path);
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
function set_swappedImage(_bitmapData:BitmapData):BitmapData
|
function set_swappedImage(_bitmapData:BitmapData):BitmapData
|
||||||
{
|
{
|
||||||
image.input = _bitmapData;
|
image.input = _bitmapData;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package funkin.graphics.shaders;
|
package funkin.graphics.shaders;
|
||||||
|
|
||||||
import flixel.math.FlxPoint;
|
|
||||||
import flixel.system.FlxAssets.FlxShader;
|
import flixel.system.FlxAssets.FlxShader;
|
||||||
|
|
||||||
class TitleOutline extends FlxShader
|
class TitleOutline extends FlxShader
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
package funkin.graphics.video;
|
package funkin.graphics.video;
|
||||||
|
|
||||||
import flixel.util.FlxColor;
|
import flixel.util.FlxColor;
|
||||||
import flixel.util.FlxSignal.FlxTypedSignal;
|
|
||||||
import funkin.audio.FunkinSound;
|
import funkin.audio.FunkinSound;
|
||||||
import openfl.display3D.textures.TextureBase;
|
|
||||||
import openfl.events.NetStatusEvent;
|
import openfl.events.NetStatusEvent;
|
||||||
import openfl.media.SoundTransform;
|
import openfl.media.SoundTransform;
|
||||||
import openfl.media.Video;
|
import openfl.media.Video;
|
||||||
@@ -12,8 +10,8 @@ import openfl.net.NetStream;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Plays a video via a NetStream. Only works on HTML5.
|
* Plays a video via a NetStream. Only works on HTML5.
|
||||||
* This does NOT replace hxCodec, nor does hxCodec replace this.
|
* This does NOT replace hxvlc, nor does hxvlc replace this.
|
||||||
* hxCodec only works on desktop and does not work on HTML5!
|
* hxvlc only works on desktop and does not work on HTML5!
|
||||||
*/
|
*/
|
||||||
class FlxVideo extends FunkinSprite
|
class FlxVideo extends FunkinSprite
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,34 +1,17 @@
|
|||||||
package funkin.graphics.video;
|
package funkin.graphics.video;
|
||||||
|
|
||||||
#if hxCodec
|
#if hxvlc
|
||||||
import hxcodec.flixel.FlxVideoSprite;
|
import hxvlc.flixel.FlxVideoSprite;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Not to be confused with FlxVideo, this is a hxcodec based video class
|
* Not to be confused with FlxVideo, this is a hxvlc based video class
|
||||||
* We override it simply to correct/control our volume easier.
|
* We override it simply to correct/control our volume easier.
|
||||||
*/
|
*/
|
||||||
class FunkinVideoSprite extends FlxVideoSprite
|
class FunkinVideoSprite extends FlxVideoSprite
|
||||||
{
|
{
|
||||||
public var volume(default, set):Float = 1;
|
|
||||||
|
|
||||||
public function new(x:Float = 0, y:Float = 0)
|
public function new(x:Float = 0, y:Float = 0)
|
||||||
{
|
{
|
||||||
super(x, y);
|
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
|
#end
|
||||||
|
|||||||
@@ -2,23 +2,17 @@ package funkin.input;
|
|||||||
|
|
||||||
import flixel.input.gamepad.FlxGamepad;
|
import flixel.input.gamepad.FlxGamepad;
|
||||||
import flixel.util.FlxDirectionFlags;
|
import flixel.util.FlxDirectionFlags;
|
||||||
import flixel.FlxObject;
|
import flixel.input.FlxInput.FlxInputState;
|
||||||
import flixel.input.FlxInput;
|
|
||||||
import flixel.input.actions.FlxAction;
|
import flixel.input.actions.FlxAction;
|
||||||
import flixel.input.actions.FlxActionInput;
|
import flixel.input.actions.FlxActionInput;
|
||||||
import flixel.input.actions.FlxActionInputAnalog.FlxActionInputAnalogClickAndDragMouseMotion;
|
|
||||||
import flixel.input.actions.FlxActionInputDigital;
|
import flixel.input.actions.FlxActionInputDigital;
|
||||||
import flixel.input.actions.FlxActionManager;
|
import flixel.input.actions.FlxActionManager;
|
||||||
import flixel.input.actions.FlxActionSet;
|
import flixel.input.actions.FlxActionSet;
|
||||||
import flixel.input.android.FlxAndroidKey;
|
import flixel.input.android.FlxAndroidKey;
|
||||||
import flixel.input.gamepad.FlxGamepadButton;
|
|
||||||
import flixel.input.gamepad.FlxGamepadInputID;
|
import flixel.input.gamepad.FlxGamepadInputID;
|
||||||
import flixel.input.keyboard.FlxKey;
|
import flixel.input.keyboard.FlxKey;
|
||||||
import flixel.input.mouse.FlxMouseButton.FlxMouseButtonID;
|
|
||||||
import flixel.math.FlxAngle;
|
import flixel.math.FlxAngle;
|
||||||
import flixel.math.FlxPoint;
|
import flixel.math.FlxPoint;
|
||||||
import flixel.util.FlxColor;
|
|
||||||
import flixel.util.FlxTimer;
|
|
||||||
import lime.ui.Haptic;
|
import lime.ui.Haptic;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -67,6 +61,8 @@ class Controls extends FlxActionSet
|
|||||||
var _freeplay_left = new FunkinAction(Action.FREEPLAY_LEFT);
|
var _freeplay_left = new FunkinAction(Action.FREEPLAY_LEFT);
|
||||||
var _freeplay_right = new FunkinAction(Action.FREEPLAY_RIGHT);
|
var _freeplay_right = new FunkinAction(Action.FREEPLAY_RIGHT);
|
||||||
var _freeplay_char_select = new FunkinAction(Action.FREEPLAY_CHAR_SELECT);
|
var _freeplay_char_select = new FunkinAction(Action.FREEPLAY_CHAR_SELECT);
|
||||||
|
var _freeplay_jump_to_top = new FunkinAction(Action.FREEPLAY_JUMP_TO_TOP);
|
||||||
|
var _freeplay_jump_to_bottom = new FunkinAction(Action.FREEPLAY_JUMP_TO_BOTTOM);
|
||||||
var _cutscene_advance = new FunkinAction(Action.CUTSCENE_ADVANCE);
|
var _cutscene_advance = new FunkinAction(Action.CUTSCENE_ADVANCE);
|
||||||
var _debug_menu = new FunkinAction(Action.DEBUG_MENU);
|
var _debug_menu = new FunkinAction(Action.DEBUG_MENU);
|
||||||
#if FEATURE_CHART_EDITOR
|
#if FEATURE_CHART_EDITOR
|
||||||
@@ -276,6 +272,16 @@ class Controls extends FlxActionSet
|
|||||||
inline function get_FREEPLAY_CHAR_SELECT()
|
inline function get_FREEPLAY_CHAR_SELECT()
|
||||||
return _freeplay_char_select.check();
|
return _freeplay_char_select.check();
|
||||||
|
|
||||||
|
public var FREEPLAY_JUMP_TO_TOP(get, never):Bool;
|
||||||
|
|
||||||
|
inline function get_FREEPLAY_JUMP_TO_TOP()
|
||||||
|
return _freeplay_jump_to_top.check();
|
||||||
|
|
||||||
|
public var FREEPLAY_JUMP_TO_BOTTOM(get, never):Bool;
|
||||||
|
|
||||||
|
inline function get_FREEPLAY_JUMP_TO_BOTTOM()
|
||||||
|
return _freeplay_jump_to_bottom.check();
|
||||||
|
|
||||||
public var CUTSCENE_ADVANCE(get, never):Bool;
|
public var CUTSCENE_ADVANCE(get, never):Bool;
|
||||||
|
|
||||||
inline function get_CUTSCENE_ADVANCE()
|
inline function get_CUTSCENE_ADVANCE()
|
||||||
@@ -337,6 +343,8 @@ class Controls extends FlxActionSet
|
|||||||
add(_freeplay_left);
|
add(_freeplay_left);
|
||||||
add(_freeplay_right);
|
add(_freeplay_right);
|
||||||
add(_freeplay_char_select);
|
add(_freeplay_char_select);
|
||||||
|
add(_freeplay_jump_to_top);
|
||||||
|
add(_freeplay_jump_to_bottom);
|
||||||
add(_cutscene_advance);
|
add(_cutscene_advance);
|
||||||
add(_debug_menu);
|
add(_debug_menu);
|
||||||
#if FEATURE_CHART_EDITOR add(_debug_chart); #end
|
#if FEATURE_CHART_EDITOR add(_debug_chart); #end
|
||||||
@@ -409,6 +417,7 @@ class Controls extends FlxActionSet
|
|||||||
|
|
||||||
public function getDialogueName(action:FlxActionDigital, ?ignoreSurrounding:Bool = false):String
|
public function getDialogueName(action:FlxActionDigital, ?ignoreSurrounding:Bool = false):String
|
||||||
{
|
{
|
||||||
|
if (action.inputs.length == 0) return 'N/A';
|
||||||
var input = action.inputs[0];
|
var input = action.inputs[0];
|
||||||
if (ignoreSurrounding == false)
|
if (ignoreSurrounding == false)
|
||||||
{
|
{
|
||||||
@@ -462,6 +471,8 @@ class Controls extends FlxActionSet
|
|||||||
case FREEPLAY_LEFT: _freeplay_left;
|
case FREEPLAY_LEFT: _freeplay_left;
|
||||||
case FREEPLAY_RIGHT: _freeplay_right;
|
case FREEPLAY_RIGHT: _freeplay_right;
|
||||||
case FREEPLAY_CHAR_SELECT: _freeplay_char_select;
|
case FREEPLAY_CHAR_SELECT: _freeplay_char_select;
|
||||||
|
case FREEPLAY_JUMP_TO_TOP: _freeplay_jump_to_top;
|
||||||
|
case FREEPLAY_JUMP_TO_BOTTOM: _freeplay_jump_to_bottom;
|
||||||
case CUTSCENE_ADVANCE: _cutscene_advance;
|
case CUTSCENE_ADVANCE: _cutscene_advance;
|
||||||
case DEBUG_MENU: _debug_menu;
|
case DEBUG_MENU: _debug_menu;
|
||||||
#if FEATURE_CHART_EDITOR case DEBUG_CHART: _debug_chart; #end
|
#if FEATURE_CHART_EDITOR case DEBUG_CHART: _debug_chart; #end
|
||||||
@@ -542,6 +553,10 @@ class Controls extends FlxActionSet
|
|||||||
func(_freeplay_right, JUST_PRESSED);
|
func(_freeplay_right, JUST_PRESSED);
|
||||||
case FREEPLAY_CHAR_SELECT:
|
case FREEPLAY_CHAR_SELECT:
|
||||||
func(_freeplay_char_select, JUST_PRESSED);
|
func(_freeplay_char_select, JUST_PRESSED);
|
||||||
|
case FREEPLAY_JUMP_TO_TOP:
|
||||||
|
func(_freeplay_jump_to_top, JUST_PRESSED);
|
||||||
|
case FREEPLAY_JUMP_TO_BOTTOM:
|
||||||
|
func(_freeplay_jump_to_bottom, JUST_PRESSED);
|
||||||
case CUTSCENE_ADVANCE:
|
case CUTSCENE_ADVANCE:
|
||||||
func(_cutscene_advance, JUST_PRESSED);
|
func(_cutscene_advance, JUST_PRESSED);
|
||||||
case DEBUG_MENU:
|
case DEBUG_MENU:
|
||||||
@@ -711,7 +726,7 @@ class Controls extends FlxActionSet
|
|||||||
forEachBound(control, function(action, state) addKeys(action, keys, state));
|
forEachBound(control, function(action, state) addKeys(action, keys, state));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function bindSwipe(control:Control, swipeDir:Int = FlxDirectionFlags.UP, ?swpLength:Float = 90)
|
public function bindSwipe(control:Control, swipeDir:FlxDirectionFlags = FlxDirectionFlags.UP, ?swpLength:Float = 90)
|
||||||
{
|
{
|
||||||
forEachBound(control, function(action, press) action.add(new FlxActionInputDigitalMobileSwipeGameplay(swipeDir, press, swpLength)));
|
forEachBound(control, function(action, press) action.add(new FlxActionInputDigitalMobileSwipeGameplay(swipeDir, press, swpLength)));
|
||||||
}
|
}
|
||||||
@@ -770,6 +785,8 @@ class Controls extends FlxActionSet
|
|||||||
bindKeys(Control.FREEPLAY_LEFT, getDefaultKeybinds(scheme, Control.FREEPLAY_LEFT));
|
bindKeys(Control.FREEPLAY_LEFT, getDefaultKeybinds(scheme, Control.FREEPLAY_LEFT));
|
||||||
bindKeys(Control.FREEPLAY_RIGHT, getDefaultKeybinds(scheme, Control.FREEPLAY_RIGHT));
|
bindKeys(Control.FREEPLAY_RIGHT, getDefaultKeybinds(scheme, Control.FREEPLAY_RIGHT));
|
||||||
bindKeys(Control.FREEPLAY_CHAR_SELECT, getDefaultKeybinds(scheme, Control.FREEPLAY_CHAR_SELECT));
|
bindKeys(Control.FREEPLAY_CHAR_SELECT, getDefaultKeybinds(scheme, Control.FREEPLAY_CHAR_SELECT));
|
||||||
|
bindKeys(Control.FREEPLAY_JUMP_TO_TOP, getDefaultKeybinds(scheme, Control.FREEPLAY_JUMP_TO_TOP));
|
||||||
|
bindKeys(Control.FREEPLAY_JUMP_TO_BOTTOM, getDefaultKeybinds(scheme, Control.FREEPLAY_JUMP_TO_BOTTOM));
|
||||||
bindKeys(Control.CUTSCENE_ADVANCE, getDefaultKeybinds(scheme, Control.CUTSCENE_ADVANCE));
|
bindKeys(Control.CUTSCENE_ADVANCE, getDefaultKeybinds(scheme, Control.CUTSCENE_ADVANCE));
|
||||||
bindKeys(Control.DEBUG_MENU, getDefaultKeybinds(scheme, Control.DEBUG_MENU));
|
bindKeys(Control.DEBUG_MENU, getDefaultKeybinds(scheme, Control.DEBUG_MENU));
|
||||||
#if FEATURE_CHART_EDITOR
|
#if FEATURE_CHART_EDITOR
|
||||||
@@ -810,6 +827,8 @@ class Controls extends FlxActionSet
|
|||||||
case Control.FREEPLAY_LEFT: return [Q]; // Switch tabs on the menu
|
case Control.FREEPLAY_LEFT: return [Q]; // Switch tabs on the menu
|
||||||
case Control.FREEPLAY_RIGHT: return [E]; // Switch tabs on the menu
|
case Control.FREEPLAY_RIGHT: return [E]; // Switch tabs on the menu
|
||||||
case Control.FREEPLAY_CHAR_SELECT: return [TAB];
|
case Control.FREEPLAY_CHAR_SELECT: return [TAB];
|
||||||
|
case Control.FREEPLAY_JUMP_TO_TOP: return [HOME];
|
||||||
|
case Control.FREEPLAY_JUMP_TO_BOTTOM: return [END];
|
||||||
case Control.CUTSCENE_ADVANCE: return [Z, ENTER];
|
case Control.CUTSCENE_ADVANCE: return [Z, ENTER];
|
||||||
case Control.DEBUG_MENU: return [GRAVEACCENT];
|
case Control.DEBUG_MENU: return [GRAVEACCENT];
|
||||||
#if FEATURE_CHART_EDITOR case Control.DEBUG_CHART: return []; #end
|
#if FEATURE_CHART_EDITOR case Control.DEBUG_CHART: return []; #end
|
||||||
@@ -839,6 +858,8 @@ class Controls extends FlxActionSet
|
|||||||
case Control.FREEPLAY_LEFT: return [Q]; // Switch tabs on the menu
|
case Control.FREEPLAY_LEFT: return [Q]; // Switch tabs on the menu
|
||||||
case Control.FREEPLAY_RIGHT: return [E]; // Switch tabs on the menu
|
case Control.FREEPLAY_RIGHT: return [E]; // Switch tabs on the menu
|
||||||
case Control.FREEPLAY_CHAR_SELECT: return [TAB];
|
case Control.FREEPLAY_CHAR_SELECT: return [TAB];
|
||||||
|
case Control.FREEPLAY_JUMP_TO_TOP: return [HOME];
|
||||||
|
case Control.FREEPLAY_JUMP_TO_BOTTOM: return [END];
|
||||||
case Control.CUTSCENE_ADVANCE: return [G, Z];
|
case Control.CUTSCENE_ADVANCE: return [G, Z];
|
||||||
case Control.DEBUG_MENU: return [GRAVEACCENT];
|
case Control.DEBUG_MENU: return [GRAVEACCENT];
|
||||||
#if FEATURE_CHART_EDITOR case Control.DEBUG_CHART: return []; #end
|
#if FEATURE_CHART_EDITOR case Control.DEBUG_CHART: return []; #end
|
||||||
@@ -868,6 +889,8 @@ class Controls extends FlxActionSet
|
|||||||
case Control.FREEPLAY_LEFT: return [];
|
case Control.FREEPLAY_LEFT: return [];
|
||||||
case Control.FREEPLAY_RIGHT: return [];
|
case Control.FREEPLAY_RIGHT: return [];
|
||||||
case Control.FREEPLAY_CHAR_SELECT: return [];
|
case Control.FREEPLAY_CHAR_SELECT: return [];
|
||||||
|
case Control.FREEPLAY_JUMP_TO_TOP: return [];
|
||||||
|
case Control.FREEPLAY_JUMP_TO_BOTTOM: return [];
|
||||||
case Control.CUTSCENE_ADVANCE: return [ENTER];
|
case Control.CUTSCENE_ADVANCE: return [ENTER];
|
||||||
case Control.DEBUG_MENU: return [];
|
case Control.DEBUG_MENU: return [];
|
||||||
#if FEATURE_CHART_EDITOR case Control.DEBUG_CHART: return []; #end
|
#if FEATURE_CHART_EDITOR case Control.DEBUG_CHART: return []; #end
|
||||||
@@ -983,6 +1006,9 @@ class Controls extends FlxActionSet
|
|||||||
Control.FREEPLAY_FAVORITE => getDefaultGamepadBinds(Control.FREEPLAY_FAVORITE),
|
Control.FREEPLAY_FAVORITE => getDefaultGamepadBinds(Control.FREEPLAY_FAVORITE),
|
||||||
Control.FREEPLAY_LEFT => getDefaultGamepadBinds(Control.FREEPLAY_LEFT),
|
Control.FREEPLAY_LEFT => getDefaultGamepadBinds(Control.FREEPLAY_LEFT),
|
||||||
Control.FREEPLAY_RIGHT => getDefaultGamepadBinds(Control.FREEPLAY_RIGHT),
|
Control.FREEPLAY_RIGHT => getDefaultGamepadBinds(Control.FREEPLAY_RIGHT),
|
||||||
|
Control.FREEPLAY_CHAR_SELECT => getDefaultGamepadBinds(Control.FREEPLAY_CHAR_SELECT),
|
||||||
|
Control.FREEPLAY_JUMP_TO_TOP => getDefaultGamepadBinds(Control.FREEPLAY_JUMP_TO_TOP),
|
||||||
|
Control.FREEPLAY_JUMP_TO_BOTTOM => getDefaultGamepadBinds(Control.FREEPLAY_JUMP_TO_BOTTOM),
|
||||||
Control.VOLUME_UP => getDefaultGamepadBinds(Control.VOLUME_UP),
|
Control.VOLUME_UP => getDefaultGamepadBinds(Control.VOLUME_UP),
|
||||||
Control.VOLUME_DOWN => getDefaultGamepadBinds(Control.VOLUME_DOWN),
|
Control.VOLUME_DOWN => getDefaultGamepadBinds(Control.VOLUME_DOWN),
|
||||||
Control.VOLUME_MUTE => getDefaultGamepadBinds(Control.VOLUME_MUTE),
|
Control.VOLUME_MUTE => getDefaultGamepadBinds(Control.VOLUME_MUTE),
|
||||||
@@ -1033,11 +1059,17 @@ class Controls extends FlxActionSet
|
|||||||
case Control.CUTSCENE_ADVANCE:
|
case Control.CUTSCENE_ADVANCE:
|
||||||
return [A];
|
return [A];
|
||||||
case Control.FREEPLAY_FAVORITE:
|
case Control.FREEPLAY_FAVORITE:
|
||||||
[FlxGamepadInputID.BACK]; // Back (i.e. Select)
|
return [Y]; // Back (i.e. Select)
|
||||||
case Control.FREEPLAY_LEFT:
|
case Control.FREEPLAY_LEFT:
|
||||||
[LEFT_SHOULDER];
|
return [LEFT_SHOULDER];
|
||||||
case Control.FREEPLAY_RIGHT:
|
case Control.FREEPLAY_RIGHT:
|
||||||
[RIGHT_SHOULDER];
|
return [RIGHT_SHOULDER];
|
||||||
|
case Control.FREEPLAY_CHAR_SELECT:
|
||||||
|
return [X];
|
||||||
|
case Control.FREEPLAY_JUMP_TO_TOP:
|
||||||
|
return [];
|
||||||
|
case Control.FREEPLAY_JUMP_TO_BOTTOM:
|
||||||
|
return [];
|
||||||
case Control.VOLUME_UP:
|
case Control.VOLUME_UP:
|
||||||
[];
|
[];
|
||||||
case Control.VOLUME_DOWN:
|
case Control.VOLUME_DOWN:
|
||||||
@@ -1446,9 +1478,9 @@ class FlxActionInputDigitalMobileSwipeGameplay extends FlxActionInputDigital
|
|||||||
var activateLength:Float = 90;
|
var activateLength:Float = 90;
|
||||||
var hapticPressure:Int = 100;
|
var hapticPressure:Int = 100;
|
||||||
|
|
||||||
public function new(swipeDir:Int = FlxDirectionFlags.ANY, Trigger:FlxInputState, ?swipeLength:Float = 90)
|
public function new(swipeDir:FlxDirectionFlags = FlxDirectionFlags.ANY, Trigger:FlxInputState, ?swipeLength:Float = 90)
|
||||||
{
|
{
|
||||||
super(OTHER, swipeDir, Trigger);
|
super(OTHER, swipeDir.toInt(), Trigger);
|
||||||
|
|
||||||
activateLength = swipeLength;
|
activateLength = swipeLength;
|
||||||
}
|
}
|
||||||
@@ -1530,16 +1562,21 @@ class FlxActionInputDigitalMobileSwipeGameplay extends FlxActionInputDigital
|
|||||||
case JUST_PRESSED:
|
case JUST_PRESSED:
|
||||||
if (swp.touchLength >= activateLength)
|
if (swp.touchLength >= activateLength)
|
||||||
{
|
{
|
||||||
switch (inputID)
|
if (inputID == FlxDirectionFlags.UP.toInt())
|
||||||
{
|
{
|
||||||
case FlxDirectionFlags.UP:
|
if (degAngle >= 45 && degAngle <= 90 + 45) return properTouch(swp);
|
||||||
if (degAngle >= 45 && degAngle <= 90 + 45) return properTouch(swp);
|
}
|
||||||
case FlxDirectionFlags.DOWN:
|
else if (inputID == FlxDirectionFlags.DOWN.toInt())
|
||||||
if (-degAngle >= 45 && -degAngle <= 90 + 45) return properTouch(swp);
|
{
|
||||||
case FlxDirectionFlags.LEFT:
|
if (-degAngle >= 45 && -degAngle <= 90 + 45) return properTouch(swp);
|
||||||
if (degAngle <= 45 && -degAngle <= 45) return properTouch(swp);
|
}
|
||||||
case FlxDirectionFlags.RIGHT:
|
else if (inputID == FlxDirectionFlags.LEFT.toInt())
|
||||||
if (degAngle >= 90 + 45 && degAngle <= -90 + -45) return properTouch(swp);
|
{
|
||||||
|
if (degAngle <= 45 && -degAngle <= 45) return properTouch(swp);
|
||||||
|
}
|
||||||
|
else if (inputID == FlxDirectionFlags.RIGHT.toInt())
|
||||||
|
{
|
||||||
|
if (degAngle >= 90 + 45 && degAngle <= -90 + -45) return properTouch(swp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -1602,10 +1639,10 @@ enum Control
|
|||||||
NOTE_UP;
|
NOTE_UP;
|
||||||
NOTE_RIGHT;
|
NOTE_RIGHT;
|
||||||
// UI
|
// UI
|
||||||
UI_UP;
|
|
||||||
UI_LEFT;
|
UI_LEFT;
|
||||||
UI_RIGHT;
|
|
||||||
UI_DOWN;
|
UI_DOWN;
|
||||||
|
UI_UP;
|
||||||
|
UI_RIGHT;
|
||||||
RESET;
|
RESET;
|
||||||
ACCEPT;
|
ACCEPT;
|
||||||
BACK;
|
BACK;
|
||||||
@@ -1617,6 +1654,8 @@ enum Control
|
|||||||
FREEPLAY_LEFT;
|
FREEPLAY_LEFT;
|
||||||
FREEPLAY_RIGHT;
|
FREEPLAY_RIGHT;
|
||||||
FREEPLAY_CHAR_SELECT;
|
FREEPLAY_CHAR_SELECT;
|
||||||
|
FREEPLAY_JUMP_TO_TOP;
|
||||||
|
FREEPLAY_JUMP_TO_BOTTOM;
|
||||||
// WINDOW
|
// WINDOW
|
||||||
#if FEATURE_SCREENSHOTS WINDOW_SCREENSHOT; #end
|
#if FEATURE_SCREENSHOTS WINDOW_SCREENSHOT; #end
|
||||||
WINDOW_FULLSCREEN;
|
WINDOW_FULLSCREEN;
|
||||||
@@ -1674,6 +1713,8 @@ enum abstract Action(String) to String from String
|
|||||||
var FREEPLAY_LEFT = "freeplay_left";
|
var FREEPLAY_LEFT = "freeplay_left";
|
||||||
var FREEPLAY_RIGHT = "freeplay_right";
|
var FREEPLAY_RIGHT = "freeplay_right";
|
||||||
var FREEPLAY_CHAR_SELECT = "freeplay_char_select";
|
var FREEPLAY_CHAR_SELECT = "freeplay_char_select";
|
||||||
|
var FREEPLAY_JUMP_TO_TOP = "freeplay_jump_to_top";
|
||||||
|
var FREEPLAY_JUMP_TO_BOTTOM = "freeplay_jump_to_bottom";
|
||||||
// VOLUME
|
// VOLUME
|
||||||
var VOLUME_UP = "volume_up";
|
var VOLUME_UP = "volume_up";
|
||||||
var VOLUME_DOWN = "volume_down";
|
var VOLUME_DOWN = "volume_down";
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ interface IStateChangingScriptedClass extends IScriptedClass
|
|||||||
public function onSubStateOpenEnd(event:SubStateScriptEvent):Void;
|
public function onSubStateOpenEnd(event:SubStateScriptEvent):Void;
|
||||||
public function onSubStateCloseBegin(event:SubStateScriptEvent):Void;
|
public function onSubStateCloseBegin(event:SubStateScriptEvent):Void;
|
||||||
public function onSubStateCloseEnd(event:SubStateScriptEvent):Void;
|
public function onSubStateCloseEnd(event:SubStateScriptEvent):Void;
|
||||||
|
|
||||||
|
public function onFocusLost(event:FocusScriptEvent):Void;
|
||||||
|
public function onFocusGained(event:FocusScriptEvent):Void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,6 +74,11 @@ interface INoteScriptedClass extends IScriptedClass
|
|||||||
* Called when EITHER player (usually the player) misses a note.
|
* Called when EITHER player (usually the player) misses a note.
|
||||||
*/
|
*/
|
||||||
public function onNoteMiss(event:NoteScriptEvent):Void;
|
public function onNoteMiss(event:NoteScriptEvent):Void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when EITHER player (usually the player) drops a hold note.
|
||||||
|
*/
|
||||||
|
public function onNoteHoldDrop(event:HoldNoteScriptEvent):Void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -140,7 +148,7 @@ interface IPlayStateScriptedClass extends INoteScriptedClass extends IBPMSyncedS
|
|||||||
/**
|
/**
|
||||||
* Called when the player restarts the song, either via pause menu or restarting after a game over.
|
* Called when the player restarts the song, either via pause menu or restarting after a game over.
|
||||||
*/
|
*/
|
||||||
public function onSongRetry(event:ScriptEvent):Void;
|
public function onSongRetry(event:SongRetryEvent):Void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the player presses a key when no note is on the strumline.
|
* Called when the player presses a key when no note is on the strumline.
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user