Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b976b0d71 | ||
|
|
ae1239c57d | ||
|
|
bfbbf9cb01 | ||
|
|
d29bb47d22 | ||
|
|
d29ecfb7c4 | ||
|
|
cdd17ce033 | ||
|
|
8e8b26f029 | ||
|
|
6f43438cdb | ||
|
|
a74369c77e | ||
|
|
def1b74eb6 | ||
|
|
c70fa7c94a | ||
|
|
c0353bca2b | ||
|
|
f385cf9fd3 | ||
|
|
301f71b428 | ||
|
|
189e028442 | ||
|
|
a9647cf9fc | ||
|
|
05b9d68645 | ||
|
|
45f0f14ed9 | ||
|
|
a8262a8d3b | ||
|
|
264c2d8e77 | ||
|
|
b2f231e889 | ||
|
|
34bb201104 | ||
|
|
d9fcaf0e63 | ||
|
|
56259b9143 | ||
|
|
dc2acac152 | ||
|
|
bf5a669592 | ||
|
|
8b9775d91f | ||
|
|
6caeda9a9d | ||
|
|
9983556748 | ||
|
|
651298e908 | ||
|
|
142ea6db0c | ||
|
|
5397924edd | ||
|
|
94bd18fe17 | ||
|
|
664d9e0fb3 | ||
|
|
edf6889af9 | ||
|
|
c65dddacc0 | ||
|
|
659ea41206 | ||
|
|
5cd47a4414 | ||
|
|
65699ee865 | ||
|
|
d423eb3ddd | ||
|
|
c67085a46a | ||
|
|
a83a4a599c | ||
|
|
f6ac4cad43 | ||
|
|
0101bae7ec | ||
|
|
4c5335d7a3 | ||
|
|
b811ca91a6 | ||
|
|
c5f2a74494 | ||
|
|
3e0dbe2758 | ||
|
|
bbc0546c74 | ||
|
|
617d5ffa06 | ||
|
|
73114e2da6 | ||
|
|
808698ace7 | ||
|
|
832bc5bd83 | ||
|
|
b15e809c63 | ||
|
|
8074d57186 | ||
|
|
31d3718e5c | ||
|
|
5fdbd23a17 | ||
|
|
23a9e7f944 | ||
|
|
0397a41a7f | ||
|
|
afbb335993 | ||
|
|
2bc1a31427 | ||
|
|
dafbb43532 | ||
|
|
4978c89ac3 | ||
|
|
29b541af38 | ||
|
|
fc09350be7 | ||
|
|
e1ca3c69f0 | ||
|
|
a15bd36943 | ||
|
|
51605e50f8 | ||
|
|
3a1b573ea3 | ||
|
|
ef6b214872 | ||
|
|
8f2a8c8335 | ||
|
|
7f5b4974c3 | ||
|
|
d63782ee9d | ||
|
|
19b1c32124 | ||
|
|
d183876514 | ||
|
|
db0ac48192 | ||
|
|
ba37405e4b | ||
|
|
66197b34f6 | ||
|
|
a7da71c8a8 | ||
|
|
5bd081c2af | ||
|
|
aefc4db277 | ||
|
|
adc14cc15d | ||
|
|
a8294a9cce | ||
|
|
80569c806c | ||
|
|
d6837eba2e | ||
|
|
924b1cf386 | ||
|
|
0839c16535 | ||
|
|
db3e012cb0 | ||
|
|
6c2ebff680 | ||
|
|
df0f915be5 | ||
|
|
104d3cffcb | ||
|
|
584b1ece07 | ||
|
|
4555b6736e | ||
|
|
0c674ac23b | ||
|
|
c5e5ab16df | ||
|
|
adefa902e7 | ||
|
|
63f216ea71 | ||
|
|
03d46084b4 | ||
|
|
19eb138666 | ||
|
|
e21b01aa66 | ||
|
|
c41c800998 | ||
|
|
b7675aa440 | ||
|
|
745f49ca6d | ||
|
|
78646c4635 | ||
|
|
f1cc092820 | ||
|
|
7e200241ef | ||
|
|
1c4b8751b7 | ||
|
|
926d8c0517 | ||
|
|
e290e0c702 | ||
|
|
53dec057be | ||
|
|
b3f1798320 | ||
|
|
f20b938f3f | ||
|
|
85915eae8c | ||
|
|
83bb3bb507 | ||
|
|
642b2142ca | ||
|
|
0c6b730647 | ||
|
|
83c367e23b | ||
|
|
fe7bf3dd55 | ||
|
|
273d950899 | ||
|
|
9e61481cfe | ||
|
|
e572c7c2fb | ||
|
|
95ade2a08b | ||
|
|
51324e9c28 | ||
|
|
e6e3a30c0c | ||
|
|
2b7254fbd2 | ||
|
|
d54df57b95 | ||
|
|
b295e31266 | ||
|
|
695c708cb0 | ||
|
|
4a7f56a7ad | ||
|
|
030abed207 | ||
|
|
0d8cfaf104 | ||
|
|
caf708fe4a | ||
|
|
e7cf5fbbdc | ||
|
|
ccaa3d1070 | ||
|
|
4b883d4339 | ||
|
|
b40b4b0369 | ||
|
|
aa37745bbd | ||
|
|
df6ffca01f | ||
|
|
9dd8a257df | ||
|
|
2984ce4852 | ||
|
|
8fb35ac822 | ||
|
|
e8c42c281f | ||
|
|
68892b92ab | ||
|
|
12ee60f776 | ||
|
|
945314d271 | ||
|
|
93d4f30cad | ||
|
|
dda91cf770 | ||
|
|
6a1160feab | ||
|
|
b48b5ae1aa | ||
|
|
5bf8154e0b | ||
|
|
6de51848fd | ||
|
|
ee906ae8c0 | ||
|
|
7eebce432d | ||
|
|
750779a3fb | ||
|
|
095bc9006d | ||
|
|
09fdd0c91e | ||
|
|
be5d6227a7 | ||
|
|
855deb5828 | ||
|
|
963e2ecd63 | ||
|
|
14975212a9 | ||
|
|
3ad14bac32 | ||
|
|
e672c0978f | ||
|
|
0d5b7d4ed4 | ||
|
|
7e90423495 | ||
|
|
c7c2620217 | ||
|
|
3944816021 | ||
|
|
db00feff93 | ||
|
|
6e2a077d6b | ||
|
|
1ee8246b48 | ||
|
|
20c3228d8c | ||
|
|
01bf3bee11 | ||
|
|
4d9638f0ac | ||
|
|
b00589367c | ||
|
|
577421a782 | ||
|
|
72e9fb641e | ||
|
|
429f6f9bc0 | ||
|
|
550bf95c76 | ||
|
|
da686ed0c4 | ||
|
|
9f3711bf43 | ||
|
|
6390725ef0 | ||
|
|
cdc468ba15 | ||
|
|
deb4e1921d | ||
|
|
068c9fb43d | ||
|
|
5be4f3bbb9 | ||
|
|
b83ae65a65 | ||
|
|
dc876be57c | ||
|
|
c08847f48d | ||
|
|
19912b5f89 | ||
|
|
019473dec2 | ||
|
|
f001562270 | ||
|
|
34bb58015d | ||
|
|
d86fe68d80 | ||
|
|
1434820d10 | ||
|
|
08fb5b8459 | ||
|
|
8e8dcaa7f9 | ||
|
|
b35059ec99 | ||
|
|
b28ba8f465 | ||
|
|
dcc8085691 | ||
|
|
c186666402 | ||
|
|
6f93960cb5 | ||
|
|
a756ceac22 | ||
|
|
c394520019 | ||
|
|
2d93baf968 | ||
|
|
4a98e6999f | ||
|
|
9ff14aed42 | ||
|
|
0a7bd3111f | ||
|
|
1f88a3b6e1 | ||
|
|
fcb47ff4fc | ||
|
|
3218ceeecd | ||
|
|
1823373d7e | ||
|
|
af14d42939 | ||
|
|
3dd3338ab2 | ||
|
|
26f2578bf2 | ||
|
|
c32ade49b9 | ||
|
|
d4eee38fd2 | ||
|
|
6543c58363 | ||
|
|
158c015682 | ||
|
|
278bac7b6d | ||
|
|
29cddfb528 | ||
|
|
da9f17cf87 | ||
|
|
02dc353032 | ||
|
|
d31ef12363 | ||
|
|
c7d4406a34 | ||
|
|
68e9daffe8 | ||
|
|
c157338783 | ||
|
|
eb9c2c2c99 | ||
|
|
afb4a3dd68 | ||
|
|
9b5032ee84 | ||
|
|
62621881c0 | ||
|
|
52be941b45 | ||
|
|
c94b3a3b65 | ||
|
|
8291fe0407 | ||
|
|
f935526baa | ||
|
|
102ac4bfc8 | ||
|
|
f8b212a318 | ||
|
|
3bd79b822e | ||
|
|
17a4be66b9 | ||
|
|
54cd52db76 | ||
|
|
4fa0334e7b | ||
|
|
b7761d45b7 | ||
|
|
3c6466bec7 | ||
|
|
1d5d0ddd52 | ||
|
|
0818bd9c93 | ||
|
|
43f6cf6267 | ||
|
|
cfd380b243 | ||
|
|
dd1b09a293 | ||
|
|
3ccd1b04da | ||
|
|
0526eb7a64 | ||
|
|
6b321b72ae | ||
|
|
9580875c23 | ||
|
|
d6d8df9f67 | ||
|
|
4eb4b00271 | ||
|
|
e7634c90e7 | ||
|
|
439be3cb26 | ||
|
|
5e93920797 | ||
|
|
a4069eba2a | ||
|
|
30168a1160 | ||
|
|
a2fcaec9ff | ||
|
|
e5e3453c1f | ||
|
|
0d8b04dd56 | ||
|
|
3b997d7c0f | ||
|
|
3ff93f1923 | ||
|
|
64d8e81ed1 | ||
|
|
a741b4eed3 | ||
|
|
81e7c6b84a | ||
|
|
77e06bc9d4 | ||
|
|
c6966c2b63 | ||
|
|
a9652544f5 | ||
|
|
042d29309f | ||
|
|
05d88ba0bf | ||
|
|
d07d173631 | ||
|
|
d02d86edcd | ||
|
|
e99526d195 | ||
|
|
14e5c1c655 | ||
|
|
f7a48bcb3b | ||
|
|
db43704fe3 | ||
|
|
35780ec9f3 | ||
|
|
36997e8b30 | ||
|
|
abe4ac8485 | ||
|
|
f61dfa1f66 | ||
|
|
0bc938d0fa | ||
|
|
668b2cdfb7 | ||
|
|
96e29b7921 | ||
|
|
00893768d1 | ||
|
|
8e43a7ae0c | ||
|
|
251e96b426 | ||
|
|
94331bd938 | ||
|
|
c4b9097868 | ||
|
|
64eb80e8b7 | ||
|
|
d84c2e7875 | ||
|
|
d08c8a541e | ||
|
|
ba7c263d23 | ||
|
|
293cd7d50a | ||
|
|
57b2ae9f37 | ||
|
|
431241bf19 | ||
|
|
703eae8c62 | ||
|
|
34c07811d1 | ||
|
|
871709bb7d | ||
|
|
91a594cc85 | ||
|
|
43bdd38a44 | ||
|
|
6e463e0188 | ||
|
|
319419c541 | ||
|
|
e8c07c9ec6 | ||
|
|
fa5a80df08 | ||
|
|
e51b6147b5 | ||
|
|
2a9712466e | ||
|
|
e9891c4d00 | ||
|
|
5c47ed4a8c | ||
|
|
6e4e7008f2 | ||
|
|
79e31636e2 | ||
|
|
8fcd145d53 | ||
|
|
e30e6ee4ed | ||
|
|
bab920dc82 | ||
|
|
ab2dd75986 | ||
|
|
a8440062b8 | ||
|
|
824cf7cf1b | ||
|
|
65786d8f1c | ||
|
|
bcd29b5d67 | ||
|
|
95847440b5 | ||
|
|
c776900df0 | ||
|
|
fc9b395138 | ||
|
|
7b54292bf7 | ||
|
|
4ef0c18212 | ||
|
|
8e4f9fe66b | ||
|
|
92b24b91e7 | ||
|
|
0db0d88256 | ||
|
|
535ff2211a | ||
|
|
f7329e0687 | ||
|
|
868932cd13 | ||
|
|
9c2ef02368 | ||
|
|
4187244305 | ||
|
|
65b9466ce3 | ||
|
|
a5e5c1dee2 | ||
|
|
a90b911653 | ||
|
|
06940573ca | ||
|
|
01693eb4ce | ||
|
|
1adcc67acb | ||
|
|
1506225100 | ||
|
|
43a09f668e | ||
|
|
07c433e6d4 | ||
|
|
febe5e84a2 | ||
|
|
d7c3b64e02 | ||
|
|
0f6e6c4701 | ||
|
|
b70946f3c3 | ||
|
|
099c309f9b | ||
|
|
02dcef4727 | ||
|
|
bf36a86877 | ||
|
|
a72fce14d1 | ||
|
|
2f339e8113 | ||
|
|
9829103671 | ||
|
|
e83b88d320 | ||
|
|
3667c51c1e | ||
|
|
a55bf60fcf | ||
|
|
90c5eea0af | ||
|
|
63883a1f07 | ||
|
|
5ecad9ea9b | ||
|
|
128642264d | ||
|
|
fa772bdec1 | ||
|
|
0a8d7a23bf | ||
|
|
bf1a238f6a | ||
|
|
3a5b625f05 | ||
|
|
f94a202f9c | ||
|
|
5e3dfbb1f5 | ||
|
|
dd82bb8ed5 | ||
|
|
8fa3384c1e | ||
|
|
d51622f563 | ||
|
|
2041b6969c | ||
|
|
e1fde6ddd9 | ||
|
|
b8f2524fa2 | ||
|
|
4adb3005ae | ||
|
|
b0151da0e7 | ||
|
|
b74df06f48 | ||
|
|
055c4d9b75 | ||
|
|
ea93ec90cb | ||
|
|
4383fcf32c | ||
|
|
cef0cbf9d4 | ||
|
|
3158e5a5da | ||
|
|
20403eb80f | ||
|
|
6b9e6c9ebc | ||
|
|
7d80e6657b | ||
|
|
bb8d3ae3dc | ||
|
|
56033b3288 | ||
|
|
6e44484a1a | ||
|
|
26de08c005 | ||
|
|
d5e3fbb384 | ||
|
|
b46e61a650 | ||
|
|
82a316c5b3 | ||
|
|
44d41a848c | ||
|
|
99657f1c79 | ||
|
|
fb26aeb71a | ||
|
|
6b404c4fcf | ||
|
|
4e05d16cb6 | ||
|
|
7ab3818344 | ||
|
|
93e6d0685b | ||
|
|
ea3f1be425 | ||
|
|
f9a40a2bf7 | ||
|
|
f8c546b82c | ||
|
|
8d3772fdfb | ||
|
|
436b735d48 | ||
|
|
fc7e518373 | ||
|
|
3fcb8ec3f2 | ||
|
|
fccfe3432b | ||
|
|
9708e20dd4 | ||
|
|
df353a1ee8 | ||
|
|
6c9c3e418a | ||
|
|
2a1766fd7b | ||
|
|
63a8403e7b | ||
|
|
7231b3f645 | ||
|
|
96bdb0d4b2 | ||
|
|
7c9f048776 | ||
|
|
6b835931c5 | ||
|
|
f4da1ced3a | ||
|
|
2633538596 | ||
|
|
6e55bcfc77 | ||
|
|
9bf83f9159 | ||
|
|
9e7b43ef4f | ||
|
|
3370e0186d | ||
|
|
85b225eeb7 | ||
|
|
9121e98428 | ||
|
|
47dda7041d | ||
|
|
2983c7b5bc | ||
|
|
6e7d511faa | ||
|
|
edb270d15e | ||
|
|
17bf4924bc | ||
|
|
7a624bc3a3 | ||
|
|
869726fc6b | ||
|
|
66ed3f2644 | ||
|
|
3168814328 | ||
|
|
e077603806 | ||
|
|
99337a7123 | ||
|
|
be0ad00913 | ||
|
|
c61a943f80 | ||
|
|
29dcf4f59a | ||
|
|
b35abffd1b | ||
|
|
07c872c775 | ||
|
|
4891e54586 | ||
|
|
a31257f10f | ||
|
|
5329a17439 | ||
|
|
3975c4fa78 | ||
|
|
04185e150c | ||
|
|
5e02db372d | ||
|
|
bd3acc319c | ||
|
|
cde7382713 | ||
|
|
44337a6b81 | ||
|
|
e610b7624c | ||
|
|
e6aadf9a31 | ||
|
|
f6caa9b67b | ||
|
|
838f7b0125 | ||
|
|
a411ce13c1 | ||
|
|
e07c4487b9 | ||
|
|
6061c7762e | ||
|
|
bda3ab1192 | ||
|
|
5054c58dca | ||
|
|
de02137d7c | ||
|
|
128afcaad1 | ||
|
|
f6d9d29181 | ||
|
|
ff56b1948a | ||
|
|
87b1fc92f8 | ||
|
|
bb974c2642 | ||
|
|
0e0c4aeb77 | ||
|
|
3492d412c6 | ||
|
|
b13bf05d16 | ||
|
|
36df61ba9e | ||
|
|
ee53ccd327 | ||
|
|
7058126e99 | ||
|
|
06c12e36c6 | ||
|
|
c06d79441d | ||
|
|
0a911ae607 | ||
|
|
13fb02599b | ||
|
|
2a3cf4a211 | ||
|
|
2a2e36a713 | ||
|
|
43d109df55 | ||
|
|
4223b1d5ec | ||
|
|
72730b67f1 | ||
|
|
3cdaf2c543 | ||
|
|
eaff62baf5 | ||
|
|
103a705e24 | ||
|
|
894d0743c9 | ||
|
|
482530c569 | ||
|
|
6795c33980 | ||
|
|
6466b88d88 | ||
|
|
9b774e3264 | ||
|
|
33f943a672 | ||
|
|
f5efc16611 | ||
|
|
7e820f9497 | ||
|
|
f3ea19ec31 | ||
|
|
40d2a892a9 | ||
|
|
b4a477e800 | ||
|
|
54ddb652b6 | ||
|
|
7cb9eb797a | ||
|
|
422feb2c6d | ||
|
|
f7df118b7b | ||
|
|
02a1af6fd9 | ||
|
|
87fe63af5e | ||
|
|
7ebf4a4558 | ||
|
|
74cd7c3353 | ||
|
|
5f80641d5f | ||
|
|
ba6461e249 | ||
|
|
24a97524ac | ||
|
|
09c2d21111 | ||
|
|
07b6d8f86f | ||
|
|
8126cca29b | ||
|
|
ba78ded457 | ||
|
|
bda5d90bbc | ||
|
|
a9d0c1cb88 | ||
|
|
f8f533ad37 | ||
|
|
0bc0ab4eec | ||
|
|
d11027fb08 | ||
|
|
7b9e375fea | ||
|
|
dd215687d4 | ||
|
|
e3eddc892d | ||
|
|
36fd05b1f9 | ||
|
|
c6ef9b17a2 | ||
|
|
943c38767b | ||
|
|
c83d2bbc69 | ||
|
|
cbcf3e2754 | ||
|
|
34c67fdb54 | ||
|
|
731f38a2bb | ||
|
|
a8b854a807 | ||
|
|
9bc217d3f5 | ||
|
|
11d6c0bc88 | ||
|
|
072264dce5 | ||
|
|
265d3808e5 | ||
|
|
609812a4a1 | ||
|
|
f48deb1ade | ||
|
|
06f6a4a42c | ||
|
|
da4af6198d | ||
|
|
557f9800f2 | ||
|
|
0a7a73388e | ||
|
|
9923e29901 | ||
|
|
58758b1d6a | ||
|
|
a07cb7ba32 | ||
|
|
58ea0287d3 | ||
|
|
60b1b9aa1f | ||
|
|
6f40aeac1c | ||
|
|
ca16d41592 | ||
|
|
72b06b4a81 | ||
|
|
e59ca3080c | ||
|
|
4f12cc4efa | ||
|
|
796cbb3812 | ||
|
|
246de95e46 | ||
|
|
fdb4ad11ab | ||
|
|
d1c87febae | ||
|
|
7100ff0ba3 | ||
|
|
b5bb09a2f7 | ||
|
|
ea2d45e6c9 | ||
|
|
d9fd814645 | ||
|
|
019a6ed25b | ||
|
|
db39b19e79 | ||
|
|
a6388962dd | ||
|
|
df142a9144 | ||
|
|
47e320e96f | ||
|
|
0c06ba942e | ||
|
|
5e939bd769 | ||
|
|
0b309403e0 | ||
|
|
c232224f04 | ||
|
|
526f326323 | ||
|
|
0b0556a8b0 | ||
|
|
62166554e7 | ||
|
|
a01bcc3da8 | ||
|
|
7e0f4d9c84 | ||
|
|
2adae38805 | ||
|
|
981aad214f | ||
|
|
48a72ce32c | ||
|
|
dfe02ec668 | ||
|
|
a3e23733db | ||
|
|
1c12b8467e | ||
|
|
ad45b72b1a | ||
|
|
785c4be88b | ||
|
|
22d41d21b8 | ||
|
|
69d8570a9e | ||
|
|
a17b0e8b3c | ||
|
|
e73be4a2b3 | ||
|
|
c38967b501 | ||
|
|
95f46d01ce | ||
|
|
8188b19622 | ||
|
|
250f01f207 | ||
|
|
8014302d58 | ||
|
|
19023645df | ||
|
|
a98f680f8f | ||
|
|
4b127b6413 | ||
|
|
2b7f62edd3 | ||
|
|
b5d95d59ff | ||
|
|
9f23a7c54c | ||
|
|
5d5d12dbfd | ||
|
|
c3a188b357 | ||
|
|
bc521b7e7f | ||
|
|
a08da8793b | ||
|
|
cb131d8c9d | ||
|
|
3aad825f86 | ||
|
|
1c2fb43ae1 | ||
|
|
3305e3099b | ||
|
|
4554487ded | ||
|
|
a0ce19f1e0 | ||
|
|
12ed07b074 | ||
|
|
6aa57a22de | ||
|
|
d294eb170d | ||
|
|
04a7007f6e | ||
|
|
bdcd7b8a91 | ||
|
|
1f5b1ad025 | ||
|
|
c4749636d3 | ||
|
|
533876680b | ||
|
|
43e55ccee0 | ||
|
|
17bb2d23ba | ||
|
|
f3ff8279c0 | ||
|
|
1dd97ff9e2 | ||
|
|
9863206fad | ||
|
|
4813e6f476 | ||
|
|
69cca7e97b | ||
|
|
f4d4b67df4 | ||
|
|
a5a5382a60 | ||
|
|
69b7bcb58b | ||
|
|
e0738e0674 | ||
|
|
8e7fc52712 | ||
|
|
bb24821f13 | ||
|
|
57870bf8c5 | ||
|
|
1274986f7d | ||
|
|
6e1eb2c8dd | ||
|
|
e69d58d0fd | ||
|
|
61a7978020 | ||
|
|
b5c5693d4d | ||
|
|
d6db842efb | ||
|
|
ec9ec2031a | ||
|
|
2383fdfade | ||
|
|
36d7de5692 | ||
|
|
7f8937d540 | ||
|
|
4ce83b2ac5 | ||
|
|
517359914b | ||
|
|
0ea4320dfb | ||
|
|
eafa3db1b5 | ||
|
|
e0911b2861 | ||
|
|
6efd8b122f | ||
|
|
f8cf32e0a9 | ||
|
|
02de1c43e2 | ||
|
|
5df14f76fe | ||
|
|
0513ac60cf | ||
|
|
83b1333191 | ||
|
|
56c94bee31 | ||
|
|
0eb1223202 | ||
|
|
da558ddaa2 | ||
|
|
be3fe9028a | ||
|
|
71ed154b81 | ||
|
|
be08bbec58 | ||
|
|
c0c5fa4617 | ||
|
|
769707e54a | ||
|
|
76bedb8079 | ||
|
|
1612005656 | ||
|
|
a5cee7c5ad | ||
|
|
c6d5bda802 | ||
|
|
a9f6521b0e | ||
|
|
e3b1a7d850 | ||
|
|
2143792369 | ||
|
|
7456b5c4fd | ||
|
|
cba2caa78b | ||
|
|
490760caa0 | ||
|
|
b28b1d8810 | ||
|
|
ef44bda9da | ||
|
|
90f6eac3fa | ||
|
|
6851cfc8aa | ||
|
|
a1cf76e89c | ||
|
|
1ac91b35ca | ||
|
|
6d48de2107 | ||
|
|
fcb48ea52f | ||
|
|
3d5e91f455 | ||
|
|
61639e5b51 | ||
|
|
d3c001c673 | ||
|
|
65dc9d487d | ||
|
|
1212c694e8 | ||
|
|
b7f2112c60 | ||
|
|
a356e6e520 | ||
|
|
71d0d38190 | ||
|
|
673b4f8a6b | ||
|
|
2ade41f152 | ||
|
|
d64cf595d6 | ||
|
|
e974718947 | ||
|
|
97da5f0122 | ||
|
|
00be427794 | ||
|
|
775904a7c0 | ||
|
|
aa08dcfbbc | ||
|
|
b75a550c5b | ||
|
|
df094f15cf | ||
|
|
e9e639500a | ||
|
|
9e13ea0b2c | ||
|
|
79188b06f4 | ||
|
|
8cad041035 | ||
|
|
0fd8624483 | ||
|
|
9cf808b5d3 | ||
|
|
3362f0bdd0 | ||
|
|
97c3114200 | ||
|
|
0d8e4a5330 | ||
|
|
6321983eba | ||
|
|
101b9f59b3 | ||
|
|
e570dfb8e7 | ||
|
|
e6b6b41766 | ||
|
|
090ddd1f1c | ||
|
|
76c8c8b520 | ||
|
|
20d9016984 | ||
|
|
144ba00377 | ||
|
|
7159cad2c1 | ||
|
|
c0b15ec066 | ||
|
|
62de2a0345 | ||
|
|
8cf346c3b0 | ||
|
|
a0cefba9fd | ||
|
|
f46f57e23d | ||
|
|
5819a43522 | ||
|
|
dff4135fc9 | ||
|
|
24ad7f4a39 | ||
|
|
4dbb0baa17 | ||
|
|
ce85ee20a7 | ||
|
|
4effd709eb | ||
|
|
09b42f3f08 | ||
|
|
55c2dabe84 |
@@ -3,13 +3,17 @@ description: Report a bug or an issue in the game.
|
||||
labels: ["type: minor bug", "status: pending triage"]
|
||||
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 my issue
|
||||
- label: I have read the [Contributing Guide](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md)
|
||||
- label: I have checked the Issues/Discussions pages to see if my issue has already been reported
|
||||
- label: I have properly titled my issue
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
@@ -40,11 +44,11 @@ body:
|
||||
- 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.5.0
|
||||
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 bug."
|
||||
@@ -56,11 +60,11 @@ body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description (include any images, videos, errors, or crash logs)
|
||||
description: Provide as much detail as you can. The better others understand your issue, the more they can help you!
|
||||
description: Provide as much detail as you can. The better others understand your issue, the more they can help you!
|
||||
placeholder: Describe your issue here...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to Reproduce
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
name: Charting Issue
|
||||
description: Report an issue with the placement of notes in the game.
|
||||
labels: ["type: charting issue", "status: pending triage"]
|
||||
title: "Charting Issue: "
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "# PLEASE READ THE [CONTRIBUTING GUIDE](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md) BEFORE OPENING ISSUES!"
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Issue Checklist
|
||||
description: Be sure to complete these steps to increase the chances of your issue being addressed!
|
||||
options:
|
||||
- label: I have read the [Contributing Guide](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md)
|
||||
- label: I have checked the Issues/Discussions pages to see if my issue has already been reported
|
||||
- label: I have properly titled my issue
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Platform
|
||||
description: Which platform are you playing on?
|
||||
options:
|
||||
- Newgrounds (Web/HTML5)
|
||||
- Itch.io (Web/HTML5)
|
||||
- Itch.io (Downloadable Build) - Windows
|
||||
- Itch.io (Downloadable Build) - MacOS
|
||||
- Itch.io (Downloadable Build) - Linux
|
||||
- Compiled from GitHub Source Code
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Browser
|
||||
description: (Web/HTML5 users only) Which browser are you playing on?
|
||||
options:
|
||||
- Google Chrome
|
||||
- Microsoft Edge
|
||||
- Firefox
|
||||
- Opera
|
||||
- Safari
|
||||
- Other (Specify in Description field)
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: Version
|
||||
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
|
||||
placeholder: ex. 0.6.3
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "## Describe the charting issue(s)."
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "### Please do not report issues from other engines. These must be reported in their respective repositories."
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Location
|
||||
description: Where did you find the issue(s)? Include the name of the song, the variation, the difficulty, the time/section of the song, and any images or videos.
|
||||
placeholder: ex. Cocoa Erect on Erect/Nightmare difficulties at Section 30
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: Why do you believe this is an issue? Is a note at the wrong time, a hold note too short/long, or something else?
|
||||
placeholder: Describe the charting issue(s) here...
|
||||
validations:
|
||||
required: true
|
||||
@@ -0,0 +1,63 @@
|
||||
name: Compiling Help
|
||||
description: Report an issue with compiling the game.
|
||||
labels: ["type: compilation help", "status: pending triage"]
|
||||
title: "Compiling Help: "
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "# PLEASE READ THE [CONTRIBUTING GUIDE](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md) BEFORE OPENING ISSUES!"
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Issue Checklist
|
||||
description: Be sure to complete these steps to increase the chances of your issue being addressed!
|
||||
options:
|
||||
- label: I have followed the [Compiling Guide](https://github.com/FunkinCrew/Funkin/blob/main/docs/COMPILING.md) and the [Troubleshooting Guide](https://github.com/FunkinCrew/Funkin/blob/main/docs/TROUBLESHOOTING.md)
|
||||
- label: I have read the [Contributing Guide](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md)
|
||||
- label: I have checked the Issues/Discussions pages to see if my issue has already been reported
|
||||
- label: I have properly titled my issue
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Platform
|
||||
description: Which platform are you compiling for?
|
||||
options:
|
||||
- Web/HTML5
|
||||
- Desktop (Windows)
|
||||
- Desktop (Mac)
|
||||
- Desktop (Linux)
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: Version
|
||||
description: Which version are you compiling? The game version is in the bottom left corner of the main menu or in the project.hxp file.
|
||||
placeholder: ex. 0.6.3
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "## Describe your compiling issue."
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "### Please do not report issues from other engines. These must be reported in their respective repositories."
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Console Output
|
||||
description: Paste any errors or warnings from the console here.
|
||||
placeholder: Paste output here...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Context
|
||||
description: Provide as much detail as you can. Did you modify any code? Provide any relevant images or videos.
|
||||
placeholder: Describe the context here...
|
||||
validations:
|
||||
required: true
|
||||
@@ -3,13 +3,18 @@ description: Report a crash that occurred while playing the game.
|
||||
labels: ["type: major bug", "status: pending triage"]
|
||||
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 my issue
|
||||
- label: I have read the [Contributing Guide](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md)
|
||||
- label: I have checked the Issues/Discussions pages to see if my issue has already been reported
|
||||
- label: I have properly titled my issue
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
@@ -41,7 +46,7 @@ body:
|
||||
attributes:
|
||||
label: Version
|
||||
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
|
||||
placeholder: ex. 0.5.0
|
||||
placeholder: ex. 0.6.3
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
@@ -3,13 +3,18 @@ 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 my enhancement
|
||||
- label: I have read the [Contributing Guide](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md)
|
||||
- label: I have checked the Issues/Discussions pages to see if my enhancement has already been suggested
|
||||
- label: I have properly titled my enhancement
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
|
||||
@@ -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 of at least 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'
|
||||
|
||||
+9
-2
@@ -1,11 +1,18 @@
|
||||
# Add Documentation tag to PR's changing markdown files, or anything in the docs folder
|
||||
Documentation:
|
||||
'pr: documentation':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- docs/*
|
||||
- '**/*.md'
|
||||
|
||||
# Add Haxe tag to PR's changing haxe code files
|
||||
Haxe:
|
||||
'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/
|
||||
|
||||
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"]
|
||||
}
|
||||
],
|
||||
|
||||
+689
-237
File diff suppressed because it is too large
Load Diff
+3
-3
@@ -14,8 +14,8 @@ Code Quality is handled by the `vshaxe.haxe-checkstyle` extension, which include
|
||||
* `IndentationCharacter` checks what is used to indent, `Indentation` checks how deep the indentation is.
|
||||
* `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.
|
||||
```
|
||||
|
||||
@@ -11,11 +11,11 @@ This game was made with love to Newgrounds and its community. Extra love to Tom
|
||||
|
||||
**PLEASE USE THE LINKS ABOVE IF YOU JUST WANT TO PLAY THE GAME**
|
||||
|
||||
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
|
||||
|
||||
|
||||
+1
-1
Submodule art updated: fbd3e3df77...8402339e2e
+1
-1
Submodule assets updated: 4abd6cc06e...c108a7ff0d
+15
-13
@@ -1,8 +1,8 @@
|
||||
FROM ubuntu:mantic
|
||||
FROM ubuntu:noble
|
||||
|
||||
ARG haxe_version=4.3.4
|
||||
ARG haxe_version=4.3.6
|
||||
ARG haxelib_version=4.1.0
|
||||
ARG 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"
|
||||
},
|
||||
|
||||
+7
-4
@@ -13,17 +13,20 @@
|
||||
- NOTE: By performing this operation, you are downloading Content which is proprietary and protected by national and international copyright and trademark laws. See [the LICENSE.md file for the Funkin.assets](https://github.com/FunkinCrew/funkin.assets/blob/main/LICENSE.md) repo for more information.
|
||||
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
|
||||
|
||||
|
||||
@@ -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**
|
||||
@@ -17,15 +20,27 @@ Most of this functionality is only available on debug builds of the game!
|
||||
- `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.
|
||||
- `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.
|
||||
- `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.
|
||||
|
||||
+33
-3
@@ -1,11 +1,19 @@
|
||||
# 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
|
||||
- `Get Thread Context Failed`
|
||||
- Turn off other expensive applications while building.
|
||||
|
||||
- `Type not found: T1`: This is thrown by `json2object`, make sure the data type of `@:default` is correct.
|
||||
- `Type not found: T1`
|
||||
- This is thrown by `json2object`, make sure the data type of `@:default` is correct.
|
||||
- NOTE: `flixel.util.typeLimit.OneOfTwo` isn't supported.
|
||||
|
||||
- `Class lists not properly generated. Try cleaning out your export folder, restarting your IDE, and rebuilding your project.`
|
||||
@@ -15,9 +23,31 @@
|
||||
- This error occurs if the PDB file located in your `export` folder is in use or exceeds 4 GB. Try deleting the `export` folder and building again from scratch.
|
||||
|
||||
- `error: RPC failed; curl 92 HTTP/2 stream 0 was not closed cleanly: PROTOCOL_ERROR (err 1)`
|
||||
- This error can happen during cloning as a result of poor network connectivity. A common fix is to run ` git config --global http.postBuffer 4096M` in your terminal.
|
||||
- This error can happen during cloning as a result of poor network connectivity. A common fix is to run `git config --global http.postBuffer 4096M` in your terminal.
|
||||
|
||||
- Repository is missing an `assets` folder, or `assets` folder is empty.
|
||||
- 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.
|
||||
|
||||
|
||||
@@ -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": "51c23588614397089a5ce182cddea729f0be6fa0",
|
||||
"ref": "07fc7aa8098deaea633b0726d01f83eb4ef8a832",
|
||||
"url": "https://github.com/haxeui/haxeui-core"
|
||||
},
|
||||
{
|
||||
"name": "haxeui-flixel",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "da27e833947f32ef007ed11f523aa5524f5a5d54",
|
||||
"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"
|
||||
},
|
||||
{
|
||||
|
||||
+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.2";
|
||||
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);
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
@@ -171,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>;
|
||||
|
||||
/**
|
||||
@@ -261,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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.system.FlxAssets.FlxShader;
|
||||
import flixel.util.FlxColor;
|
||||
import openfl.display.BitmapData;
|
||||
|
||||
class TextureSwap extends FlxShader
|
||||
@@ -9,6 +8,17 @@ class TextureSwap extends FlxShader
|
||||
public var swappedImage(default, set):BitmapData;
|
||||
public var amount(default, set):Float;
|
||||
|
||||
public function loadSwapImage(path:String)
|
||||
{
|
||||
#if html5
|
||||
BitmapData.loadFromFile(path).onComplete(function(bmp:BitmapData) {
|
||||
swappedImage = bmp;
|
||||
});
|
||||
#else
|
||||
swappedImage = BitmapData.fromFile(path);
|
||||
#end
|
||||
}
|
||||
|
||||
function set_swappedImage(_bitmapData:BitmapData):BitmapData
|
||||
{
|
||||
image.input = _bitmapData;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.system.FlxAssets.FlxShader;
|
||||
|
||||
class TitleOutline extends FlxShader
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package funkin.graphics.video;
|
||||
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxSignal.FlxTypedSignal;
|
||||
import funkin.audio.FunkinSound;
|
||||
import openfl.display3D.textures.TextureBase;
|
||||
import openfl.events.NetStatusEvent;
|
||||
import openfl.media.SoundTransform;
|
||||
import openfl.media.Video;
|
||||
@@ -12,8 +10,8 @@ import openfl.net.NetStream;
|
||||
|
||||
/**
|
||||
* Plays a video via a NetStream. Only works on HTML5.
|
||||
* This does NOT replace hxCodec, nor does hxCodec replace this.
|
||||
* hxCodec only works on desktop and does not work on HTML5!
|
||||
* This does NOT replace hxvlc, nor does hxvlc replace this.
|
||||
* hxvlc only works on desktop and does not work on HTML5!
|
||||
*/
|
||||
class FlxVideo extends FunkinSprite
|
||||
{
|
||||
|
||||
@@ -1,34 +1,17 @@
|
||||
package funkin.graphics.video;
|
||||
|
||||
#if hxCodec
|
||||
import hxcodec.flixel.FlxVideoSprite;
|
||||
#if hxvlc
|
||||
import hxvlc.flixel.FlxVideoSprite;
|
||||
|
||||
/**
|
||||
* Not to be confused with FlxVideo, this is a hxcodec based video class
|
||||
* Not to be confused with FlxVideo, this is a hxvlc based video class
|
||||
* We override it simply to correct/control our volume easier.
|
||||
*/
|
||||
class FunkinVideoSprite extends FlxVideoSprite
|
||||
{
|
||||
public var volume(default, set):Float = 1;
|
||||
|
||||
public function new(x:Float = 0, y:Float = 0)
|
||||
{
|
||||
super(x, y);
|
||||
|
||||
set_volume(1);
|
||||
}
|
||||
|
||||
override public function update(elapsed:Float):Void
|
||||
{
|
||||
super.update(elapsed);
|
||||
set_volume(volume);
|
||||
}
|
||||
|
||||
function set_volume(value:Float):Float
|
||||
{
|
||||
volume = value;
|
||||
bitmap.volume = Std.int((FlxG.sound.muted ? 0 : 1) * (FlxG.sound.logToLinear(FlxG.sound.volume) * 100) * volume);
|
||||
return volume;
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
@@ -2,23 +2,17 @@ package funkin.input;
|
||||
|
||||
import flixel.input.gamepad.FlxGamepad;
|
||||
import flixel.util.FlxDirectionFlags;
|
||||
import flixel.FlxObject;
|
||||
import flixel.input.FlxInput;
|
||||
import flixel.input.FlxInput.FlxInputState;
|
||||
import flixel.input.actions.FlxAction;
|
||||
import flixel.input.actions.FlxActionInput;
|
||||
import flixel.input.actions.FlxActionInputAnalog.FlxActionInputAnalogClickAndDragMouseMotion;
|
||||
import flixel.input.actions.FlxActionInputDigital;
|
||||
import flixel.input.actions.FlxActionManager;
|
||||
import flixel.input.actions.FlxActionSet;
|
||||
import flixel.input.android.FlxAndroidKey;
|
||||
import flixel.input.gamepad.FlxGamepadButton;
|
||||
import flixel.input.gamepad.FlxGamepadInputID;
|
||||
import flixel.input.keyboard.FlxKey;
|
||||
import flixel.input.mouse.FlxMouseButton.FlxMouseButtonID;
|
||||
import flixel.math.FlxAngle;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxTimer;
|
||||
import lime.ui.Haptic;
|
||||
|
||||
/**
|
||||
@@ -67,6 +61,8 @@ class Controls extends FlxActionSet
|
||||
var _freeplay_left = new FunkinAction(Action.FREEPLAY_LEFT);
|
||||
var _freeplay_right = new FunkinAction(Action.FREEPLAY_RIGHT);
|
||||
var _freeplay_char_select = new FunkinAction(Action.FREEPLAY_CHAR_SELECT);
|
||||
var _freeplay_jump_to_top = new FunkinAction(Action.FREEPLAY_JUMP_TO_TOP);
|
||||
var _freeplay_jump_to_bottom = new FunkinAction(Action.FREEPLAY_JUMP_TO_BOTTOM);
|
||||
var _cutscene_advance = new FunkinAction(Action.CUTSCENE_ADVANCE);
|
||||
var _debug_menu = new FunkinAction(Action.DEBUG_MENU);
|
||||
#if FEATURE_CHART_EDITOR
|
||||
@@ -276,6 +272,16 @@ class Controls extends FlxActionSet
|
||||
inline function get_FREEPLAY_CHAR_SELECT()
|
||||
return _freeplay_char_select.check();
|
||||
|
||||
public var FREEPLAY_JUMP_TO_TOP(get, never):Bool;
|
||||
|
||||
inline function get_FREEPLAY_JUMP_TO_TOP()
|
||||
return _freeplay_jump_to_top.check();
|
||||
|
||||
public var FREEPLAY_JUMP_TO_BOTTOM(get, never):Bool;
|
||||
|
||||
inline function get_FREEPLAY_JUMP_TO_BOTTOM()
|
||||
return _freeplay_jump_to_bottom.check();
|
||||
|
||||
public var CUTSCENE_ADVANCE(get, never):Bool;
|
||||
|
||||
inline function get_CUTSCENE_ADVANCE()
|
||||
@@ -337,6 +343,8 @@ class Controls extends FlxActionSet
|
||||
add(_freeplay_left);
|
||||
add(_freeplay_right);
|
||||
add(_freeplay_char_select);
|
||||
add(_freeplay_jump_to_top);
|
||||
add(_freeplay_jump_to_bottom);
|
||||
add(_cutscene_advance);
|
||||
add(_debug_menu);
|
||||
#if FEATURE_CHART_EDITOR add(_debug_chart); #end
|
||||
@@ -409,6 +417,7 @@ class Controls extends FlxActionSet
|
||||
|
||||
public function getDialogueName(action:FlxActionDigital, ?ignoreSurrounding:Bool = false):String
|
||||
{
|
||||
if (action.inputs.length == 0) return 'N/A';
|
||||
var input = action.inputs[0];
|
||||
if (ignoreSurrounding == false)
|
||||
{
|
||||
@@ -462,6 +471,8 @@ class Controls extends FlxActionSet
|
||||
case FREEPLAY_LEFT: _freeplay_left;
|
||||
case FREEPLAY_RIGHT: _freeplay_right;
|
||||
case FREEPLAY_CHAR_SELECT: _freeplay_char_select;
|
||||
case FREEPLAY_JUMP_TO_TOP: _freeplay_jump_to_top;
|
||||
case FREEPLAY_JUMP_TO_BOTTOM: _freeplay_jump_to_bottom;
|
||||
case CUTSCENE_ADVANCE: _cutscene_advance;
|
||||
case DEBUG_MENU: _debug_menu;
|
||||
#if FEATURE_CHART_EDITOR case DEBUG_CHART: _debug_chart; #end
|
||||
@@ -542,6 +553,10 @@ class Controls extends FlxActionSet
|
||||
func(_freeplay_right, JUST_PRESSED);
|
||||
case FREEPLAY_CHAR_SELECT:
|
||||
func(_freeplay_char_select, JUST_PRESSED);
|
||||
case FREEPLAY_JUMP_TO_TOP:
|
||||
func(_freeplay_jump_to_top, JUST_PRESSED);
|
||||
case FREEPLAY_JUMP_TO_BOTTOM:
|
||||
func(_freeplay_jump_to_bottom, JUST_PRESSED);
|
||||
case CUTSCENE_ADVANCE:
|
||||
func(_cutscene_advance, JUST_PRESSED);
|
||||
case DEBUG_MENU:
|
||||
@@ -711,7 +726,7 @@ class Controls extends FlxActionSet
|
||||
forEachBound(control, function(action, state) addKeys(action, keys, state));
|
||||
}
|
||||
|
||||
public function bindSwipe(control:Control, swipeDir:Int = FlxDirectionFlags.UP, ?swpLength:Float = 90)
|
||||
public function bindSwipe(control:Control, swipeDir:FlxDirectionFlags = FlxDirectionFlags.UP, ?swpLength:Float = 90)
|
||||
{
|
||||
forEachBound(control, function(action, press) action.add(new FlxActionInputDigitalMobileSwipeGameplay(swipeDir, press, swpLength)));
|
||||
}
|
||||
@@ -770,6 +785,8 @@ class Controls extends FlxActionSet
|
||||
bindKeys(Control.FREEPLAY_LEFT, getDefaultKeybinds(scheme, Control.FREEPLAY_LEFT));
|
||||
bindKeys(Control.FREEPLAY_RIGHT, getDefaultKeybinds(scheme, Control.FREEPLAY_RIGHT));
|
||||
bindKeys(Control.FREEPLAY_CHAR_SELECT, getDefaultKeybinds(scheme, Control.FREEPLAY_CHAR_SELECT));
|
||||
bindKeys(Control.FREEPLAY_JUMP_TO_TOP, getDefaultKeybinds(scheme, Control.FREEPLAY_JUMP_TO_TOP));
|
||||
bindKeys(Control.FREEPLAY_JUMP_TO_BOTTOM, getDefaultKeybinds(scheme, Control.FREEPLAY_JUMP_TO_BOTTOM));
|
||||
bindKeys(Control.CUTSCENE_ADVANCE, getDefaultKeybinds(scheme, Control.CUTSCENE_ADVANCE));
|
||||
bindKeys(Control.DEBUG_MENU, getDefaultKeybinds(scheme, Control.DEBUG_MENU));
|
||||
#if FEATURE_CHART_EDITOR
|
||||
@@ -810,6 +827,8 @@ class Controls extends FlxActionSet
|
||||
case Control.FREEPLAY_LEFT: return [Q]; // Switch tabs on the menu
|
||||
case Control.FREEPLAY_RIGHT: return [E]; // Switch tabs on the menu
|
||||
case Control.FREEPLAY_CHAR_SELECT: return [TAB];
|
||||
case Control.FREEPLAY_JUMP_TO_TOP: return [HOME];
|
||||
case Control.FREEPLAY_JUMP_TO_BOTTOM: return [END];
|
||||
case Control.CUTSCENE_ADVANCE: return [Z, ENTER];
|
||||
case Control.DEBUG_MENU: return [GRAVEACCENT];
|
||||
#if FEATURE_CHART_EDITOR case Control.DEBUG_CHART: return []; #end
|
||||
@@ -839,6 +858,8 @@ class Controls extends FlxActionSet
|
||||
case Control.FREEPLAY_LEFT: return [Q]; // Switch tabs on the menu
|
||||
case Control.FREEPLAY_RIGHT: return [E]; // Switch tabs on the menu
|
||||
case Control.FREEPLAY_CHAR_SELECT: return [TAB];
|
||||
case Control.FREEPLAY_JUMP_TO_TOP: return [HOME];
|
||||
case Control.FREEPLAY_JUMP_TO_BOTTOM: return [END];
|
||||
case Control.CUTSCENE_ADVANCE: return [G, Z];
|
||||
case Control.DEBUG_MENU: return [GRAVEACCENT];
|
||||
#if FEATURE_CHART_EDITOR case Control.DEBUG_CHART: return []; #end
|
||||
@@ -868,6 +889,8 @@ class Controls extends FlxActionSet
|
||||
case Control.FREEPLAY_LEFT: return [];
|
||||
case Control.FREEPLAY_RIGHT: return [];
|
||||
case Control.FREEPLAY_CHAR_SELECT: return [];
|
||||
case Control.FREEPLAY_JUMP_TO_TOP: return [];
|
||||
case Control.FREEPLAY_JUMP_TO_BOTTOM: return [];
|
||||
case Control.CUTSCENE_ADVANCE: return [ENTER];
|
||||
case Control.DEBUG_MENU: return [];
|
||||
#if FEATURE_CHART_EDITOR case Control.DEBUG_CHART: return []; #end
|
||||
@@ -983,6 +1006,9 @@ class Controls extends FlxActionSet
|
||||
Control.FREEPLAY_FAVORITE => getDefaultGamepadBinds(Control.FREEPLAY_FAVORITE),
|
||||
Control.FREEPLAY_LEFT => getDefaultGamepadBinds(Control.FREEPLAY_LEFT),
|
||||
Control.FREEPLAY_RIGHT => getDefaultGamepadBinds(Control.FREEPLAY_RIGHT),
|
||||
Control.FREEPLAY_CHAR_SELECT => getDefaultGamepadBinds(Control.FREEPLAY_CHAR_SELECT),
|
||||
Control.FREEPLAY_JUMP_TO_TOP => getDefaultGamepadBinds(Control.FREEPLAY_JUMP_TO_TOP),
|
||||
Control.FREEPLAY_JUMP_TO_BOTTOM => getDefaultGamepadBinds(Control.FREEPLAY_JUMP_TO_BOTTOM),
|
||||
Control.VOLUME_UP => getDefaultGamepadBinds(Control.VOLUME_UP),
|
||||
Control.VOLUME_DOWN => getDefaultGamepadBinds(Control.VOLUME_DOWN),
|
||||
Control.VOLUME_MUTE => getDefaultGamepadBinds(Control.VOLUME_MUTE),
|
||||
@@ -1033,11 +1059,17 @@ class Controls extends FlxActionSet
|
||||
case Control.CUTSCENE_ADVANCE:
|
||||
return [A];
|
||||
case Control.FREEPLAY_FAVORITE:
|
||||
[FlxGamepadInputID.BACK]; // Back (i.e. Select)
|
||||
return [Y]; // Back (i.e. Select)
|
||||
case Control.FREEPLAY_LEFT:
|
||||
[LEFT_SHOULDER];
|
||||
return [LEFT_SHOULDER];
|
||||
case Control.FREEPLAY_RIGHT:
|
||||
[RIGHT_SHOULDER];
|
||||
return [RIGHT_SHOULDER];
|
||||
case Control.FREEPLAY_CHAR_SELECT:
|
||||
return [X];
|
||||
case Control.FREEPLAY_JUMP_TO_TOP:
|
||||
return [];
|
||||
case Control.FREEPLAY_JUMP_TO_BOTTOM:
|
||||
return [];
|
||||
case Control.VOLUME_UP:
|
||||
[];
|
||||
case Control.VOLUME_DOWN:
|
||||
@@ -1446,9 +1478,9 @@ class FlxActionInputDigitalMobileSwipeGameplay extends FlxActionInputDigital
|
||||
var activateLength:Float = 90;
|
||||
var hapticPressure:Int = 100;
|
||||
|
||||
public function new(swipeDir:Int = FlxDirectionFlags.ANY, Trigger:FlxInputState, ?swipeLength:Float = 90)
|
||||
public function new(swipeDir:FlxDirectionFlags = FlxDirectionFlags.ANY, Trigger:FlxInputState, ?swipeLength:Float = 90)
|
||||
{
|
||||
super(OTHER, swipeDir, Trigger);
|
||||
super(OTHER, swipeDir.toInt(), Trigger);
|
||||
|
||||
activateLength = swipeLength;
|
||||
}
|
||||
@@ -1530,16 +1562,21 @@ class FlxActionInputDigitalMobileSwipeGameplay extends FlxActionInputDigital
|
||||
case JUST_PRESSED:
|
||||
if (swp.touchLength >= activateLength)
|
||||
{
|
||||
switch (inputID)
|
||||
if (inputID == FlxDirectionFlags.UP.toInt())
|
||||
{
|
||||
case FlxDirectionFlags.UP:
|
||||
if (degAngle >= 45 && degAngle <= 90 + 45) return properTouch(swp);
|
||||
case FlxDirectionFlags.DOWN:
|
||||
if (-degAngle >= 45 && -degAngle <= 90 + 45) return properTouch(swp);
|
||||
case FlxDirectionFlags.LEFT:
|
||||
if (degAngle <= 45 && -degAngle <= 45) return properTouch(swp);
|
||||
case FlxDirectionFlags.RIGHT:
|
||||
if (degAngle >= 90 + 45 && degAngle <= -90 + -45) return properTouch(swp);
|
||||
if (degAngle >= 45 && degAngle <= 90 + 45) return properTouch(swp);
|
||||
}
|
||||
else if (inputID == FlxDirectionFlags.DOWN.toInt())
|
||||
{
|
||||
if (-degAngle >= 45 && -degAngle <= 90 + 45) return properTouch(swp);
|
||||
}
|
||||
else if (inputID == FlxDirectionFlags.LEFT.toInt())
|
||||
{
|
||||
if (degAngle <= 45 && -degAngle <= 45) return properTouch(swp);
|
||||
}
|
||||
else if (inputID == FlxDirectionFlags.RIGHT.toInt())
|
||||
{
|
||||
if (degAngle >= 90 + 45 && degAngle <= -90 + -45) return properTouch(swp);
|
||||
}
|
||||
}
|
||||
default:
|
||||
@@ -1602,10 +1639,10 @@ enum Control
|
||||
NOTE_UP;
|
||||
NOTE_RIGHT;
|
||||
// UI
|
||||
UI_UP;
|
||||
UI_LEFT;
|
||||
UI_RIGHT;
|
||||
UI_DOWN;
|
||||
UI_UP;
|
||||
UI_RIGHT;
|
||||
RESET;
|
||||
ACCEPT;
|
||||
BACK;
|
||||
@@ -1617,6 +1654,8 @@ enum Control
|
||||
FREEPLAY_LEFT;
|
||||
FREEPLAY_RIGHT;
|
||||
FREEPLAY_CHAR_SELECT;
|
||||
FREEPLAY_JUMP_TO_TOP;
|
||||
FREEPLAY_JUMP_TO_BOTTOM;
|
||||
// WINDOW
|
||||
#if FEATURE_SCREENSHOTS WINDOW_SCREENSHOT; #end
|
||||
WINDOW_FULLSCREEN;
|
||||
@@ -1674,6 +1713,8 @@ enum abstract Action(String) to String from String
|
||||
var FREEPLAY_LEFT = "freeplay_left";
|
||||
var FREEPLAY_RIGHT = "freeplay_right";
|
||||
var FREEPLAY_CHAR_SELECT = "freeplay_char_select";
|
||||
var FREEPLAY_JUMP_TO_TOP = "freeplay_jump_to_top";
|
||||
var FREEPLAY_JUMP_TO_BOTTOM = "freeplay_jump_to_bottom";
|
||||
// VOLUME
|
||||
var VOLUME_UP = "volume_up";
|
||||
var VOLUME_DOWN = "volume_down";
|
||||
|
||||
@@ -37,6 +37,9 @@ interface IStateChangingScriptedClass extends IScriptedClass
|
||||
public function onSubStateOpenEnd(event:SubStateScriptEvent):Void;
|
||||
public function onSubStateCloseBegin(event:SubStateScriptEvent):Void;
|
||||
public function onSubStateCloseEnd(event:SubStateScriptEvent):Void;
|
||||
|
||||
public function onFocusLost(event:FocusScriptEvent):Void;
|
||||
public function onFocusGained(event:FocusScriptEvent):Void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,6 +74,11 @@ interface INoteScriptedClass extends IScriptedClass
|
||||
* Called when EITHER player (usually the player) misses a note.
|
||||
*/
|
||||
public function onNoteMiss(event:NoteScriptEvent):Void;
|
||||
|
||||
/**
|
||||
* Called when EITHER player (usually the player) drops a hold note.
|
||||
*/
|
||||
public function onNoteHoldDrop(event:HoldNoteScriptEvent):Void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,7 +148,7 @@ interface IPlayStateScriptedClass extends INoteScriptedClass extends IBPMSyncedS
|
||||
/**
|
||||
* Called when the player restarts the song, either via pause menu or restarting after a game over.
|
||||
*/
|
||||
public function onSongRetry(event:ScriptEvent):Void;
|
||||
public function onSongRetry(event:SongRetryEvent):Void;
|
||||
|
||||
/**
|
||||
* Called when the player presses a key when no note is on the strumline.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user