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 | ||
|
|
3746545452 | ||
|
|
b4c48893bc | ||
|
|
a42240e6a5 | ||
|
|
91b4544f7e | ||
|
|
9c8ba2c053 | ||
|
|
19abcb5009 | ||
|
|
1189c226ee | ||
|
|
097dbf5bb4 | ||
|
|
8890cbf270 | ||
|
|
623985a2cc | ||
|
|
8cd7f2a5b1 | ||
|
|
0a5419d7fc | ||
|
|
b2647fe09f | ||
|
|
e66290c55f | ||
|
|
31d1b3e246 | ||
|
|
880615e707 | ||
|
|
7d5d80d515 | ||
|
|
6848815e80 | ||
|
|
5d10bb9f54 | ||
|
|
f6a5a9d8ce | ||
|
|
9695e79134 | ||
|
|
3267c61258 | ||
|
|
903b3fc599 | ||
|
|
0d60929f18 | ||
|
|
bc546e86aa | ||
|
|
de5cca1120 | ||
|
|
3aa4591bab | ||
|
|
bc8f506193 | ||
|
|
f52472a476 | ||
|
|
ab5bda3ee5 | ||
|
|
777978f5a5 | ||
|
|
711e0a6b75 | ||
|
|
1fde59f999 | ||
|
|
165ad60155 | ||
|
|
d2e29879fe | ||
|
|
4fa9a0daaa | ||
|
|
c0314c85ec | ||
|
|
f862fb2c3e | ||
|
|
d7f5070317 | ||
|
|
1d2bd61119 | ||
|
|
a4ed55721f | ||
|
|
f1b6e6c4e4 | ||
|
|
29b6763290 | ||
|
|
410cfe972d | ||
|
|
39b1a42cfe | ||
|
|
b0b73c8399 | ||
|
|
5141ac13df | ||
|
|
45a5160b06 | ||
|
|
89ee33e9b9 | ||
|
|
4b54118731 | ||
|
|
dbf66ac250 | ||
|
|
79eb7d633c | ||
|
|
f61789e453 | ||
|
|
1da0da249d | ||
|
|
6cd1dec3d4 | ||
|
|
2175beaa65 | ||
|
|
6df80ba69d | ||
|
|
67f7b638fb | ||
|
|
399869cdcc | ||
|
|
c5a76cf1d5 | ||
|
|
30a9887136 | ||
|
|
9324359d2f | ||
|
|
4a9ad53695 | ||
|
|
5d013a1aa7 |
@@ -3,41 +3,49 @@ description: Report a bug or an issue in the game.
|
||||
labels: ["type: minor bug", "status: pending triage"]
|
||||
title: "Bug Report: "
|
||||
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 properly named the issue
|
||||
- label: I have checked the issues/discussions pages to see if the issue has been previously reported
|
||||
- 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: What platform are you using?
|
||||
label: Platform
|
||||
description: Which platform are you playing on?
|
||||
options:
|
||||
- Newgrounds (Web)
|
||||
- Itch.io (Web)
|
||||
- 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: If you are playing on a browser, which one are you using?
|
||||
label: Browser
|
||||
description: (Web/HTML5 users only) Which browser are you playing on?
|
||||
options:
|
||||
- Google Chrome
|
||||
- Microsoft Edge
|
||||
- Firefox
|
||||
- Opera
|
||||
- Safari
|
||||
- Other (Specify below)
|
||||
- Other (Specify in Description field)
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: Version
|
||||
description: What version are you using?
|
||||
placeholder: ex. 0.4.1
|
||||
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
|
||||
|
||||
@@ -49,14 +57,16 @@ body:
|
||||
attributes:
|
||||
value: "### Please do not report issues from other engines. These must be reported in their respective repositories."
|
||||
|
||||
- type: markdown
|
||||
- type: textarea
|
||||
attributes:
|
||||
value: "#### Provide as many details as you can."
|
||||
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!
|
||||
placeholder: Describe your issue here...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Context (Provide images, videos, etc.)
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce (or crash logs, errors, etc.)
|
||||
label: Steps to Reproduce
|
||||
description: What steps can other people take to reliably encounter this issue?
|
||||
placeholder: 1. Open the game...
|
||||
|
||||
@@ -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,68 +3,81 @@ description: Report a crash that occurred while playing the game.
|
||||
labels: ["type: major bug", "status: pending triage"]
|
||||
title: "Crash Report: "
|
||||
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 properly named the issue
|
||||
- label: I have checked the issues/discussions pages to see if the issue has been previously reported
|
||||
- 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: What platform are you using?
|
||||
label: Platform
|
||||
description: Which platform are you playing on?
|
||||
options:
|
||||
- Newgrounds (Web)
|
||||
- Itch.io (Web)
|
||||
- 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: If you are playing on a browser, which one are you using?
|
||||
label: Browser
|
||||
description: (Web/HTML5 users only) Which browser are you playing on?
|
||||
options:
|
||||
- Google Chrome
|
||||
- Microsoft Edge
|
||||
- Firefox
|
||||
- Opera
|
||||
- Safari
|
||||
- Other (Specify below)
|
||||
- Other (Specify in Description field)
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: Version
|
||||
description: What version are you using?
|
||||
placeholder: ex. 0.4.1
|
||||
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 your issue."
|
||||
value: "## Describe the crash."
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "### Please do not report issues from other engines. These must be reported in their respective repositories."
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "#### Provide as many details as you can."
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Context (Provide screenshots or videos of the crash happening)
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
label: Description
|
||||
description: Include screenshots or videos of the crash happening. Provide as much detail as you can.
|
||||
placeholder: Describe the crash here...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Crash logs (can be found in the logs folder where Funkin.exe is)
|
||||
label: Steps to Reproduce
|
||||
description: What steps can other people take to reliably trigger this crash?
|
||||
placeholder: 1. Open the game...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Crash logs
|
||||
description: These can be found in the logs folder where Funkin.exe is.
|
||||
placeholder: Upload your logs here...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -3,13 +3,21 @@ description: Suggest a new feature.
|
||||
labels: ["type: enhancement", "status: pending triage"]
|
||||
title: "Enhancement: "
|
||||
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 suggestion being considered!
|
||||
options:
|
||||
- label: I have properly named the enhancement
|
||||
- label: I have checked the issues/discussions pages to see if the enhancement has been previously suggested
|
||||
- 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 properly titled my enhancement
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: What is your suggestion, and why should it be implemented?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -5,7 +5,7 @@ inputs:
|
||||
haxe:
|
||||
description: 'Version of haxe to install'
|
||||
required: true
|
||||
default: '4.3.4'
|
||||
default: '4.3.6'
|
||||
hxcpp-cache:
|
||||
description: 'Whether to use a shared hxcpp compile cache'
|
||||
required: true
|
||||
@@ -30,7 +30,7 @@ runs:
|
||||
echo "TIMER_HAXE=$(date +%s)" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Install Haxe
|
||||
uses: funkincrew/ci-haxe@v3.1.0
|
||||
uses: funkincrew/ci-haxe@v3.2.7
|
||||
with:
|
||||
haxe-version: ${{ inputs.haxe }}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# Add 'small' to any changes below 10 lines
|
||||
small:
|
||||
# Add 'size: small' to any changes below 10 lines
|
||||
'size: small':
|
||||
max: 9
|
||||
|
||||
# Add 'medium' to any changes between 10 and 100 lines
|
||||
medium:
|
||||
# Add 'size: medium' to any changes between 10 and 100 lines
|
||||
'size: medium':
|
||||
min: 10
|
||||
max: 99
|
||||
|
||||
# Add 'large' to any changes for more than 100 lines
|
||||
large:
|
||||
# Add 'size: large' to any changes of at least 100 lines
|
||||
'size: large':
|
||||
min: 100
|
||||
|
||||
+121
-8
@@ -7,38 +7,151 @@
|
||||
comment: >
|
||||
This issue is a duplicate. Please direct all discussion to the original issue.
|
||||
# Close the issue
|
||||
close: true
|
||||
close: false
|
||||
# Set a close reason
|
||||
# close-reason: 'not planned'
|
||||
# Remove other status labels
|
||||
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'
|
||||
# Set a close reason
|
||||
close-reason: 'not planned'
|
||||
- 'status: planned'
|
||||
- 'status: rejected'
|
||||
- 'status: resolved'
|
||||
- 'status: resolved internally'
|
||||
- 'status: reviewing internally'
|
||||
- 'status: stale'
|
||||
prs:
|
||||
# Post a comment
|
||||
comment: >
|
||||
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
|
||||
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'
|
||||
# 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
|
||||
# Set a close reason
|
||||
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':
|
||||
issues:
|
||||
# Close the issue
|
||||
close: true
|
||||
# Remove other status labels
|
||||
unlabel:
|
||||
- 'status: pending triage'
|
||||
# Set a close reason
|
||||
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:
|
||||
# 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: resolved'
|
||||
- 'status: resolved internally'
|
||||
- 'status: reviewing internally'
|
||||
- 'status: stale'
|
||||
|
||||
'status: resolved':
|
||||
issues:
|
||||
# Close the issue
|
||||
close: true
|
||||
# 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'
|
||||
|
||||
+11
-4
@@ -1,11 +1,18 @@
|
||||
# Add Documentation tag to PR's changing markdown files, or anyhting in the docs folder
|
||||
Documentation:
|
||||
# Add Documentation tag to PR's changing markdown files, or anything in the docs folder
|
||||
'pr: documentation':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- docs/*
|
||||
- '**/*.md'
|
||||
|
||||
# Adds Haxe tag to PR's changing haxe code files
|
||||
Haxe:
|
||||
# Add Haxe tag to PR's changing haxe code files
|
||||
'pr: haxe':
|
||||
- changed-files:
|
||||
- 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.
|
||||
|
||||
## 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)
|
||||
default: false
|
||||
push:
|
||||
paths-ignore:
|
||||
- '**/Dockerfile'
|
||||
- '.github/workflows/build-docker-image.yml'
|
||||
|
||||
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:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- target: windows
|
||||
- target: macos
|
||||
- target: windows
|
||||
runs-on: windows
|
||||
- target: macos
|
||||
runs-on: macos
|
||||
runs-on:
|
||||
- ${{ matrix.target }}
|
||||
- ${{ matrix.runs-on }}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
BUILD_DEFINES: ${{ github.event.inputs.build-defines || '-DGITHUB_BUILD' }}
|
||||
steps:
|
||||
- name: Make git happy
|
||||
run: |
|
||||
git config --global --replace-all safe.directory $GITHUB_WORKSPACE
|
||||
|
||||
- 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: 'recursive'
|
||||
token: ${{ steps.app_token.outputs.token }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup build environment
|
||||
uses: ./.github/actions/setup-haxe
|
||||
with:
|
||||
gh-token: ${{ steps.app_token.outputs.token }}
|
||||
- name: Setup HXCPP dev commit
|
||||
run: |
|
||||
cd .haxelib/hxcpp/git/tools/hxcpp
|
||||
haxe compile.hxml
|
||||
cd ../../../../..
|
||||
- name: Build game
|
||||
if: ${{ matrix.target == 'windows' }}
|
||||
run: |
|
||||
haxelib run lime build windows -v -release ${{ github.event.inputs.build-defines }}
|
||||
timeout-minutes: 120
|
||||
- name: Build game
|
||||
if: ${{ matrix.target != 'windows' }}
|
||||
run: |
|
||||
haxelib run lime build ${{ matrix.target }} -v -release --times ${{ github.event.inputs.build-defines }}
|
||||
timeout-minutes: 120
|
||||
- name: Save build artifact to Github Actions
|
||||
if: ${{ github.event.inputs.save-artifact }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-${{ matrix.target }}
|
||||
path: export/release/${{matrix.target}}/bin/
|
||||
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: ./.github/actions/upload-itch
|
||||
with:
|
||||
butler-key: ${{ secrets.BUTLER_API_KEY}}
|
||||
target: ${{ matrix.target }}
|
||||
- name: Make git happy
|
||||
run: |
|
||||
git config --global --replace-all safe.directory $GITHUB_WORKSPACE
|
||||
- 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: 'recursive'
|
||||
token: ${{ steps.app_token.outputs.token }}
|
||||
persist-credentials: false
|
||||
- name: Setup build environment
|
||||
uses: ./.github/actions/setup-haxe
|
||||
with:
|
||||
gh-token: ${{ steps.app_token.outputs.token }}
|
||||
- name: Setup HXCPP dev commit
|
||||
run: |
|
||||
cd .haxelib/hxcpp/git/tools/hxcpp
|
||||
haxe compile.hxml
|
||||
cd ../../../../..
|
||||
- name: Build game (windows)
|
||||
if: ${{ matrix.target == 'windows' }}
|
||||
run: |
|
||||
haxelib run lime build windows -v -release $BUILD_DEFINES
|
||||
timeout-minutes: 120
|
||||
- name: Build game (unix)
|
||||
if: ${{ matrix.target != 'windows' }}
|
||||
run: |
|
||||
haxelib run lime build ${{ matrix.target }} -v -release --times $BUILD_DEFINES
|
||||
timeout-minutes: 120
|
||||
- name: Save build artifact to Github Actions
|
||||
if: ${{ github.event.inputs.save-artifact }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-${{ matrix.target }}
|
||||
path: export/release/${{matrix.target}}/bin/
|
||||
- name: Upload build artifacts
|
||||
uses: ./.github/actions/upload-itch
|
||||
with:
|
||||
butler-key: ${{ secrets.BUTLER_API_KEY}}
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
build-game-in-container:
|
||||
needs: [gather-tags, docker-image]
|
||||
if: ${{ ! cancelled() }}
|
||||
runs-on: build-set
|
||||
container: ghcr.io/funkincrew/build-dependencies:latest
|
||||
container: ghcr.io/funkincrew/build-dependencies:${{ needs.gather-tags.outputs.primary }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: linux
|
||||
- target: html5
|
||||
- target: linux
|
||||
- target: html5
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
BUILD_DEFINES: ${{ github.event.inputs.build-defines || '-DGITHUB_BUILD' }}
|
||||
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: 'recursive'
|
||||
token: ${{ steps.app_token.outputs.token }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Config haxelib
|
||||
run: |
|
||||
haxelib --never newrepo
|
||||
echo "HAXEPATH=$(haxelib config)" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Restore cached dependencies
|
||||
id: cache-hmm
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .haxelib
|
||||
key: haxe-hmm-${{ runner.os }}-${{ hashFiles('**/hmm.json') }}
|
||||
|
||||
- if: ${{ steps.cache-hmm.outputs.cache-hit != 'true' }}
|
||||
name: Install dependencies
|
||||
run: |
|
||||
git config --global 'url.https://x-access-token:${{ steps.app_token.outputs.token }}@github.com/.insteadOf' https://github.com/
|
||||
git config --global advice.detachedHead false
|
||||
haxelib --global run hmm install -q
|
||||
cd .haxelib/hxcpp/git/tools/hxcpp && haxe compile.hxml
|
||||
|
||||
- if: ${{ matrix.target != 'html5' }}
|
||||
name: Restore hxcpp cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: /usr/share/hxcpp
|
||||
key: haxe-hxcpp-${{ runner.os }}-${{ github.ref_name }}-${{ github.sha }}
|
||||
restore-keys: haxe-hxcpp-${{ runner.os }}-${{ github.ref_name }}
|
||||
|
||||
- name: Build game
|
||||
run: |
|
||||
haxelib run lime build ${{ matrix.target }} -v -release --times ${{ github.event.inputs.build-defines }}
|
||||
timeout-minutes: 120
|
||||
|
||||
- name: Save build artifact to Github Actions
|
||||
if: ${{ github.event.inputs.save-artifact }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-${{ matrix.target }}
|
||||
path: export/release/${{matrix.target}}/bin/
|
||||
- name: Upload build artifacts
|
||||
uses: ./.github/actions/upload-itch
|
||||
with:
|
||||
butler-key: ${{ secrets.BUTLER_API_KEY}}
|
||||
target: ${{ matrix.target }}
|
||||
- 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: 'recursive'
|
||||
token: ${{ steps.app_token.outputs.token }}
|
||||
persist-credentials: false
|
||||
- name: Config haxelib
|
||||
run: |
|
||||
haxelib --never newrepo
|
||||
echo "HAXEPATH=$(haxelib config)" >> "$GITHUB_ENV"
|
||||
- name: Restore cached dependencies
|
||||
id: cache-hmm
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .haxelib
|
||||
key: haxe-hmm-${{ runner.os }}-${{ hashFiles('**/hmm.json') }}
|
||||
- if: ${{ steps.cache-hmm.outputs.cache-hit != 'true' }}
|
||||
name: Install dependencies
|
||||
run: |
|
||||
git config --global 'url.https://x-access-token:${{ steps.app_token.outputs.token }}@github.com/.insteadOf' https://github.com/
|
||||
git config --global advice.detachedHead false
|
||||
haxelib --global run hmm install -q
|
||||
cd .haxelib/hxcpp/git/tools/hxcpp && haxe compile.hxml
|
||||
- if: ${{ matrix.target != 'html5' }}
|
||||
name: Restore hxcpp cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: /usr/share/hxcpp
|
||||
key: haxe-hxcpp-${{ runner.os }}-${{ github.ref_name }}-${{ github.sha }}
|
||||
restore-keys: haxe-hxcpp-${{ runner.os }}-${{ github.ref_name }}
|
||||
- name: Build game
|
||||
run: |
|
||||
haxelib run lime build ${{ matrix.target }} -v -release --times $BUILD_DEFINES
|
||||
timeout-minutes: 120
|
||||
- name: Save build artifact to Github Actions
|
||||
if: ${{ github.event.inputs.save-artifact }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-${{ matrix.target }}
|
||||
path: export/release/${{matrix.target}}/bin/
|
||||
- name: Upload build artifacts
|
||||
uses: ./.github/actions/upload-itch
|
||||
with:
|
||||
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
|
||||
dump/
|
||||
export/
|
||||
project/
|
||||
RECOVER_*.fla
|
||||
shitAudio/
|
||||
.build_time
|
||||
.swp
|
||||
NewgroundsCredentials.hx
|
||||
|
||||
# Exclude JS stuff
|
||||
node_modules/
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
[submodule "assets"]
|
||||
path = assets
|
||||
url = https://github.com/FunkinCrew/Funkin-assets-secret
|
||||
url = https://github.com/FunkinCrew/Funkin.assets
|
||||
[submodule "art"]
|
||||
path = art
|
||||
url = https://github.com/FunkinCrew/Funkin-art-secret
|
||||
url = https://github.com/FunkinCrew/Funkin.art
|
||||
|
||||
Vendored
+1
-2
@@ -6,7 +6,6 @@
|
||||
"vshaxe.hxcpp-debugger", // CPP debugging
|
||||
"openfl.lime-vscode-extension", // Lime integration
|
||||
"esbenp.prettier-vscode", // JSON formatting
|
||||
"redhat.vscode-xml", // XML formatting
|
||||
"ryanluker.vscode-coverage-gutters" // Highlight code coverage
|
||||
"redhat.vscode-xml" // XML formatting
|
||||
]
|
||||
}
|
||||
|
||||
Vendored
+16
-49
@@ -70,7 +70,7 @@
|
||||
"files.eol": "\n",
|
||||
|
||||
"haxe.displayPort": "auto",
|
||||
"haxe.enableCompilationServer": false,
|
||||
"haxe.enableCompilationServer": true,
|
||||
"haxe.enableServerView": true,
|
||||
"haxe.displayServer": {
|
||||
"arguments": ["-v"]
|
||||
@@ -91,26 +91,6 @@
|
||||
"haxecheckstyle.codeSimilarityBufferSize": 100,
|
||||
|
||||
"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)",
|
||||
"target": "windows",
|
||||
@@ -170,23 +150,13 @@
|
||||
"target": "windows",
|
||||
"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)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DANIMDEBUG", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (Debug hxCodec)",
|
||||
"label": "Windows / Debug (Debug hxvlc)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DHXC_LIBVLC_LOGGING", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
@@ -210,11 +180,6 @@
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DWAVEFORM", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Release",
|
||||
"target": "windows",
|
||||
"args": ["-release"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Release (GitHub Actions)",
|
||||
"target": "windows",
|
||||
@@ -225,29 +190,31 @@
|
||||
"target": "hl",
|
||||
"args": ["-debug", "-DWAVEFORM"]
|
||||
},
|
||||
{
|
||||
"label": "HTML5 / Debug",
|
||||
"target": "html5",
|
||||
"args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "HTML5 / Debug (Watch)",
|
||||
"target": "html5",
|
||||
"args": ["-debug", "-watch", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
}
|
||||
],
|
||||
"lime.buildTypes": [
|
||||
{
|
||||
"label": "macOS / Debug",
|
||||
"target": "mac",
|
||||
"label": "Debug",
|
||||
"args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "macOS / Release",
|
||||
"target": "mac",
|
||||
"label": "Debug (Tracy)",
|
||||
"args": ["-debug", "-DFEATURE_DEBUG_TRACY", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "Debug (Straight to Chart Editor)",
|
||||
"args": ["-debug", "-DCHARTING", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "Release",
|
||||
"args": ["-release"]
|
||||
},
|
||||
{
|
||||
"label": "macOS / Release (GitHub Actions)",
|
||||
"target": "mac",
|
||||
"label": "Release (GitHub Actions)",
|
||||
"args": ["-release", "-DGITHUB_BUILD"]
|
||||
}
|
||||
],
|
||||
|
||||
+721
-148
File diff suppressed because it is too large
Load Diff
+6
-6
@@ -4,18 +4,18 @@ Code style is enforced using Visual Studio Code extensions.
|
||||
|
||||
## .hx
|
||||
Formatting is handled by the `nadako.vshaxe` extension, which includes Haxe Formatter.
|
||||
Haxe Formatter automatically resolves issues such as intentation style and line breaks, and can be configured in `hxformat.json`.
|
||||
Haxe Formatter automatically resolves issues such as indentation style and line breaks, and can be configured in `hxformat.json`.
|
||||
|
||||
Code Quality is handled by the `vshaxe.haxe-checkstyle` extension, which includes Haxe Checkstyle.
|
||||
|
||||
### Haxe Checkstyle Notes
|
||||
* Checks can be escalated to display as different serverities in the Problems window.
|
||||
* Checks can be escalated to display as different severities in the Problems window.
|
||||
* Checks can be disabled by setting the severity to `IGNORE`.
|
||||
* `IndentationCharacter` checks what is used to indent, `Indentation` checks how deep the intentation 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.
|
||||
* TODO items: Enable these one-by-one and fix them to improve the overall code quality.
|
||||
- Reconfigure `MethodLength`
|
||||
- Reconfigure `CyclomaticComplexity`
|
||||
- Re-configure `MethodLength`
|
||||
- Re-configure `CyclomaticComplexity`
|
||||
- Re-enable `MagicNumber`
|
||||
- Re-configure `NestedControlFlow`
|
||||
- Re-configure `NestedIfDepth`
|
||||
@@ -28,5 +28,5 @@ Prettier automatically handles formatting of JSON files, and can be configured i
|
||||
|
||||
### 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.
|
||||
* 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.
|
||||
|
||||
+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)
|
||||
|
||||
## Apache 2.0 License
|
||||
|
||||
```
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
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.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
```
|
||||
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
# RIGHT NOW THE MODS FOLDER DOES NOT WORK ENTIRELY JUST YET!!!
|
||||
## THIS IS WORK IN PROGRESS!!!
|
||||
|
||||
# QUICK AND DIRTY MOD GUIDE
|
||||
|
||||
With the 0.2.6 update, I added a bit of a slightly nicer mod support backend.
|
||||
|
||||
It's POLYMOD, which is made by Lars Doucet: https://github.com/larsiusprime/polymod
|
||||
|
||||
You may have noticed that there's a new folder in the assets. MODS. Within it you will see 2 files. modList.txt, and a folder called introMod.
|
||||
modList.txt will load any folder into the game. Put the folder you want to load into a new line in modList.txt, and reboot the game.
|
||||
|
||||
Now you may be wondering, what do I put in the folder? Well later down it'll get a bit more complicated, especially as I'll make the IN-GAME mod loader nicer.
|
||||
@@ -11,11 +11,15 @@ 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**
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
Feel free to start learning to mod the game by reading our [documentation](https://funkincrew.github.io/funkin-modding-docs/) and guide to modding.
|
||||
|
||||
# Credits and Special Thanks
|
||||
|
||||
|
||||
+1
-1
Submodule art updated: fbd3e3df77...8402339e2e
+1
-1
Submodule assets updated: b2404b6b1c...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 neko_version=2.3.0
|
||||
ARG neko_version=2.4.0
|
||||
|
||||
# prepare runner
|
||||
ENV GITHUB_HOME="/github/home"
|
||||
@@ -98,11 +98,12 @@ EOF
|
||||
# neko
|
||||
# https://github.com/HaxeFoundation/neko/releases/download/v2-3-0/neko-2.3.0-linux64.tar.gz
|
||||
RUN <<EOF
|
||||
neko_url=$(curl https://api.github.com/repos/HaxeFoundation/neko/releases -sfL \
|
||||
| jq '.[] | select(.name == "'"$neko_version"'")' \
|
||||
| jq '.assets[] | select(.name | endswith("linux64.tar.gz"))' \
|
||||
| jq -r '.browser_download_url')
|
||||
curl -sfL "$neko_url" | tar -xz -C /usr/local
|
||||
#neko_url=$(curl https://api.github.com/repos/HaxeFoundation/neko/releases -fL \
|
||||
# | jq '.[] | select(.name == "'"$neko_version"'")' \
|
||||
# | jq '.assets[] | select(.name | endswith("linux64.tar.gz"))' \
|
||||
# | jq -r '.browser_download_url')
|
||||
neko_url="https://geo.thei.rs/funkin/neko-2.4.1-linux64.tar.gz"
|
||||
curl -fL "$neko_url" | tar -xz -C /usr/local
|
||||
EOF
|
||||
|
||||
RUN <<EOF
|
||||
@@ -117,11 +118,12 @@ ENV PATH="$NEKOPATH:$PATH"
|
||||
# haxe
|
||||
# https://github.com/HaxeFoundation/haxe/releases/download/4.0.5/haxe-4.0.5-linux64.tar.gz
|
||||
RUN <<EOF
|
||||
haxe_url=$(curl https://api.github.com/repos/HaxeFoundation/haxe/releases -sfL \
|
||||
| jq '.[] | select(.name == "'"$haxe_version"'")' \
|
||||
| jq '.assets[] | select(.name | endswith("linux64.tar.gz"))' \
|
||||
| jq -r '.browser_download_url')
|
||||
curl -sfL "$haxe_url" | tar -xz -C /usr/local
|
||||
#haxe_url=$(curl https://api.github.com/repos/HaxeFoundation/haxe/releases -fL \
|
||||
# | jq '.[] | select(.name == "'"$haxe_version"'")' \
|
||||
# | jq '.assets[] | select(.name | endswith("linux64.tar.gz"))' \
|
||||
# | jq -r '.browser_download_url')
|
||||
haxe_url="https://geo.thei.rs/funkin/haxe-4.3.6-linux64.tar.gz"
|
||||
curl -fL "$haxe_url" | tar -xz -C /usr/local
|
||||
EOF
|
||||
|
||||
RUN <<EOF
|
||||
|
||||
+1
-1
@@ -347,7 +347,7 @@
|
||||
{
|
||||
"props": {
|
||||
"allowSingleLineStatement": true,
|
||||
"tokens": ["FOR", "IF", "ELSE_IF", "WHILE", "DO_WHILE"]
|
||||
"tokens": ["FOR", "ELSE_IF", "WHILE", "DO_WHILE"]
|
||||
},
|
||||
"type": "NeedBraces"
|
||||
},
|
||||
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
# git-cliff ~ configuration file
|
||||
# https://git-cliff.org/docs/configuration
|
||||
|
||||
[remote.github]
|
||||
owner = "FunkinCrew"
|
||||
repo = "Funkin"
|
||||
|
||||
# To bypass "you have reached your rate limit!", you can input a github token either as an environment variable
|
||||
# or alongside the `git cliff` command like so `git cliff --github-token TOKEN_HERE`
|
||||
# Personally I like to use the github cli `gh` tool to get a token
|
||||
# `git cliff --github-token $(gh auth token)`
|
||||
|
||||
[changelog]
|
||||
# template for the changelog body
|
||||
# https://keats.github.io/tera/docs/#introduction
|
||||
body = """
|
||||
{%- macro remote_url() -%}
|
||||
https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
|
||||
{%- endmacro -%}
|
||||
|
||||
{% if version -%}
|
||||
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
||||
{% else -%}
|
||||
## [Unreleased]
|
||||
{% endif -%}
|
||||
|
||||
{% for group, commits in commits | group_by(attribute="group") %}
|
||||
### {{ group | upper_first }}
|
||||
{%- for commit in commits %}
|
||||
- {% if commit.scope %}({{commit.scope}}) {% endif %}{{ commit.message | split(pat="\n") | first | upper_first | trim }} \
|
||||
([{{ commit.id | truncate(length=7, end="") }}]({{self::remote_url()}}/commit/{{ commit.id }})) -\
|
||||
{% if commit.remote.username %} by @{{ commit.remote.username }}
|
||||
{%- elif commit.author.name %} by {{ commit.author.name }}
|
||||
{%- endif -%}
|
||||
{% if commit.remote.pr_number %} in \
|
||||
[#{{ commit.remote.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.remote.pr_number }}) \
|
||||
{%- endif -%}
|
||||
{% if commit.links | length != 0 %}\
|
||||
[#{{ commit.links }}]({{ self::remote_url() }}/pull/{{ commit.remote.pr_number }}) \
|
||||
{%- endif -%}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
||||
{%- if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %}
|
||||
## New Contributors
|
||||
{%- endif -%}
|
||||
|
||||
{% for contributor in github.contributors | filter(attribute="is_first_time", value=true) %}
|
||||
* @{{ contributor.username }} made their first contribution
|
||||
{%- if contributor.pr_number %} in \
|
||||
[#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \
|
||||
{%- endif %}
|
||||
{%- endfor %}\n
|
||||
"""
|
||||
|
||||
# remove the leading and trailing whitespace from the templates
|
||||
trim = true
|
||||
|
||||
[git]
|
||||
# parse the commits based on https://www.conventionalcommits.org
|
||||
conventional_commits = true
|
||||
# filter out the commits that are not conventional
|
||||
filter_unconventional = true
|
||||
# regex for preprocessing the commit messages
|
||||
commit_preprocessors = [
|
||||
# remove issue numbers from commits
|
||||
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "" },
|
||||
]
|
||||
# regex for parsing and grouping commits
|
||||
commit_parsers = [
|
||||
{ message = "^docs", group = "Changed", scope="docs" },
|
||||
{ message = "^[a|A]dd", group = "Added" },
|
||||
{ message = "^[s|S]upport", group = "Added" },
|
||||
{ message = "^[r|R]emove", group = "Removed" },
|
||||
{ message = "^.*: add", group = "Added" },
|
||||
{ message = "^.*: support", group = "Added" },
|
||||
{ message = "^.*: remove", group = "Removed" },
|
||||
{ message = "^.*: delete", group = "Removed" },
|
||||
{ message = "^test", group = "Fixed" },
|
||||
{ message = "^fix", group = "Fixed" },
|
||||
{ message = "^.*: fix", group = "Fixed" },
|
||||
{ message = "^.*", group = "Changed" },
|
||||
]
|
||||
# filter out the commits that are not matched by commit parsers
|
||||
filter_commits = false
|
||||
# sort the tags topologically
|
||||
topo_order = false
|
||||
# sort the commits inside sections by oldest/newest order
|
||||
sort_commits = "newest"
|
||||
|
||||
+9
-5
@@ -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.
|
||||
5. Run `haxelib --global install hmm` and then `haxelib --global run hmm setup` to install hmm.json
|
||||
6. Run `hmm install` to install all haxelibs of the current branch
|
||||
7. Run `haxelib run lime setup` to set up lime
|
||||
8. Perform additional platform setup
|
||||
- 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. 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)
|
||||
- When prompted, select "Individual Components" and make sure to download the following:
|
||||
- MSVC v143 VS 2022 C++ x64/x86 build tools
|
||||
- Windows 10/11 SDK
|
||||
- Mac: [`lime setup mac` Documentation](https://lime.openfl.org/docs/advanced-setup/macos/)
|
||||
- Linux: [`lime setup linux` Documentation](https://lime.openfl.org/docs/advanced-setup/linux/)
|
||||
- One of Funkin's dependencies uses libVLC, which requires you to install some development packages to be able to compile.
|
||||
Command for Ubuntu/Debian based systems: `sudo apt install libvlc-dev libvlccore-dev libvlccore9`
|
||||
- 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. `lime test <PLATFORM>` to build and launch the game for your platform (for example, `lime test windows`)
|
||||
10. If you are targeting for native, you may need to run `lime rebuild <PLATFORM>` and `lime rebuild <PLATFORM> -debug`
|
||||
11. `lime test <PLATFORM>` to build and launch the game for your platform (for example, `lime test windows`)
|
||||
|
||||
## Build Flags
|
||||
|
||||
@@ -34,10 +37,11 @@ There are several useful build flags you can add to a build to affect how it wor
|
||||
- `-DGITHUB_BUILD` will enable in-game debug functions (such as the ability to time travel in a song by pressing `PgUp`/`PgDn`), without enabling the other stuff
|
||||
- `-DFEATURE_POLYMOD_MODS` or `-DNO_FEATURE_POLYMOD_MODS` to forcibly enable or disable modding support.
|
||||
- `-DREDIRECT_ASSETS_FOLDER` or `-DNO_REDIRECT_ASSETS_FOLDER` to forcibly enable or disable asset redirection.
|
||||
- This feature causes the game to load exported assets from the project's assets folder rather than the exported one. Great for fast iteration, but the game
|
||||
- This feature causes the game to load exported assets from the project's assets folder rather than the exported one. Great for fast iteration, but the game will break if you try to zip it up and send it to someone, so it's disabled for release builds.
|
||||
- `-DFEATURE_DISCORD_RPC` or `-DNO_FEATURE_DISCORD_RPC` to forcibly enable or disable support for Discord Rich Presence.
|
||||
- `-DFEATURE_VIDEO_PLAYBACK` or `-DNO_FEATURE_VIDEO_PLAYBACK` to forcibly enable or disable video cutscene support.
|
||||
- `-DFEATURE_CHART_EDITOR` or `-DNO_FEATURE_CHART_EDITOR` to forcibly enable or disable the chart editor in the Debug menu.
|
||||
- `-DFEATURE_SCREENSHOTS` or `-DNO_FEATURE_SCREENSHOTS` to forcibly enable or disable the screenshots feature.
|
||||
- `-DFEATURE_STAGE_EDITOR` to forcibly enable the experimental stage editor.
|
||||
- `-DFEATURE_GHOST_TAPPING` to forcibly enable an experimental gameplay change to the anti-mash system.
|
||||
|
||||
|
||||
@@ -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!
|
||||
|
||||
## Any State
|
||||
- `INSERT`: ***MEMORY GARBAGE COLLECTION***: Immediately perform memory garbage collection.
|
||||
- `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-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.
|
||||
|
||||
## **Play State**
|
||||
@@ -15,17 +18,29 @@ Most of this functionality is only available on debug builds of the game!
|
||||
- `2`: ***GAIN HEALTH***: Debug function, add 10% to the player's health.
|
||||
- `3`: ***LOSE HEALTH***: Debug function, subtract 5% to the player's health.
|
||||
- `9`: NEATO!
|
||||
- `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.
|
||||
- `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.
|
||||
- `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**
|
||||
- `F` (Freeplay Menu) - Move to Favorites
|
||||
- `Q` (Freeplay Menu) - Back one category
|
||||
- `E` (Freeplay Menu) - Forward one category
|
||||
- `P`: ***SWITCH TO PICO/BF***: Switch to Pico or BF (reopens Freeplay as the other character).
|
||||
- `T`: ***TEST RANK ANIMATION***: Play the Gold Perfect rank animation on the currently selected song.
|
||||
- `SHIFT-ACCEPT`: ***BOTPLAY MODE***: Start a song with Botplay mode enabled.
|
||||
- `CTRL-Switch Song`: Play the first unlocked alternate instrumental as the song preview.
|
||||
|
||||
## **Title State**
|
||||
- `Y` - WOAH
|
||||
- `ESCAPE`: ***QUIT***: Quit the game.
|
||||
- `D`: COOL
|
||||
- `UI LEFT`/`UI RIGHT Hotkey`: 2COOL
|
||||
- `Y`: WOAH
|
||||
|
||||
## **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-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.
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# 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
|
||||
- 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.
|
||||
|
||||
- `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.
|
||||
|
||||
- `Class lists not properly generated. Try cleaning out your export folder, restarting your IDE, and rebuilding your project.`
|
||||
- This is a bug specific to HTML5. Simply perform the steps listed (don't forget to restart the IDE too).
|
||||
|
||||
- `LINK : fatal error LNK1201: error writing to program database ''; check for insufficient disk space, invalid path, or insufficient privilege`
|
||||
- This error occurs if the PDB file located in your `export` folder is in use or exceeds 4 GB. Try deleting the `export` folder and building again from scratch.
|
||||
|
||||
- `error: RPC failed; curl 92 HTTP/2 stream 0 was not closed cleanly: PROTOCOL_ERROR (err 1)`
|
||||
- This error can happen during cloning as a result of poor network connectivity. A common fix is to run `git config --global http.postBuffer 4096M` in your terminal.
|
||||
|
||||
- Repository is missing an `assets` folder, or `assets` folder is empty.
|
||||
- You did not clone the repository correctly! Copy the path to your `funkin` folder and run `cd the\path\you\copied`. Then follow the compilation guide starting from **Step 4**.
|
||||
|
||||
- Other compilation issues may be caused by installing bad library versions. Try deleting the `.haxelib` folder and following the guide starting from **Step 5**.
|
||||
|
||||
## 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
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
# Troubleshooting Common Compilation Issues
|
||||
|
||||
- Weird macro error with a very tall call stack: Restart Visual Studio Code
|
||||
- NOTE: This is caused by Polymod somewhere, and seems to only occur when there is another compile error somewhere in the program. There is a bounty up for it.
|
||||
|
||||
- `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.
|
||||
- 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.`
|
||||
- This is a bug specific to HTML5. Simply perform the steps listed (don't forget to restart the IDE too).
|
||||
|
||||
- `LINK : fatal error LNK1201: error writing to program database ''; check for insufficient disk space, invalid path, or insufficient privilege`
|
||||
- This error occurs if the PDB file located in your `export` folder is in use or exceeds 4 GB. Try deleting the `export` folder and building again from scratch.
|
||||
|
||||
- `error: RPC failed; curl 92 HTTP/2 stream 0 was not closed cleanly: PROTOCOL_ERROR (err 1)`
|
||||
- This error can happen during cloning as a result of poor network connectivity. A common fix is to run ` git config --global http.postBuffer 4096M` in your terminal.
|
||||
|
||||
- Repository is missing an `assets` folder, or `assets` folder is empty.
|
||||
- You did not clone the repository correctly! Copy the path to your `funkin` folder and run `cd the\path\you\copied`. Then follow the compilation guide starting from **Step 4**.
|
||||
|
||||
- Other compilation issues may be caused by installing bad library versions. Try deleting the `.haxelib` folder and following the guide starting from **Step 5**.
|
||||
@@ -4,21 +4,21 @@
|
||||
"name": "FlxPartialSound",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "a1eab7b9bf507b87200a3341719054fe427f3b15",
|
||||
"ref": "41f35ddb1eb9d10bc742e6f8b5bcc62f9ef8ad84",
|
||||
"url": "https://github.com/FunkinCrew/FlxPartialSound.git"
|
||||
},
|
||||
{
|
||||
"name": "flixel",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "f2b090d6c608471e730b051c8ee22b8b378964b1",
|
||||
"ref": "fffb1a74cf08f63dacc2ab09976340563f5b6e6d",
|
||||
"url": "https://github.com/FunkinCrew/flixel"
|
||||
},
|
||||
{
|
||||
"name": "flixel-addons",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "7424db4e9164ff46f224a7c47de1b732d2542dd7",
|
||||
"ref": "b9118f47f43a66bc0e5fbfcfd9903f0425e918ee",
|
||||
"url": "https://github.com/FunkinCrew/flixel-addons"
|
||||
},
|
||||
{
|
||||
@@ -32,8 +32,8 @@
|
||||
"name": "flxanimate",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "0654797e5eb7cd7de0c1b2dbaa1efe5a1e1d9412",
|
||||
"url": "https://github.com/Dot-Stuff/flxanimate"
|
||||
"ref": "f8842cea9883d6112a2c854cf93fa72856171428",
|
||||
"url": "https://github.com/FunkinCrew/flxanimate"
|
||||
},
|
||||
{
|
||||
"name": "format",
|
||||
@@ -44,7 +44,7 @@
|
||||
"name": "funkin.vis",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "22b1ce089dd924f15cdc4632397ef3504d464e90",
|
||||
"ref": "1966f8fbbbc509ed90d4b520f3c49c084fc92fd6",
|
||||
"url": "https://github.com/FunkinCrew/funkVis"
|
||||
},
|
||||
{
|
||||
@@ -63,36 +63,29 @@
|
||||
"name": "haxeui-core",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "c9d96b168ea2a19274ad7c766ab1a34b57baa793",
|
||||
"ref": "07fc7aa8098deaea633b0726d01f83eb4ef8a832",
|
||||
"url": "https://github.com/haxeui/haxeui-core"
|
||||
},
|
||||
{
|
||||
"name": "haxeui-flixel",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "013b9d4e56bfe9a034e028a8d685f0b274cb73c4",
|
||||
"ref": "b899a4c7d7318c5ff2b1bb645fbc73728fad1ac9",
|
||||
"url": "https://github.com/haxeui/haxeui-flixel"
|
||||
},
|
||||
{
|
||||
"name": "hscript",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "12785398e2f07082f05034cb580682e5671442a2",
|
||||
"ref": "27c86f9a761c1d16d4433c4cf252eccb7b2e18de",
|
||||
"url": "https://github.com/FunkinCrew/hscript"
|
||||
},
|
||||
{
|
||||
"name": "hxCodec",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "61b98a7a353b7f529a8fec84ed9afc919a2dffdd",
|
||||
"url": "https://github.com/FunkinCrew/hxCodec"
|
||||
},
|
||||
{
|
||||
"name": "hxcpp",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "c6bac3d6c7d683f25104296b2f4c50f8c90b8349",
|
||||
"url": "https://github.com/cortex-engine/hxcpp"
|
||||
"ref": "v4.3.75",
|
||||
"url": "https://github.com/HaxeFoundation/hxcpp"
|
||||
},
|
||||
{
|
||||
"name": "hxcpp-debug-server",
|
||||
@@ -120,6 +113,11 @@
|
||||
"type": "haxelib",
|
||||
"version": "1.3.0"
|
||||
},
|
||||
{
|
||||
"name": "hxvlc",
|
||||
"type": "haxelib",
|
||||
"version": "2.1.2"
|
||||
},
|
||||
{
|
||||
"name": "json2object",
|
||||
"type": "git",
|
||||
@@ -145,7 +143,7 @@
|
||||
"name": "lime",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "fe3368f611a84a19afc03011353945ae4da8fffd",
|
||||
"ref": "be81ad7e4e1a92c3482bcc009648a4ac892cfa35",
|
||||
"url": "https://github.com/FunkinCrew/lime"
|
||||
},
|
||||
{
|
||||
@@ -176,11 +174,18 @@
|
||||
"ref": "f61be7f7ba796595f45023ca65164a485aba0e7e",
|
||||
"url": "https://github.com/FunkinCrew/MassiveUnit"
|
||||
},
|
||||
{
|
||||
"name": "newgrounds",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "c8b30832027fa04f48fe6a45fa7903d5b265d56e",
|
||||
"url": "https://github.com/Geokureli/Newgrounds/"
|
||||
},
|
||||
{
|
||||
"name": "openfl",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "8306425c497766739510ab29e876059c96f77bd2",
|
||||
"ref": "d061c936b462f040304ec2bd42d9f59d2e59e285",
|
||||
"url": "https://github.com/FunkinCrew/openfl"
|
||||
},
|
||||
{
|
||||
@@ -205,4 +210,4 @@
|
||||
"url": "https://github.com/fponticelli/thx.semver"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+63
-20
@@ -25,7 +25,7 @@ class Project extends HXProject {
|
||||
* REMEMBER TO CHANGE THIS WHEN THE GAME UPDATES!
|
||||
* You only have to change it here, the rest of the game will query this value.
|
||||
*/
|
||||
static final VERSION:String = "0.5.1";
|
||||
static final VERSION:String = "0.6.4";
|
||||
|
||||
/**
|
||||
* 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";
|
||||
|
||||
/**
|
||||
* `-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`
|
||||
* If this flag is enabled, the game will enable the Funkin Visualizer library.
|
||||
@@ -197,7 +224,7 @@ class Project extends HXProject {
|
||||
/**
|
||||
* `-DFEATURE_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";
|
||||
|
||||
@@ -232,6 +259,12 @@ class Project extends HXProject {
|
||||
*/
|
||||
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`
|
||||
* 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.vsync = false;
|
||||
// force / allow high DPI
|
||||
this.window.allowHighDPI = true;
|
||||
|
||||
if (isWeb()) {
|
||||
this.window.resizable = true;
|
||||
@@ -460,8 +495,6 @@ class Project extends HXProject {
|
||||
|
||||
// Should be false unless explicitly requested.
|
||||
GITHUB_BUILD.apply(this, false);
|
||||
FEATURE_STAGE_EDITOR.apply(this, false);
|
||||
FEATURE_NEWGROUNDS.apply(this, false);
|
||||
FEATURE_GHOST_TAPPING.apply(this, false);
|
||||
|
||||
// Should be true unless explicitly requested.
|
||||
@@ -471,10 +504,21 @@ class Project extends HXProject {
|
||||
FEATURE_FUNKVIS.apply(this, true);
|
||||
FEATURE_PARTIAL_SOUNDS.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.
|
||||
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.
|
||||
REDIRECT_ASSETS_FOLDER.apply(this, isDebug() && isDesktop());
|
||||
@@ -576,7 +620,7 @@ class Project extends HXProject {
|
||||
|
||||
// Ensure all Flixel classes are available at runtime.
|
||||
// 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() {
|
||||
// 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');
|
||||
// setenv('BUILD_DIR', buildDir);
|
||||
@@ -647,9 +699,9 @@ class Project extends HXProject {
|
||||
}
|
||||
|
||||
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
|
||||
addHaxelib('hxCodec'); // Video playback
|
||||
addHaxelib('hxvlc'); // Video playback
|
||||
}
|
||||
|
||||
if (FEATURE_DISCORD_RPC.isEnabled(this)) {
|
||||
@@ -721,13 +773,12 @@ class Project extends HXProject {
|
||||
addAssetLibrary("weekend1", shouldEmbed, shouldPreload);
|
||||
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 shouldPreloadArt = false;
|
||||
addAssetLibrary("art", shouldEmbedArt, shouldPreloadArt);
|
||||
addAsset("art/readme.txt", "do NOT readme.txt", "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.
|
||||
*/
|
||||
public function info(message:String):Void {
|
||||
// CURSED: We have to disable info() log calls because of a bug.
|
||||
// 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);
|
||||
if(command != "display") { Log.info('[INFO] ${message}'); }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+13
-7
@@ -13,6 +13,7 @@ import openfl.events.Event;
|
||||
import openfl.Lib;
|
||||
import openfl.media.Video;
|
||||
import openfl.net.NetStream;
|
||||
import funkin.util.WindowUtil;
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
#if web
|
||||
// set this variable (which is a function) from the lime version at lime/_internal/backend/html5/HTML5Application.hx
|
||||
// The framerate cap will more thoroughly initialize via Preferences in InitState.hx
|
||||
funkin.Preferences.lockedFramerateFunction = untyped js.Syntax.code("window.requestAnimationFrame");
|
||||
#end
|
||||
|
||||
if (hasEventListener(Event.ADDED_TO_STAGE))
|
||||
{
|
||||
removeEventListener(Event.ADDED_TO_STAGE, init);
|
||||
@@ -104,10 +99,21 @@ class Main extends Sprite
|
||||
|
||||
// George recommends binding the save before FlxGame is created.
|
||||
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);
|
||||
|
||||
// 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
|
||||
@:privateAccess
|
||||
game._customSoundTray = funkin.ui.options.FunkinSoundTray;
|
||||
|
||||
+32
-1
@@ -9,10 +9,23 @@ class Prebuild
|
||||
{
|
||||
static inline final BUILD_TIME_FILE:String = '.build_time';
|
||||
|
||||
static final NG_CREDS_PATH:String = './source/funkin/api/newgrounds/NewgroundsCredentials.hx';
|
||||
|
||||
static final NG_CREDS_TEMPLATE:String = "package funkin.api.newgrounds;
|
||||
|
||||
class NewgroundsCredentials
|
||||
{
|
||||
public static final APP_ID:String = #if API_NG_APP_ID haxe.macro.Compiler.getDefine(\"API_NG_APP_ID\") #else 'INSERT APP ID HERE' #end;
|
||||
public static final ENCRYPTION_KEY:String = #if API_NG_ENC_KEY haxe.macro.Compiler.getDefine(\"API_NG_ENC_KEY\") #else 'INSERT ENCRYPTION KEY HERE' #end;
|
||||
}";
|
||||
|
||||
static function main():Void
|
||||
{
|
||||
saveBuildTime();
|
||||
trace('Building...');
|
||||
|
||||
saveBuildTime();
|
||||
|
||||
buildCredsFile();
|
||||
}
|
||||
|
||||
static function saveBuildTime():Void
|
||||
@@ -22,4 +35,22 @@ class Prebuild
|
||||
fo.writeDouble(now);
|
||||
fo.close();
|
||||
}
|
||||
|
||||
static function buildCredsFile():Void
|
||||
{
|
||||
#if sys
|
||||
if (sys.FileSystem.exists(NG_CREDS_PATH))
|
||||
{
|
||||
trace('NewgroundsCredentials.hx already exists, skipping.');
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Creating NewgroundsCredentials.hx...');
|
||||
|
||||
var fileContents:String = NG_CREDS_TEMPLATE;
|
||||
|
||||
sys.io.File.saveContent(NG_CREDS_PATH, fileContents);
|
||||
}
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import openfl.utils.Future;
|
||||
* A wrapper around `openfl.utils.Assets` which disallows access to the harmful functions.
|
||||
* Later we'll add Funkin-specific caching to this.
|
||||
*/
|
||||
@:nullSafety
|
||||
class Assets
|
||||
{
|
||||
/**
|
||||
@@ -144,16 +145,31 @@ class Assets
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
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>
|
||||
{
|
||||
return openfl.utils.Assets.loadLibrary(name);
|
||||
|
||||
@@ -413,7 +413,7 @@ class Conductor
|
||||
}
|
||||
|
||||
// Take into account instrumental and file format song offsets.
|
||||
songPos += applyOffsets ? (instrumentalOffset + formatOffset + audioVisualOffset) : 0;
|
||||
songPos += applyOffsets ? (combinedOffset) : 0;
|
||||
|
||||
var oldMeasure:Float = this.currentMeasure;
|
||||
var oldBeat:Float = this.currentBeat;
|
||||
|
||||
@@ -18,6 +18,7 @@ import funkin.data.freeplay.player.PlayerRegistry;
|
||||
import funkin.data.freeplay.style.FreeplayStyleRegistry;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.data.stickers.StickerRegistry;
|
||||
import funkin.data.event.SongEventRegistry;
|
||||
import funkin.data.stage.StageRegistry;
|
||||
import funkin.data.story.level.LevelRegistry;
|
||||
@@ -38,6 +39,9 @@ import openfl.display.BitmapData;
|
||||
#if FEATURE_DISCORD_RPC
|
||||
import funkin.api.discord.DiscordClient;
|
||||
#end
|
||||
#if FEATURE_NEWGROUNDS
|
||||
import funkin.api.newgrounds.NewgroundsClient;
|
||||
#end
|
||||
|
||||
/**
|
||||
* 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?
|
||||
WindowUtil.disableCrashHandler();
|
||||
|
||||
#if FEATURE_DEBUG_TRACY
|
||||
funkin.util.WindowUtil.initTracy();
|
||||
#end
|
||||
|
||||
// This ain't a pixel art game! (most of the time)
|
||||
FlxSprite.defaultAntialiasing = true;
|
||||
|
||||
@@ -90,9 +98,18 @@ class InitState extends FlxState
|
||||
FlxG.sound.volumeDownKeys = [];
|
||||
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.
|
||||
FlxG.game.focusLostFramerate = 30;
|
||||
|
||||
// Makes Flixel use frame times instead of locked movements per frame for things like tweens
|
||||
FlxG.fixedTimestep = false;
|
||||
|
||||
setupFlixelDebug();
|
||||
|
||||
//
|
||||
@@ -117,8 +134,8 @@ class InitState extends FlxState
|
||||
//
|
||||
// NEWGROUNDS API SETUP
|
||||
//
|
||||
#if newgrounds
|
||||
NGio.init();
|
||||
#if FEATURE_NEWGROUNDS
|
||||
NewgroundsClient.instance.init();
|
||||
#end
|
||||
|
||||
//
|
||||
@@ -151,6 +168,7 @@ class InitState extends FlxState
|
||||
#if FEATURE_SCREENSHOTS
|
||||
funkin.util.plugins.ScreenshotPlugin.initialize();
|
||||
#end
|
||||
funkin.util.plugins.NewgroundsMedalPlugin.initialize();
|
||||
funkin.util.plugins.EvacuateDebugPlugin.initialize();
|
||||
funkin.util.plugins.ForceCrashPlugin.initialize();
|
||||
funkin.util.plugins.ReloadAssetsDebugPlugin.initialize();
|
||||
@@ -176,6 +194,7 @@ class InitState extends FlxState
|
||||
FreeplayStyleRegistry.instance.loadEntries();
|
||||
AlbumRegistry.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.
|
||||
// Move it to use a BaseRegistry.
|
||||
@@ -187,6 +206,8 @@ class InitState extends FlxState
|
||||
ModuleHandler.loadModuleCache();
|
||||
ModuleHandler.callOnCreate();
|
||||
|
||||
funkin.input.Cursor.hide();
|
||||
|
||||
trace('Parsing game data took: ${TimerUtil.ms(perfStart)}');
|
||||
}
|
||||
|
||||
@@ -230,8 +251,8 @@ class InitState extends FlxState
|
||||
storyMode: true,
|
||||
title: "Cum Song Erect by Kawai Sprite",
|
||||
songId: "cum",
|
||||
characterId: "pico-playable",
|
||||
difficultyId: "nightmare",
|
||||
characterId: "pico",
|
||||
difficultyId: "hard",
|
||||
isNewHighscore: true,
|
||||
scoreData:
|
||||
{
|
||||
@@ -246,9 +267,10 @@ class InitState extends FlxState
|
||||
combo: 69,
|
||||
maxCombo: 69,
|
||||
totalNotesHit: 140,
|
||||
totalNotes: 190
|
||||
totalNotes: 240
|
||||
}
|
||||
// 2400 total notes = 7% = LOSS
|
||||
// 275 total notes = 69% = NICE
|
||||
// 240 total notes = 79% = GOOD
|
||||
// 230 total notes = 82% = GREAT
|
||||
// 210 total notes = 91% = EXCELLENT
|
||||
|
||||
@@ -2,11 +2,9 @@ package funkin;
|
||||
|
||||
import funkin.save.Save;
|
||||
import funkin.input.Controls;
|
||||
import flixel.FlxCamera;
|
||||
import funkin.input.PreciseInputManager;
|
||||
import flixel.input.actions.FlxActionInput;
|
||||
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.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package funkin;
|
||||
|
||||
import funkin.save.Save;
|
||||
import funkin.util.WindowUtil;
|
||||
|
||||
/**
|
||||
* A core class which provides a store of user-configurable, globally relevant values.
|
||||
@@ -37,7 +38,7 @@ class Preferences
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether some particularly fowl language is displayed.
|
||||
* Whether some particularly foul language is displayed.
|
||||
* @default `true`
|
||||
*/
|
||||
public static var naughtyness(get, set):Bool;
|
||||
@@ -157,6 +158,72 @@ class Preferences
|
||||
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;
|
||||
|
||||
static function get_unlockedFramerate():Bool
|
||||
@@ -199,6 +266,121 @@ class Preferences
|
||||
public static var lockedFramerateFunction = untyped js.Syntax.code("window.requestAnimationFrame");
|
||||
#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.
|
||||
*/
|
||||
@@ -206,11 +388,14 @@ class Preferences
|
||||
{
|
||||
// Apply the autoPause setting (enables automatic pausing on focus lost).
|
||||
FlxG.autoPause = Preferences.autoPause;
|
||||
// WindowUtil.setVSyncMode(Preferences.vsyncMode);
|
||||
// Apply the debugDisplay setting (enables the FPS and RAM display).
|
||||
toggleDebugDisplay(Preferences.debugDisplay);
|
||||
#if web
|
||||
toggleFramerateCap(Preferences.unlockedFramerate);
|
||||
#end
|
||||
// Apply the autoFullscreen setting (launches the game in fullscreen automatically)
|
||||
FlxG.fullscreen = Preferences.autoFullscreen;
|
||||
}
|
||||
|
||||
static function toggleFramerateCap(unlocked:Bool):Void
|
||||
|
||||
@@ -2,7 +2,10 @@ package funkin.api.discord;
|
||||
|
||||
#if FEATURE_DISCORD_RPC
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
import flash.media.Sound;
|
||||
#if flash11
|
||||
import flash.utils.ByteArray;
|
||||
#end
|
||||
import flixel.sound.FlxSound;
|
||||
import flixel.system.FlxAssets.FlxSoundAsset;
|
||||
import openfl.Assets;
|
||||
|
||||
@@ -13,9 +13,11 @@ import funkin.data.song.SongRegistry;
|
||||
import funkin.util.tools.ICloneable;
|
||||
import funkin.util.flixel.sound.FlxPartialSound;
|
||||
import funkin.Paths.PathsFunction;
|
||||
import openfl.Assets;
|
||||
import lime.app.Future;
|
||||
import lime.app.Promise;
|
||||
import lime.media.AudioSource;
|
||||
import openfl.events.Event;
|
||||
import openfl.media.Sound;
|
||||
import openfl.media.SoundChannel;
|
||||
import openfl.media.SoundMixer;
|
||||
|
||||
#if (openfl >= "8.0.0")
|
||||
@@ -110,6 +112,11 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
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?
|
||||
*/
|
||||
@@ -147,6 +154,14 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
else
|
||||
{
|
||||
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 onComplete Called when the sound finished playing.
|
||||
* @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.
|
||||
*/
|
||||
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
|
||||
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.');
|
||||
return null;
|
||||
@@ -464,6 +480,7 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
sound.volume = volume;
|
||||
sound.group = FlxG.sound.defaultSoundGroup;
|
||||
sound.persist = persist;
|
||||
sound.important = important;
|
||||
|
||||
// Make sure to add the sound to the list.
|
||||
// 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';
|
||||
}
|
||||
|
||||
@: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.
|
||||
* @param key
|
||||
* @param volume
|
||||
* @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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package funkin.audio;
|
||||
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import funkin.audio.FunkinSound;
|
||||
import flixel.tweens.FlxTween;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,60 +1,76 @@
|
||||
package funkin.audio.visualize;
|
||||
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
||||
import flixel.sound.FlxSound;
|
||||
import funkin.vis.dsp.SpectralAnalyzer;
|
||||
import funkin.vis.audioclip.frontends.LimeAudioClip;
|
||||
|
||||
using Lambda;
|
||||
|
||||
@:nullSafety
|
||||
class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
||||
{
|
||||
// public var vis:VisShit;
|
||||
var analyzer:SpectralAnalyzer;
|
||||
var analyzer:Null<SpectralAnalyzer> = null;
|
||||
|
||||
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();
|
||||
|
||||
this.snd = snd;
|
||||
|
||||
// vis = new VisShit(snd);
|
||||
// vis.snd = snd;
|
||||
var visCount = pixel ? (BAR_COUNT + 1) : (BAR_COUNT + 1);
|
||||
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
|
||||
var positionX:Array<Float> = [0, 59, 56, 66, 54, 52, 51];
|
||||
var positionY:Array<Float> = [0, -8, -3.5, -0.4, 0.5, 4.7, 7];
|
||||
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> = 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
|
||||
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.antialiasing = pixel ? false : true;
|
||||
viz.scale.set(visScale, visScale);
|
||||
add(viz);
|
||||
|
||||
var visStr = 'viz';
|
||||
viz.animation.addByPrefix('VIZ', visStr + lol, 0);
|
||||
viz.animation.play('VIZ', false, false, 6);
|
||||
viz.animation.addByPrefix('VIZ', '$visStr${index}0', 0);
|
||||
viz.animation.play('VIZ', false, false, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public function initAnalyzer()
|
||||
public function initAnalyzer():Void
|
||||
{
|
||||
if (snd == null) return;
|
||||
|
||||
@: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
|
||||
// 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;
|
||||
}
|
||||
|
||||
public function dumpSound():Void
|
||||
{
|
||||
snd = null;
|
||||
analyzer = null;
|
||||
}
|
||||
|
||||
var visTimer:Float = -1;
|
||||
var visTimeMax:Float = 1 / 30;
|
||||
|
||||
override function update(elapsed:Float)
|
||||
{
|
||||
// updateViz();
|
||||
|
||||
// updateFFT(elapsed);
|
||||
|
||||
//
|
||||
super.update(elapsed);
|
||||
}
|
||||
|
||||
@@ -86,8 +103,8 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
||||
|
||||
override function draw()
|
||||
{
|
||||
if (analyzer != null) drawFFT();
|
||||
super.draw();
|
||||
drawFFT();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,17 +112,17 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
||||
*/
|
||||
function drawFFT():Void
|
||||
{
|
||||
var levels = analyzer.getLevels();
|
||||
var levels = (analyzer != null) ? analyzer.getLevels() : getDefaultLevels();
|
||||
|
||||
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
|
||||
// Web version scales with the Flixel volume level.
|
||||
// This line brings platform parity but looks worse.
|
||||
// animFrame = Math.round(animFrame * FlxG.sound.volume);
|
||||
#end
|
||||
// don't display if we're at 0 volume from the level
|
||||
group.members[i].visible = animFrame > 0;
|
||||
|
||||
// decrement our animFrame, so we can get a value from 0-5 for animation frames
|
||||
animFrame -= 1;
|
||||
|
||||
animFrame = Math.floor(Math.min(5, animFrame));
|
||||
animFrame = Math.floor(Math.max(0, animFrame));
|
||||
@@ -116,79 +133,19 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
||||
}
|
||||
}
|
||||
|
||||
// function updateFFT(elapsed:Float)
|
||||
// {
|
||||
// if (vis.snd != null)
|
||||
// {
|
||||
// vis.checkAndSetBuffer();
|
||||
// if (vis.setBuffer)
|
||||
// {
|
||||
// var remappedShit:Int = 0;
|
||||
// if (vis.snd.playing) remappedShit = Std.int(FlxMath.remapToRange(vis.snd.time, 0, vis.snd.length, 0, vis.numSamples));
|
||||
// else
|
||||
// remappedShit = Std.int(FlxMath.remapToRange(Conductor.instance.songPosition, 0, vis.snd.length, 0, vis.numSamples));
|
||||
// var fftSamples:Array<Float> = [];
|
||||
// var swagBucks = remappedShit;
|
||||
// for (i in remappedShit...remappedShit + (Std.int((44100 * (1 / 144)))))
|
||||
// {
|
||||
// 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;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
/**
|
||||
* Explicitly define the default levels to draw when the analyzer is not available.
|
||||
* @return Array<Bar>
|
||||
*/
|
||||
static function getDefaultLevels():Array<Bar>
|
||||
{
|
||||
var result:Array<Bar> = [];
|
||||
|
||||
for (i in 0...BAR_COUNT)
|
||||
{
|
||||
result.push({value: 0, peak: 0.0});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import flixel.math.FlxMath;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.sound.FlxSound;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.audio.visualize.VisShit;
|
||||
import funkin.audio.visualize.VisShit.CurAudioInfo;
|
||||
import funkin.graphics.rendering.MeshRender;
|
||||
import lime.utils.Int16Array;
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ typedef EntryConstructorFunction = String->Void;
|
||||
* @param J The type of the JSON data used when constructing.
|
||||
*/
|
||||
@:generic
|
||||
@:autoBuild(funkin.util.macro.DataRegistryMacro.buildRegistry())
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
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.
|
||||
* @param T The JSON data type of the registry entry.
|
||||
*/
|
||||
@:autoBuild(funkin.util.macro.RegistryMacro.buildEntry())
|
||||
interface IRegistryEntry<T>
|
||||
{
|
||||
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
|
||||
{
|
||||
return {
|
||||
@@ -30,6 +40,11 @@ class AnimationDataUtil
|
||||
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.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.
|
||||
@@ -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 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()
|
||||
{
|
||||
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.data.dialogue.dialoguebox.DialogueBoxData;
|
||||
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.
|
||||
@@ -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_RULE:thx.semver.VersionRule = "1.1.x";
|
||||
|
||||
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 static final DIALOGUEBOX_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <1.2.0";
|
||||
|
||||
public function new()
|
||||
{
|
||||
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;
|
||||
|
||||
import funkin.play.cutscene.dialogue.Speaker;
|
||||
import funkin.data.dialogue.speaker.SpeakerData;
|
||||
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.
|
||||
@@ -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 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()
|
||||
{
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
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)
|
||||
abstract SongEventSchema(SongEventSchemaRaw)
|
||||
{
|
||||
|
||||
@@ -36,6 +36,13 @@ typedef AlbumData =
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -3,8 +3,10 @@ package funkin.data.freeplay.album;
|
||||
import funkin.ui.freeplay.Album;
|
||||
import funkin.data.freeplay.album.AlbumData;
|
||||
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.
|
||||
@@ -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 instance:AlbumRegistry = new AlbumRegistry();
|
||||
|
||||
public function new()
|
||||
{
|
||||
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/),
|
||||
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]
|
||||
Initial release.
|
||||
|
||||
@@ -31,6 +31,15 @@ class PlayerData
|
||||
@:default(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.
|
||||
*/
|
||||
@@ -325,12 +334,20 @@ typedef PlayerResultsAnimationData =
|
||||
*/
|
||||
var renderType:String;
|
||||
|
||||
var assetPath:String;
|
||||
@:optional
|
||||
var assetPath:Null<String>;
|
||||
|
||||
@:optional
|
||||
var scriptClass:Null<String>;
|
||||
|
||||
@:optional
|
||||
@:default([0, 0])
|
||||
var offsets:Array<Float>;
|
||||
|
||||
@:optional
|
||||
@:default("both")
|
||||
var filter:String;
|
||||
|
||||
@:optional
|
||||
@:default(500)
|
||||
var zIndex:Int;
|
||||
@@ -347,6 +364,10 @@ typedef PlayerResultsAnimationData =
|
||||
@:default('')
|
||||
var startFrameLabel:Null<String>;
|
||||
|
||||
@:optional
|
||||
@:default('')
|
||||
var sound:Null<String>;
|
||||
|
||||
@:optional
|
||||
@:default(true)
|
||||
var looped:Bool;
|
||||
|
||||
@@ -4,8 +4,10 @@ import funkin.data.freeplay.player.PlayerData;
|
||||
import funkin.ui.freeplay.charselect.PlayableCharacter;
|
||||
import funkin.ui.freeplay.charselect.ScriptedPlayableCharacter;
|
||||
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.
|
||||
@@ -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 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.
|
||||
*/
|
||||
@@ -130,79 +123,4 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData>
|
||||
{
|
||||
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.data.freeplay.style.FreeplayStyleData;
|
||||
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.
|
||||
@@ -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 instance:FreeplayStyleRegistry = new FreeplayStyleRegistry();
|
||||
|
||||
public function new()
|
||||
{
|
||||
super('FREEPLAYSTYLE', 'ui/freeplay/styles', FREEPLAYSTYLE_DATA_VERSION_RULE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
||||
* @param id The ID of the entry to load.
|
||||
* @return The parsed data object.
|
||||
*/
|
||||
public function parseEntryData(id:String):Null<FreeplayStyleData>
|
||||
{
|
||||
// JsonParser does not take type parameters,
|
||||
// otherwise this function would be in BaseRegistry.
|
||||
var parser:json2object.JsonParser<FreeplayStyleData> = new json2object.JsonParser<FreeplayStyleData>();
|
||||
parser.ignoreUnknownVariables = false;
|
||||
|
||||
switch (loadEntryFile(id))
|
||||
{
|
||||
case {fileName: fileName, contents: contents}:
|
||||
parser.fromJson(contents, fileName);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
if (parser.errors.length > 0)
|
||||
{
|
||||
printErrors(parser.errors, id);
|
||||
return null;
|
||||
}
|
||||
return parser.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and validate the JSON data and produce the corresponding data object.
|
||||
*
|
||||
* NOTE: Must be implemented on the implementation class.
|
||||
* @param contents The JSON as a string.
|
||||
* @param fileName An optional file name for error reporting.
|
||||
* @return The parsed data object.
|
||||
*/
|
||||
public function parseEntryDataRaw(contents:String, ?fileName:String):Null<FreeplayStyleData>
|
||||
{
|
||||
var parser:json2object.JsonParser<FreeplayStyleData> = new json2object.JsonParser<FreeplayStyleData>();
|
||||
parser.ignoreUnknownVariables = false;
|
||||
parser.fromJson(contents, fileName);
|
||||
|
||||
if (parser.errors.length > 0)
|
||||
{
|
||||
printErrors(parser.errors, fileName);
|
||||
return null;
|
||||
}
|
||||
return parser.value;
|
||||
}
|
||||
|
||||
function createScriptedEntry(clsName:String):FreeplayStyle
|
||||
{
|
||||
return ScriptedFreeplayStyle.init(clsName, 'unknown');
|
||||
}
|
||||
|
||||
function getScriptedClassNames():Array<String>
|
||||
{
|
||||
return ScriptedFreeplayStyle.listScriptClasses();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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/),
|
||||
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]
|
||||
### Added
|
||||
- Added several new `assets`:
|
||||
|
||||
@@ -187,6 +187,10 @@ typedef NoteStyleAssetData<T> =
|
||||
@:optional
|
||||
var isPixel:Bool;
|
||||
|
||||
@:default(1.0)
|
||||
@:optional
|
||||
var alpha:Float;
|
||||
|
||||
/**
|
||||
* If true, animations will be played on the graphic.
|
||||
* @default `false` to save performance.
|
||||
@@ -251,6 +255,30 @@ typedef NoteStyleData_NoteSplash =
|
||||
@:optional
|
||||
@:default(true)
|
||||
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 =
|
||||
@@ -262,4 +290,33 @@ typedef NoteStyleData_HoldNoteCover =
|
||||
@:optional
|
||||
@:default(true)
|
||||
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.ScriptedNoteStyle;
|
||||
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.
|
||||
@@ -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_RULE:thx.semver.VersionRule = "1.1.x";
|
||||
|
||||
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 static final NOTE_STYLE_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <1.2.0";
|
||||
|
||||
public function new()
|
||||
{
|
||||
@@ -33,61 +26,4 @@ class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData>
|
||||
{
|
||||
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/),
|
||||
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]
|
||||
### Added
|
||||
- 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
|
||||
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.
|
||||
* Defaults to 0 seconds in.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package funkin.data.song;
|
||||
|
||||
import funkin.data.freeplay.player.PlayerRegistry;
|
||||
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_1_0.SongMetadata_v2_1_0;
|
||||
@@ -9,11 +10,13 @@ import funkin.play.song.ScriptedSong;
|
||||
import funkin.play.song.Song;
|
||||
import funkin.util.assets.DataAssets;
|
||||
import funkin.util.VersionUtil;
|
||||
import funkin.util.tools.ISingleton;
|
||||
import funkin.data.DefaultRegistryImpl;
|
||||
|
||||
using funkin.data.song.migrator.SongDataMigrator;
|
||||
|
||||
@: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.
|
||||
@@ -39,19 +42,6 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
||||
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()
|
||||
{
|
||||
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>
|
||||
{
|
||||
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 (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 (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"))
|
||||
{
|
||||
@@ -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>
|
||||
{
|
||||
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.
|
||||
* TODO: Should this be hardcoded?
|
||||
* A list of all difficulties for a specific character.
|
||||
*/
|
||||
public function listBaseGameSongIds():Array<String>
|
||||
public function listAllDifficulties(characterId:String):Array<String>
|
||||
{
|
||||
return [
|
||||
"tutorial", "bopeebo", "fresh", "dadbattle", "spookeez", "south", "monster", "pico", "philly-nice", "blammed", "satin-panties", "high", "milf", "cocoa",
|
||||
"eggnog", "winter-horrorland", "senpai", "roses", "thorns", "ugh", "guns", "stress", "darnell", "lit-up", "2hot", "blazin"
|
||||
];
|
||||
}
|
||||
var allDifficulties:Array<String> = Constants.DEFAULT_DIFFICULTY_LIST.copy();
|
||||
var character = PlayerRegistry.instance.fetchEntry(characterId);
|
||||
|
||||
/**
|
||||
* A list of all installed story weeks that are not from the base game.
|
||||
*/
|
||||
public function listModdedSongIds():Array<String>
|
||||
{
|
||||
return listEntryIds().filter(function(id:String):Bool {
|
||||
return listBaseGameSongIds().indexOf(id) == -1;
|
||||
});
|
||||
if (character == null)
|
||||
{
|
||||
trace(' [WARN] Could not locate character $characterId');
|
||||
return allDifficulties;
|
||||
}
|
||||
|
||||
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/),
|
||||
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]
|
||||
### Added
|
||||
- Added the ability to specify `flipX` and `flipY` on stage props to horizontally or vertically flip, respectively.
|
||||
|
||||
@@ -20,6 +20,10 @@ class StageData
|
||||
@:optional
|
||||
public var cameraZoom:Null<Float>;
|
||||
|
||||
@:default("shared")
|
||||
@:optional
|
||||
public var directory:Null<String>;
|
||||
|
||||
public function new()
|
||||
{
|
||||
this.version = StageRegistry.STAGE_DATA_VERSION;
|
||||
@@ -167,12 +171,12 @@ typedef StageDataProp =
|
||||
* How much the prop scrolls relative to the camera. Used to create a parallax effect.
|
||||
* Represented as an [x, y] array of two floats.
|
||||
* [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.
|
||||
* @default [0, 0]
|
||||
* @default [1, 1]
|
||||
*/
|
||||
@:optional
|
||||
@:default([0, 0])
|
||||
@:default([1, 1])
|
||||
var scroll:Array<Float>;
|
||||
|
||||
/**
|
||||
@@ -198,6 +202,32 @@ typedef StageDataProp =
|
||||
@:default("sparrow")
|
||||
@:optional
|
||||
var animType:String;
|
||||
|
||||
/**
|
||||
* The angle of the prop, as a float.
|
||||
* @default 1.0
|
||||
*/
|
||||
@:optional
|
||||
@:default(0.0)
|
||||
var angle:Float;
|
||||
|
||||
/**
|
||||
* The blend mode of the prop, as a string.
|
||||
* Just like in photoshop.
|
||||
* @default Nothing.
|
||||
*/
|
||||
@:default("")
|
||||
@:optional
|
||||
var blend:String;
|
||||
|
||||
/**
|
||||
* The color of the prop overlay, as a hex string.
|
||||
* White overlays, or the ones with the value #FFFFFF, do not appear.
|
||||
* @default `#FFFFFF`
|
||||
*/
|
||||
@:default("#FFFFFF")
|
||||
@:optional
|
||||
var color:String;
|
||||
};
|
||||
|
||||
typedef StageDataCharacter =
|
||||
@@ -231,4 +261,32 @@ typedef StageDataCharacter =
|
||||
*/
|
||||
@:optional
|
||||
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;
|
||||
|
||||
import funkin.data.stage.StageData;
|
||||
import funkin.play.stage.Stage;
|
||||
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.
|
||||
* Handle breaking changes by incrementing this value
|
||||
* and adding migration to the `migrateStageData()` function.
|
||||
*/
|
||||
public static final STAGE_DATA_VERSION:thx.semver.Version = "1.0.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 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 static final STAGE_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <1.1.0";
|
||||
|
||||
public function new()
|
||||
{
|
||||
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/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.1.0]
|
||||
## [1.0.1]
|
||||
- Added `visible` attribute.
|
||||
|
||||
## [1.0.0]
|
||||
|
||||
@@ -2,10 +2,11 @@ package funkin.data.story.level;
|
||||
|
||||
import funkin.util.SortUtil;
|
||||
import funkin.ui.story.Level;
|
||||
import funkin.data.story.level.LevelData;
|
||||
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.
|
||||
@@ -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_RULE:thx.semver.VersionRule = "1.0.x";
|
||||
|
||||
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 static final LEVEL_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <1.1.0";
|
||||
|
||||
public function new()
|
||||
{
|
||||
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.
|
||||
* 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 [
|
||||
"tutorial",
|
||||
"week1",
|
||||
"week2",
|
||||
"week3",
|
||||
"week4",
|
||||
"week5",
|
||||
"week6",
|
||||
"week7",
|
||||
"weekend1"
|
||||
'tutorial',
|
||||
'week1',
|
||||
'week2',
|
||||
'week3',
|
||||
'week4',
|
||||
'week5',
|
||||
'week6',
|
||||
'week7',
|
||||
'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>
|
||||
{
|
||||
var result = listEntryIds();
|
||||
result.sort(SortUtil.defaultsThenAlphabetically.bind(listBaseGameLevelIds()));
|
||||
var result:Array<String> = listEntryIds();
|
||||
result.sort(SortUtil.defaultsThenAlphabetically.bind(listBaseGameEntryIds()));
|
||||
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 funkin.play.stage.Bopper;
|
||||
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
|
||||
* does offsets for characters. example, fixes Spirits trail
|
||||
*/
|
||||
@:nullSafety
|
||||
class FunkTrail extends FlxTrail
|
||||
{
|
||||
/**
|
||||
* Creates a new FunkTrail effect for a specific FlxSprite.
|
||||
*
|
||||
* @param Target The FlxSprite the trail is attached to.
|
||||
* @param Graphic The image to use for the trailsprites. Optional, uses the sprite's graphic if null.
|
||||
* @param Length The amount of trailsprites to create.
|
||||
* @param Delay How often to update the trail. 0 updates every frame.
|
||||
* @param Alpha The alpha value for the very first trailsprite.
|
||||
* @param Diff How much lower the alpha of the next trailsprite is.
|
||||
* @param target The FlxSprite the trail is attached to.
|
||||
* @param graphic The image to use for the trailsprites. Optional, uses the sprite's graphic if null.
|
||||
* @param length The amount of trailsprites to create.
|
||||
* @param delay How often to update the trail. 0 updates every frame.
|
||||
* @param alpha The alpha value for the very first trailsprite.
|
||||
* @param diff How much lower the alpha of the next trailsprite is.
|
||||
*/
|
||||
public function new(Target:FlxSprite, ?Graphic:FlxGraphicAsset, Length:Int = 10, Delay:Int = 3, Alpha:Float = 0.4, Diff:Float = 0.05)
|
||||
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)
|
||||
{
|
||||
var targ:Bopper = cast target;
|
||||
@:privateAccess effectOffset.set((targ.animOffsets[0] - targ.globalOffsets[0]) * targ.scale.x,
|
||||
(targ.animOffsets[1] - targ.globalOffsets[1]) * targ.scale.y);
|
||||
}
|
||||
@:privateAccess
|
||||
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 openfl.filters.ColorMatrixFilter;
|
||||
|
||||
@:nullSafety
|
||||
class RetroCameraFade
|
||||
{
|
||||
// im lazy, but we only use this for week 6
|
||||
|
||||
@@ -7,23 +7,17 @@ import flixel.FlxSprite;
|
||||
import flixel.graphics.FlxGraphic;
|
||||
import flixel.graphics.frames.FlxFrame;
|
||||
import flixel.math.FlxMatrix;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.math.FlxRect;
|
||||
import flixel.util.FlxColor;
|
||||
import lime.graphics.cairo.Cairo;
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.display.BlendMode;
|
||||
import openfl.display.DisplayObjectRenderer;
|
||||
import openfl.display.Graphics;
|
||||
import openfl.display.OpenGLRenderer;
|
||||
import openfl.display._internal.Context3DGraphics;
|
||||
import openfl.display3D.Context3D;
|
||||
import openfl.display3D.Context3DClearMask;
|
||||
import openfl.filters.BitmapFilter;
|
||||
import openfl.filters.BlurFilter;
|
||||
import openfl.geom.ColorTransform;
|
||||
import openfl.geom.Matrix;
|
||||
import openfl.geom.Point;
|
||||
import openfl.geom.Rectangle;
|
||||
#if (js && html5)
|
||||
import lime._internal.graphics.ImageCanvasUtil;
|
||||
|
||||
@@ -9,7 +9,7 @@ import funkin.graphics.framebuffer.FixedBitmapData;
|
||||
import openfl.display.BitmapData;
|
||||
import flixel.math.FlxRect;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.graphics.frames.FlxFrame;
|
||||
import flixel.graphics.frames.FlxFrame.FlxFrameAngle;
|
||||
import flixel.FlxCamera;
|
||||
|
||||
/**
|
||||
@@ -97,14 +97,58 @@ class FunkinSprite extends FlxSprite
|
||||
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.
|
||||
* @param input The OpenFL `BitmapData` to apply
|
||||
* @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;
|
||||
}
|
||||
@@ -238,6 +282,18 @@ class FunkinSprite extends FlxSprite
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param id The animation ID to check.
|
||||
* @return Whether the animation is dynamic (has multiple frames). `false` for static, one-frame animations.
|
||||
*/
|
||||
public function isAnimationDynamic(id:String):Bool
|
||||
{
|
||||
if (this.animation == null) return false;
|
||||
var animData = this.animation.getByName(id);
|
||||
if (animData == null) return false;
|
||||
return animData.numFrames > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Acts similarly to `makeGraphic`, but with improved memory usage,
|
||||
* at the expense of not being able to paint onto the resulting sprite.
|
||||
|
||||
@@ -3,10 +3,8 @@ package funkin.graphics.adobeanimate;
|
||||
import flixel.util.FlxSignal.FlxTypedSignal;
|
||||
import flxanimate.FlxAnimate;
|
||||
import flxanimate.FlxAnimate.Settings;
|
||||
import flxanimate.frames.FlxAnimateFrames;
|
||||
import flixel.graphics.frames.FlxFrame;
|
||||
import flixel.system.FlxAssets.FlxGraphicAsset;
|
||||
import openfl.display.BitmapData;
|
||||
import flixel.math.FlxPoint;
|
||||
import flxanimate.animate.FlxKeyFrame;
|
||||
|
||||
@@ -37,6 +35,11 @@ class FlxAtlasSprite extends FlxAnimate
|
||||
*/
|
||||
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 canPlayOtherAnims:Bool = true;
|
||||
@@ -198,33 +201,44 @@ class FlxAtlasSprite extends FlxAnimate
|
||||
goToFrameLabel(id);
|
||||
fr = anim.getFrameLabel(id);
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
return this.anim.finished;
|
||||
return isLoopComplete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the animation has reached the last frame.
|
||||
* Can be true even if animation is configured to loop.
|
||||
* @return Whether the animation has reached the last frame.
|
||||
*/
|
||||
public function isLoopComplete():Bool
|
||||
{
|
||||
if (this.anim == null) return false;
|
||||
if (!this.anim.isPlaying) return false;
|
||||
|
||||
if (fr != null) return (anim.reversed && anim.curFrame < fr.index || !anim.reversed && anim.curFrame >= (fr.index + fr.duration));
|
||||
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));
|
||||
}
|
||||
@@ -252,7 +266,7 @@ class FlxAtlasSprite extends FlxAnimate
|
||||
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 array = [];
|
||||
@@ -295,16 +309,17 @@ class FlxAtlasSprite extends FlxAnimate
|
||||
if (isLoopComplete())
|
||||
{
|
||||
anim.pause();
|
||||
_onAnimationComplete();
|
||||
|
||||
if (looping)
|
||||
{
|
||||
anim.curFrame = (fr != null) ? fr.index : 0;
|
||||
anim.resume();
|
||||
_onAnimationLoop();
|
||||
}
|
||||
else if (fr != null && anim.curFrame != anim.length - 1)
|
||||
{
|
||||
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> = [];
|
||||
|
||||
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);
|
||||
if (prevFrame == null) return;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.system.FlxAssets.FlxShader;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
|
||||
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 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')));
|
||||
FlxG.debugger.addTrackerProfile(new TrackerProfile(HSVShader, ['hue', 'saturation', 'value']));
|
||||
hue = 1;
|
||||
saturation = 1;
|
||||
value = 1;
|
||||
hue = h;
|
||||
saturation = s;
|
||||
value = v;
|
||||
}
|
||||
|
||||
function set_hue(value:Float):Float
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.addons.display.FlxRuntimeShader;
|
||||
|
||||
/**
|
||||
* Create a little dotting effect.
|
||||
*/
|
||||
class InverseDotsShader extends FlxRuntimeShader
|
||||
{
|
||||
public var amount:Float;
|
||||
|
||||
public function new(amount:Float = 1.0)
|
||||
{
|
||||
super(Assets.getText(Paths.frag("InverseDots")));
|
||||
setAmount(amount);
|
||||
}
|
||||
|
||||
public function setAmount(value:Float):Void
|
||||
{
|
||||
this.amount = value;
|
||||
this.setFloat("_amount", amount);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.system.FlxAssets.FlxShader;
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.display.ShaderParameter;
|
||||
import openfl.display.ShaderParameterType;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user