Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b976b0d71 | ||
|
|
ae1239c57d | ||
|
|
bfbbf9cb01 | ||
|
|
d29bb47d22 | ||
|
|
d29ecfb7c4 | ||
|
|
cdd17ce033 | ||
|
|
8e8b26f029 | ||
|
|
6f43438cdb | ||
|
|
a74369c77e | ||
|
|
def1b74eb6 | ||
|
|
c70fa7c94a | ||
|
|
c0353bca2b | ||
|
|
f385cf9fd3 | ||
|
|
301f71b428 | ||
|
|
189e028442 | ||
|
|
a9647cf9fc | ||
|
|
05b9d68645 | ||
|
|
45f0f14ed9 | ||
|
|
a8262a8d3b | ||
|
|
264c2d8e77 | ||
|
|
b2f231e889 | ||
|
|
34bb201104 | ||
|
|
d9fcaf0e63 | ||
|
|
56259b9143 | ||
|
|
dc2acac152 | ||
|
|
bf5a669592 | ||
|
|
8b9775d91f | ||
|
|
6caeda9a9d | ||
|
|
9983556748 | ||
|
|
651298e908 | ||
|
|
142ea6db0c | ||
|
|
5397924edd | ||
|
|
94bd18fe17 | ||
|
|
664d9e0fb3 | ||
|
|
edf6889af9 | ||
|
|
c65dddacc0 | ||
|
|
659ea41206 | ||
|
|
5cd47a4414 | ||
|
|
65699ee865 | ||
|
|
d423eb3ddd | ||
|
|
c67085a46a | ||
|
|
a83a4a599c | ||
|
|
f6ac4cad43 | ||
|
|
0101bae7ec | ||
|
|
4c5335d7a3 | ||
|
|
b811ca91a6 | ||
|
|
c5f2a74494 | ||
|
|
3e0dbe2758 | ||
|
|
bbc0546c74 | ||
|
|
617d5ffa06 | ||
|
|
73114e2da6 | ||
|
|
808698ace7 | ||
|
|
832bc5bd83 | ||
|
|
b15e809c63 | ||
|
|
8074d57186 | ||
|
|
31d3718e5c | ||
|
|
5fdbd23a17 | ||
|
|
23a9e7f944 | ||
|
|
0397a41a7f | ||
|
|
afbb335993 | ||
|
|
2bc1a31427 | ||
|
|
dafbb43532 | ||
|
|
4978c89ac3 | ||
|
|
29b541af38 | ||
|
|
fc09350be7 | ||
|
|
e1ca3c69f0 | ||
|
|
a15bd36943 | ||
|
|
51605e50f8 | ||
|
|
3a1b573ea3 | ||
|
|
ef6b214872 | ||
|
|
8f2a8c8335 | ||
|
|
7f5b4974c3 | ||
|
|
d63782ee9d | ||
|
|
19b1c32124 | ||
|
|
d183876514 | ||
|
|
db0ac48192 | ||
|
|
ba37405e4b | ||
|
|
66197b34f6 | ||
|
|
a7da71c8a8 | ||
|
|
5bd081c2af | ||
|
|
aefc4db277 | ||
|
|
adc14cc15d | ||
|
|
a8294a9cce | ||
|
|
80569c806c | ||
|
|
d6837eba2e | ||
|
|
924b1cf386 | ||
|
|
0839c16535 | ||
|
|
db3e012cb0 | ||
|
|
6c2ebff680 | ||
|
|
df0f915be5 | ||
|
|
104d3cffcb | ||
|
|
584b1ece07 | ||
|
|
4555b6736e | ||
|
|
0c674ac23b | ||
|
|
c5e5ab16df | ||
|
|
adefa902e7 | ||
|
|
63f216ea71 | ||
|
|
03d46084b4 | ||
|
|
19eb138666 | ||
|
|
e21b01aa66 | ||
|
|
c41c800998 | ||
|
|
b7675aa440 | ||
|
|
745f49ca6d | ||
|
|
78646c4635 | ||
|
|
f1cc092820 | ||
|
|
7e200241ef | ||
|
|
1c4b8751b7 | ||
|
|
926d8c0517 | ||
|
|
e290e0c702 | ||
|
|
53dec057be | ||
|
|
b3f1798320 | ||
|
|
f20b938f3f | ||
|
|
85915eae8c | ||
|
|
83bb3bb507 | ||
|
|
642b2142ca | ||
|
|
0c6b730647 | ||
|
|
83c367e23b | ||
|
|
fe7bf3dd55 | ||
|
|
273d950899 | ||
|
|
9e61481cfe | ||
|
|
e572c7c2fb | ||
|
|
95ade2a08b | ||
|
|
51324e9c28 | ||
|
|
e6e3a30c0c | ||
|
|
2b7254fbd2 | ||
|
|
d54df57b95 | ||
|
|
b295e31266 | ||
|
|
695c708cb0 | ||
|
|
4a7f56a7ad | ||
|
|
030abed207 | ||
|
|
0d8cfaf104 | ||
|
|
caf708fe4a | ||
|
|
e7cf5fbbdc | ||
|
|
ccaa3d1070 | ||
|
|
4b883d4339 | ||
|
|
b40b4b0369 | ||
|
|
aa37745bbd | ||
|
|
df6ffca01f | ||
|
|
9dd8a257df | ||
|
|
2984ce4852 | ||
|
|
8fb35ac822 | ||
|
|
e8c42c281f | ||
|
|
68892b92ab | ||
|
|
12ee60f776 | ||
|
|
945314d271 | ||
|
|
93d4f30cad | ||
|
|
dda91cf770 | ||
|
|
6a1160feab | ||
|
|
b48b5ae1aa | ||
|
|
5bf8154e0b | ||
|
|
6de51848fd | ||
|
|
ee906ae8c0 | ||
|
|
7eebce432d | ||
|
|
750779a3fb | ||
|
|
095bc9006d | ||
|
|
09fdd0c91e | ||
|
|
be5d6227a7 | ||
|
|
855deb5828 | ||
|
|
963e2ecd63 | ||
|
|
14975212a9 | ||
|
|
3ad14bac32 | ||
|
|
e672c0978f | ||
|
|
0d5b7d4ed4 | ||
|
|
7e90423495 | ||
|
|
c7c2620217 | ||
|
|
3944816021 | ||
|
|
db00feff93 | ||
|
|
6e2a077d6b | ||
|
|
1ee8246b48 | ||
|
|
20c3228d8c | ||
|
|
01bf3bee11 | ||
|
|
4d9638f0ac | ||
|
|
b00589367c | ||
|
|
577421a782 | ||
|
|
72e9fb641e | ||
|
|
429f6f9bc0 | ||
|
|
550bf95c76 | ||
|
|
da686ed0c4 | ||
|
|
9f3711bf43 | ||
|
|
6390725ef0 | ||
|
|
cdc468ba15 | ||
|
|
deb4e1921d | ||
|
|
068c9fb43d | ||
|
|
5be4f3bbb9 | ||
|
|
b83ae65a65 | ||
|
|
dc876be57c | ||
|
|
c08847f48d | ||
|
|
19912b5f89 | ||
|
|
019473dec2 | ||
|
|
f001562270 | ||
|
|
34bb58015d | ||
|
|
d86fe68d80 | ||
|
|
1434820d10 | ||
|
|
08fb5b8459 | ||
|
|
8e8dcaa7f9 | ||
|
|
b35059ec99 | ||
|
|
b28ba8f465 | ||
|
|
dcc8085691 | ||
|
|
c186666402 | ||
|
|
6f93960cb5 | ||
|
|
a756ceac22 | ||
|
|
c394520019 | ||
|
|
2d93baf968 | ||
|
|
4a98e6999f | ||
|
|
9ff14aed42 | ||
|
|
0a7bd3111f | ||
|
|
1f88a3b6e1 | ||
|
|
fcb47ff4fc | ||
|
|
3218ceeecd | ||
|
|
1823373d7e | ||
|
|
af14d42939 | ||
|
|
3dd3338ab2 | ||
|
|
26f2578bf2 | ||
|
|
c32ade49b9 | ||
|
|
d4eee38fd2 | ||
|
|
6543c58363 | ||
|
|
158c015682 | ||
|
|
278bac7b6d | ||
|
|
29cddfb528 | ||
|
|
da9f17cf87 | ||
|
|
02dc353032 | ||
|
|
d31ef12363 | ||
|
|
c7d4406a34 | ||
|
|
68e9daffe8 | ||
|
|
c157338783 | ||
|
|
eb9c2c2c99 | ||
|
|
afb4a3dd68 | ||
|
|
9b5032ee84 | ||
|
|
62621881c0 | ||
|
|
52be941b45 | ||
|
|
c94b3a3b65 | ||
|
|
8291fe0407 | ||
|
|
f935526baa | ||
|
|
102ac4bfc8 | ||
|
|
f8b212a318 | ||
|
|
3bd79b822e | ||
|
|
17a4be66b9 | ||
|
|
54cd52db76 | ||
|
|
4fa0334e7b | ||
|
|
3c6466bec7 | ||
|
|
3ccd1b04da | ||
|
|
439be3cb26 | ||
|
|
871709bb7d | ||
|
|
bf36a86877 | ||
|
|
9829103671 | ||
|
|
b74df06f48 | ||
|
|
055c4d9b75 | ||
|
|
ea93ec90cb | ||
|
|
4383fcf32c | ||
|
|
cef0cbf9d4 | ||
|
|
7d80e6657b | ||
|
|
bb8d3ae3dc | ||
|
|
56033b3288 | ||
|
|
6e44484a1a | ||
|
|
9708e20dd4 | ||
|
|
df353a1ee8 | ||
|
|
6c9c3e418a | ||
|
|
07c872c775 | ||
|
|
bd3acc319c | ||
|
|
894d0743c9 | ||
|
|
33f943a672 | ||
|
|
f5efc16611 | ||
|
|
7e820f9497 | ||
|
|
07b6d8f86f | ||
|
|
8126cca29b | ||
|
|
ba78ded457 | ||
|
|
bda5d90bbc | ||
|
|
a9d0c1cb88 | ||
|
|
f8f533ad37 | ||
|
|
0bc0ab4eec | ||
|
|
d11027fb08 | ||
|
|
7b9e375fea | ||
|
|
dd215687d4 | ||
|
|
e3eddc892d | ||
|
|
36fd05b1f9 | ||
|
|
c6ef9b17a2 | ||
|
|
943c38767b | ||
|
|
71ed154b81 |
@@ -6,7 +6,6 @@ body:
|
|||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: "# PLEASE READ THE [CONTRIBUTING GUIDE](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md) BEFORE OPENING ISSUES!"
|
value: "# PLEASE READ THE [CONTRIBUTING GUIDE](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md) BEFORE OPENING ISSUES!"
|
||||||
|
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
attributes:
|
attributes:
|
||||||
label: Issue Checklist
|
label: Issue Checklist
|
||||||
@@ -46,7 +45,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: Version
|
label: Version
|
||||||
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
|
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
|
||||||
placeholder: ex. 0.5.3
|
placeholder: ex. 0.6.3
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: Version
|
label: Version
|
||||||
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
|
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
|
||||||
placeholder: ex. 0.5.3
|
placeholder: ex. 0.6.3
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: Version
|
label: Version
|
||||||
description: Which version are you compiling? The game version is in the bottom left corner of the main menu or in the project.hxp file.
|
description: Which version are you compiling? The game version is in the bottom left corner of the main menu or in the project.hxp file.
|
||||||
placeholder: ex. 0.5.3
|
placeholder: ex. 0.6.3
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: Version
|
label: Version
|
||||||
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
|
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
|
||||||
placeholder: ex. 0.5.3
|
placeholder: ex. 0.6.3
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ runs:
|
|||||||
echo "TIMER_HAXE=$(date +%s)" >> "$GITHUB_ENV"
|
echo "TIMER_HAXE=$(date +%s)" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
- name: Install Haxe
|
- name: Install Haxe
|
||||||
uses: funkincrew/ci-haxe@v3.2.6
|
uses: funkincrew/ci-haxe@v3.2.7
|
||||||
with:
|
with:
|
||||||
haxe-version: ${{ inputs.haxe }}
|
haxe-version: ${{ inputs.haxe }}
|
||||||
|
|
||||||
|
|||||||
@@ -118,10 +118,11 @@ jobs:
|
|||||||
|
|
||||||
build-game-on-host:
|
build-game-on-host:
|
||||||
strategy:
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- target: windows
|
- target: windows
|
||||||
runs-on: windows-latest
|
runs-on: windows
|
||||||
- target: macos
|
- target: macos
|
||||||
runs-on: macos
|
runs-on: macos
|
||||||
runs-on:
|
runs-on:
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
APIStuff.hx
|
APIStuff.hx
|
||||||
dump/
|
dump/
|
||||||
export/
|
export/
|
||||||
|
project/
|
||||||
RECOVER_*.fla
|
RECOVER_*.fla
|
||||||
shitAudio/
|
shitAudio/
|
||||||
.build_time
|
.build_time
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
[submodule "assets"]
|
[submodule "assets"]
|
||||||
path = assets
|
path = assets
|
||||||
url = https://github.com/FunkinCrew/Funkin-assets-secret
|
url = https://github.com/FunkinCrew/Funkin.assets
|
||||||
[submodule "art"]
|
[submodule "art"]
|
||||||
path = art
|
path = art
|
||||||
url = https://github.com/FunkinCrew/Funkin-art-secret
|
url = https://github.com/FunkinCrew/Funkin.art
|
||||||
|
|||||||
+188
-38
@@ -4,20 +4,161 @@ All notable changes will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [0.6.2] - 2024-03-31
|
|
||||||
### Added
|
|
||||||
- 0.6 credits list updated
|
|
||||||
### Fixed
|
|
||||||
- Additional shader fix for Stress Pico crashing at the end (was the same issue as Senpai Pico shader error, just in a different shaderfile)
|
|
||||||
|
|
||||||
## [0.6.1] - 2024-03-31
|
|
||||||
|
## [0.6.4] - 2025-05-02
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Misses now actually reduce your clear percentage and rank, as Eric intended. ([5fdbd23](https://github.com/FunkinCrew/Funkin/commit/5fdbd23a17b8eaf21582283400be2c6444e8b198)) - by @Lasercar in [#4880](https://github.com/FunkinCrew/Funkin/pull/4880)
|
||||||
|
- The miss rebalance is now reflected on the Freeplay clear percent display. ([f6ac4ca](https://github.com/FunkinCrew/Funkin/commit/f6ac4cad43f4834a86a3fc156d6d39eca2c47c7e)) - by @Lasercar, @Hundrec, and @NotHyper-474 in [#4898](https://github.com/FunkinCrew/Funkin/pull/4898) and [#4923](https://github.com/FunkinCrew/Funkin/pull/4923)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- HTML5 builds are no longer literally unplayable. ([6f43438](https://github.com/FunkinCrew/Funkin/commit/6f43438cdbfecf26d4bcf62a0d9ff71e40333ac2)) - by @cyn0x8 in [#4398](https://github.com/FunkinCrew/Funkin/pull/4398)
|
||||||
|
- Medals no longer crash the game when obtained. ([808698a](https://github.com/FunkinCrew/Funkin/commit/808698ace7457bc76a8a52d415d63fa4ee5b9400)) - by @KoloInDaCrib in [#4815](https://github.com/FunkinCrew/Funkin/pull/4815)
|
||||||
|
- The game no longer crashes when reaching the main menu before authenticating with Newgrounds. ([a83a4a5](https://github.com/FunkinCrew/Funkin/commit/a83a4a599c2e407832ef9bbe47bb8b8c07ce712a)) - by @NotHyper-474 in [#4871](https://github.com/FunkinCrew/Funkin/pull/4871)
|
||||||
|
- User preferences and other settings are now properly saved after restarting the game. ([23a9e7f](https://github.com/FunkinCrew/Funkin/commit/23a9e7f944fb703160c2ed7b0bef38c70c5370db)) - by @Lasercar in [#4881](https://github.com/FunkinCrew/Funkin/pull/4881)
|
||||||
|
- Texture atlas sprites no longer loop infinitely, fixing issues with multiple cutscenes. ([f385cf9](https://github.com/FunkinCrew/Funkin/commit/f385cf9fd33e5358427f40dad3fecfed233c1706)) - by @AbnormalPoof in [#4564](https://github.com/FunkinCrew/Funkin/pull/4564)
|
||||||
|
- The Week 3 Pico Mix cutscene no longer breaks with Naughtyness turned off. ([e3aba5c](https://github.com/FunkinCrew/funkin.assets/commit/e3aba5c4faa8e8c705c3e0daf04e30db7ac5a136)) - by @Lasercar in [funkin.assets#169](https://github.com/FunkinCrew/funkin.assets/pull/169)
|
||||||
|
- Adjusted the pixel strumline’s position when Downscroll is enabled to match the regular notestyle. ([ea93ec9](https://github.com/FunkinCrew/Funkin/commit/ea93ec90cb5c5dbd4a5bea64114af58c70cb43f4)) - by @Lasercar in [#4318](https://github.com/FunkinCrew/Funkin/pull/4318)
|
||||||
|
- The Main Menu now correctly remembers your most recently selected menu item. ([05b9d68](https://github.com/FunkinCrew/Funkin/commit/05b9d68645b71c9cc36a5cb0c0e93af6acb36132)) - by @Lasercar and @Hundrec in [#4227](https://github.com/FunkinCrew/Funkin/pull/4227)
|
||||||
|
- Fixed an occasional softlock when returning to the Freeplay menu from a song. ([b15e809](https://github.com/FunkinCrew/Funkin/commit/b15e809c63664e039781592dcbdee4383e73800e)) - by @TechnikTil in [#4665](https://github.com/FunkinCrew/Funkin/pull/4665)
|
||||||
|
- Switching to and from the Freeplay Random capsule now behaves more consistently. ([832bc5b](https://github.com/FunkinCrew/Funkin/commit/832bc5bd83d49a646b9c8cb2b767602c848be908)) - by @VioletSnowLeopard in [#4885](https://github.com/FunkinCrew/Funkin/pull/4885)
|
||||||
|
- Changing Freeplay filters with the Random capsule selected now behaves more consistently. ([edf6889](https://github.com/FunkinCrew/Funkin/commit/edf6889af9e7f68caead9b8139178a36b4b5b2d6)) - by @VioletSnowLeopard in [#4913](https://github.com/FunkinCrew/Funkin/pull/4913)
|
||||||
|
- Switching between identical Freeplay filters no longer makes the songlist invisible. ([8b9775d](https://github.com/FunkinCrew/Funkin/commit/8b9775d91f4f2b5896a637a014b4be770319968e)) - by @VioletSnowLeopard in [#4919](https://github.com/FunkinCrew/Funkin/pull/4919)
|
||||||
|
- The millions place digit of the Freeplay score display now updates properly. ([d9fcaf0](https://github.com/FunkinCrew/Funkin/commit/d9fcaf0e6382b121f7773d23f4864364a1e6a577)) - by @Lasercar in [#4065](https://github.com/FunkinCrew/Funkin/pull/4065)
|
||||||
|
- The Freeplay clear percent display is now more consistently aligned. ([329182e](https://github.com/FunkinCrew/funkin.assets/commit/329182ea1b6839187a1d800b2d009a4c1874479d)) - by @Hundrec in [funkin.assets#37](https://github.com/FunkinCrew/funkin.assets/pull/37)
|
||||||
|
- The debris in Pico’s Great Results animation no longer grows in size. ([c108a7f](https://github.com/FunkinCrew/funkin.assets/commit/c108a7ff0d11bf328e7b232160b8f68c71e21bca)) - by @ThatRozebudDude in [funkin.assets#73](https://github.com/FunkinCrew/funkin.assets/pull/73)
|
||||||
|
- Fixed the song not starting if more than 32 sounds are playing at once. ([31d3718](https://github.com/FunkinCrew/Funkin/commit/31d3718e5c33371cacd495219f9cc3908244bf71)) - by @KoloInDaCrib in [#4352](https://github.com/FunkinCrew/Funkin/pull/4352)
|
||||||
|
- Hot reloading (pressing F5) during dialogue no longer crashes the game. ([3e0dbe2](https://github.com/FunkinCrew/Funkin/commit/3e0dbe2758d68ccf4ded1d08bec247ab05d70829)) - by @KoloInDaCrib in [#4769](https://github.com/FunkinCrew/Funkin/pull/4769)
|
||||||
|
- Restarting a song without either a player or an opponent no longer crashes the game, fixing minimal playtest mode. ([bbc0546](https://github.com/FunkinCrew/Funkin/commit/bbc0546c7450b929dc78ad9271171ea899478e9d)) - by @thesuperpig56 in [#4778](https://github.com/FunkinCrew/Funkin/pull/4778)
|
||||||
|
- The sound tray no longer behaves incorrectly after wiping save data. ([afbb335](https://github.com/FunkinCrew/Funkin/commit/afbb3359938f020707efd92a17419e3658528ea5)) - by @Lasercar in [#4617](https://github.com/FunkinCrew/Funkin/pull/4617)
|
||||||
|
- Opening a chart from the “Open Recent” section no longer crashes the Chart Editor. ([def1b74](https://github.com/FunkinCrew/Funkin/commit/def1b74eb6987272bc360cbf586ddcd1c26c6381)) - by @CrusherNotDrip in [#4936](https://github.com/FunkinCrew/Funkin/pull/4936)
|
||||||
|
- Loading an invalid variation in the Chart Editor no longer crashes the game. ([c5f2a74](https://github.com/FunkinCrew/Funkin/commit/c5f2a744940dbc685b25373fa2dabb548f0b26fa)) - by @NotHyper-474 in [#4391](https://github.com/FunkinCrew/Funkin/pull/4391)
|
||||||
|
- Default difficulties are no longer re-added when saving and loading a .FNFC chart. ([8074d57](https://github.com/FunkinCrew/Funkin/commit/8074d571860aba2c2f6f1c642d8d0e671ff5355e)) - by @Lasercar in [#4216](https://github.com/FunkinCrew/Funkin/pull/4216)
|
||||||
|
- The Chart Editor now resizes properly after entering Fullscreen during a playtest. ([142ea6d](https://github.com/FunkinCrew/Funkin/commit/142ea6db0c53f6f9288109604702c2e7e5bc6f18)) - by @NotHyper-474 in [#4266](https://github.com/FunkinCrew/Funkin/pull/4266)
|
||||||
|
- Added three missing hotkeys to the Chart Editor user guide. ([73ccb9a](https://github.com/FunkinCrew/funkin.assets/commit/73ccb9a80d08f3311f51cf88950692d7c1ea5d60)) - by @NotHyper-474 and @Hundrec in [funkin.assets#83](https://github.com/FunkinCrew/funkin.assets/pull/83)
|
||||||
|
- The “Skip Forward” button is no longer named “Skip Back” in the Chart Editor. ([78f9fb7](https://github.com/FunkinCrew/funkin.assets/commit/78f9fb7cb6eaf7466826f144a0142c008fbff2f8)) - by @Hundrec in [funkin.assets#22](https://github.com/FunkinCrew/funkin.assets/pull/22)
|
||||||
|
- Exporting a character .JSON from the Animation Editor now fills in the file name. ([a8262a8](https://github.com/FunkinCrew/Funkin/commit/a8262a8d3b4d2169327131e21c98f803da4a5ee4)) - by @anysad in [#3090](https://github.com/FunkinCrew/Funkin/pull/3090)
|
||||||
|
- Properly credited MtH as a Charter in Monster’s metadata. ([6a10799](https://github.com/FunkinCrew/funkin.assets/commit/6a10799a40eb2b2deb364bcd3cc4387abe8e8bc4)) - by @ChillyBeanBAM in [funkin.assets#66](https://github.com/FunkinCrew/funkin.assets/pull/66)
|
||||||
|
- Corrected the chances of a Pause Menu easter egg appearing. ([0101bae](https://github.com/FunkinCrew/Funkin/commit/0101bae7ecc800e26efd9590fdfa1305039b4461)) - by @VioletSnowLeopard in [#4358](https://github.com/FunkinCrew/Funkin/pull/4358)
|
||||||
|
- Removed spammy traces for Playable Pico’s animations. ([f87255d](https://github.com/FunkinCrew/funkin.assets/commit/f87255d4bcb7a7ce0dc920aefc36b98a96115c75)) - by @VioletSnowLeopard in [funkin.assets#179](https://github.com/FunkinCrew/funkin.assets/pull/179)
|
||||||
|
- Fixed memory overflowing in crash logs. ([189e028](https://github.com/FunkinCrew/Funkin/commit/189e028442c7fb892ce4dab62a5d08db658f3b2b)) - by @ACrazyTown in [#4589](https://github.com/FunkinCrew/Funkin/pull/4589)
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- The CHANGELOG.md file will no longer be included in builds. Visit the [Funkin' GitHub](https://github.com/FunkinCrew/Funkin/blob/main/CHANGELOG.md) to view the latest changes! ([a7da71c](https://github.com/FunkinCrew/Funkin/commit/a7da71c8a87baa440a0bc6e23d7e9b36c7574e14)) - by @Hundrec and @NotHyper-474 in [#4868](https://github.com/FunkinCrew/Funkin/pull/4868)
|
||||||
|
|
||||||
|
## New Contributors for 0.6.4
|
||||||
|
|
||||||
|
* @thesuperpig56 made their first contribution in [#4778](https://github.com/FunkinCrew/Funkin/pull/4778)
|
||||||
|
* @ThatRozebudDude made their first contribution in [funkin.assets#73](https://github.com/FunkinCrew/funkin.assets/pull/73)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.6.3] - 2025-04-25
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- New option in the Preferences menu: VSync!
|
||||||
|
- Set it to Off, On, or Adaptive
|
||||||
|
- Adaptive turns VSync off during FPS drops, which is better if supported by your graphics card.
|
||||||
|
- Doesn't work on Mac, sorry!
|
||||||
|
- Otis now has muzzle flashes when shooting.
|
||||||
|
- Added a little easter egg to one of Pico's Results Screen animations.
|
||||||
|
- Overhauled the sticker system to allow modders to easily add custom sticker sets. ([cdc468b](https://github.com/FunkinCrew/Funkin/commit/cdc468ba15683b3c0f54015e337673c77ddd7962)) - by @AbnormalPoof in [#4003](https://github.com/FunkinCrew/Funkin/pull/4003)
|
||||||
|
- Implemented a macro to optimize registries and entries. ([1497521](https://github.com/FunkinCrew/Funkin/commit/14975212a918adb1a5d96a349ec9e8e63c9fc64b)) - by @lemz1 in [#3694](https://github.com/FunkinCrew/Funkin/pull/3694)
|
||||||
|
- The macro is now used to retrieve base game assets. ([963e2ec](https://github.com/FunkinCrew/Funkin/commit/963e2ecd630fae24dae4206d315618cf4c4be6db)) - by @lemz1 in [#4707](https://github.com/FunkinCrew/Funkin/pull/4707)
|
||||||
|
- The Chart Editor now displays the current beat and step of the playhead. ([83bb3bb](https://github.com/FunkinCrew/Funkin/commit/83bb3bb5074ecb5f50ee21979711efcda9d8017d)) - by @NotHyper-474 in [#4649](https://github.com/FunkinCrew/Funkin/pull/4649)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- The mod API version is now 0.6.3.
|
||||||
|
- Be sure to update the version in your mods' metadata, then ensure they are still working!
|
||||||
|
- Eric's scoring rebalance update!
|
||||||
|
- Misses (not ghost misses) are now worth -100 points, up from -10.
|
||||||
|
- Hold note trails now grant 20% less health.
|
||||||
|
- Dropping a hold note early now plays the miss animation and deducts health and score.
|
||||||
|
- Made a LOT of charting changes!
|
||||||
|
- Many missing, extra, and mistimed notes have been corrected.
|
||||||
|
- Stacked notes that were preventing full combos have been removed.
|
||||||
|
- Camera events have been adjusted to stay within the boundaries of the stage.
|
||||||
|
- Some songs have been recharted.
|
||||||
|
- The Freeplay menu can now always switch between variations.
|
||||||
|
- The nearest song with an Erect variation will be selected when switching.
|
||||||
|
- Switching difficulties now always plays the capsule jump-in animation.
|
||||||
|
- The "Random" capsule now plays animations consistent with other capsules.
|
||||||
|
- The Eggnog Erect cutscene can now be skipped. ([0303a03](https://github.com/FunkinCrew/funkin.assets/commit/1202651db8ea938fe11b6b734fbf7884d101e6ad)) - by @ShadzXD in [#62](https://github.com/FunkinCrew/funkin.assets/pull/62)
|
||||||
|
- Swapped the positions of “skip” and “restart” items in the dialogue pause menu for consistency. ([1f88a3b](https://github.com/FunkinCrew/Funkin/commit/1f88a3b6e13e9283ad680805deab5e27ba141d96)) - by @VioletSnowLeopard in [#4553](https://github.com/FunkinCrew/Funkin/pull/4553)
|
||||||
|
- The Controls menu now prevents unbinding essential UI controls to safeguard against softlocking save files. ([7eebce4](https://github.com/FunkinCrew/Funkin/commit/7eebce432d7627a1cf0bbd43f22a6f9bcb63ff65)) - by @VioletSnowLeopard in [#4382](https://github.com/FunkinCrew/Funkin/pull/4382)
|
||||||
|
- Renamed “Auto Pause” preference to “Pause on Unfocus” for clarity. ([52be941](https://github.com/FunkinCrew/Funkin/commit/52be941b4503da0ac76918e2482ab1804866f2cf)) - by @JackXson-Real in [#4346](https://github.com/FunkinCrew/Funkin/pull/4346)
|
||||||
|
- Overhauled `FileUtil`, introducing various fixes, new functions, and sandboxing. ([95ade2a](https://github.com/FunkinCrew/Funkin/commit/95ade2a08b7709e8208ec1b3e123bf5b4308ba10)) - by @cyn0x8 in [#3032](https://github.com/FunkinCrew/Funkin/pull/3032)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Story mode medals are now awarded even without scoring a new personal best.
|
||||||
|
- Highscores are now submitted to Newgrounds even without scoring a new personal best.
|
||||||
|
- The Freeplay menu no longer displays songs without Erect variations when returning from an Erect variation song.
|
||||||
|
- Fixed Freeplay DJ animations for Boyfriend and Pico when idling (properly this time).
|
||||||
|
- Alternate instrumentals for Cocoa, Senpai, Roses, and Stress are now locked until their Pico Mix is beaten.
|
||||||
|
- The Roses Pico alternate instrumental is now offset to align with the original song's voices.
|
||||||
|
- Entering Stress or Stress (Pico Mix) a second time no longer crashes the game.
|
||||||
|
- Restarting Stress or Stress (Pico Mix) no longer spawns too many Tankmen in the background.
|
||||||
|
- Pico's game over animation now displays properly on Ugh (Pico Mix) and Guns (Pico Mix).
|
||||||
|
- Tankman's death lines now play for Boyfriend in Ugh, Guns, and Ugh Erect.
|
||||||
|
- Week 6 dialogue no longer plays for all non-default variations when entered through Freeplay.
|
||||||
|
- Nene (Pixel) now plays her knife raising animation when the player has low health.
|
||||||
|
- A-Bot's visualizer is now blank before the song starts.
|
||||||
|
- Debug editor tooltips no longer display behind windows.
|
||||||
|
- Clearing save data no longer crashes the game.
|
||||||
|
- Adding custom parameters to note kinds no longer crashes the Chart Editor. ([0a7bd31](https://github.com/FunkinCrew/Funkin/commit/0a7bd3111f59efbecfe097f8bbcfdefa5ace299d)) - by @Lasercar in [#136](https://github.com/FunkinCrew/funkin.assets/pull/136)
|
||||||
|
- Improved performance in the Freeplay menu before entering a song. ([2b7254f](https://github.com/FunkinCrew/Funkin/commit/2b7254fbd2356e9da91e216f178a88f17874a6eb)) - by @superpowers04 in [#4729](https://github.com/FunkinCrew/Funkin/pull/4729)
|
||||||
|
- Prevented a crash by capping Freeplay and Results screen score displays at their largest possible values (actual scores are not capped). ([51324e9](https://github.com/FunkinCrew/Funkin/commit/51324e9c283c43ca861d3369ba9b3e1db9c89765)) - by @KoloInDaCrib in [#3634](https://github.com/FunkinCrew/Funkin/pull/3634)
|
||||||
|
- The Animation Editor no longer crashes when opened. ([b40b4b0](https://github.com/FunkinCrew/Funkin/commit/b40b4b03692910afceff361cf6ca3298fd787e3c)) - by @sector-a in [#4582](https://github.com/FunkinCrew/Funkin/pull/4582)
|
||||||
|
- Disabled navigation in the Options menu while the Clear Save Data prompt is open. ([855deb5](https://github.com/FunkinCrew/Funkin/commit/855deb58280e850c0ad1581807c70f05d6b6a2cb)) - by @KoloInDaCrib in [#4703](https://github.com/FunkinCrew/Funkin/pull/4703)
|
||||||
|
- Save data is no longer wiped after failing to load the current save. ([068c9fb](https://github.com/FunkinCrew/Funkin/commit/068c9fb43d49ff6ea13e5a73f77a42207954be2c)) - by @KoloInDaCrib in [#4574](https://github.com/FunkinCrew/Funkin/pull/4574)
|
||||||
|
- Selecting a different type of event in the Chart Editor no longer crashes or resets the event to its default values. ([71ed154](https://github.com/FunkinCrew/Funkin/commit/71ed154b81abbd002d78c09c2dada8a2ad3fa73e)) - by @KoloInDaCrib in [#3913](https://github.com/FunkinCrew/Funkin/pull/3913)
|
||||||
|
- Deleting a stacked event in the Chart Editor now properly removes the deleted event’s sprite. ([3ad14ba](https://github.com/FunkinCrew/Funkin/commit/3ad14bac32096e45259997ab9957bec5fecf296f)) - by @NotHyper-474 in [#4724](https://github.com/FunkinCrew/Funkin/pull/4724)
|
||||||
|
- Patched a security vulnerability. ([53dec05](https://github.com/FunkinCrew/Funkin/commit/53dec057bee0a913d60e0c70d45dbb59a58620b0)) - by @nebulazorua in [#4740](https://github.com/FunkinCrew/Funkin/pull/4740)
|
||||||
|
- Added null safety to a bunch of classes.
|
||||||
|
- Even more tiny bug fixes.
|
||||||
|
|
||||||
|
## New Contributors for 0.6.3
|
||||||
|
|
||||||
|
* @JackXson-Real made their first contribution in [#4346](https://github.com/FunkinCrew/Funkin/pull/4346)
|
||||||
|
* @VioletSnowLeopard made their first contribution in [#4382](https://github.com/FunkinCrew/Funkin/pull/4382)
|
||||||
|
* @superpowers04 made their first contribution in [#4729](https://github.com/FunkinCrew/Funkin/pull/4729)
|
||||||
|
* @ShadzXD made their first contribution in [#62](https://github.com/FunkinCrew/Funkin/pull/4729)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.6.2] - 2025-03-31
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Updated the 0.6 credits list
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Additional shader fix for Stress (Pico Mix) crashing at the end (was the same issue as Senpai Pico shader error, just in a different shaderfile)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.6.1] - 2025-03-31
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
- Hopefully Senpai Pico/Erect mix shader isn't brokey
|
- Hopefully Senpai Pico/Erect mix shader isn't brokey
|
||||||
- NG API encryption key was added proper, so medals + leaderboards posting should work
|
- NG API encryption key was added proper, so medals + leaderboards posting should work
|
||||||
|
|
||||||
## [0.6.0] - 2024-03-31
|
|
||||||
|
|
||||||
|
## [0.6.0] - 2025-03-31
|
||||||
The Pit Stop 2 update!
|
The Pit Stop 2 update!
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added six (!) new playable songs! Check them out in the Freeplay menu for their respective characters.
|
- Added six (!) new playable songs! Check them out in the Freeplay menu for their respective characters.
|
||||||
- Cocoa (Pico Mix)
|
- Cocoa (Pico Mix)
|
||||||
- Senpai (Pico Mix)
|
- Senpai (Pico Mix)
|
||||||
@@ -38,66 +179,75 @@ The Pit Stop 2 update!
|
|||||||
- The new sticker system isn't fully available to mods yet, but we're working on it!
|
- The new sticker system isn't fully available to mods yet, but we're working on it!
|
||||||
- New option in the Preferences menu: Strumline Backgrounds!
|
- New option in the Preferences menu: Strumline Backgrounds!
|
||||||
- Options in the Preferences menu now display an on-screen description when selected.
|
- Options in the Preferences menu now display an on-screen description when selected.
|
||||||
- Added a little easter egg to Pico's Good Results Screen animation.
|
- New song event type in the Chart Editor: Set Health Icon!
|
||||||
|
- This event is now used in Stress (Pico Mix).
|
||||||
- The HOME and END keys now jump to the top and bottom of the Freeplay song list, respectively. ([bb974c2](https://github.com/FunkinCrew/Funkin/commit/bb974c264270d10ff503784063e5d77bb352b3f7)) - by @AbnormalPoof in [#4103](https://github.com/FunkinCrew/Funkin/pull/4103)
|
- The HOME and END keys now jump to the top and bottom of the Freeplay song list, respectively. ([bb974c2](https://github.com/FunkinCrew/Funkin/commit/bb974c264270d10ff503784063e5d77bb352b3f7)) - by @AbnormalPoof in [#4103](https://github.com/FunkinCrew/Funkin/pull/4103)
|
||||||
- Added an option to launch the game in fullscreen. ([ee53ccd](https://github.com/FunkinCrew/Funkin/commit/ee53ccd32721e0790adfe82c60d4aca419db0a7f)) - by @AbnormalPoof in [#3738](https://github.com/FunkinCrew/Funkin/pull/3738)
|
- Added an option to launch the game in fullscreen. ([ee53ccd](https://github.com/FunkinCrew/Funkin/commit/ee53ccd32721e0790adfe82c60d4aca419db0a7f)) - by @AbnormalPoof in [#3738](https://github.com/FunkinCrew/Funkin/pull/3738)
|
||||||
- Added descriptions for each item in the Preferences menu. ([a17b0e8](https://github.com/FunkinCrew/Funkin/commit/a17b0e8b3cc1d56fcdc0b51eaca9fd57cdb5bce0)) - by @anysad in [#3872](https://github.com/FunkinCrew/Funkin/pull/3872)
|
- Added on-screen descriptions for each item in the Preferences menu. ([a17b0e8](https://github.com/FunkinCrew/Funkin/commit/a17b0e8b3cc1d56fcdc0b51eaca9fd57cdb5bce0)) - by @anysad in [#3872](https://github.com/FunkinCrew/Funkin/pull/3872)
|
||||||
- Added precise scrolling in the Chart Editor using Ctrl-Mouse Wheel. ([0d8e4a5](https://github.com/FunkinCrew/Funkin/commit/0d8e4a53305d6d069454812766300122f3581e31)) - by @ninjamuffin99 in [#3806](https://github.com/FunkinCrew/Funkin/pull/3806)
|
- Added precise scrolling in the Chart Editor using Ctrl-Mouse Wheel. ([0d8e4a5](https://github.com/FunkinCrew/Funkin/commit/0d8e4a53305d6d069454812766300122f3581e31)) - by @ninjamuffin99 in [#3806](https://github.com/FunkinCrew/Funkin/pull/3806)
|
||||||
- Added a “None” option to the character selector in the Chart Editor. ([d9637d3](https://github.com/FunkinCrew/Funkin/commit/d9637d3a19466b9fc68a102676c46a74ef504909)) - by @Lasercar in [#4279](https://github.com/FunkinCrew/Funkin/pull/4279)
|
- Added a “None” option to the character selector in the Chart Editor. ([9c2ef02](https://github.com/FunkinCrew/Funkin/commit/9c2ef0236818883ad1275571dac49eab70ca0ea0)) - by @Lasercar in [#4279](https://github.com/FunkinCrew/Funkin/pull/4279)
|
||||||
- Added the ability to flip the character in the Animation Editor. ([de02137](https://github.com/FunkinCrew/Funkin/commit/de02137d7c7d1779e85aeda34743f506a5b9cc27)) - by @AbnormalPoof in [#3028](https://github.com/FunkinCrew/Funkin/pull/3028)
|
- Added the ability to flip the character in the Animation Editor. ([de02137](https://github.com/FunkinCrew/Funkin/commit/de02137d7c7d1779e85aeda34743f506a5b9cc27)) - by @AbnormalPoof in [#3028](https://github.com/FunkinCrew/Funkin/pull/3028)
|
||||||
- Added offsets support for album titles. ([69d8570](https://github.com/FunkinCrew/Funkin/commit/69d8570a9eb06011ed6dd95fcbef83d90f7f8684)) - by @AbnormalPoof in [#3618](https://github.com/FunkinCrew/Funkin/pull/3618)
|
- Added offsets support for album titles. ([69d8570](https://github.com/FunkinCrew/Funkin/commit/69d8570a9eb06011ed6dd95fcbef83d90f7f8684)) - by @AbnormalPoof in [#3618](https://github.com/FunkinCrew/Funkin/pull/3618)
|
||||||
- Added three new properties to stage data: `angle`, `scroll`, and `alpha`. ([ff56b19](https://github.com/FunkinCrew/Funkin/commit/ff56b1948aef42bbb6bb4ede4f9b2012d49ab044)) - by @AbnormalPoof in [#3720](https://github.com/FunkinCrew/Funkin/pull/3720)
|
- Added three new properties to stage data: `angle`, `scroll`, and `alpha`. ([ff56b19](https://github.com/FunkinCrew/Funkin/commit/ff56b1948aef42bbb6bb4ede4f9b2012d49ab044)) - by @AbnormalPoof in [#3720](https://github.com/FunkinCrew/Funkin/pull/3720)
|
||||||
- Added script events for losing/gaining focus. ([4b127b6](https://github.com/FunkinCrew/Funkin/commit/4b127b64130f6f753d0574ec66a1672322e4bd13)) - by @AbnormalPoof in [#3721](https://github.com/FunkinCrew/Funkin/pull/3721)
|
- Added script events for losing/gaining focus. ([4b127b6](https://github.com/FunkinCrew/Funkin/commit/4b127b64130f6f753d0574ec66a1672322e4bd13)) - by @AbnormalPoof in [#3721](https://github.com/FunkinCrew/Funkin/pull/3721)
|
||||||
- Added 10 new functions to `ReflectUtil`.([6216655](https://github.com/FunkinCrew/Funkin/commit/62166554e7a176245d1a63bd15122033044c4e40)) - by @AbnormalPoof in [#3622](https://github.com/FunkinCrew/Funkin/pull/3622), [#3809](https://github.com/FunkinCrew/Funkin/pull/3809), and [#4019](https://github.com/FunkinCrew/Funkin/pull/4019)
|
- Added 10 new functions to `ReflectUtil`. ([6216655](https://github.com/FunkinCrew/Funkin/commit/62166554e7a176245d1a63bd15122033044c4e40)) - by @AbnormalPoof in [#3622](https://github.com/FunkinCrew/Funkin/pull/3622), [#3809](https://github.com/FunkinCrew/Funkin/pull/3809), and [#4019](https://github.com/FunkinCrew/Funkin/pull/4019)
|
||||||
- Added `DEBUG_BUILD` value to `Constants` to indicate whether a build has debug functions enabled. ([ad45b72](https://github.com/FunkinCrew/Funkin/commit/ad45b72b1ae8eb73a12dc51bcb59f66cc55e7bbd)) - by @AbnormalPoof in [#3853](https://github.com/FunkinCrew/Funkin/pull/3853)
|
- Added `DEBUG_BUILD` value to `Constants` to indicate whether a build has debug functions enabled. ([ad45b72](https://github.com/FunkinCrew/Funkin/commit/ad45b72b1ae8eb73a12dc51bcb59f66cc55e7bbd)) - by @AbnormalPoof in [#3853](https://github.com/FunkinCrew/Funkin/pull/3853)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Switched from hxCodec to hxvlc for video playback. This may break a mod or two.
|
- Switched from hxCodec to hxvlc for video playback. This may break a mod or two.
|
||||||
- Check the [Funkin Modding Docs](https://funkincrew.github.io/funkin-modding-docs/09-migration/09-02-0.5.0-to-0.6.0.html) for more info on how to update your mods.
|
- Check the [Funkin Modding Docs](https://funkincrew.github.io/funkin-modding-docs/09-migration/09-02-0.5.0-to-0.6.0.html) for more info on how to update your mods.
|
||||||
- Polymod should now ignore `.git` files when loading mods.
|
- Polymod should now ignore `.git` files when loading mods.
|
||||||
- Lots of improvements to issue and pull request organization. - by @Hundrec and @AbnormalPoof
|
- The pause menu can now be opened and closed rapidly.
|
||||||
|
- Adjusted difficulty ratings and scroll speeds for many songs.
|
||||||
|
- Chart Editor event fields now allow for values to be as specific as desired.
|
||||||
|
- For example, the Zoom Camera event can now be set to 0.9857.
|
||||||
|
- Lots of improvements to GitHub issue and pull request organization. - by @Hundrec and @AbnormalPoof
|
||||||
- Overhauled the Changelog to improve readability and properly credit contributors. ([4383fcf](https://github.com/FunkinCrew/Funkin/commit/4383fcf32c280a1c0ee7b9c80d255611d497cabc)) - by @Hundrec in [#4296](https://github.com/FunkinCrew/Funkin/pull/4296) and [#4298](https://github.com/FunkinCrew/Funkin/pull/4298)
|
- Overhauled the Changelog to improve readability and properly credit contributors. ([4383fcf](https://github.com/FunkinCrew/Funkin/commit/4383fcf32c280a1c0ee7b9c80d255611d497cabc)) - by @Hundrec in [#4296](https://github.com/FunkinCrew/Funkin/pull/4296) and [#4298](https://github.com/FunkinCrew/Funkin/pull/4298)
|
||||||
- Made various improvements to the screenshot function. ([9ce7bbc](https://github.com/FunkinCrew/Funkin/commit/9ce7bbcfbb8f30ae120c876194f89bc4c787f585)) - by @Lasercar in [#4082](https://github.com/FunkinCrew/Funkin/pull/4082)
|
- Made various improvements to the screenshot plugin. ([868932c](https://github.com/FunkinCrew/Funkin/commit/868932cd138fad4be5b541cbea3110e30479057b)) - by @Lasercar in [#4082](https://github.com/FunkinCrew/Funkin/pull/4082)
|
||||||
- Accept keybinds (Z and Space by default) can now be used to exit the Results screen. ([edb270d](https://github.com/FunkinCrew/Funkin/commit/edb270d15e41784dccbf75639ac731840e80fe23)) - by @JVNpixels in [#3799](https://github.com/FunkinCrew/Funkin/pull/3799)
|
- Accept keybinds (Z and Space by default) can now be used to exit the Results screen. ([edb270d](https://github.com/FunkinCrew/Funkin/commit/edb270d15e41784dccbf75639ac731840e80fe23)) - by @JVNpixels in [#3799](https://github.com/FunkinCrew/Funkin/pull/3799)
|
||||||
- Reordered UI keybinds in the controls menu for consistency. ([a01bcc3](https://github.com/FunkinCrew/Funkin/commit/a01bcc3da836ec52851ca9de13ef459daf61269a)) - by @lemz1 in [#3027](https://github.com/FunkinCrew/Funkin/pull/3027)
|
- Reordered UI keybinds in the controls menu for consistency. ([a01bcc3](https://github.com/FunkinCrew/Funkin/commit/a01bcc3da836ec52851ca9de13ef459daf61269a)) - by @lemz1 in [#3027](https://github.com/FunkinCrew/Funkin/pull/3027)
|
||||||
- New save files now have default Freeplay controls for gamepads. ([2b7f62e](https://github.com/FunkinCrew/Funkin/commit/2b7f62edd33de5527e259d9e5643f926d35da734)) - by @MrMadera in [#3934](https://github.com/FunkinCrew/Funkin/pull/3934)
|
- New save files now have default Freeplay controls for gamepads. ([2b7f62e](https://github.com/FunkinCrew/Funkin/commit/2b7f62edd33de5527e259d9e5643f926d35da734)) - by @MrMadera in [#3934](https://github.com/FunkinCrew/Funkin/pull/3934)
|
||||||
- Renamed “Auto Pause” preference to “Pause on Unfocus” for clarity. ([49a21c1](https://github.com/FunkinCrew/Funkin/commit/49a21c198236fbecbe5902bb106f78939cc6442a)) - by @JackXson-Real in [#4346](https://github.com/FunkinCrew/Funkin/pull/4346)
|
|
||||||
- Made scrolling smoother in the Chart Editor. ([20d9016](https://github.com/FunkinCrew/Funkin/commit/20d90169845f1e50f849e39f4c5f818359756c78)) - by @ninjamuffin99 in [#3768](https://github.com/FunkinCrew/Funkin/pull/3768)
|
- Made scrolling smoother in the Chart Editor. ([20d9016](https://github.com/FunkinCrew/Funkin/commit/20d90169845f1e50f849e39f4c5f818359756c78)) - by @ninjamuffin99 in [#3768](https://github.com/FunkinCrew/Funkin/pull/3768)
|
||||||
- The Chart Editor now clears the undo/redo history after loading a new song. ([c7bdc1a](https://github.com/FunkinCrew/Funkin/commit/c7bdc1abe265678468920ca3cc3d7eface5d6925)) - by @Lasercar in [#4308](https://github.com/FunkinCrew/Funkin/pull/4308)
|
|
||||||
- Mods with missing dependencies are now skipped instead of preventing all mods from loading. ([1c2fb43](https://github.com/FunkinCrew/Funkin/commit/1c2fb43ae16cf40be5ef94c40b047e8e772b1211)) - by @AbnormalPoof in [#3993](https://github.com/FunkinCrew/Funkin/pull/3993)
|
- Mods with missing dependencies are now skipped instead of preventing all mods from loading. ([1c2fb43](https://github.com/FunkinCrew/Funkin/commit/1c2fb43ae16cf40be5ef94c40b047e8e772b1211)) - by @AbnormalPoof in [#3993](https://github.com/FunkinCrew/Funkin/pull/3993)
|
||||||
- Slightly improved flexibility for modding note hit animations. ([3aad825](https://github.com/FunkinCrew/Funkin/commit/3aad825f865c4ed87016983d44121e2c1610d332)) - by @TechnikTil in [#3936](https://github.com/FunkinCrew/Funkin/pull/3936)
|
- Slightly improved flexibility for modding note hit animations. ([3aad825](https://github.com/FunkinCrew/Funkin/commit/3aad825f865c4ed87016983d44121e2c1610d332)) - by @TechnikTil in [#3936](https://github.com/FunkinCrew/Funkin/pull/3936)
|
||||||
- Introduced several QoL modding changes. ([785c4be](https://github.com/FunkinCrew/Funkin/commit/785c4be88b52dc1b5899013822fc004ba7d9894d)) - by @Kade-github in [#4009](https://github.com/FunkinCrew/Funkin/pull/4009)
|
- Introduced several QoL modding changes. ([785c4be](https://github.com/FunkinCrew/Funkin/commit/785c4be88b52dc1b5899013822fc004ba7d9894d)) - by @Kade-github in [#4009](https://github.com/FunkinCrew/Funkin/pull/4009)
|
||||||
- Lots of smaller bug fixes.
|
- Lots of smaller changes.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Shaders no longer create thin seams within atlas sprites.
|
||||||
|
- Completing a song in Practice Mode no longer plays a new rank animation in the Freeplay menu.
|
||||||
|
- Fixed lots of charting issues across many songs.
|
||||||
|
- The Chart Editor grid now properly adjusts to the new BPM after switching variations.
|
||||||
|
- Fixed a few crashes in the Stage Editor.
|
||||||
|
- Fixed a bug where the song would restart from the beginning instead of moving to the Results screen. ([3667c51](https://github.com/FunkinCrew/Funkin/commit/3667c51c1efe14cfe7c810e2f35991f08f50781a)) - by @KoloInDaCrib in [#4309](https://github.com/FunkinCrew/Funkin/pull/4309) and @Lasercar in [#4330](https://github.com/FunkinCrew/Funkin/pull/4330)
|
||||||
|
- Reduced stuttering when resyncing instrumental and voices tracks. ([22d41d2](https://github.com/FunkinCrew/Funkin/commit/22d41d21b88acb7422a0afcda8414682710bd2ed)) - by @TechnikTil in [#3955](https://github.com/FunkinCrew/Funkin/pull/3955)
|
||||||
|
- Songs with only instrumental tracks no longer stutter. ([dfe02ec](https://github.com/FunkinCrew/Funkin/commit/dfe02ec668b61d6308f459c978d12a7487f9dc28)) - by @KoloInDaCrib in [#3861](https://github.com/FunkinCrew/Funkin/pull/3861)
|
||||||
|
- The debug mouse cursor no longer flickers before the Title Screen loads. ([1c12b84](https://github.com/FunkinCrew/Funkin/commit/1c12b8467eca350eb28138473360d5358fa620e2)) - by @sphis-Sinco in [#3881](https://github.com/FunkinCrew/Funkin/pull/3881)
|
||||||
|
- Unbound keys now display as [N/A] instead of crashing the game. ([099c309](https://github.com/FunkinCrew/Funkin/commit/099c309f9babdc1ea99b7dbed3fdccf1e952fc8e)) - by @NotHyper-474 in [#4355](https://github.com/FunkinCrew/Funkin/pull/4355)
|
||||||
|
- Songs can no longer be spam-selected after selecting an instrumental in Freeplay. ([0e0c4ae](https://github.com/FunkinCrew/Funkin/commit/0e0c4aeb7745cfb9479685ccbb635cf3743cddbb)) - by @AbnormalPoof in [#3866](https://github.com/FunkinCrew/Funkin/pull/3866)
|
||||||
|
- The Random capsule can now switch to Erect/Nightmare difficulties in Freeplay. ([a90b911](https://github.com/FunkinCrew/Funkin/commit/a90b911653a1beaba57d64b1f05b840109fec42b)) - by @KoloInDaCrib in [#3838](https://github.com/FunkinCrew/Funkin/pull/3838)
|
||||||
|
- Fixed a rare bug where a song would not register as beaten. ([a3e2373](https://github.com/FunkinCrew/Funkin/commit/a3e23733db104b1ef00cfcff17db3a5d032a4d67)) - by @AbnormalPoof in [#3820](https://github.com/FunkinCrew/Funkin/pull/3820)
|
||||||
|
- The difficulty graphic on the Results screen no longer cuts off incorrectly. ([b13bf05](https://github.com/FunkinCrew/Funkin/commit/b13bf05d16ff2977309e0c7ba3f049c0134e8902)) - by @AbnormalPoof in [#4161](https://github.com/FunkinCrew/Funkin/pull/4161)
|
||||||
|
- Four-digit long Total Notes values in the Results screen no longer overflow to the right. ([91a594c](https://github.com/FunkinCrew/Funkin/commit/91a594cc858ed086cd2146a1ac5d2379c6fdd27a)) - by @Hundrec in [#4356](https://github.com/FunkinCrew/Funkin/pull/4356)
|
||||||
|
- The Character Select screen no longer plays the unlock animation for some locked characters. ([7058126](https://github.com/FunkinCrew/Funkin/commit/7058126e99adb55e43f5f487b007d3efa9f324d5)) - by @AbnormalPoof in [#3748](https://github.com/FunkinCrew/Funkin/pull/3748)
|
||||||
|
- All time signatures in the Chart Editor now display the correct number of beat/step tick lines. ([e570dfb](https://github.com/FunkinCrew/Funkin/commit/e570dfb8e754f9cb29ac2d8fff6e8513bc68b630)) - by @Keoiki in [#2860](https://github.com/FunkinCrew/Funkin/pull/2860)
|
||||||
|
- The Debug menu now opens with the correct camera position. ([090ddd1](https://github.com/FunkinCrew/Funkin/commit/090ddd1f1c2aa48fdb83127b2235041643c99af5)) - by @ninjamuffin99 in [#3769](https://github.com/FunkinCrew/Funkin/pull/3769)
|
||||||
|
- Removed an unused Freeplay class left over from legacy versions. ([abe4ac8](https://github.com/FunkinCrew/Funkin/commit/abe4ac8485539cbebe527a9a75698950232b68d2)) - by @AbnormalPoof in [#4370](https://github.com/FunkinCrew/Funkin/pull/4370)
|
||||||
|
- Blacklisted an additional class for security. ([3492d41](https://github.com/FunkinCrew/Funkin/commit/3492d412c65c7f3fd61e6fc6c9410d8467122ab0)) - by @AbnormalPoof in [#4074](https://github.com/FunkinCrew/Funkin/pull/4074)
|
||||||
|
- Removed an unused class from Polymod blacklist. ([06c12e3](https://github.com/FunkinCrew/Funkin/commit/06c12e36c6bd6df4e2be32a3bec540172e79e162)) - by @AbnormalPoof in [#3729](https://github.com/FunkinCrew/Funkin/pull/3729)
|
||||||
|
- Many additional small bug fixes.
|
||||||
|
|
||||||
## New Contributors for 0.6.0
|
## New Contributors for 0.6.0
|
||||||
|
|
||||||
* @PatoFlamejanteTV made their first contribution in [#3843](https://github.com/FunkinCrew/Funkin/pull/3843)
|
* @PatoFlamejanteTV made their first contribution in [#3843](https://github.com/FunkinCrew/Funkin/pull/3843)
|
||||||
* @sphis-Sinco made their first contribution in [#3881](https://github.com/FunkinCrew/Funkin/pull/3881)
|
* @sphis-Sinco made their first contribution in [#3881](https://github.com/FunkinCrew/Funkin/pull/3881)
|
||||||
* @MrMadera made their first contribution in [#3934](https://github.com/FunkinCrew/Funkin/pull/3934)
|
* @MrMadera made their first contribution in [#3934](https://github.com/FunkinCrew/Funkin/pull/3934)
|
||||||
* @Lasercar made their first contribution in [#4100](https://github.com/FunkinCrew/Funkin/pull/4065)
|
|
||||||
* @MidyGamy made their first contribution in [#4068](https://github.com/FunkinCrew/Funkin/pull/4068)
|
* @MidyGamy made their first contribution in [#4068](https://github.com/FunkinCrew/Funkin/pull/4068)
|
||||||
|
* @Lasercar made their first contribution in [#4082](https://github.com/FunkinCrew/Funkin/pull/4082)
|
||||||
* @MrScottyPieey made their first contribution in [#4085](https://github.com/FunkinCrew/Funkin/pull/4085)
|
* @MrScottyPieey made their first contribution in [#4085](https://github.com/FunkinCrew/Funkin/pull/4085)
|
||||||
* @JackXson-Real made their first contribution in [#4346](https://github.com/FunkinCrew/Funkin/pull/4346)
|
|
||||||
|
|
||||||
# Fixed
|
|
||||||
- Fixed a bug where the song would restart from the beginning instead of moving to the Results screen. ([3667c51](https://github.com/FunkinCrew/Funkin/commit/3667c51c1efe14cfe7c810e2f35991f08f50781a)) - by @KoloInDaCrib in [#4309](https://github.com/FunkinCrew/Funkin/pull/4309) and @Lasercar in [#4330](https://github.com/FunkinCrew/Funkin/pull/4330)
|
|
||||||
- Reduced stuttering when resyncing instrumental and voices tracks. ([22d41d2](https://github.com/FunkinCrew/Funkin/commit/22d41d21b88acb7422a0afcda8414682710bd2ed)) - by @TechnikTil in [#3955](https://github.com/FunkinCrew/Funkin/pull/3955)
|
|
||||||
- Songs with only instrumental tracks no longer stutter. ([dfe02ec](https://github.com/FunkinCrew/Funkin/commit/dfe02ec668b61d6308f459c978d12a7487f9dc28)) - by @KoloInDaCrib in [#3861](https://github.com/FunkinCrew/Funkin/pull/3861)
|
|
||||||
- The mouse cursor is no longer visible outside of debug editors. ([1c12b84](https://github.com/FunkinCrew/Funkin/commit/1c12b8467eca350eb28138473360d5358fa620e2)) - by @sphis-Sinco in [#3881](https://github.com/FunkinCrew/Funkin/pull/3881)
|
|
||||||
- Unbound keys now display as [N/A] instead of crashing the game. ([94e5d3c](https://github.com/FunkinCrew/Funkin/commit/94e5d3ca258cbaa5d9ab6f0cb26feda83b3433a2)) - by @NotHyper-474 in [#4355](https://github.com/FunkinCrew/Funkin/pull/4355)
|
|
||||||
- Songs can no longer be spam-selected after selecting an instrumental in Freeplay. ([0e0c4ae](https://github.com/FunkinCrew/Funkin/commit/0e0c4aeb7745cfb9479685ccbb635cf3743cddbb)) - by @AbnormalPoof in [#3866](https://github.com/FunkinCrew/Funkin/pull/3866)
|
|
||||||
- The Random capsule can now switch to Erect/Nightmare difficulties in Freeplay. ([6f32747](https://github.com/FunkinCrew/Funkin/commit/6f327474f30374f50774f432ea3ccb6d02579445)) - by @KoloInDaCrib in [#3838](https://github.com/FunkinCrew/Funkin/pull/3838)
|
|
||||||
- The millions place digit of the Freeplay score now updates properly. ([3719ca6](https://github.com/FunkinCrew/Funkin/commit/3719ca6ce7a170c3c890cdb2aeea3e4534b91736)) - by @Lasercar in [#4065](https://github.com/FunkinCrew/Funkin/pull/4065)
|
|
||||||
- Fixed a rare bug where a song would not register as beaten. ([a3e2373](https://github.com/FunkinCrew/Funkin/commit/a3e23733db104b1ef00cfcff17db3a5d032a4d67)) - by @AbnormalPoof in [#3820](https://github.com/FunkinCrew/Funkin/pull/3820)
|
|
||||||
- The difficulty graphic on the Results screen no longer cuts off incorrectly. ([b13bf05](https://github.com/FunkinCrew/Funkin/commit/b13bf05d16ff2977309e0c7ba3f049c0134e8902)) - by @AbnormalPoof in [#4161](https://github.com/FunkinCrew/Funkin/pull/4161)
|
|
||||||
- Four-digit long Total Notes values in the Results screen no longer overflow to the right. ([2be4c0c](https://github.com/FunkinCrew/Funkin/commit/2be4c0c7196e018a43f9697f015179f3efc3fcdb)) - by @Hundrec in [#4356](https://github.com/FunkinCrew/Funkin/pull/4356)
|
|
||||||
- The Character Select screen no longer plays the unlock animation for some locked characters. ([7058126](https://github.com/FunkinCrew/Funkin/commit/7058126e99adb55e43f5f487b007d3efa9f324d5)) - by @AbnormalPoof in [#3748](https://github.com/FunkinCrew/Funkin/pull/3748)
|
|
||||||
- All time signatures in the Chart Editor now display the correct number of beat/step tick lines. ([e570dfb](https://github.com/FunkinCrew/Funkin/commit/e570dfb8e754f9cb29ac2d8fff6e8513bc68b630)) - by @Keoiki in [#2860](https://github.com/FunkinCrew/Funkin/pull/2860)
|
|
||||||
- The Debug menu now opens with the correct camera position. ([090ddd1](https://github.com/FunkinCrew/Funkin/commit/090ddd1f1c2aa48fdb83127b2235041643c99af5)) - by @ninjamuffin99 in [#3769](https://github.com/FunkinCrew/Funkin/pull/3769)
|
|
||||||
- Removed some Freeplay variables left over from legacy versions. ([6f6529f](https://github.com/FunkinCrew/Funkin/commit/6f6529f3c460721755dfdd0a7a26578c0de429d2)) - by @AbnormalPoof in [#4370](https://github.com/FunkinCrew/Funkin/pull/4370)
|
|
||||||
- Blacklisted an additional class for security. ([3492d41](https://github.com/FunkinCrew/Funkin/commit/3492d412c65c7f3fd61e6fc6c9410d8467122ab0)) - by @AbnormalPoof in [#4074](https://github.com/FunkinCrew/Funkin/pull/4074)
|
|
||||||
- Removed an unused class from Polymod blacklist. ([06c12e3](https://github.com/FunkinCrew/Funkin/commit/06c12e36c6bd6df4e2be32a3bec540172e79e162)) - by @AbnormalPoof in [#3729](https://github.com/FunkinCrew/Funkin/pull/3729)
|
|
||||||
- Many additional small bug fixes.
|
|
||||||
|
|
||||||
|
|
||||||
## [0.5.3] - 2024-10-18
|
## [0.5.3] - 2024-10-18
|
||||||
@@ -374,7 +524,7 @@ The Playable Pico Update!
|
|||||||
- Botplay mode now handles the player's animations properly during hold notes. ([thanks Hundrec!](https://github.com/FunkinCrew/Funkin/pull/2683))
|
- Botplay mode now handles the player's animations properly during hold notes. ([thanks Hundrec!](https://github.com/FunkinCrew/Funkin/pull/2683))
|
||||||
- Camera movement now pauses when the game is paused. ([thanks Matriculaso!](https://github.com/FunkinCrew/Funkin/pull/2684))
|
- Camera movement now pauses when the game is paused. ([thanks Matriculaso!](https://github.com/FunkinCrew/Funkin/pull/2684))
|
||||||
- Pico's gameplay sprite no longer appears on the gameover screen when dying from an explosion in 2hot.
|
- Pico's gameplay sprite no longer appears on the gameover screen when dying from an explosion in 2hot.
|
||||||
- Freeplay previews now properly fade in volume during the BF idle animation.
|
- Freeplay previews now properly fade in volume during the DJ's idle animation.
|
||||||
- DadBattle no longer incorrectly appears as DadBattle Erect when returning to Freeplay on Hard.
|
- DadBattle no longer incorrectly appears as DadBattle Erect when returning to Freeplay on Hard.
|
||||||
- 2hot now appears under the "#" category in Freeplay menu.
|
- 2hot now appears under the "#" category in Freeplay menu.
|
||||||
- The Chart Editor no longer crashes when selecting an event with the Event toolbox open.
|
- The Chart Editor no longer crashes when selecting an event with the Event toolbox open.
|
||||||
|
|||||||
+3
-3
@@ -14,8 +14,8 @@ Code Quality is handled by the `vshaxe.haxe-checkstyle` extension, which include
|
|||||||
* `IndentationCharacter` checks what is used to indent, `Indentation` checks how deep the indentation is.
|
* `IndentationCharacter` checks what is used to indent, `Indentation` checks how deep the indentation is.
|
||||||
* `CommentedOutCode` check is in place because old code should be retrieved via Git history.
|
* `CommentedOutCode` check is in place because old code should be retrieved via Git history.
|
||||||
* TODO items: Enable these one-by-one and fix them to improve the overall code quality.
|
* TODO items: Enable these one-by-one and fix them to improve the overall code quality.
|
||||||
- Reconfigure `MethodLength`
|
- Re-configure `MethodLength`
|
||||||
- Reconfigure `CyclomaticComplexity`
|
- Re-configure `CyclomaticComplexity`
|
||||||
- Re-enable `MagicNumber`
|
- Re-enable `MagicNumber`
|
||||||
- Re-configure `NestedControlFlow`
|
- Re-configure `NestedControlFlow`
|
||||||
- Re-configure `NestedIfDepth`
|
- Re-configure `NestedIfDepth`
|
||||||
@@ -28,5 +28,5 @@ Prettier automatically handles formatting of JSON files, and can be configured i
|
|||||||
|
|
||||||
### Prettier Notes
|
### Prettier Notes
|
||||||
* Prettier will automatically attempt to place expressions on a single line if they fit, but will keep them multi-line if they are manually made multi-line.
|
* Prettier will automatically attempt to place expressions on a single line if they fit, but will keep them multi-line if they are manually made multi-line.
|
||||||
* This means that long singleline objects are automatically expanded, and short multiline objects aren't automatically collapsed.
|
* This means that long single-line objects are automatically expanded, and short multi-line objects aren't automatically collapsed.
|
||||||
* You may want to use regex replacement to manually remove the first newline in short multi-line objects to convince Prettier to collapse them.
|
* You may want to use regex replacement to manually remove the first newline in short multi-line objects to convince Prettier to collapse them.
|
||||||
|
|||||||
+1
-1
Submodule art updated: 4efa0536a3...8402339e2e
+1
-1
Submodule assets updated: 24eb271f3b...c108a7ff0d
+1
-1
@@ -102,7 +102,7 @@ RUN <<EOF
|
|||||||
# | jq '.[] | select(.name == "'"$neko_version"'")' \
|
# | jq '.[] | select(.name == "'"$neko_version"'")' \
|
||||||
# | jq '.assets[] | select(.name | endswith("linux64.tar.gz"))' \
|
# | jq '.assets[] | select(.name | endswith("linux64.tar.gz"))' \
|
||||||
# | jq -r '.browser_download_url')
|
# | jq -r '.browser_download_url')
|
||||||
neko_url="https://geo.thei.rs/funkin/neko-2.4.0-linux64.tar.gz"
|
neko_url="https://geo.thei.rs/funkin/neko-2.4.1-linux64.tar.gz"
|
||||||
curl -fL "$neko_url" | tar -xz -C /usr/local
|
curl -fL "$neko_url" | tar -xz -C /usr/local
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -347,7 +347,7 @@
|
|||||||
{
|
{
|
||||||
"props": {
|
"props": {
|
||||||
"allowSingleLineStatement": true,
|
"allowSingleLineStatement": true,
|
||||||
"tokens": ["FOR", "IF", "ELSE_IF", "WHILE", "DO_WHILE"]
|
"tokens": ["FOR", "ELSE_IF", "WHILE", "DO_WHILE"]
|
||||||
},
|
},
|
||||||
"type": "NeedBraces"
|
"type": "NeedBraces"
|
||||||
},
|
},
|
||||||
|
|||||||
+7
-6
@@ -13,19 +13,20 @@
|
|||||||
- NOTE: By performing this operation, you are downloading Content which is proprietary and protected by national and international copyright and trademark laws. See [the LICENSE.md file for the Funkin.assets](https://github.com/FunkinCrew/funkin.assets/blob/main/LICENSE.md) repo for more information.
|
- NOTE: By performing this operation, you are downloading Content which is proprietary and protected by national and international copyright and trademark laws. See [the LICENSE.md file for the Funkin.assets](https://github.com/FunkinCrew/funkin.assets/blob/main/LICENSE.md) repo for more information.
|
||||||
5. Run `haxelib --global install hmm` and then `haxelib --global run hmm setup` to install hmm.json
|
5. Run `haxelib --global install hmm` and then `haxelib --global run hmm setup` to install hmm.json
|
||||||
6. Run `hmm install` to install all haxelibs of the current branch
|
6. Run `hmm install` to install all haxelibs of the current branch
|
||||||
7. Run `haxelib run lime setup` to set up Lime
|
- If you have issues installing Lime, try utilizing Funkin's patched libraries for hmm and haxelib: `haxelib --global git haxelib https://github.com/FunkinCrew/haxelib.git` and `haxelib --global git hmm https://github.com/FunkinCrew/hmm.git`
|
||||||
8. Perform additional platform setup
|
8. Run `haxelib run lime setup` to set up Lime
|
||||||
|
9. Perform additional platform setup
|
||||||
- For Windows, download the [Visual Studio Build Tools](https://aka.ms/vs/17/release/vs_BuildTools.exe)
|
- For Windows, download the [Visual Studio Build Tools](https://aka.ms/vs/17/release/vs_BuildTools.exe)
|
||||||
- When prompted, select "Individual Components" and make sure to download the following:
|
- When prompted, select "Individual Components" and make sure to download the following:
|
||||||
- MSVC v143 VS 2022 C++ x64/x86 build tools
|
- MSVC v143 VS 2022 C++ x64/x86 build tools
|
||||||
- Windows 10/11 SDK
|
- Windows 10/11 SDK
|
||||||
- Mac: [`lime setup mac` Documentation](https://lime.openfl.org/docs/advanced-setup/macos/)
|
- Mac: [`lime setup mac` Documentation](https://lime.openfl.org/docs/advanced-setup/macos/)
|
||||||
- Linux: [`lime setup linux` Documentation](https://lime.openfl.org/docs/advanced-setup/linux/)
|
- Linux: [`lime setup linux` Documentation](https://lime.openfl.org/docs/advanced-setup/linux/)
|
||||||
- Note: Funkin's fork currently doesn't come with the necessary binaries so you'll have to rebuild Lime. See [Troubleshooting](TROUBLESHOOTING.md#lime-related-issues).
|
- One of Funkin's dependencies uses libVLC, which requires you to install some development packages to be able to compile.
|
||||||
- One of Funkin's dependencies uses libVLC, which requires you to install some packages to be able to compile: `sudo apt install libvlc-dev libvlccore-dev libvlccore9`
|
Command for Ubuntu/Debian based systems: `sudo apt install libvlc-dev libvlccore-dev libvlccore9`
|
||||||
- HTML5: Compiles without any extra setup
|
- HTML5: Compiles without any extra setup
|
||||||
9. If you are targeting for native, you may need to run `lime rebuild <PLATFORM>` and `lime rebuild <PLATFORM> -debug`
|
10. If you are targeting for native, you may need to run `lime rebuild <PLATFORM>` and `lime rebuild <PLATFORM> -debug`
|
||||||
10. `lime test <PLATFORM>` to build and launch the game for your platform (for example, `lime test windows`)
|
11. `lime test <PLATFORM>` to build and launch the game for your platform (for example, `lime test windows`)
|
||||||
|
|
||||||
## Build Flags
|
## Build Flags
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# Mac Compiling Guide + Considerations
|
||||||
|
|
||||||
|
There's a few extra considerations when compiling FNF for Mac that *we* have to handle when creating a wider release.
|
||||||
|
- [Creating a Universal Binary](#creating-a-universal-binary)
|
||||||
|
- Code-signing
|
||||||
|
- Notarizing
|
||||||
|
|
||||||
|
## Creating a Universal Binary
|
||||||
|
|
||||||
|
Run the `art/macos-universal.sh` script, which automatically compiles release versions of both arm64 and x86 of Funkin. You can also see there for reference of how it's done.
|
||||||
+184
-66
@@ -1,6 +1,6 @@
|
|||||||
# Contributing
|
# Contributing
|
||||||
Welcome to the Contributing Guide!
|
Welcome to the Contributing Guide!
|
||||||
You can contribute to the Funkin' repository by opening issues or pull requests.
|
You can contribute to the Funkin' repository by opening issues or pull requests.
|
||||||
|
|
||||||
This guide will cover best practices for each type of contribution.
|
This guide will cover best practices for each type of contribution.
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ This guide will cover best practices for each type of contribution.
|
|||||||
[Part 1: Etiquette](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#part-1-etiquette)
|
[Part 1: Etiquette](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#part-1-etiquette)
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><a href="https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#part-2-issues">Part 2: Issues</a></summary>
|
<summary><a href="https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#part-2-issues">Part 2: Issues</a></summary>
|
||||||
|
|
||||||
* [Requirements](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#requirements)
|
* [Requirements](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#requirements)
|
||||||
|
|
||||||
@@ -24,8 +24,8 @@ This guide will cover best practices for each type of contribution.
|
|||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|
||||||
<summary><a href="https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#part-3-pull-requests">Part 3: Pull Requests</a></summary>
|
<summary><a href="https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#part-3-pull-requests">Part 3: Pull Requests</a></summary>
|
||||||
|
|
||||||
* [Choosing a base branch](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#choosing-a-base-branch)
|
* [Choosing a base branch](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#choosing-a-base-branch)
|
||||||
|
|
||||||
@@ -39,6 +39,8 @@ This guide will cover best practices for each type of contribution.
|
|||||||
|
|
||||||
* [funkin.assets PRs](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#funkinassets-prs)
|
* [funkin.assets PRs](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#funkinassets-prs)
|
||||||
|
|
||||||
|
* [Charting PRs](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#charting-prs)
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
[Closing](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#closing)
|
[Closing](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#closing)
|
||||||
@@ -54,11 +56,11 @@ This guide will cover best practices for each type of contribution.
|
|||||||
|
|
||||||
# Part 2: Issues
|
# Part 2: Issues
|
||||||
Issues serve many purposes, from reporting bugs to suggesting new features.
|
Issues serve many purposes, from reporting bugs to suggesting new features.
|
||||||
This section provides guidelines to follow when [opening an issue](https://github.com/FunkinCrew/Funkin/issues).
|
This section provides guidelines to follow when [opening an issue](https://github.com/FunkinCrew/Funkin/issues).
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
Make sure you're playing:
|
Make sure you're playing:
|
||||||
- the latest version of the game (currently v0.5.3)
|
- the latest version of the game (currently v0.6.3)
|
||||||
- without any mods
|
- without any mods
|
||||||
- on [Newgrounds](https://www.newgrounds.com/portal/view/770371) or downloaded from [itch.io](https://ninja-muffin24.itch.io/funkin)
|
- on [Newgrounds](https://www.newgrounds.com/portal/view/770371) or downloaded from [itch.io](https://ninja-muffin24.itch.io/funkin)
|
||||||
|
|
||||||
@@ -72,6 +74,7 @@ Here's a list of commonly suggested features and the reasons why they won't be a
|
|||||||
| Toggleable Ghost Tapping | https://github.com/FunkinCrew/Funkin/pull/2564#issuecomment-2119701802 |
|
| Toggleable Ghost Tapping | https://github.com/FunkinCrew/Funkin/pull/2564#issuecomment-2119701802 |
|
||||||
| Perfectly Centered Strumlines | _same as above^_ |
|
| Perfectly Centered Strumlines | _same as above^_ |
|
||||||
| MultiKey, 9k, More than 4 keys, etc. | https://github.com/FunkinCrew/Funkin/issues/4243#issuecomment-2692371969 |
|
| MultiKey, 9k, More than 4 keys, etc. | https://github.com/FunkinCrew/Funkin/issues/4243#issuecomment-2692371969 |
|
||||||
|
| Flashy Combo Milestones | https://github.com/FunkinCrew/Funkin/pull/4700#issuecomment-2798916579 |
|
||||||
| Losing Icons for DD and Parents | https://github.com/FunkinCrew/Funkin/issues/3048#issuecomment-2243491536 |
|
| Losing Icons for DD and Parents | https://github.com/FunkinCrew/Funkin/issues/3048#issuecomment-2243491536 |
|
||||||
| Playable GF / Speaker BF / Speaker Pico | https://github.com/FunkinCrew/Funkin/issues/2953#issuecomment-2216985230 |
|
| Playable GF / Speaker BF / Speaker Pico | https://github.com/FunkinCrew/Funkin/issues/2953#issuecomment-2216985230 |
|
||||||
| Fresh (Chill Mix) as Title Screen Music | https://github.com/FunkinCrew/Funkin/pull/4282#issuecomment-2709334718 |
|
| Fresh (Chill Mix) as Title Screen Music | https://github.com/FunkinCrew/Funkin/pull/4282#issuecomment-2709334718 |
|
||||||
@@ -84,7 +87,7 @@ Here's a list of commonly suggested features and the reasons why they won't be a
|
|||||||
| "Philly Glow" Effect from Psych Engine | https://github.com/FunkinCrew/Funkin/issues/3788#issuecomment-2688966982 |
|
| "Philly Glow" Effect from Psych Engine | https://github.com/FunkinCrew/Funkin/issues/3788#issuecomment-2688966982 |
|
||||||
| Importing Charts from Psych Engine (and other mod content) | https://github.com/FunkinCrew/Funkin/issues/2586#issuecomment-2125733327 |
|
| Importing Charts from Psych Engine (and other mod content) | https://github.com/FunkinCrew/Funkin/issues/2586#issuecomment-2125733327 |
|
||||||
| Backwards Compatibility for Modding | https://github.com/FunkinCrew/Funkin/issues/3949#issuecomment-2608391329 |
|
| Backwards Compatibility for Modding | https://github.com/FunkinCrew/Funkin/issues/3949#issuecomment-2608391329 |
|
||||||
| Lua Support | https://github.com/FunkinCrew/Funkin/issues/2643#issuecomment-2143718093 |
|
| Lua Support | https://github.com/FunkinCrew/Funkin/issues/2643#issuecomment-2143718093 |
|
||||||
|
|
||||||
|
|
||||||
## Issue Types
|
## Issue Types
|
||||||
@@ -122,6 +125,9 @@ Also only report one issue or enhancement at a time! If you have multiple bug re
|
|||||||
|
|
||||||
Once you're sure your issue is unique and specific, feel free to submit it.
|
Once you're sure your issue is unique and specific, feel free to submit it.
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> **DO NOT CLOSE YOUR ISSUE FOR ANY REASON!** Your issue will be taken care of by a moderator!
|
||||||
|
|
||||||
**Thank you for opening issues!**
|
**Thank you for opening issues!**
|
||||||
|
|
||||||
# Part 3: Pull Requests
|
# Part 3: Pull Requests
|
||||||
@@ -133,6 +139,7 @@ When creating a branch in your fork, base your branch on either the `main` or `d
|
|||||||
|
|
||||||
> [!CAUTION]
|
> [!CAUTION]
|
||||||
> Avoid using your fork's default branch (`main` in this case) for your PR. This is considered an [anti-pattern](https://jmeridth.com/posts/do-not-issue-pull-requests-from-your-master-branch/) by GitHub themselves!
|
> Avoid using your fork's default branch (`main` in this case) for your PR. This is considered an [anti-pattern](https://jmeridth.com/posts/do-not-issue-pull-requests-from-your-master-branch/) by GitHub themselves!
|
||||||
|
> Instead, make a separate branch for your additions (ex. `docs/fix-typo` or `minor-bugfix`).
|
||||||
|
|
||||||
Choose the `main` branch if you modify:
|
Choose the `main` branch if you modify:
|
||||||
- Documentation (`.md` files)
|
- Documentation (`.md` files)
|
||||||
@@ -148,11 +155,23 @@ Choose the `develop` branch if you modify:
|
|||||||
Choosing the right base branch helps keep your commit history clean and avoid merge conflicts.
|
Choosing the right base branch helps keep your commit history clean and avoid merge conflicts.
|
||||||
Once you’re satisfied with the changes you’ve made, open a PR and base it on the same branch you previously chose.
|
Once you’re satisfied with the changes you’ve made, open a PR and base it on the same branch you previously chose.
|
||||||
|
|
||||||
## Merge conflicts and rebasing
|
## Maintaining your pull request
|
||||||
|
Keeping your pull request clean and easy to review increases the chance that it will be accepted!
|
||||||
|
|
||||||
|
Our maintenance policy is as follows:
|
||||||
|
- If we require changes to your PR, we will label your PR `status: needs revision`.
|
||||||
|
- We may also leave a comment under your PR specifying what changes you should make.
|
||||||
|
- If you receive a comment, you have 90 days to implement the requested changes.
|
||||||
|
- After this period, your PR will be closed due to inactivity and labeled `status: stale`.
|
||||||
|
- Even after your PR is closed, you may request for us to reopen it. Just be sure to address the issues!
|
||||||
|
|
||||||
|
This policy ensures that PRs awaiting review are up to date and ready to merge.
|
||||||
|
|
||||||
|
### Merge conflicts and rebasing
|
||||||
Some game updates introduce significant breaking changes that may create merge conflicts in your PR. To resolve them, you will need to update or rebase your PR.
|
Some game updates introduce significant breaking changes that may create merge conflicts in your PR. To resolve them, you will need to update or rebase your PR.
|
||||||
|
|
||||||
Most merge conflicts are small and will only require you to modify a few files to resolve them.
|
Most merge conflicts are small and will only require you to modify a few files to resolve them.
|
||||||
However, some changes are so big that your commit history will look like a mess!
|
However, some changes are so big that your commit history will be a total mess!
|
||||||
In this case, you will have to perform a [**rebase**](https://docs.github.com/en/get-started/using-git/about-git-rebase).
|
In this case, you will have to perform a [**rebase**](https://docs.github.com/en/get-started/using-git/about-git-rebase).
|
||||||
This process reapplies your changes on top of the updated branch and cleanly resolves the merge conflicts.
|
This process reapplies your changes on top of the updated branch and cleanly resolves the merge conflicts.
|
||||||
|
|
||||||
@@ -165,85 +184,173 @@ This process reapplies your changes on top of the updated branch and cleanly res
|
|||||||
> This guide does not cover compiling. If you have trouble compiling the game, refer to the [Compilation Guide](https://github.com/FunkinCrew/Funkin/blob/main/docs/COMPILING.md).
|
> This guide does not cover compiling. If you have trouble compiling the game, refer to the [Compilation Guide](https://github.com/FunkinCrew/Funkin/blob/main/docs/COMPILING.md).
|
||||||
|
|
||||||
Code-based PRs make changes such as **fixing bugs** or **implementing new features** in the game.
|
Code-based PRs make changes such as **fixing bugs** or **implementing new features** in the game.
|
||||||
|
|
||||||
This involves modifying one or several of the repository’s `.hx` files, found within the `source/` folder.
|
This involves modifying one or several of the repository’s `.hx` files, found within the `source/` folder.
|
||||||
|
|
||||||
### Codestyle
|
### Codestyle
|
||||||
Before submitting your PR, check that your code follows the [Style Guide](https://github.com/FunkinCrew/Funkin/blob/main/docs/style-guide.md).
|
Before submitting your PR, check that your code follows the [Style Guide](https://github.com/FunkinCrew/Funkin/blob/main/docs/style-guide.md). This keeps your code consistent with the rest of the codebase!
|
||||||
|
|
||||||
|
### Code comments
|
||||||
|
Code comments help others understand your changes, so the way you write them is important!
|
||||||
Here are some guidelines for writing comments in your code:
|
Here are some guidelines for writing comments in your code:
|
||||||
- Leave comments only when you believe a piece of code warrants explanation.
|
- Leave comments only when you believe a piece of code warrants explanation. If a piece of code is self-explanatory, it does not need a comment.
|
||||||
- Ensure that your comments provide meaningful insight into the function or purpose of the code.
|
- Ensure that your comments provide meaningful insight into the function or purpose of the code.
|
||||||
- Write your comments in a clear and concise manner.
|
- Write your comments in a clear and concise manner.
|
||||||
- Only sign your comments with your name when your changes are complex and may require further explanation.
|
- Only sign your comments with your name when your changes are complex and may require further explanation.
|
||||||
|
|
||||||
### Example Comments
|
### Example code comments
|
||||||
#### DO NOT:
|
#### DO NOT:
|
||||||
|
Below are examples of what you SHOULD NOT do when writing code comments.
|
||||||
```haxe
|
```haxe
|
||||||
/**
|
/**
|
||||||
* jumps around the song
|
* jumps around the song
|
||||||
* works with bpm changes but skipped notes still hurt
|
* works with bpm changes but skipped notes still hurt
|
||||||
* @param sections how many sections to jump, negative = backwards
|
* @param sections how many sections to jump, negative = backwards
|
||||||
*/
|
*/
|
||||||
function changeSection(sections:Int):Void
|
function changeSection(sections:Int):Void
|
||||||
|
{
|
||||||
|
// Pause the music, as you probably guessed
|
||||||
|
// FlxG.sound.music.pause();
|
||||||
|
|
||||||
|
// Set the target time in steps, I don’t really get how this works though lol - [GitHub username]
|
||||||
|
var targetTimeSteps:Float = Conductor.instance.currentStepTime + (Conductor.instance.stepsPerMeasure * sections);
|
||||||
|
var targetTimeMs:Float = Conductor.instance.getStepTimeInMs(targetTimeSteps);
|
||||||
|
|
||||||
|
// Don't go back in time to before the song started, that would probably break a lot of things and cause a bunch of problems!
|
||||||
|
targetTimeMs = Math.max(0, targetTimeMs);
|
||||||
|
|
||||||
|
if (FlxG.sound.music != null) // If the music is not null, set the time to the target time
|
||||||
{
|
{
|
||||||
// Pause the music, as you probably guessed
|
FlxG.sound.music.time = targetTimeMs;
|
||||||
// FlxG.sound.music.pause();
|
|
||||||
|
|
||||||
// Set the target time in steps, I don’t really get how this works though lol - [GitHub username]
|
|
||||||
var targetTimeSteps:Float = Conductor.instance.currentStepTime + (Conductor.instance.stepsPerMeasure * sections);
|
|
||||||
var targetTimeMs:Float = Conductor.instance.getStepTimeInMs(targetTimeSteps);
|
|
||||||
|
|
||||||
// Don't go back in time to before the song started, that would probably break a lot of things and cause a whole bunch of problems!
|
|
||||||
targetTimeMs = Math.max(0, targetTimeMs);
|
|
||||||
|
|
||||||
if (FlxG.sound.music != null) // If the music is not null, set the time to the target time
|
|
||||||
{
|
|
||||||
FlxG.sound.music.time = targetTimeMs;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle skipped notes and events and all that jazz
|
|
||||||
handleSkippedNotes();
|
|
||||||
SongEventRegistry.handleSkippedEvents(songEvents, Conductor.instance.songPosition);
|
|
||||||
// regenNoteData(FlxG.sound.music.time);
|
|
||||||
|
|
||||||
Conductor.instance.update(FlxG.sound?.music?.time ?? 0.0);
|
|
||||||
|
|
||||||
// I hate this function - [GitHub username]
|
|
||||||
resyncVocals();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle skipped notes and events and all that jazz
|
||||||
|
handleSkippedNotes();
|
||||||
|
SongEventRegistry.handleSkippedEvents(songEvents, Conductor.instance.songPosition);
|
||||||
|
// regenNoteData(FlxG.sound.music.time);
|
||||||
|
|
||||||
|
Conductor.instance.update(FlxG.sound?.music?.time ?? 0.0);
|
||||||
|
|
||||||
|
// I hate this function - [GitHub username]
|
||||||
|
resyncVocals();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```haxe
|
||||||
|
// End the song when the music is complete.
|
||||||
|
FlxG.sound.music.onComplete = function() {
|
||||||
|
endSong(skipEndingTransition);
|
||||||
|
};
|
||||||
|
// A negative instrumental offset means the song skips the first few milliseconds of the track.
|
||||||
|
// This just gets added into the startTimestamp behavior so we don't need to do anything extra.
|
||||||
|
FlxG.sound.music.play(true, Math.max(0, startTimestamp - Conductor.instance.combinedOffset));
|
||||||
|
FlxG.sound.music.pitch = playbackRate;
|
||||||
|
|
||||||
|
// Prevent the volume from being wrong.
|
||||||
|
FlxG.sound.music.volume = 1.0;
|
||||||
|
// IF fadetween is not null we cancel it
|
||||||
|
if (FlxG.sound.music.fadeTween != null) FlxG.sound.music.fadeTween.cancel();
|
||||||
|
|
||||||
|
// Play the vocals
|
||||||
|
trace('Playing vocals...');
|
||||||
|
// Add the vocals
|
||||||
|
add(vocals);
|
||||||
|
// Play the vocals for real this time lol
|
||||||
|
vocals.play();
|
||||||
|
// Set the vocals volume
|
||||||
|
vocals.volume = 1.0;
|
||||||
|
// Set the vocals pitch
|
||||||
|
vocals.pitch = playbackRate;
|
||||||
|
// Set the vocals time to the music time
|
||||||
|
vocals.time = FlxG.sound.music.time;
|
||||||
|
// trace('${FlxG.sound.music.time}');
|
||||||
|
// trace('${vocals.time}');
|
||||||
|
// functionThatWasntHereBeforeThisPRorSomethingIdKLOL();
|
||||||
|
/* testsprite = new FlxSprite(0, 0);
|
||||||
|
testsprite.loadGraphic(Paths.image('test'));
|
||||||
|
testsprite.screenCenter();
|
||||||
|
add(testsprite); */
|
||||||
|
|
||||||
|
// Me too [GitHub username] I hate this function it gave me pain and suffering - Girlfriend
|
||||||
|
resyncVocals();
|
||||||
|
```
|
||||||
|
|
||||||
|
```haxe
|
||||||
|
#if FEATURE_DEBUG_FUNCTIONS
|
||||||
|
// PAGEUP: who knows what this does
|
||||||
|
// SHIFT+PAGEUP: There will be dire consequences.
|
||||||
|
if (FlxG.keys.justPressed.PAGEUP) changeSection(FlxG.keys.pressed.SHIFT ? 20 : 2);
|
||||||
|
// PAGEUP: who knows what this does
|
||||||
|
// SHIFT+PAGEUP: There will be dire consequences.
|
||||||
|
if (FlxG.keys.justPressed.PAGEDOWN) changeSection(FlxG.keys.pressed.SHIFT ? -20 : -2);
|
||||||
|
#end
|
||||||
```
|
```
|
||||||
|
|
||||||
#### DO:
|
#### DO:
|
||||||
|
Below are examples on what you SHOULD do when writing code comments.
|
||||||
```haxe
|
```haxe
|
||||||
/**
|
/**
|
||||||
* Jumps forward or backward a number of sections in the song.
|
* Jumps forward or backward a number of sections in the song.
|
||||||
* Accounts for BPM changes, does not prevent death from skipped notes.
|
* Accounts for BPM changes, does not prevent death from skipped notes.
|
||||||
* @param sections The number of sections to jump, negative to go backwards.
|
* @param sections The number of sections to jump, negative to go backwards.
|
||||||
*/
|
*/
|
||||||
function changeSection(sections:Int):Void
|
function changeSection(sections:Int):Void
|
||||||
|
{
|
||||||
|
var targetTimeSteps:Float = Conductor.instance.currentStepTime + (Conductor.instance.stepsPerMeasure * sections);
|
||||||
|
var targetTimeMs:Float = Conductor.instance.getStepTimeInMs(targetTimeSteps);
|
||||||
|
|
||||||
|
// Don't go back in time to before the song started.
|
||||||
|
targetTimeMs = Math.max(0, targetTimeMs);
|
||||||
|
|
||||||
|
if (FlxG.sound.music != null)
|
||||||
{
|
{
|
||||||
var targetTimeSteps:Float = Conductor.instance.currentStepTime + (Conductor.instance.stepsPerMeasure * sections);
|
FlxG.sound.music.time = targetTimeMs;
|
||||||
var targetTimeMs:Float = Conductor.instance.getStepTimeInMs(targetTimeSteps);
|
|
||||||
|
|
||||||
// Don't go back in time to before the song started.
|
|
||||||
targetTimeMs = Math.max(0, targetTimeMs);
|
|
||||||
|
|
||||||
if (FlxG.sound.music != null)
|
|
||||||
{
|
|
||||||
FlxG.sound.music.time = targetTimeMs;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSkippedNotes();
|
|
||||||
SongEventRegistry.handleSkippedEvents(songEvents, Conductor.instance.songPosition);
|
|
||||||
|
|
||||||
Conductor.instance.update(FlxG.sound?.music?.time ?? 0.0);
|
|
||||||
|
|
||||||
resyncVocals();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleSkippedNotes();
|
||||||
|
SongEventRegistry.handleSkippedEvents(songEvents, Conductor.instance.songPosition);
|
||||||
|
|
||||||
|
Conductor.instance.update(FlxG.sound?.music?.time ?? 0.0);
|
||||||
|
|
||||||
|
resyncVocals();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```haxe
|
||||||
|
FlxG.sound.music.onComplete = function() {
|
||||||
|
endSong(skipEndingTransition);
|
||||||
|
};
|
||||||
|
// A negative instrumental offset means the song skips the first few milliseconds of the track.
|
||||||
|
// This just gets added into the startTimestamp behavior so we don't need to do anything extra.
|
||||||
|
FlxG.sound.music.play(true, Math.max(0, startTimestamp - Conductor.instance.combinedOffset));
|
||||||
|
FlxG.sound.music.pitch = playbackRate;
|
||||||
|
|
||||||
|
// Prevent the volume from being wrong.
|
||||||
|
FlxG.sound.music.volume = 1.0;
|
||||||
|
if (FlxG.sound.music.fadeTween != null) FlxG.sound.music.fadeTween.cancel();
|
||||||
|
|
||||||
|
trace('Playing vocals...');
|
||||||
|
add(vocals);
|
||||||
|
vocals.play();
|
||||||
|
vocals.volume = 1.0;
|
||||||
|
vocals.pitch = playbackRate;
|
||||||
|
vocals.time = FlxG.sound.music.time;
|
||||||
|
resyncVocals();
|
||||||
|
```
|
||||||
|
|
||||||
|
```haxe
|
||||||
|
#if FEATURE_DEBUG_FUNCTIONS
|
||||||
|
// PAGEUP: Skip forward two sections.
|
||||||
|
// SHIFT+PAGEUP: Skip forward twenty sections.
|
||||||
|
if (FlxG.keys.justPressed.PAGEUP) changeSection(FlxG.keys.pressed.SHIFT ? 20 : 2);
|
||||||
|
// PAGEDOWN: Skip backward two section. Doesn't replace notes.
|
||||||
|
// SHIFT+PAGEDOWN: Skip backward twenty sections.
|
||||||
|
if (FlxG.keys.justPressed.PAGEDOWN) changeSection(FlxG.keys.pressed.SHIFT ? -20 : -2);
|
||||||
|
#end
|
||||||
```
|
```
|
||||||
|
|
||||||
## Documentation PRs
|
## Documentation PRs
|
||||||
Documentation-based PRs make changes such as **fixing typos** or **adding new information** in documentation files.
|
Documentation-based PRs make changes such as **fixing typos** or **adding new information** in documentation files.
|
||||||
|
|
||||||
This involves modifying one or several of the repository’s `.md` files, found throughout the repository.
|
This involves modifying one or several of the repository’s `.md` files, found throughout the repository.
|
||||||
|
|
||||||
@@ -297,6 +404,17 @@ If you simultaneously modify files from both repositories, then open two separat
|
|||||||
|
|
||||||
Be sure to choose `main` as the base branch for `funkin.assets` PRs, as no `develop` branch exists for that repository.
|
Be sure to choose `main` as the base branch for `funkin.assets` PRs, as no `develop` branch exists for that repository.
|
||||||
|
|
||||||
|
### Charting PRs
|
||||||
|
Charting PRs make changes such as **adjusting chart metadata**.
|
||||||
|
|
||||||
|
This involves modifying one or several of the `funkin.assets` repository's `*-metadata.json` files, found in the `preload/data/songs/` directory.
|
||||||
|
|
||||||
|
> [!CAUTION]
|
||||||
|
> We **do not** accept changes to the chart `.json` file itself.
|
||||||
|
> If you find an error in the game's charts, please open an issue in the `Funkin` repo, and the Funkin' Crew will review it and make tweaks themselves as necessary.
|
||||||
|
|
||||||
|
These PRs should only be opened in the `funkin.assets` repository.
|
||||||
|
|
||||||
# Closing
|
# Closing
|
||||||
Thank you for reading the Contributing Guide.
|
Thank you for reading the Contributing Guide.
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
Most of this functionality is only available on debug builds of the game!
|
Most of this functionality is only available on debug builds of the game!
|
||||||
|
|
||||||
## Any State
|
## Any State
|
||||||
|
- `INSERT`: ***MEMORY GARBAGE COLLECTION***: Immediately perform memory garbage collection.
|
||||||
- `F2`: ***OVERLAY***: Enables the Flixel debug overlay, which has partial support for scripting.
|
- `F2`: ***OVERLAY***: Enables the Flixel debug overlay, which has partial support for scripting.
|
||||||
- `F3`: ***SCREENSHOT***: Takes a screenshot of the game and saves it to the local `screenshots` directory. Works outside of debug builds too!
|
- `F3`: ***SCREENSHOT***: Takes a screenshot of the game and saves it to the local `screenshots` directory. Works outside of debug builds too!
|
||||||
- `F4`: ***EJECT***: Forcibly switch state to the Main Menu (with no extra transition). Useful if you're stuck in a level and you need to get out!
|
- `F4`: ***EJECT***: Forcibly switch state to the Main Menu (with no extra transition). Useful if you're stuck in a level and you need to get out!
|
||||||
@@ -19,20 +20,27 @@ Most of this functionality is only available on debug builds of the game!
|
|||||||
- `9`: NEATO!
|
- `9`: NEATO!
|
||||||
- `PAGEUP` (MacOS: `Fn-Up`): ***FORWARDS TIME TRAVEL***: Move forward by 2 sections. Hold SHIFT to move forward by 20 sections instead.
|
- `PAGEUP` (MacOS: `Fn-Up`): ***FORWARDS TIME TRAVEL***: Move forward by 2 sections. Hold SHIFT to move forward by 20 sections instead.
|
||||||
- `PAGEDOWN` (MacOS: `Fn-Down`): ***BACKWARDS TIME TRAVEL***: Move backward by 2 sections. Hold SHIFT to move backward by 20 sections instead.
|
- `PAGEDOWN` (MacOS: `Fn-Down`): ***BACKWARDS TIME TRAVEL***: Move backward by 2 sections. Hold SHIFT to move backward by 20 sections instead.
|
||||||
|
- `CHART Hotkey`: ***OPEN CHART EDITOR***: Open the Chart Editor with the current song loaded.
|
||||||
|
- `STAGE Hotkey`: ***OPEN STAGE EDITOR***: Open the old Stage Editor overlay.
|
||||||
|
|
||||||
## **Freeplay State**
|
## **Freeplay State**
|
||||||
- `F` (Freeplay Menu) - Move to Favorites
|
- `P`: ***SWITCH TO PICO/BF***: Switch to Pico or BF (reopens Freeplay as the other character).
|
||||||
- `Q` (Freeplay Menu) - Back one category
|
- `T`: ***TEST RANK ANIMATION***: Play the Gold Perfect rank animation on the currently selected song.
|
||||||
- `E` (Freeplay Menu) - Forward one category
|
- `SHIFT-ACCEPT`: ***BOTPLAY MODE***: Start a song with Botplay mode enabled.
|
||||||
|
- `CTRL-Switch Song`: Play the first unlocked alternate instrumental as the song preview.
|
||||||
|
|
||||||
## **Title State**
|
## **Title State**
|
||||||
- `Y` - WOAH
|
- `ESCAPE`: ***QUIT***: Quit the game.
|
||||||
|
- `D`: COOL
|
||||||
|
- `UI LEFT`/`UI RIGHT Hotkey`: 2COOL
|
||||||
|
- `Y`: WOAH
|
||||||
|
|
||||||
## **Main Menu**
|
## **Main Menu**
|
||||||
- `~`: ***DEBUG***: Opens a menu to access the Chart Editor and other work-in-progress editors. Rebindable in the options menu.
|
- `~`: ***DEBUG***: Opens a menu to access the Chart Editor and other work-in-progress editors. Rebindable in the options menu.
|
||||||
|
- `SHIFT-ACCEPT`: ***OPEN FREEPLAY AS PICO***: When selecting Freeplay, open the Freeplay menu with the playable character set to Pico.
|
||||||
- `CTRL-ALT-SHIFT-W`: ***ALL ACCESS***: Unlocks all songs in Freeplay. Only available on debug builds.
|
- `CTRL-ALT-SHIFT-W`: ***ALL ACCESS***: Unlocks all songs in Freeplay. Only available on debug builds.
|
||||||
- `CTRL-ALT-SHIFT-M`: ***NO MORE ACCESS***: Re-locks all songs in Freeplay except those unlocked by default. Only available on debug builds.
|
- `CTRL-ALT-SHIFT-M`: ***NO MORE ACCESS***: Re-locks all songs in Freeplay except those unlocked by default. Only available on debug builds.
|
||||||
- `CTRL-ALT-SHIFT-R`: ***GREAT SCORE?***: Give the user a hypothetical overridden score, and see if we can maintain that golden P rank. Only available on debug builds.
|
- `CTRL-ALT-SHIFT-R`: ***GREAT SCORE?***: Give the user a hypothetical overridden score, and see if we can maintain that golden P rank. Only available on debug builds.
|
||||||
- `CTRL-ALT-SHIFT-P`: ***CHARACTER UNLOCK SCREEN***: Forces the Character Select screen to play Pico's unlocking animation. Only available on debug builds.
|
- `CTRL-ALT-SHIFT-P`: ***CHARACTER UNLOCK SCREEN***: Forces the Character Select screen to play Pico's unlocking animation. Only available on debug builds.
|
||||||
- `CTRL-ALT-SHIFT-N`: ***CHARACTER NOT SEEN***: Marks all characters as not seen and enables BF's new character unlocked animation in Freeplay. Only available on debug builds.
|
- `CTRL-ALT-SHIFT-N`: ***CHARACTER NOT SEEN***: Marks all characters as not seen and enables BF's new character unlocked animation in Freeplay. Only available on debug builds.
|
||||||
- `CTRL-ALT-SHIFT-E`: ***DUMP SAVE DATA***: Prompts the user to save their save data as a JSON file, so its contents can be viewed. Only available on debug builds.
|
- `CTRL-ALT-SHIFT-E`: ***DUMP SAVE DATA***: Prompts the user to save their save data as a JSON file, so its contents can be viewed. Only available on debug builds.
|
||||||
|
|||||||
@@ -36,7 +36,8 @@
|
|||||||
(NOTE: Make sure you do this via `hmm` (e.g `hmm reinstall -f lime`) to guarantee you get Funkin's version of Lime.)
|
(NOTE: Make sure you do this via `hmm` (e.g `hmm reinstall -f lime`) to guarantee you get Funkin's version of Lime.)
|
||||||
|
|
||||||
- `Uncaught exception - Could not find lime.ndll.` ... `Advanced users may run "lime rebuild cpp" instead.`
|
- `Uncaught exception - Could not find lime.ndll.` ... `Advanced users may run "lime rebuild cpp" instead.`
|
||||||
- Usually specific to Linux. Running the commands below should fix it.
|
- If on Linux:
|
||||||
|
- The binaries GLibC version might more recent than the one your system supports. Running the commands below should fix it.
|
||||||
```
|
```
|
||||||
cd .haxelib/lime/git
|
cd .haxelib/lime/git
|
||||||
git submodule init
|
git submodule init
|
||||||
@@ -47,4 +48,6 @@
|
|||||||
sudo apt install libgl1-mesa-dev libglu1-mesa-dev g++ g++-multilib gcc-multilib libasound2-dev libx11-dev libxext-dev libxi-dev libxrandr-dev libxinerama-dev libpulse-dev
|
sudo apt install libgl1-mesa-dev libglu1-mesa-dev g++ g++-multilib gcc-multilib libasound2-dev libx11-dev libxext-dev libxi-dev libxrandr-dev libxinerama-dev libpulse-dev
|
||||||
lime rebuild cpp -64 -release -clean
|
lime rebuild cpp -64 -release -clean
|
||||||
```
|
```
|
||||||
For Windows or MacOS you can download pre-built binaries from [Funkin's Lime](https://github.com/FunkinCrew/lime/tree/dev-funkin/ndll).
|
|
||||||
|
- The binaries are missing for some reason. You can download pre-built binaries from [Funkin's Lime](https://github.com/FunkinCrew/lime/tree/dev-funkin/ndll).
|
||||||
|
You should copy them to `.haxelib/lime/git/ndll/<PLATFORM>64/`, where `<PLATFORM>` is the current platform you're on, which can be either Windows, Linux or MacOS.
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ This short document is designed to give a run-down on how code should be formatt
|
|||||||
|
|
||||||
## Notes on IDEs and Extensions
|
## Notes on IDEs and Extensions
|
||||||
|
|
||||||
The Visual Studio Code IDE is highly recommended, as this repo contains various configuration that works with VSCode extensions to automatically style certain things for you.
|
The Visual Studio Code IDE is highly recommended, as this repo contains various configurations that work with VSCode extensions to automatically style certain things for you.
|
||||||
|
|
||||||
VSCode is also the only IDE that has any good tools for Haxe so yeah.
|
VSCode is also the only IDE that has any good tools for Haxe so yeah.
|
||||||
|
|
||||||
|
|||||||
@@ -32,8 +32,8 @@
|
|||||||
"name": "flxanimate",
|
"name": "flxanimate",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "713d3de0e566d6cd54cde13711ab3e4f60f59f4b",
|
"ref": "f8842cea9883d6112a2c854cf93fa72856171428",
|
||||||
"url": "https://github.com/Dot-Stuff/flxanimate"
|
"url": "https://github.com/FunkinCrew/flxanimate"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "format",
|
"name": "format",
|
||||||
@@ -63,14 +63,14 @@
|
|||||||
"name": "haxeui-core",
|
"name": "haxeui-core",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "74ba53387eab0c4c2d3825e103fe70df8e46d9b1",
|
"ref": "07fc7aa8098deaea633b0726d01f83eb4ef8a832",
|
||||||
"url": "https://github.com/haxeui/haxeui-core"
|
"url": "https://github.com/haxeui/haxeui-core"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "haxeui-flixel",
|
"name": "haxeui-flixel",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "34fe148e45292439a9c3caf00890c93bcc6f9453",
|
"ref": "b899a4c7d7318c5ff2b1bb645fbc73728fad1ac9",
|
||||||
"url": "https://github.com/haxeui/haxeui-flixel"
|
"url": "https://github.com/haxeui/haxeui-flixel"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -116,7 +116,7 @@
|
|||||||
{
|
{
|
||||||
"name": "hxvlc",
|
"name": "hxvlc",
|
||||||
"type": "haxelib",
|
"type": "haxelib",
|
||||||
"version": "2.0.1"
|
"version": "2.1.2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "json2object",
|
"name": "json2object",
|
||||||
@@ -143,7 +143,7 @@
|
|||||||
"name": "lime",
|
"name": "lime",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "d1322e60f97b5c6e977f9e3e8a04f22b5190e7d9",
|
"ref": "be81ad7e4e1a92c3482bcc009648a4ac892cfa35",
|
||||||
"url": "https://github.com/FunkinCrew/lime"
|
"url": "https://github.com/FunkinCrew/lime"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
+2
-3
@@ -25,7 +25,7 @@ class Project extends HXProject {
|
|||||||
* REMEMBER TO CHANGE THIS WHEN THE GAME UPDATES!
|
* REMEMBER TO CHANGE THIS WHEN THE GAME UPDATES!
|
||||||
* You only have to change it here, the rest of the game will query this value.
|
* You only have to change it here, the rest of the game will query this value.
|
||||||
*/
|
*/
|
||||||
static final VERSION:String = "0.6.2";
|
static final VERSION:String = "0.6.4";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The game's name. Used as the default window title.
|
* The game's name. Used as the default window title.
|
||||||
@@ -773,13 +773,12 @@ class Project extends HXProject {
|
|||||||
addAssetLibrary("weekend1", shouldEmbed, shouldPreload);
|
addAssetLibrary("weekend1", shouldEmbed, shouldPreload);
|
||||||
addAssetPath("assets/weekend1", "assets/weekend1", "weekend1", ["*"], exclude, shouldEmbed);
|
addAssetPath("assets/weekend1", "assets/weekend1", "weekend1", ["*"], exclude, shouldEmbed);
|
||||||
|
|
||||||
// Art asset library (where README and CHANGELOG pull from)
|
// Art asset library (where README pulls from)
|
||||||
var shouldEmbedArt = false;
|
var shouldEmbedArt = false;
|
||||||
var shouldPreloadArt = false;
|
var shouldPreloadArt = false;
|
||||||
addAssetLibrary("art", shouldEmbedArt, shouldPreloadArt);
|
addAssetLibrary("art", shouldEmbedArt, shouldPreloadArt);
|
||||||
addAsset("art/readme.txt", "do NOT readme.txt", "art", shouldEmbedArt);
|
addAsset("art/readme.txt", "do NOT readme.txt", "art", shouldEmbedArt);
|
||||||
addAsset("LICENSE.md", "LICENSE.md", "art", shouldEmbedArt);
|
addAsset("LICENSE.md", "LICENSE.md", "art", shouldEmbedArt);
|
||||||
addAsset("CHANGELOG.md", "CHANGELOG.md", "art", shouldEmbedArt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+12
-6
@@ -13,6 +13,7 @@ import openfl.events.Event;
|
|||||||
import openfl.Lib;
|
import openfl.Lib;
|
||||||
import openfl.media.Video;
|
import openfl.media.Video;
|
||||||
import openfl.net.NetStream;
|
import openfl.net.NetStream;
|
||||||
|
import funkin.util.WindowUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The main class which initializes HaxeFlixel and starts the game in its initial state.
|
* The main class which initializes HaxeFlixel and starts the game in its initial state.
|
||||||
@@ -61,12 +62,6 @@ class Main extends Sprite
|
|||||||
|
|
||||||
function init(?event:Event):Void
|
function init(?event:Event):Void
|
||||||
{
|
{
|
||||||
#if web
|
|
||||||
// set this variable (which is a function) from the lime version at lime/_internal/backend/html5/HTML5Application.hx
|
|
||||||
// The framerate cap will more thoroughly initialize via Preferences in InitState.hx
|
|
||||||
funkin.Preferences.lockedFramerateFunction = untyped js.Syntax.code("window.requestAnimationFrame");
|
|
||||||
#end
|
|
||||||
|
|
||||||
if (hasEventListener(Event.ADDED_TO_STAGE))
|
if (hasEventListener(Event.ADDED_TO_STAGE))
|
||||||
{
|
{
|
||||||
removeEventListener(Event.ADDED_TO_STAGE, init);
|
removeEventListener(Event.ADDED_TO_STAGE, init);
|
||||||
@@ -104,6 +99,17 @@ class Main extends Sprite
|
|||||||
|
|
||||||
// George recommends binding the save before FlxGame is created.
|
// George recommends binding the save before FlxGame is created.
|
||||||
Save.load();
|
Save.load();
|
||||||
|
|
||||||
|
// Don't call anything from the preferences until the save is loaded!
|
||||||
|
#if web
|
||||||
|
// set this variable (which is a function) from the lime version at lime/_internal/backend/html5/HTML5Application.hx
|
||||||
|
// The framerate cap will more thoroughly initialize via Preferences in InitState.hx
|
||||||
|
funkin.Preferences.lockedFramerateFunction = untyped js.Syntax.code("window.requestAnimationFrame");
|
||||||
|
#end
|
||||||
|
|
||||||
|
WindowUtil.setVSyncMode(funkin.Preferences.vsyncMode);
|
||||||
|
|
||||||
|
|
||||||
var game:FlxGame = new FlxGame(gameWidth, gameHeight, initialState, Preferences.framerate, Preferences.framerate, skipSplash, startFullscreen);
|
var game:FlxGame = new FlxGame(gameWidth, gameHeight, initialState, Preferences.framerate, Preferences.framerate, skipSplash, startFullscreen);
|
||||||
|
|
||||||
// FlxG.game._customSoundTray wants just the class, it calls new from
|
// FlxG.game._customSoundTray wants just the class, it calls new from
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import openfl.utils.Future;
|
|||||||
* A wrapper around `openfl.utils.Assets` which disallows access to the harmful functions.
|
* A wrapper around `openfl.utils.Assets` which disallows access to the harmful functions.
|
||||||
* Later we'll add Funkin-specific caching to this.
|
* Later we'll add Funkin-specific caching to this.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class Assets
|
class Assets
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import funkin.data.freeplay.player.PlayerRegistry;
|
|||||||
import funkin.data.freeplay.style.FreeplayStyleRegistry;
|
import funkin.data.freeplay.style.FreeplayStyleRegistry;
|
||||||
import funkin.data.notestyle.NoteStyleRegistry;
|
import funkin.data.notestyle.NoteStyleRegistry;
|
||||||
import funkin.data.song.SongRegistry;
|
import funkin.data.song.SongRegistry;
|
||||||
|
import funkin.data.stickers.StickerRegistry;
|
||||||
import funkin.data.event.SongEventRegistry;
|
import funkin.data.event.SongEventRegistry;
|
||||||
import funkin.data.stage.StageRegistry;
|
import funkin.data.stage.StageRegistry;
|
||||||
import funkin.data.story.level.LevelRegistry;
|
import funkin.data.story.level.LevelRegistry;
|
||||||
@@ -97,9 +98,18 @@ class InitState extends FlxState
|
|||||||
FlxG.sound.volumeDownKeys = [];
|
FlxG.sound.volumeDownKeys = [];
|
||||||
FlxG.sound.muteKeys = [];
|
FlxG.sound.muteKeys = [];
|
||||||
|
|
||||||
|
// A small jumpstart to the soundtray, it usually sets itself to inactive (somewhere...)
|
||||||
|
// but that makes our soundtray not show up on init if we have the game muted.
|
||||||
|
// We set it to active so it at least calls it's update function once (see FlxGame.onEnterFrame(), it's called there)
|
||||||
|
// and also see FunkinSoundTray.update() to see what we do and how we check if we are muted or not
|
||||||
|
FlxG.game.soundTray.active = true;
|
||||||
|
|
||||||
// Set the game to a lower frame rate while it is in the background.
|
// Set the game to a lower frame rate while it is in the background.
|
||||||
FlxG.game.focusLostFramerate = 30;
|
FlxG.game.focusLostFramerate = 30;
|
||||||
|
|
||||||
|
// Makes Flixel use frame times instead of locked movements per frame for things like tweens
|
||||||
|
FlxG.fixedTimestep = false;
|
||||||
|
|
||||||
setupFlixelDebug();
|
setupFlixelDebug();
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -184,6 +194,7 @@ class InitState extends FlxState
|
|||||||
FreeplayStyleRegistry.instance.loadEntries();
|
FreeplayStyleRegistry.instance.loadEntries();
|
||||||
AlbumRegistry.instance.loadEntries();
|
AlbumRegistry.instance.loadEntries();
|
||||||
StageRegistry.instance.loadEntries();
|
StageRegistry.instance.loadEntries();
|
||||||
|
StickerRegistry.instance.loadEntries();
|
||||||
|
|
||||||
// TODO: CharacterDataParser doesn't use json2object, so it's way slower than the other parsers and more prone to syntax errors.
|
// TODO: CharacterDataParser doesn't use json2object, so it's way slower than the other parsers and more prone to syntax errors.
|
||||||
// Move it to use a BaseRegistry.
|
// Move it to use a BaseRegistry.
|
||||||
@@ -241,7 +252,7 @@ class InitState extends FlxState
|
|||||||
title: "Cum Song Erect by Kawai Sprite",
|
title: "Cum Song Erect by Kawai Sprite",
|
||||||
songId: "cum",
|
songId: "cum",
|
||||||
characterId: "pico",
|
characterId: "pico",
|
||||||
difficultyId: "nightmare",
|
difficultyId: "hard",
|
||||||
isNewHighscore: true,
|
isNewHighscore: true,
|
||||||
scoreData:
|
scoreData:
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package funkin;
|
package funkin;
|
||||||
|
|
||||||
import funkin.save.Save;
|
import funkin.save.Save;
|
||||||
|
import funkin.util.WindowUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A core class which provides a store of user-configurable, globally relevant values.
|
* A core class which provides a store of user-configurable, globally relevant values.
|
||||||
@@ -176,6 +177,53 @@ class Preferences
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If enabled, the game will utilize VSync (or adaptive VSync) on startup.
|
||||||
|
* @default `OFF`
|
||||||
|
*/
|
||||||
|
public static var vsyncMode(get, set):lime.ui.WindowVSyncMode;
|
||||||
|
|
||||||
|
static function get_vsyncMode():lime.ui.WindowVSyncMode
|
||||||
|
{
|
||||||
|
var value = Save?.instance?.options?.vsyncMode ?? "Off";
|
||||||
|
|
||||||
|
return switch (value)
|
||||||
|
{
|
||||||
|
case "Off":
|
||||||
|
lime.ui.WindowVSyncMode.OFF;
|
||||||
|
case "On":
|
||||||
|
lime.ui.WindowVSyncMode.ON;
|
||||||
|
case "Adaptive":
|
||||||
|
lime.ui.WindowVSyncMode.ADAPTIVE;
|
||||||
|
default:
|
||||||
|
lime.ui.WindowVSyncMode.OFF;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static function set_vsyncMode(value:lime.ui.WindowVSyncMode):lime.ui.WindowVSyncMode
|
||||||
|
{
|
||||||
|
var string;
|
||||||
|
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case lime.ui.WindowVSyncMode.OFF:
|
||||||
|
string = "Off";
|
||||||
|
case lime.ui.WindowVSyncMode.ON:
|
||||||
|
string = "On";
|
||||||
|
case lime.ui.WindowVSyncMode.ADAPTIVE:
|
||||||
|
string = "Adaptive";
|
||||||
|
default:
|
||||||
|
string = "Off";
|
||||||
|
};
|
||||||
|
|
||||||
|
WindowUtil.setVSyncMode(value);
|
||||||
|
|
||||||
|
var save:Save = Save.instance;
|
||||||
|
save.options.vsyncMode = string;
|
||||||
|
save.flush();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
public static var unlockedFramerate(get, set):Bool;
|
public static var unlockedFramerate(get, set):Bool;
|
||||||
|
|
||||||
static function get_unlockedFramerate():Bool
|
static function get_unlockedFramerate():Bool
|
||||||
@@ -340,6 +388,7 @@ class Preferences
|
|||||||
{
|
{
|
||||||
// Apply the autoPause setting (enables automatic pausing on focus lost).
|
// Apply the autoPause setting (enables automatic pausing on focus lost).
|
||||||
FlxG.autoPause = Preferences.autoPause;
|
FlxG.autoPause = Preferences.autoPause;
|
||||||
|
// WindowUtil.setVSyncMode(Preferences.vsyncMode);
|
||||||
// Apply the debugDisplay setting (enables the FPS and RAM display).
|
// Apply the debugDisplay setting (enables the FPS and RAM display).
|
||||||
toggleDebugDisplay(Preferences.debugDisplay);
|
toggleDebugDisplay(Preferences.debugDisplay);
|
||||||
#if web
|
#if web
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import io.newgrounds.objects.events.Result;
|
|||||||
/**
|
/**
|
||||||
* Use Newgrounds to perform basic telemetry. Ignore if not logged in to Newgrounds.
|
* Use Newgrounds to perform basic telemetry. Ignore if not logged in to Newgrounds.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class Events
|
class Events
|
||||||
{
|
{
|
||||||
// Only allow letters, numbers, spaces, dashes, and underscores.
|
// Only allow letters, numbers, spaces, dashes, and underscores.
|
||||||
|
|||||||
@@ -4,29 +4,33 @@ package funkin.api.newgrounds;
|
|||||||
import io.newgrounds.Call.CallError;
|
import io.newgrounds.Call.CallError;
|
||||||
import io.newgrounds.objects.ScoreBoard as LeaderboardData;
|
import io.newgrounds.objects.ScoreBoard as LeaderboardData;
|
||||||
import io.newgrounds.objects.events.Outcome;
|
import io.newgrounds.objects.events.Outcome;
|
||||||
|
import io.newgrounds.utils.ScoreBoardList;
|
||||||
|
|
||||||
class Leaderboards
|
class Leaderboards
|
||||||
{
|
{
|
||||||
public static function listLeaderboardData():Map<Leaderboard, LeaderboardData>
|
public static function listLeaderboardData():Map<Leaderboard, LeaderboardData>
|
||||||
{
|
{
|
||||||
if (NewgroundsClient.instance.leaderboards == null)
|
var leaderboardList:Null<ScoreBoardList> = NewgroundsClient.instance.leaderboards;
|
||||||
|
if (leaderboardList == null)
|
||||||
{
|
{
|
||||||
trace('[NEWGROUNDS] Not logged in, cannot fetch medal data!');
|
trace('[NEWGROUNDS] Not logged in, cannot fetch medal data!');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
else
|
||||||
var result:Map<Leaderboard, LeaderboardData> = [];
|
|
||||||
|
|
||||||
for (leaderboardId in NewgroundsClient.instance.leaderboards.keys())
|
|
||||||
{
|
{
|
||||||
var leaderboardData = NewgroundsClient.instance.leaderboards.get(leaderboardId);
|
var result:Map<Leaderboard, LeaderboardData> = [];
|
||||||
if (leaderboardData == null) continue;
|
|
||||||
|
|
||||||
// A little hacky, but it works.
|
for (leaderboardId in leaderboardList.keys())
|
||||||
result.set(cast leaderboardId, leaderboardData);
|
{
|
||||||
|
var leaderboardData = leaderboardList.get(leaderboardId);
|
||||||
|
if (leaderboardData == null) continue;
|
||||||
|
|
||||||
|
// A little hacky, but it works.
|
||||||
|
result.set(cast leaderboardId, leaderboardData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,7 +46,10 @@ class Leaderboards
|
|||||||
|
|
||||||
if (NewgroundsClient.instance.isLoggedIn())
|
if (NewgroundsClient.instance.isLoggedIn())
|
||||||
{
|
{
|
||||||
var leaderboardData = NewgroundsClient.instance.leaderboards.get(leaderboard.getId());
|
var leaderboardList = NewgroundsClient.instance.leaderboards;
|
||||||
|
if (leaderboardList == null) return;
|
||||||
|
|
||||||
|
var leaderboardData:Null<LeaderboardData> = leaderboardList.get(leaderboard.getId());
|
||||||
if (leaderboardData != null)
|
if (leaderboardData != null)
|
||||||
{
|
{
|
||||||
leaderboardData.postScore(score, function(outcome:Outcome<CallError>):Void {
|
leaderboardData.postScore(score, function(outcome:Outcome<CallError>):Void {
|
||||||
|
|||||||
@@ -5,54 +5,56 @@ import io.newgrounds.objects.Medal as MedalData;
|
|||||||
import funkin.util.plugins.NewgroundsMedalPlugin;
|
import funkin.util.plugins.NewgroundsMedalPlugin;
|
||||||
import flixel.graphics.FlxGraphic;
|
import flixel.graphics.FlxGraphic;
|
||||||
import openfl.display.BitmapData;
|
import openfl.display.BitmapData;
|
||||||
|
import io.newgrounds.utils.MedalList;
|
||||||
import haxe.Json;
|
import haxe.Json;
|
||||||
|
|
||||||
class Medals
|
class Medals
|
||||||
{
|
{
|
||||||
public static var medalJSON:Array<MedalJSON>;
|
public static var medalJSON:Array<MedalJSON> = [];
|
||||||
|
|
||||||
public static function listMedalData():Map<Medal, MedalData>
|
public static function listMedalData():Map<Medal, MedalData>
|
||||||
{
|
{
|
||||||
if (NewgroundsClient.instance.medals == null)
|
var medalList = NewgroundsClient.instance.medals;
|
||||||
|
|
||||||
|
if (medalList == null)
|
||||||
{
|
{
|
||||||
trace('[NEWGROUNDS] Not logged in, cannot fetch medal data!');
|
trace('[NEWGROUNDS] Not logged in, cannot fetch medal data!');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
else
|
||||||
var result:Map<Medal, MedalData> = [];
|
|
||||||
|
|
||||||
for (medalId in NewgroundsClient.instance.medals.keys())
|
|
||||||
{
|
{
|
||||||
var medalData = NewgroundsClient.instance.medals.get(medalId);
|
// TODO: Why do I have to do this, @:nullSafety is fucked up
|
||||||
if (medalData == null) continue;
|
var result:Map<Medal, MedalData> = [];
|
||||||
|
|
||||||
// A little hacky, but it works.
|
for (medalId in medalList.keys())
|
||||||
result.set(cast medalId, medalData);
|
{
|
||||||
|
var medalData = medalList.get(medalId);
|
||||||
|
if (medalData == null) continue;
|
||||||
|
|
||||||
|
// A little hacky, but it works.
|
||||||
|
result.set(cast medalId, medalData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static function isValid(medalData:MedalData):Bool
|
|
||||||
{
|
|
||||||
// IDK why medalData can exist but _data can be null...
|
|
||||||
// TODO: Move checks to the NG library.
|
|
||||||
@:privateAccess
|
|
||||||
return (medalData != null && medalData._data != null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function award(medal:Medal):Void
|
public static function award(medal:Medal):Void
|
||||||
{
|
{
|
||||||
if (NewgroundsClient.instance.isLoggedIn())
|
if (NewgroundsClient.instance.isLoggedIn())
|
||||||
{
|
{
|
||||||
var medalData = NewgroundsClient.instance.medals.get(medal.getId());
|
var medalList = NewgroundsClient.instance.medals;
|
||||||
if (!isValid(medalData))
|
@:privateAccess
|
||||||
|
if (medalList == null || medalList._map == null) return;
|
||||||
|
|
||||||
|
var medalData:Null<MedalData> = medalList.get(medal.getId());
|
||||||
|
@:privateAccess
|
||||||
|
if (medalData == null || medalData._data == null)
|
||||||
{
|
{
|
||||||
trace('[NEWGROUNDS] Could not retrieve data for medal: ${medal}');
|
trace('[NEWGROUNDS] Could not retrieve data for medal: ${medal}');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
else if (!medalData.unlocked)
|
||||||
if (!medalData.unlocked)
|
|
||||||
{
|
{
|
||||||
trace('[NEWGROUNDS] Awarding medal (${medal}).');
|
trace('[NEWGROUNDS] Awarding medal (${medal}).');
|
||||||
medalData.sendUnlock();
|
medalData.sendUnlock();
|
||||||
@@ -66,12 +68,10 @@ class Medals
|
|||||||
NewgroundsMedalPlugin.play(medalData.value, medalData.name, medalGraphic);
|
NewgroundsMedalPlugin.play(medalData.value, medalData.name, medalGraphic);
|
||||||
});
|
});
|
||||||
#else
|
#else
|
||||||
if (medalJSON == null) loadMedalJSON();
|
if ((medalJSON?.length ?? 0) == 0) loadMedalJSON();
|
||||||
// We have to use a medal image from the game files. We use a Base64 encoded image that NG spits out.
|
// We have to use a medal image from the game files. We use a Base64 encoded image that NG spits out.
|
||||||
// TODO: Wait, don't they give us the medal icon?
|
// TODO: Wait, don't they give us the medal icon?
|
||||||
|
|
||||||
var g:FlxGraphic = null;
|
|
||||||
|
|
||||||
var localMedalData:Null<MedalJSON> = medalJSON.filter(function(jsonMedal) {
|
var localMedalData:Null<MedalJSON> = medalJSON.filter(function(jsonMedal) {
|
||||||
#if FEATURE_NEWGROUNDS_TESTING_MEDALS
|
#if FEATURE_NEWGROUNDS_TESTING_MEDALS
|
||||||
return medal == jsonMedal.idTest;
|
return medal == jsonMedal.idTest;
|
||||||
@@ -86,10 +86,14 @@ class Medals
|
|||||||
// Lime/OpenFL parses it without the included prefix stuff, so we remove it.
|
// Lime/OpenFL parses it without the included prefix stuff, so we remove it.
|
||||||
str = str.replace("data:image/png;base64,", "").trim();
|
str = str.replace("data:image/png;base64,", "").trim();
|
||||||
var bitmapData = BitmapData.fromBase64(str, "image/png");
|
var bitmapData = BitmapData.fromBase64(str, "image/png");
|
||||||
if (str != null) g = FlxGraphic.fromBitmapData(bitmapData);
|
var medalGraphic:Null<FlxGraphic> = null;
|
||||||
g.persist = true;
|
if (str != null)
|
||||||
|
{
|
||||||
|
medalGraphic = FlxGraphic.fromBitmapData(bitmapData);
|
||||||
|
medalGraphic.persist = true;
|
||||||
|
}
|
||||||
|
|
||||||
NewgroundsMedalPlugin.play(medalData.value, medalData.name, g);
|
NewgroundsMedalPlugin.play(medalData.value, medalData.name, medalGraphic);
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -119,7 +123,7 @@ class Medals
|
|||||||
trace('[NEWGROUNDS] Failed to parse local medal data!');
|
trace('[NEWGROUNDS] Failed to parse local medal data!');
|
||||||
for (error in parser.errors)
|
for (error in parser.errors)
|
||||||
funkin.data.DataError.printError(error);
|
funkin.data.DataError.printError(error);
|
||||||
medalJSON = null;
|
medalJSON = [];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -149,6 +149,9 @@ class NewgroundsClient
|
|||||||
Save.instance.ngSessionId = null;
|
Save.instance.ngSessionId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return `true` if the user is logged in to Newgrounds.
|
||||||
|
*/
|
||||||
public function isLoggedIn():Bool
|
public function isLoggedIn():Bool
|
||||||
{
|
{
|
||||||
#if FEATURE_NEWGROUNDS
|
#if FEATURE_NEWGROUNDS
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package funkin.api.newgrounds;
|
||||||
|
|
||||||
|
#if FEATURE_NEWGROUNDS
|
||||||
|
import io.newgrounds.NG;
|
||||||
|
#end
|
||||||
|
import funkin.api.newgrounds.NewgroundsClient;
|
||||||
|
import funkin.util.WindowUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles referrals to links retrieved from the Newgrounds API.
|
||||||
|
*/
|
||||||
|
class Referral
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Opens the referral page with the given name.
|
||||||
|
* @param referralName The name of the referral.
|
||||||
|
* @param fallbackUrl The URL to open if the referral fails or is not available.
|
||||||
|
*/
|
||||||
|
static function doReferral(referralName:String, fallbackUrl:String):Void
|
||||||
|
{
|
||||||
|
#if FEATURE_NEWGROUNDS
|
||||||
|
if (NewgroundsClient.instance.isLoggedIn())
|
||||||
|
{
|
||||||
|
NG.core?.calls.loader.loadReferral(false)
|
||||||
|
.addComponentParameter("referral_name", referralName)
|
||||||
|
.addResponseHandler(response -> {
|
||||||
|
trace(response);
|
||||||
|
if (response.success)
|
||||||
|
{
|
||||||
|
WindowUtil.openURL(response.result.data.url);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WindowUtil.openURL(fallbackUrl);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.send();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WindowUtil.openURL(fallbackUrl);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
WindowUtil.openURL(fallbackUrl);
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the merch referral page.
|
||||||
|
*/
|
||||||
|
public static function doMerchReferral():Void
|
||||||
|
{
|
||||||
|
doReferral("merch_link", Constants.URL_MERCH_FALLBACK);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,10 @@ import funkin.util.tools.ICloneable;
|
|||||||
import funkin.util.flixel.sound.FlxPartialSound;
|
import funkin.util.flixel.sound.FlxPartialSound;
|
||||||
import funkin.Paths.PathsFunction;
|
import funkin.Paths.PathsFunction;
|
||||||
import lime.app.Promise;
|
import lime.app.Promise;
|
||||||
|
import lime.media.AudioSource;
|
||||||
|
import openfl.events.Event;
|
||||||
|
import openfl.media.Sound;
|
||||||
|
import openfl.media.SoundChannel;
|
||||||
import openfl.media.SoundMixer;
|
import openfl.media.SoundMixer;
|
||||||
|
|
||||||
#if (openfl >= "8.0.0")
|
#if (openfl >= "8.0.0")
|
||||||
@@ -108,6 +112,11 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
|||||||
return _waveformData;
|
return _waveformData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, the game will forcefully add this sound's channel to the list of playing sounds.
|
||||||
|
*/
|
||||||
|
public var important:Bool = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Are we in a state where the song should play but time is negative?
|
* Are we in a state where the song should play but time is negative?
|
||||||
*/
|
*/
|
||||||
@@ -145,6 +154,14 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
super.update(elapsedSec);
|
super.update(elapsedSec);
|
||||||
|
|
||||||
|
@:privateAccess
|
||||||
|
{
|
||||||
|
if (important && _channel != null && !SoundMixer.__soundChannels.contains(_channel))
|
||||||
|
{
|
||||||
|
SoundMixer.__soundChannels.push(_channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -431,13 +448,14 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
|||||||
* @param persist Whether to keep this `FunkinSound` between states, or destroy it.
|
* @param persist Whether to keep this `FunkinSound` between states, or destroy it.
|
||||||
* @param onComplete Called when the sound finished playing.
|
* @param onComplete Called when the sound finished playing.
|
||||||
* @param onLoad Called when the sound finished loading. Called immediately for succesfully loaded embedded sounds.
|
* @param onLoad Called when the sound finished loading. Called immediately for succesfully loaded embedded sounds.
|
||||||
|
* @param important If `true`, the sound channel will forcefully be added onto the channel array, even if full. Use sparingly!
|
||||||
* @return A `FunkinSound` object, or `null` if the sound could not be loaded.
|
* @return A `FunkinSound` object, or `null` if the sound could not be loaded.
|
||||||
*/
|
*/
|
||||||
public static function load(embeddedSound:FlxSoundAsset, volume:Float = 1.0, looped:Bool = false, autoDestroy:Bool = false, autoPlay:Bool = false,
|
public static function load(embeddedSound:FlxSoundAsset, volume:Float = 1.0, looped:Bool = false, autoDestroy:Bool = false, autoPlay:Bool = false,
|
||||||
persist:Bool = false, ?onComplete:Void->Void, ?onLoad:Void->Void):Null<FunkinSound>
|
persist:Bool = false, ?onComplete:Void->Void, ?onLoad:Void->Void, important:Bool = false):Null<FunkinSound>
|
||||||
{
|
{
|
||||||
@:privateAccess
|
@:privateAccess
|
||||||
if (SoundMixer.__soundChannels.length >= SoundMixer.MAX_ACTIVE_CHANNELS)
|
if (SoundMixer.__soundChannels.length >= SoundMixer.MAX_ACTIVE_CHANNELS && !important)
|
||||||
{
|
{
|
||||||
FlxG.log.error('FunkinSound could not play sound, channels exhausted! Found ${SoundMixer.__soundChannels.length} active sound channels.');
|
FlxG.log.error('FunkinSound could not play sound, channels exhausted! Found ${SoundMixer.__soundChannels.length} active sound channels.');
|
||||||
return null;
|
return null;
|
||||||
@@ -462,6 +480,7 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
|||||||
sound.volume = volume;
|
sound.volume = volume;
|
||||||
sound.group = FlxG.sound.defaultSoundGroup;
|
sound.group = FlxG.sound.defaultSoundGroup;
|
||||||
sound.persist = persist;
|
sound.persist = persist;
|
||||||
|
sound.important = important;
|
||||||
|
|
||||||
// Make sure to add the sound to the list.
|
// Make sure to add the sound to the list.
|
||||||
// If it's already in, it won't get re-added.
|
// If it's already in, it won't get re-added.
|
||||||
@@ -534,15 +553,50 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
|||||||
this._label = 'unknown';
|
this._label = 'unknown';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@:access(openfl.media.Sound)
|
||||||
|
@:access(openfl.media.SoundChannel)
|
||||||
|
@:access(openfl.media.SoundMixer)
|
||||||
|
override function startSound(startTime:Float)
|
||||||
|
{
|
||||||
|
if (!important)
|
||||||
|
{
|
||||||
|
super.startSound(startTime);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_time = startTime;
|
||||||
|
_paused = false;
|
||||||
|
|
||||||
|
if (_sound == null) return;
|
||||||
|
|
||||||
|
// Create a channel manually if the sound is considered important.
|
||||||
|
var pan:Float = FlxMath.bound(SoundMixer.__soundTransform.pan + _transform.pan, -1, 1);
|
||||||
|
var volume:Float = FlxMath.bound(SoundMixer.__soundTransform.volume * _transform.volume, 0, MAX_VOLUME);
|
||||||
|
|
||||||
|
var audioSource:AudioSource = new AudioSource(_sound.__buffer);
|
||||||
|
audioSource.offset = Std.int(startTime);
|
||||||
|
audioSource.gain = volume;
|
||||||
|
|
||||||
|
var position:lime.math.Vector4 = audioSource.position;
|
||||||
|
position.x = pan;
|
||||||
|
position.z = -1 * Math.sqrt(1 - Math.pow(pan, 2));
|
||||||
|
audioSource.position = position;
|
||||||
|
|
||||||
|
_channel = new SoundChannel(_sound, audioSource, _transform);
|
||||||
|
_channel.addEventListener(Event.SOUND_COMPLETE, stopped);
|
||||||
|
pitch = _pitch;
|
||||||
|
active = true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Play a sound effect once, then destroy it.
|
* Play a sound effect once, then destroy it.
|
||||||
* @param key
|
* @param key
|
||||||
* @param volume
|
* @param volume
|
||||||
* @return A `FunkinSound` object, or `null` if the sound could not be loaded.
|
* @return A `FunkinSound` object, or `null` if the sound could not be loaded.
|
||||||
*/
|
*/
|
||||||
public static function playOnce(key:String, volume:Float = 1.0, ?onComplete:Void->Void, ?onLoad:Void->Void):Null<FunkinSound>
|
public static function playOnce(key:String, volume:Float = 1.0, ?onComplete:Void->Void, ?onLoad:Void->Void, important:Bool = false):Null<FunkinSound>
|
||||||
{
|
{
|
||||||
var result:Null<FunkinSound> = FunkinSound.load(key, volume, false, true, true, false, onComplete, onLoad);
|
var result:Null<FunkinSound> = FunkinSound.load(key, volume, false, true, true, false, onComplete, onLoad, important);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,14 +9,17 @@ import funkin.vis.dsp.SpectralAnalyzer;
|
|||||||
|
|
||||||
using Lambda;
|
using Lambda;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
||||||
{
|
{
|
||||||
// public var vis:VisShit;
|
// public var vis:VisShit;
|
||||||
var analyzer:SpectralAnalyzer;
|
var analyzer:Null<SpectralAnalyzer> = null;
|
||||||
|
|
||||||
var volumes:Array<Float> = [];
|
var volumes:Array<Float> = [];
|
||||||
|
|
||||||
public var snd:FlxSound;
|
public var snd:Null<FlxSound> = null;
|
||||||
|
|
||||||
|
static final BAR_COUNT:Int = 7;
|
||||||
|
|
||||||
// TODO: Make the sprites easier to soft code.
|
// TODO: Make the sprites easier to soft code.
|
||||||
public function new(snd:FlxSound, pixel:Bool)
|
public function new(snd:FlxSound, pixel:Bool)
|
||||||
@@ -25,7 +28,7 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
|||||||
|
|
||||||
this.snd = snd;
|
this.snd = snd;
|
||||||
|
|
||||||
var visCount = pixel ? (7 + 1) : (7 + 1);
|
var visCount = pixel ? (BAR_COUNT + 1) : (BAR_COUNT + 1);
|
||||||
var visScale = pixel ? 6 : 1;
|
var visScale = pixel ? 6 : 1;
|
||||||
|
|
||||||
var visFrms:FlxAtlasFrames = Paths.getSparrowAtlas(pixel ? 'characters/abotPixel/aBotVizPixel' : 'characters/abot/aBotViz');
|
var visFrms:FlxAtlasFrames = Paths.getSparrowAtlas(pixel ? 'characters/abotPixel/aBotVizPixel' : 'characters/abot/aBotViz');
|
||||||
@@ -58,8 +61,10 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
|||||||
|
|
||||||
public function initAnalyzer():Void
|
public function initAnalyzer():Void
|
||||||
{
|
{
|
||||||
|
if (snd == null) return;
|
||||||
|
|
||||||
@:privateAccess
|
@:privateAccess
|
||||||
analyzer = new SpectralAnalyzer(snd._channel.__audioSource, 7, 0.1, 40);
|
analyzer = new SpectralAnalyzer(snd._channel.__audioSource, BAR_COUNT, 0.1, 40);
|
||||||
// A-Bot tuning...
|
// A-Bot tuning...
|
||||||
analyzer.minDb = -65;
|
analyzer.minDb = -65;
|
||||||
analyzer.maxDb = -25;
|
analyzer.maxDb = -25;
|
||||||
@@ -77,16 +82,17 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
|||||||
// analyzer.fftN = 2048;
|
// analyzer.fftN = 2048;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function dumpSound():Void
|
||||||
|
{
|
||||||
|
snd = null;
|
||||||
|
analyzer = null;
|
||||||
|
}
|
||||||
|
|
||||||
var visTimer:Float = -1;
|
var visTimer:Float = -1;
|
||||||
var visTimeMax:Float = 1 / 30;
|
var visTimeMax:Float = 1 / 30;
|
||||||
|
|
||||||
override function update(elapsed:Float)
|
override function update(elapsed:Float)
|
||||||
{
|
{
|
||||||
// updateViz();
|
|
||||||
|
|
||||||
// updateFFT(elapsed);
|
|
||||||
|
|
||||||
//
|
|
||||||
super.update(elapsed);
|
super.update(elapsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,8 +103,8 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
|||||||
|
|
||||||
override function draw()
|
override function draw()
|
||||||
{
|
{
|
||||||
if (analyzer != null) drawFFT();
|
|
||||||
super.draw();
|
super.draw();
|
||||||
|
drawFFT();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -106,7 +112,7 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
|||||||
*/
|
*/
|
||||||
function drawFFT():Void
|
function drawFFT():Void
|
||||||
{
|
{
|
||||||
var levels = analyzer.getLevels();
|
var levels = (analyzer != null) ? analyzer.getLevels() : getDefaultLevels();
|
||||||
|
|
||||||
for (i in 0...min(group.members.length, levels.length))
|
for (i in 0...min(group.members.length, levels.length))
|
||||||
{
|
{
|
||||||
@@ -118,12 +124,6 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
|||||||
// decrement our animFrame, so we can get a value from 0-5 for animation frames
|
// decrement our animFrame, so we can get a value from 0-5 for animation frames
|
||||||
animFrame -= 1;
|
animFrame -= 1;
|
||||||
|
|
||||||
#if desktop
|
|
||||||
// Web version scales with the Flixel volume level.
|
|
||||||
// This line brings platform parity but looks worse.
|
|
||||||
// animFrame = Math.round(animFrame * FlxG.sound.volume);
|
|
||||||
#end
|
|
||||||
|
|
||||||
animFrame = Math.floor(Math.min(5, animFrame));
|
animFrame = Math.floor(Math.min(5, animFrame));
|
||||||
animFrame = Math.floor(Math.max(0, animFrame));
|
animFrame = Math.floor(Math.max(0, animFrame));
|
||||||
|
|
||||||
@@ -133,79 +133,19 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// function updateFFT(elapsed:Float)
|
/**
|
||||||
// {
|
* Explicitly define the default levels to draw when the analyzer is not available.
|
||||||
// if (vis.snd != null)
|
* @return Array<Bar>
|
||||||
// {
|
*/
|
||||||
// vis.checkAndSetBuffer();
|
static function getDefaultLevels():Array<Bar>
|
||||||
// if (vis.setBuffer)
|
{
|
||||||
// {
|
var result:Array<Bar> = [];
|
||||||
// var remappedShit:Int = 0;
|
|
||||||
// if (vis.snd.playing) remappedShit = Std.int(FlxMath.remapToRange(vis.snd.time, 0, vis.snd.length, 0, vis.numSamples));
|
for (i in 0...BAR_COUNT)
|
||||||
// else
|
{
|
||||||
// remappedShit = Std.int(FlxMath.remapToRange(Conductor.instance.songPosition, 0, vis.snd.length, 0, vis.numSamples));
|
result.push({value: 0, peak: 0.0});
|
||||||
// var fftSamples:Array<Float> = [];
|
}
|
||||||
// var swagBucks = remappedShit;
|
|
||||||
// for (i in remappedShit...remappedShit + (Std.int((44100 * (1 / 144)))))
|
return result;
|
||||||
// {
|
}
|
||||||
// var left = vis.audioData[swagBucks] / 32767;
|
|
||||||
// var right = vis.audioData[swagBucks + 1] / 32767;
|
|
||||||
// var balanced = (left + right) / 2;
|
|
||||||
// swagBucks += 2;
|
|
||||||
// fftSamples.push(balanced);
|
|
||||||
// }
|
|
||||||
// var freqShit = vis.funnyFFT(fftSamples);
|
|
||||||
// for (i in 0...group.members.length)
|
|
||||||
// {
|
|
||||||
// var getSliceShit = function(s:Int) {
|
|
||||||
// var powShit = FlxMath.remapToRange(s, 0, group.members.length, 0, MathUtil.logBase(10, freqShit[0].length));
|
|
||||||
// return Math.round(Math.pow(10, powShit));
|
|
||||||
// };
|
|
||||||
// // var powShit:Float = getSliceShit(i);
|
|
||||||
// var hzSliced:Int = getSliceShit(i);
|
|
||||||
// var sliceLength:Int = Std.int(freqShit[0].length / group.members.length);
|
|
||||||
// var volSlice = freqShit[0].slice(hzSliced, getSliceShit(i + 1));
|
|
||||||
// var avgVel:Float = 0;
|
|
||||||
// for (slice in volSlice)
|
|
||||||
// {
|
|
||||||
// avgVel += slice;
|
|
||||||
// }
|
|
||||||
// avgVel /= volSlice.length;
|
|
||||||
// avgVel *= 10000000;
|
|
||||||
// volumes[i] += avgVel - (elapsed * (volumes[i] * 50));
|
|
||||||
// var animFrame:Int = Std.int(volumes[i]);
|
|
||||||
// animFrame = Math.floor(Math.min(5, animFrame));
|
|
||||||
// animFrame = Math.floor(Math.max(0, animFrame));
|
|
||||||
// animFrame = Std.int(Math.abs(animFrame - 5)); // shitty dumbass flip, cuz dave got da shit backwards lol!
|
|
||||||
// group.members[i].animation.curAnim.curFrame = animFrame;
|
|
||||||
// if (FlxG.keys.justPressed.U)
|
|
||||||
// {
|
|
||||||
// trace(avgVel);
|
|
||||||
// trace(group.members[i].animation.curAnim.curFrame);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// // group.members[0].animation.curAnim.curFrame =
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// public function updateViz()
|
|
||||||
// {
|
|
||||||
// if (vis.snd != null)
|
|
||||||
// {
|
|
||||||
// var remappedShit:Int = 0;
|
|
||||||
// vis.checkAndSetBuffer();
|
|
||||||
// if (vis.setBuffer)
|
|
||||||
// {
|
|
||||||
// // var startingSample:Int = Std.int(FlxMath.remapToRange)
|
|
||||||
// if (vis.snd.playing) remappedShit = Std.int(FlxMath.remapToRange(vis.snd.time, 0, vis.snd.length, 0, vis.numSamples));
|
|
||||||
// for (i in 0...group.members.length)
|
|
||||||
// {
|
|
||||||
// var sampleApprox:Int = Std.int(FlxMath.remapToRange(i, 0, group.members.length, remappedShit, remappedShit + 500));
|
|
||||||
// var left = vis.audioData[sampleApprox] / 32767;
|
|
||||||
// var animFrame:Int = Std.int(FlxMath.remapToRange(left, -1, 1, 0, 6));
|
|
||||||
// group.members[i].animation.curAnim.curFrame = animFrame;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ typedef EntryConstructorFunction = String->Void;
|
|||||||
* @param J The type of the JSON data used when constructing.
|
* @param J The type of the JSON data used when constructing.
|
||||||
*/
|
*/
|
||||||
@:generic
|
@:generic
|
||||||
|
@:autoBuild(funkin.util.macro.DataRegistryMacro.buildRegistry())
|
||||||
abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructorFunction>), J>
|
abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructorFunction>), J>
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@@ -189,6 +190,19 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
|||||||
return entries.get(id);
|
return entries.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of all entries included in the base game.
|
||||||
|
* The actual function exists and is auto-generated on each registry at build time.
|
||||||
|
* @return Array<String>
|
||||||
|
*/
|
||||||
|
// public function listBaseGameEntryIds():Array<String> {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of all entries that are not included in the base game.
|
||||||
|
* @return Array<String>
|
||||||
|
*/
|
||||||
|
// public function listModdedEntryIds():Array<String> {}
|
||||||
|
|
||||||
public function toString():String
|
public function toString():String
|
||||||
{
|
{
|
||||||
return 'Registry(' + registryId + ', ${countEntries()} entries)';
|
return 'Registry(' + registryId + ', ${countEntries()} entries)';
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package funkin.data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface which automatically implements essential fields for a class extending `BaseRegistry`.
|
||||||
|
*
|
||||||
|
* @see `funkin.data.BaseRegistry`
|
||||||
|
*/
|
||||||
|
@:autoBuild(funkin.util.macro.RegistryMacro.buildRegistry())
|
||||||
|
interface DefaultRegistryImpl {}
|
||||||
@@ -5,6 +5,7 @@ package funkin.data;
|
|||||||
* A `String->Void` constructor is also mandatory, but enforced elsewhere.
|
* A `String->Void` constructor is also mandatory, but enforced elsewhere.
|
||||||
* @param T The JSON data type of the registry entry.
|
* @param T The JSON data type of the registry entry.
|
||||||
*/
|
*/
|
||||||
|
@:autoBuild(funkin.util.macro.RegistryMacro.buildEntry())
|
||||||
interface IRegistryEntry<T>
|
interface IRegistryEntry<T>
|
||||||
{
|
{
|
||||||
public final id:String;
|
public final id:String;
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ package funkin.data.dialogue.conversation;
|
|||||||
|
|
||||||
import funkin.play.cutscene.dialogue.Conversation;
|
import funkin.play.cutscene.dialogue.Conversation;
|
||||||
import funkin.play.cutscene.dialogue.ScriptedConversation;
|
import funkin.play.cutscene.dialogue.ScriptedConversation;
|
||||||
|
import funkin.util.tools.ISingleton;
|
||||||
|
import funkin.data.DefaultRegistryImpl;
|
||||||
|
|
||||||
class ConversationRegistry extends BaseRegistry<Conversation, ConversationData>
|
class ConversationRegistry extends BaseRegistry<Conversation, ConversationData> implements ISingleton implements DefaultRegistryImpl
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The current version string for the dialogue box data format.
|
* The current version string for the dialogue box data format.
|
||||||
@@ -14,74 +16,8 @@ class ConversationRegistry extends BaseRegistry<Conversation, ConversationData>
|
|||||||
|
|
||||||
public static final CONVERSATION_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
|
public static final CONVERSATION_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
|
||||||
|
|
||||||
public static var instance(get, never):ConversationRegistry;
|
|
||||||
static var _instance:Null<ConversationRegistry> = null;
|
|
||||||
|
|
||||||
static function get_instance():ConversationRegistry
|
|
||||||
{
|
|
||||||
if (_instance == null) _instance = new ConversationRegistry();
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function new()
|
public function new()
|
||||||
{
|
{
|
||||||
super('CONVERSATION', 'dialogue/conversations', CONVERSATION_DATA_VERSION_RULE);
|
super('CONVERSATION', 'dialogue/conversations', CONVERSATION_DATA_VERSION_RULE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
|
||||||
*/
|
|
||||||
public function parseEntryData(id:String):Null<ConversationData>
|
|
||||||
{
|
|
||||||
// JsonParser does not take type parameters,
|
|
||||||
// otherwise this function would be in BaseRegistry.
|
|
||||||
var parser = new json2object.JsonParser<ConversationData>();
|
|
||||||
parser.ignoreUnknownVariables = false;
|
|
||||||
|
|
||||||
switch (loadEntryFile(id))
|
|
||||||
{
|
|
||||||
case {fileName: fileName, contents: contents}:
|
|
||||||
parser.fromJson(contents, fileName);
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parser.errors.length > 0)
|
|
||||||
{
|
|
||||||
printErrors(parser.errors, id);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return parser.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse and validate the JSON data and produce the corresponding data object.
|
|
||||||
*
|
|
||||||
* NOTE: Must be implemented on the implementation class.
|
|
||||||
* @param contents The JSON as a string.
|
|
||||||
* @param fileName An optional file name for error reporting.
|
|
||||||
*/
|
|
||||||
public function parseEntryDataRaw(contents:String, ?fileName:String):Null<ConversationData>
|
|
||||||
{
|
|
||||||
var parser = new json2object.JsonParser<ConversationData>();
|
|
||||||
parser.ignoreUnknownVariables = false;
|
|
||||||
parser.fromJson(contents, fileName);
|
|
||||||
|
|
||||||
if (parser.errors.length > 0)
|
|
||||||
{
|
|
||||||
printErrors(parser.errors, fileName);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return parser.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createScriptedEntry(clsName:String):Conversation
|
|
||||||
{
|
|
||||||
return ScriptedConversation.init(clsName, "unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
function getScriptedClassNames():Array<String>
|
|
||||||
{
|
|
||||||
return ScriptedConversation.listScriptClasses();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ package funkin.data.dialogue.dialoguebox;
|
|||||||
import funkin.play.cutscene.dialogue.DialogueBox;
|
import funkin.play.cutscene.dialogue.DialogueBox;
|
||||||
import funkin.data.dialogue.dialoguebox.DialogueBoxData;
|
import funkin.data.dialogue.dialoguebox.DialogueBoxData;
|
||||||
import funkin.play.cutscene.dialogue.ScriptedDialogueBox;
|
import funkin.play.cutscene.dialogue.ScriptedDialogueBox;
|
||||||
|
import funkin.util.tools.ISingleton;
|
||||||
|
import funkin.data.DefaultRegistryImpl;
|
||||||
|
|
||||||
class DialogueBoxRegistry extends BaseRegistry<DialogueBox, DialogueBoxData>
|
class DialogueBoxRegistry extends BaseRegistry<DialogueBox, DialogueBoxData> implements ISingleton implements DefaultRegistryImpl
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The current version string for the dialogue box data format.
|
* The current version string for the dialogue box data format.
|
||||||
@@ -15,74 +17,8 @@ class DialogueBoxRegistry extends BaseRegistry<DialogueBox, DialogueBoxData>
|
|||||||
|
|
||||||
public static final DIALOGUEBOX_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <1.2.0";
|
public static final DIALOGUEBOX_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <1.2.0";
|
||||||
|
|
||||||
public static var instance(get, never):DialogueBoxRegistry;
|
|
||||||
static var _instance:Null<DialogueBoxRegistry> = null;
|
|
||||||
|
|
||||||
static function get_instance():DialogueBoxRegistry
|
|
||||||
{
|
|
||||||
if (_instance == null) _instance = new DialogueBoxRegistry();
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function new()
|
public function new()
|
||||||
{
|
{
|
||||||
super('DIALOGUEBOX', 'dialogue/boxes', DIALOGUEBOX_DATA_VERSION_RULE);
|
super('DIALOGUEBOX', 'dialogue/boxes', DIALOGUEBOX_DATA_VERSION_RULE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
|
||||||
*/
|
|
||||||
public function parseEntryData(id:String):Null<DialogueBoxData>
|
|
||||||
{
|
|
||||||
// JsonParser does not take type parameters,
|
|
||||||
// otherwise this function would be in BaseRegistry.
|
|
||||||
var parser = new json2object.JsonParser<DialogueBoxData>();
|
|
||||||
parser.ignoreUnknownVariables = false;
|
|
||||||
|
|
||||||
switch (loadEntryFile(id))
|
|
||||||
{
|
|
||||||
case {fileName: fileName, contents: contents}:
|
|
||||||
parser.fromJson(contents, fileName);
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parser.errors.length > 0)
|
|
||||||
{
|
|
||||||
printErrors(parser.errors, id);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return parser.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse and validate the JSON data and produce the corresponding data object.
|
|
||||||
*
|
|
||||||
* NOTE: Must be implemented on the implementation class.
|
|
||||||
* @param contents The JSON as a string.
|
|
||||||
* @param fileName An optional file name for error reporting.
|
|
||||||
*/
|
|
||||||
public function parseEntryDataRaw(contents:String, ?fileName:String):Null<DialogueBoxData>
|
|
||||||
{
|
|
||||||
var parser = new json2object.JsonParser<DialogueBoxData>();
|
|
||||||
parser.ignoreUnknownVariables = false;
|
|
||||||
parser.fromJson(contents, fileName);
|
|
||||||
|
|
||||||
if (parser.errors.length > 0)
|
|
||||||
{
|
|
||||||
printErrors(parser.errors, fileName);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return parser.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createScriptedEntry(clsName:String):DialogueBox
|
|
||||||
{
|
|
||||||
return ScriptedDialogueBox.init(clsName, "unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
function getScriptedClassNames():Array<String>
|
|
||||||
{
|
|
||||||
return ScriptedDialogueBox.listScriptClasses();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ package funkin.data.dialogue.speaker;
|
|||||||
|
|
||||||
import funkin.play.cutscene.dialogue.Speaker;
|
import funkin.play.cutscene.dialogue.Speaker;
|
||||||
import funkin.play.cutscene.dialogue.ScriptedSpeaker;
|
import funkin.play.cutscene.dialogue.ScriptedSpeaker;
|
||||||
|
import funkin.util.tools.ISingleton;
|
||||||
|
import funkin.data.DefaultRegistryImpl;
|
||||||
|
|
||||||
class SpeakerRegistry extends BaseRegistry<Speaker, SpeakerData>
|
class SpeakerRegistry extends BaseRegistry<Speaker, SpeakerData> implements ISingleton implements DefaultRegistryImpl
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The current version string for the speaker data format.
|
* The current version string for the speaker data format.
|
||||||
@@ -14,74 +16,8 @@ class SpeakerRegistry extends BaseRegistry<Speaker, SpeakerData>
|
|||||||
|
|
||||||
public static final SPEAKER_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
|
public static final SPEAKER_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
|
||||||
|
|
||||||
public static var instance(get, never):SpeakerRegistry;
|
|
||||||
static var _instance:Null<SpeakerRegistry> = null;
|
|
||||||
|
|
||||||
static function get_instance():SpeakerRegistry
|
|
||||||
{
|
|
||||||
if (_instance == null) _instance = new SpeakerRegistry();
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function new()
|
public function new()
|
||||||
{
|
{
|
||||||
super('SPEAKER', 'dialogue/speakers', SPEAKER_DATA_VERSION_RULE);
|
super('SPEAKER', 'dialogue/speakers', SPEAKER_DATA_VERSION_RULE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
|
||||||
*/
|
|
||||||
public function parseEntryData(id:String):Null<SpeakerData>
|
|
||||||
{
|
|
||||||
// JsonParser does not take type parameters,
|
|
||||||
// otherwise this function would be in BaseRegistry.
|
|
||||||
var parser = new json2object.JsonParser<SpeakerData>();
|
|
||||||
parser.ignoreUnknownVariables = false;
|
|
||||||
|
|
||||||
switch (loadEntryFile(id))
|
|
||||||
{
|
|
||||||
case {fileName: fileName, contents: contents}:
|
|
||||||
parser.fromJson(contents, fileName);
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parser.errors.length > 0)
|
|
||||||
{
|
|
||||||
printErrors(parser.errors, id);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return parser.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse and validate the JSON data and produce the corresponding data object.
|
|
||||||
*
|
|
||||||
* NOTE: Must be implemented on the implementation class.
|
|
||||||
* @param contents The JSON as a string.
|
|
||||||
* @param fileName An optional file name for error reporting.
|
|
||||||
*/
|
|
||||||
public function parseEntryDataRaw(contents:String, ?fileName:String):Null<SpeakerData>
|
|
||||||
{
|
|
||||||
var parser = new json2object.JsonParser<SpeakerData>();
|
|
||||||
parser.ignoreUnknownVariables = false;
|
|
||||||
parser.fromJson(contents, fileName);
|
|
||||||
|
|
||||||
if (parser.errors.length > 0)
|
|
||||||
{
|
|
||||||
printErrors(parser.errors, fileName);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return parser.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createScriptedEntry(clsName:String):Speaker
|
|
||||||
{
|
|
||||||
return ScriptedSpeaker.init(clsName, "unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
function getScriptedClassNames():Array<String>
|
|
||||||
{
|
|
||||||
return ScriptedSpeaker.listScriptClasses();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ package funkin.data.freeplay.album;
|
|||||||
import funkin.ui.freeplay.Album;
|
import funkin.ui.freeplay.Album;
|
||||||
import funkin.data.freeplay.album.AlbumData;
|
import funkin.data.freeplay.album.AlbumData;
|
||||||
import funkin.ui.freeplay.ScriptedAlbum;
|
import funkin.ui.freeplay.ScriptedAlbum;
|
||||||
|
import funkin.util.tools.ISingleton;
|
||||||
|
import funkin.data.DefaultRegistryImpl;
|
||||||
|
|
||||||
class AlbumRegistry extends BaseRegistry<Album, AlbumData>
|
class AlbumRegistry extends BaseRegistry<Album, AlbumData> implements ISingleton implements DefaultRegistryImpl
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The current version string for the album data format.
|
* The current version string for the album data format.
|
||||||
@@ -15,70 +17,8 @@ class AlbumRegistry extends BaseRegistry<Album, AlbumData>
|
|||||||
|
|
||||||
public static final ALBUM_DATA_VERSION_RULE:thx.semver.VersionRule = '1.0.x';
|
public static final ALBUM_DATA_VERSION_RULE:thx.semver.VersionRule = '1.0.x';
|
||||||
|
|
||||||
public static final instance:AlbumRegistry = new AlbumRegistry();
|
|
||||||
|
|
||||||
public function new()
|
public function new()
|
||||||
{
|
{
|
||||||
super('ALBUM', 'ui/freeplay/albums', ALBUM_DATA_VERSION_RULE);
|
super('ALBUM', 'ui/freeplay/albums', ALBUM_DATA_VERSION_RULE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
|
||||||
* @param id The ID of the entry to load.
|
|
||||||
* @return The parsed data object.
|
|
||||||
*/
|
|
||||||
public function parseEntryData(id:String):Null<AlbumData>
|
|
||||||
{
|
|
||||||
// JsonParser does not take type parameters,
|
|
||||||
// otherwise this function would be in BaseRegistry.
|
|
||||||
var parser:json2object.JsonParser<AlbumData> = new json2object.JsonParser<AlbumData>();
|
|
||||||
parser.ignoreUnknownVariables = false;
|
|
||||||
|
|
||||||
switch (loadEntryFile(id))
|
|
||||||
{
|
|
||||||
case {fileName: fileName, contents: contents}:
|
|
||||||
parser.fromJson(contents, fileName);
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parser.errors.length > 0)
|
|
||||||
{
|
|
||||||
printErrors(parser.errors, id);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return parser.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse and validate the JSON data and produce the corresponding data object.
|
|
||||||
*
|
|
||||||
* NOTE: Must be implemented on the implementation class.
|
|
||||||
* @param contents The JSON as a string.
|
|
||||||
* @param fileName An optional file name for error reporting.
|
|
||||||
* @return The parsed data object.
|
|
||||||
*/
|
|
||||||
public function parseEntryDataRaw(contents:String, ?fileName:String):Null<AlbumData>
|
|
||||||
{
|
|
||||||
var parser:json2object.JsonParser<AlbumData> = new json2object.JsonParser<AlbumData>();
|
|
||||||
parser.ignoreUnknownVariables = false;
|
|
||||||
parser.fromJson(contents, fileName);
|
|
||||||
|
|
||||||
if (parser.errors.length > 0)
|
|
||||||
{
|
|
||||||
printErrors(parser.errors, fileName);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return parser.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createScriptedEntry(clsName:String):Album
|
|
||||||
{
|
|
||||||
return ScriptedAlbum.init(clsName, 'unknown');
|
|
||||||
}
|
|
||||||
|
|
||||||
function getScriptedClassNames():Array<String>
|
|
||||||
{
|
|
||||||
return ScriptedAlbum.listScriptClasses();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,5 +5,10 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.0.1]
|
||||||
|
### Added
|
||||||
|
- Added `stickerPack` to specify which sticker pack to use when transitioning into and out of song featuring this character.
|
||||||
|
- If the value isn't present, it will use a default sticker pack instead.
|
||||||
|
|
||||||
## [1.0.0]
|
## [1.0.0]
|
||||||
Initial release.
|
Initial release.
|
||||||
|
|||||||
@@ -31,6 +31,15 @@ class PlayerData
|
|||||||
@:default(false)
|
@:default(false)
|
||||||
public var showUnownedChars:Bool = false;
|
public var showUnownedChars:Bool = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default sticker pack to use for songs featuring this playable character.
|
||||||
|
* Can be overridden by specific songs.
|
||||||
|
* @default `default`
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
@:default(funkin.util.Constants.DEFAULT_STICKER_PACK)
|
||||||
|
public var stickerPack:String = Constants.DEFAULT_STICKER_PACK;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Which freeplay style to use for this character.
|
* Which freeplay style to use for this character.
|
||||||
*/
|
*/
|
||||||
@@ -325,7 +334,11 @@ typedef PlayerResultsAnimationData =
|
|||||||
*/
|
*/
|
||||||
var renderType:String;
|
var renderType:String;
|
||||||
|
|
||||||
var assetPath:String;
|
@:optional
|
||||||
|
var assetPath:Null<String>;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
var scriptClass:Null<String>;
|
||||||
|
|
||||||
@:optional
|
@:optional
|
||||||
@:default([0, 0])
|
@:default([0, 0])
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import funkin.data.freeplay.player.PlayerData;
|
|||||||
import funkin.ui.freeplay.charselect.PlayableCharacter;
|
import funkin.ui.freeplay.charselect.PlayableCharacter;
|
||||||
import funkin.ui.freeplay.charselect.ScriptedPlayableCharacter;
|
import funkin.ui.freeplay.charselect.ScriptedPlayableCharacter;
|
||||||
import funkin.save.Save;
|
import funkin.save.Save;
|
||||||
|
import funkin.util.tools.ISingleton;
|
||||||
|
import funkin.data.DefaultRegistryImpl;
|
||||||
|
|
||||||
class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData>
|
class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData> implements ISingleton implements DefaultRegistryImpl
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The current version string for the stage data format.
|
* The current version string for the stage data format.
|
||||||
@@ -16,15 +18,6 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData>
|
|||||||
|
|
||||||
public static final PLAYER_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
|
public static final PLAYER_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
|
||||||
|
|
||||||
public static var instance(get, never):PlayerRegistry;
|
|
||||||
static var _instance:Null<PlayerRegistry> = null;
|
|
||||||
|
|
||||||
static function get_instance():PlayerRegistry
|
|
||||||
{
|
|
||||||
if (_instance == null) _instance = new PlayerRegistry();
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A mapping between stage character IDs and Freeplay playable character IDs.
|
* A mapping between stage character IDs and Freeplay playable character IDs.
|
||||||
*/
|
*/
|
||||||
@@ -130,79 +123,4 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData>
|
|||||||
{
|
{
|
||||||
return ownedCharacterIds.exists(characterId);
|
return ownedCharacterIds.exists(characterId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
|
||||||
*/
|
|
||||||
public function parseEntryData(id:String):Null<PlayerData>
|
|
||||||
{
|
|
||||||
// JsonParser does not take type parameters,
|
|
||||||
// otherwise this function would be in BaseRegistry.
|
|
||||||
var parser = new json2object.JsonParser<PlayerData>();
|
|
||||||
parser.ignoreUnknownVariables = false;
|
|
||||||
|
|
||||||
switch (loadEntryFile(id))
|
|
||||||
{
|
|
||||||
case {fileName: fileName, contents: contents}:
|
|
||||||
parser.fromJson(contents, fileName);
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parser.errors.length > 0)
|
|
||||||
{
|
|
||||||
printErrors(parser.errors, id);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return parser.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse and validate the JSON data and produce the corresponding data object.
|
|
||||||
*
|
|
||||||
* NOTE: Must be implemented on the implementation class.
|
|
||||||
* @param contents The JSON as a string.
|
|
||||||
* @param fileName An optional file name for error reporting.
|
|
||||||
*/
|
|
||||||
public function parseEntryDataRaw(contents:String, ?fileName:String):Null<PlayerData>
|
|
||||||
{
|
|
||||||
var parser = new json2object.JsonParser<PlayerData>();
|
|
||||||
parser.ignoreUnknownVariables = false;
|
|
||||||
parser.fromJson(contents, fileName);
|
|
||||||
|
|
||||||
if (parser.errors.length > 0)
|
|
||||||
{
|
|
||||||
printErrors(parser.errors, fileName);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return parser.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createScriptedEntry(clsName:String):PlayableCharacter
|
|
||||||
{
|
|
||||||
return ScriptedPlayableCharacter.init(clsName, "unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
function getScriptedClassNames():Array<String>
|
|
||||||
{
|
|
||||||
return ScriptedPlayableCharacter.listScriptClasses();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A list of all the playable characters from the base game, in order.
|
|
||||||
*/
|
|
||||||
public function listBaseGamePlayerIds():Array<String>
|
|
||||||
{
|
|
||||||
return ["bf", "pico"];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A list of all installed playable characters that are not from the base game.
|
|
||||||
*/
|
|
||||||
public function listModdedPlayerIds():Array<String>
|
|
||||||
{
|
|
||||||
return listEntryIds().filter(function(id:String):Bool {
|
|
||||||
return listBaseGamePlayerIds().indexOf(id) == -1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ package funkin.data.freeplay.style;
|
|||||||
import funkin.ui.freeplay.FreeplayStyle;
|
import funkin.ui.freeplay.FreeplayStyle;
|
||||||
import funkin.data.freeplay.style.FreeplayStyleData;
|
import funkin.data.freeplay.style.FreeplayStyleData;
|
||||||
import funkin.ui.freeplay.ScriptedFreeplayStyle;
|
import funkin.ui.freeplay.ScriptedFreeplayStyle;
|
||||||
|
import funkin.util.tools.ISingleton;
|
||||||
|
import funkin.data.DefaultRegistryImpl;
|
||||||
|
|
||||||
class FreeplayStyleRegistry extends BaseRegistry<FreeplayStyle, FreeplayStyleData>
|
class FreeplayStyleRegistry extends BaseRegistry<FreeplayStyle, FreeplayStyleData> implements ISingleton implements DefaultRegistryImpl
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The current version string for the style data format.
|
* The current version string for the style data format.
|
||||||
@@ -15,70 +17,8 @@ class FreeplayStyleRegistry extends BaseRegistry<FreeplayStyle, FreeplayStyleDat
|
|||||||
|
|
||||||
public static final FREEPLAYSTYLE_DATA_VERSION_RULE:thx.semver.VersionRule = '1.0.x';
|
public static final FREEPLAYSTYLE_DATA_VERSION_RULE:thx.semver.VersionRule = '1.0.x';
|
||||||
|
|
||||||
public static final instance:FreeplayStyleRegistry = new FreeplayStyleRegistry();
|
|
||||||
|
|
||||||
public function new()
|
public function new()
|
||||||
{
|
{
|
||||||
super('FREEPLAYSTYLE', 'ui/freeplay/styles', FREEPLAYSTYLE_DATA_VERSION_RULE);
|
super('FREEPLAYSTYLE', 'ui/freeplay/styles', FREEPLAYSTYLE_DATA_VERSION_RULE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
|
||||||
* @param id The ID of the entry to load.
|
|
||||||
* @return The parsed data object.
|
|
||||||
*/
|
|
||||||
public function parseEntryData(id:String):Null<FreeplayStyleData>
|
|
||||||
{
|
|
||||||
// JsonParser does not take type parameters,
|
|
||||||
// otherwise this function would be in BaseRegistry.
|
|
||||||
var parser:json2object.JsonParser<FreeplayStyleData> = new json2object.JsonParser<FreeplayStyleData>();
|
|
||||||
parser.ignoreUnknownVariables = false;
|
|
||||||
|
|
||||||
switch (loadEntryFile(id))
|
|
||||||
{
|
|
||||||
case {fileName: fileName, contents: contents}:
|
|
||||||
parser.fromJson(contents, fileName);
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parser.errors.length > 0)
|
|
||||||
{
|
|
||||||
printErrors(parser.errors, id);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return parser.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse and validate the JSON data and produce the corresponding data object.
|
|
||||||
*
|
|
||||||
* NOTE: Must be implemented on the implementation class.
|
|
||||||
* @param contents The JSON as a string.
|
|
||||||
* @param fileName An optional file name for error reporting.
|
|
||||||
* @return The parsed data object.
|
|
||||||
*/
|
|
||||||
public function parseEntryDataRaw(contents:String, ?fileName:String):Null<FreeplayStyleData>
|
|
||||||
{
|
|
||||||
var parser:json2object.JsonParser<FreeplayStyleData> = new json2object.JsonParser<FreeplayStyleData>();
|
|
||||||
parser.ignoreUnknownVariables = false;
|
|
||||||
parser.fromJson(contents, fileName);
|
|
||||||
|
|
||||||
if (parser.errors.length > 0)
|
|
||||||
{
|
|
||||||
printErrors(parser.errors, fileName);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return parser.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createScriptedEntry(clsName:String):FreeplayStyle
|
|
||||||
{
|
|
||||||
return ScriptedFreeplayStyle.init(clsName, 'unknown');
|
|
||||||
}
|
|
||||||
|
|
||||||
function getScriptedClassNames():Array<String>
|
|
||||||
{
|
|
||||||
return ScriptedFreeplayStyle.listScriptClasses();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ package funkin.data.notestyle;
|
|||||||
import funkin.play.notes.notestyle.NoteStyle;
|
import funkin.play.notes.notestyle.NoteStyle;
|
||||||
import funkin.play.notes.notestyle.ScriptedNoteStyle;
|
import funkin.play.notes.notestyle.ScriptedNoteStyle;
|
||||||
import funkin.data.notestyle.NoteStyleData;
|
import funkin.data.notestyle.NoteStyleData;
|
||||||
|
import funkin.util.tools.ISingleton;
|
||||||
|
import funkin.data.DefaultRegistryImpl;
|
||||||
|
|
||||||
class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData>
|
class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData> implements ISingleton implements DefaultRegistryImpl
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The current version string for the note style data format.
|
* The current version string for the note style data format.
|
||||||
@@ -15,15 +17,6 @@ class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData>
|
|||||||
|
|
||||||
public static final NOTE_STYLE_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <1.2.0";
|
public static final NOTE_STYLE_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <1.2.0";
|
||||||
|
|
||||||
public static var instance(get, never):NoteStyleRegistry;
|
|
||||||
static var _instance:Null<NoteStyleRegistry> = null;
|
|
||||||
|
|
||||||
static function get_instance():NoteStyleRegistry
|
|
||||||
{
|
|
||||||
if (_instance == null) _instance = new NoteStyleRegistry();
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function new()
|
public function new()
|
||||||
{
|
{
|
||||||
super('NOTESTYLE', 'notestyles', NOTE_STYLE_DATA_VERSION_RULE);
|
super('NOTESTYLE', 'notestyles', NOTE_STYLE_DATA_VERSION_RULE);
|
||||||
@@ -33,61 +26,4 @@ class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData>
|
|||||||
{
|
{
|
||||||
return fetchEntry(Constants.DEFAULT_NOTE_STYLE);
|
return fetchEntry(Constants.DEFAULT_NOTE_STYLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
|
||||||
*/
|
|
||||||
public function parseEntryData(id:String):Null<NoteStyleData>
|
|
||||||
{
|
|
||||||
// JsonParser does not take type parameters,
|
|
||||||
// otherwise this function would be in BaseRegistry.
|
|
||||||
var parser = new json2object.JsonParser<NoteStyleData>();
|
|
||||||
parser.ignoreUnknownVariables = false;
|
|
||||||
|
|
||||||
switch (loadEntryFile(id))
|
|
||||||
{
|
|
||||||
case {fileName: fileName, contents: contents}:
|
|
||||||
parser.fromJson(contents, fileName);
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parser.errors.length > 0)
|
|
||||||
{
|
|
||||||
printErrors(parser.errors, id);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return parser.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse and validate the JSON data and produce the corresponding data object.
|
|
||||||
*
|
|
||||||
* NOTE: Must be implemented on the implementation class.
|
|
||||||
* @param contents The JSON as a string.
|
|
||||||
* @param fileName An optional file name for error reporting.
|
|
||||||
*/
|
|
||||||
public function parseEntryDataRaw(contents:String, ?fileName:String):Null<NoteStyleData>
|
|
||||||
{
|
|
||||||
var parser = new json2object.JsonParser<NoteStyleData>();
|
|
||||||
parser.ignoreUnknownVariables = false;
|
|
||||||
parser.fromJson(contents, fileName);
|
|
||||||
|
|
||||||
if (parser.errors.length > 0)
|
|
||||||
{
|
|
||||||
printErrors(parser.errors, fileName);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return parser.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createScriptedEntry(clsName:String):NoteStyle
|
|
||||||
{
|
|
||||||
return ScriptedNoteStyle.init(clsName, "unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
function getScriptedClassNames():Array<String>
|
|
||||||
{
|
|
||||||
return ScriptedNoteStyle.listScriptClasses();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [2.2.5]
|
||||||
|
### Added
|
||||||
|
- Added `playData.stickerPack` to specify which sticker pack to use when transitioning into and out of a song.
|
||||||
|
- If the value isn't present, it will use the character's sticker pack instead.
|
||||||
|
|
||||||
## [2.2.4]
|
## [2.2.4]
|
||||||
### Added
|
### Added
|
||||||
- Added `playData.characters.opponentVocals` to specify which vocal track(s) to play for the opponent.
|
- Added `playData.characters.opponentVocals` to specify which vocal track(s) to play for the opponent.
|
||||||
|
|||||||
@@ -471,6 +471,13 @@ class SongPlayData implements ICloneable<SongPlayData>
|
|||||||
@:optional
|
@:optional
|
||||||
public var album:Null<String>;
|
public var album:Null<String>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The sticker pack for the song to use during transitions.
|
||||||
|
* If `null`, display the character's sticker pack.
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
public var stickerPack:Null<String>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The start time for the audio preview in Freeplay.
|
* The start time for the audio preview in Freeplay.
|
||||||
* Defaults to 0 seconds in.
|
* Defaults to 0 seconds in.
|
||||||
|
|||||||
@@ -10,11 +10,13 @@ import funkin.play.song.ScriptedSong;
|
|||||||
import funkin.play.song.Song;
|
import funkin.play.song.Song;
|
||||||
import funkin.util.assets.DataAssets;
|
import funkin.util.assets.DataAssets;
|
||||||
import funkin.util.VersionUtil;
|
import funkin.util.VersionUtil;
|
||||||
|
import funkin.util.tools.ISingleton;
|
||||||
|
import funkin.data.DefaultRegistryImpl;
|
||||||
|
|
||||||
using funkin.data.song.migrator.SongDataMigrator;
|
using funkin.data.song.migrator.SongDataMigrator;
|
||||||
|
|
||||||
@:nullSafety
|
@:nullSafety
|
||||||
class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
class SongRegistry extends BaseRegistry<Song, SongMetadata> implements ISingleton implements DefaultRegistryImpl
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The current version string for the stage data format.
|
* The current version string for the stage data format.
|
||||||
@@ -40,19 +42,6 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||||||
return '${Constants.TITLE} - ${Constants.VERSION}';
|
return '${Constants.TITLE} - ${Constants.VERSION}';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: What if there was a Singleton macro which automatically created the property for us?
|
|
||||||
*/
|
|
||||||
public static var instance(get, never):SongRegistry;
|
|
||||||
|
|
||||||
static var _instance:Null<SongRegistry> = null;
|
|
||||||
|
|
||||||
static function get_instance():SongRegistry
|
|
||||||
{
|
|
||||||
if (_instance == null) _instance = new SongRegistry();
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function new()
|
public function new()
|
||||||
{
|
{
|
||||||
super('SONG', 'songs', SONG_METADATA_VERSION_RULE);
|
super('SONG', 'songs', SONG_METADATA_VERSION_RULE);
|
||||||
@@ -419,16 +408,6 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createScriptedEntry(clsName:String):Song
|
|
||||||
{
|
|
||||||
return ScriptedSong.init(clsName, "unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
function getScriptedClassNames():Array<String>
|
|
||||||
{
|
|
||||||
return ScriptedSong.listScriptClasses();
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadEntryMetadataFile(id:String, ?variation:String):Null<JsonFile>
|
function loadEntryMetadataFile(id:String, ?variation:String):Null<JsonFile>
|
||||||
{
|
{
|
||||||
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
||||||
@@ -503,52 +482,6 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||||||
return chartData;
|
return chartData;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A list of all the story weeks from the base game, in order.
|
|
||||||
* TODO: Should this be hardcoded?
|
|
||||||
*/
|
|
||||||
public function listBaseGameSongIds():Array<String>
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
"tutorial",
|
|
||||||
"bopeebo",
|
|
||||||
"fresh",
|
|
||||||
"dadbattle",
|
|
||||||
"spookeez",
|
|
||||||
"south",
|
|
||||||
"monster",
|
|
||||||
"pico",
|
|
||||||
"philly-nice",
|
|
||||||
"blammed",
|
|
||||||
"satin-panties",
|
|
||||||
"high",
|
|
||||||
"milf",
|
|
||||||
"cocoa",
|
|
||||||
"eggnog",
|
|
||||||
"winter-horrorland",
|
|
||||||
"senpai",
|
|
||||||
"roses",
|
|
||||||
"thorns",
|
|
||||||
"ugh",
|
|
||||||
"guns",
|
|
||||||
"stress",
|
|
||||||
"darnell",
|
|
||||||
"lit-up",
|
|
||||||
"2hot",
|
|
||||||
"blazin"
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A list of all installed story weeks that are not from the base game.
|
|
||||||
*/
|
|
||||||
public function listModdedSongIds():Array<String>
|
|
||||||
{
|
|
||||||
return listEntryIds().filter(function(id:String):Bool {
|
|
||||||
return listBaseGameSongIds().indexOf(id) == -1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list of all difficulties for a specific character.
|
* A list of all difficulties for a specific character.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ package funkin.data.stage;
|
|||||||
|
|
||||||
import funkin.play.stage.Stage;
|
import funkin.play.stage.Stage;
|
||||||
import funkin.play.stage.ScriptedStage;
|
import funkin.play.stage.ScriptedStage;
|
||||||
|
import funkin.util.tools.ISingleton;
|
||||||
|
import funkin.data.DefaultRegistryImpl;
|
||||||
|
|
||||||
class StageRegistry extends BaseRegistry<Stage, StageData>
|
class StageRegistry extends BaseRegistry<Stage, StageData> implements ISingleton implements DefaultRegistryImpl
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The current version string for the stage data format.
|
* The current version string for the stage data format.
|
||||||
@@ -14,110 +16,8 @@ class StageRegistry extends BaseRegistry<Stage, StageData>
|
|||||||
|
|
||||||
public static final STAGE_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <1.1.0";
|
public static final STAGE_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <1.1.0";
|
||||||
|
|
||||||
public static var instance(get, never):StageRegistry;
|
|
||||||
static var _instance:Null<StageRegistry> = null;
|
|
||||||
|
|
||||||
static function get_instance():StageRegistry
|
|
||||||
{
|
|
||||||
if (_instance == null) _instance = new StageRegistry();
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function new()
|
public function new()
|
||||||
{
|
{
|
||||||
super('STAGE', 'stages', STAGE_DATA_VERSION_RULE);
|
super('STAGE', 'stages', STAGE_DATA_VERSION_RULE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
|
||||||
*/
|
|
||||||
public function parseEntryData(id:String):Null<StageData>
|
|
||||||
{
|
|
||||||
// JsonParser does not take type parameters,
|
|
||||||
// otherwise this function would be in BaseRegistry.
|
|
||||||
var parser = new json2object.JsonParser<StageData>();
|
|
||||||
parser.ignoreUnknownVariables = false;
|
|
||||||
|
|
||||||
switch (loadEntryFile(id))
|
|
||||||
{
|
|
||||||
case {fileName: fileName, contents: contents}:
|
|
||||||
parser.fromJson(contents, fileName);
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parser.errors.length > 0)
|
|
||||||
{
|
|
||||||
printErrors(parser.errors, id);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return parser.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse and validate the JSON data and produce the corresponding data object.
|
|
||||||
*
|
|
||||||
* NOTE: Must be implemented on the implementation class.
|
|
||||||
* @param contents The JSON as a string.
|
|
||||||
* @param fileName An optional file name for error reporting.
|
|
||||||
*/
|
|
||||||
public function parseEntryDataRaw(contents:String, ?fileName:String):Null<StageData>
|
|
||||||
{
|
|
||||||
var parser = new json2object.JsonParser<StageData>();
|
|
||||||
parser.ignoreUnknownVariables = false;
|
|
||||||
parser.fromJson(contents, fileName);
|
|
||||||
|
|
||||||
if (parser.errors.length > 0)
|
|
||||||
{
|
|
||||||
printErrors(parser.errors, fileName);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return parser.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createScriptedEntry(clsName:String):Stage
|
|
||||||
{
|
|
||||||
return ScriptedStage.init(clsName, "unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
function getScriptedClassNames():Array<String>
|
|
||||||
{
|
|
||||||
return ScriptedStage.listScriptClasses();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A list of all the stages from the base game, in order.
|
|
||||||
* TODO: Should this be hardcoded?
|
|
||||||
*/
|
|
||||||
public function listBaseGameStageIds():Array<String>
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
"mainStage",
|
|
||||||
"mainStageErect",
|
|
||||||
"spookyMansion",
|
|
||||||
"phillyTrain",
|
|
||||||
"phillyTrainErect",
|
|
||||||
"limoRide",
|
|
||||||
"limoRideErect",
|
|
||||||
"mallXmas",
|
|
||||||
"mallXmasErect",
|
|
||||||
"mallEvil",
|
|
||||||
"school",
|
|
||||||
"schoolEvil",
|
|
||||||
"tankmanBattlefield",
|
|
||||||
"phillyStreets",
|
|
||||||
"phillyStreetsErect",
|
|
||||||
"phillyBlazin",
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A list of all installed story weeks that are not from the base game.
|
|
||||||
*/
|
|
||||||
public function listModdedStageIds():Array<String>
|
|
||||||
{
|
|
||||||
return listEntryIds().filter(function(id:String):Bool {
|
|
||||||
return listBaseGameStageIds().indexOf(id) == -1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Sticker Pack Data Schema Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.0.0]
|
||||||
|
Initial release. Sticker packs have been restructured compared to previous versions to be less complicated.
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package funkin.data.stickers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A type definition for a sticker pack.
|
||||||
|
* It includes things like its name, the artist, and what sticker graphics to use.
|
||||||
|
* @see https://lib.haxe.org/p/json2object/
|
||||||
|
*/
|
||||||
|
typedef StickerData =
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Semantic version of the sticker set data.
|
||||||
|
*/
|
||||||
|
public var version:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Readable name of the sticker set.
|
||||||
|
*/
|
||||||
|
public var name:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The artist of the sticker set.
|
||||||
|
*/
|
||||||
|
public var artist:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The stickers in the set.
|
||||||
|
* This is simply a list of asset files to use.
|
||||||
|
*/
|
||||||
|
public var stickers:Array<String>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package funkin.data.stickers;
|
||||||
|
|
||||||
|
import funkin.data.stickers.StickerData;
|
||||||
|
import funkin.ui.transition.stickers.StickerPack;
|
||||||
|
import funkin.ui.transition.stickers.ScriptedStickerPack;
|
||||||
|
|
||||||
|
class StickerRegistry extends BaseRegistry<StickerPack, StickerData>
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The current version string for the sticker pack data format.
|
||||||
|
* Handle breaking changes by incrementing this value
|
||||||
|
* and adding migration to the `migrateStickerData()` function.
|
||||||
|
*/
|
||||||
|
public static final STICKER_DATA_VERSION:thx.semver.Version = '1.0.0';
|
||||||
|
|
||||||
|
public static final STICKER_DATA_VERSION_RULE:thx.semver.VersionRule = '1.0.x';
|
||||||
|
|
||||||
|
public static final instance:StickerRegistry = new StickerRegistry();
|
||||||
|
|
||||||
|
public function new()
|
||||||
|
{
|
||||||
|
super('STICKER', 'stickerpacks', STICKER_DATA_VERSION_RULE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fetchDefault():StickerPack
|
||||||
|
{
|
||||||
|
return fetchEntry(Constants.DEFAULT_STICKER_PACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
||||||
|
* @param id The ID of the entry to load.
|
||||||
|
* @return The parsed data object.
|
||||||
|
*/
|
||||||
|
public function parseEntryData(id:String):Null<StickerData>
|
||||||
|
{
|
||||||
|
// JsonParser does not take type parameters,
|
||||||
|
// otherwise this function would be in BaseRegistry.
|
||||||
|
var parser:json2object.JsonParser<StickerData> = new json2object.JsonParser<StickerData>();
|
||||||
|
parser.ignoreUnknownVariables = false;
|
||||||
|
|
||||||
|
switch (loadEntryFile(id))
|
||||||
|
{
|
||||||
|
case {fileName: fileName, contents: contents}:
|
||||||
|
parser.fromJson(contents, fileName);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parser.errors.length > 0)
|
||||||
|
{
|
||||||
|
printErrors(parser.errors, id);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return parser.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse and validate the JSON data and produce the corresponding data object.
|
||||||
|
*
|
||||||
|
* NOTE: Must be implemented on the implementation class.
|
||||||
|
* @param contents The JSON as a string.
|
||||||
|
* @param fileName An optional file name for error reporting.
|
||||||
|
* @return The parsed data object.
|
||||||
|
*/
|
||||||
|
public function parseEntryDataRaw(contents:String, ?fileName:String):Null<StickerData>
|
||||||
|
{
|
||||||
|
var parser:json2object.JsonParser<StickerData> = new json2object.JsonParser<StickerData>();
|
||||||
|
parser.ignoreUnknownVariables = false;
|
||||||
|
parser.fromJson(contents, fileName);
|
||||||
|
|
||||||
|
if (parser.errors.length > 0)
|
||||||
|
{
|
||||||
|
printErrors(parser.errors, fileName);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return parser.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createScriptedEntry(clsName:String):StickerPack
|
||||||
|
{
|
||||||
|
return ScriptedStickerPack.init(clsName, 'unknown');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getScriptedClassNames():Array<String>
|
||||||
|
{
|
||||||
|
return ScriptedStickerPack.listScriptClasses();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,8 +3,10 @@ package funkin.data.story.level;
|
|||||||
import funkin.util.SortUtil;
|
import funkin.util.SortUtil;
|
||||||
import funkin.ui.story.Level;
|
import funkin.ui.story.Level;
|
||||||
import funkin.ui.story.ScriptedLevel;
|
import funkin.ui.story.ScriptedLevel;
|
||||||
|
import funkin.util.tools.ISingleton;
|
||||||
|
import funkin.data.DefaultRegistryImpl;
|
||||||
|
|
||||||
class LevelRegistry extends BaseRegistry<Level, LevelData>
|
class LevelRegistry extends BaseRegistry<Level, LevelData> implements ISingleton implements DefaultRegistryImpl
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The current version string for the level data format.
|
* The current version string for the level data format.
|
||||||
@@ -15,110 +17,41 @@ class LevelRegistry extends BaseRegistry<Level, LevelData>
|
|||||||
|
|
||||||
public static final LEVEL_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <1.1.0";
|
public static final LEVEL_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <1.1.0";
|
||||||
|
|
||||||
public static var instance(get, never):LevelRegistry;
|
|
||||||
static var _instance:Null<LevelRegistry> = null;
|
|
||||||
|
|
||||||
static function get_instance():LevelRegistry
|
|
||||||
{
|
|
||||||
if (_instance == null) _instance = new LevelRegistry();
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function new()
|
public function new()
|
||||||
{
|
{
|
||||||
super('LEVEL', 'levels', LEVEL_DATA_VERSION_RULE);
|
super('LEVEL', 'levels', LEVEL_DATA_VERSION_RULE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
|
||||||
*/
|
|
||||||
public function parseEntryData(id:String):Null<LevelData>
|
|
||||||
{
|
|
||||||
// JsonParser does not take type parameters,
|
|
||||||
// otherwise this function would be in BaseRegistry.
|
|
||||||
var parser = new json2object.JsonParser<LevelData>();
|
|
||||||
parser.ignoreUnknownVariables = false;
|
|
||||||
|
|
||||||
switch (loadEntryFile(id))
|
|
||||||
{
|
|
||||||
case {fileName: fileName, contents: contents}:
|
|
||||||
parser.fromJson(contents, fileName);
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parser.errors.length > 0)
|
|
||||||
{
|
|
||||||
printErrors(parser.errors, id);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return parser.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse and validate the JSON data and produce the corresponding data object.
|
|
||||||
*
|
|
||||||
* NOTE: Must be implemented on the implementation class.
|
|
||||||
* @param contents The JSON as a string.
|
|
||||||
* @param fileName An optional file name for error reporting.
|
|
||||||
*/
|
|
||||||
public function parseEntryDataRaw(contents:String, ?fileName:String):Null<LevelData>
|
|
||||||
{
|
|
||||||
var parser = new json2object.JsonParser<LevelData>();
|
|
||||||
parser.ignoreUnknownVariables = false;
|
|
||||||
parser.fromJson(contents, fileName);
|
|
||||||
|
|
||||||
if (parser.errors.length > 0)
|
|
||||||
{
|
|
||||||
printErrors(parser.errors, fileName);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return parser.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createScriptedEntry(clsName:String):Level
|
|
||||||
{
|
|
||||||
return ScriptedLevel.init(clsName, "unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
function getScriptedClassNames():Array<String>
|
|
||||||
{
|
|
||||||
return ScriptedLevel.listScriptClasses();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list of all the story weeks from the base game, in order.
|
* A list of all the story weeks from the base game, in order.
|
||||||
* TODO: Should this be hardcoded?
|
* @return Array<String>
|
||||||
*/
|
*/
|
||||||
public function listBaseGameLevelIds():Array<String>
|
public function listBaseGameEntryIds():Array<String>
|
||||||
{
|
{
|
||||||
|
// This MUST be hard-coded (overriding the auto-generated method)
|
||||||
|
// because the auto-generated method spits out values in alphabetical order.
|
||||||
return [
|
return [
|
||||||
"tutorial",
|
'tutorial',
|
||||||
"week1",
|
'week1',
|
||||||
"week2",
|
'week2',
|
||||||
"week3",
|
'week3',
|
||||||
"week4",
|
'week4',
|
||||||
"week5",
|
'week5',
|
||||||
"week6",
|
'week6',
|
||||||
"week7",
|
'week7',
|
||||||
"weekend1"
|
'weekend1'
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of all the story weeks in the game, in order.
|
||||||
|
* Modded levels are in alphabetical order at the end of the list.
|
||||||
|
* @return Array<String>
|
||||||
|
*/
|
||||||
public function listSortedLevelIds():Array<String>
|
public function listSortedLevelIds():Array<String>
|
||||||
{
|
{
|
||||||
var result = listEntryIds();
|
var result:Array<String> = listEntryIds();
|
||||||
result.sort(SortUtil.defaultsThenAlphabetically.bind(listBaseGameLevelIds()));
|
result.sort(SortUtil.defaultsThenAlphabetically.bind(listBaseGameEntryIds()));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A list of all installed story weeks that are not from the base game.
|
|
||||||
*/
|
|
||||||
public function listModdedLevelIds():Array<String>
|
|
||||||
{
|
|
||||||
return listEntryIds().filter(function(id:String):Bool {
|
|
||||||
return listBaseGameLevelIds().indexOf(id) == -1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import flixel.math.FlxPoint;
|
|||||||
* An offshoot of FlxTrail, but accomodates the way Funkin
|
* An offshoot of FlxTrail, but accomodates the way Funkin
|
||||||
* does offsets for characters. example, fixes Spirits trail
|
* does offsets for characters. example, fixes Spirits trail
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class FunkTrail extends FlxTrail
|
class FunkTrail extends FlxTrail
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import flixel.util.FlxTimer;
|
|||||||
import flixel.FlxCamera;
|
import flixel.FlxCamera;
|
||||||
import openfl.filters.ColorMatrixFilter;
|
import openfl.filters.ColorMatrixFilter;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class RetroCameraFade
|
class RetroCameraFade
|
||||||
{
|
{
|
||||||
// im lazy, but we only use this for week 6
|
// im lazy, but we only use this for week 6
|
||||||
|
|||||||
@@ -35,6 +35,11 @@ class FlxAtlasSprite extends FlxAnimate
|
|||||||
*/
|
*/
|
||||||
public var onAnimationComplete:FlxTypedSignal<String->Void> = new FlxTypedSignal();
|
public var onAnimationComplete:FlxTypedSignal<String->Void> = new FlxTypedSignal();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal dispatched when a looping animation finishes playing.
|
||||||
|
*/
|
||||||
|
public var onAnimationLoop:FlxTypedSignal<String->Void> = new FlxTypedSignal();
|
||||||
|
|
||||||
var currentAnimation:String;
|
var currentAnimation:String;
|
||||||
|
|
||||||
var canPlayOtherAnims:Bool = true;
|
var canPlayOtherAnims:Bool = true;
|
||||||
@@ -61,6 +66,8 @@ class FlxAtlasSprite extends FlxAnimate
|
|||||||
throw 'FlxAtlasSprite not initialized properly. Are you sure the path (${path}) exists?';
|
throw 'FlxAtlasSprite not initialized properly. Are you sure the path (${path}) exists?';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onAnimationComplete.add(cleanupAnimation);
|
||||||
|
|
||||||
// This defaults the sprite to play the first animation in the atlas,
|
// This defaults the sprite to play the first animation in the atlas,
|
||||||
// then pauses it. This ensures symbols are intialized properly.
|
// then pauses it. This ensures symbols are intialized properly.
|
||||||
this.anim.play('');
|
this.anim.play('');
|
||||||
@@ -307,12 +314,11 @@ class FlxAtlasSprite extends FlxAnimate
|
|||||||
{
|
{
|
||||||
anim.curFrame = (fr != null) ? fr.index : 0;
|
anim.curFrame = (fr != null) ? fr.index : 0;
|
||||||
anim.resume();
|
anim.resume();
|
||||||
// _onAnimationComplete not called since this is a loop.
|
_onAnimationLoop();
|
||||||
}
|
}
|
||||||
else if (fr != null && anim.curFrame != anim.length - 1)
|
else if (fr != null && anim.curFrame != anim.length - 1)
|
||||||
{
|
{
|
||||||
anim.curFrame--;
|
anim.curFrame--;
|
||||||
cleanupAnimation(currentAnimation ?? "");
|
|
||||||
_onAnimationComplete();
|
_onAnimationComplete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -331,6 +337,18 @@ class FlxAtlasSprite extends FlxAnimate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _onAnimationLoop():Void
|
||||||
|
{
|
||||||
|
if (currentAnimation != null)
|
||||||
|
{
|
||||||
|
onAnimationLoop.dispatch(currentAnimation);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
onAnimationLoop.dispatch('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var prevFrames:Map<Int, FlxFrame> = [];
|
var prevFrames:Map<Int, FlxFrame> = [];
|
||||||
|
|
||||||
public function replaceFrameGraphic(index:Int, ?graphic:FlxGraphicAsset):Void
|
public function replaceFrameGraphic(index:Int, ?graphic:FlxGraphicAsset):Void
|
||||||
|
|||||||
@@ -74,6 +74,11 @@ interface INoteScriptedClass extends IScriptedClass
|
|||||||
* Called when EITHER player (usually the player) misses a note.
|
* Called when EITHER player (usually the player) misses a note.
|
||||||
*/
|
*/
|
||||||
public function onNoteMiss(event:NoteScriptEvent):Void;
|
public function onNoteMiss(event:NoteScriptEvent):Void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when EITHER player (usually the player) drops a hold note.
|
||||||
|
*/
|
||||||
|
public function onNoteHoldDrop(event:HoldNoteScriptEvent):Void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import funkin.play.notes.notekind.NoteKindManager;
|
|||||||
import funkin.data.song.SongRegistry;
|
import funkin.data.song.SongRegistry;
|
||||||
import funkin.data.freeplay.player.PlayerRegistry;
|
import funkin.data.freeplay.player.PlayerRegistry;
|
||||||
import funkin.data.stage.StageRegistry;
|
import funkin.data.stage.StageRegistry;
|
||||||
|
import funkin.data.stickers.StickerRegistry;
|
||||||
import funkin.data.freeplay.album.AlbumRegistry;
|
import funkin.data.freeplay.album.AlbumRegistry;
|
||||||
import funkin.modding.module.ModuleHandler;
|
import funkin.modding.module.ModuleHandler;
|
||||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||||
@@ -38,7 +39,7 @@ class PolymodHandler
|
|||||||
* Using more complex rules allows mods from older compatible versions to stay functioning,
|
* Using more complex rules allows mods from older compatible versions to stay functioning,
|
||||||
* while preventing mods made for future versions from being installed.
|
* while preventing mods made for future versions from being installed.
|
||||||
*/
|
*/
|
||||||
static final API_VERSION_RULE:String = ">=0.5.0 <0.6.0";
|
static final API_VERSION_RULE:String = ">=0.6.3 <0.7.0";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Where relative to the executable that mods are located.
|
* Where relative to the executable that mods are located.
|
||||||
@@ -79,8 +80,10 @@ class PolymodHandler
|
|||||||
*/
|
*/
|
||||||
public static function loadAllMods():Void
|
public static function loadAllMods():Void
|
||||||
{
|
{
|
||||||
|
#if sys
|
||||||
// Create the mod root if it doesn't exist.
|
// Create the mod root if it doesn't exist.
|
||||||
createModRoot();
|
createModRoot();
|
||||||
|
#end
|
||||||
trace('Initializing Polymod (using all mods)...');
|
trace('Initializing Polymod (using all mods)...');
|
||||||
loadModsById(getAllModIds());
|
loadModsById(getAllModIds());
|
||||||
}
|
}
|
||||||
@@ -90,9 +93,10 @@ class PolymodHandler
|
|||||||
*/
|
*/
|
||||||
public static function loadEnabledMods():Void
|
public static function loadEnabledMods():Void
|
||||||
{
|
{
|
||||||
|
#if sys
|
||||||
// Create the mod root if it doesn't exist.
|
// Create the mod root if it doesn't exist.
|
||||||
createModRoot();
|
createModRoot();
|
||||||
|
#end
|
||||||
trace('Initializing Polymod (using configured mods)...');
|
trace('Initializing Polymod (using configured mods)...');
|
||||||
loadModsById(Save.instance.enabledModIds);
|
loadModsById(Save.instance.enabledModIds);
|
||||||
}
|
}
|
||||||
@@ -102,9 +106,10 @@ class PolymodHandler
|
|||||||
*/
|
*/
|
||||||
public static function loadNoMods():Void
|
public static function loadNoMods():Void
|
||||||
{
|
{
|
||||||
|
#if sys
|
||||||
// Create the mod root if it doesn't exist.
|
// Create the mod root if it doesn't exist.
|
||||||
createModRoot();
|
createModRoot();
|
||||||
|
#end
|
||||||
// We still need to configure the debug print calls etc.
|
// We still need to configure the debug print calls etc.
|
||||||
trace('Initializing Polymod (using no mods)...');
|
trace('Initializing Polymod (using no mods)...');
|
||||||
loadModsById([]);
|
loadModsById([]);
|
||||||
@@ -250,6 +255,9 @@ class PolymodHandler
|
|||||||
Polymod.addImportAlias('lime.utils.Assets', funkin.Assets);
|
Polymod.addImportAlias('lime.utils.Assets', funkin.Assets);
|
||||||
Polymod.addImportAlias('openfl.utils.Assets', funkin.Assets);
|
Polymod.addImportAlias('openfl.utils.Assets', funkin.Assets);
|
||||||
|
|
||||||
|
// `funkin.util.FileUtil` has unrestricted access to the file system.
|
||||||
|
Polymod.addImportAlias('funkin.util.FileUtil', funkin.util.FileUtilSandboxed);
|
||||||
|
|
||||||
// Add blacklisting for prohibited classes and packages.
|
// Add blacklisting for prohibited classes and packages.
|
||||||
|
|
||||||
// `Sys`
|
// `Sys`
|
||||||
@@ -480,6 +488,7 @@ class PolymodHandler
|
|||||||
SpeakerRegistry.instance.loadEntries();
|
SpeakerRegistry.instance.loadEntries();
|
||||||
AlbumRegistry.instance.loadEntries();
|
AlbumRegistry.instance.loadEntries();
|
||||||
StageRegistry.instance.loadEntries();
|
StageRegistry.instance.loadEntries();
|
||||||
|
StickerRegistry.instance.loadEntries();
|
||||||
|
|
||||||
CharacterDataParser.loadCharacterCache(); // TODO: Migrate characters to BaseRegistry.
|
CharacterDataParser.loadCharacterCache(); // TODO: Migrate characters to BaseRegistry.
|
||||||
NoteKindManager.loadScripts();
|
NoteKindManager.loadScripts();
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import funkin.data.song.SongData.SongEventData;
|
|||||||
import flixel.FlxState;
|
import flixel.FlxState;
|
||||||
import flixel.FlxSubState;
|
import flixel.FlxSubState;
|
||||||
import funkin.play.notes.NoteSprite;
|
import funkin.play.notes.NoteSprite;
|
||||||
|
import funkin.play.notes.SustainTrail;
|
||||||
import funkin.play.cutscene.dialogue.Conversation;
|
import funkin.play.cutscene.dialogue.Conversation;
|
||||||
import funkin.play.Countdown.CountdownStep;
|
import funkin.play.Countdown.CountdownStep;
|
||||||
import funkin.play.notes.NoteDirection;
|
import funkin.play.notes.NoteDirection;
|
||||||
@@ -226,6 +227,48 @@ class GhostMissNoteScriptEvent extends ScriptEvent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class HoldNoteScriptEvent extends NoteScriptEvent
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The hold note that was hit (or dropped).
|
||||||
|
*/
|
||||||
|
public var holdNote:SustainTrail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The score the player received for hitting the note.
|
||||||
|
*/
|
||||||
|
public var score:Int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the hit causes a combo break.
|
||||||
|
*/
|
||||||
|
public var isComboBreak:Bool = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time difference when the player hit the note
|
||||||
|
*/
|
||||||
|
public var hitDiff:Float = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this note hit causes a note splash to display.
|
||||||
|
* Defaults to true only on "sick" notes.
|
||||||
|
*/
|
||||||
|
public var doesNotesplash:Bool = false;
|
||||||
|
|
||||||
|
public function new(type:ScriptEventType, holdNote:SustainTrail, healthChange:Float, score:Int, isComboBreak:Bool, cancelable:Bool = false):Void
|
||||||
|
{
|
||||||
|
super(type, null, healthChange, comboCount, true);
|
||||||
|
this.holdNote = holdNote;
|
||||||
|
this.score = score;
|
||||||
|
this.isComboBreak = isComboBreak;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override function toString():String
|
||||||
|
{
|
||||||
|
return 'HoldNoteScriptEvent(type=$type, holdNote=$holdNote, healthChange=$healthChange, score=$score, isComboBreak=$isComboBreak, cancelable=$cancelable)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An event that is fired when the song reaches an event.
|
* An event that is fired when the song reaches an event.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -90,6 +90,9 @@ class ScriptEventDispatcher
|
|||||||
case NOTE_MISS:
|
case NOTE_MISS:
|
||||||
t.onNoteMiss(cast event);
|
t.onNoteMiss(cast event);
|
||||||
return;
|
return;
|
||||||
|
case NOTE_HOLD_DROP:
|
||||||
|
t.onNoteHoldDrop(cast event);
|
||||||
|
return;
|
||||||
default: // Continue;
|
default: // Continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,6 +88,14 @@ enum abstract ScriptEventType(String) from String to String
|
|||||||
*/
|
*/
|
||||||
var NOTE_MISS = 'NOTE_MISS';
|
var NOTE_MISS = 'NOTE_MISS';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a character lets go of a hold note.
|
||||||
|
* Important information such as note data, player/opponent, etc. are all provided.
|
||||||
|
*
|
||||||
|
* This event is not cancelable.
|
||||||
|
*/
|
||||||
|
var NOTE_HOLD_DROP = 'NOTE_HOLD_DROP';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a character presses a note when there was none there, causing them to lose health.
|
* Called when a character presses a note when there was none there, causing them to lose health.
|
||||||
* Important information such as direction pressed, etc. are all provided.
|
* Important information such as direction pressed, etc. are all provided.
|
||||||
|
|||||||
@@ -57,6 +57,9 @@ class Module implements IPlayStateScriptedClass implements IStateChangingScripte
|
|||||||
|
|
||||||
// TODO: Half of these aren't actually being called!!!!!!!
|
// TODO: Half of these aren't actually being called!!!!!!!
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when ANY script event is dispatched.
|
||||||
|
*/
|
||||||
public function onScriptEvent(event:ScriptEvent) {}
|
public function onScriptEvent(event:ScriptEvent) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,55 +74,140 @@ class Module implements IPlayStateScriptedClass implements IStateChangingScripte
|
|||||||
*/
|
*/
|
||||||
public function onDestroy(event:ScriptEvent) {}
|
public function onDestroy(event:ScriptEvent) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called every frame.
|
||||||
|
*/
|
||||||
public function onUpdate(event:UpdateScriptEvent) {}
|
public function onUpdate(event:UpdateScriptEvent) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the game is paused.
|
||||||
|
*/
|
||||||
public function onPause(event:PauseScriptEvent) {}
|
public function onPause(event:PauseScriptEvent) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the game is resumed.
|
||||||
|
*/
|
||||||
public function onResume(event:ScriptEvent) {}
|
public function onResume(event:ScriptEvent) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the song begins.
|
||||||
|
*/
|
||||||
public function onSongStart(event:ScriptEvent) {}
|
public function onSongStart(event:ScriptEvent) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the song ends.
|
||||||
|
*/
|
||||||
public function onSongEnd(event:ScriptEvent) {}
|
public function onSongEnd(event:ScriptEvent) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the player dies.
|
||||||
|
*/
|
||||||
public function onGameOver(event:ScriptEvent) {}
|
public function onGameOver(event:ScriptEvent) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a note on the strumline has been rendered and is now onscreen.
|
||||||
|
* This gets dispatched for both the player and opponent strumlines.
|
||||||
|
*/
|
||||||
public function onNoteIncoming(event:NoteScriptEvent) {}
|
public function onNoteIncoming(event:NoteScriptEvent) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a note has been hit.
|
||||||
|
* This gets dispatched for both the player and opponent strumlines.
|
||||||
|
*/
|
||||||
public function onNoteHit(event:HitNoteScriptEvent) {}
|
public function onNoteHit(event:HitNoteScriptEvent) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a note has been missed.
|
||||||
|
* This gets dispatched for both the player and opponent strumlines.
|
||||||
|
*/
|
||||||
public function onNoteMiss(event:NoteScriptEvent) {}
|
public function onNoteMiss(event:NoteScriptEvent) {}
|
||||||
|
|
||||||
|
public function onNoteHoldDrop(event:HoldNoteScriptEvent) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the player presses a key without any notes present.
|
||||||
|
*/
|
||||||
public function onNoteGhostMiss(event:GhostMissNoteScriptEvent) {}
|
public function onNoteGhostMiss(event:GhostMissNoteScriptEvent) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a step is hit in the song.
|
||||||
|
*/
|
||||||
public function onStepHit(event:SongTimeScriptEvent) {}
|
public function onStepHit(event:SongTimeScriptEvent) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a beat is hit in the song.
|
||||||
|
*/
|
||||||
public function onBeatHit(event:SongTimeScriptEvent) {}
|
public function onBeatHit(event:SongTimeScriptEvent) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a song event is triggered.
|
||||||
|
*/
|
||||||
public function onSongEvent(event:SongEventScriptEvent) {}
|
public function onSongEvent(event:SongEventScriptEvent) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the countdown begins.
|
||||||
|
*/
|
||||||
public function onCountdownStart(event:CountdownScriptEvent) {}
|
public function onCountdownStart(event:CountdownScriptEvent) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called for every step in the countdown.
|
||||||
|
*/
|
||||||
public function onCountdownStep(event:CountdownScriptEvent) {}
|
public function onCountdownStep(event:CountdownScriptEvent) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the countdown ends, but BEFORE the song starts.
|
||||||
|
*/
|
||||||
public function onCountdownEnd(event:CountdownScriptEvent) {}
|
public function onCountdownEnd(event:CountdownScriptEvent) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the song's chart has been parsed and loaded.
|
||||||
|
*/
|
||||||
public function onSongLoaded(event:SongLoadScriptEvent) {}
|
public function onSongLoaded(event:SongLoadScriptEvent) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the game is about to switch to a new state.
|
||||||
|
*/
|
||||||
public function onStateChangeBegin(event:StateChangeScriptEvent) {}
|
public function onStateChangeBegin(event:StateChangeScriptEvent) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after the game has switched to a new state.
|
||||||
|
*/
|
||||||
public function onStateChangeEnd(event:StateChangeScriptEvent) {}
|
public function onStateChangeEnd(event:StateChangeScriptEvent) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the game regains focus.
|
||||||
|
* This does not get called if "Auto Pause" is disabled.
|
||||||
|
*/
|
||||||
public function onFocusGained(event:FocusScriptEvent) {}
|
public function onFocusGained(event:FocusScriptEvent) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the game loses focus.
|
||||||
|
* This does not get called if "Auto Pause" is disabled.
|
||||||
|
*/
|
||||||
public function onFocusLost(event:FocusScriptEvent) {}
|
public function onFocusLost(event:FocusScriptEvent) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the game is about to open a substate.
|
||||||
|
*/
|
||||||
public function onSubStateOpenBegin(event:SubStateScriptEvent) {}
|
public function onSubStateOpenBegin(event:SubStateScriptEvent) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a substate has been opened.
|
||||||
|
*/
|
||||||
public function onSubStateOpenEnd(event:SubStateScriptEvent) {}
|
public function onSubStateOpenEnd(event:SubStateScriptEvent) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the game is about to close a substate.
|
||||||
|
*/
|
||||||
public function onSubStateCloseBegin(event:SubStateScriptEvent) {}
|
public function onSubStateCloseBegin(event:SubStateScriptEvent) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a substate has been closed.
|
||||||
|
*/
|
||||||
public function onSubStateCloseEnd(event:SubStateScriptEvent) {}
|
public function onSubStateCloseEnd(event:SubStateScriptEvent) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the song has been restarted.
|
||||||
|
*/
|
||||||
public function onSongRetry(event:SongRetryEvent) {}
|
public function onSongRetry(event:SongRetryEvent) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -255,7 +255,7 @@ class Countdown
|
|||||||
var path = noteStyle.getCountdownSoundPath(step);
|
var path = noteStyle.getCountdownSoundPath(step);
|
||||||
if (path == null) return null;
|
if (path == null) return null;
|
||||||
|
|
||||||
return FunkinSound.playOnce(path, Constants.COUNTDOWN_VOLUME);
|
return FunkinSound.playOnce(path, Constants.COUNTDOWN_VOLUME, null, null, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function decrement(step:CountdownStep):CountdownStep
|
public static function decrement(step:CountdownStep):CountdownStep
|
||||||
|
|||||||
@@ -291,7 +291,7 @@ class GameOverSubState extends MusicBeatSubState
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var targetState:funkin.ui.transition.StickerSubState->FlxState = (PlayStatePlaylist.isStoryMode) ? (sticker) ->
|
var targetState:funkin.ui.transition.stickers.StickerSubState->FlxState = (PlayStatePlaylist.isStoryMode) ? (sticker) ->
|
||||||
new StoryMenuState(sticker) : (sticker) -> FreeplayState.build(sticker);
|
new StoryMenuState(sticker) : (sticker) -> FreeplayState.build(sticker);
|
||||||
|
|
||||||
if (PlayStatePlaylist.isStoryMode)
|
if (PlayStatePlaylist.isStoryMode)
|
||||||
@@ -299,16 +299,20 @@ class GameOverSubState extends MusicBeatSubState
|
|||||||
PlayStatePlaylist.reset();
|
PlayStatePlaylist.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
var playerCharacterId = PlayerRegistry.instance.getCharacterOwnerId(PlayState.instance.currentChart.characters.player);
|
var stickerPackId:Null<String> = PlayState.instance.currentChart.stickerPack;
|
||||||
var stickerSet = (playerCharacterId == "pico") ? "stickers-set-2" : "stickers-set-1";
|
|
||||||
var stickerPack = switch (PlayState.instance.currentChart.song.id)
|
|
||||||
{
|
|
||||||
case "tutorial": "tutorial";
|
|
||||||
case "darnell" | "lit-up" | "2hot": "weekend";
|
|
||||||
default: "all";
|
|
||||||
};
|
|
||||||
|
|
||||||
openSubState(new funkin.ui.transition.StickerSubState({targetState: targetState, stickerSet: stickerSet, stickerPack: stickerPack}));
|
if (stickerPackId == null)
|
||||||
|
{
|
||||||
|
var playerCharacterId = PlayerRegistry.instance.getCharacterOwnerId(PlayState.instance.currentChart.characters.player);
|
||||||
|
var playerCharacter = PlayerRegistry.instance.fetchEntry(playerCharacterId ?? Constants.DEFAULT_CHARACTER);
|
||||||
|
|
||||||
|
if (playerCharacter != null)
|
||||||
|
{
|
||||||
|
stickerPackId = playerCharacter.getStickerPackID();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
openSubState(new funkin.ui.transition.stickers.StickerSubState({targetState: targetState, stickerPack: stickerPackId}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,8 +82,8 @@ class PauseSubState extends MusicBeatSubState
|
|||||||
*/
|
*/
|
||||||
static final PAUSE_MENU_ENTRIES_CONVERSATION:Array<PauseMenuEntry> = [
|
static final PAUSE_MENU_ENTRIES_CONVERSATION:Array<PauseMenuEntry> = [
|
||||||
{text: 'Resume', callback: resume},
|
{text: 'Resume', callback: resume},
|
||||||
{text: 'Restart Dialogue', callback: restartConversation},
|
|
||||||
{text: 'Skip Dialogue', callback: skipConversation},
|
{text: 'Skip Dialogue', callback: skipConversation},
|
||||||
|
{text: 'Restart Dialogue', callback: restartConversation},
|
||||||
{text: 'Exit to Menu', callback: quitToMenu},
|
{text: 'Exit to Menu', callback: quitToMenu},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -737,8 +737,8 @@ class PauseSubState extends MusicBeatSubState
|
|||||||
FlxTransitionableState.skipNextTransIn = true;
|
FlxTransitionableState.skipNextTransIn = true;
|
||||||
FlxTransitionableState.skipNextTransOut = true;
|
FlxTransitionableState.skipNextTransOut = true;
|
||||||
|
|
||||||
var targetState:funkin.ui.transition.StickerSubState->FlxState = (PlayStatePlaylist.isStoryMode) ? (sticker) -> new StoryMenuState(sticker) : (sticker) ->
|
var targetState:funkin.ui.transition.stickers.StickerSubState->FlxState = (PlayStatePlaylist.isStoryMode) ? (sticker) ->
|
||||||
FreeplayState.build(sticker);
|
new StoryMenuState(sticker) : (sticker) -> FreeplayState.build(sticker);
|
||||||
|
|
||||||
// Do this AFTER because this resets the value of isStoryMode!
|
// Do this AFTER because this resets the value of isStoryMode!
|
||||||
if (PlayStatePlaylist.isStoryMode)
|
if (PlayStatePlaylist.isStoryMode)
|
||||||
@@ -746,16 +746,20 @@ class PauseSubState extends MusicBeatSubState
|
|||||||
PlayStatePlaylist.reset();
|
PlayStatePlaylist.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
var playerCharacterId = PlayerRegistry.instance.getCharacterOwnerId(PlayState.instance.currentChart.characters.player);
|
var stickerPackId:Null<String> = PlayState.instance.currentChart.stickerPack;
|
||||||
var stickerSet = (playerCharacterId == "pico") ? "stickers-set-2" : "stickers-set-1";
|
|
||||||
var stickerPack = switch (PlayState.instance.currentChart.song.id)
|
|
||||||
{
|
|
||||||
case "tutorial": "tutorial";
|
|
||||||
case "darnell" | "lit-up" | "2hot": "weekend";
|
|
||||||
default: "all";
|
|
||||||
};
|
|
||||||
|
|
||||||
state.openSubState(new funkin.ui.transition.StickerSubState({targetState: targetState, stickerSet: stickerSet, stickerPack: stickerPack}));
|
if (stickerPackId == null)
|
||||||
|
{
|
||||||
|
var playerCharacterId = PlayerRegistry.instance.getCharacterOwnerId(PlayState.instance.currentChart.characters.player);
|
||||||
|
var playerCharacter = PlayerRegistry.instance.fetchEntry(playerCharacterId ?? Constants.DEFAULT_CHARACTER);
|
||||||
|
|
||||||
|
if (playerCharacter != null)
|
||||||
|
{
|
||||||
|
stickerPackId = playerCharacter.getStickerPackID();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.openSubState(new funkin.ui.transition.stickers.StickerSubState({targetState: targetState, stickerPack: stickerPackId}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+77
-126
@@ -32,7 +32,6 @@ import funkin.api.newgrounds.Events;
|
|||||||
import funkin.modding.events.ScriptEventDispatcher;
|
import funkin.modding.events.ScriptEventDispatcher;
|
||||||
import funkin.play.character.BaseCharacter;
|
import funkin.play.character.BaseCharacter;
|
||||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||||
import funkin.play.components.ComboMilestone;
|
|
||||||
import funkin.play.components.HealthIcon;
|
import funkin.play.components.HealthIcon;
|
||||||
import funkin.play.components.PopUpStuff;
|
import funkin.play.components.PopUpStuff;
|
||||||
import funkin.play.cutscene.dialogue.Conversation;
|
import funkin.play.cutscene.dialogue.Conversation;
|
||||||
@@ -657,9 +656,6 @@ class PlayState extends MusicBeatSubState
|
|||||||
cameraFollowPoint = new FlxObject(0, 0);
|
cameraFollowPoint = new FlxObject(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reduce physics accuracy (who cares!!!) to improve animation quality.
|
|
||||||
FlxG.fixedTimestep = false;
|
|
||||||
|
|
||||||
// This state receives update() even when a substate is active.
|
// This state receives update() even when a substate is active.
|
||||||
this.persistentUpdate = true;
|
this.persistentUpdate = true;
|
||||||
// This state receives draw calls even when a substate is active.
|
// This state receives draw calls even when a substate is active.
|
||||||
@@ -838,8 +834,6 @@ class PlayState extends MusicBeatSubState
|
|||||||
startingSong = true;
|
startingSong = true;
|
||||||
isPlayerDying = false;
|
isPlayerDying = false;
|
||||||
|
|
||||||
inputSpitter = [];
|
|
||||||
|
|
||||||
// Reset music properly.
|
// Reset music properly.
|
||||||
if (FlxG.sound.music != null)
|
if (FlxG.sound.music != null)
|
||||||
{
|
{
|
||||||
@@ -881,6 +875,9 @@ class PlayState extends MusicBeatSubState
|
|||||||
// Delete all notes and reset the arrays.
|
// Delete all notes and reset the arrays.
|
||||||
regenNoteData();
|
regenNoteData();
|
||||||
|
|
||||||
|
// so the song doesn't start too early :D
|
||||||
|
Conductor.instance.update(-5000, false);
|
||||||
|
|
||||||
// Reset camera zooming
|
// Reset camera zooming
|
||||||
cameraBopIntensity = Constants.DEFAULT_BOP_INTENSITY;
|
cameraBopIntensity = Constants.DEFAULT_BOP_INTENSITY;
|
||||||
hudCameraZoomIntensity = (cameraBopIntensity - 1.0) * 2.0;
|
hudCameraZoomIntensity = (cameraBopIntensity - 1.0) * 2.0;
|
||||||
@@ -889,11 +886,20 @@ class PlayState extends MusicBeatSubState
|
|||||||
health = Constants.HEALTH_STARTING;
|
health = Constants.HEALTH_STARTING;
|
||||||
songScore = 0;
|
songScore = 0;
|
||||||
Highscore.tallies.combo = 0;
|
Highscore.tallies.combo = 0;
|
||||||
Countdown.performCountdown();
|
// timer for vwoosh
|
||||||
|
var vwooshTimer = new FlxTimer();
|
||||||
|
vwooshTimer.start(0.5, function(t:FlxTimer) {
|
||||||
|
Conductor.instance.update(startTimestamp - Conductor.instance.combinedOffset, false);
|
||||||
|
if (playerStrumline.notes.length == 0) playerStrumline.updateNotes();
|
||||||
|
if (opponentStrumline.notes.length == 0) opponentStrumline.updateNotes();
|
||||||
|
playerStrumline.vwooshInNotes();
|
||||||
|
opponentStrumline.vwooshInNotes();
|
||||||
|
Countdown.performCountdown();
|
||||||
|
});
|
||||||
|
|
||||||
// Reset the health icons.
|
// Reset the health icons.
|
||||||
currentStage.getBoyfriend().initHealthIcon(false);
|
currentStage?.getBoyfriend()?.initHealthIcon(false);
|
||||||
currentStage.getDad().initHealthIcon(true);
|
currentStage?.getDad()?.initHealthIcon(true);
|
||||||
|
|
||||||
needsReset = false;
|
needsReset = false;
|
||||||
}
|
}
|
||||||
@@ -942,7 +948,7 @@ class PlayState extends MusicBeatSubState
|
|||||||
// Attempt to pause the game.
|
// Attempt to pause the game.
|
||||||
if ((controls.PAUSE || androidPause) && isInCountdown && mayPauseGame && !justUnpaused)
|
if ((controls.PAUSE || androidPause) && isInCountdown && mayPauseGame && !justUnpaused)
|
||||||
{
|
{
|
||||||
var event = new PauseScriptEvent(FlxG.random.bool(1 / 1000));
|
var event = new PauseScriptEvent(FlxG.random.bool((1 / 1000) * 100));
|
||||||
|
|
||||||
dispatchEvent(event);
|
dispatchEvent(event);
|
||||||
|
|
||||||
@@ -1132,7 +1138,7 @@ class PlayState extends MusicBeatSubState
|
|||||||
if (!isMinimalMode)
|
if (!isMinimalMode)
|
||||||
{
|
{
|
||||||
if (iconP1 != null) iconP1.updatePosition();
|
if (iconP1 != null) iconP1.updatePosition();
|
||||||
if (iconP1 != null) iconP2.updatePosition();
|
if (iconP2 != null) iconP2.updatePosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transition to the game over substate.
|
// Transition to the game over substate.
|
||||||
@@ -1415,9 +1421,10 @@ class PlayState extends MusicBeatSubState
|
|||||||
*/
|
*/
|
||||||
override function reloadAssets():Void
|
override function reloadAssets():Void
|
||||||
{
|
{
|
||||||
|
performCleanup();
|
||||||
|
|
||||||
funkin.modding.PolymodHandler.forceReloadAssets();
|
funkin.modding.PolymodHandler.forceReloadAssets();
|
||||||
lastParams.targetSong = SongRegistry.instance.fetchEntry(currentSong.id);
|
lastParams.targetSong = SongRegistry.instance.fetchEntry(currentSong.id);
|
||||||
this.remove(currentStage);
|
|
||||||
LoadingState.loadPlayState(lastParams);
|
LoadingState.loadPlayState(lastParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1510,35 +1517,6 @@ class PlayState extends MusicBeatSubState
|
|||||||
|
|
||||||
// Break up into individual lines to aid debugging.
|
// Break up into individual lines to aid debugging.
|
||||||
|
|
||||||
var shouldShowComboText:Bool = false;
|
|
||||||
// TODO: Re-enable combo text (how to do this without sections?).
|
|
||||||
// if (currentSong != null)
|
|
||||||
// {
|
|
||||||
// shouldShowComboText = (Conductor.instance.currentBeat % 8 == 7);
|
|
||||||
// var daSection = .getSong()[Std.int(Conductor.instance.currentBeat / 16)];
|
|
||||||
// shouldShowComboText = shouldShowComboText && (daSection != null && daSection.mustHitSection);
|
|
||||||
// shouldShowComboText = shouldShowComboText && (Highscore.tallies.combo > 5);
|
|
||||||
//
|
|
||||||
// var daNextSection = .getSong()[Std.int(Conductor.instance.currentBeat / 16) + 1];
|
|
||||||
// var isEndOfSong = .getSong().length < Std.int(Conductor.instance.currentBeat / 16);
|
|
||||||
// shouldShowComboText = shouldShowComboText && (isEndOfSong || (daNextSection != null && !daNextSection.mustHitSection));
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (shouldShowComboText)
|
|
||||||
{
|
|
||||||
var animShit:ComboMilestone = new ComboMilestone(-100, 300, Highscore.tallies.combo);
|
|
||||||
animShit.scrollFactor.set(0.6, 0.6);
|
|
||||||
animShit.zIndex = 1100;
|
|
||||||
animShit.cameras = [camHUD];
|
|
||||||
add(animShit);
|
|
||||||
|
|
||||||
var frameShit:Float = (1 / 24) * 2; // equals 2 frames in the animation
|
|
||||||
|
|
||||||
new FlxTimer().start(((Conductor.instance.beatLengthMs / 1000) * 1.25) - frameShit, function(tmr) {
|
|
||||||
animShit.forceFinish();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (playerStrumline != null) playerStrumline.onBeatHit();
|
if (playerStrumline != null) playerStrumline.onBeatHit();
|
||||||
if (opponentStrumline != null) opponentStrumline.onBeatHit();
|
if (opponentStrumline != null) opponentStrumline.onBeatHit();
|
||||||
|
|
||||||
@@ -1812,13 +1790,13 @@ class PlayState extends MusicBeatSubState
|
|||||||
// Position the player strumline on the right half of the screen
|
// Position the player strumline on the right half of the screen
|
||||||
playerStrumline.x = FlxG.width / 2 + Constants.STRUMLINE_X_OFFSET; // Classic style
|
playerStrumline.x = FlxG.width / 2 + Constants.STRUMLINE_X_OFFSET; // Classic style
|
||||||
// playerStrumline.x = FlxG.width - playerStrumline.width - Constants.STRUMLINE_X_OFFSET; // Centered style
|
// playerStrumline.x = FlxG.width - playerStrumline.width - Constants.STRUMLINE_X_OFFSET; // Centered style
|
||||||
playerStrumline.y = Preferences.downscroll ? FlxG.height - playerStrumline.height - Constants.STRUMLINE_Y_OFFSET : Constants.STRUMLINE_Y_OFFSET;
|
playerStrumline.y = Preferences.downscroll ? FlxG.height - playerStrumline.height - Constants.STRUMLINE_Y_OFFSET - noteStyle.getStrumlineOffsets()[1] : Constants.STRUMLINE_Y_OFFSET;
|
||||||
playerStrumline.zIndex = 1001;
|
playerStrumline.zIndex = 1001;
|
||||||
playerStrumline.cameras = [camHUD];
|
playerStrumline.cameras = [camHUD];
|
||||||
|
|
||||||
// Position the opponent strumline on the left half of the screen
|
// Position the opponent strumline on the left half of the screen
|
||||||
opponentStrumline.x = Constants.STRUMLINE_X_OFFSET;
|
opponentStrumline.x = Constants.STRUMLINE_X_OFFSET;
|
||||||
opponentStrumline.y = Preferences.downscroll ? FlxG.height - opponentStrumline.height - Constants.STRUMLINE_Y_OFFSET : Constants.STRUMLINE_Y_OFFSET;
|
opponentStrumline.y = Preferences.downscroll ? FlxG.height - opponentStrumline.height - Constants.STRUMLINE_Y_OFFSET - noteStyle.getStrumlineOffsets()[1] : Constants.STRUMLINE_Y_OFFSET;
|
||||||
opponentStrumline.zIndex = 1000;
|
opponentStrumline.zIndex = 1000;
|
||||||
opponentStrumline.cameras = [camHUD];
|
opponentStrumline.cameras = [camHUD];
|
||||||
|
|
||||||
@@ -2369,7 +2347,7 @@ class PlayState extends MusicBeatSubState
|
|||||||
{
|
{
|
||||||
// Call an event to allow canceling the note miss.
|
// Call an event to allow canceling the note miss.
|
||||||
// NOTE: This is what handles the character animations!
|
// NOTE: This is what handles the character animations!
|
||||||
var event:NoteScriptEvent = new NoteScriptEvent(NOTE_MISS, note, -Constants.HEALTH_MISS_PENALTY, 0, true);
|
var event:NoteScriptEvent = new NoteScriptEvent(NOTE_MISS, note, Constants.HEALTH_MISS_PENALTY, 0, true);
|
||||||
dispatchEvent(event);
|
dispatchEvent(event);
|
||||||
|
|
||||||
// Calling event.cancelEvent() skips all the other logic! Neat!
|
// Calling event.cancelEvent() skips all the other logic! Neat!
|
||||||
@@ -2416,18 +2394,45 @@ class PlayState extends MusicBeatSubState
|
|||||||
// The player dropped a hold note.
|
// The player dropped a hold note.
|
||||||
holdNote.handledMiss = true;
|
holdNote.handledMiss = true;
|
||||||
|
|
||||||
// Mute vocals and play miss animation, but don't penalize.
|
// Mute vocals and play miss animation.
|
||||||
// vocals.playerVolume = 0;
|
// vocals.playerVolume = 0;
|
||||||
// if (currentStage != null && currentStage.getBoyfriend() != null) currentStage.getBoyfriend().playSingAnimation(holdNote.noteData.getDirection(), true);
|
// if (currentStage != null && currentStage.getBoyfriend() != null) currentStage.getBoyfriend().playSingAnimation(holdNote.noteData.getDirection(), true);
|
||||||
|
|
||||||
|
if (!isBotPlayMode)
|
||||||
|
{
|
||||||
|
if (holdNote.sustainLength > Constants.HOLD_DROP_PENALTY_THRESHOLD_MS)
|
||||||
|
{
|
||||||
|
// Penalize the player for letting go of a hold note too early.
|
||||||
|
trace('Player dropped a hold note, penalizing... (has hit: ${holdNote.hitNote})');
|
||||||
|
|
||||||
|
// Different penalty based on whether the note itself was missed,
|
||||||
|
// or the note was hit and then the hold was dropped.
|
||||||
|
var remainingLengthSec = holdNote.sustainLength / Constants.MS_PER_SEC;
|
||||||
|
var healthChangeUncapped = remainingLengthSec * Constants.HEALTH_HOLD_DROP_PENALTY_PER_SECOND;
|
||||||
|
// If the base note of the hold was missed, don't penalize them more on top of that.
|
||||||
|
var healthChangeMax = Constants.HEALTH_HOLD_DROP_PENALTY_MAX - (holdNote.hitNote ? -Constants.HEALTH_MISS_PENALTY : 0);
|
||||||
|
var healthChange = healthChangeUncapped.clamp(healthChangeMax, 0);
|
||||||
|
var scoreChange = Std.int(Constants.SCORE_HOLD_DROP_PENALTY_PER_SECOND * remainingLengthSec);
|
||||||
|
|
||||||
|
var event:HoldNoteScriptEvent = new HoldNoteScriptEvent(NOTE_HOLD_DROP, holdNote, healthChange, scoreChange, true);
|
||||||
|
dispatchEvent(event);
|
||||||
|
|
||||||
|
trace('Penalizing score by ${event.score} and health by ${event.healthChange} for dropping hold note (is combo break: ${event.isComboBreak})!');
|
||||||
|
applyScore(event.score, '', event.healthChange, event.isComboBreak);
|
||||||
|
|
||||||
|
// Play the miss sound.
|
||||||
|
vocals.playerVolume = 0;
|
||||||
|
FunkinSound.playOnce(Paths.soundRandom('missnote', 1, 3), FlxG.random.float(0.5, 0.6));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trace('Hold note too short, not penalizing...');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Spitting out the input for ravy 🙇♂️!!
|
|
||||||
*/
|
|
||||||
var inputSpitter:Array<ScoreInput> = [];
|
|
||||||
|
|
||||||
function handleSkippedNotes():Void
|
function handleSkippedNotes():Void
|
||||||
{
|
{
|
||||||
for (note in playerStrumline.notes.members)
|
for (note in playerStrumline.notes.members)
|
||||||
@@ -2612,31 +2617,10 @@ class PlayState extends MusicBeatSubState
|
|||||||
{
|
{
|
||||||
if (pressArray[i]) indices.push(i);
|
if (pressArray[i]) indices.push(i);
|
||||||
}
|
}
|
||||||
if (indices.length > 0)
|
|
||||||
{
|
|
||||||
for (i in 0...indices.length)
|
|
||||||
{
|
|
||||||
inputSpitter.push(
|
|
||||||
{
|
|
||||||
t: Std.int(Conductor.instance.songPosition),
|
|
||||||
d: indices[i],
|
|
||||||
l: 20
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
inputSpitter.push(
|
|
||||||
{
|
|
||||||
t: Std.int(Conductor.instance.songPosition),
|
|
||||||
d: -1,
|
|
||||||
l: 20
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
vocals.playerVolume = 0;
|
vocals.playerVolume = 0;
|
||||||
|
|
||||||
applyScore(-10, 'miss', healthChange, true);
|
applyScore(Scoring.getMissScore(), 'miss', healthChange, true);
|
||||||
|
|
||||||
if (playSound)
|
if (playSound)
|
||||||
{
|
{
|
||||||
@@ -2657,7 +2641,7 @@ class PlayState extends MusicBeatSubState
|
|||||||
{
|
{
|
||||||
var event:GhostMissNoteScriptEvent = new GhostMissNoteScriptEvent(direction, // Direction missed in.
|
var event:GhostMissNoteScriptEvent = new GhostMissNoteScriptEvent(direction, // Direction missed in.
|
||||||
hasPossibleNotes, // Whether there was a note you could have hit.
|
hasPossibleNotes, // Whether there was a note you could have hit.
|
||||||
- 1 * Constants.HEALTH_MISS_PENALTY, // How much health to add (negative).
|
Constants.HEALTH_GHOST_MISS_PENALTY, // How much health to add (negative).
|
||||||
- 10 // Amount of score to add (negative).
|
- 10 // Amount of score to add (negative).
|
||||||
);
|
);
|
||||||
dispatchEvent(event);
|
dispatchEvent(event);
|
||||||
@@ -2682,15 +2666,6 @@ class PlayState extends MusicBeatSubState
|
|||||||
{
|
{
|
||||||
if (pressArray[i]) indices.push(i);
|
if (pressArray[i]) indices.push(i);
|
||||||
}
|
}
|
||||||
for (i in 0...indices.length)
|
|
||||||
{
|
|
||||||
inputSpitter.push(
|
|
||||||
{
|
|
||||||
t: Std.int(Conductor.instance.songPosition),
|
|
||||||
d: indices[i],
|
|
||||||
l: 20
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.playSound)
|
if (event.playSound)
|
||||||
@@ -2754,7 +2729,7 @@ class PlayState extends MusicBeatSubState
|
|||||||
#end
|
#end
|
||||||
|
|
||||||
// 9: Toggle the old icon.
|
// 9: Toggle the old icon.
|
||||||
if (FlxG.keys.justPressed.NINE) iconP1.toggleOldIcon();
|
if (FlxG.keys.justPressed.NINE && iconP1 != null) iconP1.toggleOldIcon();
|
||||||
|
|
||||||
#if FEATURE_DEBUG_FUNCTIONS
|
#if FEATURE_DEBUG_FUNCTIONS
|
||||||
// PAGEUP: Skip forward two sections.
|
// PAGEUP: Skip forward two sections.
|
||||||
@@ -2764,8 +2739,6 @@ class PlayState extends MusicBeatSubState
|
|||||||
// SHIFT+PAGEDOWN: Skip backward twenty sections.
|
// SHIFT+PAGEDOWN: Skip backward twenty sections.
|
||||||
if (FlxG.keys.justPressed.PAGEDOWN) changeSection(FlxG.keys.pressed.SHIFT ? -20 : -2);
|
if (FlxG.keys.justPressed.PAGEDOWN) changeSection(FlxG.keys.pressed.SHIFT ? -20 : -2);
|
||||||
#end
|
#end
|
||||||
|
|
||||||
if (FlxG.keys.justPressed.B) trace(inputSpitter.join('\n'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2785,6 +2758,8 @@ class PlayState extends MusicBeatSubState
|
|||||||
Highscore.tallies.shit += 1;
|
Highscore.tallies.shit += 1;
|
||||||
case 'miss':
|
case 'miss':
|
||||||
Highscore.tallies.missed += 1;
|
Highscore.tallies.missed += 1;
|
||||||
|
default:
|
||||||
|
// Nothing!
|
||||||
}
|
}
|
||||||
health += healthChange;
|
health += healthChange;
|
||||||
if (isComboBreak)
|
if (isComboBreak)
|
||||||
@@ -2831,27 +2806,6 @@ class PlayState extends MusicBeatSubState
|
|||||||
{
|
{
|
||||||
if (pressArray[i]) indices.push(i);
|
if (pressArray[i]) indices.push(i);
|
||||||
}
|
}
|
||||||
if (indices.length > 0)
|
|
||||||
{
|
|
||||||
for (i in 0...indices.length)
|
|
||||||
{
|
|
||||||
inputSpitter.push(
|
|
||||||
{
|
|
||||||
t: Std.int(Conductor.instance.songPosition),
|
|
||||||
d: indices[i],
|
|
||||||
l: 20
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
inputSpitter.push(
|
|
||||||
{
|
|
||||||
t: Std.int(Conductor.instance.songPosition),
|
|
||||||
d: -1,
|
|
||||||
l: 20
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
comboPopUps.displayRating(daRating);
|
comboPopUps.displayRating(daRating);
|
||||||
if (combo >= 10) comboPopUps.displayCombo(combo);
|
if (combo >= 10) comboPopUps.displayCombo(combo);
|
||||||
@@ -2935,13 +2889,6 @@ class PlayState extends MusicBeatSubState
|
|||||||
dispatchEvent(event);
|
dispatchEvent(event);
|
||||||
if (event.eventCanceled) return;
|
if (event.eventCanceled) return;
|
||||||
|
|
||||||
#if sys
|
|
||||||
// spitter for ravy, teehee!!
|
|
||||||
var writer = new json2object.JsonWriter<Array<ScoreInput>>();
|
|
||||||
var output = writer.write(inputSpitter, ' ');
|
|
||||||
sys.io.File.saveContent("./scores.json", output);
|
|
||||||
#end
|
|
||||||
|
|
||||||
deathCounter = 0;
|
deathCounter = 0;
|
||||||
|
|
||||||
// TODO: This line of code makes me sad, but you can't really fix it without a breaking migration.
|
// TODO: This line of code makes me sad, but you can't really fix it without a breaking migration.
|
||||||
@@ -2975,6 +2922,10 @@ class PlayState extends MusicBeatSubState
|
|||||||
// adds current song data into the tallies for the level (story levels)
|
// adds current song data into the tallies for the level (story levels)
|
||||||
Highscore.talliesLevel = Highscore.combineTallies(Highscore.tallies, Highscore.talliesLevel);
|
Highscore.talliesLevel = Highscore.combineTallies(Highscore.tallies, Highscore.talliesLevel);
|
||||||
|
|
||||||
|
#if FEATURE_NEWGROUNDS
|
||||||
|
Leaderboards.submitSongScore(currentSong.id, suffixedDifficulty, songScore);
|
||||||
|
#end
|
||||||
|
|
||||||
if (!isPracticeMode && !isBotPlayMode)
|
if (!isPracticeMode && !isBotPlayMode)
|
||||||
{
|
{
|
||||||
#if FEATURE_NEWGROUNDS
|
#if FEATURE_NEWGROUNDS
|
||||||
@@ -3017,7 +2968,7 @@ class PlayState extends MusicBeatSubState
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Award various medals based on variation, difficulty, and scoring rank.
|
// Award various medals based on variation, difficulty, song ID, and scoring rank.
|
||||||
if (scoreRank == ScoringRank.SHIT) Medals.award(LossRating);
|
if (scoreRank == ScoringRank.SHIT) Medals.award(LossRating);
|
||||||
if (scoreRank >= ScoringRank.PERFECT && currentDifficulty == 'hard') Medals.award(PerfectRatingHard);
|
if (scoreRank >= ScoringRank.PERFECT && currentDifficulty == 'hard') Medals.award(PerfectRatingHard);
|
||||||
if (scoreRank == ScoringRank.PERFECT_GOLD && currentDifficulty == 'hard') Medals.award(GoldPerfectRatingHard);
|
if (scoreRank == ScoringRank.PERFECT_GOLD && currentDifficulty == 'hard') Medals.award(GoldPerfectRatingHard);
|
||||||
@@ -3062,19 +3013,18 @@ class PlayState extends MusicBeatSubState
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (currentSong.validScore
|
#if FEATURE_NEWGROUNDS
|
||||||
&& Save.instance.isLevelHighScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data))
|
// Award a medal for beating a Story level.
|
||||||
|
Medals.awardStoryLevel(PlayStatePlaylist.campaignId);
|
||||||
|
|
||||||
|
// Submit the score for the Story level to Newgrounds.
|
||||||
|
Leaderboards.submitLevelScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, PlayStatePlaylist.campaignScore);
|
||||||
|
|
||||||
|
Events.logCompleteLevel(PlayStatePlaylist.campaignId);
|
||||||
|
#end
|
||||||
|
|
||||||
|
if (Save.instance.isLevelHighScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data))
|
||||||
{
|
{
|
||||||
#if FEATURE_NEWGROUNDS
|
|
||||||
// Award a medal for beating a Story level.
|
|
||||||
Medals.awardStoryLevel(PlayStatePlaylist.campaignId);
|
|
||||||
|
|
||||||
// Submit the score for the Story level to Newgrounds.
|
|
||||||
Leaderboards.submitLevelScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, PlayStatePlaylist.campaignScore);
|
|
||||||
|
|
||||||
Events.logCompleteLevel(PlayStatePlaylist.campaignId);
|
|
||||||
#end
|
|
||||||
|
|
||||||
Save.instance.setLevelScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data);
|
Save.instance.setLevelScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data);
|
||||||
isNewHighscore = true;
|
isNewHighscore = true;
|
||||||
}
|
}
|
||||||
@@ -3331,6 +3281,7 @@ class PlayState extends MusicBeatSubState
|
|||||||
storyMode: PlayStatePlaylist.isStoryMode,
|
storyMode: PlayStatePlaylist.isStoryMode,
|
||||||
songId: currentChart.song.id,
|
songId: currentChart.song.id,
|
||||||
difficultyId: currentDifficulty,
|
difficultyId: currentDifficulty,
|
||||||
|
variationId: currentVariation,
|
||||||
characterId: currentChart.characters.player,
|
characterId: currentChart.characters.player,
|
||||||
title: PlayStatePlaylist.isStoryMode ? ('${PlayStatePlaylist.campaignTitle}') : ('${currentChart.songName} by ${currentChart.songArtist}'),
|
title: PlayStatePlaylist.isStoryMode ? ('${PlayStatePlaylist.campaignTitle}') : ('${currentChart.songName} by ${currentChart.songArtist}'),
|
||||||
prevScoreData: prevScoreData,
|
prevScoreData: prevScoreData,
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ class ResultScore extends FlxTypedSpriteGroup<ScoreNum>
|
|||||||
var dumbNumb = Std.parseInt(Std.string(val));
|
var dumbNumb = Std.parseInt(Std.string(val));
|
||||||
var prevNum:ScoreNum;
|
var prevNum:ScoreNum;
|
||||||
|
|
||||||
|
dumbNumb = Std.int(Math.min(dumbNumb, Math.pow(10, group.members.length) - 1));
|
||||||
|
|
||||||
while (dumbNumb > 0)
|
while (dumbNumb > 0)
|
||||||
{
|
{
|
||||||
scoreStart += 1;
|
scoreStart += 1;
|
||||||
|
|||||||
@@ -1,34 +1,37 @@
|
|||||||
package funkin.play;
|
package funkin.play;
|
||||||
|
|
||||||
import funkin.ui.story.StoryMenuState;
|
import flixel.addons.display.FlxBackdrop;
|
||||||
import funkin.graphics.adobeanimate.FlxAtlasSprite;
|
import flixel.effects.FlxFlicker;
|
||||||
import flixel.FlxSprite;
|
import flixel.FlxSprite;
|
||||||
import flixel.FlxSubState;
|
import flixel.FlxSubState;
|
||||||
import funkin.graphics.FunkinSprite;
|
|
||||||
import flixel.effects.FlxFlicker;
|
|
||||||
import flixel.graphics.frames.FlxBitmapFont;
|
import flixel.graphics.frames.FlxBitmapFont;
|
||||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||||
import flixel.math.FlxPoint;
|
import flixel.math.FlxPoint;
|
||||||
import funkin.ui.MusicBeatSubState;
|
|
||||||
import flixel.math.FlxRect;
|
import flixel.math.FlxRect;
|
||||||
import flixel.text.FlxBitmapText;
|
import flixel.text.FlxBitmapText;
|
||||||
import funkin.data.freeplay.player.PlayerRegistry;
|
|
||||||
import funkin.data.freeplay.player.PlayerData.PlayerResultsAnimationData;
|
|
||||||
import funkin.ui.freeplay.charselect.PlayableCharacter;
|
|
||||||
import flixel.util.FlxColor;
|
|
||||||
import flixel.tweens.FlxEase;
|
import flixel.tweens.FlxEase;
|
||||||
import funkin.graphics.FunkinCamera;
|
|
||||||
import funkin.ui.freeplay.FreeplayState;
|
|
||||||
import flixel.tweens.FlxTween;
|
import flixel.tweens.FlxTween;
|
||||||
import flixel.addons.display.FlxBackdrop;
|
import flixel.util.FlxColor;
|
||||||
import funkin.audio.FunkinSound;
|
|
||||||
import flixel.util.FlxGradient;
|
import flixel.util.FlxGradient;
|
||||||
import flixel.util.FlxTimer;
|
import flixel.util.FlxTimer;
|
||||||
import funkin.play.scoring.Scoring;
|
import funkin.audio.FunkinSound;
|
||||||
import funkin.save.Save.SaveScoreData;
|
import funkin.data.freeplay.player.PlayerData.PlayerResultsAnimationData;
|
||||||
|
import funkin.data.freeplay.player.PlayerRegistry;
|
||||||
|
import funkin.graphics.adobeanimate.FlxAtlasSprite;
|
||||||
|
import funkin.graphics.FunkinCamera;
|
||||||
|
import funkin.graphics.FunkinSprite;
|
||||||
import funkin.graphics.shaders.LeftMaskShader;
|
import funkin.graphics.shaders.LeftMaskShader;
|
||||||
import funkin.play.components.TallyCounter;
|
|
||||||
import funkin.play.components.ClearPercentCounter;
|
import funkin.play.components.ClearPercentCounter;
|
||||||
|
import funkin.play.components.TallyCounter;
|
||||||
|
import funkin.play.scoring.Scoring;
|
||||||
|
import funkin.play.song.Song;
|
||||||
|
import funkin.data.song.SongRegistry;
|
||||||
|
import funkin.save.Save.SaveScoreData;
|
||||||
|
import funkin.ui.freeplay.charselect.PlayableCharacter;
|
||||||
|
import funkin.ui.freeplay.FreeplayState;
|
||||||
|
import funkin.ui.MusicBeatSubState;
|
||||||
|
import funkin.ui.story.StoryMenuState;
|
||||||
|
import funkin.modding.base.ScriptedFlxAtlasSprite;
|
||||||
#if FEATURE_NEWGROUNDS
|
#if FEATURE_NEWGROUNDS
|
||||||
import funkin.api.newgrounds.Medals;
|
import funkin.api.newgrounds.Medals;
|
||||||
#end
|
#end
|
||||||
@@ -72,9 +75,10 @@ class ResultState extends MusicBeatSubState
|
|||||||
delay:Float
|
delay:Float
|
||||||
}> = [];
|
}> = [];
|
||||||
|
|
||||||
var playerCharacterId:Null<String>;
|
var playerCharacterId:Null<String> = null;
|
||||||
|
var playerCharacter:Null<PlayableCharacter> = null;
|
||||||
|
|
||||||
var introMusicAudio:Null<FunkinSound>;
|
var introMusicAudio:Null<FunkinSound> = null;
|
||||||
|
|
||||||
var rankBg:FunkinSprite;
|
var rankBg:FunkinSprite;
|
||||||
final cameraBG:FunkinCamera;
|
final cameraBG:FunkinCamera;
|
||||||
@@ -171,7 +175,7 @@ class ResultState extends MusicBeatSubState
|
|||||||
|
|
||||||
// Fetch playable character data. Default to BF on the results screen if we can't find it.
|
// Fetch playable character data. Default to BF on the results screen if we can't find it.
|
||||||
playerCharacterId = PlayerRegistry.instance.getCharacterOwnerId(params.characterId);
|
playerCharacterId = PlayerRegistry.instance.getCharacterOwnerId(params.characterId);
|
||||||
var playerCharacter:Null<PlayableCharacter> = PlayerRegistry.instance.fetchEntry(playerCharacterId ?? 'bf');
|
playerCharacter = PlayerRegistry.instance.fetchEntry(playerCharacterId ?? 'bf');
|
||||||
|
|
||||||
trace('Got playable character: ${playerCharacter?.getName()}');
|
trace('Got playable character: ${playerCharacter?.getName()}');
|
||||||
// Query JSON data based on the rank, then use that to build the animation(s) the player sees.
|
// Query JSON data based on the rank, then use that to build the animation(s) the player sees.
|
||||||
@@ -186,13 +190,27 @@ class ResultState extends MusicBeatSubState
|
|||||||
if (Preferences.naughtyness && animData.filter != "naughty" || !Preferences.naughtyness && animData.filter != "safe") continue;
|
if (Preferences.naughtyness && animData.filter != "naughty" || !Preferences.naughtyness && animData.filter != "safe") continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var animPath:String = Paths.stripLibrary(animData.assetPath);
|
var animPath:String = "";
|
||||||
var animLibrary:String = Paths.getLibrary(animData.assetPath);
|
var animLibrary:String = "";
|
||||||
|
|
||||||
|
if (animData.assetPath != null)
|
||||||
|
{
|
||||||
|
animPath = Paths.stripLibrary(animData.assetPath);
|
||||||
|
animLibrary = Paths.getLibrary(animData.assetPath);
|
||||||
|
}
|
||||||
var offsets = animData.offsets ?? [0, 0];
|
var offsets = animData.offsets ?? [0, 0];
|
||||||
switch (animData.renderType)
|
switch (animData.renderType)
|
||||||
{
|
{
|
||||||
case 'animateatlas':
|
case 'animateatlas':
|
||||||
var animation:FlxAtlasSprite = new FlxAtlasSprite(offsets[0], offsets[1], Paths.animateAtlas(animPath, animLibrary));
|
@:nullSafety(Off)
|
||||||
|
var animation:FlxAtlasSprite = null;
|
||||||
|
|
||||||
|
if (animData.scriptClass != null) animation = ScriptedFlxAtlasSprite.init(animData.scriptClass, offsets[0], offsets[1]);
|
||||||
|
else
|
||||||
|
animation = new FlxAtlasSprite(offsets[0], offsets[1], Paths.animateAtlas(animPath, animLibrary));
|
||||||
|
|
||||||
|
if (animation == null) continue;
|
||||||
|
|
||||||
animation.zIndex = animData.zIndex ?? 500;
|
animation.zIndex = animData.zIndex ?? 500;
|
||||||
|
|
||||||
animation.scale.set(animData.scale ?? 1.0, animData.scale ?? 1.0);
|
animation.scale.set(animData.scale ?? 1.0, animData.scale ?? 1.0);
|
||||||
@@ -221,7 +239,6 @@ class ResultState extends MusicBeatSubState
|
|||||||
animation.onAnimationComplete.add((_name:String) -> {
|
animation.onAnimationComplete.add((_name:String) -> {
|
||||||
if (animation != null)
|
if (animation != null)
|
||||||
{
|
{
|
||||||
trace("AHAHAH");
|
|
||||||
animation.anim.curFrame = animData.loopFrame ?? 0;
|
animation.anim.curFrame = animData.loopFrame ?? 0;
|
||||||
animation.anim.play(); // unpauses this anim, since it's on PlayOnce!
|
animation.anim.play(); // unpauses this anim, since it's on PlayOnce!
|
||||||
}
|
}
|
||||||
@@ -267,7 +284,7 @@ class ResultState extends MusicBeatSubState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var diffSpr:String = 'diff_${params?.difficultyId ?? 'Normal'}';
|
var diffSpr:String = 'diff_${params?.difficultyId ?? Constants.DEFAULT_DIFFICULTY}';
|
||||||
difficulty.loadGraphic(Paths.image("resultScreen/" + diffSpr));
|
difficulty.loadGraphic(Paths.image("resultScreen/" + diffSpr));
|
||||||
add(difficulty);
|
add(difficulty);
|
||||||
|
|
||||||
@@ -471,7 +488,8 @@ class ResultState extends MusicBeatSubState
|
|||||||
FlxTween.tween(bgFlash, {alpha: 0}, 5 / 24);
|
FlxTween.tween(bgFlash, {alpha: 0}, 5 / 24);
|
||||||
// NOTE: Only divide if totalNotes > 0 to prevent divide-by-zero errors.
|
// NOTE: Only divide if totalNotes > 0 to prevent divide-by-zero errors.
|
||||||
var clearPercentFloat = params.scoreData.tallies.totalNotes == 0 ? 0.0 : (params.scoreData.tallies.sick +
|
var clearPercentFloat = params.scoreData.tallies.totalNotes == 0 ? 0.0 : (params.scoreData.tallies.sick +
|
||||||
params.scoreData.tallies.good) / params.scoreData.tallies.totalNotes * 100;
|
params.scoreData.tallies.good
|
||||||
|
- params.scoreData.tallies.missed) / params.scoreData.tallies.totalNotes * 100;
|
||||||
clearPercentTarget = Math.floor(clearPercentFloat);
|
clearPercentTarget = Math.floor(clearPercentFloat);
|
||||||
// Prevent off-by-one errors.
|
// Prevent off-by-one errors.
|
||||||
|
|
||||||
@@ -489,7 +507,7 @@ class ResultState extends MusicBeatSubState
|
|||||||
// Only play the tick sound if the number increased.
|
// Only play the tick sound if the number increased.
|
||||||
if (clearPercentLerp != clearPercentCounter.curNumber)
|
if (clearPercentLerp != clearPercentCounter.curNumber)
|
||||||
{
|
{
|
||||||
trace('$clearPercentLerp and ${clearPercentCounter.curNumber}');
|
// trace('$clearPercentLerp and ${clearPercentCounter.curNumber}');
|
||||||
clearPercentLerp = clearPercentCounter.curNumber;
|
clearPercentLerp = clearPercentCounter.curNumber;
|
||||||
FunkinSound.playOnce(Paths.sound('scrollMenu'));
|
FunkinSound.playOnce(Paths.sound('scrollMenu'));
|
||||||
}
|
}
|
||||||
@@ -502,8 +520,9 @@ class ResultState extends MusicBeatSubState
|
|||||||
clearPercentCounter.curNumber = clearPercentTarget;
|
clearPercentCounter.curNumber = clearPercentTarget;
|
||||||
|
|
||||||
#if FEATURE_NEWGROUNDS
|
#if FEATURE_NEWGROUNDS
|
||||||
|
var isScoreValid = !(params?.isPracticeMode ?? false) && !(params?.isBotPlayMode ?? false);
|
||||||
// This is the easiest spot to do the medal calculation lol.
|
// This is the easiest spot to do the medal calculation lol.
|
||||||
if (clearPercentTarget == 69) Medals.award(Nice);
|
if (isScoreValid && clearPercentTarget == 69) Medals.award(Nice);
|
||||||
#end
|
#end
|
||||||
|
|
||||||
clearPercentCounter.flash(true);
|
clearPercentCounter.flash(true);
|
||||||
@@ -697,34 +716,6 @@ class ResultState extends MusicBeatSubState
|
|||||||
|
|
||||||
override function update(elapsed:Float):Void
|
override function update(elapsed:Float):Void
|
||||||
{
|
{
|
||||||
// if(FlxG.keys.justPressed.R){
|
|
||||||
// FlxG.switchState(() -> new funkin.play.ResultState(
|
|
||||||
// {
|
|
||||||
// storyMode: false,
|
|
||||||
// title: "Cum Song Erect by Kawai Sprite",
|
|
||||||
// songId: "cum",
|
|
||||||
// difficultyId: "nightmare",
|
|
||||||
// isNewHighscore: true,
|
|
||||||
// scoreData:
|
|
||||||
// {
|
|
||||||
// score: 1_234_567,
|
|
||||||
// tallies:
|
|
||||||
// {
|
|
||||||
// sick: 200,
|
|
||||||
// good: 0,
|
|
||||||
// bad: 0,
|
|
||||||
// shit: 0,
|
|
||||||
// missed: 0,
|
|
||||||
// combo: 0,
|
|
||||||
// maxCombo: 69,
|
|
||||||
// totalNotesHit: 200,
|
|
||||||
// totalNotes: 200 // 0,
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// }));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// maskShaderSongName.swagSprX = songName.x;
|
|
||||||
maskShaderDifficulty.swagSprX = difficulty.x;
|
maskShaderDifficulty.swagSprX = difficulty.x;
|
||||||
|
|
||||||
if (movingSongStuff)
|
if (movingSongStuff)
|
||||||
@@ -745,8 +736,16 @@ class ResultState extends MusicBeatSubState
|
|||||||
if (controls.PAUSE || controls.ACCEPT)
|
if (controls.PAUSE || controls.ACCEPT)
|
||||||
{
|
{
|
||||||
if (_parentState is funkin.ui.debug.results.ResultsDebugSubState)
|
if (_parentState is funkin.ui.debug.results.ResultsDebugSubState)
|
||||||
|
{
|
||||||
|
if (introMusicAudio != null)
|
||||||
|
{
|
||||||
|
introMusicAudio.stop();
|
||||||
|
introMusicAudio.destroy();
|
||||||
|
introMusicAudio = null;
|
||||||
|
}
|
||||||
close(); // IF we are a substate, we will close ourselves. This is used from ResultsDebugSubState
|
close(); // IF we are a substate, we will close ourselves. This is used from ResultsDebugSubState
|
||||||
if (introMusicAudio != null)
|
}
|
||||||
|
else if (introMusicAudio != null)
|
||||||
{
|
{
|
||||||
@:nullSafety(Off)
|
@:nullSafety(Off)
|
||||||
introMusicAudio.onComplete = null;
|
introMusicAudio.onComplete = null;
|
||||||
@@ -792,13 +791,18 @@ class ResultState extends MusicBeatSubState
|
|||||||
var shouldTween = false;
|
var shouldTween = false;
|
||||||
var shouldUseSubstate = false;
|
var shouldUseSubstate = false;
|
||||||
|
|
||||||
var stickerSet = (playerCharacterId == "pico") ? "stickers-set-2" : "stickers-set-1";
|
var stickerPackId:Null<String> = null;
|
||||||
var stickerPack = switch (PlayState.instance?.currentChart?.song?.id)
|
|
||||||
|
var song:Null<Song> = params.songId == null ? null : SongRegistry.instance.fetchEntry(params.songId);
|
||||||
|
|
||||||
|
if (song != null)
|
||||||
{
|
{
|
||||||
case "tutorial": "tutorial";
|
stickerPackId = song.getStickerPackId(params?.difficultyId ?? Constants.DEFAULT_DIFFICULTY, params?.variationId ?? Constants.DEFAULT_VARIATION);
|
||||||
case "darnell" | "lit-up" | "2hot": "weekend";
|
}
|
||||||
default: "all";
|
if (stickerPackId == null && playerCharacter != null)
|
||||||
};
|
{
|
||||||
|
stickerPackId = playerCharacter.getStickerPackID();
|
||||||
|
}
|
||||||
|
|
||||||
if (params.storyMode)
|
if (params.storyMode)
|
||||||
{
|
{
|
||||||
@@ -821,11 +825,10 @@ class ResultState extends MusicBeatSubState
|
|||||||
// No new characters.
|
// No new characters.
|
||||||
shouldTween = false;
|
shouldTween = false;
|
||||||
shouldUseSubstate = true;
|
shouldUseSubstate = true;
|
||||||
targetState = new funkin.ui.transition.StickerSubState(
|
targetState = new funkin.ui.transition.stickers.StickerSubState(
|
||||||
{
|
{
|
||||||
targetState: (sticker) -> new StoryMenuState(sticker),
|
targetState: (sticker) -> new StoryMenuState(sticker),
|
||||||
stickerSet: stickerSet,
|
stickerPack: stickerPackId
|
||||||
stickerPack: stickerPack
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -859,11 +862,10 @@ class ResultState extends MusicBeatSubState
|
|||||||
{
|
{
|
||||||
shouldTween = false;
|
shouldTween = false;
|
||||||
shouldUseSubstate = true;
|
shouldUseSubstate = true;
|
||||||
targetState = new funkin.ui.transition.StickerSubState(
|
targetState = new funkin.ui.transition.stickers.StickerSubState(
|
||||||
{
|
{
|
||||||
targetState: (sticker) -> FreeplayState.build(null, sticker),
|
targetState: (sticker) -> FreeplayState.build(null, sticker),
|
||||||
stickerSet: stickerSet,
|
stickerPack: stickerPackId
|
||||||
stickerPack: stickerPack
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -943,10 +945,16 @@ typedef ResultsStateParams =
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The difficulty ID of the song/week we just played.
|
* The difficulty ID of the song/week we just played.
|
||||||
* @default Normal
|
* @default `Constants.DEFAULT_DIFFICULTY`
|
||||||
*/
|
*/
|
||||||
var ?difficultyId:String;
|
var ?difficultyId:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The variation ID of the song/week we just played.
|
||||||
|
* @default `Constants.DEFAULT_VARIATION`
|
||||||
|
*/
|
||||||
|
var ?variationId:String;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The score, accuracy, and judgements.
|
* The score, accuracy, and judgements.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -553,17 +553,40 @@ class BaseCharacter extends Bopper
|
|||||||
|
|
||||||
if (event.note.noteData.getMustHitNote() && characterType == BF)
|
if (event.note.noteData.getMustHitNote() && characterType == BF)
|
||||||
{
|
{
|
||||||
// If the note is from the same strumline, play the sing animation.
|
// If the note is from the same strumline, play the miss animation.
|
||||||
this.playSingAnimation(event.note.noteData.getDirection(), true);
|
this.playSingAnimation(event.note.noteData.getDirection(), true);
|
||||||
}
|
}
|
||||||
else if (!event.note.noteData.getMustHitNote() && characterType == DAD)
|
else if (!event.note.noteData.getMustHitNote() && characterType == DAD)
|
||||||
{
|
{
|
||||||
// If the note is from the same strumline, play the sing animation.
|
// If the note is from the same strumline, play the miss animation.
|
||||||
this.playSingAnimation(event.note.noteData.getDirection(), true);
|
this.playSingAnimation(event.note.noteData.getDirection(), true);
|
||||||
}
|
}
|
||||||
else if (event.note.noteData.getMustHitNote() && characterType == GF)
|
else if (event.note.noteData.getMustHitNote() && characterType == GF)
|
||||||
{
|
{
|
||||||
playComboDropAnimation(Highscore.tallies.combo);
|
playComboDropAnimation(event.comboCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override function onNoteHoldDrop(event:HoldNoteScriptEvent)
|
||||||
|
{
|
||||||
|
super.onNoteHoldDrop(event);
|
||||||
|
|
||||||
|
// If another script cancelled the event, don't do anything.
|
||||||
|
if (event.eventCanceled) return;
|
||||||
|
|
||||||
|
if (event.holdNote.noteData.getMustHitNote() && characterType == BF)
|
||||||
|
{
|
||||||
|
// If the note is from the same strumline, play the miss animation.
|
||||||
|
this.playSingAnimation(event.holdNote.noteData.getDirection(), true);
|
||||||
|
}
|
||||||
|
else if (!event.holdNote.noteData.getMustHitNote() && characterType == DAD)
|
||||||
|
{
|
||||||
|
// If the note is from the same strumline, play the miss animation.
|
||||||
|
this.playSingAnimation(event.holdNote.noteData.getDirection(), true);
|
||||||
|
}
|
||||||
|
else if (event.holdNote.noteData.getMustHitNote() && event.isComboBreak && characterType == GF)
|
||||||
|
{
|
||||||
|
playComboDropAnimation(event.comboCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -296,7 +296,7 @@ class CharacterDataParser
|
|||||||
charPath += "monsterpixel";
|
charPath += "monsterpixel";
|
||||||
case "mom" | "mom-car":
|
case "mom" | "mom-car":
|
||||||
charPath += "mommypixel";
|
charPath += "mommypixel";
|
||||||
case "pico-blazin" | "pico-playable" | "pico-speaker":
|
case "pico-blazin" | "pico-playable" | "pico-speaker" | "pico-pixel" | "pico-holding-nene":
|
||||||
charPath += "picopixel";
|
charPath += "picopixel";
|
||||||
case "gf-christmas" | "gf-car" | "gf-pixel" | "gf-tankmen" | "gf-dark":
|
case "gf-christmas" | "gf-car" | "gf-pixel" | "gf-tankmen" | "gf-dark":
|
||||||
charPath += "gfpixel";
|
charPath += "gfpixel";
|
||||||
@@ -308,7 +308,7 @@ class CharacterDataParser
|
|||||||
charPath += "senpaipixel";
|
charPath += "senpaipixel";
|
||||||
case "spooky-dark":
|
case "spooky-dark":
|
||||||
charPath += "spookypixel";
|
charPath += "spookypixel";
|
||||||
case "tankman-atlas":
|
case "tankman-atlas" | "tankman-bloody":
|
||||||
charPath += "tankmanpixel";
|
charPath += "tankmanpixel";
|
||||||
case "pico-christmas" | "pico-dark":
|
case "pico-christmas" | "pico-dark":
|
||||||
charPath += "picopixel";
|
charPath += "picopixel";
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class ClearPercentCounter extends FlxTypedSpriteGroup<FlxSprite>
|
|||||||
var seperatedScore:Array<Int> = [];
|
var seperatedScore:Array<Int> = [];
|
||||||
var tempCombo:Int = Math.round(curNumber);
|
var tempCombo:Int = Math.round(curNumber);
|
||||||
|
|
||||||
while (tempCombo != 0)
|
while (tempCombo > 0 && tempCombo != 0)
|
||||||
{
|
{
|
||||||
seperatedScore.push(tempCombo % 10);
|
seperatedScore.push(tempCombo % 10);
|
||||||
tempCombo = Math.floor(tempCombo / 10);
|
tempCombo = Math.floor(tempCombo / 10);
|
||||||
|
|||||||
@@ -1,126 +0,0 @@
|
|||||||
package funkin.play.components;
|
|
||||||
|
|
||||||
import flixel.FlxSprite;
|
|
||||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
|
||||||
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
|
||||||
import flixel.util.FlxTimer;
|
|
||||||
import funkin.audio.FunkinSound;
|
|
||||||
|
|
||||||
class ComboMilestone extends FlxTypedSpriteGroup<FlxSprite>
|
|
||||||
{
|
|
||||||
var effectStuff:FlxSprite;
|
|
||||||
|
|
||||||
var wasComboSetup:Bool = false;
|
|
||||||
var daCombo:Int = 0;
|
|
||||||
|
|
||||||
var grpNumbers:FlxTypedGroup<ComboMilestoneNumber>;
|
|
||||||
|
|
||||||
var onScreenTime:Float = 0;
|
|
||||||
|
|
||||||
public function new(x:Float, y:Float, daCombo:Int = 0)
|
|
||||||
{
|
|
||||||
super(x, y);
|
|
||||||
|
|
||||||
this.daCombo = daCombo;
|
|
||||||
|
|
||||||
effectStuff = new FlxSprite(0, 0);
|
|
||||||
effectStuff.frames = Paths.getSparrowAtlas('comboMilestone');
|
|
||||||
effectStuff.animation.addByPrefix('funny', 'NOTE COMBO animation', 24, false);
|
|
||||||
effectStuff.animation.play('funny');
|
|
||||||
effectStuff.animation.finishCallback = function(nameThing) {
|
|
||||||
kill();
|
|
||||||
};
|
|
||||||
effectStuff.setGraphicSize(Std.int(effectStuff.width * 0.7));
|
|
||||||
add(effectStuff);
|
|
||||||
|
|
||||||
grpNumbers = new FlxTypedGroup<ComboMilestoneNumber>();
|
|
||||||
// add(grpNumbers);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function forceFinish():Void
|
|
||||||
{
|
|
||||||
if (onScreenTime < 0.9)
|
|
||||||
{
|
|
||||||
new FlxTimer().start((Conductor.instance.beatLengthMs / 1000) * 0.25, function(tmr) {
|
|
||||||
forceFinish();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
effectStuff.animation.play('funny', true, false, 18);
|
|
||||||
}
|
|
||||||
|
|
||||||
override function update(elapsed:Float)
|
|
||||||
{
|
|
||||||
onScreenTime += elapsed;
|
|
||||||
|
|
||||||
if (effectStuff.animation.curAnim.curFrame == 17) effectStuff.animation.pause();
|
|
||||||
|
|
||||||
if (effectStuff.animation.curAnim.curFrame == 2 && !wasComboSetup)
|
|
||||||
{
|
|
||||||
setupCombo(daCombo);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (effectStuff.animation.curAnim.curFrame == 18)
|
|
||||||
{
|
|
||||||
grpNumbers.forEach(function(spr:ComboMilestoneNumber) {
|
|
||||||
spr.animation.reset();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (effectStuff.animation.curAnim.curFrame == 20)
|
|
||||||
{
|
|
||||||
grpNumbers.forEach(function(spr:ComboMilestoneNumber) {
|
|
||||||
spr.kill();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
super.update(elapsed);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupCombo(daCombo:Int)
|
|
||||||
{
|
|
||||||
FunkinSound.playOnce(Paths.sound('comboSound'));
|
|
||||||
|
|
||||||
wasComboSetup = true;
|
|
||||||
var loopNum:Int = 0;
|
|
||||||
|
|
||||||
while (daCombo > 0)
|
|
||||||
{
|
|
||||||
var comboNumber:ComboMilestoneNumber = new ComboMilestoneNumber(450 - (100 * loopNum), 20 + 14 * loopNum, daCombo % 10);
|
|
||||||
comboNumber.setGraphicSize(Std.int(comboNumber.width * 0.7));
|
|
||||||
grpNumbers.add(comboNumber);
|
|
||||||
add(comboNumber);
|
|
||||||
|
|
||||||
loopNum += 1;
|
|
||||||
|
|
||||||
daCombo = Math.floor(daCombo / 10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ComboMilestoneNumber extends FlxSprite
|
|
||||||
{
|
|
||||||
public function new(x:Float, y:Float, digit:Int)
|
|
||||||
{
|
|
||||||
super(x - 20, y);
|
|
||||||
|
|
||||||
var stringNum:String = Std.string(digit);
|
|
||||||
frames = Paths.getSparrowAtlas('comboMilestoneNumbers');
|
|
||||||
animation.addByPrefix(stringNum, stringNum, 24, false);
|
|
||||||
animation.play(stringNum);
|
|
||||||
updateHitbox();
|
|
||||||
}
|
|
||||||
|
|
||||||
var shiftedX:Bool = false;
|
|
||||||
|
|
||||||
override function update(elapsed:Float)
|
|
||||||
{
|
|
||||||
if (animation.curAnim.curFrame == 2 && !shiftedX)
|
|
||||||
{
|
|
||||||
shiftedX = true;
|
|
||||||
x += 20;
|
|
||||||
}
|
|
||||||
|
|
||||||
super.update(elapsed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,6 +8,7 @@ import funkin.util.MathUtil;
|
|||||||
/**
|
/**
|
||||||
* Numerical counters used next to each judgement in the Results screen.
|
* Numerical counters used next to each judgement in the Results screen.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class TallyCounter extends FlxTypedSpriteGroup<FlxSprite>
|
class TallyCounter extends FlxTypedSpriteGroup<FlxSprite>
|
||||||
{
|
{
|
||||||
public var curNumber:Float = 0;
|
public var curNumber:Float = 0;
|
||||||
@@ -17,13 +18,13 @@ class TallyCounter extends FlxTypedSpriteGroup<FlxSprite>
|
|||||||
|
|
||||||
public var align:FlxTextAlign = FlxTextAlign.LEFT;
|
public var align:FlxTextAlign = FlxTextAlign.LEFT;
|
||||||
|
|
||||||
public function new(x:Float, y:Float, neededNumber:Int = 0, ?flavour:Int = 0xFFFFFFFF, align:FlxTextAlign = FlxTextAlign.LEFT)
|
public function new(x:Float, y:Float, neededNumber:Int = 0, ?flavour:Int, align:FlxTextAlign = FlxTextAlign.LEFT)
|
||||||
{
|
{
|
||||||
super(x, y);
|
super(x, y);
|
||||||
|
|
||||||
this.align = align;
|
this.align = align;
|
||||||
|
|
||||||
this.flavour = flavour;
|
this.flavour = flavour ?? 0xFFFFFFFF;
|
||||||
|
|
||||||
this.neededNumber = neededNumber;
|
this.neededNumber = neededNumber;
|
||||||
|
|
||||||
@@ -79,6 +80,7 @@ class TallyCounter extends FlxTypedSpriteGroup<FlxSprite>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class TallyNumber extends FlxSprite
|
class TallyNumber extends FlxSprite
|
||||||
{
|
{
|
||||||
public function new(x:Float, y:Float, digit:Int)
|
public function new(x:Float, y:Float, digit:Int)
|
||||||
|
|||||||
@@ -28,22 +28,11 @@ import funkin.util.EaseUtil;
|
|||||||
@:nullSafety
|
@:nullSafety
|
||||||
class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass implements IRegistryEntry<ConversationData>
|
class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass implements IRegistryEntry<ConversationData>
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* The ID of the conversation.
|
|
||||||
*/
|
|
||||||
public final id:String;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current state of the conversation.
|
* The current state of the conversation.
|
||||||
*/
|
*/
|
||||||
var state:ConversationState = ConversationState.Start;
|
var state:ConversationState = ConversationState.Start;
|
||||||
|
|
||||||
/**
|
|
||||||
* Conversation data as parsed from the JSON file.
|
|
||||||
* `null` if the data could not be parsed or loaded.
|
|
||||||
*/
|
|
||||||
public final _data:Null<ConversationData>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current entry in the dialogue.
|
* The current entry in the dialogue.
|
||||||
*/
|
*/
|
||||||
@@ -659,16 +648,6 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
|||||||
outroTween = null;
|
outroTween = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override function toString():String
|
|
||||||
{
|
|
||||||
return 'Conversation($id)';
|
|
||||||
}
|
|
||||||
|
|
||||||
static function _fetchData(id:String):Null<ConversationData>
|
|
||||||
{
|
|
||||||
return ConversationRegistry.instance.parseEntryDataWithMigration(id, ConversationRegistry.instance.fetchEntryVersion(id));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Managing things with a single enum is a lot easier than a multitude of flags.
|
// Managing things with a single enum is a lot easier than a multitude of flags.
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ import funkin.data.dialogue.dialoguebox.DialogueBoxRegistry;
|
|||||||
|
|
||||||
class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass implements IRegistryEntry<DialogueBoxData>
|
class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass implements IRegistryEntry<DialogueBoxData>
|
||||||
{
|
{
|
||||||
public final id:String;
|
|
||||||
|
|
||||||
public var dialogueBoxName(get, never):String;
|
public var dialogueBoxName(get, never):String;
|
||||||
|
|
||||||
function get_dialogueBoxName():String
|
function get_dialogueBoxName():String
|
||||||
@@ -25,8 +23,6 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple
|
|||||||
return _data.name ?? 'UNKNOWN';
|
return _data.name ?? 'UNKNOWN';
|
||||||
}
|
}
|
||||||
|
|
||||||
public final _data:DialogueBoxData;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Offset the speaker's sprite by this much when playing each animation.
|
* Offset the speaker's sprite by this much when playing each animation.
|
||||||
*/
|
*/
|
||||||
@@ -247,8 +243,8 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple
|
|||||||
var animNames:Array<String> = this.boxSprite?.animation?.getNameList() ?? [];
|
var animNames:Array<String> = this.boxSprite?.animation?.getNameList() ?? [];
|
||||||
trace('[DIALOGUE BOX] Successfully loaded ${animNames.length} animations for ${id}');
|
trace('[DIALOGUE BOX] Successfully loaded ${animNames.length} animations for ${id}');
|
||||||
|
|
||||||
boxSprite.animation.callback = this.onAnimationFrame;
|
boxSprite.animation.onFrameChange.add(this.onAnimationFrame);
|
||||||
boxSprite.animation.finishCallback = this.onAnimationFinished;
|
boxSprite.animation.onFinish.add(this.onAnimationFinished);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -419,14 +415,4 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function onScriptEvent(event:ScriptEvent):Void {}
|
public function onScriptEvent(event:ScriptEvent):Void {}
|
||||||
|
|
||||||
public override function toString():String
|
|
||||||
{
|
|
||||||
return 'DialogueBox($id)';
|
|
||||||
}
|
|
||||||
|
|
||||||
static function _fetchData(id:String):Null<DialogueBoxData>
|
|
||||||
{
|
|
||||||
return DialogueBoxRegistry.instance.parseEntryDataWithMigration(id, DialogueBoxRegistry.instance.fetchEntryVersion(id));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,16 +16,6 @@ import funkin.data.dialogue.speaker.SpeakerRegistry;
|
|||||||
*/
|
*/
|
||||||
class Speaker extends FlxSprite implements IDialogueScriptedClass implements IRegistryEntry<SpeakerData>
|
class Speaker extends FlxSprite implements IDialogueScriptedClass implements IRegistryEntry<SpeakerData>
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* The internal ID for this speaker.
|
|
||||||
*/
|
|
||||||
public final id:String;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The full data for a speaker.
|
|
||||||
*/
|
|
||||||
public final _data:SpeakerData;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A readable name for this speaker.
|
* A readable name for this speaker.
|
||||||
*/
|
*/
|
||||||
@@ -298,14 +288,4 @@ class Speaker extends FlxSprite implements IDialogueScriptedClass implements IRe
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function onScriptEvent(event:ScriptEvent):Void {}
|
public function onScriptEvent(event:ScriptEvent):Void {}
|
||||||
|
|
||||||
public override function toString():String
|
|
||||||
{
|
|
||||||
return 'Speaker($id)';
|
|
||||||
}
|
|
||||||
|
|
||||||
static function _fetchData(id:String):Null<SpeakerData>
|
|
||||||
{
|
|
||||||
return SpeakerRegistry.instance.parseEntryDataWithMigration(id, SpeakerRegistry.instance.fetchEntryVersion(id));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,6 +104,11 @@ class NoteSprite extends FunkinSprite
|
|||||||
return noteData.length > 0;
|
return noteData.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Y Offset of the note.
|
||||||
|
*/
|
||||||
|
public var yOffset:Float = 0.0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set this flag to true when hitting the note to avoid scoring it multiple times.
|
* Set this flag to true when hitting the note to avoid scoring it multiple times.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package funkin.play.notes;
|
package funkin.play.notes;
|
||||||
|
|
||||||
|
import flixel.math.FlxPoint;
|
||||||
import flixel.util.FlxSignal.FlxTypedSignal;
|
import flixel.util.FlxSignal.FlxTypedSignal;
|
||||||
import flixel.FlxG;
|
import flixel.FlxG;
|
||||||
import funkin.play.notes.notestyle.NoteStyle;
|
import funkin.play.notes.notestyle.NoteStyle;
|
||||||
@@ -18,35 +19,46 @@ import funkin.play.notes.notekind.NoteKindManager;
|
|||||||
*/
|
*/
|
||||||
class Strumline extends FlxSpriteGroup
|
class Strumline extends FlxSpriteGroup
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* The directions of the notes on the strumline, in order.
|
||||||
|
*/
|
||||||
public static final DIRECTIONS:Array<NoteDirection> = [NoteDirection.LEFT, NoteDirection.DOWN, NoteDirection.UP, NoteDirection.RIGHT];
|
public static final DIRECTIONS:Array<NoteDirection> = [NoteDirection.LEFT, NoteDirection.DOWN, NoteDirection.UP, NoteDirection.RIGHT];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A magic number for the size of the strumline, in pixels.
|
||||||
|
*/
|
||||||
public static final STRUMLINE_SIZE:Int = 104;
|
public static final STRUMLINE_SIZE:Int = 104;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The spacing between notes on the strumline, in pixels.
|
||||||
|
*/
|
||||||
public static final NOTE_SPACING:Int = STRUMLINE_SIZE + 8;
|
public static final NOTE_SPACING:Int = STRUMLINE_SIZE + 8;
|
||||||
|
|
||||||
// Positional fixes for new strumline graphics.
|
// Positional fixes for new strumline graphics.
|
||||||
static final INITIAL_OFFSET = -0.275 * STRUMLINE_SIZE;
|
static final INITIAL_OFFSET:Float = -0.275 * STRUMLINE_SIZE;
|
||||||
static final NUDGE:Float = 2.0;
|
static final NUDGE:Float = 2.0;
|
||||||
|
|
||||||
static final KEY_COUNT:Int = 4;
|
static final KEY_COUNT:Int = 4;
|
||||||
static final NOTE_SPLASH_CAP:Int = 6;
|
static final NOTE_SPLASH_CAP:Int = 6;
|
||||||
|
|
||||||
static var RENDER_DISTANCE_MS(get, never):Float;
|
var renderDistanceMs(get, never):Float;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The custom render distance for the strumline.
|
* The custom render distance for the strumline.
|
||||||
* This should be in miliseconds only! Not pixels.
|
* This should be in miliseconds only! Not pixels.
|
||||||
*/
|
*/
|
||||||
public static var CUSTOM_RENDER_DISTANCE_MS:Float = 0.0;
|
public var customRenderDistanceMs:Float = 0.0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to use the custom render distance.
|
* Whether to use the custom render distance.
|
||||||
* If false, the render distance will be calculated based on the screen height.
|
* If false, the render distance will be calculated based on the screen height.
|
||||||
*/
|
*/
|
||||||
public static var USE_CUSTOM_RENDER_DISTANCE:Bool = false;
|
public var useCustomRenderDistance:Bool = false;
|
||||||
|
|
||||||
static function get_RENDER_DISTANCE_MS():Float
|
function get_renderDistanceMs():Float
|
||||||
{
|
{
|
||||||
if (USE_CUSTOM_RENDER_DISTANCE) return CUSTOM_RENDER_DISTANCE_MS;
|
if (useCustomRenderDistance) return customRenderDistanceMs;
|
||||||
return FlxG.height / Constants.PIXELS_PER_MS;
|
return FlxG.height / Constants.PIXELS_PER_MS / scrollSpeed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,6 +83,9 @@ class Strumline extends FlxSpriteGroup
|
|||||||
// Used in-game to control the scroll speed within a song
|
// Used in-game to control the scroll speed within a song
|
||||||
public var scrollSpeed:Float = 1.0;
|
public var scrollSpeed:Float = 1.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the scroll speed to the current chart's scroll speed.
|
||||||
|
*/
|
||||||
public function resetScrollSpeed():Void
|
public function resetScrollSpeed():Void
|
||||||
{
|
{
|
||||||
scrollSpeed = PlayState.instance?.currentChart?.scrollSpeed ?? 1.0;
|
scrollSpeed = PlayState.instance?.currentChart?.scrollSpeed ?? 1.0;
|
||||||
@@ -101,8 +116,16 @@ class Strumline extends FlxSpriteGroup
|
|||||||
*/
|
*/
|
||||||
public var notes:FlxTypedSpriteGroup<NoteSprite>;
|
public var notes:FlxTypedSpriteGroup<NoteSprite>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The hold notes currently being rendered on the strumline.
|
||||||
|
* This group iterates over this every frame to update hold note positions.
|
||||||
|
* The PlayState also iterates over this to calculate user inputs.
|
||||||
|
*/
|
||||||
public var holdNotes:FlxTypedSpriteGroup<SustainTrail>;
|
public var holdNotes:FlxTypedSpriteGroup<SustainTrail>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A signal that is dispatched when a note is spawned and heading towards the strumline.
|
||||||
|
*/
|
||||||
public var onNoteIncoming:FlxTypedSignal<NoteSprite->Void>;
|
public var onNoteIncoming:FlxTypedSignal<NoteSprite->Void>;
|
||||||
|
|
||||||
var background:FunkinSprite;
|
var background:FunkinSprite;
|
||||||
@@ -218,6 +241,9 @@ class Strumline extends FlxSpriteGroup
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the strumline, sorting its children by z-index.
|
||||||
|
*/
|
||||||
public function refresh():Void
|
public function refresh():Void
|
||||||
{
|
{
|
||||||
sort(SortUtil.byZIndex, FlxSort.ASCENDING);
|
sort(SortUtil.byZIndex, FlxSort.ASCENDING);
|
||||||
@@ -241,7 +267,7 @@ class Strumline extends FlxSpriteGroup
|
|||||||
|
|
||||||
#if FEATURE_GHOST_TAPPING
|
#if FEATURE_GHOST_TAPPING
|
||||||
/**
|
/**
|
||||||
* Returns `true` if no notes are in range of the strumline and the player can spam without penalty.
|
* @return `true` if no notes are in range of the strumline and the player can spam without penalty.
|
||||||
*/
|
*/
|
||||||
public function mayGhostTap():Bool
|
public function mayGhostTap():Bool
|
||||||
{
|
{
|
||||||
@@ -286,31 +312,41 @@ class Strumline extends FlxSpriteGroup
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getNoteSprite(noteData:SongNoteData):NoteSprite
|
/**
|
||||||
|
* Get a note sprite corresponding to the given note data.
|
||||||
|
* @param target The note data for the note sprite.
|
||||||
|
* @return The note sprite.
|
||||||
|
*/
|
||||||
|
public function getNoteSprite(target:SongNoteData):NoteSprite
|
||||||
{
|
{
|
||||||
if (noteData == null) return null;
|
if (target == null) return null;
|
||||||
|
|
||||||
for (note in notes.members)
|
for (note in notes.members)
|
||||||
{
|
{
|
||||||
if (note == null) continue;
|
if (note == null) continue;
|
||||||
if (note.alive) continue;
|
if (note.alive) continue;
|
||||||
|
|
||||||
if (note.noteData == noteData) return note;
|
if (note.noteData == target) return note;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getHoldNoteSprite(noteData:SongNoteData):SustainTrail
|
/**
|
||||||
|
* Get a hold note sprite corresponding to the given note data.
|
||||||
|
* @param target The note data for the hold note.
|
||||||
|
* @return The hold note sprite.
|
||||||
|
*/
|
||||||
|
public function getHoldNoteSprite(target:SongNoteData):SustainTrail
|
||||||
{
|
{
|
||||||
if (noteData == null || ((noteData.length ?? 0.0) <= 0.0)) return null;
|
if (target == null || ((target.length ?? 0.0) <= 0.0)) return null;
|
||||||
|
|
||||||
for (holdNote in holdNotes.members)
|
for (holdNote in holdNotes.members)
|
||||||
{
|
{
|
||||||
if (holdNote == null) continue;
|
if (holdNote == null) continue;
|
||||||
if (holdNote.alive) continue;
|
if (holdNote.alive) continue;
|
||||||
|
|
||||||
if (holdNote.noteData == noteData) return holdNote;
|
if (holdNote.noteData == target) return holdNote;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -321,6 +357,8 @@ class Strumline extends FlxSpriteGroup
|
|||||||
*/
|
*/
|
||||||
public function vwooshNotes():Void
|
public function vwooshNotes():Void
|
||||||
{
|
{
|
||||||
|
var vwooshTime:Float = 0.5;
|
||||||
|
|
||||||
for (note in notes.members)
|
for (note in notes.members)
|
||||||
{
|
{
|
||||||
if (note == null) continue;
|
if (note == null) continue;
|
||||||
@@ -331,7 +369,7 @@ class Strumline extends FlxSpriteGroup
|
|||||||
|
|
||||||
var targetY:Float = FlxG.height + note.y;
|
var targetY:Float = FlxG.height + note.y;
|
||||||
if (Preferences.downscroll) targetY = 0 - note.height;
|
if (Preferences.downscroll) targetY = 0 - note.height;
|
||||||
FlxTween.tween(note, {y: targetY}, 0.5,
|
FlxTween.tween(note, {y: targetY}, vwooshTime,
|
||||||
{
|
{
|
||||||
ease: FlxEase.expoIn,
|
ease: FlxEase.expoIn,
|
||||||
onComplete: function(twn) {
|
onComplete: function(twn) {
|
||||||
@@ -352,7 +390,7 @@ class Strumline extends FlxSpriteGroup
|
|||||||
|
|
||||||
var targetY:Float = FlxG.height + holdNote.y;
|
var targetY:Float = FlxG.height + holdNote.y;
|
||||||
if (Preferences.downscroll) targetY = 0 - holdNote.height;
|
if (Preferences.downscroll) targetY = 0 - holdNote.height;
|
||||||
FlxTween.tween(holdNote, {y: targetY}, 0.5,
|
FlxTween.tween(holdNote, {y: targetY}, vwooshTime,
|
||||||
{
|
{
|
||||||
ease: FlxEase.expoIn,
|
ease: FlxEase.expoIn,
|
||||||
onComplete: function(twn) {
|
onComplete: function(twn) {
|
||||||
@@ -365,23 +403,65 @@ class Strumline extends FlxSpriteGroup
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For a note's strumTime, calculate its Y position relative to the strumline.
|
* Reverse of vwooshNotes, we bring the notes IN (by their offsets)
|
||||||
* NOTE: Assumes Conductor and PlayState are both initialized.
|
|
||||||
* @param strumTime
|
|
||||||
* @return Float
|
|
||||||
*/
|
*/
|
||||||
public function calculateNoteYPos(strumTime:Float, vwoosh:Bool = true):Float
|
public function vwooshInNotes():Void
|
||||||
{
|
{
|
||||||
// Make the note move faster visually as it moves offscreen.
|
var vwooshTime:Float = 0.5;
|
||||||
// var vwoosh:Float = (strumTime < Conductor.songPosition) && vwoosh ? 2.0 : 1.0;
|
|
||||||
// ^^^ commented this out... do NOT make it move faster as it moves offscreen!
|
|
||||||
var vwoosh:Float = 1.0;
|
|
||||||
|
|
||||||
return
|
for (note in notes.members)
|
||||||
Constants.PIXELS_PER_MS * (conductorInUse.songPosition - strumTime - Conductor.instance.inputOffset) * scrollSpeed * vwoosh * (Preferences.downscroll ? 1 : -1);
|
{
|
||||||
|
if (note == null) continue;
|
||||||
|
if (!note.alive) continue;
|
||||||
|
|
||||||
|
note.yOffset = 200;
|
||||||
|
if (Preferences.downscroll)
|
||||||
|
{
|
||||||
|
note.yOffset = -200;
|
||||||
|
}
|
||||||
|
FlxTween.tween(note, {yOffset: 0}, vwooshTime,
|
||||||
|
{
|
||||||
|
ease: FlxEase.expoOut,
|
||||||
|
onComplete: function(twn) {
|
||||||
|
note.yOffset = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (holdNote in holdNotes.members)
|
||||||
|
{
|
||||||
|
if (holdNote == null) continue;
|
||||||
|
if (!holdNote.alive) continue;
|
||||||
|
|
||||||
|
holdNote.yOffset = 200;
|
||||||
|
if (Preferences.downscroll)
|
||||||
|
{
|
||||||
|
holdNote.yOffset = -200;
|
||||||
|
}
|
||||||
|
FlxTween.tween(holdNote, {yOffset: 0}, vwooshTime,
|
||||||
|
{
|
||||||
|
ease: FlxEase.expoOut,
|
||||||
|
onComplete: function(twn) {
|
||||||
|
holdNote.yOffset = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateNotes():Void
|
|
||||||
|
/**
|
||||||
|
* For a note's strumTime, calculate its Y position relative to the strumline.
|
||||||
|
* NOTE: Assumes Conductor and PlayState are both initialized.
|
||||||
|
* @param strumTime The strumtime of the note.
|
||||||
|
* @return The Y position of the note.
|
||||||
|
*/
|
||||||
|
public function calculateNoteYPos(strumTime:Float):Float
|
||||||
|
{
|
||||||
|
return
|
||||||
|
Constants.PIXELS_PER_MS * (conductorInUse.songPosition - strumTime - Conductor.instance.inputOffset) * scrollSpeed * (Preferences.downscroll ? 1 : -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateNotes():Void
|
||||||
{
|
{
|
||||||
if (noteData.length == 0) return;
|
if (noteData.length == 0) return;
|
||||||
|
|
||||||
@@ -392,7 +472,7 @@ class Strumline extends FlxSpriteGroup
|
|||||||
|
|
||||||
var songStart:Float = PlayState.instance?.startTimestamp ?? 0.0;
|
var songStart:Float = PlayState.instance?.startTimestamp ?? 0.0;
|
||||||
var hitWindowStart:Float = conductorInUse.songPosition - Constants.HIT_WINDOW_MS;
|
var hitWindowStart:Float = conductorInUse.songPosition - Constants.HIT_WINDOW_MS;
|
||||||
var renderWindowStart:Float = conductorInUse.songPosition + RENDER_DISTANCE_MS;
|
var renderWindowStart:Float = conductorInUse.songPosition + renderDistanceMs;
|
||||||
|
|
||||||
for (noteIndex in nextNoteIndex...noteData.length)
|
for (noteIndex in nextNoteIndex...noteData.length)
|
||||||
{
|
{
|
||||||
@@ -407,7 +487,7 @@ class Strumline extends FlxSpriteGroup
|
|||||||
}
|
}
|
||||||
if (note.time > renderWindowStart) break; // Note is too far ahead to render
|
if (note.time > renderWindowStart) break; // Note is too far ahead to render
|
||||||
|
|
||||||
var noteSprite = buildNoteSprite(note);
|
var noteSprite:NoteSprite = buildNoteSprite(note);
|
||||||
|
|
||||||
if (note.length > 0)
|
if (note.length > 0)
|
||||||
{
|
{
|
||||||
@@ -424,12 +504,11 @@ class Strumline extends FlxSpriteGroup
|
|||||||
{
|
{
|
||||||
if (note == null || !note.alive) continue;
|
if (note == null || !note.alive) continue;
|
||||||
|
|
||||||
var vwoosh:Bool = note.holdNoteSprite == null;
|
|
||||||
// Set the note's position.
|
// Set the note's position.
|
||||||
if (!customPositionData) note.y = this.y - INITIAL_OFFSET + calculateNoteYPos(note.strumTime, vwoosh);
|
if (!customPositionData) note.y = this.y - INITIAL_OFFSET + calculateNoteYPos(note.strumTime) + note.yOffset;
|
||||||
|
|
||||||
// If the note is miss
|
// If the note is miss
|
||||||
var isOffscreen = Preferences.downscroll ? note.y > FlxG.height : note.y < -note.height;
|
var isOffscreen:Bool = Preferences.downscroll ? note.y > FlxG.height : note.y < -note.height;
|
||||||
if (note.handledMiss && isOffscreen)
|
if (note.handledMiss && isOffscreen)
|
||||||
{
|
{
|
||||||
killNote(note);
|
killNote(note);
|
||||||
@@ -453,7 +532,8 @@ class Strumline extends FlxSpriteGroup
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var renderWindowEnd = holdNote.strumTime + holdNote.fullSustainLength + Constants.HIT_WINDOW_MS + RENDER_DISTANCE_MS / 8;
|
final magicNumberIGuess:Float = 8;
|
||||||
|
var renderWindowEnd:Float = holdNote.strumTime + holdNote.fullSustainLength + Constants.HIT_WINDOW_MS + (renderDistanceMs / magicNumberIGuess);
|
||||||
|
|
||||||
if (holdNote.missedNote && conductorInUse.songPosition >= renderWindowEnd)
|
if (holdNote.missedNote && conductorInUse.songPosition >= renderWindowEnd)
|
||||||
{
|
{
|
||||||
@@ -494,17 +574,20 @@ class Strumline extends FlxSpriteGroup
|
|||||||
|
|
||||||
var yOffset:Float = (holdNote.fullSustainLength - holdNote.sustainLength) * Constants.PIXELS_PER_MS;
|
var yOffset:Float = (holdNote.fullSustainLength - holdNote.sustainLength) * Constants.PIXELS_PER_MS;
|
||||||
|
|
||||||
var vwoosh:Bool = false;
|
|
||||||
|
|
||||||
if (!customPositionData)
|
if (!customPositionData)
|
||||||
{
|
{
|
||||||
if (Preferences.downscroll)
|
if (Preferences.downscroll)
|
||||||
{
|
{
|
||||||
holdNote.y = this.y - INITIAL_OFFSET + calculateNoteYPos(holdNote.strumTime, vwoosh) - holdNote.height + STRUMLINE_SIZE / 2;
|
holdNote.y = this.y
|
||||||
|
- INITIAL_OFFSET
|
||||||
|
+ calculateNoteYPos(holdNote.strumTime)
|
||||||
|
- holdNote.height
|
||||||
|
+ STRUMLINE_SIZE / 2
|
||||||
|
+ holdNote.yOffset;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
holdNote.y = this.y - INITIAL_OFFSET + calculateNoteYPos(holdNote.strumTime, vwoosh) + yOffset + STRUMLINE_SIZE / 2;
|
holdNote.y = this.y - INITIAL_OFFSET + calculateNoteYPos(holdNote.strumTime) + yOffset + STRUMLINE_SIZE / 2 + holdNote.yOffset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -544,17 +627,21 @@ class Strumline extends FlxSpriteGroup
|
|||||||
{
|
{
|
||||||
// Hold note is new, render it normally.
|
// Hold note is new, render it normally.
|
||||||
holdNote.visible = true;
|
holdNote.visible = true;
|
||||||
var vwoosh:Bool = false;
|
|
||||||
|
|
||||||
if (!customPositionData)
|
if (!customPositionData)
|
||||||
{
|
{
|
||||||
if (Preferences.downscroll)
|
if (Preferences.downscroll)
|
||||||
{
|
{
|
||||||
holdNote.y = this.y - INITIAL_OFFSET + calculateNoteYPos(holdNote.strumTime, vwoosh) - holdNote.height + STRUMLINE_SIZE / 2;
|
holdNote.y = this.y
|
||||||
|
- INITIAL_OFFSET
|
||||||
|
+ calculateNoteYPos(holdNote.strumTime)
|
||||||
|
- holdNote.height
|
||||||
|
+ STRUMLINE_SIZE / 2
|
||||||
|
+ holdNote.yOffset;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
holdNote.y = this.y - INITIAL_OFFSET + calculateNoteYPos(holdNote.strumTime, vwoosh) + STRUMLINE_SIZE / 2;
|
holdNote.y = this.y - INITIAL_OFFSET + calculateNoteYPos(holdNote.strumTime) + STRUMLINE_SIZE / 2 + holdNote.yOffset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -607,6 +694,9 @@ class Strumline extends FlxSpriteGroup
|
|||||||
nextNoteIndex = 0;
|
nextNoteIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called on each beat of the song.
|
||||||
|
*/
|
||||||
public function onBeatHit():Void
|
public function onBeatHit():Void
|
||||||
{
|
{
|
||||||
if (notes.members.length > 1) notes.members.insertionSort(compareNoteSprites.bind(FlxSort.ASCENDING));
|
if (notes.members.length > 1) notes.members.insertionSort(compareNoteSprites.bind(FlxSort.ASCENDING));
|
||||||
@@ -614,16 +704,29 @@ class Strumline extends FlxSpriteGroup
|
|||||||
if (holdNotes.members.length > 1) holdNotes.members.insertionSort(compareHoldNoteSprites.bind(FlxSort.ASCENDING));
|
if (holdNotes.members.length > 1) holdNotes.members.insertionSort(compareHoldNoteSprites.bind(FlxSort.ASCENDING));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a key is pressed.
|
||||||
|
* @param dir The direction of the key that was pressed.
|
||||||
|
*/
|
||||||
public function pressKey(dir:NoteDirection):Void
|
public function pressKey(dir:NoteDirection):Void
|
||||||
{
|
{
|
||||||
heldKeys[dir] = true;
|
heldKeys[dir] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a key is released.
|
||||||
|
* @param dir The direction of the key that was released.
|
||||||
|
*/
|
||||||
public function releaseKey(dir:NoteDirection):Void
|
public function releaseKey(dir:NoteDirection):Void
|
||||||
{
|
{
|
||||||
heldKeys[dir] = false;
|
heldKeys[dir] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a key is held down.
|
||||||
|
* @param dir The direction of the key to check.
|
||||||
|
* @return `true` if the key is held down, `false` otherwise.
|
||||||
|
*/
|
||||||
public function isKeyHeld(dir:NoteDirection):Bool
|
public function isKeyHeld(dir:NoteDirection):Bool
|
||||||
{
|
{
|
||||||
return heldKeys[dir];
|
return heldKeys[dir];
|
||||||
@@ -673,6 +776,11 @@ class Strumline extends FlxSpriteGroup
|
|||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply note data from a chart to this strumline.
|
||||||
|
* Note data should be valid and apply only to this strumline.
|
||||||
|
* @param data The note data to apply.
|
||||||
|
*/
|
||||||
public function applyNoteData(data:Array<SongNoteData>):Void
|
public function applyNoteData(data:Array<SongNoteData>):Void
|
||||||
{
|
{
|
||||||
this.notes.clear();
|
this.notes.clear();
|
||||||
@@ -685,6 +793,7 @@ class Strumline extends FlxSpriteGroup
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Hit a note.
|
||||||
* @param note The note to hit.
|
* @param note The note to hit.
|
||||||
* @param removeNote True to remove the note immediately, false to make it transparent and let it move offscreen.
|
* @param removeNote True to remove the note immediately, false to make it transparent and let it move offscreen.
|
||||||
*/
|
*/
|
||||||
@@ -716,6 +825,10 @@ class Strumline extends FlxSpriteGroup
|
|||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kill a note heading towards the strumline.
|
||||||
|
* @param note The note to kill. Gets recycled and reused for performance.
|
||||||
|
*/
|
||||||
public function killNote(note:NoteSprite):Void
|
public function killNote(note:NoteSprite):Void
|
||||||
{
|
{
|
||||||
if (note == null) return;
|
if (note == null) return;
|
||||||
@@ -730,41 +843,76 @@ class Strumline extends FlxSpriteGroup
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a strumline note sprite by its index.
|
||||||
|
* @param index The index of the note to get.
|
||||||
|
* @return The note.
|
||||||
|
*/
|
||||||
public function getByIndex(index:Int):StrumlineNote
|
public function getByIndex(index:Int):StrumlineNote
|
||||||
{
|
{
|
||||||
return this.strumlineNotes.members[index];
|
return this.strumlineNotes.members[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a strumline note sprite by its direction.
|
||||||
|
* @param direction The direction of the note to get.
|
||||||
|
* @return The note.
|
||||||
|
*/
|
||||||
public function getByDirection(direction:NoteDirection):StrumlineNote
|
public function getByDirection(direction:NoteDirection):StrumlineNote
|
||||||
{
|
{
|
||||||
return getByIndex(DIRECTIONS.indexOf(direction));
|
return getByIndex(DIRECTIONS.indexOf(direction));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play a static animation for a given direction.
|
||||||
|
* @param direction The direction of the note to play the static animation for.
|
||||||
|
*/
|
||||||
public function playStatic(direction:NoteDirection):Void
|
public function playStatic(direction:NoteDirection):Void
|
||||||
{
|
{
|
||||||
getByDirection(direction).playStatic();
|
getByDirection(direction).playStatic();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play a press animation for a given direction.
|
||||||
|
* @param direction The direction of the note to play the press animation for.
|
||||||
|
*/
|
||||||
public function playPress(direction:NoteDirection):Void
|
public function playPress(direction:NoteDirection):Void
|
||||||
{
|
{
|
||||||
getByDirection(direction).playPress();
|
getByDirection(direction).playPress();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play a confirm animation for a given direction.
|
||||||
|
* @param direction The direction of the note to play the confirm animation for.
|
||||||
|
*/
|
||||||
public function playConfirm(direction:NoteDirection):Void
|
public function playConfirm(direction:NoteDirection):Void
|
||||||
{
|
{
|
||||||
getByDirection(direction).playConfirm();
|
getByDirection(direction).playConfirm();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play a confirm animation for a hold note.
|
||||||
|
* @param direction The direction of the note to play the confirm animation for.
|
||||||
|
*/
|
||||||
public function holdConfirm(direction:NoteDirection):Void
|
public function holdConfirm(direction:NoteDirection):Void
|
||||||
{
|
{
|
||||||
getByDirection(direction).holdConfirm();
|
getByDirection(direction).holdConfirm();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a given direction is playing the confirm animation.
|
||||||
|
* @param direction The direction of the note to check.
|
||||||
|
* @return `true` if the note is playing the confirm animation, `false` otherwise.
|
||||||
|
*/
|
||||||
public function isConfirm(direction:NoteDirection):Bool
|
public function isConfirm(direction:NoteDirection):Bool
|
||||||
{
|
{
|
||||||
return getByDirection(direction).isConfirm();
|
return getByDirection(direction).isConfirm();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play a note splash for a given direction.
|
||||||
|
* @param direction The direction of the note to play the splash animation for.
|
||||||
|
*/
|
||||||
public function playNoteSplash(direction:NoteDirection):Void
|
public function playNoteSplash(direction:NoteDirection):Void
|
||||||
{
|
{
|
||||||
if (!showNotesplash) return;
|
if (!showNotesplash) return;
|
||||||
@@ -787,6 +935,10 @@ class Strumline extends FlxSpriteGroup
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play a note hold cover for a given hold note.
|
||||||
|
* @param holdNote The hold note to play the cover animation for.
|
||||||
|
*/
|
||||||
public function playNoteHoldCover(holdNote:SustainTrail):Void
|
public function playNoteHoldCover(holdNote:SustainTrail):Void
|
||||||
{
|
{
|
||||||
if (!showNotesplash) return;
|
if (!showNotesplash) return;
|
||||||
@@ -817,6 +969,11 @@ class Strumline extends FlxSpriteGroup
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a note sprite for a given note data.
|
||||||
|
* @param note The note data to build the note sprite for.
|
||||||
|
* @return The note sprite. Will recycle a note sprite from the pool if available for performance.
|
||||||
|
*/
|
||||||
public function buildNoteSprite(note:SongNoteData):NoteSprite
|
public function buildNoteSprite(note:SongNoteData):NoteSprite
|
||||||
{
|
{
|
||||||
var noteSprite:NoteSprite = constructNoteSprite();
|
var noteSprite:NoteSprite = constructNoteSprite();
|
||||||
@@ -833,13 +990,17 @@ class Strumline extends FlxSpriteGroup
|
|||||||
noteSprite.x += getXPos(DIRECTIONS[note.getDirection() % KEY_COUNT]);
|
noteSprite.x += getXPos(DIRECTIONS[note.getDirection() % KEY_COUNT]);
|
||||||
noteSprite.x -= (noteSprite.width - Strumline.STRUMLINE_SIZE) / 2; // Center it
|
noteSprite.x -= (noteSprite.width - Strumline.STRUMLINE_SIZE) / 2; // Center it
|
||||||
noteSprite.x -= NUDGE;
|
noteSprite.x -= NUDGE;
|
||||||
// noteSprite.x += INITIAL_OFFSET;
|
|
||||||
noteSprite.y = -9999;
|
noteSprite.y = -9999;
|
||||||
}
|
}
|
||||||
|
|
||||||
return noteSprite;
|
return noteSprite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a hold note sprite for a given note data.
|
||||||
|
* @param note The note data to build the hold note sprite for.
|
||||||
|
* @return The hold note sprite. Will recycle a hold note sprite from the pool if available for performance.
|
||||||
|
*/
|
||||||
public function buildHoldNoteSprite(note:SongNoteData):SustainTrail
|
public function buildHoldNoteSprite(note:SongNoteData):SustainTrail
|
||||||
{
|
{
|
||||||
var holdNoteSprite:SustainTrail = constructHoldNoteSprite();
|
var holdNoteSprite:SustainTrail = constructHoldNoteSprite();
|
||||||
@@ -874,7 +1035,7 @@ class Strumline extends FlxSpriteGroup
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom recycling behavior.
|
* Custom recycling behavior for note splashes.
|
||||||
*/
|
*/
|
||||||
function constructNoteSplash():NoteSplash
|
function constructNoteSplash():NoteSplash
|
||||||
{
|
{
|
||||||
@@ -908,7 +1069,7 @@ class Strumline extends FlxSpriteGroup
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom recycling behavior.
|
* Custom recycling behavior for note hold covers.
|
||||||
*/
|
*/
|
||||||
function constructNoteHoldCover():NoteHoldCover
|
function constructNoteHoldCover():NoteHoldCover
|
||||||
{
|
{
|
||||||
@@ -942,7 +1103,7 @@ class Strumline extends FlxSpriteGroup
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom recycling behavior.
|
* Custom recycling behavior for note sprites.
|
||||||
*/
|
*/
|
||||||
function constructNoteSprite():NoteSprite
|
function constructNoteSprite():NoteSprite
|
||||||
{
|
{
|
||||||
@@ -968,7 +1129,7 @@ class Strumline extends FlxSpriteGroup
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom recycling behavior.
|
* Custom recycling behavior for hold note sprites.
|
||||||
*/
|
*/
|
||||||
function constructHoldNoteSprite():SustainTrail
|
function constructHoldNoteSprite():SustainTrail
|
||||||
{
|
{
|
||||||
@@ -1020,6 +1181,10 @@ class Strumline extends FlxSpriteGroup
|
|||||||
FlxTween.tween(arrow, {y: arrow.y + 10, alpha: 1}, 1, {ease: FlxEase.circOut, startDelay: 0.5 + (0.2 * index)});
|
FlxTween.tween(arrow, {y: arrow.y + 10, alpha: 1}, 1, {ease: FlxEase.circOut, startDelay: 0.5 + (0.2 * index)});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play a fade in animation on all arrows in the strumline.
|
||||||
|
* Used when starting a song in Freeplay mode.
|
||||||
|
*/
|
||||||
public function fadeInArrows():Void
|
public function fadeInArrows():Void
|
||||||
{
|
{
|
||||||
for (index => arrow in this.strumlineNotes.members.keyValueIterator())
|
for (index => arrow in this.strumlineNotes.members.keyValueIterator())
|
||||||
@@ -1028,24 +1193,50 @@ class Strumline extends FlxSpriteGroup
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare two note data objects by their strumtime.
|
||||||
|
* @param order The order to sort the notes in.
|
||||||
|
* @param a The first note data object.
|
||||||
|
* @param b The second note data object.
|
||||||
|
* @return The comparison result, based on the time of the notes.
|
||||||
|
*/
|
||||||
function compareNoteData(order:Int, a:SongNoteData, b:SongNoteData):Int
|
function compareNoteData(order:Int, a:SongNoteData, b:SongNoteData):Int
|
||||||
{
|
{
|
||||||
return FlxSort.byValues(order, a.time, b.time);
|
return FlxSort.byValues(order, a.time, b.time);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare two note sprites by their strumtime.
|
||||||
|
* @param order The order to sort the notes in.
|
||||||
|
* @param a The first note sprite.
|
||||||
|
* @param b The second note sprite.
|
||||||
|
* @return The comparison result, based on the time of the notes.
|
||||||
|
*/
|
||||||
function compareNoteSprites(order:Int, a:NoteSprite, b:NoteSprite):Int
|
function compareNoteSprites(order:Int, a:NoteSprite, b:NoteSprite):Int
|
||||||
{
|
{
|
||||||
return FlxSort.byValues(order, a?.strumTime, b?.strumTime);
|
return FlxSort.byValues(order, a?.strumTime, b?.strumTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare two hold note sprites by their strumtime.
|
||||||
|
* @param order The order to sort the notes in.
|
||||||
|
* @param a The first hold note sprite.
|
||||||
|
* @param b The second hold note sprite.
|
||||||
|
* @return The comparison result, based on the time of the notes.
|
||||||
|
*/
|
||||||
function compareHoldNoteSprites(order:Int, a:SustainTrail, b:SustainTrail):Int
|
function compareHoldNoteSprites(order:Int, a:SustainTrail, b:SustainTrail):Int
|
||||||
{
|
{
|
||||||
return FlxSort.byValues(order, a?.strumTime, b?.strumTime);
|
return FlxSort.byValues(order, a?.strumTime, b?.strumTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
override function findMinYHelper()
|
/**
|
||||||
|
* Find the minimum Y position of the strumline.
|
||||||
|
* Ignores the background to ensure the strumline is positioned correctly.
|
||||||
|
* @return The minimum Y position of the strumline.
|
||||||
|
*/
|
||||||
|
override function findMinYHelper():Float
|
||||||
{
|
{
|
||||||
var value = Math.POSITIVE_INFINITY;
|
var value:Float = Math.POSITIVE_INFINITY;
|
||||||
for (member in group.members)
|
for (member in group.members)
|
||||||
{
|
{
|
||||||
if (member == null) continue;
|
if (member == null) continue;
|
||||||
@@ -1053,18 +1244,28 @@ class Strumline extends FlxSpriteGroup
|
|||||||
if (member == this.background) continue;
|
if (member == this.background) continue;
|
||||||
|
|
||||||
var minY:Float;
|
var minY:Float;
|
||||||
if (member.flixelType == SPRITEGROUP) minY = (cast member : FlxSpriteGroup).findMinY();
|
if (member.flixelType == SPRITEGROUP)
|
||||||
|
{
|
||||||
|
minY = (cast member : FlxSpriteGroup).findMinY();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
minY = member.y;
|
minY = member.y;
|
||||||
|
}
|
||||||
|
|
||||||
if (minY < value) value = minY;
|
if (minY < value) value = minY;
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
override function findMaxYHelper()
|
/**
|
||||||
|
* Find the maximum Y position of the strumline.
|
||||||
|
* Ignores the background to ensure the strumline is positioned correctly.
|
||||||
|
* @return The maximum Y position of the strumline.
|
||||||
|
*/
|
||||||
|
override function findMaxYHelper():Float
|
||||||
{
|
{
|
||||||
var value = Math.NEGATIVE_INFINITY;
|
var value:Float = Math.NEGATIVE_INFINITY;
|
||||||
for (member in group.members)
|
for (member in group.members)
|
||||||
{
|
{
|
||||||
if (member == null) continue;
|
if (member == null) continue;
|
||||||
@@ -1072,9 +1273,14 @@ class Strumline extends FlxSpriteGroup
|
|||||||
if (member == this.background) continue;
|
if (member == this.background) continue;
|
||||||
|
|
||||||
var maxY:Float;
|
var maxY:Float;
|
||||||
if (member.flixelType == SPRITEGROUP) maxY = (cast member : FlxSpriteGroup).findMaxY();
|
if (member.flixelType == SPRITEGROUP)
|
||||||
|
{
|
||||||
|
maxY = (cast member : FlxSpriteGroup).findMaxY();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
maxY = member.y + member.height;
|
maxY = member.y + member.height;
|
||||||
|
}
|
||||||
|
|
||||||
if (maxY > value) value = maxY;
|
if (maxY > value) value = maxY;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,11 @@ class StrumlineNote extends FunkinSprite
|
|||||||
return this.direction;
|
return this.direction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Y Offset of the note.
|
||||||
|
*/
|
||||||
|
public var yOffset:Float = 0.0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set this flag to `true` to disable performance optimizations that cause
|
* Set this flag to `true` to disable performance optimizations that cause
|
||||||
* the Strumline note sprite to ignore `velocity` and `acceleration`.
|
* the Strumline note sprite to ignore `velocity` and `acceleration`.
|
||||||
@@ -52,8 +57,8 @@ class StrumlineNote extends FunkinSprite
|
|||||||
|
|
||||||
setup(noteStyle);
|
setup(noteStyle);
|
||||||
|
|
||||||
this.animation.callback = onAnimationFrame;
|
this.animation.onFrameChange.add(onAnimationFrame);
|
||||||
this.animation.finishCallback = onAnimationFinished;
|
this.animation.onFinish.add(onAnimationFinished);
|
||||||
|
|
||||||
// Must be true for animations to play.
|
// Must be true for animations to play.
|
||||||
this.active = true;
|
this.active = true;
|
||||||
|
|||||||
@@ -33,6 +33,11 @@ class SustainTrail extends FlxSprite
|
|||||||
|
|
||||||
public var cover:NoteHoldCover = null;
|
public var cover:NoteHoldCover = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Y Offset of the note.
|
||||||
|
*/
|
||||||
|
public var yOffset:Float = 0.0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set to `true` if the user hit the note and is currently holding the sustain.
|
* Set to `true` if the user hit the note and is currently holding the sustain.
|
||||||
* Should display associated effects.
|
* Should display associated effects.
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ class NoteKind implements INoteScriptedClass
|
|||||||
public function onNoteHit(event:HitNoteScriptEvent):Void {}
|
public function onNoteHit(event:HitNoteScriptEvent):Void {}
|
||||||
|
|
||||||
public function onNoteMiss(event:NoteScriptEvent):Void {}
|
public function onNoteMiss(event:NoteScriptEvent):Void {}
|
||||||
|
|
||||||
|
public function onNoteHoldDrop(event:HoldNoteScriptEvent) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class NoteKindManager
|
|||||||
{
|
{
|
||||||
var noteEvent:NoteScriptEvent = cast(event, NoteScriptEvent);
|
var noteEvent:NoteScriptEvent = cast(event, NoteScriptEvent);
|
||||||
|
|
||||||
var noteKind:NoteKind = noteKinds.get(noteEvent.note.kind);
|
var noteKind:NoteKind = noteKinds.get(noteEvent?.note?.kind);
|
||||||
|
|
||||||
if (noteKind != null)
|
if (noteKind != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -19,11 +19,6 @@ using funkin.data.animation.AnimationData.AnimationDataUtil;
|
|||||||
@:nullSafety
|
@:nullSafety
|
||||||
class NoteStyle implements IRegistryEntry<NoteStyleData>
|
class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* The ID of the note style.
|
|
||||||
*/
|
|
||||||
public final id:String;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Note style data as parsed from the JSON file.
|
* Note style data as parsed from the JSON file.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -69,6 +69,19 @@ class Scoring
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function getMissScore(scoringSystem:ScoringSystem = PBOT1):Int
|
||||||
|
{
|
||||||
|
return switch (scoringSystem)
|
||||||
|
{
|
||||||
|
case LEGACY: LEGACY_MISS_SCORE;
|
||||||
|
case WEEK7: WEEK7_MISS_SCORE;
|
||||||
|
case PBOT1: PBOT1_MISS_SCORE;
|
||||||
|
default:
|
||||||
|
FlxG.log.error('Unknown scoring system: ${scoringSystem}');
|
||||||
|
0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The maximum score a note can receive.
|
* The maximum score a note can receive.
|
||||||
*/
|
*/
|
||||||
@@ -92,7 +105,7 @@ class Scoring
|
|||||||
/**
|
/**
|
||||||
* The score a note receives when it is missed.
|
* The score a note receives when it is missed.
|
||||||
*/
|
*/
|
||||||
public static final PBOT1_MISS_SCORE:Int = 0;
|
public static final PBOT1_MISS_SCORE:Int = -100;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The threshold at which a note hit is considered perfect and always given the max score.
|
* The threshold at which a note hit is considered perfect and always given the max score.
|
||||||
@@ -228,6 +241,11 @@ class Scoring
|
|||||||
*/
|
*/
|
||||||
public static final LEGACY_SHIT_SCORE:Int = 50;
|
public static final LEGACY_SHIT_SCORE:Int = 50;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The score a note receives when missed.
|
||||||
|
*/
|
||||||
|
public static final LEGACY_MISS_SCORE:Int = -10;
|
||||||
|
|
||||||
static function scoreNoteLEGACY(msTiming:Float):Int
|
static function scoreNoteLEGACY(msTiming:Float):Int
|
||||||
{
|
{
|
||||||
var absTiming:Float = Math.abs(msTiming);
|
var absTiming:Float = Math.abs(msTiming);
|
||||||
@@ -276,6 +294,7 @@ class Scoring
|
|||||||
public static final WEEK7_BAD_THRESHOLD:Float = 0.8; // 80% of the hit window, or ~125ms
|
public static final WEEK7_BAD_THRESHOLD:Float = 0.8; // 80% of the hit window, or ~125ms
|
||||||
public static final WEEK7_GOOD_THRESHOLD:Float = 0.55; // 55% of the hit window, or ~91ms
|
public static final WEEK7_GOOD_THRESHOLD:Float = 0.55; // 55% of the hit window, or ~91ms
|
||||||
public static final WEEK7_SICK_THRESHOLD:Float = 0.2; // 20% of the hit window, or ~33ms
|
public static final WEEK7_SICK_THRESHOLD:Float = 0.2; // 20% of the hit window, or ~33ms
|
||||||
|
public static final WEEK7_MISS_SCORE:Int = -10;
|
||||||
public static final WEEK7_SHIT_SCORE:Int = 50;
|
public static final WEEK7_SHIT_SCORE:Int = 50;
|
||||||
public static final WEEK7_BAD_SCORE:Int = 100;
|
public static final WEEK7_BAD_SCORE:Int = 100;
|
||||||
public static final WEEK7_GOOD_SCORE:Int = 200;
|
public static final WEEK7_GOOD_SCORE:Int = 200;
|
||||||
@@ -363,10 +382,9 @@ class Scoring
|
|||||||
|
|
||||||
// Else, use the standard grades
|
// Else, use the standard grades
|
||||||
|
|
||||||
// Grade % (only good and sick), 1.00 is a full combo
|
// Final Grade = (Sick + Good - Miss) / (Total Notes)
|
||||||
var grade = (scoreData.tallies.sick + scoreData.tallies.good) / scoreData.tallies.totalNotes;
|
|
||||||
// Clear % (including bad and shit). 1.00 is a full clear but not a full combo
|
var grade = (scoreData.tallies.sick + scoreData.tallies.good - scoreData.tallies.missed) / scoreData.tallies.totalNotes;
|
||||||
var clear = (scoreData.tallies.totalNotesHit) / scoreData.tallies.totalNotes;
|
|
||||||
|
|
||||||
if (grade == Constants.RANK_PERFECT_THRESHOLD)
|
if (grade == Constants.RANK_PERFECT_THRESHOLD)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -65,19 +65,6 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||||||
*/
|
*/
|
||||||
public static final DEFAULT_SCROLLSPEED:Float = 1.0;
|
public static final DEFAULT_SCROLLSPEED:Float = 1.0;
|
||||||
|
|
||||||
/**
|
|
||||||
* The internal ID of the song.
|
|
||||||
*/
|
|
||||||
public final id:String;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Song metadata as parsed from the JSON file.
|
|
||||||
* This is the data for the `default` variation specifically,
|
|
||||||
* and is needed for the IRegistryEntry interface.
|
|
||||||
* Will only be null if the song data could not be loaded.
|
|
||||||
*/
|
|
||||||
public final _data:Null<SongMetadata>;
|
|
||||||
|
|
||||||
// key = variation id, value = metadata
|
// key = variation id, value = metadata
|
||||||
final _metadata:Map<String, SongMetadata>;
|
final _metadata:Map<String, SongMetadata>;
|
||||||
|
|
||||||
@@ -289,6 +276,14 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||||||
return diff.album ?? '';
|
return diff.album ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getStickerPackId(diffId:String, variation:String):Null<String>
|
||||||
|
{
|
||||||
|
var diff:Null<SongDifficulty> = getDifficulty(diffId, variation);
|
||||||
|
if (diff == null) return null;
|
||||||
|
|
||||||
|
return diff.stickerPack;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Populate the difficulty data from the provided metadata.
|
* Populate the difficulty data from the provided metadata.
|
||||||
* Does not load chart data (that is triggered later when we want to play the song).
|
* Does not load chart data (that is triggered later when we want to play the song).
|
||||||
@@ -331,6 +326,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||||||
|
|
||||||
difficulty.difficultyRating = metadata.playData.ratings.get(diffId) ?? 0;
|
difficulty.difficultyRating = metadata.playData.ratings.get(diffId) ?? 0;
|
||||||
difficulty.album = metadata.playData.album;
|
difficulty.album = metadata.playData.album;
|
||||||
|
difficulty.stickerPack = metadata.playData.stickerPack;
|
||||||
|
|
||||||
difficulty.stage = metadata.playData.stage;
|
difficulty.stage = metadata.playData.stage;
|
||||||
difficulty.noteStyle = metadata.playData.noteStyle;
|
difficulty.noteStyle = metadata.playData.noteStyle;
|
||||||
@@ -624,13 +620,6 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toString():String
|
|
||||||
{
|
|
||||||
return 'Song($id)';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function destroy():Void {}
|
|
||||||
|
|
||||||
public function onPause(event:PauseScriptEvent):Void {};
|
public function onPause(event:PauseScriptEvent):Void {};
|
||||||
|
|
||||||
public function onResume(event:ScriptEvent):Void {};
|
public function onResume(event:ScriptEvent):Void {};
|
||||||
@@ -651,6 +640,8 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||||||
|
|
||||||
public function onNoteMiss(event:NoteScriptEvent):Void {};
|
public function onNoteMiss(event:NoteScriptEvent):Void {};
|
||||||
|
|
||||||
|
public function onNoteHoldDrop(event:HoldNoteScriptEvent) {}
|
||||||
|
|
||||||
public function onNoteGhostMiss(event:GhostMissNoteScriptEvent):Void {};
|
public function onNoteGhostMiss(event:GhostMissNoteScriptEvent):Void {};
|
||||||
|
|
||||||
public function onSongEvent(event:SongEventScriptEvent):Void {};
|
public function onSongEvent(event:SongEventScriptEvent):Void {};
|
||||||
@@ -750,6 +741,7 @@ class SongDifficulty
|
|||||||
|
|
||||||
public var difficultyRating:Int = 0;
|
public var difficultyRating:Int = 0;
|
||||||
public var album:Null<String> = null;
|
public var album:Null<String> = null;
|
||||||
|
public var stickerPack:Null<String> = null;
|
||||||
|
|
||||||
public function new(song:Song, diffId:String, variation:String)
|
public function new(song:Song, diffId:String, variation:String)
|
||||||
{
|
{
|
||||||
@@ -809,7 +801,7 @@ class SongDifficulty
|
|||||||
{
|
{
|
||||||
var suffix:String = (instId != '') ? '-$instId' : '';
|
var suffix:String = (instId != '') ? '-$instId' : '';
|
||||||
|
|
||||||
FlxG.sound.music = FunkinSound.load(Paths.inst(this.song.id, suffix), volume, looped, false, true);
|
FlxG.sound.music = FunkinSound.load(Paths.inst(this.song.id, suffix), volume, looped, false, true, false, null, null, true);
|
||||||
|
|
||||||
// Workaround for a bug where FlxG.sound.music.update() was being called twice.
|
// Workaround for a bug where FlxG.sound.music.update() was being called twice.
|
||||||
FlxG.sound.list.remove(FlxG.sound.music);
|
FlxG.sound.list.remove(FlxG.sound.music);
|
||||||
@@ -956,16 +948,21 @@ class SongDifficulty
|
|||||||
for (playerVoice in playerVoiceList)
|
for (playerVoice in playerVoiceList)
|
||||||
{
|
{
|
||||||
if (!Assets.exists(playerVoice)) continue;
|
if (!Assets.exists(playerVoice)) continue;
|
||||||
result.addPlayerVoice(FunkinSound.load(playerVoice));
|
result.addPlayerVoice(FunkinSound.load(playerVoice, 1.0, false, false, false, false, null, null, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add opponent vocals.
|
// Add opponent vocals.
|
||||||
for (opponentVoice in opponentVoiceList)
|
for (opponentVoice in opponentVoiceList)
|
||||||
{
|
{
|
||||||
if (!Assets.exists(opponentVoice)) continue;
|
if (!Assets.exists(opponentVoice)) continue;
|
||||||
result.addOpponentVoice(FunkinSound.load(opponentVoice));
|
result.addOpponentVoice(FunkinSound.load(opponentVoice, 1.0, false, false, false, false, null, null, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sometimes the sounds don't set their important value to true, so we have to do this manually.
|
||||||
|
result.forEach(function(snd:FunkinSound) {
|
||||||
|
snd.important = true;
|
||||||
|
});
|
||||||
|
|
||||||
result.playerVoicesOffset = offsets.getVocalOffset(characters.player, instId);
|
result.playerVoicesOffset = offsets.getVocalOffset(characters.player, instId);
|
||||||
result.opponentVoicesOffset = offsets.getVocalOffset(characters.opponent, instId);
|
result.opponentVoicesOffset = offsets.getVocalOffset(characters.opponent, instId);
|
||||||
|
|
||||||
|
|||||||
@@ -107,8 +107,8 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
|
|||||||
|
|
||||||
if (this.animation != null)
|
if (this.animation != null)
|
||||||
{
|
{
|
||||||
this.animation.callback = this.onAnimationFrame;
|
this.animation.onFrameChange.add(this.onAnimationFrame);
|
||||||
this.animation.finishCallback = this.onAnimationFinished;
|
this.animation.onFinish.add(this.onAnimationFinished);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,6 +371,8 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
|
|||||||
|
|
||||||
public function onNoteHit(event:HitNoteScriptEvent) {}
|
public function onNoteHit(event:HitNoteScriptEvent) {}
|
||||||
|
|
||||||
|
public function onNoteHoldDrop(event:HoldNoteScriptEvent) {}
|
||||||
|
|
||||||
public function onNoteMiss(event:NoteScriptEvent) {}
|
public function onNoteMiss(event:NoteScriptEvent) {}
|
||||||
|
|
||||||
public function onSongEvent(event:SongEventScriptEvent) {}
|
public function onSongEvent(event:SongEventScriptEvent) {}
|
||||||
|
|||||||
@@ -30,10 +30,6 @@ typedef StagePropGroup = FlxTypedSpriteGroup<StageProp>;
|
|||||||
*/
|
*/
|
||||||
class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements IRegistryEntry<StageData>
|
class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements IRegistryEntry<StageData>
|
||||||
{
|
{
|
||||||
public final id:String;
|
|
||||||
|
|
||||||
public final _data:StageData;
|
|
||||||
|
|
||||||
public var stageName(get, never):String;
|
public var stageName(get, never):String;
|
||||||
|
|
||||||
function get_stageName():String
|
function get_stageName():String
|
||||||
@@ -859,16 +855,6 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override function toString():String
|
|
||||||
{
|
|
||||||
return 'Stage($id)';
|
|
||||||
}
|
|
||||||
|
|
||||||
static function _fetchData(id:String):Null<StageData>
|
|
||||||
{
|
|
||||||
return StageRegistry.instance.parseEntryDataWithMigration(id, StageRegistry.instance.fetchEntryVersion(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onScriptEvent(event:ScriptEvent)
|
public function onScriptEvent(event:ScriptEvent)
|
||||||
{
|
{
|
||||||
// Ensure all custom events get broadcast to the elements of the stage.
|
// Ensure all custom events get broadcast to the elements of the stage.
|
||||||
@@ -901,6 +887,8 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
|
|||||||
|
|
||||||
public function onNoteMiss(event:NoteScriptEvent) {}
|
public function onNoteMiss(event:NoteScriptEvent) {}
|
||||||
|
|
||||||
|
public function onNoteHoldDrop(event:HoldNoteScriptEvent) {}
|
||||||
|
|
||||||
public function onSongEvent(event:SongEventScriptEvent) {}
|
public function onSongEvent(event:SongEventScriptEvent) {}
|
||||||
|
|
||||||
public function onNoteGhostMiss(event:GhostMissNoteScriptEvent) {}
|
public function onNoteGhostMiss(event:GhostMissNoteScriptEvent) {}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class Save
|
|||||||
{
|
{
|
||||||
if (_instance == null)
|
if (_instance == null)
|
||||||
{
|
{
|
||||||
return _instance = load();
|
return load();
|
||||||
}
|
}
|
||||||
return _instance;
|
return _instance;
|
||||||
}
|
}
|
||||||
@@ -55,7 +55,10 @@ class Save
|
|||||||
trace("[SAVE] Loading save...");
|
trace("[SAVE] Loading save...");
|
||||||
|
|
||||||
// Bind save data.
|
// Bind save data.
|
||||||
return loadFromSlot(BASE_SAVE_SLOT);
|
var loadedSave:Save = loadFromSlot(BASE_SAVE_SLOT);
|
||||||
|
if (_instance == null) _instance = loadedSave;
|
||||||
|
|
||||||
|
return loadedSave;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function clearData():Void
|
public static function clearData():Void
|
||||||
@@ -112,6 +115,7 @@ class Save
|
|||||||
zoomCamera: true,
|
zoomCamera: true,
|
||||||
debugDisplay: false,
|
debugDisplay: false,
|
||||||
autoPause: true,
|
autoPause: true,
|
||||||
|
vsyncMode: 'Off',
|
||||||
strumlineBackgroundOpacity: 0,
|
strumlineBackgroundOpacity: 0,
|
||||||
autoFullscreen: false,
|
autoFullscreen: false,
|
||||||
inputOffset: 0,
|
inputOffset: 0,
|
||||||
@@ -730,10 +734,6 @@ class Save
|
|||||||
// Directly set the highscore.
|
// Directly set the highscore.
|
||||||
setSongScore(songId, difficultyId, newScoreData);
|
setSongScore(songId, difficultyId, newScoreData);
|
||||||
|
|
||||||
#if FEATURE_NEWGROUNDS
|
|
||||||
Leaderboards.submitSongScore(songId, difficultyId, newScoreData.score);
|
|
||||||
#end
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -987,6 +987,7 @@ class Save
|
|||||||
* If you set slot to `2`, it will load an independent save file from slot 2.
|
* If you set slot to `2`, it will load an independent save file from slot 2.
|
||||||
* @param slot
|
* @param slot
|
||||||
*/
|
*/
|
||||||
|
@:haxe.warning("-WDeprecated")
|
||||||
static function loadFromSlot(slot:Int):Save
|
static function loadFromSlot(slot:Int):Save
|
||||||
{
|
{
|
||||||
trace('[SAVE] Loading save from slot $slot...');
|
trace('[SAVE] Loading save from slot $slot...');
|
||||||
@@ -1128,6 +1129,7 @@ class Save
|
|||||||
* @param slot The slot number to check.
|
* @param slot The slot number to check.
|
||||||
* @return Whether the slot is not empty.
|
* @return Whether the slot is not empty.
|
||||||
*/
|
*/
|
||||||
|
@:haxe.warning("-WDeprecated")
|
||||||
static function querySlot(slot:Int):Bool
|
static function querySlot(slot:Int):Bool
|
||||||
{
|
{
|
||||||
var targetSaveData = new FlxSave();
|
var targetSaveData = new FlxSave();
|
||||||
@@ -1397,6 +1399,12 @@ typedef SaveDataOptions =
|
|||||||
*/
|
*/
|
||||||
var autoPause:Bool;
|
var autoPause:Bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If enabled, the game will utilize VSync (or adaptive VSync) on startup.
|
||||||
|
* @default `Off`
|
||||||
|
*/
|
||||||
|
var vsyncMode:String;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If >0, the game will display a semi-opaque background under the notes.
|
* If >0, the game will display a semi-opaque background under the notes.
|
||||||
* `0` for no background, `100` for solid black if you're freaky like that
|
* `0` for no background, `100` for solid black if you're freaky like that
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import funkin.input.Controls;
|
|||||||
* MusicBeatState actually represents the core utility FlxState of the game.
|
* MusicBeatState actually represents the core utility FlxState of the game.
|
||||||
* It includes functionality for event handling, as well as maintaining BPM-based update events.
|
* It includes functionality for event handling, as well as maintaining BPM-based update events.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class MusicBeatState extends FlxTransitionableState implements IEventHandler
|
class MusicBeatState extends FlxTransitionableState implements IEventHandler
|
||||||
{
|
{
|
||||||
var controls(get, never):Controls;
|
var controls(get, never):Controls;
|
||||||
@@ -25,8 +26,8 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
|
|||||||
inline function get_controls():Controls
|
inline function get_controls():Controls
|
||||||
return PlayerSettings.player1.controls;
|
return PlayerSettings.player1.controls;
|
||||||
|
|
||||||
public var leftWatermarkText:FlxText = null;
|
public var leftWatermarkText:Null<FlxText> = null;
|
||||||
public var rightWatermarkText:FlxText = null;
|
public var rightWatermarkText:Null<FlxText> = null;
|
||||||
|
|
||||||
public var conductorInUse(get, set):Conductor;
|
public var conductorInUse(get, set):Conductor;
|
||||||
|
|
||||||
@@ -163,6 +164,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
|
|||||||
sort(SortUtil.byZIndex, FlxSort.ASCENDING);
|
sort(SortUtil.byZIndex, FlxSort.ASCENDING);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@:nullSafety(Off)
|
||||||
override function startOutro(onComplete:() -> Void):Void
|
override function startOutro(onComplete:() -> Void):Void
|
||||||
{
|
{
|
||||||
var event = new StateChangeScriptEvent(STATE_CHANGE_BEGIN, null, true);
|
var event = new StateChangeScriptEvent(STATE_CHANGE_BEGIN, null, true);
|
||||||
|
|||||||
@@ -16,10 +16,11 @@ import funkin.input.Controls;
|
|||||||
/**
|
/**
|
||||||
* MusicBeatSubState reincorporates the functionality of MusicBeatState into an FlxSubState.
|
* MusicBeatSubState reincorporates the functionality of MusicBeatState into an FlxSubState.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class MusicBeatSubState extends FlxSubState implements IEventHandler
|
class MusicBeatSubState extends FlxSubState implements IEventHandler
|
||||||
{
|
{
|
||||||
public var leftWatermarkText:FlxText = null;
|
public var leftWatermarkText:Null<FlxText> = null;
|
||||||
public var rightWatermarkText:FlxText = null;
|
public var rightWatermarkText:Null<FlxText> = null;
|
||||||
|
|
||||||
public var conductorInUse(get, set):Conductor;
|
public var conductorInUse(get, set):Conductor;
|
||||||
|
|
||||||
@@ -186,6 +187,7 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler
|
|||||||
this._parentState.openSubState(substate);
|
this._parentState.openSubState(substate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@:nullSafety(Off)
|
||||||
override function startOutro(onComplete:() -> Void):Void
|
override function startOutro(onComplete:() -> Void):Void
|
||||||
{
|
{
|
||||||
var event = new StateChangeScriptEvent(STATE_CHANGE_BEGIN, null, true);
|
var event = new StateChangeScriptEvent(STATE_CHANGE_BEGIN, null, true);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import funkin.graphics.FlxFilteredSprite;
|
|||||||
* The icon that gets used for Freeplay capsules and char select
|
* The icon that gets used for Freeplay capsules and char select
|
||||||
* NOT to be confused with the CharIcon class, which is for the in-game icons
|
* NOT to be confused with the CharIcon class, which is for the in-game icons
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class PixelatedIcon extends FlxFilteredSprite
|
class PixelatedIcon extends FlxFilteredSprite
|
||||||
{
|
{
|
||||||
public function new(x:Float, y:Float)
|
public function new(x:Float, y:Float)
|
||||||
@@ -28,7 +29,7 @@ class PixelatedIcon extends FlxFilteredSprite
|
|||||||
charPath += "monsterpixel";
|
charPath += "monsterpixel";
|
||||||
case "mom" | "mom-car":
|
case "mom" | "mom-car":
|
||||||
charPath += "mommypixel";
|
charPath += "mommypixel";
|
||||||
case "pico-blazin" | "pico-playable" | "pico-speaker":
|
case "pico-blazin" | "pico-playable" | "pico-speaker" | "pico-pixel" | "pico-holding-nene":
|
||||||
charPath += "picopixel";
|
charPath += "picopixel";
|
||||||
case "gf-christmas" | "gf-car" | "gf-pixel" | "gf-tankmen":
|
case "gf-christmas" | "gf-car" | "gf-pixel" | "gf-tankmen":
|
||||||
charPath += "gfpixel";
|
charPath += "gfpixel";
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package funkin.ui;
|
|||||||
import funkin.ui.AtlasText;
|
import funkin.ui.AtlasText;
|
||||||
import funkin.ui.MenuList;
|
import funkin.ui.MenuList;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class TextMenuList extends MenuTypedList<TextMenuItem>
|
class TextMenuList extends MenuTypedList<TextMenuItem>
|
||||||
{
|
{
|
||||||
public function new(navControls:NavControls = Vertical, ?wrapMode)
|
public function new(navControls:NavControls = Vertical, ?wrapMode)
|
||||||
@@ -18,6 +19,7 @@ class TextMenuList extends MenuTypedList<TextMenuItem>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class TextMenuItem extends TextTypedMenuItem<AtlasText>
|
class TextMenuItem extends TextTypedMenuItem<AtlasText>
|
||||||
{
|
{
|
||||||
public function new(x = 0.0, y = 0.0, name:String, font:AtlasFont = BOLD, ?callback:Void->Void)
|
public function new(x = 0.0, y = 0.0, name:String, font:AtlasFont = BOLD, ?callback:Void->Void)
|
||||||
@@ -27,6 +29,7 @@ class TextMenuItem extends TextTypedMenuItem<AtlasText>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class TextTypedMenuItem<T:AtlasText> extends MenuTypedItem<T>
|
class TextTypedMenuItem<T:AtlasText> extends MenuTypedItem<T>
|
||||||
{
|
{
|
||||||
public function new(x = 0.0, y = 0.0, label:T, name:String, ?callback:Void->Void)
|
public function new(x = 0.0, y = 0.0, label:T, name:String, ?callback:Void->Void)
|
||||||
|
|||||||
@@ -54,20 +54,6 @@ class CharSelectGF extends FlxAtlasSprite implements IBPMSyncedScriptedClass
|
|||||||
doFade(animInInfo);
|
doFade(animInInfo);
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
#if FEATURE_DEBUG_FUNCTIONS
|
|
||||||
if (FlxG.keys.justPressed.J)
|
|
||||||
{
|
|
||||||
alpha = 1;
|
|
||||||
x = y = 0;
|
|
||||||
fadingStatus = FADE_OUT;
|
|
||||||
}
|
|
||||||
if (FlxG.keys.justPressed.K)
|
|
||||||
{
|
|
||||||
alpha = 0;
|
|
||||||
fadingStatus = FADE_IN;
|
|
||||||
}
|
|
||||||
#end
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onStepHit(event:SongTimeScriptEvent):Void {}
|
public function onStepHit(event:SongTimeScriptEvent):Void {}
|
||||||
|
|||||||
@@ -425,6 +425,7 @@ class CharSelectSubState extends MusicBeatSubState
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
#if FEATURE_NEWGROUNDS
|
#if FEATURE_NEWGROUNDS
|
||||||
|
// Make the character unlock medal retroactive.
|
||||||
if (availableChars.size() > 1) Medals.award(CharSelect);
|
if (availableChars.size() > 1) Medals.award(CharSelect);
|
||||||
#end
|
#end
|
||||||
|
|
||||||
@@ -497,7 +498,7 @@ class CharSelectSubState extends MusicBeatSubState
|
|||||||
grpIcons.scrollFactor.set();
|
grpIcons.scrollFactor.set();
|
||||||
}
|
}
|
||||||
|
|
||||||
function unLock()
|
function unLock():Void
|
||||||
{
|
{
|
||||||
var index = nonLocks[0];
|
var index = nonLocks[0];
|
||||||
|
|
||||||
@@ -570,6 +571,7 @@ class CharSelectSubState extends MusicBeatSubState
|
|||||||
});
|
});
|
||||||
|
|
||||||
#if FEATURE_NEWGROUNDS
|
#if FEATURE_NEWGROUNDS
|
||||||
|
// Grant the medal when the player unlocks a character.
|
||||||
Medals.award(CharSelect);
|
Medals.award(CharSelect);
|
||||||
#end
|
#end
|
||||||
|
|
||||||
|
|||||||
@@ -18,9 +18,6 @@ class Nametag extends FlxSprite
|
|||||||
shader = mosaicShader;
|
shader = mosaicShader;
|
||||||
|
|
||||||
switchChar("bf");
|
switchChar("bf");
|
||||||
|
|
||||||
FlxG.debugger.addTrackerProfile(new TrackerProfile(Nametag, ["midpointX", "midpointY"]));
|
|
||||||
FlxG.debugger.track(this, "Nametag");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updatePosition():Void
|
public function updatePosition():Void
|
||||||
@@ -71,16 +68,12 @@ class Nametag extends FlxSprite
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setBlockTimer(frame:Int, ?forceX:Float, ?forceY:Float)
|
function setBlockTimer(frame:Int, ?forceX:Float, ?forceY:Float):Void
|
||||||
{
|
{
|
||||||
var daX:Float = 10 * FlxG.random.int(1, 4);
|
var daX:Float = forceX ?? 10 * FlxG.random.int(1, 4);
|
||||||
var daY:Float = 10 * FlxG.random.int(1, 4);
|
var daY:Float = forceY ?? 10 * FlxG.random.int(1, 4);
|
||||||
|
|
||||||
if (forceX != null) daX = forceX;
|
FlxTimer.wait(frame / 30, () -> {
|
||||||
|
|
||||||
if (forceY != null) daY = forceY;
|
|
||||||
|
|
||||||
new FlxTimer().start(frame / 30, _ -> {
|
|
||||||
mosaicShader.setBlockSize(daX, daY);
|
mosaicShader.setBlockSize(daX, daY);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ class DebugMenuSubState extends MusicBeatSubState
|
|||||||
|
|
||||||
function testStickers()
|
function testStickers()
|
||||||
{
|
{
|
||||||
openSubState(new funkin.ui.transition.StickerSubState({}));
|
openSubState(new funkin.ui.transition.stickers.StickerSubState({}));
|
||||||
trace('opened stickers');
|
trace('opened stickers');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -528,8 +528,11 @@ class DebugBoundingState extends FlxState
|
|||||||
trace('Added ${offsetAnimationDropdown.dataSource.size} to HaxeUI dropdown');
|
trace('Added ${offsetAnimationDropdown.dataSource.size} to HaxeUI dropdown');
|
||||||
|
|
||||||
offsetAnimationDropdown.onChange = function(event:UIEvent) {
|
offsetAnimationDropdown.onChange = function(event:UIEvent) {
|
||||||
trace('Selected animation ${event?.data?.id}');
|
if (event.data != null)
|
||||||
playCharacterAnimation(event.data.id, true);
|
{
|
||||||
|
trace('Selected animation ${event.data.id}');
|
||||||
|
playCharacterAnimation(event.data.id, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
txtOffsetShit.text = 'Offset: ' + swagChar.animOffsets;
|
txtOffsetShit.text = 'Offset: ' + swagChar.animOffsets;
|
||||||
@@ -569,7 +572,7 @@ class DebugBoundingState extends FlxState
|
|||||||
_file.addEventListener(Event.COMPLETE, onSaveComplete);
|
_file.addEventListener(Event.COMPLETE, onSaveComplete);
|
||||||
_file.addEventListener(Event.CANCEL, onSaveCancel);
|
_file.addEventListener(Event.CANCEL, onSaveCancel);
|
||||||
_file.addEventListener(IOErrorEvent.IO_ERROR, onSaveError);
|
_file.addEventListener(IOErrorEvent.IO_ERROR, onSaveError);
|
||||||
_file.save(saveString,);
|
_file.save(saveString, fileName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ class FlxAnimateTest extends MusicBeatState
|
|||||||
{
|
{
|
||||||
super.create();
|
super.create();
|
||||||
|
|
||||||
sprite = new FlxAtlasSprite(0, 0, 'assets/images/charSelect/maskTest');
|
sprite = new FlxAtlasSprite(-100, 0, 'week3:assets/week3/images/philly/erect/picoFlowers');
|
||||||
add(sprite);
|
add(sprite);
|
||||||
sprite.playAnimation(null, false, false, true);
|
sprite.playAnimation(sprite.anim.stageInstance.symbol.name, false, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override function update(elapsed:Float):Void
|
public override function update(elapsed:Float):Void
|
||||||
|
|||||||
@@ -1334,7 +1334,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||||||
result = [];
|
result = [];
|
||||||
trace('Initializing blank chart for difficulty ' + selectedDifficulty);
|
trace('Initializing blank chart for difficulty ' + selectedDifficulty);
|
||||||
currentSongChartData.notes.set(selectedDifficulty, result);
|
currentSongChartData.notes.set(selectedDifficulty, result);
|
||||||
currentSongMetadata.playData.difficulties.pushUnique(selectedDifficulty);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@@ -1343,7 +1342,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||||||
function set_currentSongChartNoteData(value:Array<SongNoteData>):Array<SongNoteData>
|
function set_currentSongChartNoteData(value:Array<SongNoteData>):Array<SongNoteData>
|
||||||
{
|
{
|
||||||
currentSongChartData.notes.set(selectedDifficulty, value);
|
currentSongChartData.notes.set(selectedDifficulty, value);
|
||||||
currentSongMetadata.playData.difficulties.pushUnique(selectedDifficulty);
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1636,9 +1634,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||||||
noteTooltipsDirty = true;
|
noteTooltipsDirty = true;
|
||||||
notePreviewViewportBoundsDirty = true;
|
notePreviewViewportBoundsDirty = true;
|
||||||
|
|
||||||
// Make sure the difficulty we selected is in the list of difficulties.
|
|
||||||
currentSongMetadata.playData.difficulties.pushUnique(selectedDifficulty);
|
|
||||||
|
|
||||||
return selectedDifficulty;
|
return selectedDifficulty;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2361,7 +2356,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!FileUtil.doesFileExist(chartPath))
|
if (!FileUtil.fileExists(chartPath))
|
||||||
{
|
{
|
||||||
trace('Previously loaded chart file (${chartPath.toString()}) does not exist, disabling link...');
|
trace('Previously loaded chart file (${chartPath.toString()}) does not exist, disabling link...');
|
||||||
menuItemRecentChart.disabled = true;
|
menuItemRecentChart.disabled = true;
|
||||||
@@ -3230,7 +3225,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||||||
#if sys
|
#if sys
|
||||||
// TODO: Is there a way to open a folder and highlight a file in it?
|
// TODO: Is there a way to open a folder and highlight a file in it?
|
||||||
var absoluteBackupsPath:String = Path.join([Sys.getCwd(), ChartEditorImportExportHandler.BACKUPS_PATH]);
|
var absoluteBackupsPath:String = Path.join([Sys.getCwd(), ChartEditorImportExportHandler.BACKUPS_PATH]);
|
||||||
WindowUtil.openFolder(absoluteBackupsPath);
|
FileUtil.openFolder(absoluteBackupsPath);
|
||||||
return true;
|
return true;
|
||||||
#else
|
#else
|
||||||
trace('No file system access, cannot open backups folder.');
|
trace('No file system access, cannot open backups folder.');
|
||||||
@@ -5118,6 +5113,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||||||
if (songPos < 0) songPosMinutes = '-' + songPosMinutes;
|
if (songPos < 0) songPosMinutes = '-' + songPosMinutes;
|
||||||
var songPosString:String = '${songPosMinutes}:${songPosSeconds}.${songPosMilliseconds}';
|
var songPosString:String = '${songPosMinutes}:${songPosSeconds}.${songPosMilliseconds}';
|
||||||
|
|
||||||
|
var roundedBeat = FlxMath.roundDecimal(Conductor.instance.currentBeatTime, 2);
|
||||||
|
var parts:Array<String> = Std.string(roundedBeat).split('.');
|
||||||
|
if (parts.length == 1) parts.push('00');
|
||||||
|
else if (parts[1].length < 2) parts[1] += '0';
|
||||||
|
songPosString += ' | Beat: ${parts.join('.')} | Step: ${Conductor.instance.currentStep}';
|
||||||
|
|
||||||
if (playbarSongPos.value != songPosString) playbarSongPos.value = songPosString;
|
if (playbarSongPos.value != songPosString) playbarSongPos.value = songPosString;
|
||||||
|
|
||||||
var songRemaining:Float = Math.max(songLengthInMs - songPos, 0.0);
|
var songRemaining:Float = Math.max(songLengthInMs - songPos, 0.0);
|
||||||
@@ -5837,6 +5838,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||||||
{
|
{
|
||||||
uiCamera.revive();
|
uiCamera.revive();
|
||||||
FlxG.cameras.reset(uiCamera);
|
FlxG.cameras.reset(uiCamera);
|
||||||
|
uiCamera.onResize();
|
||||||
|
|
||||||
add(this.root);
|
add(this.root);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,8 @@ class ChartEditorWelcomeDialog extends ChartEditorBaseDialog
|
|||||||
#end
|
#end
|
||||||
|
|
||||||
linkRecentChart.onClick = function(_event) {
|
linkRecentChart.onClick = function(_event) {
|
||||||
|
linkRecentChart.hide();
|
||||||
|
|
||||||
this.hideDialog(DialogButton.CANCEL);
|
this.hideDialog(DialogButton.CANCEL);
|
||||||
|
|
||||||
// Load chart from file
|
// Load chart from file
|
||||||
@@ -106,7 +108,7 @@ class ChartEditorWelcomeDialog extends ChartEditorBaseDialog
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!FileUtil.doesFileExist(chartPath))
|
if (!FileUtil.fileExists(chartPath))
|
||||||
{
|
{
|
||||||
trace('Previously loaded chart file (${chartPath}) does not exist, disabling link...');
|
trace('Previously loaded chart file (${chartPath}) does not exist, disabling link...');
|
||||||
linkRecentChart.disabled = true;
|
linkRecentChart.disabled = true;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user