Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9580875c23 | ||
|
|
d6d8df9f67 | ||
|
|
4eb4b00271 | ||
|
|
e7634c90e7 | ||
|
|
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 | ||
|
|
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 | ||
|
|
a72fce14d1 | ||
|
|
2f339e8113 | ||
|
|
e83b88d320 | ||
|
|
3667c51c1e | ||
|
|
a55bf60fcf | ||
|
|
90c5eea0af | ||
|
|
63883a1f07 | ||
|
|
5ecad9ea9b | ||
|
|
128642264d | ||
|
|
fa772bdec1 | ||
|
|
0a8d7a23bf | ||
|
|
bf1a238f6a | ||
|
|
3a5b625f05 | ||
|
|
f94a202f9c | ||
|
|
5e3dfbb1f5 | ||
|
|
dd82bb8ed5 | ||
|
|
8fa3384c1e | ||
|
|
d51622f563 | ||
|
|
2041b6969c | ||
|
|
e1fde6ddd9 | ||
|
|
b8f2524fa2 | ||
|
|
4adb3005ae | ||
|
|
b0151da0e7 | ||
|
|
3158e5a5da | ||
|
|
20403eb80f | ||
|
|
6b9e6c9ebc | ||
|
|
26de08c005 | ||
|
|
d5e3fbb384 | ||
|
|
b46e61a650 | ||
|
|
82a316c5b3 | ||
|
|
44d41a848c | ||
|
|
99657f1c79 | ||
|
|
fb26aeb71a | ||
|
|
6b404c4fcf | ||
|
|
4e05d16cb6 | ||
|
|
7ab3818344 | ||
|
|
93e6d0685b | ||
|
|
ea3f1be425 | ||
|
|
f9a40a2bf7 | ||
|
|
f8c546b82c | ||
|
|
8d3772fdfb | ||
|
|
436b735d48 | ||
|
|
fc7e518373 | ||
|
|
3fcb8ec3f2 | ||
|
|
fccfe3432b | ||
|
|
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 | ||
|
|
4891e54586 | ||
|
|
a31257f10f | ||
|
|
5329a17439 | ||
|
|
3975c4fa78 | ||
|
|
04185e150c | ||
|
|
5e02db372d | ||
|
|
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 | ||
|
|
482530c569 | ||
|
|
6795c33980 | ||
|
|
6466b88d88 | ||
|
|
9b774e3264 | ||
|
|
f3ea19ec31 | ||
|
|
40d2a892a9 | ||
|
|
b4a477e800 | ||
|
|
54ddb652b6 | ||
|
|
7cb9eb797a | ||
|
|
422feb2c6d | ||
|
|
f7df118b7b | ||
|
|
02a1af6fd9 | ||
|
|
87fe63af5e | ||
|
|
7ebf4a4558 | ||
|
|
74cd7c3353 | ||
|
|
5f80641d5f | ||
|
|
ba6461e249 | ||
|
|
24a97524ac | ||
|
|
09c2d21111 | ||
|
|
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 | ||
|
|
0ea4320dfb | ||
|
|
eafa3db1b5 | ||
|
|
e0911b2861 | ||
|
|
6efd8b122f | ||
|
|
f8cf32e0a9 | ||
|
|
02de1c43e2 | ||
|
|
5df14f76fe | ||
|
|
0513ac60cf | ||
|
|
83b1333191 | ||
|
|
56c94bee31 | ||
|
|
0eb1223202 | ||
|
|
da558ddaa2 | ||
|
|
be3fe9028a | ||
|
|
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 | ||
|
|
8cad041035 | ||
|
|
0fd8624483 | ||
|
|
9cf808b5d3 | ||
|
|
3362f0bdd0 | ||
|
|
97c3114200 | ||
|
|
0d8e4a5330 | ||
|
|
6321983eba | ||
|
|
101b9f59b3 | ||
|
|
e570dfb8e7 | ||
|
|
e6b6b41766 | ||
|
|
090ddd1f1c | ||
|
|
76c8c8b520 | ||
|
|
20d9016984 | ||
|
|
144ba00377 | ||
|
|
7159cad2c1 | ||
|
|
62de2a0345 | ||
|
|
8cf346c3b0 | ||
|
|
a0cefba9fd | ||
|
|
f46f57e23d | ||
|
|
5819a43522 | ||
|
|
dff4135fc9 | ||
|
|
24ad7f4a39 | ||
|
|
4dbb0baa17 | ||
|
|
ce85ee20a7 | ||
|
|
4effd709eb | ||
|
|
55c2dabe84 |
@@ -3,13 +3,18 @@ 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 +45,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.5.3
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "## Describe your bug."
|
||||
@@ -56,11 +61,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.5.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.5.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.5.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.6
|
||||
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
|
||||
|
||||
@@ -7,21 +7,23 @@
|
||||
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'
|
||||
# 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'
|
||||
- 'status: planned'
|
||||
- 'status: rejected'
|
||||
- 'status: resolved'
|
||||
- 'status: resolved internally'
|
||||
- 'status: reviewing internally'
|
||||
- 'status: stale'
|
||||
prs:
|
||||
@@ -29,6 +31,27 @@
|
||||
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'
|
||||
- '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'
|
||||
@@ -37,15 +60,36 @@
|
||||
- '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: stale'
|
||||
|
||||
'status: rejected':
|
||||
issues:
|
||||
@@ -60,18 +104,18 @@
|
||||
- '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
|
||||
# Set a close reason
|
||||
close-reason: 'not planned'
|
||||
# Remove other status labels
|
||||
unlabel:
|
||||
- 'status: accepted'
|
||||
@@ -79,11 +123,13 @@
|
||||
- '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'
|
||||
|
||||
@@ -100,10 +146,12 @@
|
||||
- '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 }}
|
||||
+213
-121
@@ -12,146 +12,238 @@ 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:
|
||||
matrix:
|
||||
include:
|
||||
- target: windows
|
||||
- target: macos
|
||||
- target: windows
|
||||
runs-on: windows-latest
|
||||
- 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"
|
||||
@@ -8,6 +8,7 @@ RECOVER_*.fla
|
||||
shitAudio/
|
||||
.build_time
|
||||
.swp
|
||||
NewgroundsCredentials.hx
|
||||
|
||||
# Exclude JS stuff
|
||||
node_modules/
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
[submodule "assets"]
|
||||
path = assets
|
||||
url = https://github.com/FunkinCrew/Funkin.assets
|
||||
url = https://github.com/FunkinCrew/Funkin-assets-secret
|
||||
[submodule "art"]
|
||||
path = art
|
||||
url = https://github.com/FunkinCrew/Funkin.art
|
||||
url = https://github.com/FunkinCrew/Funkin-art-secret
|
||||
|
||||
Vendored
+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"]
|
||||
}
|
||||
],
|
||||
|
||||
+462
-238
File diff suppressed because it is too large
Load Diff
+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...4efa0536a3
+1
-1
Submodule assets updated: 4abd6cc06e...c445377389
+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.0-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
|
||||
|
||||
+3
-1
@@ -13,7 +13,7 @@
|
||||
- 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
|
||||
7. Run `haxelib run lime setup` to set up Lime
|
||||
8. Perform additional platform setup
|
||||
- For Windows, download the [Visual Studio Build Tools](https://aka.ms/vs/17/release/vs_BuildTools.exe)
|
||||
- When prompted, select "Individual Components" and make sure to download the following:
|
||||
@@ -21,6 +21,8 @@
|
||||
- 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/)
|
||||
- Note: Funkin's fork currently doesn't come with the necessary binaries so you'll have to rebuild Lime. See [Troubleshooting](TROUBLESHOOTING.md#lime-related-issues).
|
||||
- One of Funkin's dependencies uses libVLC, which requires you to install some packages to be able to compile: `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`)
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
# 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)
|
||||
|
||||
</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.5.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 |
|
||||
| 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.
|
||||
|
||||
**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!
|
||||
|
||||
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.
|
||||
|
||||
## 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 look like a 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).
|
||||
|
||||
Here are some guidelines for writing comments in your code:
|
||||
- Leave comments only when you believe a piece of code warrants explanation.
|
||||
- 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 Comments
|
||||
#### DO NOT:
|
||||
```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 whole 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();
|
||||
}
|
||||
```
|
||||
|
||||
#### DO:
|
||||
```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();
|
||||
}
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
# Closing
|
||||
Thank you for reading the Contributing Guide.
|
||||
|
||||
We look forward to seeing your contributions to the game!
|
||||
@@ -7,6 +7,8 @@ Most of this functionality is only available on debug builds of the game!
|
||||
- `F3`: ***SCREENSHOT***: Takes a screenshot of the game and saves it to the local `screenshots` directory. Works outside of debug builds too!
|
||||
- `F4`: ***EJECT***: Forcibly switch state to the Main Menu (with no extra transition). Useful if you're stuck in a level and you need to get out!
|
||||
- `F5`: ***HOT RELOAD***: Forcibly reload the game's scripts and data files, then restart the current state. If any files in the `assets` folder have been modified, the game should process the changes for you! NOTE: Known bug, this does not reset song charts or song scripts, but it should reset everything else (such as stage layout data and character animation data).
|
||||
- `CTRL-ALT-SHIFT-L`: ***FORCE CRASH***: Immediately crash the game with a detailed crash log and a stack trace. (Only works in the Main Menu on debug builds).
|
||||
|
||||
- `CTRL-SHIFT-L`: ***FORCE CRASH***: Immediately crash the game with a detailed crash log and a stack trace.
|
||||
|
||||
## **Play State**
|
||||
@@ -29,3 +31,8 @@ Most of this functionality is only available on debug builds of the game!
|
||||
## **Main Menu**
|
||||
- `~`: ***DEBUG***: Opens a menu to access the Chart Editor and other work-in-progress editors. Rebindable in the options menu.
|
||||
- `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.
|
||||
+30
-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,28 @@
|
||||
- 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.`
|
||||
- Usually specific to Linux. 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
|
||||
```
|
||||
For Windows or MacOS you can download pre-built binaries from [Funkin's Lime](https://github.com/FunkinCrew/lime/tree/dev-funkin/ndll).
|
||||
|
||||
@@ -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": "ffa691cb2d2d81de35b900a4411e4062ac84ab58",
|
||||
"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,7 +32,7 @@
|
||||
"name": "flxanimate",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "0654797e5eb7cd7de0c1b2dbaa1efe5a1e1d9412",
|
||||
"ref": "713d3de0e566d6cd54cde13711ab3e4f60f59f4b",
|
||||
"url": "https://github.com/Dot-Stuff/flxanimate"
|
||||
},
|
||||
{
|
||||
@@ -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": "74ba53387eab0c4c2d3825e103fe70df8e46d9b1",
|
||||
"url": "https://github.com/haxeui/haxeui-core"
|
||||
},
|
||||
{
|
||||
"name": "haxeui-flixel",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "da27e833947f32ef007ed11f523aa5524f5a5d54",
|
||||
"ref": "34fe148e45292439a9c3caf00890c93bcc6f9453",
|
||||
"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.0.1"
|
||||
},
|
||||
{
|
||||
"name": "json2object",
|
||||
"type": "git",
|
||||
@@ -145,7 +143,7 @@
|
||||
"name": "lime",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "fe3368f611a84a19afc03011353945ae4da8fffd",
|
||||
"ref": "d1322e60f97b5c6e977f9e3e8a04f22b5190e7d9",
|
||||
"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"
|
||||
},
|
||||
{
|
||||
|
||||
+50
-16
@@ -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.3";
|
||||
static final VERSION:String = "0.6.0";
|
||||
|
||||
/**
|
||||
* 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";
|
||||
|
||||
@@ -330,6 +357,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,7 +489,6 @@ class Project extends HXProject {
|
||||
|
||||
// Should be false unless explicitly requested.
|
||||
GITHUB_BUILD.apply(this, false);
|
||||
FEATURE_NEWGROUNDS.apply(this, false);
|
||||
FEATURE_GHOST_TAPPING.apply(this, false);
|
||||
|
||||
// Should be true unless explicitly requested.
|
||||
@@ -479,6 +507,12 @@ class Project extends HXProject {
|
||||
// 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());
|
||||
|
||||
@@ -579,7 +613,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' ])");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -593,7 +627,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);
|
||||
@@ -650,9 +692,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)) {
|
||||
@@ -1041,15 +1083,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}'); }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -107,7 +107,7 @@ class Main extends Sprite
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,16 +144,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);
|
||||
|
||||
@@ -38,6 +38,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 +85,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;
|
||||
|
||||
@@ -117,8 +124,8 @@ class InitState extends FlxState
|
||||
//
|
||||
// NEWGROUNDS API SETUP
|
||||
//
|
||||
#if newgrounds
|
||||
NGio.init();
|
||||
#if FEATURE_NEWGROUNDS
|
||||
NewgroundsClient.instance.init();
|
||||
#end
|
||||
|
||||
//
|
||||
@@ -151,6 +158,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();
|
||||
@@ -187,6 +195,8 @@ class InitState extends FlxState
|
||||
ModuleHandler.loadModuleCache();
|
||||
ModuleHandler.callOnCreate();
|
||||
|
||||
funkin.input.Cursor.hide();
|
||||
|
||||
trace('Parsing game data took: ${TimerUtil.ms(perfStart)}');
|
||||
}
|
||||
|
||||
@@ -230,7 +240,7 @@ class InitState extends FlxState
|
||||
storyMode: true,
|
||||
title: "Cum Song Erect by Kawai Sprite",
|
||||
songId: "cum",
|
||||
characterId: "pico-playable",
|
||||
characterId: "pico",
|
||||
difficultyId: "nightmare",
|
||||
isNewHighscore: true,
|
||||
scoreData:
|
||||
@@ -246,9 +256,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.
|
||||
|
||||
@@ -157,6 +157,25 @@ 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;
|
||||
}
|
||||
|
||||
public static var unlockedFramerate(get, set):Bool;
|
||||
|
||||
static function get_unlockedFramerate():Bool
|
||||
@@ -199,6 +218,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.
|
||||
*/
|
||||
@@ -211,6 +345,8 @@ class Preferences
|
||||
#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,105 @@
|
||||
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.
|
||||
*/
|
||||
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,388 @@
|
||||
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;
|
||||
|
||||
class Leaderboards
|
||||
{
|
||||
public static function listLeaderboardData():Map<Leaderboard, LeaderboardData>
|
||||
{
|
||||
if (NewgroundsClient.instance.leaderboards == null)
|
||||
{
|
||||
trace('[NEWGROUNDS] Not logged in, cannot fetch medal data!');
|
||||
return [];
|
||||
}
|
||||
|
||||
var result:Map<Leaderboard, LeaderboardData> = [];
|
||||
|
||||
for (leaderboardId in NewgroundsClient.instance.leaderboards.keys())
|
||||
{
|
||||
var leaderboardData = NewgroundsClient.instance.leaderboards.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 leaderboardData = NewgroundsClient.instance.leaderboards.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,343 @@
|
||||
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 haxe.Json;
|
||||
|
||||
class Medals
|
||||
{
|
||||
public static var medalJSON:Array<MedalJSON>;
|
||||
|
||||
public static function listMedalData():Map<Medal, MedalData>
|
||||
{
|
||||
if (NewgroundsClient.instance.medals == null)
|
||||
{
|
||||
trace('[NEWGROUNDS] Not logged in, cannot fetch medal data!');
|
||||
return [];
|
||||
}
|
||||
|
||||
var result:Map<Medal, MedalData> = [];
|
||||
|
||||
for (medalId in NewgroundsClient.instance.medals.keys())
|
||||
{
|
||||
var medalData = NewgroundsClient.instance.medals.get(medalId);
|
||||
if (medalData == null) continue;
|
||||
|
||||
// A little hacky, but it works.
|
||||
result.set(cast medalId, medalData);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static function isValid(medalData:MedalData):Bool
|
||||
{
|
||||
// IDK why medalData can exist but _data can be null...
|
||||
// TODO: Move checks to the NG library.
|
||||
@:privateAccess
|
||||
return (medalData != null && medalData._data != null);
|
||||
}
|
||||
|
||||
public static function award(medal:Medal):Void
|
||||
{
|
||||
if (NewgroundsClient.instance.isLoggedIn())
|
||||
{
|
||||
var medalData = NewgroundsClient.instance.medals.get(medal.getId());
|
||||
if (!isValid(medalData))
|
||||
{
|
||||
trace('[NEWGROUNDS] Could not retrieve data for medal: ${medal}');
|
||||
return;
|
||||
}
|
||||
|
||||
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 == null) 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 g:FlxGraphic = null;
|
||||
|
||||
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");
|
||||
if (str != null) g = FlxGraphic.fromBitmapData(bitmapData);
|
||||
g.persist = true;
|
||||
|
||||
NewgroundsMedalPlugin.play(medalData.value, medalData.name, g);
|
||||
#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 = null;
|
||||
}
|
||||
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,331 @@
|
||||
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;
|
||||
}
|
||||
|
||||
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.
|
||||
@@ -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,8 +13,6 @@ 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 openfl.media.SoundMixer;
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package funkin.audio;
|
||||
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import funkin.audio.FunkinSound;
|
||||
import flixel.tweens.FlxTween;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
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;
|
||||
|
||||
@@ -18,43 +18,54 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
||||
|
||||
public var snd:FlxSound;
|
||||
|
||||
public function new(snd:FlxSound)
|
||||
// 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 ? (7 + 1) : (7 + 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
|
||||
{
|
||||
@:privateAccess
|
||||
analyzer = new SpectralAnalyzer(snd._channel.__audioSource, 7, 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
|
||||
@@ -99,7 +110,13 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
||||
|
||||
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);
|
||||
|
||||
// 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;
|
||||
|
||||
#if desktop
|
||||
// Web version scales with the Flixel volume level.
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,7 +13,7 @@ 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 final DIALOGUEBOX_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <1.2.0";
|
||||
|
||||
public static var instance(get, never):DialogueBoxRegistry;
|
||||
static var _instance:Null<DialogueBoxRegistry> = null;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package funkin.data.dialogue.speaker;
|
||||
|
||||
import funkin.play.cutscene.dialogue.Speaker;
|
||||
import funkin.data.dialogue.speaker.SpeakerData;
|
||||
import funkin.play.cutscene.dialogue.ScriptedSpeaker;
|
||||
|
||||
class SpeakerRegistry extends BaseRegistry<Speaker, SpeakerData>
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -331,6 +331,10 @@ typedef PlayerResultsAnimationData =
|
||||
@:default([0, 0])
|
||||
var offsets:Array<Float>;
|
||||
|
||||
@:optional
|
||||
@:default("both")
|
||||
var filter:String;
|
||||
|
||||
@:optional
|
||||
@:default(500)
|
||||
var zIndex:Int;
|
||||
@@ -347,6 +351,10 @@ typedef PlayerResultsAnimationData =
|
||||
@:default('')
|
||||
var startFrameLabel:Null<String>;
|
||||
|
||||
@:optional
|
||||
@:default('')
|
||||
var sound:Null<String>;
|
||||
|
||||
@:optional
|
||||
@:default(true)
|
||||
var looped:Bool;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,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 final NOTE_STYLE_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <1.2.0";
|
||||
|
||||
public static var instance(get, never):NoteStyleRegistry;
|
||||
static var _instance:Null<NoteStyleRegistry> = null;
|
||||
|
||||
@@ -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;
|
||||
@@ -171,7 +172,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 +193,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"))
|
||||
{
|
||||
@@ -508,8 +510,32 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
||||
public function listBaseGameSongIds():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"
|
||||
"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"
|
||||
];
|
||||
}
|
||||
|
||||
@@ -522,4 +548,39 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
||||
return listBaseGameSongIds().indexOf(id) == -1;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of all difficulties for a specific character.
|
||||
*/
|
||||
public function listAllDifficulties(characterId:String):Array<String>
|
||||
{
|
||||
var allDifficulties:Array<String> = Constants.DEFAULT_DIFFICULTY_LIST.copy();
|
||||
var character = PlayerRegistry.instance.fetchEntry(characterId);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,6 +1,5 @@
|
||||
package funkin.data.stage;
|
||||
|
||||
import funkin.data.stage.StageData;
|
||||
import funkin.play.stage.Stage;
|
||||
import funkin.play.stage.ScriptedStage;
|
||||
|
||||
@@ -13,7 +12,7 @@ class StageRegistry extends BaseRegistry<Stage, StageData>
|
||||
*/
|
||||
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.3";
|
||||
public static final STAGE_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <1.1.0";
|
||||
|
||||
public static var instance(get, never):StageRegistry;
|
||||
static var _instance:Null<StageRegistry> = null;
|
||||
@@ -93,8 +92,22 @@ class StageRegistry extends BaseRegistry<Stage, StageData>
|
||||
public function listBaseGameStageIds():Array<String>
|
||||
{
|
||||
return [
|
||||
"mainStage", "mainStageErect", "spookyMansion", "phillyTrain", "phillyTrainErect", "limoRide", "limoRideErect", "mallXmas", "mallXmasErect", "mallEvil",
|
||||
"school", "schoolEvil", "tankmanBattlefield", "phillyStreets", "phillyStreetsErect", "phillyBlazin",
|
||||
"mainStage",
|
||||
"mainStageErect",
|
||||
"spookyMansion",
|
||||
"phillyTrain",
|
||||
"phillyTrainErect",
|
||||
"limoRide",
|
||||
"limoRideErect",
|
||||
"mallXmas",
|
||||
"mallXmasErect",
|
||||
"mallEvil",
|
||||
"school",
|
||||
"schoolEvil",
|
||||
"tankmanBattlefield",
|
||||
"phillyStreets",
|
||||
"phillyStreetsErect",
|
||||
"phillyBlazin",
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -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,7 +2,6 @@ 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;
|
||||
|
||||
class LevelRegistry extends BaseRegistry<Level, LevelData>
|
||||
@@ -14,7 +13,7 @@ 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 final LEVEL_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <1.1.0";
|
||||
|
||||
public static var instance(get, never):LevelRegistry;
|
||||
static var _instance:Null<LevelRegistry> = null;
|
||||
|
||||
@@ -3,7 +3,8 @@ 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
|
||||
@@ -14,27 +15,41 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -63,8 +61,6 @@ class FlxAtlasSprite extends FlxAnimate
|
||||
throw 'FlxAtlasSprite not initialized properly. Are you sure the path (${path}) exists?';
|
||||
}
|
||||
|
||||
onAnimationComplete.add(cleanupAnimation);
|
||||
|
||||
// This defaults the sprite to play the first animation in the atlas,
|
||||
// then pauses it. This ensures symbols are intialized properly.
|
||||
this.anim.play('');
|
||||
@@ -198,33 +194,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 +259,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 +302,18 @@ class FlxAtlasSprite extends FlxAnimate
|
||||
if (isLoopComplete())
|
||||
{
|
||||
anim.pause();
|
||||
_onAnimationComplete();
|
||||
|
||||
if (looping)
|
||||
{
|
||||
anim.curFrame = (fr != null) ? fr.index : 0;
|
||||
anim.resume();
|
||||
// _onAnimationComplete not called since this is a loop.
|
||||
}
|
||||
else if (fr != null && anim.curFrame != anim.length - 1)
|
||||
{
|
||||
anim.curFrame--;
|
||||
cleanupAnimation(currentAnimation ?? "");
|
||||
_onAnimationComplete();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -326,7 +335,16 @@ class FlxAtlasSprite extends FlxAnimate
|
||||
|
||||
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 ? intensity = float(intensity > thr2) : intensity = 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 ? intensity = float(intensity > thr2) : intensity = 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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,7 +143,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.
|
||||
|
||||
@@ -151,11 +151,13 @@ class PolymodHandler
|
||||
frameworkParams: buildFrameworkParams(),
|
||||
|
||||
// List of filenames to ignore in mods. Use the default list to ignore the metadata file, etc.
|
||||
ignoredFiles: Polymod.getDefaultIgnoreList(),
|
||||
ignoredFiles: buildIgnoreList(),
|
||||
|
||||
// Parsing rules for various data formats.
|
||||
parseRules: buildParseRules(),
|
||||
|
||||
skipDependencyErrors: true,
|
||||
|
||||
// Parse hxc files and register the scripted classes in them.
|
||||
useScriptedClasses: true,
|
||||
loadScriptsAsync: #if html5 true #else false #end,
|
||||
@@ -255,12 +257,12 @@ class PolymodHandler
|
||||
Polymod.blacklistImport('Sys');
|
||||
|
||||
// `Reflect`
|
||||
// Reflect.callMethod() can access blacklisted packages
|
||||
Polymod.blacklistImport('Reflect');
|
||||
// Reflect.callMethod() can access blacklisted packages, but some functions are whitelisted
|
||||
Polymod.addImportAlias('Reflect', funkin.util.ReflectUtil);
|
||||
|
||||
// `Type`
|
||||
// Type.createInstance(Type.resolveClass()) can access blacklisted packages
|
||||
Polymod.blacklistImport('Type');
|
||||
// Type.createInstance(Type.resolveClass()) can access blacklisted packages, but some functions are whitelisted
|
||||
Polymod.addImportAlias('Type', funkin.util.ReflectUtil);
|
||||
|
||||
// `cpp.Lib`
|
||||
// Lib.load() can load malicious DLLs
|
||||
@@ -288,12 +290,21 @@ class PolymodHandler
|
||||
Polymod.blacklistImport('openfl.utils.Assets');
|
||||
Polymod.blacklistImport('openfl.Lib');
|
||||
Polymod.blacklistImport('openfl.system.ApplicationDomain');
|
||||
Polymod.blacklistImport('funkin.util.FunkinTypeResolver');
|
||||
Polymod.blacklistImport('openfl.net.SharedObject');
|
||||
|
||||
// `openfl.desktop.NativeProcess`
|
||||
// Can load native processes on the host operating system.
|
||||
Polymod.blacklistImport('openfl.desktop.NativeProcess');
|
||||
|
||||
// `funkin.api.*`
|
||||
// Contains functions which may allow for cheating and such.
|
||||
for (cls in ClassMacro.listClassesInPackage('funkin.api'))
|
||||
{
|
||||
if (cls == null) continue;
|
||||
var className:String = Type.getClassName(cls);
|
||||
Polymod.blacklistImport(className);
|
||||
}
|
||||
|
||||
// `polymod.*`
|
||||
// Contains functions which may allow for un-blacklisting other modules.
|
||||
for (cls in ClassMacro.listClassesInPackage('polymod'))
|
||||
@@ -303,6 +314,24 @@ class PolymodHandler
|
||||
Polymod.blacklistImport(className);
|
||||
}
|
||||
|
||||
// `funkin.api.newgrounds.*`
|
||||
// Contains functions which allow for cheating medals and leaderboards.
|
||||
for (cls in ClassMacro.listClassesInPackage('funkin.api.newgrounds'))
|
||||
{
|
||||
if (cls == null) continue;
|
||||
var className:String = Type.getClassName(cls);
|
||||
Polymod.blacklistImport(className);
|
||||
}
|
||||
|
||||
// `io.newgrounds.*`
|
||||
// Contains functions which allow for cheating medals and leaderboards.
|
||||
for (cls in ClassMacro.listClassesInPackage('io.newgrounds'))
|
||||
{
|
||||
if (cls == null) continue;
|
||||
var className:String = Type.getClassName(cls);
|
||||
Polymod.blacklistImport(className);
|
||||
}
|
||||
|
||||
// `sys.*`
|
||||
// Access to system utilities such as the file system.
|
||||
for (cls in ClassMacro.listClassesInPackage('sys'))
|
||||
@@ -313,6 +342,21 @@ class PolymodHandler
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a list of file paths that will be ignored in mods.
|
||||
*/
|
||||
static function buildIgnoreList():Array<String>
|
||||
{
|
||||
var result = Polymod.getDefaultIgnoreList();
|
||||
|
||||
result.push('.git');
|
||||
result.push('.gitignore');
|
||||
result.push('.gitattributes');
|
||||
result.push('README.md');
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static function buildParseRules():polymod.format.ParseRules
|
||||
{
|
||||
var output:polymod.format.ParseRules = polymod.format.ParseRules.getDefault();
|
||||
@@ -333,8 +377,19 @@ class PolymodHandler
|
||||
{
|
||||
return {
|
||||
assetLibraryPaths: [
|
||||
'default' => 'preload', 'shared' => 'shared', 'songs' => 'songs', 'videos' => 'videos', 'tutorial' => 'tutorial', 'week1' => 'week1',
|
||||
'week2' => 'week2', 'week3' => 'week3', 'week4' => 'week4', 'week5' => 'week5', 'week6' => 'week6', 'week7' => 'week7', 'weekend1' => 'weekend1',
|
||||
'default' => 'preload',
|
||||
'shared' => 'shared',
|
||||
'songs' => 'songs',
|
||||
'videos' => 'videos',
|
||||
'tutorial' => 'tutorial',
|
||||
'week1' => 'week1',
|
||||
'week2' => 'week2',
|
||||
'week3' => 'week3',
|
||||
'week4' => 'week4',
|
||||
'week5' => 'week5',
|
||||
'week6' => 'week6',
|
||||
'week7' => 'week7',
|
||||
'weekend1' => 'weekend1',
|
||||
],
|
||||
coreAssetRedirect: CORE_FOLDER,
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import funkin.play.notes.NoteSprite;
|
||||
import funkin.play.cutscene.dialogue.Conversation;
|
||||
import funkin.play.Countdown.CountdownStep;
|
||||
import funkin.play.notes.NoteDirection;
|
||||
import openfl.events.EventType;
|
||||
import openfl.events.KeyboardEvent;
|
||||
|
||||
/**
|
||||
@@ -414,6 +413,28 @@ class SongLoadScriptEvent extends ScriptEvent
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AAn event that is fired when the player retries the song.
|
||||
*/
|
||||
class SongRetryEvent extends ScriptEvent
|
||||
{
|
||||
/**
|
||||
* The new difficulty of the song.
|
||||
*/
|
||||
public var difficulty(default, null):String;
|
||||
|
||||
public function new(difficulty:String):Void
|
||||
{
|
||||
super(SONG_RETRY, false);
|
||||
this.difficulty = difficulty;
|
||||
}
|
||||
|
||||
public override function toString():String
|
||||
{
|
||||
return 'SongRetryEvent(difficulty=$difficulty)';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An event that is fired when moving out of or into an FlxState.
|
||||
*/
|
||||
@@ -436,6 +457,22 @@ class StateChangeScriptEvent extends ScriptEvent
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An event that is fired when the game loses or gains focus.
|
||||
*/
|
||||
class FocusScriptEvent extends ScriptEvent
|
||||
{
|
||||
public function new(type:ScriptEventType):Void
|
||||
{
|
||||
super(type, false);
|
||||
}
|
||||
|
||||
public override function toString():String
|
||||
{
|
||||
return 'FocusScriptEvent(type=' + type + ')';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An event that is fired when moving out of or into an FlxSubState.
|
||||
*/
|
||||
|
||||
@@ -124,7 +124,7 @@ class ScriptEventDispatcher
|
||||
t.onSongEnd(event);
|
||||
return;
|
||||
case SONG_RETRY:
|
||||
t.onSongRetry(event);
|
||||
t.onSongRetry(cast event);
|
||||
return;
|
||||
case GAME_OVER:
|
||||
t.onGameOver(event);
|
||||
@@ -177,6 +177,12 @@ class ScriptEventDispatcher
|
||||
case SUBSTATE_CLOSE_END:
|
||||
t.onSubStateCloseEnd(cast event);
|
||||
return;
|
||||
case FOCUS_LOST:
|
||||
t.onFocusLost(cast event);
|
||||
return;
|
||||
case FOCUS_GAINED:
|
||||
t.onFocusGained(cast event);
|
||||
return;
|
||||
default: // Continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,6 +223,20 @@ enum abstract ScriptEventType(String) from String to String
|
||||
*/
|
||||
var SUBSTATE_CLOSE_END = 'SUBSTATE_CLOSE_END';
|
||||
|
||||
/**
|
||||
* Called when the game regains focus.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
var FOCUS_GAINED = 'FOCUS_GAINED';
|
||||
|
||||
/**
|
||||
* Called when the game loses focus.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
var FOCUS_LOST = 'FOCUS_LOST';
|
||||
|
||||
/**
|
||||
* Called when the game starts a conversation.
|
||||
*
|
||||
|
||||
@@ -109,6 +109,10 @@ class Module implements IPlayStateScriptedClass implements IStateChangingScripte
|
||||
|
||||
public function onStateChangeEnd(event:StateChangeScriptEvent) {}
|
||||
|
||||
public function onFocusGained(event:FocusScriptEvent) {}
|
||||
|
||||
public function onFocusLost(event:FocusScriptEvent) {}
|
||||
|
||||
public function onSubStateOpenBegin(event:SubStateScriptEvent) {}
|
||||
|
||||
public function onSubStateOpenEnd(event:SubStateScriptEvent) {}
|
||||
@@ -117,5 +121,5 @@ class Module implements IPlayStateScriptedClass implements IStateChangingScripte
|
||||
|
||||
public function onSubStateCloseEnd(event:SubStateScriptEvent) {}
|
||||
|
||||
public function onSongRetry(event:ScriptEvent) {}
|
||||
public function onSongRetry(event:SongRetryEvent) {}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,6 @@ package funkin.play;
|
||||
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.FlxSprite;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.events.ScriptEvent.CountdownScriptEvent;
|
||||
import flixel.util.FlxTimer;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package funkin.play;
|
||||
|
||||
import flixel.FlxState;
|
||||
import funkin.data.freeplay.player.PlayerRegistry;
|
||||
import flixel.FlxG;
|
||||
import flixel.FlxObject;
|
||||
import flixel.FlxSprite;
|
||||
@@ -145,10 +147,13 @@ class GameOverSubState extends MusicBeatSubState
|
||||
else
|
||||
{
|
||||
boyfriend = PlayState.instance.currentStage.getBoyfriend(true);
|
||||
boyfriend.canPlayOtherAnims = true;
|
||||
boyfriend.isDead = true;
|
||||
add(boyfriend);
|
||||
boyfriend.resetCharacter();
|
||||
if (boyfriend != null)
|
||||
{
|
||||
boyfriend.canPlayOtherAnims = true;
|
||||
boyfriend.isDead = true;
|
||||
add(boyfriend);
|
||||
boyfriend.resetCharacter();
|
||||
}
|
||||
}
|
||||
|
||||
setCameraTarget();
|
||||
@@ -269,6 +274,14 @@ class GameOverSubState extends MusicBeatSubState
|
||||
// PlayState.seenCutscene = false; // old thing...
|
||||
if (gameOverMusic != null) gameOverMusic.stop();
|
||||
|
||||
// Stop death quotes immediately.
|
||||
hasPlayedDeathQuote = true;
|
||||
if (deathQuoteSound != null)
|
||||
{
|
||||
deathQuoteSound.stop();
|
||||
deathQuoteSound = null;
|
||||
}
|
||||
|
||||
if (isChartingMode)
|
||||
{
|
||||
this.close();
|
||||
@@ -276,13 +289,26 @@ class GameOverSubState extends MusicBeatSubState
|
||||
PlayState.instance.close(); // This only works because PlayState is a substate!
|
||||
return;
|
||||
}
|
||||
else if (PlayStatePlaylist.isStoryMode)
|
||||
{
|
||||
openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new StoryMenuState(sticker)));
|
||||
}
|
||||
else
|
||||
{
|
||||
openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> FreeplayState.build(sticker)));
|
||||
var targetState:funkin.ui.transition.StickerSubState->FlxState = (PlayStatePlaylist.isStoryMode) ? (sticker) ->
|
||||
new StoryMenuState(sticker) : (sticker) -> FreeplayState.build(sticker);
|
||||
|
||||
if (PlayStatePlaylist.isStoryMode)
|
||||
{
|
||||
PlayStatePlaylist.reset();
|
||||
}
|
||||
|
||||
var playerCharacterId = PlayerRegistry.instance.getCharacterOwnerId(PlayState.instance.currentChart.characters.player);
|
||||
var stickerSet = (playerCharacterId == "pico") ? "stickers-set-2" : "stickers-set-1";
|
||||
var stickerPack = switch (PlayState.instance.currentChart.song.id)
|
||||
{
|
||||
case "tutorial": "tutorial";
|
||||
case "darnell" | "lit-up" | "2hot": "weekend";
|
||||
default: "all";
|
||||
};
|
||||
|
||||
openSubState(new funkin.ui.transition.StickerSubState({targetState: targetState, stickerSet: stickerSet, stickerPack: stickerPack}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,26 +327,23 @@ class GameOverSubState extends MusicBeatSubState
|
||||
else
|
||||
{
|
||||
// Music hasn't started yet.
|
||||
switch (PlayStatePlaylist.campaignId)
|
||||
|
||||
if (boyfriend.getDeathQuote() != null)
|
||||
{
|
||||
// TODO: Make the behavior for playing Jeff's voicelines generic or un-hardcoded.
|
||||
// This will simplify the class and make it easier for mods to add death quotes.
|
||||
case 'week7':
|
||||
if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished() && !playingJeffQuote)
|
||||
{
|
||||
playingJeffQuote = true;
|
||||
playJeffQuote();
|
||||
// Start music at lower volume
|
||||
startDeathMusic(0.2, false);
|
||||
boyfriend.playAnimation('deathLoop' + animationSuffix);
|
||||
}
|
||||
default:
|
||||
// Start music at normal volume once the initial death animation finishes.
|
||||
if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished())
|
||||
{
|
||||
startDeathMusic(1.0, false);
|
||||
boyfriend.playAnimation('deathLoop' + animationSuffix);
|
||||
}
|
||||
if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished() && !hasPlayedDeathQuote)
|
||||
{
|
||||
hasPlayedDeathQuote = true;
|
||||
playDeathQuote();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Start music at normal volume once the initial death animation finishes.
|
||||
if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished())
|
||||
{
|
||||
startDeathMusic(1.0, false);
|
||||
boyfriend.playAnimation('deathLoop' + animationSuffix);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -329,6 +352,33 @@ class GameOverSubState extends MusicBeatSubState
|
||||
super.update(elapsed);
|
||||
}
|
||||
|
||||
var deathQuoteSound:Null<FunkinSound> = null;
|
||||
|
||||
function playDeathQuote():Void
|
||||
{
|
||||
if (boyfriend == null) return;
|
||||
|
||||
var deathQuote = boyfriend.getDeathQuote();
|
||||
if (deathQuote == null) return;
|
||||
|
||||
if (deathQuoteSound != null)
|
||||
{
|
||||
deathQuoteSound.stop();
|
||||
deathQuoteSound = null;
|
||||
}
|
||||
|
||||
// Start music at lower volume
|
||||
startDeathMusic(0.2, false);
|
||||
boyfriend.playAnimation('deathLoop' + animationSuffix);
|
||||
deathQuoteSound = FunkinSound.playOnce(deathQuote, function() {
|
||||
// Once the quote ends, fade in the game over music.
|
||||
if (!isEnding && gameOverMusic != null)
|
||||
{
|
||||
gameOverMusic.fadeIn(4, 0.2, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Do behavior which occurs when you confirm and move to restart the level.
|
||||
*/
|
||||
@@ -339,6 +389,14 @@ class GameOverSubState extends MusicBeatSubState
|
||||
isEnding = true;
|
||||
startDeathMusic(1.0, true); // isEnding changes this function's behavior.
|
||||
|
||||
// Stop death quotes immediately.
|
||||
hasPlayedDeathQuote = true;
|
||||
if (deathQuoteSound != null)
|
||||
{
|
||||
deathQuoteSound.stop();
|
||||
deathQuoteSound = null;
|
||||
}
|
||||
|
||||
if (PlayState.instance.isMinimalMode || boyfriend == null) {}
|
||||
else
|
||||
{
|
||||
@@ -487,26 +545,7 @@ class GameOverSubState extends MusicBeatSubState
|
||||
}
|
||||
}
|
||||
|
||||
var playingJeffQuote:Bool = false;
|
||||
|
||||
/**
|
||||
* Week 7-specific hardcoded behavior, to play a custom death quote.
|
||||
* TODO: Make this a module somehow.
|
||||
*/
|
||||
function playJeffQuote():Void
|
||||
{
|
||||
var randomCensor:Array<Int> = [];
|
||||
|
||||
if (!Preferences.naughtyness) randomCensor = [1, 3, 8, 13, 17, 21];
|
||||
|
||||
FunkinSound.playOnce(Paths.sound('jeffGameover/jeffGameover-' + FlxG.random.int(1, 25, randomCensor)), function() {
|
||||
// Once the quote ends, fade in the game over music.
|
||||
if (!isEnding && gameOverMusic != null)
|
||||
{
|
||||
gameOverMusic.fadeIn(4, 0.2, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
var hasPlayedDeathQuote:Bool = false;
|
||||
|
||||
public override function destroy():Void
|
||||
{
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package funkin.play;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.play.PlayState.PlayStateParams;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.ui.MusicBeatState;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
|
||||
@@ -1,27 +1,23 @@
|
||||
package funkin.play;
|
||||
|
||||
import flixel.FlxState;
|
||||
import funkin.ui.story.StoryMenuState;
|
||||
import funkin.data.freeplay.player.PlayerRegistry;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
import flixel.FlxG;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
||||
import flixel.math.FlxMath;
|
||||
import flixel.sound.FlxSound;
|
||||
import flixel.text.FlxText;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.audio.FunkinSound;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.ui.freeplay.FreeplayState;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.play.cutscene.VideoCutscene;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.ui.AtlasText;
|
||||
import funkin.ui.debug.latency.LatencyState;
|
||||
import funkin.ui.MusicBeatSubState;
|
||||
import funkin.ui.transition.StickerSubState;
|
||||
|
||||
/**
|
||||
* Parameters for initializing the PauseSubState.
|
||||
@@ -126,7 +122,10 @@ class PauseSubState extends MusicBeatSubState
|
||||
* Disallow input until transitions are complete!
|
||||
* This prevents the pause menu from immediately closing when opened, among other things.
|
||||
*/
|
||||
public var allowInput:Bool = false;
|
||||
public var allowInput:Bool = true;
|
||||
|
||||
// If this is true, it means we are frame 1 of our substate.
|
||||
var justOpened:Bool = true;
|
||||
|
||||
/**
|
||||
* The entries currently displayed in the pause menu.
|
||||
@@ -395,10 +394,6 @@ class PauseSubState extends MusicBeatSubState
|
||||
FlxTween.tween(child, {alpha: 1, y: child.y + 5}, 1.8, {ease: FlxEase.quartOut, startDelay: delay});
|
||||
delay += 0.1;
|
||||
}
|
||||
|
||||
new FlxTimer().start(0.2, (_) -> {
|
||||
allowInput = true;
|
||||
});
|
||||
}
|
||||
|
||||
// ===============
|
||||
@@ -421,15 +416,18 @@ class PauseSubState extends MusicBeatSubState
|
||||
changeSelection(1);
|
||||
}
|
||||
|
||||
if (controls.ACCEPT)
|
||||
if (controls.ACCEPT && !justOpened)
|
||||
{
|
||||
currentMenuEntries[currentEntry].callback(this);
|
||||
}
|
||||
else if (controls.PAUSE)
|
||||
else if (controls.PAUSE && !justOpened)
|
||||
{
|
||||
resume(this);
|
||||
}
|
||||
|
||||
// we only want justOpened to be true for 1 single frame, when we first get into the pause menu substate
|
||||
justOpened = false;
|
||||
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
// to pause the game and get screenshots easy, press H on pause menu!
|
||||
if (FlxG.keys.justPressed.H)
|
||||
@@ -438,6 +436,7 @@ class PauseSubState extends MusicBeatSubState
|
||||
|
||||
metadata.visible = visible;
|
||||
menuEntryText.visible = visible;
|
||||
background.visible = visible;
|
||||
this.bgColor = visible ? 0x99000000 : 0x00000000; // 60% or fully transparent black
|
||||
}
|
||||
#end
|
||||
@@ -649,6 +648,7 @@ class PauseSubState extends MusicBeatSubState
|
||||
// So if you switch difficulty on the last song of a week you get a really low overall score.
|
||||
PlayStatePlaylist.campaignScore = 0;
|
||||
PlayStatePlaylist.campaignDifficulty = difficulty;
|
||||
PlayState.instance.previousDifficulty = PlayState.instance.currentDifficulty;
|
||||
PlayState.instance.currentDifficulty = PlayStatePlaylist.campaignDifficulty;
|
||||
|
||||
FreeplayState.rememberedDifficulty = difficulty;
|
||||
@@ -737,15 +737,25 @@ class PauseSubState extends MusicBeatSubState
|
||||
FlxTransitionableState.skipNextTransIn = true;
|
||||
FlxTransitionableState.skipNextTransOut = true;
|
||||
|
||||
var targetState:funkin.ui.transition.StickerSubState->FlxState = (PlayStatePlaylist.isStoryMode) ? (sticker) -> new StoryMenuState(sticker) : (sticker) ->
|
||||
FreeplayState.build(sticker);
|
||||
|
||||
// Do this AFTER because this resets the value of isStoryMode!
|
||||
if (PlayStatePlaylist.isStoryMode)
|
||||
{
|
||||
PlayStatePlaylist.reset();
|
||||
state.openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new funkin.ui.story.StoryMenuState(sticker)));
|
||||
}
|
||||
else
|
||||
|
||||
var playerCharacterId = PlayerRegistry.instance.getCharacterOwnerId(PlayState.instance.currentChart.characters.player);
|
||||
var stickerSet = (playerCharacterId == "pico") ? "stickers-set-2" : "stickers-set-1";
|
||||
var stickerPack = switch (PlayState.instance.currentChart.song.id)
|
||||
{
|
||||
state.openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> FreeplayState.build(null, sticker)));
|
||||
}
|
||||
case "tutorial": "tutorial";
|
||||
case "darnell" | "lit-up" | "2hot": "weekend";
|
||||
default: "all";
|
||||
};
|
||||
|
||||
state.openSubState(new funkin.ui.transition.StickerSubState({targetState: targetState, stickerSet: stickerSet, stickerPack: stickerPack}));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+109
-58
@@ -1,28 +1,22 @@
|
||||
package funkin.play;
|
||||
|
||||
import flixel.addons.display.FlxPieDial;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
import flixel.addons.transition.Transition;
|
||||
import flixel.FlxCamera;
|
||||
import flixel.FlxObject;
|
||||
import flixel.FlxState;
|
||||
import flixel.FlxSubState;
|
||||
import flixel.math.FlxMath;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.math.FlxRect;
|
||||
import flixel.text.FlxText;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.ui.FlxBar;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxStringUtil;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.api.newgrounds.NGio;
|
||||
import funkin.audio.FunkinSound;
|
||||
import funkin.audio.VoicesGroup;
|
||||
import funkin.data.dialogue.conversation.ConversationRegistry;
|
||||
import funkin.data.event.SongEventRegistry;
|
||||
import funkin.data.notestyle.NoteStyleData;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.data.song.SongData.SongCharacterData;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
@@ -34,6 +28,7 @@ import funkin.graphics.FunkinSprite;
|
||||
import funkin.Highscore.Tallies;
|
||||
import funkin.input.PreciseInputManager;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.api.newgrounds.Events;
|
||||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.play.character.BaseCharacter;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
@@ -45,7 +40,6 @@ import funkin.play.cutscene.VanillaCutscenes;
|
||||
import funkin.play.cutscene.VideoCutscene;
|
||||
import funkin.play.notes.NoteDirection;
|
||||
import funkin.play.notes.notekind.NoteKindManager;
|
||||
import funkin.play.notes.NoteSplash;
|
||||
import funkin.play.notes.NoteSprite;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
import funkin.play.notes.Strumline;
|
||||
@@ -58,18 +52,16 @@ import funkin.ui.debug.charting.ChartEditorState;
|
||||
import funkin.ui.debug.stage.StageOffsetSubState;
|
||||
import funkin.ui.mainmenu.MainMenuState;
|
||||
import funkin.ui.MusicBeatSubState;
|
||||
import funkin.ui.options.PreferencesMenu;
|
||||
import funkin.ui.story.StoryMenuState;
|
||||
import funkin.ui.transition.LoadingState;
|
||||
import funkin.util.SerializerUtil;
|
||||
import haxe.Int64;
|
||||
import lime.ui.Haptic;
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.geom.Rectangle;
|
||||
import openfl.Lib;
|
||||
#if FEATURE_DISCORD_RPC
|
||||
import funkin.api.discord.DiscordClient;
|
||||
#end
|
||||
#if FEATURE_NEWGROUNDS
|
||||
import funkin.api.newgrounds.Medals;
|
||||
import funkin.api.newgrounds.Leaderboards;
|
||||
#end
|
||||
|
||||
/**
|
||||
* Parameters used to initialize the PlayState.
|
||||
@@ -340,10 +332,16 @@ class PlayState extends MusicBeatSubState
|
||||
public var isInCutscene:Bool = false;
|
||||
|
||||
/**
|
||||
* Whether the inputs should be disabled for whatever reason... used for the stage edit lol!
|
||||
* Whether the inputs should be disabled for whatever reason...
|
||||
* Used after the song ends, and in the Stage Editor.
|
||||
*/
|
||||
public var disableKeys:Bool = false;
|
||||
|
||||
/**
|
||||
* The previous difficulty the player was playing on.
|
||||
*/
|
||||
public var previousDifficulty:String = Constants.DEFAULT_DIFFICULTY;
|
||||
|
||||
public var isSubState(get, never):Bool;
|
||||
|
||||
function get_isSubState():Bool
|
||||
@@ -365,13 +363,13 @@ class PlayState extends MusicBeatSubState
|
||||
|
||||
/**
|
||||
* Key press inputs which have been received but not yet processed.
|
||||
* These are encoded with an OS timestamp, so they
|
||||
* These are encoded with an OS timestamp, so we can account for input latency.
|
||||
**/
|
||||
var inputPressQueue:Array<PreciseInputEvent> = [];
|
||||
|
||||
/**
|
||||
* Key release inputs which have been received but not yet processed.
|
||||
* These are encoded with an OS timestamp, so they
|
||||
* These are encoded with an OS timestamp, so we can account for input latency.
|
||||
**/
|
||||
var inputReleaseQueue:Array<PreciseInputEvent> = [];
|
||||
|
||||
@@ -615,6 +613,7 @@ class PlayState extends MusicBeatSubState
|
||||
// Apply parameters.
|
||||
currentSong = params.targetSong;
|
||||
if (params.targetDifficulty != null) currentDifficulty = params.targetDifficulty;
|
||||
previousDifficulty = currentDifficulty;
|
||||
if (params.targetVariation != null) currentVariation = params.targetVariation;
|
||||
if (params.targetInstrumental != null) currentInstrumental = params.targetInstrumental;
|
||||
isPracticeMode = params.practiceMode ?? false;
|
||||
@@ -760,24 +759,6 @@ class PlayState extends MusicBeatSubState
|
||||
refresh();
|
||||
}
|
||||
|
||||
public override function draw():Void
|
||||
{
|
||||
// if (FlxG.renderBlit)
|
||||
// {
|
||||
// camGame.fill(BACKGROUND_COLOR);
|
||||
// }
|
||||
// else if (FlxG.renderTile)
|
||||
// {
|
||||
// FlxG.log.warn("PlayState background not displayed properly on tile renderer!");
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// FlxG.log.warn("PlayState background not displayed properly, unknown renderer!");
|
||||
// }
|
||||
|
||||
super.draw();
|
||||
}
|
||||
|
||||
function assertChartExists():Bool
|
||||
{
|
||||
// Returns null if the song failed to load or doesn't have the selected difficulty.
|
||||
@@ -826,8 +807,6 @@ class PlayState extends MusicBeatSubState
|
||||
|
||||
public override function update(elapsed:Float):Void
|
||||
{
|
||||
// TOTAL: 9.42% CPU Time when profiled in VS 2019.
|
||||
|
||||
if (criticalFailure) return;
|
||||
|
||||
super.update(elapsed);
|
||||
@@ -843,7 +822,11 @@ class PlayState extends MusicBeatSubState
|
||||
|
||||
prevScrollTargets = [];
|
||||
|
||||
dispatchEvent(new ScriptEvent(SONG_RETRY));
|
||||
var retryEvent = new SongRetryEvent(currentDifficulty);
|
||||
|
||||
previousDifficulty = currentDifficulty;
|
||||
|
||||
dispatchEvent(retryEvent);
|
||||
|
||||
resetCamera();
|
||||
|
||||
@@ -908,6 +891,10 @@ class PlayState extends MusicBeatSubState
|
||||
Highscore.tallies.combo = 0;
|
||||
Countdown.performCountdown();
|
||||
|
||||
// Reset the health icons.
|
||||
currentStage.getBoyfriend().initHealthIcon(false);
|
||||
currentStage.getDad().initHealthIcon(true);
|
||||
|
||||
needsReset = false;
|
||||
}
|
||||
|
||||
@@ -936,7 +923,14 @@ class PlayState extends MusicBeatSubState
|
||||
Conductor.instance.formatOffset = 0.0;
|
||||
}
|
||||
|
||||
Conductor.instance.update(); // Normal conductor update.
|
||||
Conductor.instance.update(Conductor.instance.songPosition + elapsed * 1000, false); // Normal conductor update.
|
||||
|
||||
// If, after updating the conductor, the instrumental has finished, end the song immediately.
|
||||
// This helps prevent a major bug where the level suddenly loops back to the start or middle.
|
||||
if (Conductor.instance.songPosition >= (FlxG.sound.music.endTime ?? FlxG.sound.music.length))
|
||||
{
|
||||
if (mayPauseGame) endSong(skipEndingTransition);
|
||||
}
|
||||
}
|
||||
|
||||
var androidPause:Bool = false;
|
||||
@@ -1053,6 +1047,9 @@ class PlayState extends MusicBeatSubState
|
||||
if (FlxG.sound.music != null) FlxG.sound.music.pause();
|
||||
|
||||
deathCounter += 1;
|
||||
#if FEATURE_NEWGROUNDS
|
||||
Events.logFailSong(currentSong.id, currentVariation);
|
||||
#end
|
||||
|
||||
dispatchEvent(new ScriptEvent(GAME_OVER));
|
||||
|
||||
@@ -1134,8 +1131,8 @@ class PlayState extends MusicBeatSubState
|
||||
|
||||
if (!isMinimalMode)
|
||||
{
|
||||
iconP1.updatePosition();
|
||||
iconP2.updatePosition();
|
||||
if (iconP1 != null) iconP1.updatePosition();
|
||||
if (iconP1 != null) iconP2.updatePosition();
|
||||
}
|
||||
|
||||
// Transition to the game over substate.
|
||||
@@ -1214,7 +1211,7 @@ class PlayState extends MusicBeatSubState
|
||||
{
|
||||
// If there is a substate which requires the game to continue,
|
||||
// then make this a condition.
|
||||
var shouldPause = (Std.isOfType(subState, PauseSubState) || Std.isOfType(subState, GameOverSubState));
|
||||
var shouldPause:Bool = (Std.isOfType(subState, PauseSubState) || Std.isOfType(subState, GameOverSubState));
|
||||
|
||||
if (shouldPause)
|
||||
{
|
||||
@@ -1460,7 +1457,7 @@ class PlayState extends MusicBeatSubState
|
||||
var playerVoicesError:Float = 0;
|
||||
var opponentVoicesError:Float = 0;
|
||||
|
||||
if (vocals != null)
|
||||
if (vocals != null && vocals.playing)
|
||||
{
|
||||
@:privateAccess // todo: maybe make the groups public :thinking:
|
||||
{
|
||||
@@ -1477,7 +1474,9 @@ class PlayState extends MusicBeatSubState
|
||||
}
|
||||
|
||||
if (!startingSong
|
||||
&& (Math.abs(FlxG.sound.music.time - correctSync) > 5 || Math.abs(playerVoicesError) > 5 || Math.abs(opponentVoicesError) > 5))
|
||||
&& (Math.abs(FlxG.sound.music.time - correctSync) > 100
|
||||
|| Math.abs(playerVoicesError) > 100
|
||||
|| Math.abs(opponentVoicesError) > 100))
|
||||
{
|
||||
trace("VOCALS NEED RESYNC");
|
||||
if (vocals != null)
|
||||
@@ -2077,7 +2076,7 @@ class PlayState extends MusicBeatSubState
|
||||
}
|
||||
|
||||
FlxG.sound.music.onComplete = function() {
|
||||
endSong(skipEndingTransition);
|
||||
if (mayPauseGame) 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.
|
||||
@@ -2118,6 +2117,10 @@ class PlayState extends MusicBeatSubState
|
||||
}
|
||||
|
||||
dispatchEvent(new ScriptEvent(SONG_START));
|
||||
|
||||
#if FEATURE_NEWGROUNDS
|
||||
Events.logStartSong(currentSong.id, currentVariation);
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2576,7 +2579,7 @@ class PlayState extends MusicBeatSubState
|
||||
|
||||
Highscore.tallies.totalNotesHit++;
|
||||
// Display the hit on the strums
|
||||
playerStrumline.hitNote(note, !isComboBreak);
|
||||
playerStrumline.hitNote(note, !event.isComboBreak);
|
||||
if (event.doesNotesplash) playerStrumline.playNoteSplash(note.noteData.getDirection());
|
||||
if (note.isHoldNote && note.holdNoteSprite != null) playerStrumline.playNoteHoldCover(note.holdNoteSprite);
|
||||
vocals.playerVolume = 1;
|
||||
@@ -2924,6 +2927,9 @@ class PlayState extends MusicBeatSubState
|
||||
vocals.volume = 0;
|
||||
mayPauseGame = false;
|
||||
|
||||
// Prevent ghost misses while the song is ending.
|
||||
disableKeys = true;
|
||||
|
||||
// Check if any events want to prevent the song from ending.
|
||||
var event = new ScriptEvent(SONG_END, true);
|
||||
dispatchEvent(event);
|
||||
@@ -2971,6 +2977,10 @@ class PlayState extends MusicBeatSubState
|
||||
|
||||
if (!isPracticeMode && !isBotPlayMode)
|
||||
{
|
||||
#if FEATURE_NEWGROUNDS
|
||||
Events.logCompleteSong(currentSong.id, currentVariation);
|
||||
#end
|
||||
|
||||
isNewHighscore = Save.instance.isSongHighScore(currentSong.id, suffixedDifficulty, data);
|
||||
|
||||
// If no high score is present, save both score and rank.
|
||||
@@ -2978,15 +2988,48 @@ class PlayState extends MusicBeatSubState
|
||||
// If neither are higher, nothing will change.
|
||||
Save.instance.applySongRank(currentSong.id, suffixedDifficulty, data);
|
||||
|
||||
if (isNewHighscore)
|
||||
{
|
||||
#if newgrounds
|
||||
NGio.postScore(score, currentSong.id);
|
||||
#end
|
||||
}
|
||||
if (isNewHighscore) {}
|
||||
}
|
||||
}
|
||||
|
||||
#if FEATURE_NEWGROUNDS
|
||||
// Only award medals if we are LEGIT.
|
||||
if (!isPracticeMode && !isBotPlayMode && !isChartingMode && currentSong.validScore)
|
||||
{
|
||||
// Award a medal for beating at least one song on any difficulty on a Friday.
|
||||
if (Date.now().getDay() == 5) Medals.award(FridayNight);
|
||||
|
||||
// Determine the score rank for this song we just finished.
|
||||
var scoreRank:ScoringRank = Scoring.calculateRank(
|
||||
{
|
||||
score: songScore,
|
||||
tallies:
|
||||
{
|
||||
sick: Highscore.tallies.sick,
|
||||
good: Highscore.tallies.good,
|
||||
bad: Highscore.tallies.bad,
|
||||
shit: Highscore.tallies.shit,
|
||||
missed: Highscore.tallies.missed,
|
||||
combo: Highscore.tallies.combo,
|
||||
maxCombo: Highscore.tallies.maxCombo,
|
||||
totalNotesHit: Highscore.tallies.totalNotesHit,
|
||||
totalNotes: Highscore.tallies.totalNotes,
|
||||
}
|
||||
});
|
||||
|
||||
// Award various medals based on variation, difficulty, and scoring rank.
|
||||
if (scoreRank == ScoringRank.SHIT) Medals.award(LossRating);
|
||||
if (scoreRank >= ScoringRank.PERFECT && currentDifficulty == 'hard') Medals.award(PerfectRatingHard);
|
||||
if (scoreRank == ScoringRank.PERFECT_GOLD && currentDifficulty == 'hard') Medals.award(GoldPerfectRatingHard);
|
||||
if (Constants.DEFAULT_DIFFICULTY_LIST_ERECT.contains(currentDifficulty)) Medals.award(ErectDifficulty);
|
||||
if (scoreRank == ScoringRank.PERFECT_GOLD && currentDifficulty == 'nightmare') Medals.award(GoldPerfectRatingNightmare);
|
||||
if (currentVariation == 'pico' && !PlayStatePlaylist.isStoryMode) Medals.award(FreeplayPicoMix);
|
||||
if (currentVariation == 'pico' && currentSong.id == 'stress') Medals.award(FreeplayStressPico);
|
||||
|
||||
Events.logEarnRank(scoreRank.toString());
|
||||
}
|
||||
#end
|
||||
|
||||
if (PlayStatePlaylist.isStoryMode)
|
||||
{
|
||||
isNewHighscore = false;
|
||||
@@ -3001,8 +3044,6 @@ class PlayState extends MusicBeatSubState
|
||||
{
|
||||
if (currentSong.validScore)
|
||||
{
|
||||
NGio.unlockMedal(60961);
|
||||
|
||||
var data =
|
||||
{
|
||||
score: PlayStatePlaylist.campaignScore,
|
||||
@@ -3021,12 +3062,20 @@ class PlayState extends MusicBeatSubState
|
||||
},
|
||||
};
|
||||
|
||||
if (Save.instance.isLevelHighScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data))
|
||||
if (currentSong.validScore
|
||||
&& Save.instance.isLevelHighScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data))
|
||||
{
|
||||
Save.instance.setLevelScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data);
|
||||
#if newgrounds
|
||||
NGio.postScore(score, 'Level ${PlayStatePlaylist.campaignId}');
|
||||
#if FEATURE_NEWGROUNDS
|
||||
// Award a medal for beating a Story level.
|
||||
Medals.awardStoryLevel(PlayStatePlaylist.campaignId);
|
||||
|
||||
// Submit the score for the Story level to Newgrounds.
|
||||
Leaderboards.submitLevelScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, PlayStatePlaylist.campaignScore);
|
||||
|
||||
Events.logCompleteLevel(PlayStatePlaylist.campaignId);
|
||||
#end
|
||||
|
||||
Save.instance.setLevelScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data);
|
||||
isNewHighscore = true;
|
||||
}
|
||||
}
|
||||
@@ -3301,7 +3350,9 @@ class PlayState extends MusicBeatSubState
|
||||
totalNotes: talliesToUse.totalNotes,
|
||||
},
|
||||
},
|
||||
isNewHighscore: isNewHighscore
|
||||
isNewHighscore: isNewHighscore,
|
||||
isPracticeMode: isPracticeMode,
|
||||
isBotPlayMode: isBotPlayMode,
|
||||
});
|
||||
this.persistentDraw = false;
|
||||
openSubState(res);
|
||||
|
||||
@@ -50,12 +50,12 @@ class ResultScore extends FlxTypedSpriteGroup<ScoreNum>
|
||||
|
||||
public function animateNumbers():Void
|
||||
{
|
||||
for (i in group.members.length-scoreStart...group.members.length)
|
||||
for (i in group.members.length - scoreStart...group.members.length)
|
||||
{
|
||||
// if(i.finalDigit == 10) continue;
|
||||
// if(i.finalDigit == 10) continue;
|
||||
|
||||
new FlxTimer().start((i-1)/24, _ -> {
|
||||
group.members[i].finalDelay = scoreStart - (i-1);
|
||||
new FlxTimer().start((i - 1) / 24, _ -> {
|
||||
group.members[i].finalDelay = scoreStart - (i - 1);
|
||||
group.members[i].playAnim();
|
||||
group.members[i].shuffle();
|
||||
});
|
||||
@@ -97,10 +97,13 @@ class ScoreNum extends FlxSprite
|
||||
{
|
||||
if (val >= 0 && animation.curAnim != null && animation.curAnim.name != numToString[val])
|
||||
{
|
||||
if(glow){
|
||||
if (glow)
|
||||
{
|
||||
animation.play(numToString[val], true, false, 0);
|
||||
glow = false;
|
||||
}else{
|
||||
}
|
||||
else
|
||||
{
|
||||
animation.play(numToString[val], true, false, 4);
|
||||
}
|
||||
updateHitbox();
|
||||
@@ -108,18 +111,18 @@ class ScoreNum extends FlxSprite
|
||||
switch (val)
|
||||
{
|
||||
case 1:
|
||||
// offset.x -= 15;
|
||||
// offset.x -= 15;
|
||||
case 5:
|
||||
// set offsets
|
||||
// offset.x += 0;
|
||||
// offset.y += 10;
|
||||
// set offsets
|
||||
// offset.x += 0;
|
||||
// offset.y += 10;
|
||||
|
||||
case 7:
|
||||
// offset.y += 6;
|
||||
// offset.y += 6;
|
||||
case 4:
|
||||
// offset.y += 5;
|
||||
// offset.y += 5;
|
||||
case 9:
|
||||
// offset.y += 5;
|
||||
// offset.y += 5;
|
||||
default:
|
||||
centerOffsets(false);
|
||||
}
|
||||
@@ -144,44 +147,45 @@ class ScoreNum extends FlxSprite
|
||||
"ZERO", "ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN", "EIGHT", "NINE", "DISABLED"
|
||||
];
|
||||
|
||||
function finishShuffleTween():Void{
|
||||
|
||||
function finishShuffleTween():Void
|
||||
{
|
||||
var tweenFunction = function(x) {
|
||||
var digitRounded = Math.floor(x);
|
||||
//if(digitRounded == finalDigit) glow = true;
|
||||
// if(digitRounded == finalDigit) glow = true;
|
||||
digit = digitRounded;
|
||||
};
|
||||
|
||||
finalTween = FlxTween.num(0.0, finalDigit, 23/24, {
|
||||
ease: FlxEase.quadOut,
|
||||
onComplete: function (input) {
|
||||
new FlxTimer().start((finalDelay)/24, _ -> {
|
||||
animation.play(animation.curAnim.name, true, false, 0);
|
||||
});
|
||||
// fuck
|
||||
}
|
||||
}, tweenFunction);
|
||||
finalTween = FlxTween.num(0.0, finalDigit, 23 / 24,
|
||||
{
|
||||
ease: FlxEase.quadOut,
|
||||
onComplete: function(input) {
|
||||
new FlxTimer().start((finalDelay) / 24, _ -> {
|
||||
animation.play(animation.curAnim.name, true, false, 0);
|
||||
});
|
||||
// fuck
|
||||
}
|
||||
}, tweenFunction);
|
||||
}
|
||||
|
||||
|
||||
function shuffleProgress(shuffleTimer:FlxTimer):Void
|
||||
{
|
||||
var tempDigit:Int = digit;
|
||||
tempDigit += 1;
|
||||
if(tempDigit > 9) tempDigit = 0;
|
||||
if(tempDigit < 0) tempDigit = 0;
|
||||
if (tempDigit > 9) tempDigit = 0;
|
||||
if (tempDigit < 0) tempDigit = 0;
|
||||
digit = tempDigit;
|
||||
|
||||
if (shuffleTimer.loops > 0 && shuffleTimer.loopsLeft == 0)
|
||||
{
|
||||
//digit = finalDigit;
|
||||
// digit = finalDigit;
|
||||
finishShuffleTween();
|
||||
}
|
||||
}
|
||||
|
||||
public function shuffle():Void{
|
||||
var duration:Float = 41/24;
|
||||
var interval:Float = 1/24;
|
||||
public function shuffle():Void
|
||||
{
|
||||
var duration:Float = 41 / 24;
|
||||
var interval:Float = 1 / 24;
|
||||
shuffleTimer = new FlxTimer().start(interval, shuffleProgress, Std.int(duration / interval));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package funkin.play;
|
||||
|
||||
import funkin.util.MathUtil;
|
||||
import funkin.ui.story.StoryMenuState;
|
||||
import funkin.graphics.adobeanimate.FlxAtlasSprite;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.FlxState;
|
||||
import flixel.FlxSubState;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import flixel.effects.FlxFlicker;
|
||||
@@ -14,10 +12,8 @@ import flixel.math.FlxPoint;
|
||||
import funkin.ui.MusicBeatSubState;
|
||||
import flixel.math.FlxRect;
|
||||
import flixel.text.FlxBitmapText;
|
||||
import funkin.ui.freeplay.FreeplayScore;
|
||||
import flixel.text.FlxText;
|
||||
import funkin.data.freeplay.player.PlayerRegistry;
|
||||
import funkin.data.freeplay.player.PlayerData;
|
||||
import funkin.data.freeplay.player.PlayerData.PlayerResultsAnimationData;
|
||||
import funkin.ui.freeplay.charselect.PlayableCharacter;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.tweens.FlxEase;
|
||||
@@ -28,12 +24,14 @@ import flixel.addons.display.FlxBackdrop;
|
||||
import funkin.audio.FunkinSound;
|
||||
import flixel.util.FlxGradient;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.save.Save;
|
||||
import funkin.play.scoring.Scoring;
|
||||
import funkin.save.Save.SaveScoreData;
|
||||
import funkin.graphics.shaders.LeftMaskShader;
|
||||
import funkin.play.components.TallyCounter;
|
||||
import funkin.play.components.ClearPercentCounter;
|
||||
#if FEATURE_NEWGROUNDS
|
||||
import funkin.api.newgrounds.Medals;
|
||||
#end
|
||||
|
||||
/**
|
||||
* The state for the results screen after a song or week is finished.
|
||||
@@ -64,7 +62,9 @@ class ResultState extends MusicBeatSubState
|
||||
{
|
||||
sprite:FlxAtlasSprite,
|
||||
delay:Float,
|
||||
forceLoop:Bool
|
||||
forceLoop:Bool,
|
||||
startFrameLabel:String,
|
||||
sound:String
|
||||
}> = [];
|
||||
var characterSparrowAnimations:Array<
|
||||
{
|
||||
@@ -181,6 +181,11 @@ class ResultState extends MusicBeatSubState
|
||||
{
|
||||
if (animData == null) continue;
|
||||
|
||||
if (animData.filter != "both")
|
||||
{
|
||||
if (Preferences.naughtyness && animData.filter != "naughty" || !Preferences.naughtyness && animData.filter != "safe") continue;
|
||||
}
|
||||
|
||||
var animPath:String = Paths.stripLibrary(animData.assetPath);
|
||||
var animLibrary:String = Paths.getLibrary(animData.assetPath);
|
||||
var offsets = animData.offsets ?? [0, 0];
|
||||
@@ -196,7 +201,6 @@ class ResultState extends MusicBeatSubState
|
||||
{
|
||||
// Animation is not looped.
|
||||
animation.onAnimationComplete.add((_name:String) -> {
|
||||
trace("AHAHAH 2");
|
||||
if (animation != null)
|
||||
{
|
||||
animation.anim.pause();
|
||||
@@ -206,7 +210,6 @@ class ResultState extends MusicBeatSubState
|
||||
else if (animData.loopFrameLabel != null)
|
||||
{
|
||||
animation.onAnimationComplete.add((_name:String) -> {
|
||||
trace("AHAHAH 2");
|
||||
if (animation != null)
|
||||
{
|
||||
animation.playAnimation(animData.loopFrameLabel ?? '', true, false, true); // unpauses this anim, since it's on PlayOnce!
|
||||
@@ -224,7 +227,6 @@ class ResultState extends MusicBeatSubState
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Hide until ready to play.
|
||||
animation.visible = false;
|
||||
// Queue to play.
|
||||
@@ -232,7 +234,9 @@ class ResultState extends MusicBeatSubState
|
||||
{
|
||||
sprite: animation,
|
||||
delay: animData.delay ?? 0.0,
|
||||
forceLoop: (animData.loopFrame ?? -1) == 0
|
||||
forceLoop: (animData.loopFrame ?? -1) == 0,
|
||||
startFrameLabel: (animData.startFrameLabel ?? ""),
|
||||
sound: (animData.sound ?? "")
|
||||
});
|
||||
// Add to the scene.
|
||||
add(animation);
|
||||
@@ -278,8 +282,7 @@ class ResultState extends MusicBeatSubState
|
||||
songName.shader = maskShaderSongName;
|
||||
difficulty.shader = maskShaderDifficulty;
|
||||
|
||||
// maskShaderSongName.swagMaskX = difficulty.x - 15;
|
||||
maskShaderDifficulty.swagMaskX = difficulty.x - 15;
|
||||
maskShaderDifficulty.swagMaskX = difficulty.x - 30;
|
||||
|
||||
var blackTopBar:FlxSprite = new FlxSprite().loadGraphic(Paths.image("resultScreen/topBarBlack"));
|
||||
blackTopBar.y = -blackTopBar.height;
|
||||
@@ -366,6 +369,12 @@ class ResultState extends MusicBeatSubState
|
||||
var maxCombo:TallyCounter = new TallyCounter(375, hStuf * 4, params.scoreData.tallies.maxCombo);
|
||||
ratingGrp.add(maxCombo);
|
||||
|
||||
if (params.scoreData.tallies.totalNotesHit >= 1000)
|
||||
{
|
||||
totalHit.x -= 30;
|
||||
maxCombo.x -= 30;
|
||||
}
|
||||
|
||||
hStuf += 2;
|
||||
var extraYOffset:Float = 7;
|
||||
|
||||
@@ -492,6 +501,11 @@ class ResultState extends MusicBeatSubState
|
||||
// Just to be sure that the lerp didn't mess things up.
|
||||
clearPercentCounter.curNumber = clearPercentTarget;
|
||||
|
||||
#if FEATURE_NEWGROUNDS
|
||||
// This is the easiest spot to do the medal calculation lol.
|
||||
if (clearPercentTarget == 69) Medals.award(Nice);
|
||||
#end
|
||||
|
||||
clearPercentCounter.flash(true);
|
||||
new FlxTimer().start(0.4, _ -> {
|
||||
clearPercentCounter.flash(false);
|
||||
@@ -591,7 +605,14 @@ class ResultState extends MusicBeatSubState
|
||||
new FlxTimer().start(atlas.delay, _ -> {
|
||||
if (atlas.sprite == null) return;
|
||||
atlas.sprite.visible = true;
|
||||
atlas.sprite.playAnimation('');
|
||||
atlas.sprite.playAnimation(atlas.startFrameLabel);
|
||||
if (atlas.sound != "")
|
||||
{
|
||||
var sndPath:String = Paths.stripLibrary(atlas.sound);
|
||||
var sndLibrary:String = Paths.getLibrary(atlas.sound);
|
||||
|
||||
FunkinSound.playOnce(Paths.sound(sndPath, sndLibrary), 1.0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -721,28 +742,26 @@ class ResultState extends MusicBeatSubState
|
||||
}
|
||||
}
|
||||
|
||||
if (FlxG.keys.justPressed.RIGHT) speedOfTween.x += 0.1;
|
||||
|
||||
if (FlxG.keys.justPressed.LEFT)
|
||||
if (controls.PAUSE || controls.ACCEPT)
|
||||
{
|
||||
speedOfTween.x -= 0.1;
|
||||
}
|
||||
|
||||
if (controls.PAUSE)
|
||||
{
|
||||
if (introMusicAudio != null) {
|
||||
if (_parentState is funkin.ui.debug.results.ResultsDebugSubState)
|
||||
close(); // IF we are a substate, we will close ourselves. This is used from ResultsDebugSubState
|
||||
if (introMusicAudio != null)
|
||||
{
|
||||
@:nullSafety(Off)
|
||||
introMusicAudio.onComplete = null;
|
||||
|
||||
FlxTween.tween(introMusicAudio, {volume: 0}, 0.8, {
|
||||
onComplete: _ -> {
|
||||
if (introMusicAudio != null) {
|
||||
introMusicAudio.stop();
|
||||
introMusicAudio.destroy();
|
||||
introMusicAudio = null;
|
||||
FlxTween.tween(introMusicAudio, {volume: 0}, 0.8,
|
||||
{
|
||||
onComplete: _ -> {
|
||||
if (introMusicAudio != null)
|
||||
{
|
||||
introMusicAudio.stop();
|
||||
introMusicAudio.destroy();
|
||||
introMusicAudio = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
FlxTween.tween(introMusicAudio, {pitch: 3}, 0.1,
|
||||
{
|
||||
onComplete: _ -> {
|
||||
@@ -752,12 +771,13 @@ class ResultState extends MusicBeatSubState
|
||||
}
|
||||
else if (FlxG.sound.music != null)
|
||||
{
|
||||
FlxTween.tween(FlxG.sound.music, {volume: 0}, 0.8, {
|
||||
onComplete: _ -> {
|
||||
FlxG.sound.music.stop();
|
||||
FlxG.sound.music.destroy();
|
||||
}
|
||||
});
|
||||
FlxTween.tween(FlxG.sound.music, {volume: 0}, 0.8,
|
||||
{
|
||||
onComplete: _ -> {
|
||||
FlxG.sound.music.stop();
|
||||
FlxG.sound.music.destroy();
|
||||
}
|
||||
});
|
||||
FlxTween.tween(FlxG.sound.music, {pitch: 3}, 0.1,
|
||||
{
|
||||
onComplete: _ -> {
|
||||
@@ -772,6 +792,14 @@ class ResultState extends MusicBeatSubState
|
||||
var shouldTween = false;
|
||||
var shouldUseSubstate = false;
|
||||
|
||||
var stickerSet = (playerCharacterId == "pico") ? "stickers-set-2" : "stickers-set-1";
|
||||
var stickerPack = switch (PlayState.instance?.currentChart?.song?.id)
|
||||
{
|
||||
case "tutorial": "tutorial";
|
||||
case "darnell" | "lit-up" | "2hot": "weekend";
|
||||
default: "all";
|
||||
};
|
||||
|
||||
if (params.storyMode)
|
||||
{
|
||||
if (PlayerRegistry.instance.hasNewCharacter())
|
||||
@@ -793,12 +821,21 @@ class ResultState extends MusicBeatSubState
|
||||
// No new characters.
|
||||
shouldTween = false;
|
||||
shouldUseSubstate = true;
|
||||
targetState = new funkin.ui.transition.StickerSubState(null, (sticker) -> new StoryMenuState(sticker));
|
||||
targetState = new funkin.ui.transition.StickerSubState(
|
||||
{
|
||||
targetState: (sticker) -> new StoryMenuState(sticker),
|
||||
stickerSet: stickerSet,
|
||||
stickerPack: stickerPack
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rank > Scoring.calculateRank(params?.prevScoreData))
|
||||
var isScoreValid = !(params?.isPracticeMode ?? false) && !(params?.isBotPlayMode ?? false);
|
||||
|
||||
var isPersonalBest = rank > Scoring.calculateRank(params?.prevScoreData);
|
||||
|
||||
if (isScoreValid && isPersonalBest)
|
||||
{
|
||||
trace('THE RANK IS Higher.....');
|
||||
|
||||
@@ -822,7 +859,12 @@ class ResultState extends MusicBeatSubState
|
||||
{
|
||||
shouldTween = false;
|
||||
shouldUseSubstate = true;
|
||||
targetState = new funkin.ui.transition.StickerSubState(null, (sticker) -> FreeplayState.build(null, sticker));
|
||||
targetState = new funkin.ui.transition.StickerSubState(
|
||||
{
|
||||
targetState: (sticker) -> FreeplayState.build(null, sticker),
|
||||
stickerSet: stickerSet,
|
||||
stickerPack: stickerPack
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -889,6 +931,16 @@ typedef ResultsStateParams =
|
||||
*/
|
||||
var ?isNewHighscore:Bool;
|
||||
|
||||
/**
|
||||
* Whether the displayed score is from a song played with Practice Mode enabled.
|
||||
*/
|
||||
var ?isPracticeMode:Bool;
|
||||
|
||||
/**
|
||||
* Whether the displayed score is from a song played with Bot Play Mode enabled.
|
||||
*/
|
||||
var ?isBotPlayMode:Bool;
|
||||
|
||||
/**
|
||||
* The difficulty ID of the song/week we just played.
|
||||
* @default Normal
|
||||
|
||||
@@ -16,6 +16,7 @@ import flixel.util.FlxDestroyUtil;
|
||||
import funkin.graphics.adobeanimate.FlxAtlasSprite;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.play.character.CharacterData.CharacterRenderType;
|
||||
import flixel.util.FlxDirectionFlags;
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.display.BlendMode;
|
||||
|
||||
@@ -74,10 +75,14 @@ class AnimateAtlasCharacter extends BaseCharacter
|
||||
|
||||
override function onCreate(event:ScriptEvent):Void
|
||||
{
|
||||
trace('Creating Animate Atlas character: ' + this.characterId);
|
||||
// Display a custom scope for debugging purposes.
|
||||
#if FEATURE_DEBUG_TRACY
|
||||
cpp.vm.tracy.TracyProfiler.zoneScoped('AnimateAtlasCharacter.create(${this.characterId})');
|
||||
#end
|
||||
|
||||
try
|
||||
{
|
||||
trace('Loading assets for Animate Atlas character "${characterId}"', flixel.util.FlxColor.fromString("#89CFF0"));
|
||||
var atlasSprite:FlxAtlasSprite = loadAtlasSprite();
|
||||
setSprite(atlasSprite);
|
||||
|
||||
@@ -93,12 +98,10 @@ class AnimateAtlasCharacter extends BaseCharacter
|
||||
|
||||
public override function playAnimation(name:String, restart:Bool = false, ignoreOther:Bool = false, reverse:Bool = false):Void
|
||||
{
|
||||
if ((!canPlayOtherAnims && !ignoreOther)) return;
|
||||
|
||||
var correctName = correctAnimationName(name);
|
||||
if (correctName == null)
|
||||
{
|
||||
trace('Could not find Atlas animation: ' + name);
|
||||
trace('$characterName Could not find Atlas animation: ' + name);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -145,9 +148,16 @@ class AnimateAtlasCharacter extends BaseCharacter
|
||||
{
|
||||
super.onAnimationFinished(prefix);
|
||||
|
||||
if (!getCurrentAnimation().endsWith(Constants.ANIMATION_HOLD_SUFFIX)
|
||||
&& hasAnimation(getCurrentAnimation() + Constants.ANIMATION_HOLD_SUFFIX))
|
||||
{
|
||||
playAnimation(getCurrentAnimation() + Constants.ANIMATION_HOLD_SUFFIX);
|
||||
}
|
||||
|
||||
if (getAnimationData() != null && getAnimationData().looped)
|
||||
{
|
||||
playAnimation(currentAnimName, true, false);
|
||||
if (StringTools.endsWith(prefix, "-hold")) trace(prefix);
|
||||
playAnimation(prefix, true, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -380,7 +390,7 @@ class AnimateAtlasCharacter extends BaseCharacter
|
||||
inline function directAlphaTransform(sprite:FlxSprite, alpha:Float):Void
|
||||
sprite.alpha = alpha; // direct set
|
||||
|
||||
inline function facingTransform(sprite:FlxSprite, facing:Int):Void
|
||||
inline function facingTransform(sprite:FlxSprite, facing:FlxDirectionFlags):Void
|
||||
sprite.facing = facing;
|
||||
|
||||
inline function flipXTransform(sprite:FlxSprite, flipX:Bool):Void
|
||||
@@ -449,6 +459,57 @@ class AnimateAtlasCharacter extends BaseCharacter
|
||||
}
|
||||
}
|
||||
|
||||
var resS:FlxPoint = new FlxPoint();
|
||||
|
||||
/**
|
||||
* Reset the character so it can be used at the start of the level.
|
||||
* Call this when restarting the level.
|
||||
*/
|
||||
override public function resetCharacter(resetCamera:Bool = true):Void
|
||||
{
|
||||
trace("RESETTING ATLAS " + characterName);
|
||||
|
||||
// Reset the animation offsets. This will modify x and y to be the absolute position of the character.
|
||||
// this.animOffsets = [0, 0];
|
||||
|
||||
// Now we can set the x and y to be their original values without having to account for animOffsets.
|
||||
this.resetPosition();
|
||||
mainSprite.setPosition(originalPosition.x, originalPosition.y);
|
||||
|
||||
// Then reapply animOffsets...
|
||||
// applyAnimationOffsets(getCurrentAnimation());
|
||||
|
||||
// Make sure we are playing the idle animation
|
||||
// ...then update the hitbox so that this.width and this.height are correct.
|
||||
|
||||
mainSprite.scale.set(1, 1);
|
||||
mainSprite.alpha = 0.0001;
|
||||
mainSprite.width = 0;
|
||||
mainSprite.height = 0;
|
||||
this.dance(true); // Force to avoid the old animation playing with the wrong offset at the start of the song.
|
||||
|
||||
mainSprite.draw(); // refresh frame
|
||||
|
||||
if (resS.x == 0)
|
||||
{
|
||||
resS.x = mainSprite.width; // clunky bizz
|
||||
resS.y = mainSprite.height;
|
||||
}
|
||||
|
||||
mainSprite.alpha = alpha;
|
||||
|
||||
mainSprite.width = resS.x;
|
||||
mainSprite.height = resS.y;
|
||||
frameWidth = 0;
|
||||
frameHeight = 0;
|
||||
|
||||
scaleCallback(scale);
|
||||
this.updateHitbox();
|
||||
|
||||
// Reset the camera focus point while we're at it.
|
||||
if (resetCamera) this.resetCameraFocusPoint();
|
||||
}
|
||||
|
||||
inline function offsetCallback(offset:FlxPoint):Void
|
||||
transformChildren(offsetTransform, offset);
|
||||
|
||||
@@ -528,7 +589,7 @@ class AnimateAtlasCharacter extends BaseCharacter
|
||||
return alpha = value;
|
||||
}
|
||||
|
||||
override function set_facing(value:Int):Int
|
||||
override function set_facing(value:FlxDirectionFlags):FlxDirectionFlags
|
||||
{
|
||||
if (exists && facing != value) transformChildren(facingTransform, value);
|
||||
return facing = value;
|
||||
|
||||
@@ -149,6 +149,7 @@ class BaseCharacter extends Bopper
|
||||
public function new(id:String, renderType:CharacterRenderType)
|
||||
{
|
||||
super(CharacterDataParser.DEFAULT_DANCEEVERY);
|
||||
|
||||
this.characterId = id;
|
||||
|
||||
ignoreExclusionPref = ["sing"];
|
||||
@@ -643,6 +644,11 @@ class BaseCharacter extends Bopper
|
||||
{
|
||||
super.playAnimation(name, restart, ignoreOther, reversed);
|
||||
}
|
||||
|
||||
public function getDeathQuote():Null<String>
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,7 +26,10 @@ class MultiSparrowCharacter extends BaseCharacter
|
||||
|
||||
override function onCreate(event:ScriptEvent):Void
|
||||
{
|
||||
trace('Creating Multi-Sparrow character: ' + this.characterId);
|
||||
// Display a custom scope for debugging purposes.
|
||||
#if FEATURE_DEBUG_TRACY
|
||||
cpp.vm.tracy.TracyProfiler.zoneScoped('MultiSparrowCharacter.create(${this.characterId})');
|
||||
#end
|
||||
|
||||
buildSprites();
|
||||
super.onCreate(event);
|
||||
@@ -41,8 +44,8 @@ class MultiSparrowCharacter extends BaseCharacter
|
||||
{
|
||||
this.isPixel = true;
|
||||
this.antialiasing = false;
|
||||
pixelPerfectRender = true;
|
||||
pixelPerfectPosition = true;
|
||||
// pixelPerfectRender = true;
|
||||
// pixelPerfectPosition = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -53,6 +56,8 @@ class MultiSparrowCharacter extends BaseCharacter
|
||||
|
||||
function buildSpritesheet():Void
|
||||
{
|
||||
trace('Loading assets for Multi-Sparrow character "${characterId}"', flixel.util.FlxColor.fromString("#89CFF0"));
|
||||
|
||||
var assetList = [];
|
||||
for (anim in _data.animations)
|
||||
{
|
||||
@@ -123,10 +128,6 @@ class MultiSparrowCharacter extends BaseCharacter
|
||||
|
||||
public override function playAnimation(name:String, restart:Bool = false, ignoreOther:Bool = false, reverse:Bool = false):Void
|
||||
{
|
||||
// Make sure we ignore other animations if we're currently playing a forced one,
|
||||
// unless we're forcing a new animation.
|
||||
if (!this.canPlayOtherAnims && !ignoreOther) return;
|
||||
|
||||
super.playAnimation(name, restart, ignoreOther, reverse);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,10 @@ class PackerCharacter extends BaseCharacter
|
||||
|
||||
override function onCreate(event:ScriptEvent):Void
|
||||
{
|
||||
trace('Creating Packer character: ' + this.characterId);
|
||||
// Display a custom scope for debugging purposes.
|
||||
#if FEATURE_DEBUG_TRACY
|
||||
cpp.vm.tracy.TracyProfiler.zoneScoped('PackerCharacter.create(${this.characterId})');
|
||||
#end
|
||||
|
||||
loadSpritesheet();
|
||||
loadAnimations();
|
||||
@@ -28,7 +31,7 @@ class PackerCharacter extends BaseCharacter
|
||||
|
||||
function loadSpritesheet():Void
|
||||
{
|
||||
trace('[PACKERCHAR] Loading spritesheet ${_data.assetPath} for ${characterId}');
|
||||
trace('Loading assets for Packer character "${characterId}"', flixel.util.FlxColor.fromString("#89CFF0"));
|
||||
|
||||
var tex:FlxFramesCollection = Paths.getPackerAtlas(_data.assetPath);
|
||||
if (tex == null)
|
||||
@@ -43,8 +46,8 @@ class PackerCharacter extends BaseCharacter
|
||||
{
|
||||
this.isPixel = true;
|
||||
this.antialiasing = false;
|
||||
pixelPerfectRender = true;
|
||||
pixelPerfectPosition = true;
|
||||
// pixelPerfectRender = true;
|
||||
// pixelPerfectPosition = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -21,7 +21,10 @@ class SparrowCharacter extends BaseCharacter
|
||||
|
||||
override function onCreate(event:ScriptEvent):Void
|
||||
{
|
||||
trace('Creating Sparrow character: ' + this.characterId);
|
||||
// Display a custom scope for debugging purposes.
|
||||
#if FEATURE_DEBUG_TRACY
|
||||
cpp.vm.tracy.TracyProfiler.zoneScoped('SparrowCharacter.create(${this.characterId})');
|
||||
#end
|
||||
|
||||
loadSpritesheet();
|
||||
loadAnimations();
|
||||
@@ -31,7 +34,7 @@ class SparrowCharacter extends BaseCharacter
|
||||
|
||||
function loadSpritesheet()
|
||||
{
|
||||
trace('[SPARROWCHAR] Loading spritesheet ${_data.assetPath} for ${characterId}');
|
||||
trace('Loading assets for Sparrow character "${characterId}"', flixel.util.FlxColor.fromString("#89CFF0"));
|
||||
|
||||
var tex:FlxFramesCollection = Paths.getSparrowAtlas(_data.assetPath);
|
||||
if (tex == null)
|
||||
@@ -46,8 +49,8 @@ class SparrowCharacter extends BaseCharacter
|
||||
{
|
||||
this.isPixel = true;
|
||||
this.antialiasing = false;
|
||||
pixelPerfectRender = true;
|
||||
pixelPerfectPosition = true;
|
||||
// pixelPerfectRender = true;
|
||||
// pixelPerfectPosition = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -3,13 +3,7 @@ package funkin.play.components;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.graphics.shaders.PureColor;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
||||
import flixel.math.FlxMath;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.text.FlxText.FlxTextAlign;
|
||||
import funkin.util.MathUtil;
|
||||
import flixel.util.FlxColor;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
package funkin.play.components;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.util.FlxDirection;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.util.TimerUtil;
|
||||
import funkin.util.EaseUtil;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
|
||||
@:nullSafety
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user