Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61a7978020 | ||
|
|
b5c5693d4d | ||
|
|
d6db842efb | ||
|
|
ec9ec2031a | ||
|
|
2383fdfade | ||
|
|
36d7de5692 | ||
|
|
7f8937d540 | ||
|
|
4ce83b2ac5 | ||
|
|
517359914b | ||
|
|
be08bbec58 | ||
|
|
9e13ea0b2c | ||
|
|
79188b06f4 | ||
|
|
c0b15ec066 | ||
|
|
09b42f3f08 | ||
|
|
3746545452 | ||
|
|
b4c48893bc | ||
|
|
a42240e6a5 | ||
|
|
91b4544f7e | ||
|
|
9c8ba2c053 | ||
|
|
19abcb5009 | ||
|
|
1189c226ee | ||
|
|
097dbf5bb4 | ||
|
|
8890cbf270 | ||
|
|
623985a2cc | ||
|
|
8cd7f2a5b1 | ||
|
|
0a5419d7fc | ||
|
|
b2647fe09f | ||
|
|
e66290c55f | ||
|
|
31d1b3e246 | ||
|
|
880615e707 | ||
|
|
7d5d80d515 | ||
|
|
6848815e80 | ||
|
|
5d10bb9f54 | ||
|
|
f6a5a9d8ce | ||
|
|
9695e79134 | ||
|
|
3267c61258 | ||
|
|
903b3fc599 | ||
|
|
0d60929f18 | ||
|
|
bc546e86aa | ||
|
|
de5cca1120 | ||
|
|
3aa4591bab | ||
|
|
bc8f506193 | ||
|
|
f52472a476 | ||
|
|
ab5bda3ee5 | ||
|
|
777978f5a5 | ||
|
|
711e0a6b75 | ||
|
|
1fde59f999 | ||
|
|
165ad60155 | ||
|
|
d2e29879fe | ||
|
|
4fa9a0daaa | ||
|
|
c0314c85ec | ||
|
|
f862fb2c3e | ||
|
|
d7f5070317 | ||
|
|
1d2bd61119 | ||
|
|
a4ed55721f | ||
|
|
f1b6e6c4e4 | ||
|
|
29b6763290 | ||
|
|
410cfe972d | ||
|
|
39b1a42cfe | ||
|
|
b0b73c8399 | ||
|
|
5141ac13df | ||
|
|
45a5160b06 | ||
|
|
89ee33e9b9 | ||
|
|
4b54118731 | ||
|
|
dbf66ac250 | ||
|
|
79eb7d633c | ||
|
|
f61789e453 | ||
|
|
1da0da249d | ||
|
|
6cd1dec3d4 | ||
|
|
2175beaa65 | ||
|
|
6df80ba69d | ||
|
|
67f7b638fb | ||
|
|
399869cdcc | ||
|
|
c5a76cf1d5 | ||
|
|
30a9887136 | ||
|
|
9324359d2f | ||
|
|
4a9ad53695 | ||
|
|
5d013a1aa7 | ||
|
|
0971a0bb4c | ||
|
|
136e7358b1 | ||
|
|
84ff83759f | ||
|
|
9d3131bc8d | ||
|
|
90f6f80a71 | ||
|
|
e3ed153a7c | ||
|
|
66781181fa | ||
|
|
93039c3d65 | ||
|
|
a1370157a4 | ||
|
|
7ce7d7839e | ||
|
|
042dfb7442 | ||
|
|
7a3d983bba | ||
|
|
ac3e2b2560 | ||
|
|
db0944712d | ||
|
|
fb1b3cc1cd | ||
|
|
007a32dc9c | ||
|
|
9a57762a9c | ||
|
|
06fee5599c | ||
|
|
cd2b458da2 | ||
|
|
8a8160dfe7 | ||
|
|
5630e74584 | ||
|
|
067f52ca91 | ||
|
|
2eb5208c46 | ||
|
|
b5f3996236 | ||
|
|
378179d1f7 | ||
|
|
f1c1e63375 | ||
|
|
ec749c7157 | ||
|
|
e321861b9f | ||
|
|
c4ed31b246 | ||
|
|
2053f0d691 | ||
|
|
ffd76963bd | ||
|
|
4669ac03a9 | ||
|
|
cb16a8d55e | ||
|
|
e1aa25a249 | ||
|
|
67126b33fa | ||
|
|
a84ae6069e | ||
|
|
c3f982b32c | ||
|
|
67bce85343 | ||
|
|
998bbb213a | ||
|
|
8d02a3f984 | ||
|
|
85763813ae | ||
|
|
b29296288e | ||
|
|
5693d626b5 | ||
|
|
84d8c02920 | ||
|
|
ec9b5db210 | ||
|
|
7e75308423 | ||
|
|
073dbaa023 | ||
|
|
f6697f8ddc | ||
|
|
78d0ad9956 | ||
|
|
52f5c24f96 | ||
|
|
252b87c451 | ||
|
|
0389cd6760 | ||
|
|
29c860ed43 | ||
|
|
a86cfded2a | ||
|
|
3d5482660c | ||
|
|
299f4840cf | ||
|
|
8749372725 | ||
|
|
50f65177cb | ||
|
|
1c2a403dea | ||
|
|
84ce43eff7 | ||
|
|
cca997b724 | ||
|
|
298bc9496b | ||
|
|
2edb318901 | ||
|
|
15bce04a50 | ||
|
|
5c6d72d8d5 | ||
|
|
1937529c1f | ||
|
|
0bf1bead03 | ||
|
|
58508755f9 | ||
|
|
bcb7bc4cbc | ||
|
|
28d50a4567 | ||
|
|
6a113addca | ||
|
|
c7238fcfd1 | ||
|
|
29a7005d8a | ||
|
|
d23ae7c55b | ||
|
|
d11acfd83b | ||
|
|
754e2edbeb | ||
|
|
5548fd52f4 | ||
|
|
6c743b3b89 | ||
|
|
e978f5df76 | ||
|
|
3601b50e32 | ||
|
|
e535ae4ca6 | ||
|
|
ec8ecc6163 | ||
|
|
01e9888839 | ||
|
|
2c73b241e8 | ||
|
|
3ef238a2f6 | ||
|
|
690f3090b7 | ||
|
|
a41d3033d5 | ||
|
|
d02e834460 | ||
|
|
2a6eadcbff | ||
|
|
78cde16af2 | ||
|
|
4946e7f4c7 | ||
|
|
8a9b1b4727 | ||
|
|
5f64d89d0e | ||
|
|
143ef88e6f | ||
|
|
cb4ac352e6 | ||
|
|
ccb2e1889f | ||
|
|
9045ae2d9c | ||
|
|
e3a13bee8c | ||
|
|
426bff3bf5 | ||
|
|
249ff69f9f | ||
|
|
4af4c2defc | ||
|
|
76bf5df57f | ||
|
|
c46483722e | ||
|
|
69bcd0834e | ||
|
|
29e89c8acb | ||
|
|
778ce54047 | ||
|
|
47ed9d7fb2 | ||
|
|
00c1428c0f | ||
|
|
0b908b3cab | ||
|
|
50de041251 | ||
|
|
f9186b67a7 | ||
|
|
a8aeae4f7f | ||
|
|
4d73e0cc47 | ||
|
|
b03c8a9cf8 | ||
|
|
3cbba8ba29 | ||
|
|
bfee63f588 | ||
|
|
35aa8731d0 | ||
|
|
8fd84f9d13 | ||
|
|
25cafe7550 | ||
|
|
7d6fa19ff4 | ||
|
|
9f0bc2bd52 | ||
|
|
d81e09b1c3 | ||
|
|
a128afedf7 | ||
|
|
472d41a893 | ||
|
|
215e20cabe | ||
|
|
3c30575727 | ||
|
|
f30c0cff49 | ||
|
|
fc6e194d20 | ||
|
|
35005df32a | ||
|
|
62149aea39 | ||
|
|
fdddfba3be | ||
|
|
ab3b976f3c | ||
|
|
e02bb9b21c | ||
|
|
afcb57aaf0 | ||
|
|
99657da1f9 | ||
|
|
3bb981adb0 | ||
|
|
50a5304ad4 | ||
|
|
a4b59d14bf | ||
|
|
9d5cde0bf1 | ||
|
|
5efbff044b | ||
|
|
a469385cdb | ||
|
|
970816592f | ||
|
|
ba90a6abe0 | ||
|
|
d54dc6179a | ||
|
|
b5ed7f40d4 | ||
|
|
b28af79eb3 | ||
|
|
5f563f0555 | ||
|
|
1beb21e57a | ||
|
|
b6b2248aa3 | ||
|
|
d30fe61ca0 | ||
|
|
7b7d5deed5 | ||
|
|
19e6029fb2 | ||
|
|
2a5269c2a7 | ||
|
|
f7fb640e52 | ||
|
|
119148acf0 | ||
|
|
598a3bea24 | ||
|
|
b03d320c12 | ||
|
|
61d23b9985 | ||
|
|
b70983a0e4 | ||
|
|
de07497ae3 | ||
|
|
ea9dab4b73 | ||
|
|
f2b3f47e73 | ||
|
|
6e33cac9ec | ||
|
|
dab417b391 | ||
|
|
0706192cbe | ||
|
|
7fbbc5cf95 | ||
|
|
4939b09fb0 | ||
|
|
aae7dcdada | ||
|
|
e6525d0129 | ||
|
|
6efdbde8da | ||
|
|
b7c3f5ac9f | ||
|
|
811caf6b70 | ||
|
|
d710318830 | ||
|
|
1fc835494d | ||
|
|
2e9223ec72 | ||
|
|
9624b57e44 | ||
|
|
7d2b946e9c | ||
|
|
5a877a14a0 | ||
|
|
535ac0e528 | ||
|
|
35b5957715 | ||
|
|
e915b637c5 | ||
|
|
f38a95bbf7 | ||
|
|
ebfce9f990 | ||
|
|
359e4b41e5 | ||
|
|
b0c9365c56 | ||
|
|
65fddd95bb | ||
|
|
f1d3ace247 | ||
|
|
9e90e67673 | ||
|
|
3ce7cd6990 | ||
|
|
d4cd2d031c | ||
|
|
081d2d8fd8 | ||
|
|
99f4d13e47 | ||
|
|
8cad10263e | ||
|
|
8470880e4a | ||
|
|
6bde7c34a0 | ||
|
|
3ac10fb45c | ||
|
|
fb0b277a9c | ||
|
|
519a6f3412 | ||
|
|
27f700dd98 | ||
|
|
f015976521 | ||
|
|
265039b025 | ||
|
|
8c6cb72d47 | ||
|
|
b3221c0e3f | ||
|
|
c7cb03dd1d | ||
|
|
a7c0512503 | ||
|
|
74a94d0080 | ||
|
|
d214ae1747 | ||
|
|
6ee1bddacb | ||
|
|
8b96d1bbac | ||
|
|
d4e11f786c | ||
|
|
724c415016 | ||
|
|
055ae5b932 | ||
|
|
85e9dbb959 | ||
|
|
c5060b6eec | ||
|
|
df77d11896 | ||
|
|
aa2d934f22 | ||
|
|
3f6ad6f8ad | ||
|
|
45efd78931 | ||
|
|
b95e228322 | ||
|
|
c9e2e7bef5 | ||
|
|
bab4ac4902 | ||
|
|
b8d1341a21 | ||
|
|
55e0970e4c | ||
|
|
82c2d54079 | ||
|
|
c4b475e86a | ||
|
|
9fddb727b8 | ||
|
|
224d930ae6 | ||
|
|
3b9d400f57 | ||
|
|
f8d0ddef34 | ||
|
|
18edadb712 | ||
|
|
80fc0cd526 | ||
|
|
e7674ec31c | ||
|
|
0b531c5b21 | ||
|
|
8efcd1eae1 | ||
|
|
06485518db | ||
|
|
57523c9439 | ||
|
|
42544ee757 | ||
|
|
4be4570c94 | ||
|
|
d0ec26c44a | ||
|
|
8252b2a6b3 | ||
|
|
a5ac717677 | ||
|
|
ea2e6506c8 | ||
|
|
1e8d3c715b | ||
|
|
4bc2510d3c | ||
|
|
0574f8b07b | ||
|
|
11224c5999 | ||
|
|
367e3e9008 | ||
|
|
a27c4ae24f | ||
|
|
1f21e1114b | ||
|
|
16aff6e7c3 | ||
|
|
9f3af1ab94 | ||
|
|
e23a85682a | ||
|
|
fd6ca6e267 | ||
|
|
d5c7e4ffdb | ||
|
|
688abb5571 | ||
|
|
0a8719088d | ||
|
|
df87d677e1 | ||
|
|
517984d120 | ||
|
|
3fba55a422 | ||
|
|
178389ea0b | ||
|
|
16905c8210 | ||
|
|
77346696f8 | ||
|
|
df266e66d3 | ||
|
|
00c7767f8a | ||
|
|
c62b53ebe9 | ||
|
|
d43042fd36 | ||
|
|
964f6878c3 | ||
|
|
8778ab1d0e | ||
|
|
59ad15aee4 | ||
|
|
c8caeb42de | ||
|
|
ba96b11196 | ||
|
|
ff1ab1eb42 | ||
|
|
4d81aa0fe6 | ||
|
|
691da783fc | ||
|
|
a93cd05aeb | ||
|
|
ee44981995 | ||
|
|
5e1d25e791 | ||
|
|
f8a0627fd2 | ||
|
|
f8232911cb | ||
|
|
aed100df47 | ||
|
|
9d3c043f8a | ||
|
|
2547fbb8f4 | ||
|
|
2993e17278 | ||
|
|
0c5815c176 | ||
|
|
97e69c6343 | ||
|
|
51c304a6fa | ||
|
|
47a2cf4e5d | ||
|
|
a12b0e6ec3 | ||
|
|
2be22055cc | ||
|
|
2fb55ba3ed | ||
|
|
9ad2bb35f9 | ||
|
|
481f7ab61a | ||
|
|
717894300d | ||
|
|
f82f0d3b05 | ||
|
|
927e37b35b | ||
|
|
be5e0aafeb | ||
|
|
488fc6888f | ||
|
|
8e81f6bdf4 | ||
|
|
09c8a2600e | ||
|
|
0a7902c4b4 | ||
|
|
02dd7e7264 | ||
|
|
91bc63004a | ||
|
|
f3e04114eb | ||
|
|
ef2885ed0e | ||
|
|
3b3b9d97ba | ||
|
|
be38ae6c00 | ||
|
|
0589624d11 | ||
|
|
dd7a894b51 | ||
|
|
73efd963b9 | ||
|
|
250ec840ef | ||
|
|
ba4677857a | ||
|
|
e3159745fb | ||
|
|
b7eaa238e0 | ||
|
|
fb71f9087d | ||
|
|
9618cd2128 | ||
|
|
9468d95506 | ||
|
|
4997bc27fd | ||
|
|
73982fbd60 | ||
|
|
7904a6a20f | ||
|
|
ade4aeb3f7 | ||
|
|
e46108fa15 | ||
|
|
7346a46975 | ||
|
|
5baa4c7ef7 | ||
|
|
4ce79cc386 | ||
|
|
11fee0f200 | ||
|
|
26b357df72 | ||
|
|
4a531888b5 | ||
|
|
262bcc4d8b | ||
|
|
088ff3744a | ||
|
|
01b1ce679c | ||
|
|
267fd0eadc | ||
|
|
814d72cb3f | ||
|
|
b75f6e5853 | ||
|
|
dd6d12de23 | ||
|
|
f86a163692 | ||
|
|
e0fe7cb1eb | ||
|
|
47cbd4b620 | ||
|
|
0a6f1abd33 | ||
|
|
92b84168e1 | ||
|
|
fae625a795 | ||
|
|
c49b66f0e4 | ||
|
|
3cc7f7407d |
@@ -6,41 +6,45 @@ body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Issue Checklist
|
||||
description: Be sure to complete these steps to increase the chances of your issue being addressed!
|
||||
options:
|
||||
- label: I have properly named the issue
|
||||
- label: I have checked the issues/discussions pages to see if the issue has been previously reported
|
||||
- label: I have properly named my issue
|
||||
- label: I have checked the Issues/Discussions pages to see if my issue has already been reported
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: What platform are you using?
|
||||
label: Platform
|
||||
description: Which platform are you playing on?
|
||||
options:
|
||||
- Newgrounds (Web)
|
||||
- Itch.io (Web)
|
||||
- Newgrounds (Web/HTML5)
|
||||
- Itch.io (Web/HTML5)
|
||||
- Itch.io (Downloadable Build) - Windows
|
||||
- Itch.io (Downloadable Build) - MacOS
|
||||
- Itch.io (Downloadable Build) - Linux
|
||||
- Compiled from GitHub Source Code
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: If you are playing on a browser, which one are you using?
|
||||
label: Browser
|
||||
description: (Web/HTML5 users only) Which browser are you playing on?
|
||||
options:
|
||||
- Google Chrome
|
||||
- Microsoft Edge
|
||||
- Firefox
|
||||
- Opera
|
||||
- Safari
|
||||
- Other (Specify below)
|
||||
- Other (Specify in Description field)
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: Version
|
||||
description: What version are you using?
|
||||
placeholder: ex. 0.4.1
|
||||
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
|
||||
placeholder: ex. 0.5.0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "## Describe your bug."
|
||||
@@ -49,14 +53,16 @@ body:
|
||||
attributes:
|
||||
value: "### Please do not report issues from other engines. These must be reported in their respective repositories."
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "#### Provide as many details as you can."
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Context (Provide images, videos, etc.)
|
||||
|
||||
label: Description (include any images, videos, errors, or crash logs)
|
||||
description: Provide as much detail as you can. The better others understand your issue, the more they can help you!
|
||||
placeholder: Describe your issue here...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce (or crash logs, errors, etc.)
|
||||
label: Steps to Reproduce
|
||||
description: What steps can other people take to reliably encounter this issue?
|
||||
placeholder: 1. Open the game...
|
||||
|
||||
@@ -6,65 +6,73 @@ body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Issue Checklist
|
||||
description: Be sure to complete these steps to increase the chances of your issue being addressed!
|
||||
options:
|
||||
- label: I have properly named the issue
|
||||
- label: I have checked the issues/discussions pages to see if the issue has been previously reported
|
||||
- label: I have properly named my issue
|
||||
- label: I have checked the Issues/Discussions pages to see if my issue has already been reported
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: What platform are you using?
|
||||
label: Platform
|
||||
description: Which platform are you playing on?
|
||||
options:
|
||||
- Newgrounds (Web)
|
||||
- Itch.io (Web)
|
||||
- Newgrounds (Web/HTML5)
|
||||
- Itch.io (Web/HTML5)
|
||||
- Itch.io (Downloadable Build) - Windows
|
||||
- Itch.io (Downloadable Build) - MacOS
|
||||
- Itch.io (Downloadable Build) - Linux
|
||||
- Compiled from GitHub Source Code
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: If you are playing on a browser, which one are you using?
|
||||
label: Browser
|
||||
description: (Web/HTML5 users only) Which browser are you playing on?
|
||||
options:
|
||||
- Google Chrome
|
||||
- Microsoft Edge
|
||||
- Firefox
|
||||
- Opera
|
||||
- Safari
|
||||
- Other (Specify below)
|
||||
- Other (Specify in Description field)
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: Version
|
||||
description: What version are you using?
|
||||
placeholder: ex. 0.4.1
|
||||
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
|
||||
placeholder: ex. 0.5.0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "## Describe your issue."
|
||||
value: "## Describe the crash."
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "### Please do not report issues from other engines. These must be reported in their respective repositories."
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "#### Provide as many details as you can."
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Context (Provide screenshots or videos of the crash happening)
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
label: Description
|
||||
description: Include screenshots or videos of the crash happening. Provide as much detail as you can.
|
||||
placeholder: Describe the crash here...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Crash logs (can be found in the logs folder where Funkin.exe is)
|
||||
label: Steps to Reproduce
|
||||
description: What steps can other people take to reliably trigger this crash?
|
||||
placeholder: 1. Open the game...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Crash logs
|
||||
description: These can be found in the logs folder where Funkin.exe is.
|
||||
placeholder: Upload your logs here...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -6,10 +6,13 @@ body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Issue Checklist
|
||||
description: Be sure to complete these steps to increase the chances of your suggestion being considered!
|
||||
options:
|
||||
- label: I have properly named the enhancement
|
||||
- label: I have checked the issues/discussions pages to see if the enhancement has been previously suggested
|
||||
- label: I have properly named my enhancement
|
||||
- label: I have checked the Issues/Discussions pages to see if my enhancement has already been suggested
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: What is your suggestion, and why should it be implemented?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -7,6 +7,6 @@ medium:
|
||||
min: 10
|
||||
max: 99
|
||||
|
||||
# Add 'large' to any changes for more than 100 lines
|
||||
# Add 'large' to any changes of at least 100 lines
|
||||
large:
|
||||
min: 100
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
# Configuration for Label Actions - https://github.com/dessant/label-actions
|
||||
|
||||
# Automatically close issues and pull requests when the `status: duplicate` label is applied
|
||||
'status: duplicate':
|
||||
issues:
|
||||
# Post a comment
|
||||
comment: >
|
||||
This issue is a duplicate. Please direct all discussion to the original issue.
|
||||
# Close the issue
|
||||
close: true
|
||||
# Set a close reason
|
||||
close-reason: 'not planned'
|
||||
# Remove other status labels
|
||||
unlabel:
|
||||
- 'status: accepted'
|
||||
- 'status: bug reproduced'
|
||||
- 'status: cannot reproduce'
|
||||
- 'status: needs clarification'
|
||||
- 'status: needs revision'
|
||||
- 'status: pending pull request'
|
||||
- 'status: pending triage'
|
||||
- 'status: planned'
|
||||
- 'status: rejected'
|
||||
- 'status: resolved'
|
||||
- 'status: reviewing internally'
|
||||
- 'status: stale'
|
||||
prs:
|
||||
# Post a comment
|
||||
comment: >
|
||||
This pull request is a duplicate. Please direct all discussion to the original pull request.
|
||||
# Close the pull request
|
||||
close: true
|
||||
# Set a close reason
|
||||
close-reason: 'not planned'
|
||||
# Remove other status labels
|
||||
unlabel:
|
||||
- 'status: accepted'
|
||||
- 'status: bug reproduced'
|
||||
- 'status: cannot reproduce'
|
||||
- 'status: needs clarification'
|
||||
- 'status: needs revision'
|
||||
- 'status: pending pull request'
|
||||
- 'status: pending triage'
|
||||
- 'status: planned'
|
||||
- 'status: rejected'
|
||||
- 'status: resolved'
|
||||
- 'status: reviewing internally'
|
||||
- 'status: stale'
|
||||
|
||||
'status: rejected':
|
||||
issues:
|
||||
# Close the issue
|
||||
close: true
|
||||
# Set a close reason
|
||||
close-reason: 'not planned'
|
||||
# Remove other status labels
|
||||
unlabel:
|
||||
- 'status: accepted'
|
||||
- 'status: bug reproduced'
|
||||
- 'status: cannot reproduce'
|
||||
- 'status: duplicate'
|
||||
- 'status: needs clarification'
|
||||
- 'status: needs revision'
|
||||
- 'status: pending pull request'
|
||||
- 'status: pending triage'
|
||||
- 'status: planned'
|
||||
- 'status: resolved'
|
||||
- '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'
|
||||
- 'status: bug reproduced'
|
||||
- 'status: cannot reproduce'
|
||||
- 'status: duplicate'
|
||||
- 'status: needs clarification'
|
||||
- 'status: needs revision'
|
||||
- 'status: pending pull request'
|
||||
- 'status: pending triage'
|
||||
- 'status: planned'
|
||||
- 'status: resolved'
|
||||
- 'status: reviewing internally'
|
||||
- 'status: stale'
|
||||
|
||||
'status: resolved':
|
||||
issues:
|
||||
# Close the issue
|
||||
close: true
|
||||
# Set a close reason
|
||||
close-reason: 'completed'
|
||||
# Remove other status labels
|
||||
unlabel:
|
||||
- 'status: accepted'
|
||||
- 'status: bug reproduced'
|
||||
- 'status: cannot reproduce'
|
||||
- 'status: duplicate'
|
||||
- 'status: needs clarification'
|
||||
- 'status: needs revision'
|
||||
- 'status: pending pull request'
|
||||
- 'status: pending triage'
|
||||
- 'status: planned'
|
||||
- 'status: rejected'
|
||||
- 'status: reviewing internally'
|
||||
- 'status: stale'
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
# Add Documentation tag to PR's changing markdown files, or anyhting in the docs folder
|
||||
# Add Documentation tag to PR's changing markdown files, or anything in the docs folder
|
||||
Documentation:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- docs/*
|
||||
- '**/*.md'
|
||||
|
||||
# Adds Haxe tag to PR's changing haxe code files
|
||||
# Add Haxe tag to PR's changing haxe code files
|
||||
Haxe:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: '**/*.hx'
|
||||
|
||||
@@ -2,6 +2,15 @@ name: Build and Upload nightly game builds
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
build-defines:
|
||||
type: string
|
||||
description: Build defines to use
|
||||
default: '-DGITHUB_BUILD'
|
||||
save-artifact:
|
||||
type: boolean
|
||||
description: Save the build artifact to Github Actions (sends to itch otherwise)
|
||||
default: false
|
||||
push:
|
||||
paths-ignore:
|
||||
- '**/Dockerfile'
|
||||
@@ -53,13 +62,20 @@ jobs:
|
||||
- name: Build game
|
||||
if: ${{ matrix.target == 'windows' }}
|
||||
run: |
|
||||
haxelib run lime build windows -v -release -DGITHUB_BUILD
|
||||
haxelib run lime build windows -v -release ${{ github.event.inputs.build-defines }}
|
||||
timeout-minutes: 120
|
||||
- name: Build game
|
||||
if: ${{ matrix.target != 'windows' }}
|
||||
run: |
|
||||
haxelib run lime build ${{ matrix.target }} -v -release --times -DGITHUB_BUILD
|
||||
haxelib run lime build ${{ matrix.target }} -v -release --times ${{ github.event.inputs.build-defines }}
|
||||
timeout-minutes: 120
|
||||
- name: Save build artifact to Github Actions
|
||||
if: ${{ github.event.inputs.save-artifact }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-${{ matrix.target }}
|
||||
path: export/release/${{matrix.target}}/bin/
|
||||
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: ./.github/actions/upload-itch
|
||||
@@ -125,9 +141,15 @@ jobs:
|
||||
|
||||
- name: Build game
|
||||
run: |
|
||||
haxelib run lime build ${{ matrix.target }} -v -release --times -DGITHUB_BUILD
|
||||
haxelib run lime build ${{ matrix.target }} -v -release --times ${{ github.event.inputs.build-defines }}
|
||||
timeout-minutes: 120
|
||||
|
||||
- name: Save build artifact to Github Actions
|
||||
if: ${{ github.event.inputs.save-artifact }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-${{ matrix.target }}
|
||||
path: export/release/${{matrix.target}}/bin/
|
||||
- name: Upload build artifacts
|
||||
uses: ./.github/actions/upload-itch
|
||||
with:
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# Perform actions when labels are applied to issues, discussions, or pull requests
|
||||
# See .github/label-actions.yml
|
||||
name: 'Label Actions'
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- labeled
|
||||
- unlabeled
|
||||
pull_request_target:
|
||||
types:
|
||||
- labeled
|
||||
- unlabeled
|
||||
discussion:
|
||||
types:
|
||||
- labeled
|
||||
- unlabeled
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
discussions: write
|
||||
|
||||
jobs:
|
||||
action:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/label-actions@v4
|
||||
@@ -0,0 +1,52 @@
|
||||
name: "Issue Labeler"
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- edited
|
||||
|
||||
jobs:
|
||||
# When an issue is opened, perform a similarity check for potential duplicates.
|
||||
# If some are found, add a label and comment listing the potential duplicate issues.
|
||||
potential-duplicate:
|
||||
name: Detect potential duplicate issues
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: wow-actions/potential-duplicates@v1
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Issue title filter work with anymatch https://www.npmjs.com/package/anymatch.
|
||||
# Any matched issue will stop detection immediately.
|
||||
# You can specify multi filters in each line.
|
||||
filter: ''
|
||||
# Exclude keywords in title before detecting.
|
||||
exclude: ''
|
||||
# Label to set, when potential duplicates are detected.
|
||||
label: 'potential duplicate'
|
||||
# Get issues with state to compare. Supported state: 'all', 'closed', 'open'.
|
||||
state: all
|
||||
# If similarity is higher than this threshold([0,1]), issue will be marked as duplicate.
|
||||
# Turn this up if the detection is too sensitive
|
||||
threshold: 0.6
|
||||
# Reactions to be add to comment when potential duplicates are detected.
|
||||
# Available reactions: "-1", "+1", "confused", "laugh", "heart", "hooray", "rocket", "eyes"
|
||||
# reactions: '-1'
|
||||
# Comment to post when potential duplicates are detected.
|
||||
comment: >
|
||||
Potential duplicates: {{#issues}}
|
||||
- [#{{ number }}] {{ title }} ({{ accuracy }}%)
|
||||
{{/issues}}
|
||||
# When an issue is opened, detect if it has an empty body or incomplete issue form.
|
||||
# If it does, close the issue immediately.
|
||||
empty-issues:
|
||||
name: Close empty issues
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Run empty issues closer action
|
||||
uses: rickstaa/empty-issues-closer-action@v1
|
||||
env:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
close_comment: Closing this issue because it appears to be empty. Please update the issue for it to be reopened.
|
||||
open_comment: Reopening this issue because the author provided more information.
|
||||
@@ -3,6 +3,7 @@ on:
|
||||
- pull_request_target
|
||||
|
||||
jobs:
|
||||
# Apply labels to pull requests based on which files were edited
|
||||
labeler:
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -13,6 +14,7 @@ jobs:
|
||||
uses: actions/labeler@v5
|
||||
with:
|
||||
sync-labels: true
|
||||
# Apply labels to pull requests based on how many lines were edited
|
||||
changed-lines-count-labeler:
|
||||
permissions:
|
||||
contents: read
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
[submodule "assets"]
|
||||
path = assets
|
||||
url = https://github.com/FunkinCrew/Funkin-Assets-secret
|
||||
url = https://github.com/FunkinCrew/Funkin.assets
|
||||
[submodule "art"]
|
||||
path = art
|
||||
url = https://github.com/FunkinCrew/Funkin-Art-secret
|
||||
url = https://github.com/FunkinCrew/Funkin.art
|
||||
|
||||
Vendored
+10
@@ -96,6 +96,11 @@
|
||||
"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",
|
||||
@@ -106,6 +111,11 @@
|
||||
"target": "hl",
|
||||
"args": ["-debug"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (Discord)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS", "-DFEATURE_DISCORD_RPC"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (FlxAnimate Test)",
|
||||
"target": "windows",
|
||||
|
||||
+281
-90
@@ -4,24 +4,166 @@ All notable changes will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.5.3] - 2024-10-18
|
||||
This patch resolves a critical issue which could cause user's save data to become corrupted. It is recommended that users switch to this version immediately and avoid using version 0.5.2.
|
||||
### Fixed
|
||||
- Fixed a critical issue in which the Stage Editor theme value could not be parsed by older versions of the game, resulting in all save data being destroyed.
|
||||
- Added a check that prevents save data from being loaded if it is corrupted rather than overriding it.
|
||||
- `optionsChartEditor.chartEditorLiveInputStyle` in the save data converted from an Enum to a String to fix save data compatibility issues.
|
||||
- `optionsStageEditor.theme` in the save data converted from an Enum to a String to fix save data compatibility issues.
|
||||
- In the future, Enum values should not be used in order to prevent incompatibilities caused by introducing new types to the save data that older versions cannot parse.
|
||||
- Fixed an issue where some publicly distributed release builds of the game were not updated to the latest version.
|
||||
|
||||
|
||||
## [0.5.2] - 2024-10-11
|
||||
### Added
|
||||
- Added InverseDotsShader that emulates flash selections ([097dbf5](https://github.com/FunkinCrew/Funkin/commit/097dbf5bb4346d431d8ca9f0ec4bc5b5e6f4523f)) - by @ninjamuffin99
|
||||
- Added a new reworked Stage Editor ([27a0b44](https://github.com/FunkinCrew/Funkin/pull/3482/commits/27a0b4426f86f04362f97e16e2eff580c9402f34)) - by @JustKolosaki in [#3482](https://github.com/FunkinCrew/Funkin/pull/3482)
|
||||
- Added the `color` attribute to stage prop JSON data to allow them to be tinted without code. ([27a0b44](https://github.com/FunkinCrew/Funkin/pull/3482/commits/27a0b4426f86f04362f97e16e2eff580c9402f34)) - by @JustKolosaki
|
||||
- Added the `angle` attribute to stage prop JSON data to allow them to be rotated without code. ([27a0b44](https://github.com/FunkinCrew/Funkin/pull/3482/commits/27a0b4426f86f04362f97e16e2eff580c9402f34)) - by @JustKolosaki
|
||||
- Added the `blend` attribute to the stage prop JSON data to allow blend modes to be applied without code. ([27a0b44](https://github.com/FunkinCrew/Funkin/pull/3482/commits/27a0b4426f86f04362f97e16e2eff580c9402f34)) - by @JustKolosaki
|
||||
### Changed
|
||||
- (docs) Delete Modding.md since we have a separate modding documentation ([a42240e](https://github.com/FunkinCrew/Funkin/commit/a42240e6a595d33034f2c887bf38a350d1fa0f15)) - by @AbnormalPoof in [#3651](https://github.com/FunkinCrew/Funkin/pull/3651)
|
||||
- (docs) Create a git cliff template for easier changelog stuff ([91b4544](https://github.com/FunkinCrew/Funkin/commit/91b4544f7ebc51485e3e28c3d716ba6ee69ad885)) - by @ninjamuffin99 in [#3652](https://github.com/FunkinCrew/Funkin/pull/3652)
|
||||
- (docs) Add additional `variation` input parameter to `Save.hasBeatenSong()` to allow usage of the function by inputting a variation id ([4fa9a0d](https://github.com/FunkinCrew/Funkin/commit/4fa9a0daaa67e0977460b147bd1f74a118e3e2a5)) - by @ninjamuffin99
|
||||
- (docs) Added modding docs link in readme ([4b54118](https://github.com/FunkinCrew/Funkin/commit/4b54118731e26118111e06558ae4853c577fe4bb)) - by @Cartridge-Man in [#3082](https://github.com/FunkinCrew/Funkin/pull/3082)
|
||||
- (docs) Fix some misspellings and grammar in code documentation ([2175bea](https://github.com/FunkinCrew/Funkin/commit/2175beaa651e009332202985be4b7eb4ed36e5a4)) - by @Hundrec in [#3477](https://github.com/FunkinCrew/Funkin/pull/3477)
|
||||
- (docs) Improvements to Github Issues templates ([399869c](https://github.com/FunkinCrew/Funkin/commit/399869cdccc9c5ac27cecfbcdc33c3d7eb4b348c)) - by @Hundrec in [#3458](https://github.com/FunkinCrew/Funkin/pull/3458)
|
||||
### Fixed
|
||||
- Fix the user song offsets being applied incorrectly, causing stuttering or skipping ([410cfe9](https://github.com/FunkinCrew/Funkin/commit/410cfe972d6df9de4d4d128375cf8380c4f06d92)) - by @JustKolosaki in [#3546](https://github.com/FunkinCrew/Funkin/pull/3546) in [#3506](https://github.com/FunkinCrew/Funkin/pull/3506)
|
||||
- Fixed issues with variation / difficulty loading for Freeplay Menu which caused some songs to disappear ([c0314c8](https://github.com/FunkinCrew/Funkin/commit/c0314c85ecd5116641aff3de8e9153f7fe48e79c)) - by @ninjamuffin99
|
||||
- Pico's songs now display properly on the Freeplay Menu ([1d2bd61](https://github.com/FunkinCrew/Funkin/commit/1d2bd61119e5f418df7f11d7ef2a0fdedee17d3d)) - by @ninjamuffin99 in [#3506](https://github.com/FunkinCrew/Funkin/pull/3506)
|
||||
- Exiting the chart editor doesn't crash the game anymore ([f52472a](https://github.com/FunkinCrew/Funkin/commit/f52472a4767388b22cfbab0f5f7860f6e6762856)) - by @EliteMasterEric and @ianharrigan in [#3519](https://github.com/FunkinCrew/Funkin/pull/3519)
|
||||
- Disable flickering when attempting to select FPS in the options menu ([b2647fe](https://github.com/FunkinCrew/Funkin/commit/b2647fe09f5281ce7074b26d47bc1524764168ee)) - by @lemz1 in [#3629](https://github.com/FunkinCrew/Funkin/pull/3629)
|
||||
- Anti-alias / smooth the volume sound tray ([e66290c](https://github.com/FunkinCrew/Funkin/commit/e66290c55f7141402223644f06ec8a69edeee089)) - by @Kn1ghtNight in [#2853](https://github.com/FunkinCrew/Funkin/pull/2853)
|
||||
- Don't restart the FreeplayState song preview when changing the difficulty within the same variation ([903b3fc](https://github.com/FunkinCrew/Funkin/commit/903b3fc59905a70802618a1cd67407722ea956ed)) - by @JustKolosaki in [#3587](https://github.com/FunkinCrew/Funkin/pull/3587)
|
||||
- Character Select cursor moves properly at lower framerates ([ab5bda3](https://github.com/FunkinCrew/Funkin/commit/ab5bda3ee573a6e03595ec6941e6de38df851889)) - by @ninjamuffin99 in [#3507](https://github.com/FunkinCrew/Funkin/pull/3507)
|
||||
- Stop allowing F1 to create more than one help dialog window in the Charting Editor ([777978f](https://github.com/FunkinCrew/Funkin/commit/777978f5a544e1b7c89b47dcc365f734eb6d0df1)) - by @amyspark-ng in [#3552](https://github.com/FunkinCrew/Funkin/pull/3552)
|
||||
- Main menu music doesn't cut out when switching states anymore. ([711e0a6](https://github.com/FunkinCrew/Funkin/commit/711e0a6b7547eb04113e9318dab900f01ad576a5)) - by @EliteMasterEric in [#3530](https://github.com/FunkinCrew/Funkin/pull/3530)
|
||||
- The dialog now shows up on the animation debugger view ([1fde59f](https://github.com/FunkinCrew/Funkin/commit/1fde59f999eac94eb10fc22094885de2f5310705)) - by @EliteMasterEric in [#3530](https://github.com/FunkinCrew/Funkin/pull/3530)
|
||||
- Center preloader 'fnf' and 'dsp' text so it doesn't clip anymore ([165ad60](https://github.com/FunkinCrew/Funkin/commit/165ad6015539a295e9eefdaef291c312e9566b26)) - by @Burgerballs in [#3567](https://github.com/FunkinCrew/Funkin/pull/3567)
|
||||
- `Song.getFirstValidVariation()` now properly takes into account multiple variations/difficulty input ([d2e2987](https://github.com/FunkinCrew/Funkin/commit/d2e29879fe2acc6febfe0f335f655b741d630c34)) - by @ninjamuffin99 in [#3506](https://github.com/FunkinCrew/Funkin/pull/3506)
|
||||
- (debug) No more fullscreening when typing "F" in the flixel debugger console ([29b6763](https://github.com/FunkinCrew/Funkin/commit/29b6763290df05d42039806f3d142740568c80f0)) - by @ninjamuffin99
|
||||
- Fix crash in LatencyState when exiting / cleaning up state data ([39b1a42](https://github.com/FunkinCrew/Funkin/commit/39b1a42cfeafe2b7be8b66e2fe529e853d9ae197)) - by @lemz1 in [#3493](https://github.com/FunkinCrew/Funkin/pull/3493)
|
||||
- Add additional classes to Polymod blacklist for security ([b0b73c8](https://github.com/FunkinCrew/Funkin/commit/b0b73c83994f33118c6a69550da9ec8ec1c07adc)) - by @EliteMasterEric in [#3558](https://github.com/FunkinCrew/Funkin/pull/3558)
|
||||
- Stop allowing inputs after selecting a character ([dbf66ac](https://github.com/FunkinCrew/Funkin/commit/dbf66ac250137262866d75f7c1387645b35d88d0)) - by @ACrazyTown in [#3398](https://github.com/FunkinCrew/Funkin/pull/3398)
|
||||
- Fix conflict with modded StrumlineNote sprite looping animation ([bc546e8](https://github.com/FunkinCrew/Funkin/commit/bc546e86aa77ffc795b3f079de5f590289a9c583)) - by @DaWaterMalone in [#3577](https://github.com/FunkinCrew/Funkin/pull/3577)
|
||||
- Properly format the millisecond counter in the chart editor playbar ([f1b6e6c](https://github.com/FunkinCrew/Funkin/commit/f1b6e6c4e42455e0c2900d738ebc24893f2479a0)) - by @afreetoplaynoob in [#3537](https://github.com/FunkinCrew/Funkin/pull/3537)
|
||||
- Fixed an issue where the player and girlfriend would disappear or overlap themselves in Character Select ([community fix by @gamerbross](https://github.com/FunkinCrew/Funkin/pull/3457))
|
||||
- Fixed an issue where the game would show the wrong girlfriend in Character Select ([community fix by @gamerbross](https://github.com/FunkinCrew/Funkin/pull/3457))
|
||||
- Fixed an issue where the cursor wouldn't update properly in Character Select ([community fix by @gamerbross](https://github.com/FunkinCrew/Funkin/pull/3457))
|
||||
- Fixed an issue where the player would display double after entering character select or when spamming buttons ([community fix by @gamerbross](https://github.com/FunkinCrew/Funkin/pull/3457))
|
||||
|
||||
## New Contributors for 0.5.2
|
||||
* @JustKolosaki made their first contribution in [#3482](https://github.com/FunkinCrew/Funkin/pull/3482)
|
||||
* @Kn1ghtNight made their first contribution in [#2853](https://github.com/FunkinCrew/Funkin/pull/2853)
|
||||
* @DaWaterMalone made their first contribution in [#3577](https://github.com/FunkinCrew/Funkin/pull/3577)
|
||||
* @amyspark-ng made their first contribution in [#3552](https://github.com/FunkinCrew/Funkin/pull/3552)
|
||||
* @Cartridge-Man made their first contribution in [#3082](https://github.com/FunkinCrew/Funkin/pull/3082)
|
||||
* @afreetoplaynoob made their first contribution in [#3537](https://github.com/FunkinCrew/Funkin/pull/3537)
|
||||
|
||||
|
||||
## [0.5.1] - 2024-09-30
|
||||
### Added
|
||||
- Readded the Merch button to the main menu.
|
||||
- Click it to check out our Makeship campaign!
|
||||
- Added Discord Rich Presence support. People can now see what song you are playing from Discord!
|
||||
- We'll get mod support working for this eventually.
|
||||
- Added an FPS limit option to the Preferences menu.
|
||||
- You can now change how high the game tries to push your frame rate, from as little as 30 to as high as 300.
|
||||
- Added support for the Tracy instrumenation-based profiling tool in development environments. Enable it with the `-DFEATURE_DEBUG_TRACY` compilation flag.
|
||||
- For the people who aren't nerds, this is a tool for tracking down performance issues!
|
||||
- Playable Character data now defines an asset location for an Animate Atlas to display Girlfriend.
|
||||
- This includes the option to display a visualizer, if configured correctly.
|
||||
- Separated the Perfect and Perfect (Gold) animations in the Playable Character data.
|
||||
- Base game just uses the same animation for both, but modders can split the animations up on their custom characters now.
|
||||
- Added a bunch of Flash project files from the Weekend 1 and Playable Pico updates to the `Funkin.art` repository.
|
||||
- Added the `flipX` and `flipY` parameters to props in the Stage data. ([community feature by AbnormalPoof](https://github.com/FunkinCrew/Funkin/pull/3474))
|
||||
### Changed
|
||||
- The game's mod API version check is now more dynamic.
|
||||
- The update accepts mods with API version `0.5.0` as well as `0.5.1`.
|
||||
- Pico is no longer unlocked for all players automatically.
|
||||
- You need to beat Weekend 1 in Story Mode in order to unlock him in Character Select.
|
||||
- Removed some of the more spammy `trace()` calls to improve debugging a bit.
|
||||
- Improved some of the compilation and modding documentation.
|
||||
- The game now complains if you create a song variation with symbols in the name.
|
||||
- Switched the force crash keybind from Ctrl-Shift-L to Ctrl-Alt-Shift-L.
|
||||
- Added some additional functions to `funkin.Assets` after `openfl.utils.Assets` had to get blacklisted from scripts.
|
||||
### Fixed
|
||||
- Fixed an issue where DadBattle (Pico Mix) was not properly credited to `TeraVex (ft. Saruky)`.
|
||||
- Fixed an issue where DadBattle (Pico Mix) was missing charts on the Normal and Easy difficulty.
|
||||
- Fixed an issue where Spookeez (Pico Mix) was not properly credited to `Six Impala (ft. Saster)`.
|
||||
- Fixed an issue where the "Shit!" judgement would display with anti-aliasing in Week 6.
|
||||
- Fixed an issue where Pico Erect could be played with different instrumentals.
|
||||
- Fixed an issue where Pico would not play his shooting animations in Stress.
|
||||
- Fixed an issue where Freeplay would display no custom songs when switching characters.
|
||||
- Fixed an issue where Freeplay would sometimes display the wrong text on the capsules.
|
||||
- Fixed an issue where duplicate difficulties from custom variations wouldn't display properly in Freeplay.
|
||||
- Fixed an issue where custom note styles would sometimes just use default values rather than using the fallback note style.
|
||||
- Fixed an issue where custom note styles would randomly fail to fetch information about their fallback note style.
|
||||
- Fixed an issue where the Screenshots and Chart Editor binds displayed in the controls menu on Web builds (where they are disabled).
|
||||
- Fixed an issue where the Stage Editor bind displayed in the controls menu even when the feature was disabled.
|
||||
- Fixed an issue where the Freeplay Character Select keybind displayed weird in the keybinds menu.
|
||||
- Fixed an issue where setting the input offset or visual offset too high would cause the song to skip.
|
||||
- Fixed an issue where video cutscenes would not scale their volume properly.
|
||||
- Fixed an issue where Cocoa Erect (Erect difficulty) had a tap note stacked on top of a hold note,
|
||||
- Fixed an issue where the game would stutter when playing on the Week 5 Remix stage.
|
||||
- Fixed an issue where the save data version number wouldn't get written to save data properly.
|
||||
- Fixed an issue where the example mod could not be loaded.
|
||||
- Fixed an issue where a script error would display when entering Blazin'.
|
||||
- Fixed an issue where pressing F5 to force reload a song would sometimes cause the game to crash.
|
||||
- Fixed an issue where animations on Animate Atlas characters would throw a bunch of warnings in the console.
|
||||
- Fixed an issue where characters with high offsets would shift over after the player dies or restarts.
|
||||
- Fixed an issue where Pico wouldn't play out his full burp animation in South (Pico Mix).
|
||||
- Fixed an issue where Results screen audio could continue into Freeplay or even gameplay.
|
||||
- Fixed an issue where some audio tracks would get destroyed even if they were flagged as persistent.
|
||||
- Fixed an issue where the audio track would stay muted if you missed a note just before Pico burps.
|
||||
- Fixed an issue where the curtains in Week 1 would display in front of larger characters.
|
||||
- Fixed an issue where Boyfriend wouldn't play his death animation properly on Week 2's Remix stage.
|
||||
- Fixed an issue in Freeplay where the clear % would not display after switching characters.
|
||||
- Fixed an issue in Freeplay where character remixes would display the base song's highscore.
|
||||
- Fixed an issue where Pico would become locked every time the game starts, and you would have to watch the unlock animation each game boot.
|
||||
- The animation should now play only once per save file.
|
||||
- Fixed an issue where Spirit's trail in Week 6 would not display correctly.
|
||||
- Fixed an issue where the Input Offsets menu would crash when entering it before playing a song on web builds.
|
||||
- Fixed an issue where the Results screen would spam the percentage tick noise instead of playing when the value changes.
|
||||
- Fixed an issue where parts of the Chart Editor could not be interacted with. ([community fix by Kade-github](https://github.com/FunkinCrew/Funkin/pull/3337))
|
||||
- Fixed an issue where classic FocusCamera song events could cause the camera to snap in place. ([community fix by nebulazorua](https://github.com/FunkinCrew/Funkin/pull/2331))
|
||||
- Fixed an issue where achieving the same rank on a song (but a different clear %) would override your clear %, even if it was lower. ([community fix by lemz1](https://github.com/FunkinCrew/Funkin/pull/3019))
|
||||
- Fixed an issue where the FPS counter would display even if Debug Display was turned off. ([community fix by Lethrial](https://github.com/FunkinCrew/Funkin/pull/3356))
|
||||
- Fixed an issue where selecting the area to the left of the Chart Editor would select some of the player's notes ([community fix by NotHyper-474](https://github.com/FunkinCrew/Funkin/pull/3093))
|
||||
- Fixed an issue where pixel icons in the Chart Editor would not display correctly. ([community fix by Techniktil](https://github.com/FunkinCrew/Funkin/pull/3339))
|
||||
- Fixed an issue where `Stage.addCharacter` would not properly assign the `characterType`. ([community fix by Kade-github](https://github.com/FunkinCrew/Funkin/pull/3357))
|
||||
- Fixed an issue where players could interact with Character Select during the unlock sequence, causing a crash. ([community fix by ActualMandM](https://github.com/FunkinCrew/Funkin/pull/3355))
|
||||
- Fixed an issue where hold notes in Week 6 were not scaled/positioned correctly. ([community fix by dombomb64](https://github.com/FunkinCrew/Funkin/pull/3351))
|
||||
- Fixed an issue where audio offets would not interact with the Chart Editor properly. ([community fix by Kade-github](https://github.com/FunkinCrew/Funkin/pull/3384))
|
||||
- Fixed an issue where fetching Modules during the `onDestroy` event would fail at random. ([community fix by cyn0x8](https://github.com/FunkinCrew/Funkin/pull/3131))
|
||||
- Fixed an issue where `onSubStateOpenEnd` and `onSubStateCloseEnd` script events would not always get called. ([community fix by lemz1](https://github.com/FunkinCrew/Funkin/pull/3138))
|
||||
|
||||
## New Contributors for 0.5.1
|
||||
* @Lethrial made their first contribution in [#3356](https://github.com/FunkinCrew/Funkin/pull/3356)
|
||||
* @dombomb64 made their first contribution in [#3351](https://github.com/FunkinCrew/Funkin/pull/3351)
|
||||
|
||||
|
||||
## [0.5.0] - 2024-09-12
|
||||
### Added
|
||||
- Added a new Character Select screen to switch between playable characters in Freeplay
|
||||
- Modding isn't 100% there but we're working on it!
|
||||
- Added Pico as a playable character! Unlock him by completing Weekend 1 (if you haven't already done that)
|
||||
- The songs from Weekend 1 have moved; you must now switch to Pico in Freeplay to access them
|
||||
- Added 10 new Pico remixes! Access them by selecting Pico from in the Character Select screen
|
||||
- The songs from Weekend 1 have moved; you must now switch to Pico via Character Select screen in Freeplay to access them
|
||||
- Added 11 new Pico remixes! Access them by selecting Pico from in the Character Select screen
|
||||
- Bopeebo (Pico Mix)
|
||||
- Fresh (Pico Mix)
|
||||
- DadBattle (Pico Mix)
|
||||
- Spookeez (Pico Mix)
|
||||
- South (Pico Mix)
|
||||
- Pico (Pico Mix)
|
||||
- Philly Nice (Pico Mix)
|
||||
- Blammed (Pico Mix)
|
||||
- Blammed (Pico Mix)
|
||||
- Eggnog (Pico Mix)
|
||||
- Ugh (Pico Mix)
|
||||
- Guns (Pico Mix)
|
||||
- Added 1 new Boyfriend remix! Access it by selecting Pico from in the Character Select screen
|
||||
- Added 1 new Boyfriend remix! Access it by completing Weekend 1 as Pico and then selecting Boyfriend from in the Character Select screen
|
||||
- Darnell (BF Mix)
|
||||
- Added 2 new Erect remixes! Access them by switching difficulty on the song
|
||||
- Cocoa Erect
|
||||
@@ -38,7 +180,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Implemented alternate animations and music for Pico in the results screen.
|
||||
- These display on Pico remixes, as well as when playing Weekend 1.
|
||||
- Implemented support for scripted Note Kinds. You can use HScript define a different note style to display for these notes as well as custom behavior. (community feature by lemz1)
|
||||
- Implemented support for Numeric and Selector options in the Options menu. (community feature by FlooferLand)
|
||||
- Implemented support for Numeric and Selector options in the Options menu. ([community feature by FlooferLand](https://github.com/FunkinCrew/Funkin/pull/2942))
|
||||
- Implemented new animations for Tankman and Pico.
|
||||
## Changed
|
||||
- Girlfriend and Nene now perform previously unused animations when you achieve a large combo, or drop a large combo.
|
||||
- The pixel character icons in the Freeplay menu now display an animation!
|
||||
@@ -46,40 +189,51 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Character offsets are now independent of the character's scale.
|
||||
- This should resolve issues with offsets when porting characters from older mods.
|
||||
- Pixel character offsets have been modified to compensate.
|
||||
- Note style data can now specify custom combo count graphics, judgement graphics, countdown graphics, and countdown audio. (community feature by anysad)
|
||||
- Note style data can now specify custom combo count graphics, judgement graphics, countdown graphics, and countdown audio. ([community feature by anysad](https://github.com/FunkinCrew/Funkin/pull/3020))
|
||||
- These were previously using hardcoded values based on whether the stage was `school` or `schoolEvil`.
|
||||
- The `danceEvery` property of characters and stage props can now use values with a precision of `0.25`, to play their idle animation up to four times per beat.
|
||||
- Reworked the JSON merging system in Polymod; you can now include JSONPatch files under `_merge` in your mod folder to add, modify, or remove values in a JSON without replacing it entirely!
|
||||
- Cutscenes now automatically pause when tabbing out (community fix by AbnormalPoof)
|
||||
- Characters will now respect the `danceEvery` property (community fix by gamerbross)
|
||||
- The F5 function now reloads the current song's chart data from disc (community feature by gamerbross)
|
||||
- Refactored the compilation guide and added common troubleshooting steps (community fix by Hundrec)
|
||||
- Made several layout improvements and fixes to the Animation Offsets editor in the Debug menu (community fix by gamerbross)
|
||||
- Fixed a bug where the Back sound would be not played when leaving the Story menu and Options menu (community fix by AppleHair)
|
||||
- Animation offsets no longer directly modify the `x` and `y` position of props, which makes props work better with tweens (community fix by Sword352)
|
||||
- The YEAH! events in Tutorial now use chart events rather than being hard-coded (community fix by anysad)
|
||||
- Cutscenes now automatically pause when tabbing out ([community fix by AbnormalPoof](https://github.com/FunkinCrew/Funkin/pull/2903))
|
||||
- Characters will now respect the `danceEvery` property ([community fix by gamerbross](https://github.com/FunkinCrew/Funkin/pull/2925))
|
||||
- The F5 function now reloads the current song's chart data from disc ([community feature by gamerbross](https://github.com/FunkinCrew/Funkin/pull/2990))
|
||||
- Refactored the compilation guide and added common troubleshooting steps ([community fix by Hundrec](https://github.com/FunkinCrew/Funkin/pull/2813))
|
||||
- Made several layout improvements and fixes to the Animation Offsets editor in the Debug menu ([community fix by gamerbross](https://github.com/FunkinCrew/Funkin/pull/2820))
|
||||
- Fixed a bug where the Back sound would be not played when leaving the Story menu and Options menu ([community fix by AppleHair](https://github.com/FunkinCrew/Funkin/pull/2986))
|
||||
- Animation offsets no longer directly modify the `x` and `y` position of props, which makes props work better with tweens ([community fix by Sword352](https://github.com/FunkinCrew/Funkin/pull/2310))
|
||||
- The YEAH! events in Tutorial now use chart events rather than being hard-coded ([community fix by anysad](https://github.com/FunkinCrew/Funkin/pull/3007))
|
||||
- The player's Score now displays commas in it (community fix by loggo)
|
||||
## Fixed
|
||||
- Fixed an issue where songs with no notes would crash on the Results screen.
|
||||
- Fixed an issue where the old icon easter egg would not work properly on pixel levels.
|
||||
- Fixed an issue where you could play notes during the Thorns cutscene.
|
||||
- Fixed an issue where the Heart icon when favoriting a song in Freeplay would be malformed.
|
||||
- Fixed an issue where Pico's death animation displays a faint blue background (community fix by doggogit)
|
||||
- Fixed an issue where mod songs would not play a preview in the Freeplay menu (community fix by KarimAkra)
|
||||
- Fixed an issue where the Memory Usage counter could overflow and display a negative number (community fix by KarimAkra)
|
||||
- Fixed an issue where pressing the Chart Editor keybind while playtesting a chart would reset the chart editor (community fix by gamerbross)
|
||||
- Fixed a crash bug when pressing F5 after seeing the sticker transition (community fix by gamerbross)
|
||||
- Fixed an issue where the Story Mode menu couldn't be scrolled with a mouse (community fix by JVNpixels)
|
||||
- Fixed an issue causing the song to majorly desync sometimes (community fix by Burgerballs)
|
||||
- Fixed an issue where the Freeplay song preview would not respect the instrumental ID specified in the song metadata (community fix by AppleHair)
|
||||
- Fixed an issue where Tankman's icon wouldn't display in the Chart Editor (community fix by hundrec)
|
||||
- Fixed an issue where pausing the game during a camera zoom would zoom the pause menu. (community fix by gamerbros)
|
||||
- Fixed an issue where certain UI elements would not flash at a consistent rate (community fix by cyn0x8)
|
||||
- Fixed an issue where the game would not use the placeholder health icon as a fallback (community fix by gamerbross)
|
||||
- Fixed an issue where the chart editor could get stuck creating a hold note when using Live Inputs (community fix by gamerbross)
|
||||
- Fixed an issue where character graphics could not be placed in week folders (community fix by 7oltan)
|
||||
- Fixed a crash issue when a Freeplay song has no `Normal` difficulty (community fix by Applehair and gamerbross)
|
||||
- Fixed an issue in Story Mode where a song that isn't valid for the current variation could be selected (community fix by Applehair)
|
||||
- Fixed an issue where Pico's death animation displays a faint blue background ([community fix by doggogit](https://github.com/FunkinCrew/funkin.assets/pull/1))
|
||||
- Fixed an issue where mod songs would not play a preview in the Freeplay menu ([community fix by KarimAkra](https://github.com/FunkinCrew/Funkin/pull/2724))
|
||||
- Fixed an issue where the Memory Usage counter could overflow and display a negative number ([community fix by KarimAkra](https://github.com/FunkinCrew/Funkin/pull/2713))
|
||||
- Fixed an issue where pressing the Chart Editor keybind while playtesting a chart would reset the chart editor ([community fix by gamerbross](https://github.com/FunkinCrew/Funkin/pull/2739))
|
||||
- Fixed a crash bug when pressing F5 after seeing the sticker transition ([community fix by gamerbross](https://github.com/FunkinCrew/Funkin/pull/2863))
|
||||
- Fixed an issue where the Story Mode menu couldn't be scrolled with a mouse ([community fix by JVNpixels](https://github.com/FunkinCrew/Funkin/pull/2873))
|
||||
- Fixed an issue causing the song to majorly desync sometimes ([community fix by Burgerballs](https://github.com/FunkinCrew/Funkin/pull/3058))
|
||||
- Fixed an issue where the Freeplay song preview would not respect the instrumental ID specified in the song metadata ([community fix by AppleHair](https://github.com/FunkinCrew/Funkin/pull/2742))
|
||||
- Fixed an issue where Tankman's icon wouldn't display in the Chart Editor ([community fix by Hundrec](https://github.com/FunkinCrew/Funkin/pull/2912))
|
||||
- Fixed an issue where pausing the game during a camera zoom would zoom the pause menu. ([community fix by gamerbross](https://github.com/FunkinCrew/Funkin/pull/2567))
|
||||
- Fixed an issue where certain UI elements would not flash at a consistent rate ([community fix by cyn0x8](https://github.com/FunkinCrew/Funkin/pull/2494))
|
||||
- Fixed an issue where the game would not use the placeholder health icon as a fallback ([community fix by gamerbross](https://github.com/FunkinCrew/Funkin/pull/3005))
|
||||
- Fixed an issue where the chart editor could get stuck creating a hold note when using Live Inputs ([community fix by gamerbross](https://github.com/FunkinCrew/Funkin/pull/2992))
|
||||
- Fixed an issue where character graphics could not be placed in week folders ([community fix by 7oltan](https://github.com/FunkinCrew/Funkin/pull/3035))
|
||||
- Fixed a crash issue when a Freeplay song has no `Normal` difficulty ([community fix by AppleHair](https://github.com/FunkinCrew/Funkin/pull/3036) and [gamerbross](https://github.com/FunkinCrew/Funkin/pull/2712))
|
||||
- Fixed an issue in Story Mode where a song that isn't valid for the current variation could be selected ([community fix by AppleHair](https://github.com/FunkinCrew/Funkin/pull/3037))
|
||||
|
||||
## New Contributors for 0.5.0
|
||||
* @Flooferland made their first contribution in [#2942](https://github.com/FunkinCrew/Funkin/pull/2942)
|
||||
* @anysad made their first contribution in [#3007](https://github.com/FunkinCrew/Funkin/pull/3007)
|
||||
* @Sword352 made their first contribution in [#2310](https://github.com/FunkinCrew/Funkin/pull/2310)
|
||||
* @KarimAkra made their first contribution in [#2713](https://github.com/FunkinCrew/Funkin/pull/2713)
|
||||
* @JVNpixels made their first contribution in [#2873](https://github.com/FunkinCrew/Funkin/pull/2873)
|
||||
* @AppleHair made their first contribution in [#2742](https://github.com/FunkinCrew/Funkin/pull/2742)
|
||||
* @7oltan made their first contribution in [#3035](https://github.com/FunkinCrew/Funkin/pull/3035)
|
||||
* @cyn0x8 made their first contribution in [#2494](https://github.com/FunkinCrew/Funkin/pull/2494)
|
||||
|
||||
|
||||
## [0.4.1] - 2024-06-12
|
||||
### Added
|
||||
@@ -88,7 +242,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Changed
|
||||
- Highscores and ranks are now saved separately, which fixes the issue where people would overwrite their saves with higher scores,
|
||||
which would remove their rank if they had a lower one.
|
||||
- A-Bot speaker now reacts to the user's volume preference on desktop (thanks to [M7theguy for the issue report/suggestion](https://github.com/FunkinCrew/Funkin/issues/2744)!)
|
||||
- A-Bot speaker now reacts to the user's volume preference on desktop ([thanks to M7theguy for the issue report/suggestion](https://github.com/FunkinCrew/Funkin/issues/2744)!)
|
||||
- On Freeplay, heart icons are shifted to the right when you favorite a song that has no rank on it.
|
||||
- Only play `scrollMenu` sound effect when there's a real change on the freeplay menu ([thanks gamerbross for the PR!](https://github.com/FunkinCrew/Funkin/pull/2741))
|
||||
- Gave antialiasing to the edge of the dad graphic on Freeplay
|
||||
@@ -96,20 +250,25 @@ which would remove their rank if they had a lower one.
|
||||
- Made several chart revisions
|
||||
- Re-enabled custom camera events in Roses (Erect/Nightmare)
|
||||
- Tweaked the chart for Lit Up (Hard)
|
||||
- Corrected the difficulty ratings for M.I.L.F. (Easy/Normal/Hard)
|
||||
- Corrected the difficulty ratings for M.I.L.F (Easy/Normal/Hard)
|
||||
### Fixed
|
||||
- Fixed an issue in the controls menu where some control binds would overlap their names
|
||||
- Fixed crash when attempting to exit the gameover screen when also attempting to retry the song ([thanks DMMaster636 for the PR!](https://github.com/FunkinCrew/Funkin/pull/2709))
|
||||
- Fix botplay sustain release bug ([thanks Hundrec!](Fix botplay sustain release bug #2683))
|
||||
- Fix botplay sustain release bug ([thanks Hundrec!](https://github.com/FunkinCrew/Funkin/pull/2683))
|
||||
- Fix for the camera not pausing during a gameplay pause ([thanks gamerbross!](https://github.com/FunkinCrew/Funkin/pull/2684))
|
||||
- Fixed issue where Pico's gameplay sprite would unintentionally appear on the gameover screen when dying on 2Hot from an explosion
|
||||
- Fixed issue where Pico's gameplay sprite would unintentionally appear on the gameover screen when dying on 2hot from an explosion
|
||||
- Freeplay previews properly fade volume during the BF idle animation
|
||||
- Fixed bug where Dadbattle incorrectly appeared as Dadbattle Erect when returning to freeplay on Hard
|
||||
- Fixed 2Hot not appearing under the "#" category in Freeplay menu
|
||||
- Fixed bug where DadBattle incorrectly appeared as DadBattle Erect when returning to freeplay on Hard
|
||||
- Fixed 2hot not appearing under the "#" category in Freeplay menu
|
||||
- Fixed a bug where the Chart Editor would crash when attempting to select an event with the Event toolbox open
|
||||
- Improved offsets for Pico and Tankman opponents so they don't slide around as much.
|
||||
- Fixed the black "temp" graphic on freeplay from being incorrectly sized / masked, now it's identical to the dad freeplay graphic
|
||||
|
||||
## New Contributors for 0.4.1
|
||||
* @DMMaster636 made their first contribution in [#2709](https://github.com/FunkinCrew/Funkin/pull/2709)
|
||||
* @Hundrec made their first contribution in [#2683](https://github.com/FunkinCrew/Funkin/pull/2683)
|
||||
|
||||
|
||||
## [0.4.0] - 2024-06-06
|
||||
### Added
|
||||
- 2 new Erect remixes, Eggnog and Satin Panties. Check them out from the Freeplay menu!
|
||||
@@ -118,14 +277,14 @@ which would remove their rank if they had a lower one.
|
||||
- Freeplay now plays a preview of songs when you hover over them.
|
||||
- Added a Charter field to the chart format, to allow for crediting the creator of a level's chart.
|
||||
- You can see who charted a song from the Pause menu.
|
||||
- Added a new Scroll Speed chart event to change the note speed mid-song (thanks burgerballs!)
|
||||
- Added a new Scroll Speed chart event to change the note speed mid-song ([thanks Burgerballs!](https://github.com/FunkinCrew/Funkin/pull/2409))
|
||||
### Changed
|
||||
- Tweaked the charts for several songs:
|
||||
- Tutorial (increased the note speed slightly)
|
||||
- Spookeez
|
||||
- Monster
|
||||
- Winter Horrorland
|
||||
- M.I.L.F.
|
||||
- M.I.L.F
|
||||
- Senpai (increased the note speed)
|
||||
- Roses
|
||||
- Thorns (increased the note speed slightly)
|
||||
@@ -135,55 +294,75 @@ which would remove their rank if they had a lower one.
|
||||
- Favorite songs marked in Freeplay are now stored between sessions.
|
||||
- The Freeplay easter eggs are now easier to see.
|
||||
- In the event that the game cannot load your save data, it will now perform a backup before clearing it, so that we can try to repair it in the future.
|
||||
- Custom note styles are now properly supported for songs; add new notestyles via JSON, then select it for use from the Chart Editor Metadata toolbox. (thanks Keoiki!)
|
||||
- Health icons now support a Winning frame without requiring a spritesheet, simply include a third frame in the icon file. (thanks gamerbross!)
|
||||
- Custom note styles are now properly supported for songs; add new notestyles via JSON, then select it for use from the Chart Editor Metadata toolbox. ([thanks Keoiki!](https://github.com/FunkinCrew/Funkin/pull/2581))
|
||||
- Health icons now support a Winning frame without requiring a spritesheet, simply include a third frame in the icon file. ([thanks gamerbross!](https://github.com/FunkinCrew/Funkin/pull/2593))
|
||||
- Remember that for more complex behaviors such as animations or transitions, you should use an XML file to define each frame.
|
||||
- Improved the Event Toolbox in the Chart Editor; dropdowns are now bigger, include search field, and display elements in alphabetical order rather than a random order.
|
||||
### Fixed
|
||||
- Fixed an issue where Nene's visualizer would not play on Desktop builds
|
||||
- Fixed a bug where the game would silently fail to load saves on HTML5
|
||||
- Fixed some bugs with the props on the Story Menu not bopping properly
|
||||
- Additional fixes to the Loading bar on HTML5 (thanks lemz1!)
|
||||
- Fixed several bugs with the TitleState, including missing music when returning from the Main Menu (thanks gamerbross!)
|
||||
- Fixed a camera bug in the Main Menu (thanks richTrash21!)
|
||||
- Fixed a bug where changing difficulties in Story mode wouldn't update the score (thanks sectorA!)
|
||||
- Fixed a crash in Freeplay caused by a level referencing an invalid song (thanks gamerbross!)
|
||||
- Fixed a bug where pressing the volume keys would stop the Toy commercial (thanks gamerbross!)
|
||||
- Fixed a bug where the Chart Editor Playtest would crash when losing (thanks gamerbross!)
|
||||
- Fixed a bug where hold notes would display improperly in the Chart Editor when downscroll was enabled for gameplay (thanks gamerbross!)
|
||||
- Fixed a bug where hold notes would be positioned wrong on downscroll (thanks MaybeMaru!)
|
||||
- Removed a large number of unused imports to optimize builds (thanks Ethan-makes-music!)
|
||||
- Improved debug logging for unscripted stages (thanks gamerbross!)
|
||||
- Additional fixes to the Loading bar on HTML5 ([thanks lemz1!](https://github.com/FunkinCrew/Funkin/pull/2553))
|
||||
- Fixed several bugs with the TitleState, including missing music when returning from the Main Menu ([thanks gamerbross!](https://github.com/FunkinCrew/Funkin/pull/2539))
|
||||
- Fixed a camera bug in the Main Menu ([thanks richTrash21!](https://github.com/FunkinCrew/Funkin/pull/2576))
|
||||
- Fixed a bug where changing difficulties in Story mode wouldn't update the score ([thanks sector-a!](https://github.com/FunkinCrew/Funkin/pull/2585))
|
||||
- Fixed a crash in Freeplay caused by a level referencing an invalid song ([thanks gamerbross!](https://github.com/FunkinCrew/Funkin/pull/2457))
|
||||
- Fixed a bug where pressing the volume keys would stop the Toy commercial ([thanks gamerbross!](https://github.com/FunkinCrew/Funkin/pull/2540))
|
||||
- Fixed a bug where the Chart Editor Playtest would crash when losing ([thanks gamerbross!](https://github.com/FunkinCrew/Funkin/pull/2518))
|
||||
- Fixed a bug where hold notes would display improperly in the Chart Editor when downscroll was enabled for gameplay ([thanks gamerbross!](https://github.com/FunkinCrew/Funkin/pull/2565))
|
||||
- Fixed a bug where hold notes would be positioned wrong on downscroll ([thanks MaybeMaru!](https://github.com/FunkinCrew/Funkin/pull/2488))
|
||||
- Removed a large number of unused imports to optimize builds ([thanks Ethan-makes-music!](https://github.com/FunkinCrew/Funkin/pull/2624))
|
||||
- Improved debug logging for unscripted stages ([thanks gamerbross!](https://github.com/FunkinCrew/Funkin/pull/2603))
|
||||
- Fixed a crash on Linux caused by an old version of hxCodec ([thanks Noobz4Life!](https://github.com/FunkinCrew/Funkin/pull/2472))
|
||||
- Optimized animation handling for characters ([thanks richTrash21!](https://github.com/FunkinCrew/Funkin/pull/2493))
|
||||
- Made improvements to compiling documentation (thanks gedehari!)
|
||||
- Fixed a crash on Linux caused by an old version of hxCodec (thanks Noobz4Life!)
|
||||
- Optimized animation handling for characters (thanks richTrash21!)
|
||||
- Made improvements to compiling documentation (thanks gedehari!)
|
||||
- Fixed an issue where the Chart Editor would use an incorrect instrumental on imported Legacy songs (thanks gamerbross!)
|
||||
- Fixed a camera bug in the Main Menu (thanks richTrash21!)
|
||||
- Fixed a bug where opening the game from the command line would crash the preloader (thanks NotHyper474!)
|
||||
- Fixed a bug where characters would sometimes use the wrong scale value (thanks PurSnake!)
|
||||
- Fixed an issue where the Chart Editor would use an incorrect instrumental on imported Legacy songs ([thanks gamerbross!](https://github.com/FunkinCrew/Funkin/pull/2604))
|
||||
- Fixed a bug where opening the game from the command line would crash the preloader ([thanks NotHyper-474!](https://github.com/FunkinCrew/Funkin/pull/2629))
|
||||
- Fixed a bug where characters would sometimes use the wrong scale value ([thanks PurSnake!](https://github.com/FunkinCrew/Funkin/pull/2610))
|
||||
- Additional bug fixes and optimizations.
|
||||
|
||||
## New Contributors for 0.4.0
|
||||
* @Keoiki made their first contribution in [#2581](https://github.com/FunkinCrew/Funkin/pull/2581)
|
||||
* @richTrash21 made their first contribution in [#2493](https://github.com/FunkinCrew/Funkin/pull/2493)
|
||||
* @sector-a made their first contribution in [#2585](https://github.com/FunkinCrew/Funkin/pull/2585)
|
||||
* @MaybeMaru made their first contribution in [#2488](https://github.com/FunkinCrew/Funkin/pull/2488)
|
||||
* @Ethan-makes-music made their first contribution in [#2624](https://github.com/FunkinCrew/Funkin/pull/2624)
|
||||
* @Noobz4Life made their first contribution in [#2472](https://github.com/FunkinCrew/Funkin/pull/2472)
|
||||
* @NotHyper-474 made their first contribution in [#2629](https://github.com/FunkinCrew/Funkin/pull/2629)
|
||||
* @PurSnake made their first contribution in [#2610](https://github.com/FunkinCrew/Funkin/pull/2610)
|
||||
|
||||
|
||||
## [0.3.3] - 2024-05-14
|
||||
### Changed
|
||||
- Cleaned up some code in `PlayAnimationSongEvent.hx` (thanks BurgerBalls!)
|
||||
- Cleaned up some code in `PlayAnimationSongEvent.hx` ([thanks Burgerballs!](https://github.com/FunkinCrew/Funkin/pull/2308))
|
||||
### Fixed
|
||||
- Fixes to the Loading bar on HTML5 (thanks lemz1!)
|
||||
- Don't allow any more inputs when exiting freeplay (thanks gamerbros!)
|
||||
- Fixed using mouse wheel to scroll on freeplay (thanks JugieNoob!)
|
||||
- Fixed the reset's of the health icons, score, and notes when re-entering gameplay from gameover (thanks ImCodist!)
|
||||
- Fixed the chart editor character selector's hitbox width (thanks MadBear422!)
|
||||
- Fixed camera stutter once a wipe transition to the Main Menu completes (thanks ImCodist!)
|
||||
- Fixed an issue where hold note would be invisible for a single frame (thanks ImCodist!)
|
||||
- Fix tween accumulation on title screen when pressing Y multiple times (thanks TheGaloXx!)
|
||||
- Fixes to the Loading bar on HTML5 ([thanks lemz1!](https://github.com/FunkinCrew/Funkin/pull/2499))
|
||||
- Don't allow any more inputs when exiting freeplay ([thanks gamerbross!](https://github.com/FunkinCrew/Funkin/pull/2470))
|
||||
- Fixed using mouse wheel to scroll on freeplay ([thanks JugieNoob!](https://github.com/FunkinCrew/Funkin/pull/2466))
|
||||
- Fixed the resets of the health icons, score, and notes when re-entering gameplay from gameover ([thanks ImCodist!](https://github.com/FunkinCrew/Funkin/pull/2390))
|
||||
- Fixed the chart editor character selector's hitbox width ([thanks MadBear422!](https://github.com/FunkinCrew/Funkin/pull/2370))
|
||||
- Fixed camera stutter once a wipe transition to the Main Menu completes ([thanks ImCodist!](https://github.com/FunkinCrew/Funkin/pull/2315))
|
||||
- Fixed an issue where hold note would be invisible for a single frame ([thanks ImCodist!](https://github.com/FunkinCrew/Funkin/pull/2309))
|
||||
- Fix tween accumulation on title screen when pressing Y multiple times ([thanks TheGaloXx!](https://github.com/FunkinCrew/Funkin/pull/2300))
|
||||
- Fix a crash when querying FlxG.state in the crash handler
|
||||
- Fix for a game over easter egg so you don't accidentally exit it when viewing
|
||||
- Fix an issue where the Freeplay menu never displays 100% clear
|
||||
- Fix an issue where Weekend 1 Pico attempted to retrieve a missing asset.
|
||||
- Fix an issue where duplicate keybinds would be stoed, potentially causing a crash
|
||||
- Chart debug key now properly returns you to the previous chart editor session if you were playtesting a chart (thanks nebulazorua!)
|
||||
- Fix an issue where duplicate keybinds would be stored, potentially causing a crash
|
||||
- Chart debug key now properly returns you to the previous chart editor session if you were playtesting a chart ([thanks nebulazorua!](https://github.com/FunkinCrew/Funkin/pull/2323))
|
||||
- Fix a crash on Freeplay found on AMD graphics cards
|
||||
|
||||
## New Contributors for 0.3.3
|
||||
* @Burgerballs made their first contribution in [#2308](https://github.com/FunkinCrew/Funkin/pull/2308)
|
||||
* @lemz1 made their first contribution in [#2499](https://github.com/FunkinCrew/Funkin/pull/2499)
|
||||
* @gamerbross made their first contribution in [#2470](https://github.com/FunkinCrew/Funkin/pull/2470)
|
||||
* @JugieNoob made their first contribution in [#2466](https://github.com/FunkinCrew/Funkin/pull/2466)
|
||||
* @MadBear422 made their first contribution in [#2370](https://github.com/FunkinCrew/Funkin/pull/2370)
|
||||
* @ImCodist made their first contribution in [#2309](https://github.com/FunkinCrew/Funkin/pull/2309)
|
||||
* @TheGaloXx made their first contribution in [#2300](https://github.com/FunkinCrew/Funkin/pull/2300)
|
||||
* @nebulazorua made their first contribution in [#2323](https://github.com/FunkinCrew/Funkin/pull/2323)
|
||||
|
||||
|
||||
## [0.3.2] - 2024-05-03
|
||||
### Added
|
||||
- Added `,` and `.` keybinds to the Chart Editor. These place Focus Camera events at the playhead, for the opponent and player respectively.
|
||||
@@ -209,6 +388,7 @@ which would remove their rank if they had a lower one.
|
||||
### Removed
|
||||
- Removed some unused `.txt` files in the `assets/data` folder.
|
||||
|
||||
|
||||
## [0.3.1] - 2024-05-01
|
||||
### Changed
|
||||
- Ensure the Git commit hash always displays in the log files.
|
||||
@@ -225,6 +405,7 @@ which would remove their rank if they had a lower one.
|
||||
- When exiting from a song into freeplay, main menu no longer takes inputs unintentionally (aka issues with merch links opening up when selecting songs)
|
||||
- Fix for arrow keys causing web browser page scroll
|
||||
|
||||
|
||||
## [0.3.0] - 2024-04-30
|
||||
### Added
|
||||
- New Story Level: Weekend 1, starting Pico, Darnell, and Nene.
|
||||
@@ -258,6 +439,7 @@ which would remove their rank if they had a lower one.
|
||||
### Fixed
|
||||
- 17 quadrillion bugs across hundreds of PRs.
|
||||
|
||||
|
||||
## [0.2.8] - 2021-04-18 (note, this one is iffy cuz we slacked wit it lol!)
|
||||
### Added
|
||||
- TANKMAN! 3 NEW SONGS BY KAWAISPRITE (UGH, GUNS, STRESS)! Charting help by MtH!
|
||||
@@ -272,6 +454,7 @@ which would remove their rank if they had a lower one.
|
||||
### Fixed
|
||||
- That one random note on Bopeebo
|
||||
|
||||
|
||||
## [0.2.7.1] - 2021-02-14
|
||||
### Added
|
||||
- Easter eggs
|
||||
@@ -282,13 +465,14 @@ which would remove their rank if they had a lower one.
|
||||
- Offset of the Newgrounds logo on boot screen.
|
||||
- Made the changelog txt so it can be opened easier by normal people who don't have a markdown reader (most normal people);
|
||||
### Fixed
|
||||
- Fixed crashes on Week 6 story mode dialogue if spam too fast ([Thanks to Lotusotho for the Pull Request!](https://github.com/ninjamuffin99/Funkin/pull/357))
|
||||
- Fixed crashes on Week 6 story mode dialogue if spam too fast ([Thanks to Lotusotho for the Pull Request!](https://github.com/FunkinCrew/Funkin/pull/357))
|
||||
- Should show intro credits on desktop versions of the game more consistently
|
||||
- Layering on Week 4 songs with GF and the LIMO LOL HOW TF I MISS THIS
|
||||
- Chart's and chart editor now support changeBPM, GOD BLESS MTH FOR THIS ONE I BEEN STRUGGLIN WIT THAT SINCE OCTOBER LMAO ([GOD BLESS MTH](https://github.com/ninjamuffin99/Funkin/pull/382))
|
||||
- Fixed sustain note trails ALSO THANKS TO MTH U A REAL ONE ([MTH VERY POWERFUL](https://github.com/ninjamuffin99/Funkin/pull/415))
|
||||
- Chart's and chart editor now support changeBPM, GOD BLESS MTH FOR THIS ONE I BEEN STRUGGLIN WIT THAT SINCE OCTOBER LMAO ([GOD BLESS MTH](https://github.com/FunkinCrew/Funkin/pull/382))
|
||||
- Fixed sustain note trails ALSO THANKS TO MTH U A REAL ONE ([MTH VERY POWERFUL](https://github.com/FunkinCrew/Funkin/pull/415))
|
||||
- Antialiasing on the skyscraper lights
|
||||
|
||||
|
||||
## [0.2.7] - 2021-02-02
|
||||
### Added
|
||||
- PIXEL DAY UPDATE LOL 1 WEEK LATER
|
||||
@@ -300,12 +484,13 @@ which would remove their rank if they had a lower one.
|
||||
- Made it so you lose sliiiightly more health when you miss a note.
|
||||
- Removed the default HaxeFlixel pause screen when the game window loses focus, can get screenshots of the game easier hehehe
|
||||
### Fixed
|
||||
- Idle animation bug with BF christmas and BF hair blow sprites ([Thanks to Injourn for the Pull Request!](https://github.com/ninjamuffin99/Funkin/pull/237))
|
||||
- Idle animation bug with BF christmas and BF hair blow sprites ([Thanks to Injourn for the Pull Request!](https://github.com/FunkinCrew/Funkin/pull/237))
|
||||
|
||||
|
||||
## [0.2.6] - 2021-01-20
|
||||
### Added
|
||||
- 3 NEW CHRISTMAS SONGS. 2 BY KAWAISPRITE, 1 BY BASSETFILMS!!!!! BF WITH DRIP! SANTA HANGIN OUT!
|
||||
- Enemy icons change when they you are winning a lot ([Thanks to pahaze for the Pull Request!](https://github.com/ninjamuffin99/Funkin/pull/138))
|
||||
- Enemy icons change when they you are winning a lot ([Thanks to pahaze for the Pull Request!](https://github.com/FunkinCrew/Funkin/pull/138))
|
||||
- Holding CTRL in charting editor places notes on both sides
|
||||
- Q and E changes sustain lengths in note editor
|
||||
- Other charting editor workflow improvements
|
||||
@@ -317,11 +502,12 @@ which would remove their rank if they had a lower one.
|
||||
- Removed APE
|
||||
### Fixed
|
||||
- Maybe fixed double notes / jump notes. Need to tweak it for balance, but should open things up for cooler charts in the future.
|
||||
- Old Verison popup screen weirdness ([Thanks to gedehari for the Pull Request!](https://github.com/ninjamuffin99/Funkin/pull/155))
|
||||
- Song no longer loops when finishing the song. ([Thanks Injourn for the Pull Request!](https://github.com/ninjamuffin99/Funkin/pull/132))
|
||||
- Old Verison popup screen weirdness ([Thanks to gedehari for the Pull Request!](https://github.com/FunkinCrew/Funkin/pull/155))
|
||||
- Song no longer loops when finishing the song. ([Thanks Injourn for the Pull Request!](https://github.com/FunkinCrew/Funkin/pull/132))
|
||||
- Screen wipe being cut off in the limo/mom stage. Should fill the whole screen now.
|
||||
- Boyfriend animations on hold notes, and pressing on repeating notes should behave differently
|
||||
|
||||
|
||||
## [0.2.5] - 2020-12-27
|
||||
### Added
|
||||
- MOMMY GF, 3 NEW ASS SONGS BY KAWAISPRITE, NEW ART BY PHANTOMARCADE,WOOOOOOAH!!!!
|
||||
@@ -336,8 +522,9 @@ which would remove their rank if they had a lower one.
|
||||
- Mouse is now visible in note editor
|
||||
### Fixed
|
||||
- Crash when playing Week 3 and then playing a non-week 3 song
|
||||
- When pausing music at the start, it doesn't continue the song anyways. ([shoutouts gedehari for the Pull Request!](https://github.com/ninjamuffin99/Funkin/pull/48))
|
||||
- IDK i think backing out of song menu should play main menu songs again hehe ([shoutouts gedehari for the Pull Request!](https://github.com/ninjamuffin99/Funkin/pull/48))
|
||||
- When pausing music at the start, it doesn't continue the song anyways. ([shoutouts gedehari for the Pull Request!](https://github.com/FunkinCrew/Funkin/pull/48))
|
||||
- IDK i think backing out of song menu should play main menu songs again hehe ([shoutouts gedehari for the Pull Request!](https://github.com/FunkinCrew/Funkin/pull/48))
|
||||
|
||||
|
||||
## [0.2.4] - 2020-12-11
|
||||
### Added
|
||||
@@ -347,17 +534,19 @@ which would remove their rank if they had a lower one.
|
||||
### Changed
|
||||
- Made it less punishing to ATTEMPT to hit a note and miss, rather than let it pass you
|
||||
### Fixed
|
||||
- Song desync of you paused and unpaused frequently ([shoutouts SonicBlam](https://github.com/ninjamuffin99/Funkin/issues/37))
|
||||
- Song desync of you paused and unpaused frequently ([shoutouts SonicBlam](https://github.com/FunkinCrew/Funkin/issues/37))
|
||||
- Animation offsets when GF is scared
|
||||
|
||||
|
||||
## [0.2.3] - 2020-12-04
|
||||
### Added
|
||||
- More intro texts
|
||||
### Fixed
|
||||
- Exploit where you could potentially give yourself a high score via the debug menu
|
||||
- Issue/bug where you could spam the confirm button on the story menu ([shoutouts lotusotho for the CODE contribution/pull request!](https://github.com/ninjamuffin99/Funkin/pull/19))
|
||||
- Glitch where if you never would lose health if you missed a note on a fast song (shoutouts [MrDulfin](https://github.com/ninjamuffin99/Funkin/issues/10), [HotSauceBurritos](https://github.com/ninjamuffin99/Funkin/issues/13) and [LobsterMango](https://lobstermango.newgrounds.com))
|
||||
- Fixed tiny note bleed over thingies (shoutouts [lotusotho](https://github.com/ninjamuffin99/Funkin/pull/24))
|
||||
- Issue/bug where you could spam the confirm button on the story menu ([shoutouts lotusotho for the CODE contribution/pull request!](https://github.com/FunkinCrew/Funkin/pull/19))
|
||||
- Glitch where if you never would lose health if you missed a note on a fast song (shoutouts [MrDulfin](https://github.com/FunkinCrew/Funkin/issues/10), [HotSauceBurritos](https://github.com/FunkinCrew/Funkin/issues/13) and [LobsterMango](https://lobstermango.newgrounds.com))
|
||||
- Fixed tiny note bleed over thingies (shoutouts [lotusotho](https://github.com/FunkinCrew/Funkin/pull/24))
|
||||
|
||||
|
||||
## [0.2.2] - 2020-11-20
|
||||
### Added
|
||||
@@ -366,16 +555,16 @@ which would remove their rank if they had a lower one.
|
||||
- Score now shows mid-song.
|
||||
- Menu on pause screen! Can resume, and restart song, or go back to main menu.
|
||||
- New music made for pause menu!
|
||||
|
||||
### Changed
|
||||
- Moved all the intro texts to its own txt file instead of being hardcoded, this allows for much easier customization. File is in the data folder, called "introText.txt", follow the format in there and you're probably good to go!
|
||||
### Fixed
|
||||
- Fixed soft lock when pausing on song finish ([shoutouts gedehari](https://github.com/ninjamuffin99/Funkin/issues/15))
|
||||
- Think I fixed issue that led to in-game scores being off by 2 ([shoutouts Mike](https://github.com/ninjamuffin99/Funkin/issues/4))
|
||||
- Should have fixed the 1 frame note appearance thing. ([shoutouts Mike](https://github.com/ninjamuffin99/Funkin/issues/6))
|
||||
- Fixed soft lock when pausing on song finish ([shoutouts gedehari](https://github.com/FunkinCrew/Funkin/issues/15))
|
||||
- Think I fixed issue that led to in-game scores being off by 2 ([shoutouts Mike](https://github.com/FunkinCrew/Funkin/issues/4))
|
||||
- Should have fixed the 1 frame note appearance thing. ([shoutouts Mike](https://github.com/FunkinCrew/Funkin/issues/6))
|
||||
- Cleaned up some charting on South on hard mode
|
||||
- Fixed some animation timings, should feel both better to play, and watch. (shoutouts Dave/Ivan lol)
|
||||
- Animation issue where GF would freak out on the title screen if you returned to it([shoutouts MultiXIII](https://github.com/ninjamuffin99/Funkin/issues/12)).
|
||||
- Animation issue where GF would freak out on the title screen if you returned to it([shoutouts MultiXIII](https://github.com/FunkinCrew/Funkin/issues/12)).
|
||||
|
||||
|
||||
## [0.2.1.2] - 2020-11-06
|
||||
### Fixed
|
||||
@@ -383,10 +572,12 @@ which would remove their rank if they had a lower one.
|
||||
- Difficulty on storymode and in freeplay scores
|
||||
- Hard mode difficulty on campaign levels have been fixed
|
||||
|
||||
|
||||
## [0.2.1.1] - 2020-11-06
|
||||
### Fixed
|
||||
- Week 2 not unlocking properly
|
||||
|
||||
|
||||
## [0.2.1] - 2020-11-06
|
||||
### Added
|
||||
- Scores to the freeplay menu
|
||||
@@ -394,18 +585,18 @@ which would remove their rank if they had a lower one.
|
||||
- Lightning effect in Spooky stages
|
||||
- Campaign scores, can now compete on scoreboards for campaign!
|
||||
- Can now change difficulties in Freeplay mode
|
||||
|
||||
### Changed
|
||||
- Balanced out Normal mode for the harder songs(Dadbattle and Spookeez, not South yet). Should be much easier all around.
|
||||
- Balanced out Normal mode for the harder songs(DadBattle and Spookeez, not South yet). Should be much easier all around.
|
||||
- Put tutorial in it's own 'week', so that if you want to play week 1, you don't have to play the tutorial.
|
||||
|
||||
### Fixed
|
||||
- One of the charting bits on South and Spookeez during the intro.
|
||||
|
||||
|
||||
## [0.2.0] - 2020-11-01
|
||||
### Added
|
||||
- Uhh Newgrounds release lolol I always lose track of shit.
|
||||
|
||||
|
||||
## [0.1.0] - 2020-10-05
|
||||
### Added
|
||||
- Uh, everything. This the game's initial gamejam release. We put it out
|
||||
|
||||
+3
-3
@@ -4,14 +4,14 @@ Code style is enforced using Visual Studio Code extensions.
|
||||
|
||||
## .hx
|
||||
Formatting is handled by the `nadako.vshaxe` extension, which includes Haxe Formatter.
|
||||
Haxe Formatter automatically resolves issues such as intentation style and line breaks, and can be configured in `hxformat.json`.
|
||||
Haxe Formatter automatically resolves issues such as indentation style and line breaks, and can be configured in `hxformat.json`.
|
||||
|
||||
Code Quality is handled by the `vshaxe.haxe-checkstyle` extension, which includes Haxe Checkstyle.
|
||||
|
||||
### Haxe Checkstyle Notes
|
||||
* Checks can be escalated to display as different serverities in the Problems window.
|
||||
* Checks can be escalated to display as different severities in the Problems window.
|
||||
* Checks can be disabled by setting the severity to `IGNORE`.
|
||||
* `IndentationCharacter` checks what is used to indent, `Indentation` checks how deep the intentation is.
|
||||
* `IndentationCharacter` checks what is used to indent, `Indentation` checks how deep the indentation is.
|
||||
* `CommentedOutCode` check is in place because old code should be retrieved via Git history.
|
||||
* TODO items: Enable these one-by-one and fix them to improve the overall code quality.
|
||||
- Reconfigure `MethodLength`
|
||||
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
# RIGHT NOW THE MODS FOLDER DOES NOT WORK ENTIRELY JUST YET!!!
|
||||
## THIS IS WORK IN PROGRESS!!!
|
||||
|
||||
# QUICK AND DIRTY MOD GUIDE
|
||||
|
||||
With the 0.2.6 update, I added a bit of a slightly nicer mod support backend.
|
||||
|
||||
It's POLYMOD, which is made by Lars Doucet: https://github.com/larsiusprime/polymod
|
||||
|
||||
You may have noticed that there's a new folder in the assets. MODS. Within it you will see 2 files. modList.txt, and a folder called introMod.
|
||||
modList.txt will load any folder into the game. Put the folder you want to load into a new line in modList.txt, and reboot the game.
|
||||
|
||||
Now you may be wondering, what do I put in the folder? Well later down it'll get a bit more complicated, especially as I'll make the IN-GAME mod loader nicer.
|
||||
@@ -17,9 +17,13 @@ To learn how to install the necessary dependencies and compile the game from sou
|
||||
|
||||
Please check out our [Contributor's guide](./CONTRIBUTORS.md) on how you can actively participate in the development of Friday Night Funkin'.
|
||||
|
||||
# Modding
|
||||
|
||||
Feel free to start learning to mod the game by reading our [documentation](https://funkincrew.github.io/funkin-modding-docs/) and guide to modding.
|
||||
|
||||
# Credits and Special Thanks
|
||||
|
||||
Full credits can be found in-game, or wherever the credits.json file is.
|
||||
Full credits can be found in-game, or in the `credits.json` file which is located [here](https://github.com/FunkinCrew/funkin.assets/blob/main/exclude/data/credits.json).
|
||||
|
||||
## Programming
|
||||
- [ninjamuffin99](https://twitter.com/ninja_muffin99) - Lead Programmer
|
||||
|
||||
+1
-1
Submodule art updated: 0dee03f11a...fbd3e3df77
+1
-1
Submodule assets updated: bc7009b424...4abd6cc06e
+90
@@ -0,0 +1,90 @@
|
||||
# git-cliff ~ configuration file
|
||||
# https://git-cliff.org/docs/configuration
|
||||
|
||||
[remote.github]
|
||||
owner = "FunkinCrew"
|
||||
repo = "Funkin"
|
||||
|
||||
# To bypass "you have reached your rate limit!", you can input a github token either as an environment variable
|
||||
# or alongside the `git cliff` command like so `git cliff --github-token TOKEN_HERE`
|
||||
# Personally I like to use the github cli `gh` tool to get a token
|
||||
# `git cliff --github-token $(gh auth token)`
|
||||
|
||||
[changelog]
|
||||
# template for the changelog body
|
||||
# https://keats.github.io/tera/docs/#introduction
|
||||
body = """
|
||||
{%- macro remote_url() -%}
|
||||
https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
|
||||
{%- endmacro -%}
|
||||
|
||||
{% if version -%}
|
||||
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
||||
{% else -%}
|
||||
## [Unreleased]
|
||||
{% endif -%}
|
||||
|
||||
{% for group, commits in commits | group_by(attribute="group") %}
|
||||
### {{ group | upper_first }}
|
||||
{%- for commit in commits %}
|
||||
- {% if commit.scope %}({{commit.scope}}) {% endif %}{{ commit.message | split(pat="\n") | first | upper_first | trim }} \
|
||||
([{{ commit.id | truncate(length=7, end="") }}]({{self::remote_url()}}/commit/{{ commit.id }})) -\
|
||||
{% if commit.remote.username %} by @{{ commit.remote.username }}
|
||||
{%- elif commit.author.name %} by {{ commit.author.name }}
|
||||
{%- endif -%}
|
||||
{% if commit.remote.pr_number %} in \
|
||||
[#{{ commit.remote.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.remote.pr_number }}) \
|
||||
{%- endif -%}
|
||||
{% if commit.links | length != 0 %}\
|
||||
[#{{ commit.links }}]({{ self::remote_url() }}/pull/{{ commit.remote.pr_number }}) \
|
||||
{%- endif -%}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
||||
{%- if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %}
|
||||
## New Contributors
|
||||
{%- endif -%}
|
||||
|
||||
{% for contributor in github.contributors | filter(attribute="is_first_time", value=true) %}
|
||||
* @{{ contributor.username }} made their first contribution
|
||||
{%- if contributor.pr_number %} in \
|
||||
[#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \
|
||||
{%- endif %}
|
||||
{%- endfor %}\n
|
||||
"""
|
||||
|
||||
# remove the leading and trailing whitespace from the templates
|
||||
trim = true
|
||||
|
||||
[git]
|
||||
# parse the commits based on https://www.conventionalcommits.org
|
||||
conventional_commits = true
|
||||
# filter out the commits that are not conventional
|
||||
filter_unconventional = true
|
||||
# regex for preprocessing the commit messages
|
||||
commit_preprocessors = [
|
||||
# remove issue numbers from commits
|
||||
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "" },
|
||||
]
|
||||
# regex for parsing and grouping commits
|
||||
commit_parsers = [
|
||||
{ message = "^docs", group = "Changed", scope="docs" },
|
||||
{ message = "^[a|A]dd", group = "Added" },
|
||||
{ message = "^[s|S]upport", group = "Added" },
|
||||
{ message = "^[r|R]emove", group = "Removed" },
|
||||
{ message = "^.*: add", group = "Added" },
|
||||
{ message = "^.*: support", group = "Added" },
|
||||
{ message = "^.*: remove", group = "Removed" },
|
||||
{ message = "^.*: delete", group = "Removed" },
|
||||
{ message = "^test", group = "Fixed" },
|
||||
{ message = "^fix", group = "Fixed" },
|
||||
{ message = "^.*: fix", group = "Fixed" },
|
||||
{ message = "^.*", group = "Changed" },
|
||||
]
|
||||
# filter out the commits that are not matched by commit parsers
|
||||
filter_commits = false
|
||||
# sort the tags topologically
|
||||
topo_order = false
|
||||
# sort the commits inside sections by oldest/newest order
|
||||
sort_commits = "newest"
|
||||
|
||||
+4
-6
@@ -34,16 +34,14 @@ There are several useful build flags you can add to a build to affect how it wor
|
||||
- `-DGITHUB_BUILD` will enable in-game debug functions (such as the ability to time travel in a song by pressing `PgUp`/`PgDn`), without enabling the other stuff
|
||||
- `-DFEATURE_POLYMOD_MODS` or `-DNO_FEATURE_POLYMOD_MODS` to forcibly enable or disable modding support.
|
||||
- `-DREDIRECT_ASSETS_FOLDER` or `-DNO_REDIRECT_ASSETS_FOLDER` to forcibly enable or disable asset redirection.
|
||||
- This feature causes the game to load exported assets from the project's assets folder rather than the exported one. Great for fast iteration, but the game
|
||||
- This feature causes the game to load exported assets from the project's assets folder rather than the exported one. Great for fast iteration, but the game will break if you try to zip it up and send it to someone, so it's disabled for release builds.
|
||||
- `-DFEATURE_DISCORD_RPC` or `-DNO_FEATURE_DISCORD_RPC` to forcibly enable or disable support for Discord Rich Presence.
|
||||
- `-DFEATURE_VIDEO_PLAYBACK` or `-DNO_FEATURE_VIDEO_PLAYBACK` to forcibly enable or disable video cutscene support.
|
||||
- `-DFEATURE_CHART_EDITOR` or `-DNO_FEATURE_CHART_EDITOR` to forcibly enable or disable the chart editor in the Debug menu.
|
||||
- `-DFEATURE_SCREENSHOTS` or `-DNO_FEATURE_SCREENSHOTS` to forcibly enable or disable the screenshots feature.
|
||||
- `-DFEATURE_STAGE_EDITOR` to forcibly enable the experimental stage editor.
|
||||
- `-DFEATURE_GHOST_TAPPING` to forcibly enable an experimental gameplay change to the anti-mash system.
|
||||
|
||||
# Troubleshooting - GO THROUGH THESE STEPS BEFORE OPENING ISSUES ON GITHUB!
|
||||
# Troubleshooting
|
||||
|
||||
- During the cloning process, you may experience an error along the lines of `error: RPC failed; curl 92 HTTP/2 stream 0 was not closed cleanly: PROTOCOL_ERROR (err 1)` due to poor connectivity. A common fix is to run ` git config --global http.postBuffer 4096M`.
|
||||
- Make sure your game directory has an `assets` folder! If it's missing, copy the path to your `funkin` folder and run `cd the\path\you\copied`. Then follow the guide starting from **Step 4**.
|
||||
- Check that your `assets` folder is not empty! If it is, go back to **Step 4** and follow the guide from there.
|
||||
- The compilation process often fails due to having the wrong versions of the required libraries. Many errors can be resolved by deleting the `.haxelib` folder and following the guide starting from **Step 5**.
|
||||
If you experience any issues during the compilation process, DO NOT open an issue on GitHub. Instead, check the [Troubleshooting Guide](TROUBLESHOOTING.md) for steps on how to resolve common problems.
|
||||
|
||||
@@ -15,8 +15,8 @@ Most of this functionality is only available on debug builds of the game!
|
||||
- `2`: ***GAIN HEALTH***: Debug function, add 10% to the player's health.
|
||||
- `3`: ***LOSE HEALTH***: Debug function, subtract 5% to the player's health.
|
||||
- `9`: NEATO!
|
||||
- `PAGEUP` (MacOS: `Fn-Up`): ***FORWARDS TIME TRAVEL****: Move forward by 2 sections. Hold SHIFT to move forward by 20 sections instead.
|
||||
- `PAGEDOWN` (MacOS: `Fn-Down`): ***BACKWARDS TIME TRAVEL****: Move backward by 2 sections. Hold SHIFT to move backward by 20 sections instead.
|
||||
- `PAGEUP` (MacOS: `Fn-Up`): ***FORWARDS TIME TRAVEL***: Move forward by 2 sections. Hold SHIFT to move forward by 20 sections instead.
|
||||
- `PAGEDOWN` (MacOS: `Fn-Down`): ***BACKWARDS TIME TRAVEL***: Move backward by 2 sections. Hold SHIFT to move backward by 20 sections instead.
|
||||
|
||||
## **Freeplay State**
|
||||
- `F` (Freeplay Menu) - Move to Favorites
|
||||
@@ -27,5 +27,5 @@ Most of this functionality is only available on debug builds of the game!
|
||||
- `Y` - WOAH
|
||||
|
||||
## **Main Menu**
|
||||
- `~`: ***DEBUG****: Opens a menu to access the Chart Editor and other work-in-progress editors. Rebindable in the options menu.
|
||||
- `~`: ***DEBUG***: Opens a menu to access the Chart Editor and other work-in-progress editors. Rebindable in the options menu.
|
||||
- `CTRL-ALT-SHIFT-W`: ***ALL ACCESS***: Unlocks all songs in Freeplay. Only available on debug builds.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Troubleshooting Common Issues
|
||||
# Troubleshooting Common Compilation Issues
|
||||
|
||||
- Weird macro error with a very tall call stack: Restart Visual Studio Code
|
||||
- NOTE: This is caused by Polymod somewhere, and seems to only occur when there is another compile error somewhere in the program. There is a bounty up for it.
|
||||
@@ -13,3 +13,11 @@
|
||||
|
||||
- `LINK : fatal error LNK1201: error writing to program database ''; check for insufficient disk space, invalid path, or insufficient privilege`
|
||||
- This error occurs if the PDB file located in your `export` folder is in use or exceeds 4 GB. Try deleting the `export` folder and building again from scratch.
|
||||
|
||||
- `error: RPC failed; curl 92 HTTP/2 stream 0 was not closed cleanly: PROTOCOL_ERROR (err 1)`
|
||||
- This error can happen during cloning as a result of poor network connectivity. A common fix is to run ` git config --global http.postBuffer 4096M` in your terminal.
|
||||
|
||||
- Repository is missing an `assets` folder, or `assets` folder is empty.
|
||||
- You did not clone the repository correctly! Copy the path to your `funkin` folder and run `cd the\path\you\copied`. Then follow the compilation guide starting from **Step 4**.
|
||||
|
||||
- Other compilation issues may be caused by installing bad library versions. Try deleting the `.haxelib` folder and following the guide starting from **Step 5**.
|
||||
@@ -6,7 +6,7 @@
|
||||
"name": "EliteMasterEric"
|
||||
}
|
||||
],
|
||||
"api_version": "0.1.0",
|
||||
"api_version": "0.5.0",
|
||||
"mod_version": "1.0.0",
|
||||
"license": "Apache-2.0"
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"name": "EliteMasterEric"
|
||||
}
|
||||
],
|
||||
"api_version": "0.1.0",
|
||||
"api_version": "0.5.0",
|
||||
"mod_version": "1.0.0",
|
||||
"license": "Apache-2.0"
|
||||
}
|
||||
|
||||
@@ -7,25 +7,18 @@
|
||||
"ref": "a1eab7b9bf507b87200a3341719054fe427f3b15",
|
||||
"url": "https://github.com/FunkinCrew/FlxPartialSound.git"
|
||||
},
|
||||
{
|
||||
"name": "discord_rpc",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "2d83fa863ef0c1eace5f1cf67c3ac315d1a3a8a5",
|
||||
"url": "https://github.com/FunkinCrew/linc_discord-rpc"
|
||||
},
|
||||
{
|
||||
"name": "flixel",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "f2b090d6c608471e730b051c8ee22b8b378964b1",
|
||||
"ref": "ffa691cb2d2d81de35b900a4411e4062ac84ab58",
|
||||
"url": "https://github.com/FunkinCrew/flixel"
|
||||
},
|
||||
{
|
||||
"name": "flixel-addons",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "9c6fb47968e894eb36bf10e94725cd7640c49281",
|
||||
"ref": "7424db4e9164ff46f224a7c47de1b732d2542dd7",
|
||||
"url": "https://github.com/FunkinCrew/flixel-addons"
|
||||
},
|
||||
{
|
||||
@@ -35,13 +28,6 @@
|
||||
"ref": "951a0103a17bfa55eed86703ce50b4fb0d7590bc",
|
||||
"url": "https://github.com/FunkinCrew/flixel-text-input"
|
||||
},
|
||||
{
|
||||
"name": "flixel-ui",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "27f1ba626f80a6282fa8a187115e79a4a2133dc2",
|
||||
"url": "https://github.com/HaxeFlixel/flixel-ui"
|
||||
},
|
||||
{
|
||||
"name": "flxanimate",
|
||||
"type": "git",
|
||||
@@ -77,14 +63,14 @@
|
||||
"name": "haxeui-core",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "22f7c5a8ffca90d4677cffd6e570f53761709fbc",
|
||||
"ref": "51c23588614397089a5ce182cddea729f0be6fa0",
|
||||
"url": "https://github.com/haxeui/haxeui-core"
|
||||
},
|
||||
{
|
||||
"name": "haxeui-flixel",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "28bb710d0ae5d94b5108787593052165be43b980",
|
||||
"ref": "da27e833947f32ef007ed11f523aa5524f5a5d54",
|
||||
"url": "https://github.com/haxeui/haxeui-flixel"
|
||||
},
|
||||
{
|
||||
@@ -105,8 +91,8 @@
|
||||
"name": "hxcpp",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "904ea40643b050a5a154c5e4c33a83fd2aec18b1",
|
||||
"url": "https://github.com/HaxeFoundation/hxcpp"
|
||||
"ref": "c6bac3d6c7d683f25104296b2f4c50f8c90b8349",
|
||||
"url": "https://github.com/cortex-engine/hxcpp"
|
||||
},
|
||||
{
|
||||
"name": "hxcpp-debug-server",
|
||||
@@ -115,6 +101,13 @@
|
||||
"ref": "147294123f983e35f50a966741474438069a7a8f",
|
||||
"url": "https://github.com/FunkinCrew/hxcpp-debugger"
|
||||
},
|
||||
{
|
||||
"name": "hxdiscord_rpc",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "82c47ecc1a454b7dd644e4fcac7e91155f176dec",
|
||||
"url": "https://github.com/FunkinCrew/hxdiscord_rpc"
|
||||
},
|
||||
{
|
||||
"name": "hxjsonast",
|
||||
"type": "git",
|
||||
|
||||
+50
-6
@@ -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.0";
|
||||
static final VERSION:String = "0.5.3";
|
||||
|
||||
/**
|
||||
* The game's name. Used as the default window title.
|
||||
@@ -149,6 +149,21 @@ class Project extends HXProject {
|
||||
*/
|
||||
static final FEATURE_DEBUG_FUNCTIONS:FeatureFlag = "FEATURE_DEBUG_FUNCTIONS";
|
||||
|
||||
/**
|
||||
* `-DFEATURE_DEBUG_TRACY`
|
||||
* If this flag is enabled, the game will have the necessary hooks for the Tracy profiler.
|
||||
* Only enable this if you're using the correct fork of Haxe to support this.
|
||||
* @see https://github.com/HaxeFoundation/hxcpp/pull/1153
|
||||
*/
|
||||
static final FEATURE_DEBUG_TRACY:FeatureFlag = "FEATURE_DEBUG_TRACY";
|
||||
|
||||
/**
|
||||
* `-DFEATURE_LOG_TRACE`
|
||||
* If this flag is enabled, the game will print debug traces to the console.
|
||||
* Disable to improve performance a bunch.
|
||||
*/
|
||||
static final FEATURE_LOG_TRACE:FeatureFlag = "FEATURE_LOG_TRACE";
|
||||
|
||||
/**
|
||||
* `-DFEATURE_DISCORD_RPC`
|
||||
* If this flag is enabled, the game will enable the Discord Remote Procedure Call library.
|
||||
@@ -199,6 +214,12 @@ class Project extends HXProject {
|
||||
*/
|
||||
static final FEATURE_OPEN_URL:FeatureFlag = "FEATURE_OPEN_URL";
|
||||
|
||||
/**
|
||||
* `-DFEATURE_SCREENSHOTS`
|
||||
* If this flag is enabled, the game will support the screenshots feature.
|
||||
*/
|
||||
static final FEATURE_SCREENSHOTS:FeatureFlag = "FEATURE_SCREENSHOTS";
|
||||
|
||||
/**
|
||||
* `-DFEATURE_CHART_EDITOR`
|
||||
* If this flag is enabled, the Chart Editor will be accessible from the debug menu.
|
||||
@@ -439,7 +460,6 @@ class Project extends HXProject {
|
||||
|
||||
// Should be false unless explicitly requested.
|
||||
GITHUB_BUILD.apply(this, false);
|
||||
FEATURE_STAGE_EDITOR.apply(this, false);
|
||||
FEATURE_NEWGROUNDS.apply(this, false);
|
||||
FEATURE_GHOST_TAPPING.apply(this, false);
|
||||
|
||||
@@ -450,17 +470,21 @@ class Project extends HXProject {
|
||||
FEATURE_FUNKVIS.apply(this, true);
|
||||
FEATURE_PARTIAL_SOUNDS.apply(this, true);
|
||||
FEATURE_VIDEO_PLAYBACK.apply(this, true);
|
||||
FEATURE_STAGE_EDITOR.apply(this, true);
|
||||
|
||||
// Should be true on debug builds or if GITHUB_BUILD is enabled.
|
||||
FEATURE_DEBUG_FUNCTIONS.apply(this, isDebug() || GITHUB_BUILD.isEnabled(this));
|
||||
|
||||
// Got a lot of complains about this being turned off by default on some builds.
|
||||
// TODO: Look into ways to optimize logging (maybe by using a thread pool?)
|
||||
FEATURE_LOG_TRACE.apply(this, true);
|
||||
|
||||
// Should default to true on workspace builds and false on release builds.
|
||||
REDIRECT_ASSETS_FOLDER.apply(this, isDebug() && isDesktop());
|
||||
|
||||
// Should be true on release, non-tester builds.
|
||||
// Should be true on desktop, release, non-tester builds.
|
||||
// We don't want testers to accidentally leak songs to their Discord friends!
|
||||
// TODO: Re-enable this.
|
||||
FEATURE_DISCORD_RPC.apply(this, false && !FEATURE_DEBUG_FUNCTIONS.isEnabled(this));
|
||||
FEATURE_DISCORD_RPC.apply(this, isDesktop() && !FEATURE_DEBUG_FUNCTIONS.isEnabled(this));
|
||||
|
||||
// Should be true only on web builds.
|
||||
// Audio context issues only exist there.
|
||||
@@ -478,6 +502,10 @@ class Project extends HXProject {
|
||||
// Should be true except on web builds.
|
||||
// Chart editor doesn't work there.
|
||||
FEATURE_CHART_EDITOR.apply(this, !isWeb());
|
||||
|
||||
// Should be true except on web builds.
|
||||
// Screenshots doesn't work there.
|
||||
FEATURE_SCREENSHOTS.apply(this, !isWeb());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -516,6 +544,16 @@ class Project extends HXProject {
|
||||
|
||||
// Cleaner looking compiler errors.
|
||||
setHaxedef("message.reporting", "pretty");
|
||||
|
||||
if (FEATURE_DEBUG_TRACY.isEnabled(this)) {
|
||||
setHaxedef("HXCPP_TELEMETRY"); // Enable telemetry
|
||||
setHaxedef("HXCPP_TRACY"); // Enable Tracy telemetry
|
||||
setHaxedef("HXCPP_TRACY_MEMORY"); // Track memory allocations
|
||||
setHaxedef("HXCPP_TRACY_ON_DEMAND"); // Only collect telemetry when Tracy is open and reachable
|
||||
// setHaxedef("HXCPP_TRACY_INCLUDE_CALLSTACKS"); // Inspect callstacks per zone, inflating telemetry data
|
||||
|
||||
setHaxedef("absolute-paths"); // Fix source locations so Tracy can see them
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -618,7 +656,7 @@ class Project extends HXProject {
|
||||
}
|
||||
|
||||
if (FEATURE_DISCORD_RPC.isEnabled(this)) {
|
||||
addHaxelib('discord_rpc'); // Discord API
|
||||
addHaxelib('hxdiscord_rpc'); // Discord API
|
||||
}
|
||||
|
||||
if (FEATURE_NEWGROUNDS.isEnabled(this)) {
|
||||
@@ -941,6 +979,8 @@ class Project extends HXProject {
|
||||
var commitHash:String = process.stdout.readLine();
|
||||
var commitHashSplice:String = commitHash.substr(0, 7);
|
||||
|
||||
process.close();
|
||||
|
||||
return commitHashSplice;
|
||||
}
|
||||
|
||||
@@ -955,6 +995,8 @@ class Project extends HXProject {
|
||||
|
||||
var branchName:String = branchProcess.stdout.readLine();
|
||||
|
||||
branchProcess.close();
|
||||
|
||||
return branchName;
|
||||
}
|
||||
|
||||
@@ -979,6 +1021,8 @@ class Project extends HXProject {
|
||||
}
|
||||
}
|
||||
|
||||
branchProcess.close();
|
||||
|
||||
return output.length > 0;
|
||||
}
|
||||
|
||||
|
||||
+2
-9
@@ -2,6 +2,7 @@ package;
|
||||
|
||||
import flixel.FlxGame;
|
||||
import flixel.FlxState;
|
||||
import funkin.Preferences;
|
||||
import funkin.util.logging.CrashHandler;
|
||||
import funkin.ui.debug.MemoryCounter;
|
||||
import funkin.save.Save;
|
||||
@@ -22,12 +23,6 @@ class Main extends Sprite
|
||||
var gameHeight:Int = 720; // Height of the game in pixels (might be less / more in actual pixels depending on your zoom).
|
||||
var initialState:Class<FlxState> = funkin.InitState; // The FlxState the game starts with.
|
||||
var zoom:Float = -1; // If -1, zoom is automatically calculated to fit the window dimensions.
|
||||
#if (web || CHEEMS)
|
||||
var framerate:Int = 60; // How many frames per second the game should run at.
|
||||
#else
|
||||
// TODO: This should probably be in the options menu?
|
||||
var framerate:Int = 144; // How many frames per second the game should run at.
|
||||
#end
|
||||
var skipSplash:Bool = true; // Whether to skip the flixel splash screen that appears in release mode.
|
||||
var startFullscreen:Bool = false; // Whether to start the game in fullscreen on desktop targets
|
||||
|
||||
@@ -109,7 +104,7 @@ class Main extends Sprite
|
||||
|
||||
// George recommends binding the save before FlxGame is created.
|
||||
Save.load();
|
||||
var game:FlxGame = new FlxGame(gameWidth, gameHeight, initialState, framerate, framerate, skipSplash, startFullscreen);
|
||||
var game:FlxGame = new FlxGame(gameWidth, gameHeight, initialState, Preferences.framerate, Preferences.framerate, skipSplash, startFullscreen);
|
||||
|
||||
// FlxG.game._customSoundTray wants just the class, it calls new from
|
||||
// create() in there, which gets called when it's added to stage
|
||||
@@ -123,8 +118,6 @@ class Main extends Sprite
|
||||
game.debugger.interaction.addTool(new funkin.util.TrackerToolButtonUtil());
|
||||
#end
|
||||
|
||||
addChild(fpsCounter);
|
||||
|
||||
#if hxcpp_debug_server
|
||||
trace('hxcpp_debug_server is enabled! You can now connect to the game with a debugger.');
|
||||
#else
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Pulled from Tracey profiler PR
|
||||
* @see https://github.com/HaxeFoundation/haxe/pull/11772
|
||||
*/
|
||||
|
||||
package cpp.vm.tracy;
|
||||
|
||||
#if (!HXCPP_TRACY)
|
||||
#error "This class cannot be used without -D HXCPP_TRACY"
|
||||
#end
|
||||
enum abstract PlotFormatType(cpp.UInt8) from cpp.UInt8 to cpp.UInt8
|
||||
{
|
||||
var Number = 0;
|
||||
var Memory = 1;
|
||||
var Percentage = 2;
|
||||
}
|
||||
|
||||
@:include('hx/TelemetryTracy.h')
|
||||
extern class Native_TracyProfiler
|
||||
{
|
||||
/**
|
||||
Mark a frame. Call this at the end of each frame loop.
|
||||
**/
|
||||
@:native('::__hxcpp_tracy_framemark')
|
||||
public static function frameMark():Void;
|
||||
|
||||
/**
|
||||
Print a message into Tracy's log.
|
||||
**/
|
||||
@:native('::__hxcpp_tracy_message')
|
||||
public static function message(_msg:String, ?_color:Int = 0x000000):Void;
|
||||
|
||||
/**
|
||||
Tracy can collect additional information about the profiled application,
|
||||
which will be available in the trace description.
|
||||
This can include data such as the source repository revision,
|
||||
the application’s environment (dev/prod), etc.
|
||||
**/
|
||||
@:native('::__hxcpp_tracy_message_app_info')
|
||||
public static function messageAppInfo(_info:String):Void;
|
||||
|
||||
/**
|
||||
Plot a named value to tracy. This will generate a graph in the profiler for you.
|
||||
**/
|
||||
@:native('::__hxcpp_tracy_plot')
|
||||
public static function plot(_name:String, _val:cpp.Float32):Void;
|
||||
|
||||
/**
|
||||
Configure how values are plotted and displayed.
|
||||
**/
|
||||
@:native('::__hxcpp_tracy_plot_config')
|
||||
public static function plotConfig(_name:String, _format:PlotFormatType, ?_step:Bool = false, ?_fill:Bool = false, ?_color:Int = 0x000000):Void;
|
||||
|
||||
/**
|
||||
Set a name for the current thread this function is called in. Supply an optional groupHint so threads become grouped in Tracy's UI.
|
||||
**/
|
||||
@:native('::__hxcpp_tracy_set_thread_name_and_group')
|
||||
public static function setThreadName(_name:String, ?_groupHint:Int = 1):Void;
|
||||
|
||||
/**
|
||||
Create a custom named scoped zone in your code.
|
||||
**/
|
||||
@:native('HXCPP_TRACY_ZONE')
|
||||
public static function zoneScoped(_name:String):Void;
|
||||
}
|
||||
|
||||
#if (scriptable || cppia)
|
||||
class Cppia_TracyProfiler
|
||||
{
|
||||
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.frameMark)
|
||||
public static function frameMark()
|
||||
Native_TracyProfiler.frameMark();
|
||||
|
||||
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.message)
|
||||
public static function message(_msg:String, ?_color:Int = 0x000000)
|
||||
Native_TracyProfiler.message(_msg, _color);
|
||||
|
||||
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.messageAppInfo)
|
||||
public static function messageAppInfo(_info:String)
|
||||
Native_TracyProfiler.messageAppInfo(_info);
|
||||
|
||||
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.plot)
|
||||
public static function plot(_name:String, _val:Float)
|
||||
Native_TracyProfiler.plot(_name, _val);
|
||||
|
||||
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.plotConfig)
|
||||
public static function plotConfig(_name:String, _format:PlotFormatType, ?_step:Bool = false, ?_fill:Bool = false, ?_color:Int = 0x000000)
|
||||
Native_TracyProfiler.plotConfig(_name, _format, _step, _fill, _color);
|
||||
|
||||
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.setThreadName)
|
||||
public static function setThreadName(_name:String, ?_groupHint:Int = 1)
|
||||
Native_TracyProfiler.setThreadName(_name, _groupHint);
|
||||
|
||||
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.zoneScoped)
|
||||
public static function zoneScoped(_name:String)
|
||||
Native_TracyProfiler.zoneScoped(_name);
|
||||
}
|
||||
|
||||
typedef TracyProfiler = Cppia_TracyProfiler;
|
||||
#else
|
||||
typedef TracyProfiler = Native_TracyProfiler;
|
||||
#end
|
||||
+136
-13
@@ -1,38 +1,161 @@
|
||||
package funkin;
|
||||
|
||||
import openfl.utils.Future;
|
||||
|
||||
/**
|
||||
* A wrapper around `openfl.utils.Assets` which disallows access to the harmful functions.
|
||||
* Later we'll add Funkin-specific caching to this.
|
||||
*/
|
||||
class Assets
|
||||
{
|
||||
public static function getText(path:String):String
|
||||
/**
|
||||
* Get the file system path for an asset
|
||||
* @param path The asset path to load from, relative to the assets folder
|
||||
* @return The path to the asset on the file system
|
||||
*/
|
||||
public static function getPath(path:String):String
|
||||
{
|
||||
return openfl.utils.Assets.getText(path);
|
||||
}
|
||||
|
||||
public static function getMusic(path:String):openfl.media.Sound
|
||||
{
|
||||
return openfl.utils.Assets.getMusic(path);
|
||||
}
|
||||
|
||||
public static function getBitmapData(path:String):openfl.display.BitmapData
|
||||
{
|
||||
return openfl.utils.Assets.getBitmapData(path);
|
||||
return openfl.utils.Assets.getPath(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load bytes from an asset
|
||||
* May cause stutters or throw an error if the asset is not cached
|
||||
* @param path The asset path to load from
|
||||
* @return The byte contents of the file
|
||||
*/
|
||||
public static function getBytes(path:String):haxe.io.Bytes
|
||||
{
|
||||
return openfl.utils.Assets.getBytes(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load bytes from an asset asynchronously
|
||||
* @param path The asset path to load from
|
||||
* @return A future which promises to return the byte contents of the file
|
||||
*/
|
||||
public static function loadBytes(path:String):Future<openfl.utils.ByteArray>
|
||||
{
|
||||
return openfl.utils.Assets.loadBytes(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load text from an asset.
|
||||
* May cause stutters or throw an error if the asset is not cached
|
||||
* @param path The asset path to load from
|
||||
* @return The text contents of the file
|
||||
*/
|
||||
public static function getText(path:String):String
|
||||
{
|
||||
return openfl.utils.Assets.getText(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load text from an asset asynchronously
|
||||
* @param path The asset path to load from
|
||||
* @return A future which promises to return the text contents of the file
|
||||
*/
|
||||
public static function loadText(path:String):Future<String>
|
||||
{
|
||||
return openfl.utils.Assets.loadText(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a Sound file from an asset
|
||||
* May cause stutters or throw an error if the asset is not cached
|
||||
* @param path The asset path to load from
|
||||
* @return The loaded sound
|
||||
*/
|
||||
public static function getSound(path:String):openfl.media.Sound
|
||||
{
|
||||
return openfl.utils.Assets.getSound(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a Sound file from an asset asynchronously
|
||||
* @param path The asset path to load from
|
||||
* @return A future which promises to return the loaded sound
|
||||
*/
|
||||
public static function loadSound(path:String):Future<openfl.media.Sound>
|
||||
{
|
||||
return openfl.utils.Assets.loadSound(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a Sound file from an asset, with optimizations specific to long-duration music
|
||||
* May cause stutters or throw an error if the asset is not cached
|
||||
* @param path The asset path to load from
|
||||
* @return The loaded sound
|
||||
*/
|
||||
public static function getMusic(path:String):openfl.media.Sound
|
||||
{
|
||||
return openfl.utils.Assets.getMusic(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a Sound file from an asset asynchronously, with optimizations specific to long-duration music
|
||||
* @param path The asset path to load from
|
||||
* @return A future which promises to return the loaded sound
|
||||
*/
|
||||
public static function loadMusic(path:String):Future<openfl.media.Sound>
|
||||
{
|
||||
return openfl.utils.Assets.loadMusic(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a Bitmap from an asset
|
||||
* May cause stutters or throw an error if the asset is not cached
|
||||
* @param path The asset path to load from
|
||||
* @return The loaded Bitmap image
|
||||
*/
|
||||
public static function getBitmapData(path:String):openfl.display.BitmapData
|
||||
{
|
||||
return openfl.utils.Assets.getBitmapData(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a Bitmap from an asset asynchronously
|
||||
* @param path The asset path to load from
|
||||
* @return The future which promises to return the loaded Bitmap image
|
||||
*/
|
||||
public static function loadBitmapData(path:String):Future<openfl.display.BitmapData>
|
||||
{
|
||||
return openfl.utils.Assets.loadBitmapData(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the given asset of the given type exists.
|
||||
* @param path The asset path to check
|
||||
* @param type The asset type to check
|
||||
* @return Whether the asset exists
|
||||
*/
|
||||
public static function exists(path:String, ?type:openfl.utils.AssetType):Bool
|
||||
{
|
||||
return openfl.utils.Assets.exists(path, type);
|
||||
}
|
||||
|
||||
public static function list(type:openfl.utils.AssetType):Array<String>
|
||||
/**
|
||||
* Retrieve a list of all assets of the given type
|
||||
* @param type The asset type to check
|
||||
* @return A list of asset paths
|
||||
*/
|
||||
public static function list(?type:openfl.utils.AssetType):Array<String>
|
||||
{
|
||||
return openfl.utils.Assets.list(type);
|
||||
}
|
||||
|
||||
public static function hasLibrary(name:String):Bool
|
||||
{
|
||||
return openfl.utils.Assets.hasLibrary(name);
|
||||
}
|
||||
|
||||
public static function getLibrary(name:String):lime.utils.AssetLibrary
|
||||
{
|
||||
return openfl.utils.Assets.getLibrary(name);
|
||||
}
|
||||
|
||||
public static function loadLibrary(name:String):Future<openfl.utils.AssetLibrary>
|
||||
{
|
||||
return openfl.utils.Assets.loadLibrary(name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,6 +275,13 @@ class Conductor
|
||||
return Save.instance.options.audioVisualOffset;
|
||||
}
|
||||
|
||||
public var combinedOffset(get, never):Float;
|
||||
|
||||
function get_combinedOffset():Float
|
||||
{
|
||||
return instrumentalOffset + audioVisualOffset + inputOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of beats in a measure. May be fractional depending on the time signature.
|
||||
*/
|
||||
@@ -397,20 +404,32 @@ class Conductor
|
||||
*/
|
||||
public function update(?songPos:Float, applyOffsets:Bool = true, forceDispatch:Bool = false)
|
||||
{
|
||||
var currentTime:Float = (FlxG.sound.music != null) ? FlxG.sound.music.time : 0.0;
|
||||
var currentLength:Float = (FlxG.sound.music != null) ? FlxG.sound.music.length : 0.0;
|
||||
|
||||
if (songPos == null)
|
||||
{
|
||||
songPos = (FlxG.sound.music != null) ? FlxG.sound.music.time : 0.0;
|
||||
songPos = currentTime;
|
||||
}
|
||||
|
||||
// Take into account instrumental and file format song offsets.
|
||||
songPos += applyOffsets ? (instrumentalOffset + formatOffset + audioVisualOffset) : 0;
|
||||
songPos += applyOffsets ? (combinedOffset) : 0;
|
||||
|
||||
var oldMeasure:Float = this.currentMeasure;
|
||||
var oldBeat:Float = this.currentBeat;
|
||||
var oldStep:Float = this.currentStep;
|
||||
|
||||
// If the song is playing, limit the song position to the length of the song or beginning of the song.
|
||||
if (FlxG.sound.music != null && FlxG.sound.music.playing)
|
||||
{
|
||||
this.songPosition = Math.min(currentLength, Math.max(0, songPos));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.songPosition = songPos;
|
||||
}
|
||||
|
||||
// Set the song position we are at (for purposes of calculating note positions, etc).
|
||||
this.songPosition = songPos;
|
||||
|
||||
currentTimeChange = timeChanges[0];
|
||||
if (this.songPosition > 0.0)
|
||||
@@ -430,7 +449,8 @@ class Conductor
|
||||
else if (currentTimeChange != null && this.songPosition > 0.0)
|
||||
{
|
||||
// roundDecimal prevents representing 8 as 7.9999999
|
||||
this.currentStepTime = FlxMath.roundDecimal((currentTimeChange.beatTime * Constants.STEPS_PER_BEAT) + (this.songPosition - currentTimeChange.timeStamp) / stepLengthMs, 6);
|
||||
this.currentStepTime = FlxMath.roundDecimal((currentTimeChange.beatTime * Constants.STEPS_PER_BEAT)
|
||||
+ (this.songPosition - currentTimeChange.timeStamp) / stepLengthMs, 6);
|
||||
this.currentBeatTime = currentStepTime / Constants.STEPS_PER_BEAT;
|
||||
this.currentMeasureTime = currentStepTime / stepsPerMeasure;
|
||||
this.currentStep = Math.floor(currentStepTime);
|
||||
|
||||
+22
-20
@@ -1,42 +1,42 @@
|
||||
package funkin;
|
||||
|
||||
import funkin.data.freeplay.player.PlayerRegistry;
|
||||
import funkin.ui.debug.charting.ChartEditorState;
|
||||
import funkin.ui.transition.LoadingState;
|
||||
import flixel.FlxState;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
import flixel.addons.transition.FlxTransitionSprite.GraphicTransTileDiamond;
|
||||
import flixel.addons.transition.TransitionData;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.FlxState;
|
||||
import flixel.graphics.FlxGraphic;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.math.FlxRect;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.system.debug.log.LogStyle;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.util.macro.MacroUtil;
|
||||
import funkin.util.WindowUtil;
|
||||
import funkin.play.PlayStatePlaylist;
|
||||
import openfl.display.BitmapData;
|
||||
import funkin.data.story.level.LevelRegistry;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.data.freeplay.style.FreeplayStyleRegistry;
|
||||
import funkin.data.event.SongEventRegistry;
|
||||
import funkin.data.stage.StageRegistry;
|
||||
import funkin.data.dialogue.conversation.ConversationRegistry;
|
||||
import funkin.data.dialogue.dialoguebox.DialogueBoxRegistry;
|
||||
import funkin.data.dialogue.speaker.SpeakerRegistry;
|
||||
import funkin.data.freeplay.album.AlbumRegistry;
|
||||
import funkin.data.freeplay.player.PlayerRegistry;
|
||||
import funkin.data.freeplay.style.FreeplayStyleRegistry;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.data.event.SongEventRegistry;
|
||||
import funkin.data.stage.StageRegistry;
|
||||
import funkin.data.story.level.LevelRegistry;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import funkin.play.notes.notekind.NoteKindManager;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.play.PlayStatePlaylist;
|
||||
import funkin.ui.debug.charting.ChartEditorState;
|
||||
import funkin.ui.title.TitleState;
|
||||
import funkin.ui.transition.LoadingState;
|
||||
import funkin.util.CLIUtil;
|
||||
import funkin.util.CLIUtil.CLIParams;
|
||||
import funkin.util.macro.MacroUtil;
|
||||
import funkin.util.TimerUtil;
|
||||
import funkin.util.TrackerUtil;
|
||||
import funkin.util.WindowUtil;
|
||||
import openfl.display.BitmapData;
|
||||
#if FEATURE_DISCORD_RPC
|
||||
import Discord.DiscordClient;
|
||||
import funkin.api.discord.DiscordClient;
|
||||
#end
|
||||
|
||||
/**
|
||||
@@ -125,10 +125,10 @@ class InitState extends FlxState
|
||||
// DISCORD API SETUP
|
||||
//
|
||||
#if FEATURE_DISCORD_RPC
|
||||
DiscordClient.initialize();
|
||||
DiscordClient.instance.init();
|
||||
|
||||
Application.current.onExit.add(function(exitCode) {
|
||||
DiscordClient.shutdown();
|
||||
lime.app.Application.current.onExit.add(function(exitCode) {
|
||||
DiscordClient.instance.shutdown();
|
||||
});
|
||||
#end
|
||||
|
||||
@@ -148,10 +148,12 @@ class InitState extends FlxState
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
funkin.util.plugins.MemoryGCPlugin.initialize();
|
||||
#end
|
||||
#if FEATURE_SCREENSHOTS
|
||||
funkin.util.plugins.ScreenshotPlugin.initialize();
|
||||
#end
|
||||
funkin.util.plugins.EvacuateDebugPlugin.initialize();
|
||||
funkin.util.plugins.ForceCrashPlugin.initialize();
|
||||
funkin.util.plugins.ReloadAssetsDebugPlugin.initialize();
|
||||
funkin.util.plugins.ScreenshotPlugin.initialize();
|
||||
funkin.util.plugins.VolumePlugin.initialize();
|
||||
funkin.util.plugins.WatchPlugin.initialize();
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package funkin;
|
||||
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import openfl.utils.AssetType;
|
||||
import openfl.utils.Assets as OpenFlAssets;
|
||||
|
||||
/**
|
||||
* A core class which handles determining asset paths.
|
||||
@@ -44,11 +43,11 @@ class Paths
|
||||
if (currentLevel != null)
|
||||
{
|
||||
var levelPath:String = getLibraryPathForce(file, currentLevel);
|
||||
if (OpenFlAssets.exists(levelPath, type)) return levelPath;
|
||||
if (Assets.exists(levelPath, type)) return levelPath;
|
||||
}
|
||||
|
||||
var levelPath:String = getLibraryPathForce(file, 'shared');
|
||||
if (OpenFlAssets.exists(levelPath, type)) return levelPath;
|
||||
if (Assets.exists(levelPath, type)) return levelPath;
|
||||
|
||||
return getPreloadPath(file);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,36 @@ import funkin.save.Save;
|
||||
class Preferences
|
||||
{
|
||||
/**
|
||||
* Whether some particularly fowl language is displayed.
|
||||
* FPS
|
||||
* @default `60`
|
||||
*/
|
||||
public static var framerate(get, set):Int;
|
||||
|
||||
static function get_framerate():Int
|
||||
{
|
||||
#if web
|
||||
return 60;
|
||||
#else
|
||||
return Save?.instance?.options?.framerate ?? 60;
|
||||
#end
|
||||
}
|
||||
|
||||
static function set_framerate(value:Int):Int
|
||||
{
|
||||
#if web
|
||||
return 60;
|
||||
#else
|
||||
var save:Save = Save.instance;
|
||||
save.options.framerate = value;
|
||||
save.flush();
|
||||
FlxG.updateFramerate = value;
|
||||
FlxG.drawFramerate = value;
|
||||
return value;
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether some particularly foul language is displayed.
|
||||
* @default `true`
|
||||
*/
|
||||
public static var naughtyness(get, set):Bool;
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
package funkin.api.discord;
|
||||
|
||||
import Sys.sleep;
|
||||
#if FEATURE_DISCORD_RPC
|
||||
import discord_rpc.DiscordRpc;
|
||||
#end
|
||||
|
||||
class DiscordClient
|
||||
{
|
||||
#if FEATURE_DISCORD_RPC
|
||||
public function new()
|
||||
{
|
||||
trace("Discord Client starting...");
|
||||
DiscordRpc.start(
|
||||
{
|
||||
clientID: "814588678700924999",
|
||||
onReady: onReady,
|
||||
onError: onError,
|
||||
onDisconnected: onDisconnected
|
||||
});
|
||||
trace("Discord Client started.");
|
||||
|
||||
while (true)
|
||||
{
|
||||
DiscordRpc.process();
|
||||
sleep(2);
|
||||
// trace("Discord Client Update");
|
||||
}
|
||||
|
||||
DiscordRpc.shutdown();
|
||||
}
|
||||
|
||||
public static function shutdown()
|
||||
{
|
||||
DiscordRpc.shutdown();
|
||||
}
|
||||
|
||||
static function onReady()
|
||||
{
|
||||
DiscordRpc.presence(
|
||||
{
|
||||
details: "In the Menus",
|
||||
state: null,
|
||||
largeImageKey: 'icon',
|
||||
largeImageText: "Friday Night Funkin'"
|
||||
});
|
||||
}
|
||||
|
||||
static function onError(_code:Int, _message:String)
|
||||
{
|
||||
trace('Error! $_code : $_message');
|
||||
}
|
||||
|
||||
static function onDisconnected(_code:Int, _message:String)
|
||||
{
|
||||
trace('Disconnected! $_code : $_message');
|
||||
}
|
||||
|
||||
public static function initialize()
|
||||
{
|
||||
var DiscordDaemon = sys.thread.Thread.create(() -> {
|
||||
new DiscordClient();
|
||||
});
|
||||
trace("Discord Client initialized");
|
||||
}
|
||||
|
||||
public static function changePresence(details:String, ?state:String, ?smallImageKey:String, ?hasStartTimestamp:Bool, ?endTimestamp:Float)
|
||||
{
|
||||
var startTimestamp:Float = if (hasStartTimestamp) Date.now().getTime() else 0;
|
||||
|
||||
if (endTimestamp > 0)
|
||||
{
|
||||
endTimestamp = startTimestamp + endTimestamp;
|
||||
}
|
||||
|
||||
DiscordRpc.presence(
|
||||
{
|
||||
details: details,
|
||||
state: state,
|
||||
largeImageKey: 'icon',
|
||||
largeImageText: "Friday Night Funkin'",
|
||||
smallImageKey: smallImageKey,
|
||||
// Obtained times are in milliseconds so they are divided so Discord can use it
|
||||
startTimestamp: Std.int(startTimestamp / 1000),
|
||||
endTimestamp: Std.int(endTimestamp / 1000)
|
||||
});
|
||||
|
||||
// trace('Discord RPC Updated. Arguments: $details, $state, $smallImageKey, $hasStartTimestamp, $endTimestamp');
|
||||
}
|
||||
#end
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
package funkin.api.discord;
|
||||
|
||||
#if FEATURE_DISCORD_RPC
|
||||
import hxdiscord_rpc.Discord;
|
||||
import hxdiscord_rpc.Types;
|
||||
import sys.thread.Thread;
|
||||
|
||||
class DiscordClient
|
||||
{
|
||||
static final CLIENT_ID:String = "816168432860790794";
|
||||
|
||||
public static var instance(get, never):DiscordClient;
|
||||
static var _instance:Null<DiscordClient> = null;
|
||||
|
||||
static function get_instance():DiscordClient
|
||||
{
|
||||
if (DiscordClient._instance == null) _instance = new DiscordClient();
|
||||
if (DiscordClient._instance == null) throw "Could not initialize singleton DiscordClient!";
|
||||
return DiscordClient._instance;
|
||||
}
|
||||
|
||||
var handlers:DiscordEventHandlers;
|
||||
|
||||
private function new()
|
||||
{
|
||||
trace('[DISCORD] Initializing event handlers...');
|
||||
|
||||
handlers = DiscordEventHandlers.create();
|
||||
|
||||
handlers.ready = cpp.Function.fromStaticFunction(onReady);
|
||||
handlers.disconnected = cpp.Function.fromStaticFunction(onDisconnected);
|
||||
handlers.errored = cpp.Function.fromStaticFunction(onError);
|
||||
}
|
||||
|
||||
public function init():Void
|
||||
{
|
||||
trace('[DISCORD] Initializing connection...');
|
||||
|
||||
// Discord.initialize(CLIENT_ID, handlers, true, null);
|
||||
Discord.Initialize(CLIENT_ID, cpp.RawPointer.addressOf(handlers), 1, null);
|
||||
|
||||
createDaemon();
|
||||
}
|
||||
|
||||
var daemon:Thread = null;
|
||||
|
||||
function createDaemon():Void
|
||||
{
|
||||
daemon = Thread.create(doDaemonWork);
|
||||
}
|
||||
|
||||
function doDaemonWork():Void
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
trace('[DISCORD] Performing client update...');
|
||||
|
||||
#if DISCORD_DISABLE_IO_THREAD
|
||||
Discord.updateConnection();
|
||||
#end
|
||||
|
||||
Discord.runCallbacks();
|
||||
Sys.sleep(2);
|
||||
}
|
||||
}
|
||||
|
||||
public function shutdown():Void
|
||||
{
|
||||
trace('[DISCORD] Shutting down...');
|
||||
|
||||
Discord.shutdown();
|
||||
}
|
||||
|
||||
public function setPresence(params:DiscordClientPresenceParams):Void
|
||||
{
|
||||
trace('[DISCORD] Updating presence... (${params})');
|
||||
|
||||
Discord.updatePresence(buildPresence(params));
|
||||
}
|
||||
|
||||
function buildPresence(params:DiscordClientPresenceParams):DiscordRichPresence
|
||||
{
|
||||
var presence = DiscordRichPresence.create();
|
||||
|
||||
// Presence should always be playing the game.
|
||||
presence.type = DiscordActivityType_Playing;
|
||||
|
||||
// Text when hovering over the large image. We just leave this as the game name.
|
||||
presence.largeImageText = "Friday Night Funkin'";
|
||||
|
||||
// State should be generally what the person is doing, like "In the Menus" or "Pico (Pico Mix) [Freeplay Hard]"
|
||||
presence.state = cast(params.state, Null<String>);
|
||||
// Details should be what the person is specifically doing, including stuff like timestamps (maybe something like "03:24 elapsed").
|
||||
presence.details = cast(params.details, Null<String>);
|
||||
|
||||
// The large image displaying what the user is doing.
|
||||
// This should probably be album art.
|
||||
// IMPORTANT NOTE: This can be an asset key uploaded to Discord's developer panel OR any URL you like.
|
||||
presence.largeImageKey = cast(params.largeImageKey, Null<String>) ?? "album-volume1";
|
||||
|
||||
trace('[DISCORD] largeImageKey: ${presence.largeImageKey}');
|
||||
|
||||
// TODO: Make this use the song's album art.
|
||||
// presence.largeImageKey = "icon";
|
||||
// presence.largeImageKey = "https://f4.bcbits.com/img/a0746694746_16.jpg";
|
||||
|
||||
// The small inset image for what the user is doing.
|
||||
// This can be the opponent's health icon?
|
||||
// NOTE: Like largeImageKey, this can be a URL, or an asset key.
|
||||
presence.smallImageKey = cast(params.smallImageKey, Null<String>);
|
||||
|
||||
// NOTE: In previous versions, this showed as "Elapsed", but now shows as playtime and doesn't look good
|
||||
// presence.startTimestamp = time - 10;
|
||||
// presence.endTimestamp = time + 30;
|
||||
|
||||
final button1:DiscordButton = DiscordButton.create();
|
||||
button1.label = "Play on Web";
|
||||
button1.url = Constants.URL_NEWGROUNDS;
|
||||
presence.buttons[0] = button1;
|
||||
|
||||
final button2:DiscordButton = DiscordButton.create();
|
||||
button2.label = "Download";
|
||||
button2.url = Constants.URL_ITCH;
|
||||
presence.buttons[1] = button2;
|
||||
|
||||
return presence;
|
||||
}
|
||||
|
||||
// TODO: WHAT THE FUCK get this pointer bullfuckery out of here
|
||||
private static function onReady(request:cpp.RawConstPointer<DiscordUser>):Void
|
||||
{
|
||||
trace('[DISCORD] Client has connected!');
|
||||
|
||||
final username:String = request[0].username;
|
||||
final globalName:String = request[0].username;
|
||||
final discriminator:Int = Std.parseInt(request[0].discriminator);
|
||||
|
||||
if (discriminator != 0)
|
||||
{
|
||||
trace('[DISCORD] User: ${username}#${discriminator} (${globalName})');
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[DISCORD] User: @${username} (${globalName})');
|
||||
}
|
||||
}
|
||||
|
||||
private static function onDisconnected(errorCode:Int, message:cpp.ConstCharStar):Void
|
||||
{
|
||||
trace('[DISCORD] Client has disconnected! ($errorCode) "${cast (message, String)}"');
|
||||
}
|
||||
|
||||
private static function onError(errorCode:Int, message:cpp.ConstCharStar):Void
|
||||
{
|
||||
trace('[DISCORD] Client has received an error! ($errorCode) "${cast (message, String)}"');
|
||||
}
|
||||
|
||||
// public var type(get, set):DiscordActivityType;
|
||||
// public var state(get, set):String;
|
||||
// public var details(get, set):String;
|
||||
// public var startTimestamp(get, set):Int;
|
||||
// public var endTimestamp(get, set):Int;
|
||||
// public var largeImageKey(get, set):String;
|
||||
// public var largeImageText(get, set):String;
|
||||
// public var smallImageKey(get, set):String;
|
||||
// public var smallImageText(get, set):String;
|
||||
//
|
||||
//
|
||||
// public var partyId(get, set)
|
||||
// public var partySize(get, set)
|
||||
// public var partyMax(get, set)
|
||||
// public var partyPrivacy(get, set)
|
||||
//
|
||||
// public var buttons(get, set)
|
||||
//
|
||||
// public var matchSecret(get, set)
|
||||
// public var joinSecret(get, set)
|
||||
// public var spectateSecret(get, set)
|
||||
}
|
||||
|
||||
typedef DiscordClientPresenceParams =
|
||||
{
|
||||
/**
|
||||
* The first row of text below the game title.
|
||||
*/
|
||||
var state:String;
|
||||
|
||||
/**
|
||||
* The second row of text below the game title.
|
||||
* Use `null` to display no text.
|
||||
*/
|
||||
var details:Null<String>;
|
||||
|
||||
/**
|
||||
* A large, 4-row high image to the left of the content.
|
||||
*/
|
||||
var ?largeImageKey:String;
|
||||
|
||||
/**
|
||||
* A small, inset image to the bottom right of `largeImageKey`.
|
||||
*/
|
||||
var ?smallImageKey:String;
|
||||
}
|
||||
#end
|
||||
@@ -390,7 +390,7 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
}
|
||||
else
|
||||
{
|
||||
var music = FunkinSound.load(pathToUse, params?.startingVolume ?? 1.0, params.loop ?? true, false, true, params.onComplete);
|
||||
var music = FunkinSound.load(pathToUse, params?.startingVolume ?? 1.0, params.loop ?? true, false, true, params.persist ?? false, params.onComplete);
|
||||
if (music != null)
|
||||
{
|
||||
FlxG.sound.music = music;
|
||||
@@ -430,12 +430,13 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
* @param autoDestroy Whether to destroy this sound when it finishes playing.
|
||||
* Leave this value set to `false` if you want to re-use this `FunkinSound` instance.
|
||||
* @param autoPlay Whether to play the sound immediately or wait for a `play()` call.
|
||||
* @param persist Whether to keep this `FunkinSound` between states, or destroy it.
|
||||
* @param onComplete Called when the sound finished playing.
|
||||
* @param onLoad Called when the sound finished loading. Called immediately for succesfully loaded embedded sounds.
|
||||
* @return A `FunkinSound` object, or `null` if the sound could not be loaded.
|
||||
*/
|
||||
public static function load(embeddedSound:FlxSoundAsset, volume:Float = 1.0, looped:Bool = false, autoDestroy:Bool = false, autoPlay:Bool = false,
|
||||
?onComplete:Void->Void, ?onLoad:Void->Void):Null<FunkinSound>
|
||||
persist:Bool = false, ?onComplete:Void->Void, ?onLoad:Void->Void):Null<FunkinSound>
|
||||
{
|
||||
@:privateAccess
|
||||
if (SoundMixer.__soundChannels.length >= SoundMixer.MAX_ACTIVE_CHANNELS)
|
||||
@@ -462,7 +463,7 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
if (autoPlay) sound.play();
|
||||
sound.volume = volume;
|
||||
sound.group = FlxG.sound.defaultSoundGroup;
|
||||
sound.persist = true;
|
||||
sound.persist = persist;
|
||||
|
||||
// Make sure to add the sound to the list.
|
||||
// If it's already in, it won't get re-added.
|
||||
@@ -513,7 +514,7 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
});
|
||||
|
||||
soundRequest.future.onComplete(function(partialSound) {
|
||||
var snd = FunkinSound.load(partialSound, volume, looped, autoDestroy, autoPlay, onComplete, onLoad);
|
||||
var snd = FunkinSound.load(partialSound, volume, looped, autoDestroy, autoPlay, false, onComplete, onLoad);
|
||||
promise.complete(snd);
|
||||
});
|
||||
}
|
||||
@@ -543,18 +544,19 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
*/
|
||||
public static function playOnce(key:String, volume:Float = 1.0, ?onComplete:Void->Void, ?onLoad:Void->Void):Null<FunkinSound>
|
||||
{
|
||||
var result = FunkinSound.load(key, volume, false, true, true, onComplete, onLoad);
|
||||
var result:Null<FunkinSound> = FunkinSound.load(key, volume, false, true, true, false, onComplete, onLoad);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop all sounds in the pool and allow them to be recycled.
|
||||
*/
|
||||
public static function stopAllAudio(musicToo:Bool = false):Void
|
||||
public static function stopAllAudio(musicToo:Bool = false, persistToo:Bool = false):Void
|
||||
{
|
||||
for (sound in pool)
|
||||
{
|
||||
if (sound == null) continue;
|
||||
if (!persistToo && sound.persist) continue;
|
||||
if (!musicToo && sound == FlxG.sound.music) continue;
|
||||
sound.destroy();
|
||||
}
|
||||
@@ -628,6 +630,11 @@ typedef FunkinSoundPlayMusicParams =
|
||||
|
||||
var ?partialParams:PartialSoundParams;
|
||||
|
||||
/**
|
||||
* Whether the sound should be destroyed on state switches
|
||||
*/
|
||||
var ?persist:Bool;
|
||||
|
||||
var ?onComplete:Void->Void;
|
||||
var ?onLoad:Void->Void;
|
||||
}
|
||||
|
||||
@@ -264,12 +264,32 @@ class PlayerCharSelectData
|
||||
*/
|
||||
@:optional
|
||||
public var position:Null<Int>;
|
||||
|
||||
/**
|
||||
* The GF name to assign for this character.
|
||||
*/
|
||||
@:optional
|
||||
public var gf:PlayerCharSelectGFData;
|
||||
}
|
||||
|
||||
typedef PlayerCharSelectGFData =
|
||||
{
|
||||
@:optional
|
||||
public var assetPath:String;
|
||||
|
||||
@:optional
|
||||
public var animInfoPath:String;
|
||||
|
||||
@:optional
|
||||
@:default(false)
|
||||
public var visualizer:Bool;
|
||||
}
|
||||
|
||||
typedef PlayerResultsData =
|
||||
{
|
||||
var music:PlayerResultsMusicData;
|
||||
|
||||
var perfectGold:Array<PlayerResultsAnimationData>;
|
||||
var perfect:Array<PlayerResultsAnimationData>;
|
||||
var excellent:Array<PlayerResultsAnimationData>;
|
||||
var great:Array<PlayerResultsAnimationData>;
|
||||
|
||||
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.0.3]
|
||||
### Added
|
||||
- Added the `color` attribute on stage props to tint them.
|
||||
- Added the `blend` attribute on stage props to apply blend modes.
|
||||
- Added the `angle` attribute on stage props to apply a rotation to them.
|
||||
|
||||
## [1.0.2]
|
||||
### Added
|
||||
- Added the ability to specify `flipX` and `flipY` on stage props to horizontally or vertically flip, respectively.
|
||||
|
||||
## [1.0.1]
|
||||
### Added
|
||||
- Added the ability to specify a hexadecimal color in the `assetPath` field instead of a texture key.
|
||||
|
||||
@@ -20,6 +20,10 @@ class StageData
|
||||
@:optional
|
||||
public var cameraZoom:Null<Float>;
|
||||
|
||||
@:default("shared")
|
||||
@:optional
|
||||
public var directory:Null<String>;
|
||||
|
||||
public function new()
|
||||
{
|
||||
this.version = StageRegistry.STAGE_DATA_VERSION;
|
||||
@@ -118,6 +122,22 @@ typedef StageDataProp =
|
||||
@:default(false)
|
||||
var isPixel:Bool;
|
||||
|
||||
/**
|
||||
* If set to true, the prop will be flipped horizontally.
|
||||
* @default false
|
||||
*/
|
||||
@:optional
|
||||
@:default(false)
|
||||
var flipX:Bool;
|
||||
|
||||
/**
|
||||
* If set to true, the prop will be flipped vertically.
|
||||
* @default false
|
||||
*/
|
||||
@:optional
|
||||
@:default(false)
|
||||
var flipY:Bool;
|
||||
|
||||
/**
|
||||
* Either the scale of the prop as a float, or the [w, h] scale as an array of two floats.
|
||||
* Pro tip: On pixel-art levels, save the sprite small and set this value to 6 or so to save memory.
|
||||
@@ -182,6 +202,32 @@ typedef StageDataProp =
|
||||
@:default("sparrow")
|
||||
@:optional
|
||||
var animType:String;
|
||||
|
||||
/**
|
||||
* The angle of the prop, as a float.
|
||||
* @default 1.0
|
||||
*/
|
||||
@:optional
|
||||
@:default(0.0)
|
||||
var angle:Float;
|
||||
|
||||
/**
|
||||
* The blend mode of the prop, as a string.
|
||||
* Just like in photoshop.
|
||||
* @default Nothing.
|
||||
*/
|
||||
@:default("")
|
||||
@:optional
|
||||
var blend:String;
|
||||
|
||||
/**
|
||||
* The color of the prop overlay, as a hex string.
|
||||
* White overlays, or the ones with the value #FFFFFF, do not appear.
|
||||
* @default `#FFFFFF`
|
||||
*/
|
||||
@:default("#FFFFFF")
|
||||
@:optional
|
||||
var color:String;
|
||||
};
|
||||
|
||||
typedef StageDataCharacter =
|
||||
|
||||
@@ -11,9 +11,9 @@ class StageRegistry extends BaseRegistry<Stage, StageData>
|
||||
* Handle breaking changes by incrementing this value
|
||||
* and adding migration to the `migrateStageData()` function.
|
||||
*/
|
||||
public static final STAGE_DATA_VERSION:thx.semver.Version = "1.0.0";
|
||||
public static final STAGE_DATA_VERSION:thx.semver.Version = "1.0.3";
|
||||
|
||||
public static final STAGE_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
|
||||
public static final STAGE_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <=1.0.3";
|
||||
|
||||
public static var instance(get, never):StageRegistry;
|
||||
static var _instance:Null<StageRegistry> = null;
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package funkin.effects;
|
||||
|
||||
import flixel.addons.effects.FlxTrail;
|
||||
import funkin.play.stage.Bopper;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.system.FlxAssets;
|
||||
|
||||
/**
|
||||
* An offshoot of FlxTrail, but accomodates the way Funkin
|
||||
* does offsets for characters. example, fixes Spirits trail
|
||||
*/
|
||||
class FunkTrail extends FlxTrail
|
||||
{
|
||||
/**
|
||||
* Creates a new FunkTrail effect for a specific FlxSprite.
|
||||
*
|
||||
* @param Target The FlxSprite the trail is attached to.
|
||||
* @param Graphic The image to use for the trailsprites. Optional, uses the sprite's graphic if null.
|
||||
* @param Length The amount of trailsprites to create.
|
||||
* @param Delay How often to update the trail. 0 updates every frame.
|
||||
* @param Alpha The alpha value for the very first trailsprite.
|
||||
* @param Diff How much lower the alpha of the next trailsprite is.
|
||||
*/
|
||||
public function new(Target:FlxSprite, ?Graphic:FlxGraphicAsset, Length:Int = 10, Delay:Int = 3, Alpha:Float = 0.4, Diff:Float = 0.05)
|
||||
{
|
||||
super(Target, Graphic, Length, Delay, Alpha, Diff);
|
||||
}
|
||||
|
||||
override public function update(elapsed:Float):Void
|
||||
{
|
||||
if (target is Bopper)
|
||||
{
|
||||
var targ:Bopper = cast target;
|
||||
@:privateAccess effectOffset.set((targ.animOffsets[0] - targ.globalOffsets[0]) * targ.scale.x,
|
||||
(targ.animOffsets[1] - targ.globalOffsets[1]) * targ.scale.y);
|
||||
}
|
||||
|
||||
super.update(elapsed);
|
||||
}
|
||||
}
|
||||
@@ -238,6 +238,18 @@ class FunkinSprite extends FlxSprite
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param id The animation ID to check.
|
||||
* @return Whether the animation is dynamic (has multiple frames). `false` for static, one-frame animations.
|
||||
*/
|
||||
public function isAnimationDynamic(id:String):Bool
|
||||
{
|
||||
if (this.animation == null) return false;
|
||||
var animData = this.animation.getByName(id);
|
||||
if (animData == null) return false;
|
||||
return animData.numFrames > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Acts similarly to `makeGraphic`, but with improved memory usage,
|
||||
* at the expense of not being able to paint onto the resulting sprite.
|
||||
|
||||
@@ -7,7 +7,6 @@ import flxanimate.frames.FlxAnimateFrames;
|
||||
import flixel.graphics.frames.FlxFrame;
|
||||
import flixel.system.FlxAssets.FlxGraphicAsset;
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.utils.Assets;
|
||||
import flixel.math.FlxPoint;
|
||||
import flxanimate.animate.FlxKeyFrame;
|
||||
|
||||
@@ -184,7 +183,7 @@ class FlxAtlasSprite extends FlxAnimate
|
||||
|
||||
// Move to the first frame of the animation.
|
||||
// goToFrameLabel(id);
|
||||
trace('Playing animation $id');
|
||||
// trace('Playing animation $id');
|
||||
if ((id == null || id == "") || this.anim.symbolDictionary.exists(id) || (this.anim.getByName(id) != null))
|
||||
{
|
||||
this.anim.play(id, restart, false, startFrame);
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.addons.display.FlxRuntimeShader;
|
||||
import funkin.Paths;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
class AdjustColorShader extends FlxRuntimeShader
|
||||
{
|
||||
@@ -52,4 +50,9 @@ class AdjustColorShader extends FlxRuntimeShader
|
||||
|
||||
return this.contrast;
|
||||
}
|
||||
|
||||
public override function toString():String
|
||||
{
|
||||
return 'AdjustColorShader(${this.hue}, ${this.saturation}, ${this.brightness}, ${this.contrast})';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.addons.display.FlxRuntimeShader;
|
||||
import funkin.Paths;
|
||||
import openfl.utils.Assets;
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.display.ShaderInput;
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.addons.display.FlxRuntimeShader;
|
||||
import funkin.Paths;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
/**
|
||||
* Note... not actually gaussian!
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.addons.display.FlxRuntimeShader;
|
||||
import funkin.Paths;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
class Grayscale extends FlxRuntimeShader
|
||||
{
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.addons.display.FlxRuntimeShader;
|
||||
import funkin.Paths;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
class HSVShader extends FlxRuntimeShader
|
||||
{
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.addons.display.FlxRuntimeShader;
|
||||
|
||||
/**
|
||||
* Create a little dotting effect.
|
||||
*/
|
||||
class InverseDotsShader extends FlxRuntimeShader
|
||||
{
|
||||
public var amount:Float;
|
||||
|
||||
public function new(amount:Float = 1.0)
|
||||
{
|
||||
super(Assets.getText(Paths.frag("InverseDots")));
|
||||
setAmount(amount);
|
||||
}
|
||||
|
||||
public function setAmount(value:Float):Void
|
||||
{
|
||||
this.amount = value;
|
||||
this.setFloat("_amount", amount);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.addons.display.FlxRuntimeShader;
|
||||
import openfl.utils.Assets;
|
||||
import funkin.Paths;
|
||||
import flixel.math.FlxPoint;
|
||||
|
||||
class MosaicEffect extends FlxRuntimeShader
|
||||
|
||||
@@ -2,7 +2,6 @@ package funkin.graphics.shaders;
|
||||
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.display.BlendMode;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
class RuntimeCustomBlendShader extends RuntimePostEffectShader
|
||||
{
|
||||
|
||||
@@ -5,7 +5,6 @@ import openfl.display.BitmapData;
|
||||
import openfl.display.ShaderParameter;
|
||||
import openfl.display.ShaderParameterType;
|
||||
import flixel.util.FlxColor;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
typedef Light =
|
||||
{
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package funkin.graphics.video;
|
||||
|
||||
#if hxCodec
|
||||
import hxcodec.flixel.FlxVideoSprite;
|
||||
|
||||
/**
|
||||
* Not to be confused with FlxVideo, this is a hxcodec based video class
|
||||
* We override it simply to correct/control our volume easier.
|
||||
*/
|
||||
class FunkinVideoSprite extends FlxVideoSprite
|
||||
{
|
||||
public var volume(default, set):Float = 1;
|
||||
|
||||
public function new(x:Float = 0, y:Float = 0)
|
||||
{
|
||||
super(x, y);
|
||||
|
||||
set_volume(1);
|
||||
}
|
||||
|
||||
override public function update(elapsed:Float):Void
|
||||
{
|
||||
super.update(elapsed);
|
||||
set_volume(volume);
|
||||
}
|
||||
|
||||
function set_volume(value:Float):Float
|
||||
{
|
||||
volume = value;
|
||||
bitmap.volume = Std.int((FlxG.sound.muted ? 0 : 1) * (FlxG.sound.logToLinear(FlxG.sound.volume) * 100) * volume);
|
||||
return volume;
|
||||
}
|
||||
}
|
||||
#end
|
||||
@@ -3,6 +3,7 @@ package;
|
||||
#if !macro
|
||||
// Only import these when we aren't in a macro.
|
||||
import funkin.util.Constants;
|
||||
import funkin.Assets;
|
||||
import funkin.Paths;
|
||||
import funkin.Preferences;
|
||||
import flixel.FlxG; // This one in particular causes a compile error if you're using macros.
|
||||
|
||||
@@ -59,7 +59,9 @@ class Controls extends FlxActionSet
|
||||
var _back = new FunkinAction(Action.BACK);
|
||||
var _pause = new FunkinAction(Action.PAUSE);
|
||||
var _reset = new FunkinAction(Action.RESET);
|
||||
#if FEATURE_SCREENSHOTS
|
||||
var _window_screenshot = new FunkinAction(Action.WINDOW_SCREENSHOT);
|
||||
#end
|
||||
var _window_fullscreen = new FunkinAction(Action.WINDOW_FULLSCREEN);
|
||||
var _freeplay_favorite = new FunkinAction(Action.FREEPLAY_FAVORITE);
|
||||
var _freeplay_left = new FunkinAction(Action.FREEPLAY_LEFT);
|
||||
@@ -67,8 +69,12 @@ class Controls extends FlxActionSet
|
||||
var _freeplay_char_select = new FunkinAction(Action.FREEPLAY_CHAR_SELECT);
|
||||
var _cutscene_advance = new FunkinAction(Action.CUTSCENE_ADVANCE);
|
||||
var _debug_menu = new FunkinAction(Action.DEBUG_MENU);
|
||||
#if FEATURE_CHART_EDITOR
|
||||
var _debug_chart = new FunkinAction(Action.DEBUG_CHART);
|
||||
#end
|
||||
#if FEATURE_STAGE_EDITOR
|
||||
var _debug_stage = new FunkinAction(Action.DEBUG_STAGE);
|
||||
#end
|
||||
var _volume_up = new FunkinAction(Action.VOLUME_UP);
|
||||
var _volume_down = new FunkinAction(Action.VOLUME_DOWN);
|
||||
var _volume_mute = new FunkinAction(Action.VOLUME_MUTE);
|
||||
@@ -243,10 +249,12 @@ class Controls extends FlxActionSet
|
||||
inline function get_WINDOW_FULLSCREEN()
|
||||
return _window_fullscreen.check();
|
||||
|
||||
#if FEATURE_SCREENSHOTS
|
||||
public var WINDOW_SCREENSHOT(get, never):Bool;
|
||||
|
||||
inline function get_WINDOW_SCREENSHOT()
|
||||
return _window_screenshot.check();
|
||||
#end
|
||||
|
||||
public var FREEPLAY_FAVORITE(get, never):Bool;
|
||||
|
||||
@@ -278,15 +286,19 @@ class Controls extends FlxActionSet
|
||||
inline function get_DEBUG_MENU()
|
||||
return _debug_menu.check();
|
||||
|
||||
#if FEATURE_CHART_EDITOR
|
||||
public var DEBUG_CHART(get, never):Bool;
|
||||
|
||||
inline function get_DEBUG_CHART()
|
||||
return _debug_chart.check();
|
||||
#end
|
||||
|
||||
#if FEATURE_STAGE_EDITOR
|
||||
public var DEBUG_STAGE(get, never):Bool;
|
||||
|
||||
inline function get_DEBUG_STAGE()
|
||||
return _debug_stage.check();
|
||||
#end
|
||||
|
||||
public var VOLUME_UP(get, never):Bool;
|
||||
|
||||
@@ -319,7 +331,7 @@ class Controls extends FlxActionSet
|
||||
add(_back);
|
||||
add(_pause);
|
||||
add(_reset);
|
||||
add(_window_screenshot);
|
||||
#if FEATURE_SCREENSHOTS add(_window_screenshot); #end
|
||||
add(_window_fullscreen);
|
||||
add(_freeplay_favorite);
|
||||
add(_freeplay_left);
|
||||
@@ -327,8 +339,8 @@ class Controls extends FlxActionSet
|
||||
add(_freeplay_char_select);
|
||||
add(_cutscene_advance);
|
||||
add(_debug_menu);
|
||||
add(_debug_chart);
|
||||
add(_debug_stage);
|
||||
#if FEATURE_CHART_EDITOR add(_debug_chart); #end
|
||||
#if FEATURE_STAGE_EDITOR add(_debug_stage); #end
|
||||
add(_volume_up);
|
||||
add(_volume_down);
|
||||
add(_volume_mute);
|
||||
@@ -444,7 +456,7 @@ class Controls extends FlxActionSet
|
||||
case BACK: _back;
|
||||
case PAUSE: _pause;
|
||||
case RESET: _reset;
|
||||
case WINDOW_SCREENSHOT: _window_screenshot;
|
||||
#if FEATURE_SCREENSHOTS case WINDOW_SCREENSHOT: _window_screenshot; #end
|
||||
case WINDOW_FULLSCREEN: _window_fullscreen;
|
||||
case FREEPLAY_FAVORITE: _freeplay_favorite;
|
||||
case FREEPLAY_LEFT: _freeplay_left;
|
||||
@@ -452,8 +464,8 @@ class Controls extends FlxActionSet
|
||||
case FREEPLAY_CHAR_SELECT: _freeplay_char_select;
|
||||
case CUTSCENE_ADVANCE: _cutscene_advance;
|
||||
case DEBUG_MENU: _debug_menu;
|
||||
case DEBUG_CHART: _debug_chart;
|
||||
case DEBUG_STAGE: _debug_stage;
|
||||
#if FEATURE_CHART_EDITOR case DEBUG_CHART: _debug_chart; #end
|
||||
#if FEATURE_STAGE_EDITOR case DEBUG_STAGE: _debug_stage; #end
|
||||
case VOLUME_UP: _volume_up;
|
||||
case VOLUME_DOWN: _volume_down;
|
||||
case VOLUME_MUTE: _volume_mute;
|
||||
@@ -516,8 +528,10 @@ class Controls extends FlxActionSet
|
||||
func(_pause, JUST_PRESSED);
|
||||
case RESET:
|
||||
func(_reset, JUST_PRESSED);
|
||||
#if FEATURE_SCREENSHOTS
|
||||
case WINDOW_SCREENSHOT:
|
||||
func(_window_screenshot, JUST_PRESSED);
|
||||
#end
|
||||
case WINDOW_FULLSCREEN:
|
||||
func(_window_fullscreen, JUST_PRESSED);
|
||||
case FREEPLAY_FAVORITE:
|
||||
@@ -532,10 +546,14 @@ class Controls extends FlxActionSet
|
||||
func(_cutscene_advance, JUST_PRESSED);
|
||||
case DEBUG_MENU:
|
||||
func(_debug_menu, JUST_PRESSED);
|
||||
#if FEATURE_CHART_EDITOR
|
||||
case DEBUG_CHART:
|
||||
func(_debug_chart, JUST_PRESSED);
|
||||
#end
|
||||
#if FEATURE_STAGE_EDITOR
|
||||
case DEBUG_STAGE:
|
||||
func(_debug_stage, JUST_PRESSED);
|
||||
#end
|
||||
case VOLUME_UP:
|
||||
func(_volume_up, JUST_PRESSED);
|
||||
case VOLUME_DOWN:
|
||||
@@ -744,7 +762,9 @@ class Controls extends FlxActionSet
|
||||
bindKeys(Control.BACK, getDefaultKeybinds(scheme, Control.BACK));
|
||||
bindKeys(Control.PAUSE, getDefaultKeybinds(scheme, Control.PAUSE));
|
||||
bindKeys(Control.RESET, getDefaultKeybinds(scheme, Control.RESET));
|
||||
#if FEATURE_SCREENSHOTS
|
||||
bindKeys(Control.WINDOW_SCREENSHOT, getDefaultKeybinds(scheme, Control.WINDOW_SCREENSHOT));
|
||||
#end
|
||||
bindKeys(Control.WINDOW_FULLSCREEN, getDefaultKeybinds(scheme, Control.WINDOW_FULLSCREEN));
|
||||
bindKeys(Control.FREEPLAY_FAVORITE, getDefaultKeybinds(scheme, Control.FREEPLAY_FAVORITE));
|
||||
bindKeys(Control.FREEPLAY_LEFT, getDefaultKeybinds(scheme, Control.FREEPLAY_LEFT));
|
||||
@@ -752,8 +772,12 @@ class Controls extends FlxActionSet
|
||||
bindKeys(Control.FREEPLAY_CHAR_SELECT, getDefaultKeybinds(scheme, Control.FREEPLAY_CHAR_SELECT));
|
||||
bindKeys(Control.CUTSCENE_ADVANCE, getDefaultKeybinds(scheme, Control.CUTSCENE_ADVANCE));
|
||||
bindKeys(Control.DEBUG_MENU, getDefaultKeybinds(scheme, Control.DEBUG_MENU));
|
||||
#if FEATURE_CHART_EDITOR
|
||||
bindKeys(Control.DEBUG_CHART, getDefaultKeybinds(scheme, Control.DEBUG_CHART));
|
||||
#end
|
||||
#if FEATURE_STAGE_EDITOR
|
||||
bindKeys(Control.DEBUG_STAGE, getDefaultKeybinds(scheme, Control.DEBUG_STAGE));
|
||||
#end
|
||||
bindKeys(Control.VOLUME_UP, getDefaultKeybinds(scheme, Control.VOLUME_UP));
|
||||
bindKeys(Control.VOLUME_DOWN, getDefaultKeybinds(scheme, Control.VOLUME_DOWN));
|
||||
bindKeys(Control.VOLUME_MUTE, getDefaultKeybinds(scheme, Control.VOLUME_MUTE));
|
||||
@@ -781,15 +805,15 @@ class Controls extends FlxActionSet
|
||||
case Control.PAUSE: return [P, ENTER, ESCAPE];
|
||||
case Control.RESET: return [R];
|
||||
case Control.WINDOW_FULLSCREEN: return [F11]; // We use F for other things LOL.
|
||||
case Control.WINDOW_SCREENSHOT: return [F3];
|
||||
#if FEATURE_SCREENSHOTS case Control.WINDOW_SCREENSHOT: return [F3]; #end
|
||||
case Control.FREEPLAY_FAVORITE: return [F]; // Favorite a song on the menu
|
||||
case Control.FREEPLAY_LEFT: return [Q]; // Switch tabs on the menu
|
||||
case Control.FREEPLAY_RIGHT: return [E]; // Switch tabs on the menu
|
||||
case Control.FREEPLAY_CHAR_SELECT: return [TAB];
|
||||
case Control.CUTSCENE_ADVANCE: return [Z, ENTER];
|
||||
case Control.DEBUG_MENU: return [GRAVEACCENT];
|
||||
case Control.DEBUG_CHART: return [];
|
||||
case Control.DEBUG_STAGE: return [];
|
||||
#if FEATURE_CHART_EDITOR case Control.DEBUG_CHART: return []; #end
|
||||
#if FEATURE_STAGE_EDITOR case Control.DEBUG_STAGE: return []; #end
|
||||
case Control.VOLUME_UP: return [PLUS, NUMPADPLUS];
|
||||
case Control.VOLUME_DOWN: return [MINUS, NUMPADMINUS];
|
||||
case Control.VOLUME_MUTE: return [ZERO, NUMPADZERO];
|
||||
@@ -809,7 +833,7 @@ class Controls extends FlxActionSet
|
||||
case Control.BACK: return [H, X];
|
||||
case Control.PAUSE: return [ONE];
|
||||
case Control.RESET: return [R];
|
||||
case Control.WINDOW_SCREENSHOT: return [F3];
|
||||
#if FEATURE_SCREENSHOTS case Control.WINDOW_SCREENSHOT: return [F3]; #end
|
||||
case Control.WINDOW_FULLSCREEN: return [F11];
|
||||
case Control.FREEPLAY_FAVORITE: return [F]; // Favorite a song on the menu
|
||||
case Control.FREEPLAY_LEFT: return [Q]; // Switch tabs on the menu
|
||||
@@ -817,8 +841,8 @@ class Controls extends FlxActionSet
|
||||
case Control.FREEPLAY_CHAR_SELECT: return [TAB];
|
||||
case Control.CUTSCENE_ADVANCE: return [G, Z];
|
||||
case Control.DEBUG_MENU: return [GRAVEACCENT];
|
||||
case Control.DEBUG_CHART: return [];
|
||||
case Control.DEBUG_STAGE: return [];
|
||||
#if FEATURE_CHART_EDITOR case Control.DEBUG_CHART: return []; #end
|
||||
#if FEATURE_STAGE_EDITOR case Control.DEBUG_STAGE: return []; #end
|
||||
case Control.VOLUME_UP: return [PLUS];
|
||||
case Control.VOLUME_DOWN: return [MINUS];
|
||||
case Control.VOLUME_MUTE: return [ZERO];
|
||||
@@ -838,7 +862,7 @@ class Controls extends FlxActionSet
|
||||
case Control.BACK: return [ESCAPE];
|
||||
case Control.PAUSE: return [ONE];
|
||||
case Control.RESET: return [R];
|
||||
case Control.WINDOW_SCREENSHOT: return [];
|
||||
#if FEATURE_SCREENSHOTS case Control.WINDOW_SCREENSHOT: return []; #end
|
||||
case Control.WINDOW_FULLSCREEN: return [];
|
||||
case Control.FREEPLAY_FAVORITE: return [];
|
||||
case Control.FREEPLAY_LEFT: return [];
|
||||
@@ -846,8 +870,8 @@ class Controls extends FlxActionSet
|
||||
case Control.FREEPLAY_CHAR_SELECT: return [];
|
||||
case Control.CUTSCENE_ADVANCE: return [ENTER];
|
||||
case Control.DEBUG_MENU: return [];
|
||||
case Control.DEBUG_CHART: return [];
|
||||
case Control.DEBUG_STAGE: return [];
|
||||
#if FEATURE_CHART_EDITOR case Control.DEBUG_CHART: return []; #end
|
||||
#if FEATURE_STAGE_EDITOR case Control.DEBUG_STAGE: return []; #end
|
||||
case Control.VOLUME_UP: return [NUMPADPLUS];
|
||||
case Control.VOLUME_DOWN: return [NUMPADMINUS];
|
||||
case Control.VOLUME_MUTE: return [NUMPADZERO];
|
||||
@@ -952,7 +976,9 @@ class Controls extends FlxActionSet
|
||||
Control.PAUSE => getDefaultGamepadBinds(Control.PAUSE),
|
||||
Control.RESET => getDefaultGamepadBinds(Control.RESET),
|
||||
Control.WINDOW_FULLSCREEN => getDefaultGamepadBinds(Control.WINDOW_FULLSCREEN),
|
||||
#if FEATURE_SCREENSHOTS
|
||||
Control.WINDOW_SCREENSHOT => getDefaultGamepadBinds(Control.WINDOW_SCREENSHOT),
|
||||
#end
|
||||
Control.CUTSCENE_ADVANCE => getDefaultGamepadBinds(Control.CUTSCENE_ADVANCE),
|
||||
Control.FREEPLAY_FAVORITE => getDefaultGamepadBinds(Control.FREEPLAY_FAVORITE),
|
||||
Control.FREEPLAY_LEFT => getDefaultGamepadBinds(Control.FREEPLAY_LEFT),
|
||||
@@ -961,8 +987,12 @@ class Controls extends FlxActionSet
|
||||
Control.VOLUME_DOWN => getDefaultGamepadBinds(Control.VOLUME_DOWN),
|
||||
Control.VOLUME_MUTE => getDefaultGamepadBinds(Control.VOLUME_MUTE),
|
||||
Control.DEBUG_MENU => getDefaultGamepadBinds(Control.DEBUG_MENU),
|
||||
#if FEATURE_CHART_EDITOR
|
||||
Control.DEBUG_CHART => getDefaultGamepadBinds(Control.DEBUG_CHART),
|
||||
#end
|
||||
#if FEATURE_STAGE_EDITOR
|
||||
Control.DEBUG_STAGE => getDefaultGamepadBinds(Control.DEBUG_STAGE),
|
||||
#end
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -996,8 +1026,10 @@ class Controls extends FlxActionSet
|
||||
return [FlxGamepadInputID.BACK]; // Back (i.e. Select)
|
||||
case Control.WINDOW_FULLSCREEN:
|
||||
[];
|
||||
#if FEATURE_SCREENSHOTS
|
||||
case Control.WINDOW_SCREENSHOT:
|
||||
[];
|
||||
#end
|
||||
case Control.CUTSCENE_ADVANCE:
|
||||
return [A];
|
||||
case Control.FREEPLAY_FAVORITE:
|
||||
@@ -1014,10 +1046,14 @@ class Controls extends FlxActionSet
|
||||
[];
|
||||
case Control.DEBUG_MENU:
|
||||
[];
|
||||
#if FEATURE_CHART_EDITOR
|
||||
case Control.DEBUG_CHART:
|
||||
[];
|
||||
#end
|
||||
#if FEATURE_STAGE_EDITOR
|
||||
case Control.DEBUG_STAGE:
|
||||
[];
|
||||
#end
|
||||
default:
|
||||
// Fallthrough.
|
||||
}
|
||||
@@ -1582,7 +1618,7 @@ enum Control
|
||||
FREEPLAY_RIGHT;
|
||||
FREEPLAY_CHAR_SELECT;
|
||||
// WINDOW
|
||||
WINDOW_SCREENSHOT;
|
||||
#if FEATURE_SCREENSHOTS WINDOW_SCREENSHOT; #end
|
||||
WINDOW_FULLSCREEN;
|
||||
// VOLUME
|
||||
VOLUME_UP;
|
||||
@@ -1590,8 +1626,8 @@ enum Control
|
||||
VOLUME_MUTE;
|
||||
// DEBUG
|
||||
DEBUG_MENU;
|
||||
DEBUG_CHART;
|
||||
DEBUG_STAGE;
|
||||
#if FEATURE_CHART_EDITOR DEBUG_CHART; #end
|
||||
#if FEATURE_STAGE_EDITOR DEBUG_STAGE; #end
|
||||
}
|
||||
|
||||
enum abstract Action(String) to String from String
|
||||
@@ -1628,7 +1664,9 @@ enum abstract Action(String) to String from String
|
||||
var RESET = "reset";
|
||||
// WINDOW
|
||||
var WINDOW_FULLSCREEN = "window_fullscreen";
|
||||
#if FEATURE_SCREENSHOTS
|
||||
var WINDOW_SCREENSHOT = "window_screenshot";
|
||||
#end
|
||||
// CUTSCENE
|
||||
var CUTSCENE_ADVANCE = "cutscene_advance";
|
||||
// FREEPLAY
|
||||
@@ -1642,8 +1680,12 @@ enum abstract Action(String) to String from String
|
||||
var VOLUME_MUTE = "volume_mute";
|
||||
// DEBUG
|
||||
var DEBUG_MENU = "debug_menu";
|
||||
#if FEATURE_CHART_EDITOR
|
||||
var DEBUG_CHART = "debug_chart";
|
||||
#end
|
||||
#if FEATURE_STAGE_EDITOR
|
||||
var DEBUG_STAGE = "debug_stage";
|
||||
#end
|
||||
}
|
||||
|
||||
enum Device
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package funkin.input;
|
||||
|
||||
import haxe.ui.backend.flixel.CursorHelper;
|
||||
import openfl.utils.Assets;
|
||||
import lime.app.Future;
|
||||
import openfl.display.BitmapData;
|
||||
|
||||
|
||||
@@ -27,11 +27,18 @@ import polymod.Polymod;
|
||||
class PolymodHandler
|
||||
{
|
||||
/**
|
||||
* The API version that mods should comply with.
|
||||
* Indicates which mods are compatible with this version of the game.
|
||||
* The API version for the current version of the game. Since 0.5.0, we've just made this the game version!
|
||||
* Minor updates rarely impact mods but major versions often do.
|
||||
*/
|
||||
static final API_VERSION:String = "0.5.0"; // Constants.VERSION;
|
||||
// static final API_VERSION:String = Constants.VERSION;
|
||||
|
||||
/**
|
||||
* The Semantic Versioning rule
|
||||
* Indicates which mods are compatible with this version of the game.
|
||||
* Using more complex rules allows mods from older compatible versions to stay functioning,
|
||||
* while preventing mods made for future versions from being installed.
|
||||
*/
|
||||
static final API_VERSION_RULE:String = ">=0.5.0 <0.6.0";
|
||||
|
||||
/**
|
||||
* Where relative to the executable that mods are located.
|
||||
@@ -131,7 +138,7 @@ class PolymodHandler
|
||||
// Framework being used to load assets.
|
||||
framework: OPENFL,
|
||||
// The current version of our API.
|
||||
apiVersionRule: API_VERSION,
|
||||
apiVersionRule: API_VERSION_RULE,
|
||||
// Call this function any time an error occurs.
|
||||
errorCallback: PolymodErrorHandler.onPolymodError,
|
||||
// Enforce semantic version patterns for each mod.
|
||||
@@ -228,6 +235,8 @@ class PolymodHandler
|
||||
static function buildImports():Void
|
||||
{
|
||||
// Add default imports for common classes.
|
||||
Polymod.addDefaultImport(funkin.Assets);
|
||||
Polymod.addDefaultImport(funkin.Paths);
|
||||
|
||||
// Add import aliases for certain classes.
|
||||
// NOTE: Scripted classes are automatically aliased to their parent class.
|
||||
@@ -258,7 +267,7 @@ class PolymodHandler
|
||||
Polymod.blacklistImport('cpp.Lib');
|
||||
|
||||
// `Unserializer`
|
||||
// Unserializerr.DEFAULT_RESOLVER.resolveClass() can access blacklisted packages
|
||||
// Unserializer.DEFAULT_RESOLVER.resolveClass() can access blacklisted packages
|
||||
Polymod.blacklistImport('Unserializer');
|
||||
|
||||
// `lime.system.CFFI`
|
||||
@@ -273,6 +282,14 @@ class PolymodHandler
|
||||
// System.load() can load malicious DLLs
|
||||
Polymod.blacklistImport('lime.system.System');
|
||||
|
||||
// `lime.utils.Assets`
|
||||
// Literally just has a private `resolveClass` function for some reason?
|
||||
Polymod.blacklistImport('lime.utils.Assets');
|
||||
Polymod.blacklistImport('openfl.utils.Assets');
|
||||
Polymod.blacklistImport('openfl.Lib');
|
||||
Polymod.blacklistImport('openfl.system.ApplicationDomain');
|
||||
Polymod.blacklistImport('funkin.util.FunkinTypeResolver');
|
||||
|
||||
// `openfl.desktop.NativeProcess`
|
||||
// Can load native processes on the host operating system.
|
||||
Polymod.blacklistImport('openfl.desktop.NativeProcess');
|
||||
@@ -336,7 +353,7 @@ class PolymodHandler
|
||||
var modMetadata:Array<ModMetadata> = Polymod.scan(
|
||||
{
|
||||
modRoot: MOD_FOLDER,
|
||||
apiVersionRule: API_VERSION,
|
||||
apiVersionRule: API_VERSION_RULE,
|
||||
fileSystem: modFileSystem,
|
||||
errorCallback: PolymodErrorHandler.onPolymodError
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package funkin.modding.base;
|
||||
|
||||
/**
|
||||
* A script that can be tied to an FlxSprite.
|
||||
* Create a scripted class that extends FlxSprite to use this.
|
||||
* A script that can be tied to a FunkinSprite.
|
||||
* Create a scripted class that extends FunkinSprite to use this.
|
||||
*/
|
||||
@:hscriptClass
|
||||
class ScriptedFunkinSprite extends funkin.graphics.FunkinSprite implements HScriptedClass {}
|
||||
|
||||
@@ -103,7 +103,7 @@ class NoteScriptEvent extends ScriptEvent
|
||||
public var comboCount(default, null):Int;
|
||||
|
||||
/**
|
||||
* Whether to play the record scratch sound (if this eventn type is `NOTE_MISS`).
|
||||
* Whether to play the record scratch sound (if this event type is `NOTE_MISS`).
|
||||
*/
|
||||
public var playSound(default, default):Bool;
|
||||
|
||||
|
||||
@@ -125,7 +125,6 @@ class ModuleHandler
|
||||
for (key => value in moduleCache)
|
||||
{
|
||||
ScriptEventDispatcher.callEvent(value, event);
|
||||
moduleCache.remove(key);
|
||||
}
|
||||
|
||||
moduleCache.clear();
|
||||
|
||||
@@ -11,7 +11,6 @@ import funkin.modding.events.ScriptEvent.CountdownScriptEvent;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.util.EaseUtil;
|
||||
import funkin.audio.FunkinSound;
|
||||
import openfl.utils.Assets;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ import funkin.ui.freeplay.FreeplayState;
|
||||
import funkin.ui.MusicBeatSubState;
|
||||
import funkin.ui.story.StoryMenuState;
|
||||
import funkin.util.MathUtil;
|
||||
import openfl.utils.Assets;
|
||||
import funkin.effects.RetroCameraFade;
|
||||
import flixel.math.FlxPoint;
|
||||
|
||||
|
||||
+214
-72
@@ -15,8 +15,8 @@ import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.ui.FlxBar;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxTimer;
|
||||
import flixel.util.FlxStringUtil;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.api.newgrounds.NGio;
|
||||
import funkin.audio.FunkinSound;
|
||||
import funkin.audio.VoicesGroup;
|
||||
@@ -44,12 +44,12 @@ import funkin.play.cutscene.dialogue.Conversation;
|
||||
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;
|
||||
import funkin.play.notes.SustainTrail;
|
||||
import funkin.play.notes.notekind.NoteKindManager;
|
||||
import funkin.play.scoring.Scoring;
|
||||
import funkin.play.song.Song;
|
||||
import funkin.play.stage.Stage;
|
||||
@@ -68,7 +68,7 @@ import openfl.display.BitmapData;
|
||||
import openfl.geom.Rectangle;
|
||||
import openfl.Lib;
|
||||
#if FEATURE_DISCORD_RPC
|
||||
import Discord.DiscordClient;
|
||||
import funkin.api.discord.DiscordClient;
|
||||
#end
|
||||
|
||||
/**
|
||||
@@ -447,10 +447,8 @@ class PlayState extends MusicBeatSubState
|
||||
|
||||
#if FEATURE_DISCORD_RPC
|
||||
// Discord RPC variables
|
||||
var storyDifficultyText:String = '';
|
||||
var iconRPC:String = '';
|
||||
var detailsText:String = '';
|
||||
var detailsPausedText:String = '';
|
||||
var discordRPCAlbum:String = '';
|
||||
var discordRPCIcon:String = '';
|
||||
#end
|
||||
|
||||
/**
|
||||
@@ -687,7 +685,11 @@ class PlayState extends MusicBeatSubState
|
||||
}
|
||||
|
||||
Conductor.instance.mapTimeChanges(currentChart.timeChanges);
|
||||
Conductor.instance.update((Conductor.instance.beatLengthMs * -5) + startTimestamp);
|
||||
var pre:Float = (Conductor.instance.beatLengthMs * -5) + startTimestamp;
|
||||
|
||||
trace('Attempting to start at ' + pre);
|
||||
|
||||
Conductor.instance.update(pre);
|
||||
|
||||
// The song is now loaded. We can continue to initialize the play state.
|
||||
initCameras();
|
||||
@@ -813,6 +815,7 @@ class PlayState extends MusicBeatSubState
|
||||
}
|
||||
else
|
||||
{
|
||||
this.remove(currentStage);
|
||||
FlxG.switchState(() -> new MainMenuState());
|
||||
}
|
||||
return false;
|
||||
@@ -857,7 +860,7 @@ class PlayState extends MusicBeatSubState
|
||||
// Reset music properly.
|
||||
if (FlxG.sound.music != null)
|
||||
{
|
||||
FlxG.sound.music.time = startTimestamp - Conductor.instance.instrumentalOffset;
|
||||
FlxG.sound.music.time = startTimestamp - Conductor.instance.combinedOffset;
|
||||
FlxG.sound.music.pitch = playbackRate;
|
||||
FlxG.sound.music.pause();
|
||||
}
|
||||
@@ -874,7 +877,7 @@ class PlayState extends MusicBeatSubState
|
||||
}
|
||||
}
|
||||
vocals.pause();
|
||||
vocals.time = 0;
|
||||
vocals.time = 0 - Conductor.instance.combinedOffset;
|
||||
|
||||
if (FlxG.sound.music != null) FlxG.sound.music.volume = 1;
|
||||
vocals.volume = 1;
|
||||
@@ -915,7 +918,11 @@ class PlayState extends MusicBeatSubState
|
||||
{
|
||||
// Do NOT apply offsets at this point, because they already got applied the previous frame!
|
||||
Conductor.instance.update(Conductor.instance.songPosition + elapsed * 1000, false);
|
||||
if (Conductor.instance.songPosition >= (startTimestamp)) startSong();
|
||||
if (Conductor.instance.songPosition >= (startTimestamp + Conductor.instance.combinedOffset))
|
||||
{
|
||||
trace("started song at " + Conductor.instance.songPosition);
|
||||
startSong();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -957,6 +964,7 @@ class PlayState extends MusicBeatSubState
|
||||
// It's a reference to Gitaroo Man, which doesn't let you pause the game.
|
||||
if (!isSubState && event.gitaroo)
|
||||
{
|
||||
this.remove(currentStage);
|
||||
FlxG.switchState(() -> new GitarooPause(
|
||||
{
|
||||
targetSong: currentSong,
|
||||
@@ -984,7 +992,15 @@ class PlayState extends MusicBeatSubState
|
||||
}
|
||||
|
||||
#if FEATURE_DISCORD_RPC
|
||||
DiscordClient.changePresence(detailsPausedText, currentSong.song + ' (' + storyDifficultyText + ')', iconRPC);
|
||||
DiscordClient.instance.setPresence(
|
||||
{
|
||||
details: 'Paused - ${buildDiscordRPCDetails()}',
|
||||
|
||||
state: buildDiscordRPCState(),
|
||||
|
||||
largeImageKey: discordRPCAlbum,
|
||||
smallImageKey: discordRPCIcon
|
||||
});
|
||||
#end
|
||||
}
|
||||
}
|
||||
@@ -1073,8 +1089,14 @@ class PlayState extends MusicBeatSubState
|
||||
}
|
||||
|
||||
#if FEATURE_DISCORD_RPC
|
||||
// Game Over doesn't get his own variable because it's only used here
|
||||
DiscordClient.changePresence('Game Over - ' + detailsText, currentSong.song + ' (' + storyDifficultyText + ')', iconRPC);
|
||||
DiscordClient.instance.setPresence(
|
||||
{
|
||||
details: 'Game Over - ${buildDiscordRPCDetails()}',
|
||||
state: buildDiscordRPCState(),
|
||||
|
||||
largeImageKey: discordRPCAlbum,
|
||||
smallImageKey: discordRPCIcon
|
||||
});
|
||||
#end
|
||||
}
|
||||
else if (isPlayerDying)
|
||||
@@ -1285,14 +1307,29 @@ class PlayState extends MusicBeatSubState
|
||||
Countdown.resumeCountdown();
|
||||
|
||||
#if FEATURE_DISCORD_RPC
|
||||
if (startTimer.finished)
|
||||
if (Conductor.instance.songPosition > 0)
|
||||
{
|
||||
DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC, true,
|
||||
currentSongLengthMs - Conductor.instance.songPosition);
|
||||
// DiscordClient.changePresence(detailsText, '${currentChart.songName} ($discordRPCDifficulty)', discordRPCIcon, true,
|
||||
// currentSongLengthMs - Conductor.instance.songPosition);
|
||||
DiscordClient.instance.setPresence(
|
||||
{
|
||||
state: buildDiscordRPCState(),
|
||||
details: buildDiscordRPCDetails(),
|
||||
|
||||
largeImageKey: discordRPCAlbum,
|
||||
smallImageKey: discordRPCIcon
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC);
|
||||
DiscordClient.instance.setPresence(
|
||||
{
|
||||
state: buildDiscordRPCState(),
|
||||
details: buildDiscordRPCDetails(),
|
||||
|
||||
largeImageKey: discordRPCAlbum,
|
||||
smallImageKey: discordRPCIcon
|
||||
});
|
||||
}
|
||||
#end
|
||||
|
||||
@@ -1318,16 +1355,32 @@ class PlayState extends MusicBeatSubState
|
||||
#end
|
||||
|
||||
#if FEATURE_DISCORD_RPC
|
||||
if (health > Constants.HEALTH_MIN && !paused && FlxG.autoPause)
|
||||
if (health > Constants.HEALTH_MIN && !isGamePaused && FlxG.autoPause)
|
||||
{
|
||||
if (Conductor.instance.songPosition > 0.0) DiscordClient.changePresence(detailsText, currentSong.song
|
||||
+ ' ('
|
||||
+ storyDifficultyText
|
||||
+ ')', iconRPC, true,
|
||||
currentSongLengthMs
|
||||
- Conductor.instance.songPosition);
|
||||
if (Conductor.instance.songPosition > 0.0)
|
||||
{
|
||||
DiscordClient.instance.setPresence(
|
||||
{
|
||||
state: buildDiscordRPCState(),
|
||||
details: buildDiscordRPCDetails(),
|
||||
|
||||
largeImageKey: discordRPCAlbum,
|
||||
smallImageKey: discordRPCIcon
|
||||
});
|
||||
}
|
||||
else
|
||||
DiscordClient.changePresence(detailsText, currentSong.song + ' (' + storyDifficultyText + ')', iconRPC);
|
||||
{
|
||||
DiscordClient.instance.setPresence(
|
||||
{
|
||||
state: buildDiscordRPCState(),
|
||||
details: buildDiscordRPCDetails(),
|
||||
|
||||
largeImageKey: discordRPCAlbum,
|
||||
smallImageKey: discordRPCIcon
|
||||
});
|
||||
// DiscordClient.changePresence(detailsText, '${currentChart.songName} ($discordRPCDifficulty)', discordRPCIcon, true,
|
||||
// currentSongLengthMs - Conductor.instance.songPosition);
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
@@ -1344,8 +1397,17 @@ class PlayState extends MusicBeatSubState
|
||||
#end
|
||||
|
||||
#if FEATURE_DISCORD_RPC
|
||||
if (health > Constants.HEALTH_MIN && !paused && FlxG.autoPause) DiscordClient.changePresence(detailsPausedText,
|
||||
currentSong.song + ' (' + storyDifficultyText + ')', iconRPC);
|
||||
if (health > Constants.HEALTH_MIN && !isGamePaused && FlxG.autoPause)
|
||||
{
|
||||
DiscordClient.instance.setPresence(
|
||||
{
|
||||
state: buildDiscordRPCState(),
|
||||
details: buildDiscordRPCDetails(),
|
||||
|
||||
largeImageKey: discordRPCAlbum,
|
||||
smallImageKey: discordRPCIcon
|
||||
});
|
||||
}
|
||||
#end
|
||||
|
||||
super.onFocusLost();
|
||||
@@ -1358,6 +1420,7 @@ class PlayState extends MusicBeatSubState
|
||||
{
|
||||
funkin.modding.PolymodHandler.forceReloadAssets();
|
||||
lastParams.targetSong = SongRegistry.instance.fetchEntry(currentSong.id);
|
||||
this.remove(currentStage);
|
||||
LoadingState.loadPlayState(lastParams);
|
||||
}
|
||||
|
||||
@@ -1391,15 +1454,41 @@ class PlayState extends MusicBeatSubState
|
||||
// activeNotes.sort(SortUtil.byStrumtime, FlxSort.DESCENDING);
|
||||
}
|
||||
|
||||
if (!startingSong
|
||||
&& FlxG.sound.music != null
|
||||
&& (Math.abs(FlxG.sound.music.time - (Conductor.instance.songPosition + Conductor.instance.instrumentalOffset)) > 100
|
||||
|| Math.abs(vocals.checkSyncError(Conductor.instance.songPosition + Conductor.instance.instrumentalOffset)) > 100))
|
||||
if (FlxG.sound.music != null)
|
||||
{
|
||||
trace("VOCALS NEED RESYNC");
|
||||
if (vocals != null) trace(vocals.checkSyncError(Conductor.instance.songPosition + Conductor.instance.instrumentalOffset));
|
||||
trace(FlxG.sound.music.time - (Conductor.instance.songPosition + Conductor.instance.instrumentalOffset));
|
||||
resyncVocals();
|
||||
var correctSync:Float = Math.min(FlxG.sound.music.length, Math.max(0, Conductor.instance.songPosition - Conductor.instance.combinedOffset));
|
||||
var playerVoicesError:Float = 0;
|
||||
var opponentVoicesError:Float = 0;
|
||||
|
||||
if (vocals != null)
|
||||
{
|
||||
@:privateAccess // todo: maybe make the groups public :thinking:
|
||||
{
|
||||
vocals.playerVoices.forEachAlive(function(voice:FunkinSound) {
|
||||
var currentRawVoiceTime:Float = voice.time + vocals.playerVoicesOffset;
|
||||
if (Math.abs(currentRawVoiceTime - correctSync) > Math.abs(playerVoicesError)) playerVoicesError = currentRawVoiceTime - correctSync;
|
||||
});
|
||||
|
||||
vocals.opponentVoices.forEachAlive(function(voice:FunkinSound) {
|
||||
var currentRawVoiceTime:Float = voice.time + vocals.opponentVoicesOffset;
|
||||
if (Math.abs(currentRawVoiceTime - correctSync) > Math.abs(opponentVoicesError)) opponentVoicesError = currentRawVoiceTime - correctSync;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!startingSong
|
||||
&& (Math.abs(FlxG.sound.music.time - correctSync) > 5 || Math.abs(playerVoicesError) > 5 || Math.abs(opponentVoicesError) > 5))
|
||||
{
|
||||
trace("VOCALS NEED RESYNC");
|
||||
if (vocals != null)
|
||||
{
|
||||
trace(playerVoicesError);
|
||||
trace(opponentVoicesError);
|
||||
}
|
||||
trace(FlxG.sound.music.time);
|
||||
trace(correctSync);
|
||||
resyncVocals();
|
||||
}
|
||||
}
|
||||
|
||||
// Only bop camera if zoom level is below 135%
|
||||
@@ -1612,7 +1701,7 @@ class PlayState extends MusicBeatSubState
|
||||
|
||||
if (girlfriend != null)
|
||||
{
|
||||
girlfriend.characterType = CharacterType.GF;
|
||||
// Don't need to do anything.
|
||||
}
|
||||
else if (currentCharacterData.girlfriend != '')
|
||||
{
|
||||
@@ -1630,8 +1719,6 @@ class PlayState extends MusicBeatSubState
|
||||
|
||||
if (dad != null)
|
||||
{
|
||||
dad.characterType = CharacterType.DAD;
|
||||
|
||||
//
|
||||
// OPPONENT HEALTH ICON
|
||||
//
|
||||
@@ -1641,6 +1728,11 @@ class PlayState extends MusicBeatSubState
|
||||
iconP2.zIndex = 850;
|
||||
add(iconP2);
|
||||
iconP2.cameras = [camHUD];
|
||||
|
||||
#if FEATURE_DISCORD_RPC
|
||||
discordRPCAlbum = 'album-${currentChart.album}';
|
||||
discordRPCIcon = 'icon-${currentCharacterData.opponent}';
|
||||
#end
|
||||
}
|
||||
|
||||
//
|
||||
@@ -1650,8 +1742,6 @@ class PlayState extends MusicBeatSubState
|
||||
|
||||
if (boyfriend != null)
|
||||
{
|
||||
boyfriend.characterType = CharacterType.BF;
|
||||
|
||||
//
|
||||
// PLAYER HEALTH ICON
|
||||
//
|
||||
@@ -1758,27 +1848,62 @@ class PlayState extends MusicBeatSubState
|
||||
function initDiscord():Void
|
||||
{
|
||||
#if FEATURE_DISCORD_RPC
|
||||
storyDifficultyText = difficultyString();
|
||||
iconRPC = currentSong.player2;
|
||||
|
||||
// To avoid having duplicate images in Discord assets
|
||||
switch (iconRPC)
|
||||
{
|
||||
case 'senpai-angry':
|
||||
iconRPC = 'senpai';
|
||||
case 'monster-christmas':
|
||||
iconRPC = 'monster';
|
||||
case 'mom-car':
|
||||
iconRPC = 'mom';
|
||||
}
|
||||
|
||||
// String that contains the mode defined here so it isn't necessary to call changePresence for each mode
|
||||
detailsText = isStoryMode ? 'Story Mode: Week $storyWeek' : 'Freeplay';
|
||||
detailsPausedText = 'Paused - $detailsText';
|
||||
// Determine the details strings once and reuse them.
|
||||
|
||||
// Updating Discord Rich Presence.
|
||||
DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC);
|
||||
DiscordClient.instance.setPresence(
|
||||
{
|
||||
state: buildDiscordRPCState(),
|
||||
details: buildDiscordRPCDetails(),
|
||||
|
||||
largeImageKey: discordRPCAlbum,
|
||||
smallImageKey: discordRPCIcon
|
||||
});
|
||||
#end
|
||||
|
||||
#if FEATURE_DISCORD_RPC
|
||||
// Updating Discord Rich Presence.
|
||||
DiscordClient.instance.setPresence(
|
||||
{
|
||||
state: buildDiscordRPCState(),
|
||||
details: buildDiscordRPCDetails(),
|
||||
largeImageKey: discordRPCAlbum,
|
||||
smallImageKey: discordRPCIcon
|
||||
});
|
||||
#end
|
||||
}
|
||||
|
||||
function buildDiscordRPCDetails():String
|
||||
{
|
||||
if (PlayStatePlaylist.isStoryMode)
|
||||
{
|
||||
return 'Story Mode: ${PlayStatePlaylist.campaignTitle}';
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isChartingMode)
|
||||
{
|
||||
return 'Chart Editor [Playtest]';
|
||||
}
|
||||
else if (isPracticeMode)
|
||||
{
|
||||
return 'Freeplay [Practice]';
|
||||
}
|
||||
else if (isBotPlayMode)
|
||||
{
|
||||
return 'Freeplay [Bot Play]';
|
||||
}
|
||||
else
|
||||
{
|
||||
return 'Freeplay';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buildDiscordRPCState():String
|
||||
{
|
||||
var discordRPCDifficulty = PlayState.instance.currentDifficulty.replace('-', ' ').toTitleCase();
|
||||
return '${currentChart.songName} [${discordRPCDifficulty}]';
|
||||
}
|
||||
|
||||
function initPreciseInputs():Void
|
||||
@@ -1956,7 +2081,7 @@ class PlayState extends MusicBeatSubState
|
||||
};
|
||||
// A negative instrumental offset means the song skips the first few milliseconds of the track.
|
||||
// This just gets added into the startTimestamp behavior so we don't need to do anything extra.
|
||||
FlxG.sound.music.play(true, startTimestamp - Conductor.instance.instrumentalOffset);
|
||||
FlxG.sound.music.play(true, Math.max(0, startTimestamp - Conductor.instance.combinedOffset));
|
||||
FlxG.sound.music.pitch = playbackRate;
|
||||
|
||||
// Prevent the volume from being wrong.
|
||||
@@ -1968,16 +2093,27 @@ class PlayState extends MusicBeatSubState
|
||||
vocals.play();
|
||||
vocals.volume = 1.0;
|
||||
vocals.pitch = playbackRate;
|
||||
vocals.time = FlxG.sound.music.time;
|
||||
// trace('${FlxG.sound.music.time}');
|
||||
// trace('${vocals.time}');
|
||||
resyncVocals();
|
||||
|
||||
#if FEATURE_DISCORD_RPC
|
||||
// Updating Discord Rich Presence (with Time Left)
|
||||
DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC, true, currentSongLengthMs);
|
||||
DiscordClient.instance.setPresence(
|
||||
{
|
||||
state: buildDiscordRPCState(),
|
||||
details: buildDiscordRPCDetails(),
|
||||
|
||||
largeImageKey: discordRPCAlbum,
|
||||
smallImageKey: discordRPCIcon
|
||||
});
|
||||
// DiscordClient.changePresence(detailsText, '${currentChart.songName} ($discordRPCDifficulty)', discordRPCIcon, true, currentSongLengthMs);
|
||||
#end
|
||||
|
||||
if (startTimestamp > 0)
|
||||
{
|
||||
// FlxG.sound.music.time = startTimestamp - Conductor.instance.instrumentalOffset;
|
||||
// FlxG.sound.music.time = startTimestamp - Conductor.instance.combinedOffset;
|
||||
handleSkippedNotes();
|
||||
}
|
||||
|
||||
@@ -1993,7 +2129,9 @@ class PlayState extends MusicBeatSubState
|
||||
|
||||
// Skip this if the music is paused (GameOver, Pause menu, start-of-song offset, etc.)
|
||||
if (!(FlxG.sound.music?.playing ?? false)) return;
|
||||
var timeToPlayAt:Float = Conductor.instance.songPosition - Conductor.instance.instrumentalOffset;
|
||||
|
||||
var timeToPlayAt:Float = Math.min(FlxG.sound.music.length, Math.max(0, Conductor.instance.songPosition - Conductor.instance.combinedOffset));
|
||||
trace('Resyncing vocals to ${timeToPlayAt}');
|
||||
FlxG.sound.music.pause();
|
||||
vocals.pause();
|
||||
|
||||
@@ -2373,9 +2511,9 @@ class PlayState extends MusicBeatSubState
|
||||
if (targetNote == null) continue;
|
||||
|
||||
// Judge and hit the note.
|
||||
trace('Hit note! ${targetNote.noteData}');
|
||||
// trace('Hit note! ${targetNote.noteData}');
|
||||
goodNoteHit(targetNote, input);
|
||||
trace('Score: ${songScore}');
|
||||
// trace('Score: ${songScore}');
|
||||
|
||||
notesInDirection.remove(targetNote);
|
||||
|
||||
@@ -2424,8 +2562,8 @@ class PlayState extends MusicBeatSubState
|
||||
healthChange = Constants.HEALTH_BAD_BONUS;
|
||||
isComboBreak = Constants.JUDGEMENT_BAD_COMBO_BREAK;
|
||||
case 'shit':
|
||||
isComboBreak = Constants.JUDGEMENT_SHIT_COMBO_BREAK;
|
||||
healthChange = Constants.HEALTH_SHIT_BONUS;
|
||||
isComboBreak = Constants.JUDGEMENT_SHIT_COMBO_BREAK;
|
||||
}
|
||||
|
||||
// Send the note hit event.
|
||||
@@ -2495,8 +2633,6 @@ class PlayState extends MusicBeatSubState
|
||||
}
|
||||
vocals.playerVolume = 0;
|
||||
|
||||
if (Highscore.tallies.combo != 0) if (Highscore.tallies.combo >= 10) comboPopUps.displayCombo(0);
|
||||
|
||||
applyScore(-10, 'miss', healthChange, true);
|
||||
|
||||
if (playSound)
|
||||
@@ -2566,7 +2702,7 @@ class PlayState extends MusicBeatSubState
|
||||
*/
|
||||
function debugKeyShit():Void
|
||||
{
|
||||
#if FEATURE_CHART_EDITOR
|
||||
#if FEATURE_STAGE_EDITOR
|
||||
// Open the stage editor overlaying the current state.
|
||||
if (controls.DEBUG_STAGE)
|
||||
{
|
||||
@@ -2575,7 +2711,9 @@ class PlayState extends MusicBeatSubState
|
||||
persistentUpdate = false;
|
||||
openSubState(new StageOffsetSubState());
|
||||
}
|
||||
#end
|
||||
|
||||
#if FEATURE_CHART_EDITOR
|
||||
// Redirect to the chart editor playing the current song.
|
||||
if (controls.DEBUG_CHART)
|
||||
{
|
||||
@@ -2583,11 +2721,13 @@ class PlayState extends MusicBeatSubState
|
||||
persistentUpdate = false;
|
||||
if (isChartingMode)
|
||||
{
|
||||
// Close the playtest substate.
|
||||
FlxG.sound.music?.pause();
|
||||
this.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.remove(currentStage);
|
||||
FlxG.switchState(() -> new ChartEditorState(
|
||||
{
|
||||
targetSongId: currentSong.id,
|
||||
@@ -2711,7 +2851,7 @@ class PlayState extends MusicBeatSubState
|
||||
}
|
||||
}
|
||||
comboPopUps.displayRating(daRating);
|
||||
if (combo >= 10 || combo == 0) comboPopUps.displayCombo(combo);
|
||||
if (combo >= 10) comboPopUps.displayCombo(combo);
|
||||
|
||||
vocals.playerVolume = 1;
|
||||
}
|
||||
@@ -2937,6 +3077,7 @@ class PlayState extends MusicBeatSubState
|
||||
{
|
||||
targetVariation = targetSong.getFirstValidVariation(PlayStatePlaylist.campaignDifficulty) ?? Constants.DEFAULT_VARIATION;
|
||||
}
|
||||
this.remove(currentStage);
|
||||
LoadingState.loadPlayState(
|
||||
{
|
||||
targetSong: targetSong,
|
||||
@@ -2954,6 +3095,7 @@ class PlayState extends MusicBeatSubState
|
||||
{
|
||||
targetVariation = targetSong.getFirstValidVariation(PlayStatePlaylist.campaignDifficulty) ?? Constants.DEFAULT_VARIATION;
|
||||
}
|
||||
this.remove(currentStage);
|
||||
LoadingState.loadPlayState(
|
||||
{
|
||||
targetSong: targetSong,
|
||||
@@ -3177,7 +3319,7 @@ class PlayState extends MusicBeatSubState
|
||||
/**
|
||||
* Resets the camera's zoom level and focus point.
|
||||
*/
|
||||
public function resetCamera(?resetZoom:Bool = true, ?cancelTweens:Bool = true):Void
|
||||
public function resetCamera(?resetZoom:Bool = true, ?cancelTweens:Bool = true, ?snap:Bool = true):Void
|
||||
{
|
||||
// Cancel camera tweens if any are active.
|
||||
if (cancelTweens)
|
||||
@@ -3194,7 +3336,7 @@ class PlayState extends MusicBeatSubState
|
||||
}
|
||||
|
||||
// Snap the camera to the follow point immediately.
|
||||
FlxG.camera.focusOn(cameraFollowPoint.getPosition());
|
||||
if (snap) FlxG.camera.focusOn(cameraFollowPoint.getPosition());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -74,6 +74,8 @@ class ResultState extends MusicBeatSubState
|
||||
|
||||
var playerCharacterId:Null<String>;
|
||||
|
||||
var introMusicAudio:Null<FunkinSound>;
|
||||
|
||||
var rankBg:FunkinSprite;
|
||||
final cameraBG:FunkinCamera;
|
||||
final cameraScroll:FunkinCamera;
|
||||
@@ -413,7 +415,8 @@ class ResultState extends MusicBeatSubState
|
||||
if (Assets.exists(introMusic))
|
||||
{
|
||||
// Play the intro music.
|
||||
FunkinSound.load(introMusic, 1.0, false, true, true, () -> {
|
||||
introMusicAudio = FunkinSound.load(introMusic, 1.0, false, true, true, () -> {
|
||||
introMusicAudio = null;
|
||||
FunkinSound.playMusic(getMusicPath(playerCharacter, rank),
|
||||
{
|
||||
startingVolume: 1.0,
|
||||
@@ -472,9 +475,12 @@ class ResultState extends MusicBeatSubState
|
||||
{
|
||||
ease: FlxEase.quartOut,
|
||||
onUpdate: _ -> {
|
||||
clearPercentLerp = Math.round(clearPercentLerp);
|
||||
clearPercentCounter.curNumber = Math.round(clearPercentCounter.curNumber);
|
||||
// Only play the tick sound if the number increased.
|
||||
if (clearPercentLerp != clearPercentCounter.curNumber)
|
||||
{
|
||||
trace('$clearPercentLerp and ${clearPercentCounter.curNumber}');
|
||||
clearPercentLerp = clearPercentCounter.curNumber;
|
||||
FunkinSound.playOnce(Paths.sound('scrollMenu'));
|
||||
}
|
||||
@@ -724,9 +730,34 @@ class ResultState extends MusicBeatSubState
|
||||
|
||||
if (controls.PAUSE)
|
||||
{
|
||||
if (FlxG.sound.music != null)
|
||||
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, {pitch: 3}, 0.1,
|
||||
{
|
||||
onComplete: _ -> {
|
||||
FlxTween.tween(introMusicAudio, {pitch: 0.5}, 0.4);
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (FlxG.sound.music != null)
|
||||
{
|
||||
FlxTween.tween(FlxG.sound.music, {volume: 0}, 0.8);
|
||||
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: _ -> {
|
||||
|
||||
@@ -147,7 +147,7 @@ class AnimateAtlasCharacter extends BaseCharacter
|
||||
|
||||
if (getAnimationData() != null && getAnimationData().looped)
|
||||
{
|
||||
playAnimation(prefix, true, false);
|
||||
playAnimation(currentAnimName, true, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -233,15 +233,9 @@ class BaseCharacter extends Bopper
|
||||
*/
|
||||
public function resetCharacter(resetCamera:Bool = true):Void
|
||||
{
|
||||
// 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.
|
||||
// Set the x and y to be their original values.
|
||||
this.resetPosition();
|
||||
|
||||
// Then reapply animOffsets...
|
||||
// applyAnimationOffsets(getCurrentAnimation());
|
||||
|
||||
this.dance(true); // Force to avoid the old animation playing with the wrong offset at the start of the song.
|
||||
// Make sure we are playing the idle animation
|
||||
// ...then update the hitbox so that this.width and this.height are correct.
|
||||
@@ -305,7 +299,7 @@ class BaseCharacter extends Bopper
|
||||
{
|
||||
super.onAnimationFinished(animationName);
|
||||
|
||||
trace('${characterId} has finished animation: ${animationName}');
|
||||
// trace('${characterId} has finished animation: ${animationName}');
|
||||
if ((animationName.endsWith(Constants.ANIMATION_END_SUFFIX) && !animationName.startsWith('idle') && !animationName.startsWith('dance'))
|
||||
|| animationName.startsWith('combo')
|
||||
|| animationName.startsWith('drop'))
|
||||
@@ -323,6 +317,11 @@ class BaseCharacter extends Bopper
|
||||
this.cameraFocusPoint = new FlxPoint(charCenterX + _data.cameraOffsets[0], charCenterY + _data.cameraOffsets[1]);
|
||||
}
|
||||
|
||||
public function getHealthIconId():String
|
||||
{
|
||||
return _data?.healthIcon?.id ?? Constants.DEFAULT_HEALTH_ICON;
|
||||
}
|
||||
|
||||
public function initHealthIcon(isOpponent:Bool):Void
|
||||
{
|
||||
if (!isOpponent)
|
||||
@@ -332,7 +331,7 @@ class BaseCharacter extends Bopper
|
||||
trace('[WARN] Player 1 health icon not found!');
|
||||
return;
|
||||
}
|
||||
PlayState.instance.iconP1.configure(_data.healthIcon);
|
||||
PlayState.instance.iconP1.configure(_data?.healthIcon);
|
||||
PlayState.instance.iconP1.flipX = !PlayState.instance.iconP1.flipX; // BF is looking the other way.
|
||||
}
|
||||
else
|
||||
@@ -342,7 +341,7 @@ class BaseCharacter extends Bopper
|
||||
trace('[WARN] Player 2 health icon not found!');
|
||||
return;
|
||||
}
|
||||
PlayState.instance.iconP2.configure(_data.healthIcon);
|
||||
PlayState.instance.iconP2.configure(_data?.healthIcon);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,7 +401,7 @@ class BaseCharacter extends Bopper
|
||||
FlxG.watch.addQuick('singTimeSec-${characterId}', singTimeSec);
|
||||
if (holdTimer > singTimeSec && shouldStopSinging)
|
||||
{
|
||||
trace('holdTimer reached ${holdTimer}sec (> ${singTimeSec}), stopping sing animation');
|
||||
// trace('holdTimer reached ${holdTimer}sec (> ${singTimeSec}), stopping sing animation');
|
||||
holdTimer = 0;
|
||||
|
||||
var currentAnimation:String = getCurrentAnimation();
|
||||
@@ -636,7 +635,7 @@ class BaseCharacter extends Bopper
|
||||
var anim:String = 'sing${dir.nameUpper}${miss ? 'miss' : ''}${suffix != '' ? '-${suffix}' : ''}';
|
||||
|
||||
// restart even if already playing, because the character might sing the same note twice.
|
||||
trace('Playing ${anim}...');
|
||||
// trace('Playing ${anim}...');
|
||||
playAnimation(anim, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import funkin.play.character.ScriptedCharacter.ScriptedSparrowCharacter;
|
||||
import funkin.util.assets.DataAssets;
|
||||
import funkin.util.VersionUtil;
|
||||
import haxe.Json;
|
||||
import openfl.utils.Assets;
|
||||
import flixel.graphics.frames.FlxFrame;
|
||||
|
||||
class CharacterDataParser
|
||||
{
|
||||
@@ -281,41 +281,78 @@ class CharacterDataParser
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Hardcode this.
|
||||
* Returns the idle frame of a character.
|
||||
*/
|
||||
public static function getCharPixelIconAsset(char:String):String
|
||||
public static function getCharPixelIconAsset(char:String):FlxFrame
|
||||
{
|
||||
var icon:String = char;
|
||||
var charPath:String = "freeplay/icons/";
|
||||
|
||||
switch (icon)
|
||||
// FunkinCrew please dont skin me alive for copying pixelated icon and changing it a tiny bit
|
||||
switch (char)
|
||||
{
|
||||
case "bf-christmas" | "bf-car" | "bf-pixel" | "bf-holding-gf":
|
||||
icon = "bf";
|
||||
case "bf-christmas" | "bf-car" | "bf-pixel" | "bf-holding-gf" | "bf-dark":
|
||||
charPath += "bfpixel";
|
||||
case "monster-christmas":
|
||||
icon = "monster";
|
||||
charPath += "monsterpixel";
|
||||
case "mom" | "mom-car":
|
||||
icon = "mommy";
|
||||
charPath += "mommypixel";
|
||||
case "pico-blazin" | "pico-playable" | "pico-speaker":
|
||||
icon = "pico";
|
||||
case "gf-christmas" | "gf-car" | "gf-pixel" | "gf-tankmen":
|
||||
icon = "gf";
|
||||
charPath += "picopixel";
|
||||
case "gf-christmas" | "gf-car" | "gf-pixel" | "gf-tankmen" | "gf-dark":
|
||||
charPath += "gfpixel";
|
||||
case "dad":
|
||||
icon = "daddy";
|
||||
charPath += "dadpixel";
|
||||
case "darnell-blazin":
|
||||
icon = "darnell";
|
||||
charPath += "darnellpixel";
|
||||
case "senpai-angry":
|
||||
icon = "senpai";
|
||||
charPath += "senpaipixel";
|
||||
case "spooky-dark":
|
||||
icon = "spooky";
|
||||
charPath += "spookypixel";
|
||||
case "tankman-atlas":
|
||||
icon = "tankman";
|
||||
charPath += "tankmanpixel";
|
||||
case "pico-christmas" | "pico-dark":
|
||||
charPath += "picopixel";
|
||||
default:
|
||||
charPath += '${char}pixel';
|
||||
}
|
||||
|
||||
var path = Paths.image("freeplay/icons/" + icon + "pixel");
|
||||
if (Assets.exists(path)) return path;
|
||||
if (!Assets.exists(Paths.image(charPath)))
|
||||
{
|
||||
trace('[WARN] Character ${char} has no freeplay icon.');
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: Hardcode some additional behavior or a fallback.
|
||||
return null;
|
||||
var isAnimated = Assets.exists(Paths.file('images/$charPath.xml'));
|
||||
var frame:FlxFrame = null;
|
||||
|
||||
if (isAnimated)
|
||||
{
|
||||
var frames = Paths.getSparrowAtlas(charPath);
|
||||
|
||||
var idleFrame:FlxFrame = frames.frames.find(function(frame:FlxFrame):Bool {
|
||||
return frame.name.startsWith('idle');
|
||||
});
|
||||
|
||||
if (idleFrame == null)
|
||||
{
|
||||
trace('[WARN] Character ${char} has no idle in their freeplay icon.');
|
||||
return null;
|
||||
}
|
||||
|
||||
// so, haxe.ui.backend.AssetsImpl uses the parent width and height, which makes the image go crazy when rendered
|
||||
// so this is a work around so that it uses the actual width and height
|
||||
var imageGraphic = flixel.graphics.FlxGraphic.fromFrame(idleFrame);
|
||||
|
||||
var imageFrame = flixel.graphics.frames.FlxImageFrame.fromImage(imageGraphic);
|
||||
frame = imageFrame.frame;
|
||||
}
|
||||
else
|
||||
{
|
||||
var imageFrame = flixel.graphics.frames.FlxImageFrame.fromImage(Paths.image(charPath));
|
||||
frame = imageFrame.frame;
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,7 +5,6 @@ import flixel.FlxSprite;
|
||||
import flixel.math.FlxMath;
|
||||
import flixel.math.FlxPoint;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import openfl.utils.Assets;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.util.MathUtil;
|
||||
|
||||
@@ -33,7 +32,7 @@ class HealthIcon extends FunkinSprite
|
||||
* The character this icon is representing.
|
||||
* Setting this variable will automatically update the graphic.
|
||||
*/
|
||||
public var characterId(default, set):Null<String>;
|
||||
public var characterId(default, set):String = Constants.DEFAULT_HEALTH_ICON;
|
||||
|
||||
/**
|
||||
* Whether this health icon should automatically update its state based on the character's health.
|
||||
@@ -116,7 +115,7 @@ class HealthIcon extends FunkinSprite
|
||||
*/
|
||||
static final POSITION_OFFSET:Int = 26;
|
||||
|
||||
public function new(char:String = 'bf', playerId:Int = 0)
|
||||
public function new(char:Null<String>, playerId:Int = 0)
|
||||
{
|
||||
super(0, 0);
|
||||
this.playerId = playerId;
|
||||
@@ -131,7 +130,7 @@ class HealthIcon extends FunkinSprite
|
||||
snapToTargetSize();
|
||||
}
|
||||
|
||||
function set_characterId(value:Null<String>):Null<String>
|
||||
function set_characterId(value:Null<String>):String
|
||||
{
|
||||
if (value == characterId) return value;
|
||||
|
||||
@@ -412,20 +411,9 @@ class HealthIcon extends FunkinSprite
|
||||
}
|
||||
}
|
||||
|
||||
function correctCharacterId(charId:Null<String>):String
|
||||
function iconExists(charId:String):Bool
|
||||
{
|
||||
if (charId == null)
|
||||
{
|
||||
return Constants.DEFAULT_HEALTH_ICON;
|
||||
}
|
||||
|
||||
if (!Assets.exists(Paths.image('icons/icon-$charId')))
|
||||
{
|
||||
FlxG.log.warn('No icon for character: $charId : using default placeholder face instead!');
|
||||
return Constants.DEFAULT_HEALTH_ICON;
|
||||
}
|
||||
|
||||
return charId;
|
||||
return Assets.exists(Paths.image('icons/icon-$charId'));
|
||||
}
|
||||
|
||||
function isNewSpritesheet(charId:String):Bool
|
||||
@@ -435,11 +423,11 @@ class HealthIcon extends FunkinSprite
|
||||
|
||||
function loadCharacter(charId:Null<String>):Void
|
||||
{
|
||||
if (charId == null || correctCharacterId(charId) != charId)
|
||||
if (charId == null || !iconExists(charId))
|
||||
{
|
||||
// This will recursively trigger loadCharacter to be called again.
|
||||
characterId = correctCharacterId(charId);
|
||||
return;
|
||||
FlxG.log.warn('No icon for character: $charId : using default placeholder face instead!');
|
||||
characterId = Constants.DEFAULT_HEALTH_ICON;
|
||||
charId = characterId;
|
||||
}
|
||||
|
||||
isLegacyStyle = !isNewSpritesheet(charId);
|
||||
|
||||
@@ -8,7 +8,6 @@ import funkin.graphics.FunkinSprite;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.util.TimerUtil;
|
||||
import funkin.util.EaseUtil;
|
||||
import openfl.utils.Assets;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
|
||||
@@ -95,7 +94,6 @@ class PopUpStuff extends FlxTypedGroup<FunkinSprite>
|
||||
if (numScore == null) continue;
|
||||
|
||||
numScore.x = (FlxG.width * 0.507) - (36 * daLoop) - 65;
|
||||
trace('numScore($daLoop) = ${numScore.x}');
|
||||
numScore.y = (FlxG.camera.height * 0.44);
|
||||
|
||||
numScore.x += offsets[0];
|
||||
|
||||
@@ -11,7 +11,7 @@ import flixel.util.FlxTimer;
|
||||
import funkin.graphics.video.FlxVideo;
|
||||
#end
|
||||
#if hxCodec
|
||||
import hxcodec.flixel.FlxVideoSprite;
|
||||
import funkin.graphics.video.FunkinVideoSprite;
|
||||
#end
|
||||
|
||||
/**
|
||||
@@ -26,7 +26,7 @@ class VideoCutscene
|
||||
static var vid:FlxVideo;
|
||||
#end
|
||||
#if hxCodec
|
||||
static var vid:FlxVideoSprite;
|
||||
static var vid:FunkinVideoSprite;
|
||||
#end
|
||||
|
||||
/**
|
||||
@@ -138,7 +138,7 @@ class VideoCutscene
|
||||
static function playVideoNative(filePath:String):Void
|
||||
{
|
||||
// Video displays OVER the FlxState.
|
||||
vid = new FlxVideoSprite(0, 0);
|
||||
vid = new FunkinVideoSprite(0, 0);
|
||||
|
||||
if (vid != null)
|
||||
{
|
||||
|
||||
@@ -127,7 +127,8 @@ class FocusCameraSongEvent extends SongEvent
|
||||
switch (ease)
|
||||
{
|
||||
case 'CLASSIC': // Old-school. No ease. Just set follow point.
|
||||
PlayState.instance.resetCamera(false, true);
|
||||
PlayState.instance.resetCamera(false, false, false);
|
||||
PlayState.instance.cancelCameraFollowTween();
|
||||
PlayState.instance.cameraFollowPoint.setPosition(targetX, targetY);
|
||||
case 'INSTANT': // Instant ease. Duration is automatically 0.
|
||||
PlayState.instance.tweenCameraToPosition(targetX, targetY, 0);
|
||||
|
||||
@@ -2,28 +2,46 @@ package funkin.play.notes;
|
||||
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import flixel.FlxSprite;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.play.notes.NoteSprite;
|
||||
|
||||
/**
|
||||
* The actual receptor that you see on screen.
|
||||
*/
|
||||
class StrumlineNote extends FlxSprite
|
||||
class StrumlineNote extends FunkinSprite
|
||||
{
|
||||
/**
|
||||
* Whether this strumline note is on the player's side or the opponent's side.
|
||||
*/
|
||||
public var isPlayer(default, null):Bool;
|
||||
|
||||
/**
|
||||
* The direction which this strumline note is facing.
|
||||
*/
|
||||
public var direction(default, set):NoteDirection;
|
||||
|
||||
var confirmHoldTimer:Float = -1;
|
||||
|
||||
static final CONFIRM_HOLD_TIME:Float = 0.1;
|
||||
|
||||
function set_direction(value:NoteDirection):NoteDirection
|
||||
{
|
||||
this.direction = value;
|
||||
return this.direction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this flag to `true` to disable performance optimizations that cause
|
||||
* the Strumline note sprite to ignore `velocity` and `acceleration`.
|
||||
*/
|
||||
public var forceActive:Bool = false;
|
||||
|
||||
/**
|
||||
* How long to continue the hold note animation after a note is pressed.
|
||||
*/
|
||||
static final CONFIRM_HOLD_TIME:Float = 0.1;
|
||||
|
||||
/**
|
||||
* How long the hold note animation has been playing after a note is pressed.
|
||||
*/
|
||||
var confirmHoldTimer:Float = -1;
|
||||
|
||||
public function new(noteStyle:NoteStyle, isPlayer:Bool, direction:NoteDirection)
|
||||
{
|
||||
super(0, 0);
|
||||
@@ -41,7 +59,10 @@ class StrumlineNote extends FlxSprite
|
||||
this.active = true;
|
||||
}
|
||||
|
||||
function onAnimationFrame(name:String, frameNumber:Int, frameIndex:Int):Void {}
|
||||
function onAnimationFrame(name:String, frameNumber:Int, frameIndex:Int):Void
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
function onAnimationFinished(name:String):Void
|
||||
{
|
||||
@@ -85,7 +106,8 @@ class StrumlineNote extends FlxSprite
|
||||
noteStyle.applyStrumlineFrames(this);
|
||||
noteStyle.applyStrumlineAnimations(this, this.direction);
|
||||
|
||||
this.setGraphicSize(Std.int(Strumline.STRUMLINE_SIZE * noteStyle.getStrumlineScale()));
|
||||
var scale = noteStyle.getStrumlineScale();
|
||||
this.scale.set(scale, scale);
|
||||
this.updateHitbox();
|
||||
noteStyle.applyStrumlineOffsets(this);
|
||||
|
||||
@@ -102,19 +124,19 @@ class StrumlineNote extends FlxSprite
|
||||
|
||||
public function playStatic():Void
|
||||
{
|
||||
this.active = false;
|
||||
this.active = (forceActive || isAnimationDynamic('static'));
|
||||
this.playAnimation('static', true);
|
||||
}
|
||||
|
||||
public function playPress():Void
|
||||
{
|
||||
this.active = true;
|
||||
this.active = (forceActive || isAnimationDynamic('press'));
|
||||
this.playAnimation('press', true);
|
||||
}
|
||||
|
||||
public function playConfirm():Void
|
||||
{
|
||||
this.active = true;
|
||||
this.active = (forceActive || isAnimationDynamic('confirm'));
|
||||
this.playAnimation('confirm', true);
|
||||
}
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@ class SustainTrail extends FlxSprite
|
||||
public var bottomClip:Float = 0.9;
|
||||
|
||||
public var isPixel:Bool;
|
||||
public var noteStyleOffsets:Array<Float>;
|
||||
|
||||
var graphicWidth:Float = 0;
|
||||
var graphicHeight:Float = 0;
|
||||
@@ -107,6 +108,7 @@ class SustainTrail extends FlxSprite
|
||||
this.noteDirection = noteDirection;
|
||||
|
||||
setupHoldNoteGraphic(noteStyle);
|
||||
noteStyleOffsets = noteStyle.getHoldNoteOffsets();
|
||||
|
||||
indices = new DrawData<Int>(12, true, TRIANGLE_VERTEX_INDICES);
|
||||
|
||||
@@ -137,7 +139,6 @@ class SustainTrail extends FlxSprite
|
||||
|
||||
zoom = 1.0;
|
||||
zoom *= noteStyle.fetchHoldNoteScale();
|
||||
zoom *= 0.7;
|
||||
|
||||
// CALCULATE SIZE
|
||||
graphicWidth = graphic.width / 8 * zoom; // amount of notes * 2
|
||||
@@ -202,7 +203,7 @@ class SustainTrail extends FlxSprite
|
||||
{
|
||||
width = graphicWidth;
|
||||
height = graphicHeight;
|
||||
offset.set(0, 0);
|
||||
offset.set(noteStyleOffsets[0], noteStyleOffsets[1]);
|
||||
origin.set(width * 0.5, height * 0.5);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,12 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
* The note style to use if this one doesn't have a certain asset.
|
||||
* This can be recursive, ehe.
|
||||
*/
|
||||
final fallback:Null<NoteStyle>;
|
||||
var fallback(get, never):Null<NoteStyle>;
|
||||
|
||||
function get_fallback():Null<NoteStyle> {
|
||||
if (_data == null || _data.fallback == null) return null;
|
||||
return NoteStyleRegistry.instance.fetchEntry(_data.fallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param id The ID of the JSON file to parse.
|
||||
@@ -43,9 +48,6 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
{
|
||||
this.id = id;
|
||||
_data = _fetchData(id);
|
||||
|
||||
var fallbackID = _data.fallback;
|
||||
if (fallbackID != null) this.fallback = NoteStyleRegistry.instance.fetchEntry(fallbackID);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,7 +95,8 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
buildNoteAnimations(target);
|
||||
|
||||
// Set the scale.
|
||||
target.setGraphicSize(Strumline.STRUMLINE_SIZE * getNoteScale());
|
||||
var scale = getNoteScale();
|
||||
target.scale.set(scale, scale);
|
||||
target.updateHitbox();
|
||||
}
|
||||
|
||||
@@ -102,7 +105,11 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
function buildNoteFrames(force:Bool = false):Null<FlxAtlasFrames>
|
||||
{
|
||||
var noteAssetPath = getNoteAssetPath();
|
||||
if (noteAssetPath == null) return null;
|
||||
if (noteAssetPath == null)
|
||||
{
|
||||
FlxG.log.warn('Note asset path not found: ${id}');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!FunkinSprite.isTextureCached(Paths.image(noteAssetPath)))
|
||||
{
|
||||
@@ -135,7 +142,7 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
if (raw)
|
||||
{
|
||||
var rawPath:Null<String> = _data?.assets?.note?.assetPath;
|
||||
if (rawPath == null && fallback != null) return fallback.getNoteAssetPath(true);
|
||||
if (rawPath == null) return fallback?.getNoteAssetPath(true);
|
||||
return rawPath;
|
||||
}
|
||||
|
||||
@@ -173,12 +180,13 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
|
||||
public function isNoteAnimated():Bool
|
||||
{
|
||||
return _data.assets?.note?.animated ?? false;
|
||||
// LOL is double ?? bad practice?
|
||||
return _data.assets?.note?.animated ?? fallback?.isNoteAnimated() ?? false;
|
||||
}
|
||||
|
||||
public function getNoteScale():Float
|
||||
{
|
||||
return _data.assets?.note?.scale ?? 1.0;
|
||||
return _data.assets?.note?.scale ?? fallback?.getNoteScale() ?? 1.0;
|
||||
}
|
||||
|
||||
function fetchNoteAnimationData(dir:NoteDirection):Null<AnimationData>
|
||||
@@ -191,16 +199,15 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
case RIGHT: _data.assets?.note?.data?.right?.toNamed();
|
||||
};
|
||||
|
||||
return (result == null && fallback != null) ? fallback.fetchNoteAnimationData(dir) : result;
|
||||
return result ?? fallback?.fetchNoteAnimationData(dir);
|
||||
}
|
||||
|
||||
public function getHoldNoteAssetPath(raw:Bool = false):Null<String>
|
||||
{
|
||||
if (raw)
|
||||
{
|
||||
// TODO: figure out why ?. didn't work here
|
||||
var rawPath:Null<String> = (_data?.assets?.holdNote == null) ? null : _data?.assets?.holdNote?.assetPath;
|
||||
return (rawPath == null && fallback != null) ? fallback.getHoldNoteAssetPath(true) : rawPath;
|
||||
var rawPath:Null<String> = _data?.assets?.holdNote?.assetPath;
|
||||
return rawPath ?? fallback?.getHoldNoteAssetPath(true);
|
||||
}
|
||||
|
||||
// library:path
|
||||
@@ -212,16 +219,17 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
|
||||
public function isHoldNotePixel():Bool
|
||||
{
|
||||
var data = _data?.assets?.holdNote;
|
||||
if (data == null && fallback != null) return fallback.isHoldNotePixel();
|
||||
return data?.isPixel ?? false;
|
||||
return _data?.assets?.holdNote?.isPixel ?? fallback?.isHoldNotePixel() ?? false;
|
||||
}
|
||||
|
||||
public function fetchHoldNoteScale():Float
|
||||
{
|
||||
var data = _data?.assets?.holdNote;
|
||||
if (data == null && fallback != null) return fallback.fetchHoldNoteScale();
|
||||
return data?.scale ?? 1.0;
|
||||
return _data?.assets?.holdNote?.scale ?? fallback?.fetchHoldNoteScale() ?? 1.0;
|
||||
}
|
||||
|
||||
public function getHoldNoteOffsets():Array<Float>
|
||||
{
|
||||
return _data?.assets?.holdNote?.offsets ?? fallback?.getHoldNoteOffsets() ?? [0.0, 0.0];
|
||||
}
|
||||
|
||||
public function applyStrumlineFrames(target:StrumlineNote):Void
|
||||
@@ -246,9 +254,7 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
{
|
||||
if (raw)
|
||||
{
|
||||
var rawPath:Null<String> = _data?.assets?.noteStrumline?.assetPath;
|
||||
if (rawPath == null && fallback != null) return fallback.getStrumlineAssetPath(true);
|
||||
return rawPath;
|
||||
return _data?.assets?.noteStrumline?.assetPath ?? fallback?.getStrumlineAssetPath(true);
|
||||
}
|
||||
|
||||
// library:path
|
||||
@@ -270,11 +276,19 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
FlxAnimationUtil.addAtlasAnimations(target, getStrumlineAnimationData(dir));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the animation data for the strumline.
|
||||
* NOTE: This function only queries the fallback note style if all the animations are missing for a given direction.
|
||||
*
|
||||
* @param dir The direction to fetch the animation data for.
|
||||
* @return The animation data for the strumline in that direction.
|
||||
*/
|
||||
function getStrumlineAnimationData(dir:NoteDirection):Array<AnimationData>
|
||||
{
|
||||
var result:Array<Null<AnimationData>> = switch (dir)
|
||||
{
|
||||
case NoteDirection.LEFT: [
|
||||
case NoteDirection.LEFT:
|
||||
[
|
||||
_data.assets.noteStrumline?.data?.leftStatic?.toNamed('static'),
|
||||
_data.assets.noteStrumline?.data?.leftPress?.toNamed('press'),
|
||||
_data.assets.noteStrumline?.data?.leftConfirm?.toNamed('confirm'),
|
||||
@@ -301,33 +315,39 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
default: [];
|
||||
};
|
||||
|
||||
return thx.Arrays.filterNull(result);
|
||||
// New variable so we can change the type.
|
||||
var filteredResult:Array<AnimationData> = thx.Arrays.filterNull(result);
|
||||
|
||||
if (filteredResult.length == 0) return fallback?.getStrumlineAnimationData(dir) ?? [];
|
||||
|
||||
return filteredResult;
|
||||
}
|
||||
|
||||
public function getStrumlineOffsets():Array<Float>
|
||||
{
|
||||
return _data?.assets?.noteStrumline?.offsets ?? fallback?.getStrumlineOffsets() ?? [0.0, 0.0];
|
||||
}
|
||||
|
||||
public function applyStrumlineOffsets(target:StrumlineNote):Void
|
||||
{
|
||||
var offsets = _data?.assets?.noteStrumline?.offsets ?? [0.0, 0.0];
|
||||
var offsets = getStrumlineOffsets();
|
||||
target.x += offsets[0];
|
||||
target.y += offsets[1];
|
||||
}
|
||||
|
||||
public function getStrumlineScale():Float
|
||||
{
|
||||
return _data?.assets?.noteStrumline?.scale ?? 1.0;
|
||||
return _data?.assets?.noteStrumline?.scale ?? fallback?.getStrumlineScale() ?? 1.0;
|
||||
}
|
||||
|
||||
public function isNoteSplashEnabled():Bool
|
||||
{
|
||||
var data = _data?.assets?.noteSplash?.data;
|
||||
if (data == null) return fallback?.isNoteSplashEnabled() ?? false;
|
||||
return data.enabled ?? false;
|
||||
return _data?.assets?.noteSplash?.data?.enabled ?? fallback?.isNoteSplashEnabled() ?? false;
|
||||
}
|
||||
|
||||
public function isHoldNoteCoverEnabled():Bool
|
||||
{
|
||||
var data = _data?.assets?.holdNoteCover?.data;
|
||||
if (data == null) return fallback?.isHoldNoteCoverEnabled() ?? false;
|
||||
return data.enabled ?? false;
|
||||
return _data?.assets?.holdNoteCover?.data?.enabled ?? fallback?.isHoldNoteCoverEnabled() ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -438,20 +458,20 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
{
|
||||
case THREE:
|
||||
var result = _data.assets.countdownThree?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isCountdownSpritePixel(step);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isCountdownSpritePixel(step) ?? false;
|
||||
return result;
|
||||
case TWO:
|
||||
var result = _data.assets.countdownTwo?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isCountdownSpritePixel(step);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isCountdownSpritePixel(step) ?? false;
|
||||
return result;
|
||||
case ONE:
|
||||
var result = _data.assets.countdownOne?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isCountdownSpritePixel(step);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isCountdownSpritePixel(step) ?? false;
|
||||
return result;
|
||||
case GO:
|
||||
var result = _data.assets.countdownGo?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isCountdownSpritePixel(step);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isCountdownSpritePixel(step) ?? false;
|
||||
return result;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -463,20 +483,20 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
{
|
||||
case THREE:
|
||||
var result = _data.assets.countdownThree?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getCountdownSpriteOffsets(step);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getCountdownSpriteOffsets(step) ?? [0, 0];
|
||||
return result;
|
||||
case TWO:
|
||||
var result = _data.assets.countdownTwo?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getCountdownSpriteOffsets(step);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getCountdownSpriteOffsets(step) ?? [0, 0];
|
||||
return result;
|
||||
case ONE:
|
||||
var result = _data.assets.countdownOne?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getCountdownSpriteOffsets(step);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getCountdownSpriteOffsets(step) ?? [0, 0];
|
||||
return result;
|
||||
case GO:
|
||||
var result = _data.assets.countdownGo?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getCountdownSpriteOffsets(step);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getCountdownSpriteOffsets(step) ?? [0, 0];
|
||||
return result;
|
||||
default:
|
||||
return [0, 0];
|
||||
}
|
||||
@@ -501,7 +521,7 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
null;
|
||||
}
|
||||
|
||||
return (rawPath == null && fallback != null) ? fallback.getCountdownSoundPath(step, true) : rawPath;
|
||||
return (rawPath == null) ? fallback?.getCountdownSoundPath(step, true) : rawPath;
|
||||
}
|
||||
|
||||
// library:path
|
||||
@@ -565,20 +585,20 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
{
|
||||
case "sick":
|
||||
var result = _data.assets.judgementSick?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isJudgementSpritePixel(rating);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isJudgementSpritePixel(rating) ?? false;
|
||||
return result;
|
||||
case "good":
|
||||
var result = _data.assets.judgementGood?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isJudgementSpritePixel(rating);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isJudgementSpritePixel(rating) ?? false;
|
||||
return result;
|
||||
case "bad":
|
||||
var result = _data.assets.judgementBad?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isJudgementSpritePixel(rating);
|
||||
return result ?? false;
|
||||
case "GO":
|
||||
if (result == null) result = fallback?.isJudgementSpritePixel(rating) ?? false;
|
||||
return result;
|
||||
case "shit":
|
||||
var result = _data.assets.judgementShit?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isJudgementSpritePixel(rating);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isJudgementSpritePixel(rating) ?? false;
|
||||
return result;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -616,20 +636,20 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
{
|
||||
case "sick":
|
||||
var result = _data.assets.judgementSick?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getJudgementSpriteOffsets(rating);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getJudgementSpriteOffsets(rating) ?? [0, 0];
|
||||
return result;
|
||||
case "good":
|
||||
var result = _data.assets.judgementGood?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getJudgementSpriteOffsets(rating);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getJudgementSpriteOffsets(rating) ?? [0, 0];
|
||||
return result;
|
||||
case "bad":
|
||||
var result = _data.assets.judgementBad?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getJudgementSpriteOffsets(rating);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getJudgementSpriteOffsets(rating) ?? [0, 0];
|
||||
return result;
|
||||
case "shit":
|
||||
var result = _data.assets.judgementShit?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getJudgementSpriteOffsets(rating);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getJudgementSpriteOffsets(rating) ?? [0, 0];
|
||||
return result;
|
||||
default:
|
||||
return [0, 0];
|
||||
}
|
||||
@@ -730,44 +750,44 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
{
|
||||
case 0:
|
||||
var result = _data.assets.comboNumber0?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isComboNumSpritePixel(digit) ?? false;
|
||||
return result;
|
||||
case 1:
|
||||
var result = _data.assets.comboNumber1?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isComboNumSpritePixel(digit) ?? false;
|
||||
return result;
|
||||
case 2:
|
||||
var result = _data.assets.comboNumber2?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isComboNumSpritePixel(digit) ?? false;
|
||||
return result;
|
||||
case 3:
|
||||
var result = _data.assets.comboNumber3?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isComboNumSpritePixel(digit) ?? false;
|
||||
return result;
|
||||
case 4:
|
||||
var result = _data.assets.comboNumber4?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isComboNumSpritePixel(digit) ?? false;
|
||||
return result;
|
||||
case 5:
|
||||
var result = _data.assets.comboNumber5?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isComboNumSpritePixel(digit) ?? false;
|
||||
return result;
|
||||
case 6:
|
||||
var result = _data.assets.comboNumber6?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isComboNumSpritePixel(digit) ?? false;
|
||||
return result;
|
||||
case 7:
|
||||
var result = _data.assets.comboNumber7?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isComboNumSpritePixel(digit) ?? false;
|
||||
return result;
|
||||
case 8:
|
||||
var result = _data.assets.comboNumber8?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isComboNumSpritePixel(digit) ?? false;
|
||||
return result;
|
||||
case 9:
|
||||
var result = _data.assets.comboNumber9?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isComboNumSpritePixel(digit) ?? false;
|
||||
return result;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -817,44 +837,44 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
{
|
||||
case 0:
|
||||
var result = _data.assets.comboNumber0?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getComboNumSpriteOffsets(digit) ?? [0, 0];
|
||||
return result;
|
||||
case 1:
|
||||
var result = _data.assets.comboNumber1?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getComboNumSpriteOffsets(digit) ?? [0, 0];
|
||||
return result;
|
||||
case 2:
|
||||
var result = _data.assets.comboNumber2?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getComboNumSpriteOffsets(digit) ?? [0, 0];
|
||||
return result;
|
||||
case 3:
|
||||
var result = _data.assets.comboNumber3?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getComboNumSpriteOffsets(digit) ?? [0, 0];
|
||||
return result;
|
||||
case 4:
|
||||
var result = _data.assets.comboNumber4?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getComboNumSpriteOffsets(digit) ?? [0, 0];
|
||||
return result;
|
||||
case 5:
|
||||
var result = _data.assets.comboNumber5?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getComboNumSpriteOffsets(digit) ?? [0, 0];
|
||||
return result;
|
||||
case 6:
|
||||
var result = _data.assets.comboNumber6?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getComboNumSpriteOffsets(digit) ?? [0, 0];
|
||||
return result;
|
||||
case 7:
|
||||
var result = _data.assets.comboNumber7?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getComboNumSpriteOffsets(digit) ?? [0, 0];
|
||||
return result;
|
||||
case 8:
|
||||
var result = _data.assets.comboNumber8?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getComboNumSpriteOffsets(digit) ?? [0, 0];
|
||||
return result;
|
||||
case 9:
|
||||
var result = _data.assets.comboNumber9?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getComboNumSpriteOffsets(digit) ?? [0, 0];
|
||||
return result;
|
||||
default:
|
||||
return [0, 0];
|
||||
}
|
||||
|
||||
+117
-45
@@ -15,8 +15,8 @@ import funkin.data.song.SongRegistry;
|
||||
import funkin.modding.IScriptedClass.IPlayStateScriptedClass;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.ui.freeplay.charselect.PlayableCharacter;
|
||||
import funkin.data.freeplay.player.PlayerRegistry;
|
||||
import funkin.util.SortUtil;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
/**
|
||||
* This is a data structure managing information about the current song.
|
||||
@@ -80,7 +80,12 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
|
||||
// key = variation id, value = metadata
|
||||
final _metadata:Map<String, SongMetadata>;
|
||||
final difficulties:Map<String, SongDifficulty>;
|
||||
|
||||
/**
|
||||
* holds the difficulties (as in SongDifficulty) for each variation
|
||||
* difficulties.get('default').get('easy') would return the easy difficulty for the default variation
|
||||
*/
|
||||
final difficulties:Map<String, Map<String, SongDifficulty>>;
|
||||
|
||||
/**
|
||||
* The list of variations a song has.
|
||||
@@ -93,7 +98,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
}
|
||||
|
||||
// this returns false so that any new song can override this and return true when needed
|
||||
public function isSongNew(currentDifficulty:String):Bool
|
||||
public function isSongNew(currentDifficulty:String, currentVariation:String):Bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -147,7 +152,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
{
|
||||
this.id = id;
|
||||
|
||||
difficulties = new Map<String, SongDifficulty>();
|
||||
difficulties = new Map<String, Map<String, SongDifficulty>>();
|
||||
|
||||
_data = _fetchData(id);
|
||||
|
||||
@@ -157,6 +162,12 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
{
|
||||
for (vari in _data.playData.songVariations)
|
||||
{
|
||||
if (!validateVariationId(vari))
|
||||
{
|
||||
trace(' [WARN] Variation id "$vari" is invalid, skipping...');
|
||||
continue;
|
||||
}
|
||||
|
||||
var variMeta:Null<SongMetadata> = fetchVariationMetadata(id, vari);
|
||||
if (variMeta != null)
|
||||
{
|
||||
@@ -245,22 +256,39 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
* List the album IDs for each variation of the song.
|
||||
* @return A map of variation IDs to album IDs.
|
||||
*/
|
||||
public function listAlbums():Map<String, String>
|
||||
public function listAlbums(variation:String):Map<String, String>
|
||||
{
|
||||
var result:Map<String, String> = new Map<String, String>();
|
||||
|
||||
for (difficultyId in difficulties.keys())
|
||||
for (variationMap in difficulties)
|
||||
{
|
||||
var meta:Null<SongDifficulty> = difficulties.get(difficultyId);
|
||||
if (meta != null && meta.album != null)
|
||||
for (difficultyId in variationMap.keys())
|
||||
{
|
||||
result.set(difficultyId, meta.album);
|
||||
var meta:Null<SongDifficulty> = variationMap.get(difficultyId);
|
||||
if (meta != null && meta.album != null)
|
||||
{
|
||||
result.set(difficultyId, meta.album);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Input a difficulty ID and a variation ID, and get the album ID.
|
||||
* @param diffId
|
||||
* @param variation
|
||||
* @return String
|
||||
*/
|
||||
public function getAlbumId(diffId:String, variation:String):String
|
||||
{
|
||||
var diff:Null<SongDifficulty> = getDifficulty(diffId, variation);
|
||||
if (diff == null) return '';
|
||||
|
||||
return diff.album ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the difficulty data from the provided metadata.
|
||||
* Does not load chart data (that is triggered later when we want to play the song).
|
||||
@@ -281,6 +309,9 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
continue;
|
||||
}
|
||||
|
||||
// This resides within difficulties
|
||||
var difficultyMap:Map<String, SongDifficulty> = new Map<String, SongDifficulty>();
|
||||
|
||||
// There may be more difficulties in the chart file than in the metadata,
|
||||
// (i.e. non-playable charts like the one used for Pico on the speaker in Stress)
|
||||
// but all the difficulties in the metadata must be in the chart file.
|
||||
@@ -305,10 +336,9 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
difficulty.noteStyle = metadata.playData.noteStyle;
|
||||
|
||||
difficulty.characters = metadata.playData.characters;
|
||||
|
||||
var variationSuffix = (metadata.variation != Constants.DEFAULT_VARIATION) ? '-${metadata.variation}' : '';
|
||||
difficulties.set('$diffId$variationSuffix', difficulty);
|
||||
difficultyMap.set(diffId, difficulty);
|
||||
}
|
||||
difficulties.set(metadata.variation, difficultyMap);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,15 +371,18 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
|
||||
for (diffId in chartNotes.keys())
|
||||
{
|
||||
// Retrieve the cached difficulty data.
|
||||
var variationSuffix = (variation != Constants.DEFAULT_VARIATION) ? '-$variation' : '';
|
||||
var difficulty:Null<SongDifficulty> = difficulties.get('$diffId$variationSuffix');
|
||||
if (difficulty == null)
|
||||
// Retrieve the cached difficulty data. This one could potentially be null.
|
||||
var nullDiff:Null<SongDifficulty> = getDifficulty(diffId, variation);
|
||||
|
||||
// if the difficulty doesn't exist, create a new one, and then proceed to fill it with data.
|
||||
// I mostly do this since I don't wanna throw around ? everywhere for null check lol?
|
||||
var difficulty:SongDifficulty = nullDiff ?? new SongDifficulty(this, diffId, variation);
|
||||
|
||||
if (nullDiff == null)
|
||||
{
|
||||
trace('Fabricated new difficulty for $diffId.');
|
||||
difficulty = new SongDifficulty(this, diffId, variation);
|
||||
var metadata = _metadata.get(variation);
|
||||
difficulties.set('$diffId$variationSuffix', difficulty);
|
||||
difficulties.get(variation)?.set(diffId, difficulty);
|
||||
|
||||
if (metadata != null)
|
||||
{
|
||||
@@ -392,30 +425,34 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
|
||||
for (currentVariation in variations)
|
||||
{
|
||||
var variationSuffix = (currentVariation != Constants.DEFAULT_VARIATION) ? '-$currentVariation' : '';
|
||||
|
||||
if (difficulties.exists('$diffId$variationSuffix'))
|
||||
if (difficulties.get(currentVariation)?.exists(diffId) ?? false)
|
||||
{
|
||||
return difficulties.get('$diffId$variationSuffix');
|
||||
return difficulties.get(currentVariation)?.get(diffId);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first valid variation that matches both the difficulty id, and the current character / possible input variations
|
||||
* @param diffId
|
||||
* @param currentCharacter
|
||||
* @param possibleVariations
|
||||
* @return Null<String>
|
||||
*/
|
||||
public function getFirstValidVariation(?diffId:String, ?currentCharacter:PlayableCharacter, ?possibleVariations:Array<String>):Null<String>
|
||||
{
|
||||
if (possibleVariations == null)
|
||||
{
|
||||
possibleVariations = getVariationsByCharacter(currentCharacter);
|
||||
possibleVariations.sort(SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_VARIATION_LIST));
|
||||
}
|
||||
|
||||
if (diffId == null) diffId = listDifficulties(null, possibleVariations)[0];
|
||||
|
||||
for (variationId in possibleVariations)
|
||||
{
|
||||
var variationSuffix = (variationId != Constants.DEFAULT_VARIATION) ? '-$variationId' : '';
|
||||
if (difficulties.exists('$diffId$variationSuffix')) return variationId;
|
||||
if (difficulties.get('$variationId')?.exists(diffId) ?? false) return variationId;
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -429,10 +466,14 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
*/
|
||||
public function getVariationsByCharacter(?char:PlayableCharacter):Array<String>
|
||||
{
|
||||
if (char == null) return variations;
|
||||
if (char == null)
|
||||
{
|
||||
var result = variations;
|
||||
result.sort(SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_VARIATION_LIST));
|
||||
return result;
|
||||
}
|
||||
|
||||
var result = [];
|
||||
trace('Evaluating variations for ${this.id} ${char.id}: ${this.variations}');
|
||||
for (variation in variations)
|
||||
{
|
||||
var metadata = _metadata.get(variation);
|
||||
@@ -446,9 +487,24 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
}
|
||||
}
|
||||
|
||||
result.sort(SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_VARIATION_LIST));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Nearly the same thing as getVariationsByCharacter, but takes a character ID instead.
|
||||
* @param charId
|
||||
* @return Array<String>
|
||||
* @see getVariationsByCharacter
|
||||
*/
|
||||
public function getVariationsByCharacterId(?charId:String):Array<String>
|
||||
{
|
||||
var charPlayer = PlayerRegistry.instance.fetchEntry(charId ?? '');
|
||||
|
||||
return getVariationsByCharacter(charPlayer);
|
||||
}
|
||||
|
||||
/**
|
||||
* List all the difficulties in this song.
|
||||
*
|
||||
@@ -466,18 +522,11 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
|
||||
if (variationIds.length == 0) return [];
|
||||
|
||||
// The difficulties array contains entries like 'normal', 'nightmare-erect', and 'normal-pico',
|
||||
// so we have to map it to the actual difficulty names.
|
||||
// We also filter out difficulties that don't match the variation or that don't exist.
|
||||
|
||||
var diffFiltered:Array<String> = difficulties.keys()
|
||||
.array()
|
||||
.map(function(diffId:String):Null<String> {
|
||||
var difficulty:Null<SongDifficulty> = difficulties.get(diffId);
|
||||
if (difficulty == null) return null;
|
||||
if (variationIds.length > 0 && !variationIds.contains(difficulty.variation)) return null;
|
||||
return difficulty.difficulty;
|
||||
})
|
||||
var diffFiltered:Array<String> = variationIds.map(function(variationId:String):Array<String> {
|
||||
var metadata = _metadata.get(variationId);
|
||||
return metadata?.playData?.difficulties ?? [];
|
||||
})
|
||||
.flatten()
|
||||
.filterNull()
|
||||
.distinct();
|
||||
|
||||
@@ -490,11 +539,16 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
return false;
|
||||
});
|
||||
|
||||
diffFiltered.sort(SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_DIFFICULTY_LIST));
|
||||
diffFiltered.sort(SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_DIFFICULTY_LIST_FULL));
|
||||
|
||||
return diffFiltered;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: This line of code makes me sad, but you can't really fix it without a breaking migration.
|
||||
* @return `easy`, `erect`, `normal-pico`, etc.
|
||||
* @deprecated This function is deprecated, Funkin no longer uses suffixed difficulties.
|
||||
*/
|
||||
public function listSuffixedDifficulties(variationIds:Array<String>, ?showLocked:Bool, ?showHidden:Bool):Array<String>
|
||||
{
|
||||
var result = [];
|
||||
@@ -510,6 +564,8 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
}
|
||||
}
|
||||
|
||||
result.sort(SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_DIFFICULTY_LIST_FULL));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -520,8 +576,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
|
||||
for (targetVariation in variationIds)
|
||||
{
|
||||
var variationSuffix = (targetVariation != Constants.DEFAULT_VARIATION) ? '-$targetVariation' : '';
|
||||
if (difficulties.exists('$diffId$variationSuffix')) return true;
|
||||
if (difficulties.get(targetVariation)?.exists(diffId) ?? false) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -556,13 +611,16 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge the cached chart data for each difficulty of this song.
|
||||
* Purge the cached chart data for each difficulty/variation of this song.
|
||||
*/
|
||||
public function clearCharts():Void
|
||||
{
|
||||
for (diff in difficulties)
|
||||
for (variationMap in difficulties)
|
||||
{
|
||||
diff.clearChart();
|
||||
for (diff in variationMap)
|
||||
{
|
||||
diff.clearChart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -630,6 +688,20 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
var meta:Null<SongMetadata> = SongRegistry.instance.parseEntryMetadataWithMigration(id, vari, version);
|
||||
return meta;
|
||||
}
|
||||
|
||||
static final VARIATION_REGEX = ~/^[a-z][a-z0-9]+$/;
|
||||
|
||||
/**
|
||||
* Validate that the variation ID is valid.
|
||||
* Auto-accept if it's one of the base game default variations.
|
||||
* Reject if the ID starts with a number, or contains invalid characters.
|
||||
*/
|
||||
static function validateVariationId(variation:String):Bool
|
||||
{
|
||||
if (Constants.DEFAULT_VARIATION_LIST.contains(variation)) return true;
|
||||
|
||||
return VARIATION_REGEX.match(variation);
|
||||
}
|
||||
}
|
||||
|
||||
class SongDifficulty
|
||||
|
||||
@@ -150,11 +150,8 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
|
||||
*/
|
||||
public function resetPosition()
|
||||
{
|
||||
var oldAnimOffsets = [animOffsets[0], animOffsets[1]];
|
||||
animOffsets = [0, 0];
|
||||
this.x = originalPosition.x;
|
||||
this.y = originalPosition.y;
|
||||
animOffsets = oldAnimOffsets;
|
||||
}
|
||||
|
||||
function update_shouldAlternate():Void
|
||||
|
||||
@@ -256,8 +256,15 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
|
||||
propSprite.scrollFactor.x = dataProp.scroll[0];
|
||||
propSprite.scrollFactor.y = dataProp.scroll[1];
|
||||
|
||||
propSprite.angle = dataProp.angle;
|
||||
propSprite.color = FlxColor.fromString(dataProp.color);
|
||||
@:privateAccess if (!isSolidColor) propSprite.blend = BlendMode.fromString(dataProp.blend);
|
||||
|
||||
propSprite.zIndex = dataProp.zIndex;
|
||||
|
||||
propSprite.flipX = dataProp.flipX;
|
||||
propSprite.flipY = dataProp.flipY;
|
||||
|
||||
switch (dataProp.animType)
|
||||
{
|
||||
case 'packer':
|
||||
@@ -440,12 +447,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
|
||||
character.x = stageCharData.position[0] - character.characterOrigin.x;
|
||||
character.y = stageCharData.position[1] - character.characterOrigin.y;
|
||||
|
||||
@:privateAccess(funkin.play.stage.Bopper)
|
||||
{
|
||||
// Undo animOffsets before saving original position.
|
||||
character.originalPosition.x = character.x + character.animOffsets[0];
|
||||
character.originalPosition.y = character.y + character.animOffsets[1];
|
||||
}
|
||||
character.originalPosition.set(character.x, character.y);
|
||||
|
||||
var finalScale = character.getBaseScale() * stageCharData.scale;
|
||||
character.setScale(finalScale); // Don't use scale.set for characters!
|
||||
@@ -464,6 +466,9 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
|
||||
#end
|
||||
}
|
||||
|
||||
// Set the characters type
|
||||
character.characterType = charType;
|
||||
|
||||
// Add the character to the scene.
|
||||
this.add(character);
|
||||
|
||||
|
||||
+247
-41
@@ -1,24 +1,23 @@
|
||||
package funkin.save;
|
||||
|
||||
import flixel.util.FlxSave;
|
||||
import funkin.util.FileUtil;
|
||||
import funkin.input.Controls.Device;
|
||||
import funkin.play.scoring.Scoring;
|
||||
import funkin.play.scoring.Scoring.ScoringRank;
|
||||
import funkin.save.migrator.RawSaveData_v1_0_0;
|
||||
import funkin.save.migrator.SaveDataMigrator;
|
||||
import funkin.save.migrator.SaveDataMigrator;
|
||||
import funkin.ui.debug.charting.ChartEditorState.ChartEditorLiveInputStyle;
|
||||
import funkin.ui.debug.charting.ChartEditorState.ChartEditorTheme;
|
||||
import funkin.ui.debug.stageeditor.StageEditorState.StageEditorTheme;
|
||||
import funkin.util.FileUtil;
|
||||
import funkin.util.SerializerUtil;
|
||||
import thx.semver.Version;
|
||||
import thx.semver.Version;
|
||||
|
||||
@:nullSafety
|
||||
class Save
|
||||
{
|
||||
public static final SAVE_DATA_VERSION:thx.semver.Version = "2.0.5";
|
||||
public static final SAVE_DATA_VERSION_RULE:thx.semver.VersionRule = "2.0.x";
|
||||
public static final SAVE_DATA_VERSION:thx.semver.Version = "2.1.0";
|
||||
public static final SAVE_DATA_VERSION_RULE:thx.semver.VersionRule = ">=2.1.0 <2.2.0";
|
||||
|
||||
// We load this version's saves from a new save path, to maintain SOME level of backwards compatibility.
|
||||
static final SAVE_PATH:String = 'FunkinCrew';
|
||||
@@ -34,19 +33,19 @@ class Save
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = new Save(FlxG.save.data);
|
||||
return _instance = load();
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
|
||||
var data:RawSaveData;
|
||||
|
||||
public static function load():Void
|
||||
public static function load():Save
|
||||
{
|
||||
trace("[SAVE] Loading save...");
|
||||
|
||||
// Bind save data.
|
||||
loadFromSlot(1);
|
||||
return loadFromSlot(1);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,7 +64,9 @@ class Save
|
||||
public static function getDefault():RawSaveData
|
||||
{
|
||||
return {
|
||||
version: Save.SAVE_DATA_VERSION,
|
||||
// Version number is an abstract(Array) internally.
|
||||
// This means it copies by reference, so merging save data overides the version number lol.
|
||||
version: thx.Dynamics.clone(Save.SAVE_DATA_VERSION),
|
||||
|
||||
volume: 1.0,
|
||||
mute: false,
|
||||
@@ -89,6 +90,7 @@ class Save
|
||||
options:
|
||||
{
|
||||
// Reasonable defaults.
|
||||
framerate: 60,
|
||||
naughtyness: true,
|
||||
downscroll: false,
|
||||
flashingLights: true,
|
||||
@@ -143,6 +145,14 @@ class Save
|
||||
hitsoundVolumeOpponent: 1.0,
|
||||
themeMusic: true
|
||||
},
|
||||
|
||||
optionsStageEditor:
|
||||
{
|
||||
previousFiles: [],
|
||||
moveStep: "1px",
|
||||
angleStep: 5,
|
||||
theme: StageEditorTheme.Light
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -425,6 +435,91 @@ class Save
|
||||
return data.unlocks.oldChar;
|
||||
}
|
||||
|
||||
public var stageEditorPreviousFiles(get, set):Array<String>;
|
||||
|
||||
function get_stageEditorPreviousFiles():Array<String>
|
||||
{
|
||||
if (data.optionsStageEditor.previousFiles == null) data.optionsStageEditor.previousFiles = [];
|
||||
|
||||
return data.optionsStageEditor.previousFiles;
|
||||
}
|
||||
|
||||
function set_stageEditorPreviousFiles(value:Array<String>):Array<String>
|
||||
{
|
||||
// Set and apply.
|
||||
data.optionsStageEditor.previousFiles = value;
|
||||
flush();
|
||||
return data.optionsStageEditor.previousFiles;
|
||||
}
|
||||
|
||||
public var stageEditorHasBackup(get, set):Bool;
|
||||
|
||||
function get_stageEditorHasBackup():Bool
|
||||
{
|
||||
if (data.optionsStageEditor.hasBackup == null) data.optionsStageEditor.hasBackup = false;
|
||||
|
||||
return data.optionsStageEditor.hasBackup;
|
||||
}
|
||||
|
||||
function set_stageEditorHasBackup(value:Bool):Bool
|
||||
{
|
||||
// Set and apply.
|
||||
data.optionsStageEditor.hasBackup = value;
|
||||
flush();
|
||||
return data.optionsStageEditor.hasBackup;
|
||||
}
|
||||
|
||||
public var stageEditorMoveStep(get, set):String;
|
||||
|
||||
function get_stageEditorMoveStep():String
|
||||
{
|
||||
if (data.optionsStageEditor.moveStep == null) data.optionsStageEditor.moveStep = "1px";
|
||||
|
||||
return data.optionsStageEditor.moveStep;
|
||||
}
|
||||
|
||||
function set_stageEditorMoveStep(value:String):String
|
||||
{
|
||||
// Set and apply.
|
||||
data.optionsStageEditor.moveStep = value;
|
||||
flush();
|
||||
return data.optionsStageEditor.moveStep;
|
||||
}
|
||||
|
||||
public var stageEditorAngleStep(get, set):Float;
|
||||
|
||||
function get_stageEditorAngleStep():Float
|
||||
{
|
||||
if (data.optionsStageEditor.angleStep == null) data.optionsStageEditor.angleStep = 5;
|
||||
|
||||
return data.optionsStageEditor.angleStep;
|
||||
}
|
||||
|
||||
function set_stageEditorAngleStep(value:Float):Float
|
||||
{
|
||||
// Set and apply.
|
||||
data.optionsStageEditor.angleStep = value;
|
||||
flush();
|
||||
return data.optionsStageEditor.angleStep;
|
||||
}
|
||||
|
||||
public var stageEditorTheme(get, set):StageEditorTheme;
|
||||
|
||||
function get_stageEditorTheme():StageEditorTheme
|
||||
{
|
||||
if (data.optionsStageEditor.theme == null) data.optionsStageEditor.theme = StageEditorTheme.Light;
|
||||
|
||||
return data.optionsStageEditor.theme;
|
||||
}
|
||||
|
||||
function set_stageEditorTheme(value:StageEditorTheme):StageEditorTheme
|
||||
{
|
||||
// Set and apply.
|
||||
data.optionsStageEditor.theme = value;
|
||||
flush();
|
||||
return data.optionsStageEditor.theme;
|
||||
}
|
||||
|
||||
/**
|
||||
* When we've seen a character unlock, add it to the list of characters seen.
|
||||
* @param character
|
||||
@@ -433,7 +528,9 @@ class Save
|
||||
{
|
||||
if (!data.unlocks.charactersSeen.contains(character))
|
||||
{
|
||||
trace('Character seen: ' + character);
|
||||
data.unlocks.charactersSeen.push(character);
|
||||
trace('New characters seen list: ' + data.unlocks.charactersSeen);
|
||||
flush();
|
||||
}
|
||||
}
|
||||
@@ -538,22 +635,34 @@ class Save
|
||||
*
|
||||
* @param songId The ID of the song.
|
||||
* @param difficultyId The difficulty to check.
|
||||
* @param variation The variation to check. Defaults to empty string. Appended to difficulty with `-`, e.g. `easy-pico`.
|
||||
* @return A data structure containing score, judgement counts, and accuracy. Returns `null` if no score is saved.
|
||||
*/
|
||||
public function getSongScore(songId:String, difficultyId:String = 'normal'):Null<SaveScoreData>
|
||||
public function getSongScore(songId:String, difficultyId:String = 'normal', ?variation:String):Null<SaveScoreData>
|
||||
{
|
||||
var song = data.scores.songs.get(songId);
|
||||
trace('Getting song score for $songId $difficultyId $variation');
|
||||
if (song == null)
|
||||
{
|
||||
trace('Could not find song data for $songId $difficultyId $variation');
|
||||
song = [];
|
||||
data.scores.songs.set(songId, song);
|
||||
}
|
||||
|
||||
// 'default' variations are left with no suffix ('easy', 'normal', 'hard'),
|
||||
// along with 'erect' variations ('erect', 'nightmare')
|
||||
// otherwise, we want to add a suffix of our current variation to get the save data.
|
||||
if (variation != null && variation != '' && variation != 'default' && variation != 'erect')
|
||||
{
|
||||
difficultyId = '${difficultyId}-${variation}';
|
||||
}
|
||||
|
||||
return song.get(difficultyId);
|
||||
}
|
||||
|
||||
public function getSongRank(songId:String, difficultyId:String = 'normal'):Null<ScoringRank>
|
||||
public function getSongRank(songId:String, difficultyId:String = 'normal', ?variation:String):Null<ScoringRank>
|
||||
{
|
||||
return Scoring.calculateRank(getSongScore(songId, difficultyId));
|
||||
return Scoring.calculateRank(getSongScore(songId, difficultyId, variation));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -598,11 +707,14 @@ class Save
|
||||
return;
|
||||
}
|
||||
|
||||
var newCompletion = (newScoreData.tallies.sick + newScoreData.tallies.good) / newScoreData.tallies.totalNotes;
|
||||
var previousCompletion = (previousScoreData.tallies.sick + previousScoreData.tallies.good) / previousScoreData.tallies.totalNotes;
|
||||
|
||||
// Set the high score and the high rank separately.
|
||||
var newScore:SaveScoreData =
|
||||
{
|
||||
score: (previousScoreData.score > newScoreData.score) ? previousScoreData.score : newScoreData.score,
|
||||
tallies: (previousRank > newRank) ? previousScoreData.tallies : newScoreData.tallies
|
||||
tallies: (previousRank > newRank || previousCompletion > newCompletion) ? previousScoreData.tallies : newScoreData.tallies
|
||||
};
|
||||
|
||||
song.set(difficultyId, newScore);
|
||||
@@ -670,18 +782,31 @@ class Save
|
||||
|
||||
/**
|
||||
* Has the provided song been beaten on one of the listed difficulties?
|
||||
* Note: This function can still take in the 'difficulty-variation' format for the difficultyList parameter
|
||||
* as it is used in the old save data format. However inputting a variation will append it to the difficulty
|
||||
* so you can do `hasBeatenSong('dadbattle', ['easy-pico'])` to check if you've beaten the Pico mix on easy.
|
||||
* or you can do `hasBeatenSong('dadbattle', ['easy'], 'pico')` to check if you've beaten the Pico mix on easy.
|
||||
* however you should not mix the two as it will append '-pico' to the 'easy-pico' if it's inputted into the array.
|
||||
* @param songId The song ID to check.
|
||||
* @param difficultyList The difficulties to check. Defaults to `easy`, `normal`, and `hard`.
|
||||
* @param variation The variation to check. Defaults to empty string. Appended to difficulty list with `-`, e.g. `easy-pico`.
|
||||
* This is our old format for getting difficulty/variation information, however we don't want to mess around with
|
||||
* save migration just yet.
|
||||
* @return Whether the song has been beaten on any of the listed difficulties.
|
||||
*/
|
||||
public function hasBeatenSong(songId:String, ?difficultyList:Array<String>):Bool
|
||||
public function hasBeatenSong(songId:String, ?difficultyList:Array<String>, ?variation:String):Bool
|
||||
{
|
||||
if (difficultyList == null)
|
||||
{
|
||||
difficultyList = ['easy', 'normal', 'hard'];
|
||||
}
|
||||
|
||||
if (variation == null) variation = '';
|
||||
|
||||
for (difficulty in difficultyList)
|
||||
{
|
||||
if (variation != '') difficulty = '${difficulty}-${variation}';
|
||||
|
||||
var score:Null<SaveScoreData> = getSongScore(songId, difficulty);
|
||||
if (score != null)
|
||||
{
|
||||
@@ -832,37 +957,63 @@ class Save
|
||||
* If you set slot to `2`, it will load an independe
|
||||
* @param slot
|
||||
*/
|
||||
static function loadFromSlot(slot:Int):Void
|
||||
static function loadFromSlot(slot:Int):Save
|
||||
{
|
||||
trace("[SAVE] Loading save from slot " + slot + "...");
|
||||
|
||||
// Prevent crashes if the save data is corrupted.
|
||||
SerializerUtil.initSerializer();
|
||||
trace('[SAVE] Loading save from slot $slot...');
|
||||
|
||||
FlxG.save.bind('$SAVE_NAME${slot}', SAVE_PATH);
|
||||
|
||||
if (FlxG.save.isEmpty())
|
||||
switch (FlxG.save.status)
|
||||
{
|
||||
trace('[SAVE] Save data is empty, checking for legacy save data...');
|
||||
var legacySaveData = fetchLegacySaveData();
|
||||
if (legacySaveData != null)
|
||||
{
|
||||
trace('[SAVE] Found legacy save data, converting...');
|
||||
var gameSave = SaveDataMigrator.migrateFromLegacy(legacySaveData);
|
||||
case EMPTY:
|
||||
trace('[SAVE] Save data in slot ${slot} is empty, checking for legacy save data...');
|
||||
var legacySaveData = fetchLegacySaveData();
|
||||
if (legacySaveData != null)
|
||||
{
|
||||
trace('[SAVE] Found legacy save data, converting...');
|
||||
var gameSave = SaveDataMigrator.migrateFromLegacy(legacySaveData);
|
||||
FlxG.save.mergeData(gameSave.data, true);
|
||||
return gameSave;
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[SAVE] No legacy save data found.');
|
||||
var gameSave = new Save();
|
||||
FlxG.save.mergeData(gameSave.data, true);
|
||||
return gameSave;
|
||||
}
|
||||
case ERROR(_):
|
||||
return handleSaveDataError(slot);
|
||||
case BOUND(_, _):
|
||||
trace('[SAVE] Loaded existing save data in slot ${slot}.');
|
||||
var gameSave = SaveDataMigrator.migrate(FlxG.save.data);
|
||||
FlxG.save.mergeData(gameSave.data, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[SAVE] No legacy save data found.');
|
||||
var gameSave = new Save();
|
||||
FlxG.save.mergeData(gameSave.data, true);
|
||||
}
|
||||
|
||||
return gameSave;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this when there is an error loading the save data in slot X.
|
||||
*/
|
||||
static function handleSaveDataError(slot:Int):Save
|
||||
{
|
||||
var msg = 'There was an error loading your save data in slot ${slot}.';
|
||||
msg += '\nPlease report this issue to the developers.';
|
||||
lime.app.Application.current.window.alert(msg, "Save Data Failure");
|
||||
|
||||
// Don't touch that slot anymore.
|
||||
// Instead, load the next available slot.
|
||||
|
||||
var nextSlot = slot + 1;
|
||||
|
||||
if (nextSlot < 1000)
|
||||
{
|
||||
return loadFromSlot(nextSlot);
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[SAVE] Found existing save data.');
|
||||
var gameSave = SaveDataMigrator.migrate(FlxG.save.data);
|
||||
FlxG.save.mergeData(gameSave.data, true);
|
||||
throw "End of save data slots. Can't load any more.";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -927,7 +1078,15 @@ class Save
|
||||
{
|
||||
var targetSaveData = new FlxSave();
|
||||
targetSaveData.bind('$SAVE_NAME${slot}', SAVE_PATH);
|
||||
return !targetSaveData.isEmpty();
|
||||
switch (targetSaveData.status)
|
||||
{
|
||||
case EMPTY:
|
||||
return false;
|
||||
case ERROR(_):
|
||||
return false;
|
||||
case BOUND(_, _):
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1031,6 +1190,11 @@ typedef RawSaveData =
|
||||
* The user's preferences specific to the Chart Editor.
|
||||
*/
|
||||
var optionsChartEditor:SaveDataChartEditorOptions;
|
||||
|
||||
/**
|
||||
* The user's preferences specific to the Stage Editor.
|
||||
*/
|
||||
var optionsStageEditor:SaveDataStageEditorOptions;
|
||||
};
|
||||
|
||||
typedef SaveApiData =
|
||||
@@ -1134,7 +1298,13 @@ typedef SaveScoreTallyData =
|
||||
typedef SaveDataOptions =
|
||||
{
|
||||
/**
|
||||
* Whether some particularly fowl language is displayed.
|
||||
* FPS
|
||||
* @default `60`
|
||||
*/
|
||||
var framerate:Int;
|
||||
|
||||
/**
|
||||
* Whether some particularly foul language is displayed.
|
||||
* @default `true`
|
||||
*/
|
||||
var naughtyness:Bool;
|
||||
@@ -1170,20 +1340,20 @@ typedef SaveDataOptions =
|
||||
var autoPause:Bool;
|
||||
|
||||
/**
|
||||
* Offset the users inputs by this many ms.
|
||||
* Offset the user's inputs by this many ms.
|
||||
* @default `0`
|
||||
*/
|
||||
var inputOffset:Int;
|
||||
|
||||
/**
|
||||
* Affects the delay between the audio and the visuals during gameplay
|
||||
* Affects the delay between the audio and the visuals during gameplay.
|
||||
* @default `0`
|
||||
*/
|
||||
var audioVisualOffset:Int;
|
||||
|
||||
/**
|
||||
* If we want the framerate to be unlocked on HTML5.
|
||||
* @default `false
|
||||
* @default `false`
|
||||
*/
|
||||
var unlockedFramerate:Bool;
|
||||
|
||||
@@ -1398,3 +1568,39 @@ typedef SaveDataChartEditorOptions =
|
||||
*/
|
||||
var ?playbackSpeed:Float;
|
||||
};
|
||||
|
||||
typedef SaveDataStageEditorOptions =
|
||||
{
|
||||
// a lot of these things were copied from savedatacharteditoroptions
|
||||
|
||||
/**
|
||||
* Whether the Stage Editor created a backup the last time it closed.
|
||||
* Prompt the user to load it, then set this back to `false`.
|
||||
* @default `false`
|
||||
*/
|
||||
var ?hasBackup:Bool;
|
||||
|
||||
/**
|
||||
* Previous files opened in the Stage Editor.
|
||||
* @default `[]`
|
||||
*/
|
||||
var ?previousFiles:Array<String>;
|
||||
|
||||
/**
|
||||
* The Step at which an Object or Character is moved.
|
||||
* @default `1px`
|
||||
*/
|
||||
var ?moveStep:String;
|
||||
|
||||
/**
|
||||
* The Step at which an Object is rotated.
|
||||
* @default `5`
|
||||
*/
|
||||
var ?angleStep:Float;
|
||||
|
||||
/**
|
||||
* Theme in the Stage Editor.
|
||||
* @default `StageEditorTheme.Light`
|
||||
*/
|
||||
var ?theme:StageEditorTheme;
|
||||
};
|
||||
|
||||
@@ -5,12 +5,26 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [2.1.0] - 2024-10-18
|
||||
This version introduces changes to save data loading in order to improve compatibility with older versions.
|
||||
### Changed
|
||||
- `optionsStageEditor.theme` converted from an Enum to a String to fix save data compatibility issues.
|
||||
- In the future, Enum values should not be used in order to prevent incompatibilities caused by introducing new types to the save data that older versions cannot parse.
|
||||
- `optionsChartEditor.theme` converted from an Enum to a String to fix save data compatibility issues.
|
||||
- `optionsChartEditor.chartEditorLiveInputStyle` converted from an Enum to a String to fix save data compatibility issues.
|
||||
|
||||
## [2.0.6] - 2024-10-11
|
||||
### Added
|
||||
- `optionsStageEditor` to `Save` for storing user preferences for the stage editor.
|
||||
|
||||
## [2.0.5] - 2024-05-21
|
||||
### Fixed
|
||||
- Resolved an issue where HTML5 wouldn't store the semantic version properly, causing the game to fail to load the save.
|
||||
|
||||
## [2.0.4] - 2024-05-21
|
||||
### Added
|
||||
- `unlocks.charactersSeen:Array<String>` to `Save`
|
||||
- `unlocks.oldChar:Bool` to `Save`
|
||||
- `favoriteSongs:Array<String>` to `Save`
|
||||
|
||||
## [2.0.3] - 2024-01-09
|
||||
|
||||
@@ -32,6 +32,10 @@ class SaveDataMigrator
|
||||
var save:Save = new Save(saveDataWithDefaults);
|
||||
return save;
|
||||
}
|
||||
else if (VersionUtil.validateVersion(version, "2.0.x"))
|
||||
{
|
||||
return migrate_v2_0_0(inputData);
|
||||
}
|
||||
else
|
||||
{
|
||||
var message:String = 'Error migrating save data, expected ${Save.SAVE_DATA_VERSION}.';
|
||||
@@ -43,6 +47,20 @@ class SaveDataMigrator
|
||||
}
|
||||
}
|
||||
|
||||
static function migrate_v2_0_0(inputData:Dynamic):Save
|
||||
{
|
||||
// Import the structured data.
|
||||
var saveDataWithDefaults:RawSaveData = cast thx.Objects.deepCombine(Save.getDefault(), inputData);
|
||||
|
||||
// Reset these values to valid ones.
|
||||
saveDataWithDefaults.optionsChartEditor.chartEditorLiveInputStyle = funkin.ui.debug.charting.ChartEditorState.ChartEditorLiveInputStyle.None;
|
||||
saveDataWithDefaults.optionsChartEditor.theme = funkin.ui.debug.charting.ChartEditorState.ChartEditorTheme.Light;
|
||||
saveDataWithDefaults.optionsStageEditor.theme = funkin.ui.debug.stageeditor.StageEditorState.StageEditorTheme.Light;
|
||||
|
||||
var save:Save = new Save(saveDataWithDefaults);
|
||||
return save;
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate from 1.x to the latest version.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package funkin.save.migrator;
|
||||
|
||||
// Internal enums used to ensure old save data can be parsed by the default Haxe unserializer.
|
||||
// In the future, only primitive types and abstract enums should be used in save data!
|
||||
|
||||
@:native("funkin.ui.debug.stageeditor.StageEditorTheme")
|
||||
enum StageEditorTheme
|
||||
{
|
||||
Light;
|
||||
Dark;
|
||||
}
|
||||
|
||||
@:native("funkin.ui.debug.charting.ChartEditorTheme")
|
||||
enum ChartEditorTheme
|
||||
{
|
||||
Light;
|
||||
Dark;
|
||||
}
|
||||
|
||||
@:native("funkin.ui.debug.charting.ChartEditorLiveInputStyle")
|
||||
enum ChartEditorLiveInputStyle
|
||||
{
|
||||
None;
|
||||
NumberKeys;
|
||||
WASDKeys;
|
||||
}
|
||||
@@ -37,17 +37,25 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler
|
||||
return _conductorInUse = value;
|
||||
}
|
||||
|
||||
public function new(bgColor:FlxColor = FlxColor.TRANSPARENT)
|
||||
{
|
||||
super();
|
||||
this.bgColor = bgColor;
|
||||
}
|
||||
|
||||
var controls(get, never):Controls;
|
||||
|
||||
inline function get_controls():Controls
|
||||
return PlayerSettings.player1.controls;
|
||||
|
||||
public function new(bgColor:FlxColor = FlxColor.TRANSPARENT)
|
||||
{
|
||||
super();
|
||||
this.bgColor = bgColor;
|
||||
|
||||
initCallbacks();
|
||||
}
|
||||
|
||||
function initCallbacks()
|
||||
{
|
||||
subStateOpened.add(onOpenSubStateComplete);
|
||||
subStateClosed.add(onCloseSubStateComplete);
|
||||
}
|
||||
|
||||
override function create():Void
|
||||
{
|
||||
super.create();
|
||||
|
||||
@@ -50,8 +50,13 @@ class PixelatedIcon extends FlxFilteredSprite
|
||||
if (!openfl.utils.Assets.exists(Paths.image(charPath)))
|
||||
{
|
||||
trace('[WARN] Character ${char} has no freeplay icon.');
|
||||
this.visible = false;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.visible = true;
|
||||
}
|
||||
|
||||
var isAnimated = openfl.utils.Assets.exists(Paths.file('images/$charPath.xml'));
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import funkin.modding.IScriptedClass.IBPMSyncedScriptedClass;
|
||||
import flixel.math.FlxMath;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.vis.dsp.SpectralAnalyzer;
|
||||
import funkin.data.freeplay.player.PlayerRegistry;
|
||||
|
||||
class CharSelectGF extends FlxAtlasSprite implements IBPMSyncedScriptedClass
|
||||
{
|
||||
@@ -27,7 +28,8 @@ class CharSelectGF extends FlxAtlasSprite implements IBPMSyncedScriptedClass
|
||||
|
||||
var analyzer:SpectralAnalyzer;
|
||||
|
||||
var curGF:GFChar = GF;
|
||||
var currentGFPath:Null<String>;
|
||||
var enableVisualizer:Bool = false;
|
||||
|
||||
public function new()
|
||||
{
|
||||
@@ -97,7 +99,7 @@ class CharSelectGF extends FlxAtlasSprite implements IBPMSyncedScriptedClass
|
||||
|
||||
function drawFFT()
|
||||
{
|
||||
if (curGF == NENE)
|
||||
if (enableVisualizer)
|
||||
{
|
||||
var levels = analyzer.getLevels();
|
||||
var frame = anim.curSymbol.timeline.get("VIZ_bars").get(anim.curFrame);
|
||||
@@ -172,28 +174,33 @@ class CharSelectGF extends FlxAtlasSprite implements IBPMSyncedScriptedClass
|
||||
*/
|
||||
public function switchGF(bf:String):Void
|
||||
{
|
||||
var prevGF:GFChar = curGF;
|
||||
switch (bf)
|
||||
{
|
||||
case "pico":
|
||||
curGF = NENE;
|
||||
case "bf":
|
||||
curGF = GF;
|
||||
default:
|
||||
curGF = GF;
|
||||
}
|
||||
var previousGFPath = currentGFPath;
|
||||
|
||||
var bfObj = PlayerRegistry.instance.fetchEntry(bf);
|
||||
var gfData = bfObj?.getCharSelectData()?.gf;
|
||||
currentGFPath = gfData?.assetPath != null ? Paths.animateAtlas(gfData?.assetPath) : null;
|
||||
|
||||
// We don't need to update any anims if we didn't change GF
|
||||
if (prevGF != curGF)
|
||||
trace('currentGFPath(${currentGFPath})');
|
||||
if (currentGFPath == null)
|
||||
{
|
||||
loadAtlas(Paths.animateAtlas("charSelect/" + curGF + "Chill"));
|
||||
this.visible = false;
|
||||
return;
|
||||
}
|
||||
else if (previousGFPath != currentGFPath)
|
||||
{
|
||||
this.visible = true;
|
||||
loadAtlas(currentGFPath);
|
||||
|
||||
animInInfo = FramesJSFLParser.parse(Paths.file("images/charSelect/" + curGF + "AnimInfo/" + curGF + "In.txt"));
|
||||
animOutInfo = FramesJSFLParser.parse(Paths.file("images/charSelect/" + curGF + "AnimInfo/" + curGF + "Out.txt"));
|
||||
enableVisualizer = gfData?.visualizer ?? false;
|
||||
|
||||
var animInfoPath = Paths.file('images/${gfData?.animInfoPath}');
|
||||
|
||||
animInInfo = FramesJSFLParser.parse(animInfoPath + '/In.txt');
|
||||
animOutInfo = FramesJSFLParser.parse(animInfoPath + '/Out.txt');
|
||||
}
|
||||
|
||||
playAnimation("idle", true, false, false);
|
||||
// addFrameCallback(getNextFrameLabel("idle"), () -> playAnimation("idle", true, false, false));
|
||||
|
||||
updateHitbox();
|
||||
}
|
||||
@@ -213,9 +220,3 @@ enum FadeStatus
|
||||
FADE_OUT;
|
||||
FADE_IN;
|
||||
}
|
||||
|
||||
enum abstract GFChar(String) from String to String
|
||||
{
|
||||
var GF = "gf";
|
||||
var NENE = "nene";
|
||||
}
|
||||
|
||||
@@ -47,7 +47,6 @@ class CharSelectPlayer extends FlxAtlasSprite implements IBPMSyncedScriptedClass
|
||||
//
|
||||
if (getCurrentAnimation() == "idle")
|
||||
{
|
||||
trace('Player beat hit');
|
||||
playAnimation("idle", true, false, false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -71,6 +71,7 @@ class CharSelectSubState extends MusicBeatSubState
|
||||
var availableChars:Map<Int, String> = new Map<Int, String>();
|
||||
var pressedSelect:Bool = false;
|
||||
var selectTimer:FlxTimer = new FlxTimer();
|
||||
var allowInput:Bool = false;
|
||||
|
||||
var selectSound:FunkinSound;
|
||||
var unlockSound:FunkinSound;
|
||||
@@ -174,6 +175,7 @@ class CharSelectSubState extends MusicBeatSubState
|
||||
|
||||
playerChillOut = new CharSelectPlayer(0, 0);
|
||||
playerChillOut.switchChar("bf");
|
||||
playerChillOut.visible = false;
|
||||
add(playerChillOut);
|
||||
|
||||
playerChill = new CharSelectPlayer(0, 0);
|
||||
@@ -430,6 +432,8 @@ class CharSelectSubState extends MusicBeatSubState
|
||||
overrideExisting: true,
|
||||
restartTrack: true,
|
||||
onLoad: function() {
|
||||
allowInput = true;
|
||||
|
||||
@:privateAccess
|
||||
gfChill.analyzer = new SpectralAnalyzer(FlxG.sound.music._channel.__audioSource, 7, 0.1);
|
||||
#if desktop
|
||||
@@ -540,6 +544,7 @@ class CharSelectSubState extends MusicBeatSubState
|
||||
|
||||
nametag.switchChar(char);
|
||||
gfChill.switchGF(char);
|
||||
gfChill.visible = true;
|
||||
|
||||
var icon = new PixelatedIcon(0, 0);
|
||||
icon.setCharacter(char);
|
||||
@@ -573,6 +578,8 @@ class CharSelectSubState extends MusicBeatSubState
|
||||
overrideExisting: true,
|
||||
restartTrack: true,
|
||||
onLoad: function() {
|
||||
allowInput = true;
|
||||
|
||||
@:privateAccess
|
||||
gfChill.analyzer = new SpectralAnalyzer(FlxG.sound.music._channel.__audioSource, 7, 0.1);
|
||||
#if desktop
|
||||
@@ -642,6 +649,7 @@ class CharSelectSubState extends MusicBeatSubState
|
||||
|
||||
function goToFreeplay():Void
|
||||
{
|
||||
allowInput = false;
|
||||
autoFollow = false;
|
||||
|
||||
FlxTween.tween(cursor, {alpha: 0}, 0.8, {ease: FlxEase.expoOut});
|
||||
@@ -695,7 +703,7 @@ class CharSelectSubState extends MusicBeatSubState
|
||||
|
||||
syncAudio(elapsed);
|
||||
|
||||
if (!pressedSelect)
|
||||
if (allowInput && !pressedSelect)
|
||||
{
|
||||
if (controls.UI_UP) holdTmrUp += elapsed;
|
||||
if (controls.UI_UP_R)
|
||||
@@ -782,18 +790,18 @@ class CharSelectSubState extends MusicBeatSubState
|
||||
cursorY = -1;
|
||||
}
|
||||
|
||||
if (autoFollow
|
||||
&& availableChars.exists(getCurrentSelected())
|
||||
&& Save.instance.charactersSeen.contains(availableChars[getCurrentSelected()]))
|
||||
if (availableChars.exists(getCurrentSelected()) && Save.instance.charactersSeen.contains(availableChars[getCurrentSelected()]))
|
||||
{
|
||||
gfChill.visible = true;
|
||||
curChar = availableChars.get(getCurrentSelected());
|
||||
|
||||
if (!pressedSelect && controls.ACCEPT)
|
||||
if (allowInput && !pressedSelect && controls.ACCEPT)
|
||||
{
|
||||
spamUp = false;
|
||||
spamDown = false;
|
||||
spamLeft = false;
|
||||
spamRight = false;
|
||||
|
||||
cursorConfirmed.visible = true;
|
||||
cursorConfirmed.x = cursor.x - 2;
|
||||
cursorConfirmed.y = cursor.y - 4;
|
||||
cursorConfirmed.animation.play("idle", true);
|
||||
|
||||
grpCursors.visible = false;
|
||||
@@ -817,7 +825,7 @@ class CharSelectSubState extends MusicBeatSubState
|
||||
});
|
||||
}
|
||||
|
||||
if (pressedSelect && controls.BACK)
|
||||
if (allowInput && pressedSelect && controls.BACK)
|
||||
{
|
||||
cursorConfirmed.visible = false;
|
||||
grpCursors.visible = true;
|
||||
@@ -841,17 +849,15 @@ class CharSelectSubState extends MusicBeatSubState
|
||||
selectTimer.cancel();
|
||||
}
|
||||
}
|
||||
else if (autoFollow)
|
||||
else
|
||||
{
|
||||
curChar = "locked";
|
||||
|
||||
gfChill.visible = false;
|
||||
|
||||
if (controls.ACCEPT)
|
||||
if (allowInput && controls.ACCEPT)
|
||||
{
|
||||
cursorDenied.visible = true;
|
||||
cursorDenied.x = cursor.x - 2;
|
||||
cursorDenied.y = cursor.y - 4;
|
||||
|
||||
playerChill.playAnimation("cannot select Label", true);
|
||||
|
||||
@@ -879,14 +885,20 @@ class CharSelectSubState extends MusicBeatSubState
|
||||
cursorLocIntended.x += cursorOffsetX;
|
||||
cursorLocIntended.y += cursorOffsetY;
|
||||
|
||||
cursor.x = MathUtil.coolLerp(cursor.x, cursorLocIntended.x, lerpAmnt);
|
||||
cursor.y = MathUtil.coolLerp(cursor.y, cursorLocIntended.y, lerpAmnt);
|
||||
cursor.x = MathUtil.smoothLerp(cursor.x, cursorLocIntended.x, elapsed, 0.1);
|
||||
cursor.y = MathUtil.smoothLerp(cursor.y, cursorLocIntended.y, elapsed, 0.1);
|
||||
|
||||
cursorBlue.x = MathUtil.coolLerp(cursorBlue.x, cursor.x, lerpAmnt * 0.4);
|
||||
cursorBlue.y = MathUtil.coolLerp(cursorBlue.y, cursor.y, lerpAmnt * 0.4);
|
||||
|
||||
cursorDarkBlue.x = MathUtil.coolLerp(cursorDarkBlue.x, cursorLocIntended.x, lerpAmnt * 0.2);
|
||||
cursorDarkBlue.y = MathUtil.coolLerp(cursorDarkBlue.y, cursorLocIntended.y, lerpAmnt * 0.2);
|
||||
|
||||
cursorConfirmed.x = cursor.x - 2;
|
||||
cursorConfirmed.y = cursor.y - 4;
|
||||
|
||||
cursorDenied.x = cursor.x - 2;
|
||||
cursorDenied.y = cursor.y - 4;
|
||||
}
|
||||
|
||||
var bopTimer:Float = 0;
|
||||
@@ -1051,18 +1063,21 @@ class CharSelectSubState extends MusicBeatSubState
|
||||
staticSound.stop();
|
||||
|
||||
nametag.switchChar(value);
|
||||
gfChill.visible = false;
|
||||
playerChill.visible = false;
|
||||
playerChillOut.visible = true;
|
||||
playerChillOut.playAnimation("slideout");
|
||||
var index = playerChillOut.anim.getFrameLabel("slideout").index;
|
||||
playerChillOut.onAnimationFrame.removeAll();
|
||||
playerChillOut.onAnimationFrame.add((_, frame:Int) -> {
|
||||
if (frame == index + 1)
|
||||
if (frame >= index + 1)
|
||||
{
|
||||
playerChill.visible = true;
|
||||
playerChill.switchChar(value);
|
||||
gfChill.switchGF(value);
|
||||
gfChill.visible = true;
|
||||
}
|
||||
if (frame == index + 2)
|
||||
if (frame >= index + 2)
|
||||
{
|
||||
playerChillOut.switchChar(value);
|
||||
playerChillOut.visible = false;
|
||||
|
||||
@@ -4,7 +4,7 @@ package funkin.ui.charSelect;
|
||||
import funkin.graphics.video.FlxVideo;
|
||||
#end
|
||||
#if hxCodec
|
||||
import hxcodec.flixel.FlxVideoSprite;
|
||||
import funkin.graphics.video.FunkinVideoSprite;
|
||||
#end
|
||||
import funkin.ui.MusicBeatSubState;
|
||||
import funkin.audio.FunkinSound;
|
||||
@@ -72,12 +72,12 @@ class IntroSubState extends MusicBeatSubState
|
||||
#end
|
||||
|
||||
#if hxCodec
|
||||
var vid:FlxVideoSprite;
|
||||
var vid:FunkinVideoSprite;
|
||||
|
||||
function playVideoNative(filePath:String):Void
|
||||
{
|
||||
// Video displays OVER the FlxState.
|
||||
vid = new FlxVideoSprite(0, 0);
|
||||
vid = new FunkinVideoSprite(0, 0);
|
||||
|
||||
vid.scrollFactor.set();
|
||||
|
||||
|
||||
@@ -55,12 +55,14 @@ class DebugMenuSubState extends MusicBeatSubState
|
||||
// Create each menu item.
|
||||
// Call onMenuChange when the first item is created to move the camera .
|
||||
#if FEATURE_CHART_EDITOR
|
||||
onMenuChange(createItem("CHART EDITOR", openChartEditor));
|
||||
createItem("CHART EDITOR", openChartEditor);
|
||||
#end
|
||||
createItem("ANIMATION EDITOR", openAnimationEditor);
|
||||
#if FEATURE_STAGE_EDITOR
|
||||
createItem("STAGE EDITOR", openStageEditor);
|
||||
#end
|
||||
// createItem("Input Offset Testing", openInputOffsetTesting);
|
||||
createItem("CHARACTER SELECT", openCharSelect, true);
|
||||
createItem("ANIMATION EDITOR", openAnimationEditor);
|
||||
// createItem("STAGE EDITOR", openStageEditor);
|
||||
// createItem("CHARACTER SELECT", openCharSelect, true);
|
||||
// createItem("TEST STICKERS", testStickers);
|
||||
#if sys
|
||||
createItem("OPEN CRASH LOG FOLDER", openLogFolder);
|
||||
@@ -125,6 +127,7 @@ class DebugMenuSubState extends MusicBeatSubState
|
||||
function openStageEditor()
|
||||
{
|
||||
trace('Stage Editor');
|
||||
FlxG.switchState(() -> new funkin.ui.debug.stageeditor.StageEditorState());
|
||||
}
|
||||
|
||||
#if sys
|
||||
|
||||
@@ -24,7 +24,6 @@ import haxe.ui.core.Screen;
|
||||
import haxe.ui.events.UIEvent;
|
||||
import haxe.ui.RuntimeComponentBuilder;
|
||||
import lime.utils.Assets as LimeAssets;
|
||||
import openfl.Assets;
|
||||
import openfl.events.Event;
|
||||
import openfl.events.IOErrorEvent;
|
||||
import openfl.geom.Rectangle;
|
||||
@@ -110,9 +109,11 @@ class DebugBoundingState extends FlxState
|
||||
offsetEditorDialog.cameras = [hudCam];
|
||||
|
||||
add(offsetEditorDialog);
|
||||
offsetEditorDialog.showDialog(false);
|
||||
|
||||
// Anchor to the right side by default
|
||||
// offsetEditorDialog.x = FlxG.width - offsetEditorDialog.width;
|
||||
// Anchor to the left side by default
|
||||
offsetEditorDialog.x = 16;
|
||||
offsetEditorDialog.y = 16;
|
||||
|
||||
// sets the default camera back to FlxG.camera, since we set it to hudCamera for haxeui stuf
|
||||
FlxG.cameras.setDefaultDrawTarget(FlxG.camera, true);
|
||||
@@ -239,6 +240,11 @@ class DebugBoundingState extends FlxState
|
||||
{
|
||||
movingCharacter = false;
|
||||
}
|
||||
|
||||
if (FlxG.mouse.justReleased)
|
||||
{
|
||||
movingCharacter = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,6 @@ import funkin.ui.debug.charting.commands.SetItemSelectionCommand;
|
||||
import funkin.ui.debug.charting.components.ChartEditorEventSprite;
|
||||
import funkin.ui.debug.charting.components.ChartEditorHoldNoteSprite;
|
||||
import funkin.ui.debug.charting.components.ChartEditorMeasureTicks;
|
||||
import funkin.ui.debug.charting.components.ChartEditorMeasureTicks;
|
||||
import funkin.ui.debug.charting.components.ChartEditorNotePreview;
|
||||
import funkin.ui.debug.charting.components.ChartEditorNoteSprite;
|
||||
import funkin.ui.debug.charting.components.ChartEditorPlaybarHead;
|
||||
@@ -700,7 +699,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
|
||||
function get_isCursorOverHaxeUI():Bool
|
||||
{
|
||||
return Screen.instance.hasSolidComponentUnderPoint(FlxG.mouse.screenX, FlxG.mouse.screenY);
|
||||
return Screen.instance.hasSolidComponentUnderPoint(FlxG.mouse.viewX, FlxG.mouse.viewY);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2274,8 +2273,25 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
this.openBackupAvailableDialog(welcomeDialog);
|
||||
}
|
||||
}
|
||||
|
||||
#if FEATURE_DISCORD_RPC
|
||||
updateDiscordRPC();
|
||||
#end
|
||||
}
|
||||
|
||||
#if FEATURE_DISCORD_RPC
|
||||
function updateDiscordRPC():Void
|
||||
{
|
||||
funkin.api.discord.DiscordClient.instance.setPresence(
|
||||
{
|
||||
// TODO: Make this display the song name and update when it changes.
|
||||
// state: '${currentSongName} [${selectedDifficulty}]',
|
||||
state: null,
|
||||
details: 'Chart Editor [Charting]'
|
||||
});
|
||||
}
|
||||
#end
|
||||
|
||||
function setupWelcomeMusic()
|
||||
{
|
||||
this.welcomeMusic.loadEmbedded(Paths.music('chartEditorLoop/chartEditorLoop'));
|
||||
@@ -3840,7 +3856,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
// Handle scroll anchor
|
||||
if (scrollAnchorScreenPos != null)
|
||||
{
|
||||
var currentScreenPos = new FlxPoint(FlxG.mouse.screenX, FlxG.mouse.screenY);
|
||||
var currentScreenPos = new FlxPoint(FlxG.mouse.viewX, FlxG.mouse.viewX);
|
||||
var distance = currentScreenPos - scrollAnchorScreenPos;
|
||||
|
||||
var verticalDistance = distance.y;
|
||||
@@ -4121,8 +4137,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
var overlapsRenderedEvents:Bool = FlxG.mouse.overlaps(renderedEvents);
|
||||
|
||||
// Cursor position relative to the grid.
|
||||
var cursorX:Float = FlxG.mouse.screenX - gridTiledSprite.x;
|
||||
var cursorY:Float = FlxG.mouse.screenY - gridTiledSprite.y;
|
||||
var cursorX:Float = FlxG.mouse.viewX - gridTiledSprite.x;
|
||||
var cursorY:Float = FlxG.mouse.viewY - gridTiledSprite.y;
|
||||
|
||||
var overlapsSelectionBorder:Bool = overlapsGrid
|
||||
&& ((cursorX % 40) < (GRID_SELECTION_BORDER_WIDTH / 2)
|
||||
@@ -4137,7 +4153,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
{
|
||||
if (scrollAnchorScreenPos == null)
|
||||
{
|
||||
scrollAnchorScreenPos = new FlxPoint(FlxG.mouse.screenX, FlxG.mouse.screenY);
|
||||
scrollAnchorScreenPos = new FlxPoint(FlxG.mouse.viewX, FlxG.mouse.viewY);
|
||||
selectionBoxStartPos = null;
|
||||
}
|
||||
else
|
||||
@@ -4159,11 +4175,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
else if (notePreview != null && FlxG.mouse.overlaps(notePreview) && !isCursorOverHaxeUI)
|
||||
{
|
||||
// Clicked note preview
|
||||
notePreviewScrollAreaStartPos = new FlxPoint(FlxG.mouse.screenX, FlxG.mouse.screenY);
|
||||
notePreviewScrollAreaStartPos = new FlxPoint(FlxG.mouse.viewX, FlxG.mouse.viewY);
|
||||
}
|
||||
else if (!isCursorOverHaxeUI && (!overlapsGrid || overlapsSelectionBorder))
|
||||
{
|
||||
selectionBoxStartPos = new FlxPoint(FlxG.mouse.screenX, FlxG.mouse.screenY);
|
||||
selectionBoxStartPos = new FlxPoint(FlxG.mouse.viewX, FlxG.mouse.viewY);
|
||||
// Drawing selection box.
|
||||
targetCursorMode = Crosshair;
|
||||
}
|
||||
@@ -4188,7 +4204,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
{
|
||||
// Clicked on the playhead scroll area.
|
||||
// Move the playhead to the cursor position.
|
||||
this.playheadPositionInPixels = FlxG.mouse.screenY - (GRID_INITIAL_Y_POS);
|
||||
this.playheadPositionInPixels = FlxG.mouse.viewY - (GRID_INITIAL_Y_POS);
|
||||
moveSongToScrollPosition();
|
||||
|
||||
// Cursor should be a grabby hand.
|
||||
@@ -4251,8 +4267,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
}
|
||||
else
|
||||
{
|
||||
// Minimum of 0.
|
||||
return 0;
|
||||
// Minimum of -1.
|
||||
return -1;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4313,27 +4329,27 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
// Clicking and dragging.
|
||||
|
||||
// Scroll the screen if the mouse is above or below the grid.
|
||||
if (FlxG.mouse.screenY < MENU_BAR_HEIGHT)
|
||||
if (FlxG.mouse.viewY < MENU_BAR_HEIGHT)
|
||||
{
|
||||
// Scroll up.
|
||||
var diff:Float = MENU_BAR_HEIGHT - FlxG.mouse.screenY;
|
||||
var diff:Float = MENU_BAR_HEIGHT - FlxG.mouse.viewY;
|
||||
scrollPositionInPixels -= diff * 0.5; // Too fast!
|
||||
moveSongToScrollPosition();
|
||||
}
|
||||
else if (FlxG.mouse.screenY > (playbarHeadLayout?.y ?? 0.0))
|
||||
else if (FlxG.mouse.viewY > (playbarHeadLayout?.y ?? 0.0))
|
||||
{
|
||||
// Scroll down.
|
||||
var diff:Float = FlxG.mouse.screenY - (playbarHeadLayout?.y ?? 0.0);
|
||||
var diff:Float = FlxG.mouse.viewY - (playbarHeadLayout?.y ?? 0.0);
|
||||
scrollPositionInPixels += diff * 0.5; // Too fast!
|
||||
moveSongToScrollPosition();
|
||||
}
|
||||
|
||||
// Render the selection box.
|
||||
var selectionRect:FlxRect = new FlxRect();
|
||||
selectionRect.x = Math.min(FlxG.mouse.screenX, selectionBoxStartPos.x);
|
||||
selectionRect.y = Math.min(FlxG.mouse.screenY, selectionBoxStartPos.y);
|
||||
selectionRect.width = Math.abs(FlxG.mouse.screenX - selectionBoxStartPos.x);
|
||||
selectionRect.height = Math.abs(FlxG.mouse.screenY - selectionBoxStartPos.y);
|
||||
selectionRect.x = Math.min(FlxG.mouse.viewX, selectionBoxStartPos.x);
|
||||
selectionRect.y = Math.min(FlxG.mouse.viewY, selectionBoxStartPos.y);
|
||||
selectionRect.width = Math.abs(FlxG.mouse.viewX - selectionBoxStartPos.x);
|
||||
selectionRect.height = Math.abs(FlxG.mouse.viewY - selectionBoxStartPos.y);
|
||||
setSelectionBoxBounds(selectionRect);
|
||||
|
||||
targetCursorMode = Crosshair;
|
||||
@@ -4461,8 +4477,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
// Player is clicking and holding on note preview to scrub around.
|
||||
targetCursorMode = Grabbing;
|
||||
|
||||
var clickedPosInPixels:Float = FlxMath.remapToRange(FlxG.mouse.screenY, (notePreview?.y ?? 0.0),
|
||||
(notePreview?.y ?? 0.0) + (notePreview?.height ?? 0.0), 0, songLengthInPixels);
|
||||
var clickedPosInPixels:Float = FlxMath.remapToRange(FlxG.mouse.viewY, (notePreview?.y ?? 0.0), (notePreview?.y ?? 0.0) + (notePreview?.height ?? 0.0),
|
||||
0, songLengthInPixels);
|
||||
|
||||
scrollPositionInPixels = clickedPosInPixels;
|
||||
moveSongToScrollPosition();
|
||||
@@ -4520,17 +4536,17 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
targetCursorMode = Grabbing;
|
||||
|
||||
// Scroll the screen if the mouse is above or below the grid.
|
||||
if (FlxG.mouse.screenY < MENU_BAR_HEIGHT)
|
||||
if (FlxG.mouse.viewY < MENU_BAR_HEIGHT)
|
||||
{
|
||||
// Scroll up.
|
||||
var diff:Float = MENU_BAR_HEIGHT - FlxG.mouse.screenY;
|
||||
var diff:Float = MENU_BAR_HEIGHT - FlxG.mouse.viewY;
|
||||
scrollPositionInPixels -= diff * 0.5; // Too fast!
|
||||
moveSongToScrollPosition();
|
||||
}
|
||||
else if (FlxG.mouse.screenY > (playbarHeadLayout?.y ?? 0.0))
|
||||
else if (FlxG.mouse.viewY > (playbarHeadLayout?.y ?? 0.0))
|
||||
{
|
||||
// Scroll down.
|
||||
var diff:Float = FlxG.mouse.screenY - (playbarHeadLayout?.y ?? 0.0);
|
||||
var diff:Float = FlxG.mouse.viewY - (playbarHeadLayout?.y ?? 0.0);
|
||||
scrollPositionInPixels += diff * 0.5; // Too fast!
|
||||
moveSongToScrollPosition();
|
||||
}
|
||||
@@ -4811,11 +4827,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
// Show the context menu connected to the note.
|
||||
if (useSingleNoteContextMenu)
|
||||
{
|
||||
this.openNoteContextMenu(FlxG.mouse.screenX, FlxG.mouse.screenY, highlightedNote.noteData);
|
||||
this.openNoteContextMenu(FlxG.mouse.viewX, FlxG.mouse.viewY, highlightedNote.noteData);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.openSelectionContextMenu(FlxG.mouse.screenX, FlxG.mouse.screenY);
|
||||
this.openSelectionContextMenu(FlxG.mouse.viewX, FlxG.mouse.viewY);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -4835,11 +4851,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
|| (isHighlightedEventSelected && currentEventSelection.length == 1);
|
||||
if (useSingleEventContextMenu)
|
||||
{
|
||||
this.openEventContextMenu(FlxG.mouse.screenX, FlxG.mouse.screenY, highlightedEvent.eventData);
|
||||
this.openEventContextMenu(FlxG.mouse.viewX, FlxG.mouse.viewY, highlightedEvent.eventData);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.openSelectionContextMenu(FlxG.mouse.screenX, FlxG.mouse.screenY);
|
||||
this.openSelectionContextMenu(FlxG.mouse.viewX, FlxG.mouse.viewY);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -4860,11 +4876,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
// Show the context menu connected to the note.
|
||||
if (useSingleNoteContextMenu)
|
||||
{
|
||||
this.openHoldNoteContextMenu(FlxG.mouse.screenX, FlxG.mouse.screenY, highlightedHoldNote.noteData);
|
||||
this.openHoldNoteContextMenu(FlxG.mouse.viewX, FlxG.mouse.viewY, highlightedHoldNote.noteData);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.openSelectionContextMenu(FlxG.mouse.screenX, FlxG.mouse.screenY);
|
||||
this.openSelectionContextMenu(FlxG.mouse.viewX, FlxG.mouse.viewY);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -5139,10 +5155,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
}
|
||||
|
||||
var songPos:Float = Conductor.instance.songPosition + Conductor.instance.instrumentalOffset;
|
||||
var songPosMilliseconds:String = Std.string(Math.floor(Math.abs(songPos) % 1000)).lpad('0', 2).substr(0, 2);
|
||||
var songPosSeconds:String = Std.string(Math.floor((Math.abs(songPos) / 1000) % 60)).lpad('0', 2);
|
||||
var songPosMinutes:String = Std.string(Math.floor((Math.abs(songPos) / 1000) / 60)).lpad('0', 2);
|
||||
if (songPos < 0) songPosMinutes = '-' + songPosMinutes;
|
||||
var songPosString:String = '${songPosMinutes}:${songPosSeconds}';
|
||||
var songPosString:String = '${songPosMinutes}:${songPosSeconds}.${songPosMilliseconds}';
|
||||
|
||||
if (playbarSongPos.value != songPosString) playbarSongPos.value = songPosString;
|
||||
|
||||
@@ -5614,7 +5631,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Ignoring keybinds for View menu items because we are in live input mode (${currentLiveInputStyle}).');
|
||||
// trace('Ignoring keybinds for View menu items because we are in live input mode (${currentLiveInputStyle}).');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5637,7 +5654,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
function handleHelpKeybinds():Void
|
||||
{
|
||||
// F1 = Open Help
|
||||
if (FlxG.keys.justPressed.F1) this.openUserGuideDialog();
|
||||
if (FlxG.keys.justPressed.F1 && !isHaxeUIDialogOpen)
|
||||
{
|
||||
this.openUserGuideDialog();
|
||||
}
|
||||
}
|
||||
|
||||
function handleQuickWatch():Void
|
||||
@@ -6524,22 +6544,22 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
/**
|
||||
* Available input modes for the chart editor state. Numbers/arrows/WASD available for other keybinds.
|
||||
*/
|
||||
enum ChartEditorLiveInputStyle
|
||||
enum abstract ChartEditorLiveInputStyle(String)
|
||||
{
|
||||
/**
|
||||
* No hotkeys to place notes at the playbar.
|
||||
*/
|
||||
None;
|
||||
var None;
|
||||
|
||||
/**
|
||||
* 1/2/3/4 to place notes on opponent's side, 5/6/7/8 to place notes on player's side.
|
||||
*/
|
||||
NumberKeys;
|
||||
var NumberKeys;
|
||||
|
||||
/**
|
||||
* WASD to place notes on opponent's side, Arrow keys to place notes on player's side.
|
||||
*/
|
||||
WASDKeys;
|
||||
var WASDKeys;
|
||||
}
|
||||
|
||||
typedef ChartEditorParams =
|
||||
@@ -6558,15 +6578,15 @@ typedef ChartEditorParams =
|
||||
/**
|
||||
* Available themes for the chart editor state.
|
||||
*/
|
||||
enum ChartEditorTheme
|
||||
enum abstract ChartEditorTheme(String)
|
||||
{
|
||||
/**
|
||||
* The default theme for the chart editor.
|
||||
*/
|
||||
Light;
|
||||
var Light;
|
||||
|
||||
/**
|
||||
* A theme which introduces darker colors.
|
||||
*/
|
||||
Dark;
|
||||
var Dark;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package funkin.ui.debug.charting.components;
|
||||
import funkin.data.event.SongEventRegistry;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.utils.Assets;
|
||||
import flixel.FlxObject;
|
||||
import flixel.FlxBasic;
|
||||
import flixel.FlxSprite;
|
||||
|
||||
@@ -95,7 +95,7 @@ class ChartEditorCharacterIconSelectorMenu extends ChartEditorBaseMenu
|
||||
}
|
||||
|
||||
var LIMIT = 6;
|
||||
charButton.icon = CharacterDataParser.getCharPixelIconAsset(charId);
|
||||
charButton.icon = haxe.ui.util.Variant.fromImageData(CharacterDataParser.getCharPixelIconAsset(charId));
|
||||
charButton.text = charData.name.length > LIMIT ? '${charData.name.substr(0, LIMIT)}.' : '${charData.name}';
|
||||
|
||||
charButton.onClick = _ -> {
|
||||
|
||||
@@ -13,7 +13,6 @@ import funkin.audio.waveform.WaveformSprite;
|
||||
import flixel.util.FlxColor;
|
||||
import haxe.io.Bytes;
|
||||
import haxe.io.Path;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
/**
|
||||
* Functions for loading audio for the chart editor.
|
||||
|
||||
@@ -221,7 +221,7 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox
|
||||
var charDataOpponent:Null<CharacterData> = CharacterDataParser.fetchCharacterData(chartEditorState.currentSongMetadata.playData.characters.opponent);
|
||||
if (charDataOpponent != null)
|
||||
{
|
||||
buttonCharacterOpponent.icon = CharacterDataParser.getCharPixelIconAsset(chartEditorState.currentSongMetadata.playData.characters.opponent);
|
||||
buttonCharacterOpponent.icon = haxe.ui.util.Variant.fromImageData(CharacterDataParser.getCharPixelIconAsset(chartEditorState.currentSongMetadata.playData.characters.opponent));
|
||||
buttonCharacterOpponent.text = charDataOpponent.name.length > LIMIT ? '${charDataOpponent.name.substr(0, LIMIT)}.' : '${charDataOpponent.name}';
|
||||
}
|
||||
else
|
||||
@@ -233,7 +233,7 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox
|
||||
var charDataGirlfriend:Null<CharacterData> = CharacterDataParser.fetchCharacterData(chartEditorState.currentSongMetadata.playData.characters.girlfriend);
|
||||
if (charDataGirlfriend != null)
|
||||
{
|
||||
buttonCharacterGirlfriend.icon = CharacterDataParser.getCharPixelIconAsset(chartEditorState.currentSongMetadata.playData.characters.girlfriend);
|
||||
buttonCharacterGirlfriend.icon = haxe.ui.util.Variant.fromImageData(CharacterDataParser.getCharPixelIconAsset(chartEditorState.currentSongMetadata.playData.characters.girlfriend));
|
||||
buttonCharacterGirlfriend.text = charDataGirlfriend.name.length > LIMIT ? '${charDataGirlfriend.name.substr(0, LIMIT)}.' : '${charDataGirlfriend.name}';
|
||||
}
|
||||
else
|
||||
@@ -245,7 +245,7 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox
|
||||
var charDataPlayer:Null<CharacterData> = CharacterDataParser.fetchCharacterData(chartEditorState.currentSongMetadata.playData.characters.player);
|
||||
if (charDataPlayer != null)
|
||||
{
|
||||
buttonCharacterPlayer.icon = CharacterDataParser.getCharPixelIconAsset(chartEditorState.currentSongMetadata.playData.characters.player);
|
||||
buttonCharacterPlayer.icon = haxe.ui.util.Variant.fromImageData(CharacterDataParser.getCharPixelIconAsset(chartEditorState.currentSongMetadata.playData.characters.player));
|
||||
buttonCharacterPlayer.text = charDataPlayer.name.length > LIMIT ? '${charDataPlayer.name.substr(0, LIMIT)}.' : '${charDataPlayer.name}';
|
||||
}
|
||||
else
|
||||
|
||||
@@ -16,6 +16,7 @@ import funkin.ui.debug.latency.CoolStatsGraph;
|
||||
import openfl.events.KeyboardEvent;
|
||||
import funkin.input.PreciseInputManager;
|
||||
import funkin.play.notes.Strumline;
|
||||
import funkin.ui.mainmenu.MainMenuState;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
import funkin.data.notestyle.NoteStyleData;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
@@ -188,11 +189,21 @@ class LatencyState extends MusicBeatSubState
|
||||
}
|
||||
|
||||
override public function close():Void
|
||||
{
|
||||
cleanup();
|
||||
super.close();
|
||||
}
|
||||
|
||||
function cleanup():Void
|
||||
{
|
||||
PreciseInputManager.instance.onInputPressed.remove(preciseInputPressed);
|
||||
PreciseInputManager.instance.onInputReleased.remove(preciseInputReleased);
|
||||
|
||||
FlxG.sound.music.volume = previousVolume;
|
||||
if (FlxG.sound.music != null)
|
||||
{
|
||||
FlxG.sound.music.volume = previousVolume;
|
||||
}
|
||||
|
||||
swagSong.stop();
|
||||
FlxG.sound.list.remove(swagSong);
|
||||
|
||||
@@ -200,7 +211,6 @@ class LatencyState extends MusicBeatSubState
|
||||
|
||||
FlxG.state.persistentDraw = prevPersistentDraw;
|
||||
FlxG.state.persistentUpdate = prevPersistentUpdate;
|
||||
super.close();
|
||||
}
|
||||
|
||||
function regenNoteData()
|
||||
@@ -311,7 +321,9 @@ class LatencyState extends MusicBeatSubState
|
||||
|
||||
if (controls.BACK)
|
||||
{
|
||||
close();
|
||||
// close();
|
||||
cleanup();
|
||||
FlxG.switchState(() -> new MainMenuState());
|
||||
}
|
||||
|
||||
super.update(elapsed);
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
package funkin.ui.debug.stageeditor;
|
||||
|
||||
import funkin.data.animation.AnimationData;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.graphics.shaders.InverseDotsShader;
|
||||
|
||||
/**
|
||||
* Contains all the Logic needed for Stage Editor. Only for Stage Editor, as in the gameplay StageProps and Boppers will be used.
|
||||
*/
|
||||
class StageEditorObject extends FunkinSprite
|
||||
{
|
||||
/**
|
||||
* The internal Name of the Object.
|
||||
*/
|
||||
public var name:String = "Unnamed";
|
||||
|
||||
public var selectedShader:InverseDotsShader;
|
||||
|
||||
/**
|
||||
* What animation to play upon starting.
|
||||
*/
|
||||
public var startingAnimation:String = "";
|
||||
|
||||
public var animDatas:Map<String, AnimationData> = [];
|
||||
|
||||
override public function new()
|
||||
{
|
||||
super();
|
||||
|
||||
selectedShader = new InverseDotsShader(0);
|
||||
shader = selectedShader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the Object is currently being modified in the Stage Editor.
|
||||
*/
|
||||
public var isDebugged(default, set):Bool = true;
|
||||
|
||||
function set_isDebugged(value:Bool):Bool
|
||||
{
|
||||
this.isDebugged = value;
|
||||
|
||||
if (value == false) // plays upon starting yippee!!!
|
||||
playAnim(startingAnimation, true);
|
||||
else
|
||||
{
|
||||
if (animation.curAnim != null)
|
||||
{
|
||||
animation.stop();
|
||||
offset.set();
|
||||
updateHitbox();
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public function playAnim(name:String, restart:Bool = false, reversed:Bool = false):Void
|
||||
{
|
||||
if (!animation.getNameList().contains(name)) return;
|
||||
|
||||
animation.play(name, restart, reversed, 0);
|
||||
|
||||
if (animDatas.exists(name)) offset.set(animDatas[name].offsets[0], animDatas[name].offsets[1]);
|
||||
else
|
||||
offset.set();
|
||||
}
|
||||
|
||||
/**
|
||||
* On which beat should it dance?
|
||||
*/
|
||||
public var danceEvery:Float = 0;
|
||||
|
||||
/**
|
||||
* Internal, handles danceLeft and danceRight.
|
||||
*/
|
||||
var _danced:Bool = true;
|
||||
|
||||
public function dance(restart:Bool = false):Void
|
||||
{
|
||||
if (isDebugged) return;
|
||||
|
||||
var idle = animation.getNameList().contains("idle");
|
||||
var dancing = animation.getNameList().contains("danceLeft") && animation.getNameList().contains("danceRight");
|
||||
|
||||
if (!idle && !dancing) return;
|
||||
|
||||
if (dancing)
|
||||
{
|
||||
if (_danced) playAnim("danceRight", restart);
|
||||
else
|
||||
playAnim("danceLeft", restart);
|
||||
|
||||
_danced = !_danced;
|
||||
}
|
||||
else if (idle)
|
||||
{
|
||||
playAnim("idle", restart);
|
||||
}
|
||||
}
|
||||
|
||||
public function addAnim(name:String, prefix:String, offsets:Array<Float>, indices:Array<Int>, frameRate:Int = 24, looped:Bool = true, flipX:Bool = false,
|
||||
flipY:Bool = false)
|
||||
{
|
||||
if (indices.length > 0) animation.addByIndices(name, prefix, indices, "", frameRate, looped, flipX, flipY);
|
||||
else
|
||||
animation.addByPrefix(name, prefix, frameRate, looped, flipX, flipY);
|
||||
|
||||
if (animation.getNameList().contains(name)) // sometimes the animation doesnt add
|
||||
{
|
||||
animDatas.set(name,
|
||||
{
|
||||
name: name,
|
||||
prefix: prefix,
|
||||
offsets: offsets,
|
||||
looped: looped,
|
||||
frameRate: frameRate,
|
||||
flipX: flipX,
|
||||
flipY: flipY,
|
||||
frameIndices: indices
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,6 @@
|
||||
package funkin.ui.debug.stageeditor.components;
|
||||
|
||||
import haxe.ui.containers.dialogs.Dialog;
|
||||
|
||||
@:build(haxe.ui.macros.ComponentMacros.build("assets/exclude/data/ui/stage-editor/dialogs/about.xml"))
|
||||
class AboutDialog extends Dialog {}
|
||||
@@ -0,0 +1,80 @@
|
||||
package funkin.ui.debug.stageeditor.components;
|
||||
|
||||
import haxe.ui.containers.dialogs.Dialog;
|
||||
import haxe.ui.containers.dialogs.Dialog.DialogButton;
|
||||
import funkin.util.FileUtil;
|
||||
import haxe.io.Path;
|
||||
import funkin.util.DateUtil;
|
||||
import funkin.util.WindowUtil;
|
||||
|
||||
using StringTools;
|
||||
|
||||
@:xml('
|
||||
<dialog id="backupAvailableDialog" width="475" height="150" title="Hey! Listen!">
|
||||
<vbox width="100%" height="100%">
|
||||
<label text="There is a chart backup available, would you like to open it?\n" width="100%" textAlign="center" />
|
||||
<spacer height="6" />
|
||||
<label id="backupTimeLabel" text="Jan 1, 1970 0:00" width="100%" textAlign="center" />
|
||||
<spacer height="100%" />
|
||||
<hbox width="100%">
|
||||
<button text="No Thanks" id="dialogCancel" />
|
||||
<spacer width="100%" />
|
||||
<button text="Take Me There" id="buttonGoToFolder" />
|
||||
<spacer width="100%" />
|
||||
<button text="Open It" id="buttonOpenBackup" />
|
||||
</hbox>
|
||||
</vbox>
|
||||
</dialog>
|
||||
')
|
||||
class BackupAvailableDialog extends Dialog
|
||||
{
|
||||
override public function new(state:StageEditorState, filePath:String)
|
||||
{
|
||||
super();
|
||||
|
||||
if (!FileUtil.doesFileExist(filePath)) return;
|
||||
|
||||
// time text
|
||||
var fileDate = Path.withoutExtension(Path.withoutDirectory(filePath));
|
||||
var dateParts = fileDate.split("-");
|
||||
|
||||
while (dateParts.length < 8)
|
||||
dateParts.push("0");
|
||||
|
||||
var year:Int = Std.parseInt(dateParts[2]) ?? 0; // copied parts from ChartEditorImportExportHandler.hx
|
||||
var month:Int = Std.parseInt(dateParts[3]) ?? 1;
|
||||
var day:Int = Std.parseInt(dateParts[4]) ?? 0;
|
||||
var hour:Int = Std.parseInt(dateParts[5]) ?? 0;
|
||||
var minute:Int = Std.parseInt(dateParts[6]) ?? 0;
|
||||
var second:Int = Std.parseInt(dateParts[7]) ?? 0;
|
||||
|
||||
backupTimeLabel.text = DateUtil.generateCleanTimestamp(new Date(year, month - 1, day, hour, minute, second));
|
||||
|
||||
// button callbacks
|
||||
dialogCancel.onClick = function(_) hideDialog(DialogButton.CANCEL);
|
||||
|
||||
buttonGoToFolder.onClick = function(_) {
|
||||
// :[
|
||||
#if sys
|
||||
var absoluteBackupsPath:String = Path.join([Sys.getCwd(), StageEditorState.BACKUPS_PATH]);
|
||||
WindowUtil.openFolder(absoluteBackupsPath);
|
||||
#end
|
||||
}
|
||||
|
||||
buttonOpenBackup.onClick = function(_) {
|
||||
if (FileUtil.doesFileExist(filePath) && state.welcomeDialog != null) // doing a check in case a sleezy FUCK decides to delete the backup file AFTER dialog opens
|
||||
{
|
||||
state.welcomeDialog.loadFromFilePath(filePath);
|
||||
}
|
||||
hideDialog(DialogButton.APPLY);
|
||||
}
|
||||
|
||||
// uhhh
|
||||
onDialogClosed = function(event) {
|
||||
if (event.button == DialogButton.APPLY)
|
||||
{
|
||||
if (state.welcomeDialog != null) state.welcomeDialog.hideDialog(DialogButton.APPLY);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package funkin.ui.debug.stageeditor.components;
|
||||
|
||||
import haxe.ui.containers.dialogs.Dialog;
|
||||
|
||||
@:build(haxe.ui.macros.ComponentMacros.build("assets/exclude/data/ui/stage-editor/dialogs/exit-confirm.xml"))
|
||||
class ExitConfirmDialog extends Dialog
|
||||
{
|
||||
var onComplete:Void->Void = null;
|
||||
|
||||
override public function new(onComp:Void->Void)
|
||||
{
|
||||
super();
|
||||
|
||||
onComplete = onComp;
|
||||
|
||||
buttons = DialogButton.CANCEL | "{{Proceed}}";
|
||||
defaultButton = "{{Proceed}}";
|
||||
|
||||
destroyOnClose = true;
|
||||
}
|
||||
|
||||
public override function validateDialog(button:DialogButton, fn:Bool->Void)
|
||||
{
|
||||
if (button == "{{Proceed}}" && onComplete != null)
|
||||
{
|
||||
onComplete();
|
||||
}
|
||||
|
||||
fn(true);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user