Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0971a0bb4c | ||
|
|
136e7358b1 | ||
|
|
84ff83759f | ||
|
|
9d3131bc8d | ||
|
|
90f6f80a71 | ||
|
|
e3ed153a7c | ||
|
|
66781181fa | ||
|
|
93039c3d65 | ||
|
|
a1370157a4 | ||
|
|
7ce7d7839e | ||
|
|
042dfb7442 | ||
|
|
7a3d983bba | ||
|
|
ac3e2b2560 | ||
|
|
db0944712d | ||
|
|
fb1b3cc1cd | ||
|
|
007a32dc9c | ||
|
|
9a57762a9c | ||
|
|
06fee5599c | ||
|
|
cd2b458da2 | ||
|
|
8a8160dfe7 | ||
|
|
5630e74584 | ||
|
|
067f52ca91 | ||
|
|
2eb5208c46 | ||
|
|
b5f3996236 | ||
|
|
378179d1f7 | ||
|
|
f1c1e63375 | ||
|
|
ec749c7157 | ||
|
|
e321861b9f | ||
|
|
c4ed31b246 | ||
|
|
2053f0d691 | ||
|
|
ffd76963bd | ||
|
|
4669ac03a9 | ||
|
|
cb16a8d55e | ||
|
|
e1aa25a249 | ||
|
|
67126b33fa | ||
|
|
a84ae6069e | ||
|
|
c3f982b32c | ||
|
|
67bce85343 | ||
|
|
998bbb213a | ||
|
|
8d02a3f984 | ||
|
|
85763813ae | ||
|
|
b29296288e | ||
|
|
5693d626b5 | ||
|
|
84d8c02920 | ||
|
|
ec9b5db210 | ||
|
|
7e75308423 | ||
|
|
073dbaa023 | ||
|
|
f6697f8ddc | ||
|
|
78d0ad9956 | ||
|
|
52f5c24f96 | ||
|
|
252b87c451 | ||
|
|
0389cd6760 | ||
|
|
29c860ed43 | ||
|
|
a86cfded2a | ||
|
|
3d5482660c | ||
|
|
299f4840cf | ||
|
|
8749372725 | ||
|
|
50f65177cb | ||
|
|
1c2a403dea | ||
|
|
84ce43eff7 | ||
|
|
cca997b724 | ||
|
|
298bc9496b | ||
|
|
2edb318901 | ||
|
|
15bce04a50 | ||
|
|
5c6d72d8d5 | ||
|
|
1937529c1f | ||
|
|
0bf1bead03 | ||
|
|
58508755f9 | ||
|
|
bcb7bc4cbc | ||
|
|
28d50a4567 | ||
|
|
6a113addca | ||
|
|
c7238fcfd1 | ||
|
|
29a7005d8a | ||
|
|
d23ae7c55b | ||
|
|
d11acfd83b | ||
|
|
754e2edbeb | ||
|
|
5548fd52f4 | ||
|
|
6c743b3b89 | ||
|
|
e978f5df76 | ||
|
|
3601b50e32 | ||
|
|
e535ae4ca6 | ||
|
|
ec8ecc6163 | ||
|
|
01e9888839 | ||
|
|
2c73b241e8 | ||
|
|
3ef238a2f6 | ||
|
|
690f3090b7 | ||
|
|
a41d3033d5 | ||
|
|
d02e834460 | ||
|
|
2a6eadcbff | ||
|
|
78cde16af2 | ||
|
|
4946e7f4c7 | ||
|
|
8a9b1b4727 | ||
|
|
5f64d89d0e | ||
|
|
143ef88e6f | ||
|
|
cb4ac352e6 | ||
|
|
ccb2e1889f | ||
|
|
9045ae2d9c | ||
|
|
e3a13bee8c | ||
|
|
426bff3bf5 | ||
|
|
249ff69f9f | ||
|
|
4af4c2defc | ||
|
|
76bf5df57f | ||
|
|
c46483722e | ||
|
|
69bcd0834e | ||
|
|
29e89c8acb | ||
|
|
778ce54047 | ||
|
|
47ed9d7fb2 | ||
|
|
00c1428c0f | ||
|
|
0b908b3cab | ||
|
|
50de041251 | ||
|
|
f9186b67a7 | ||
|
|
a8aeae4f7f | ||
|
|
4d73e0cc47 | ||
|
|
b03c8a9cf8 | ||
|
|
3cbba8ba29 | ||
|
|
bfee63f588 | ||
|
|
35aa8731d0 | ||
|
|
8fd84f9d13 | ||
|
|
25cafe7550 | ||
|
|
7d6fa19ff4 | ||
|
|
9f0bc2bd52 | ||
|
|
d81e09b1c3 | ||
|
|
a128afedf7 | ||
|
|
472d41a893 | ||
|
|
215e20cabe | ||
|
|
3c30575727 | ||
|
|
f30c0cff49 | ||
|
|
fc6e194d20 | ||
|
|
35005df32a | ||
|
|
62149aea39 | ||
|
|
fdddfba3be | ||
|
|
ab3b976f3c | ||
|
|
e02bb9b21c | ||
|
|
afcb57aaf0 | ||
|
|
99657da1f9 | ||
|
|
3bb981adb0 | ||
|
|
50a5304ad4 | ||
|
|
a4b59d14bf | ||
|
|
9d5cde0bf1 | ||
|
|
5efbff044b | ||
|
|
a469385cdb | ||
|
|
970816592f | ||
|
|
ba90a6abe0 | ||
|
|
d54dc6179a | ||
|
|
b5ed7f40d4 | ||
|
|
b28af79eb3 | ||
|
|
5f563f0555 | ||
|
|
1beb21e57a | ||
|
|
b6b2248aa3 | ||
|
|
d30fe61ca0 | ||
|
|
7b7d5deed5 | ||
|
|
19e6029fb2 | ||
|
|
2a5269c2a7 | ||
|
|
f7fb640e52 | ||
|
|
119148acf0 | ||
|
|
598a3bea24 | ||
|
|
b03d320c12 | ||
|
|
61d23b9985 | ||
|
|
b70983a0e4 | ||
|
|
de07497ae3 | ||
|
|
ea9dab4b73 | ||
|
|
f2b3f47e73 | ||
|
|
6e33cac9ec | ||
|
|
dab417b391 | ||
|
|
0706192cbe | ||
|
|
7fbbc5cf95 | ||
|
|
4939b09fb0 | ||
|
|
aae7dcdada | ||
|
|
e6525d0129 | ||
|
|
6efdbde8da | ||
|
|
b7c3f5ac9f | ||
|
|
811caf6b70 | ||
|
|
d710318830 | ||
|
|
1fc835494d | ||
|
|
2e9223ec72 | ||
|
|
9624b57e44 | ||
|
|
7d2b946e9c | ||
|
|
5a877a14a0 | ||
|
|
535ac0e528 | ||
|
|
35b5957715 | ||
|
|
e915b637c5 | ||
|
|
f38a95bbf7 | ||
|
|
ebfce9f990 | ||
|
|
359e4b41e5 | ||
|
|
b0c9365c56 | ||
|
|
65fddd95bb | ||
|
|
f1d3ace247 | ||
|
|
9e90e67673 | ||
|
|
3ce7cd6990 | ||
|
|
d4cd2d031c | ||
|
|
081d2d8fd8 | ||
|
|
99f4d13e47 | ||
|
|
8cad10263e | ||
|
|
8470880e4a | ||
|
|
6bde7c34a0 | ||
|
|
3ac10fb45c | ||
|
|
fb0b277a9c | ||
|
|
519a6f3412 | ||
|
|
27f700dd98 | ||
|
|
f015976521 | ||
|
|
265039b025 | ||
|
|
8c6cb72d47 | ||
|
|
b3221c0e3f | ||
|
|
c7cb03dd1d | ||
|
|
a7c0512503 | ||
|
|
74a94d0080 | ||
|
|
d214ae1747 | ||
|
|
6ee1bddacb | ||
|
|
8b96d1bbac | ||
|
|
d4e11f786c | ||
|
|
724c415016 | ||
|
|
055ae5b932 | ||
|
|
85e9dbb959 | ||
|
|
c5060b6eec | ||
|
|
df77d11896 | ||
|
|
aa2d934f22 | ||
|
|
3f6ad6f8ad | ||
|
|
45efd78931 | ||
|
|
b95e228322 | ||
|
|
c9e2e7bef5 | ||
|
|
bab4ac4902 | ||
|
|
b8d1341a21 | ||
|
|
55e0970e4c | ||
|
|
82c2d54079 | ||
|
|
c4b475e86a | ||
|
|
9fddb727b8 | ||
|
|
224d930ae6 | ||
|
|
3b9d400f57 | ||
|
|
f8d0ddef34 | ||
|
|
18edadb712 | ||
|
|
80fc0cd526 | ||
|
|
e7674ec31c | ||
|
|
0b531c5b21 | ||
|
|
8efcd1eae1 | ||
|
|
06485518db | ||
|
|
57523c9439 | ||
|
|
42544ee757 | ||
|
|
4be4570c94 | ||
|
|
d0ec26c44a | ||
|
|
8252b2a6b3 | ||
|
|
a5ac717677 | ||
|
|
ea2e6506c8 | ||
|
|
1e8d3c715b | ||
|
|
4bc2510d3c | ||
|
|
0574f8b07b | ||
|
|
11224c5999 | ||
|
|
367e3e9008 | ||
|
|
a27c4ae24f | ||
|
|
1f21e1114b | ||
|
|
16aff6e7c3 | ||
|
|
9f3af1ab94 | ||
|
|
e23a85682a | ||
|
|
fd6ca6e267 | ||
|
|
d5c7e4ffdb | ||
|
|
688abb5571 | ||
|
|
0a8719088d | ||
|
|
df87d677e1 | ||
|
|
517984d120 | ||
|
|
3fba55a422 | ||
|
|
178389ea0b | ||
|
|
16905c8210 | ||
|
|
77346696f8 | ||
|
|
df266e66d3 | ||
|
|
00c7767f8a | ||
|
|
c62b53ebe9 | ||
|
|
d43042fd36 | ||
|
|
964f6878c3 | ||
|
|
8778ab1d0e | ||
|
|
59ad15aee4 | ||
|
|
c8caeb42de | ||
|
|
ba96b11196 | ||
|
|
ff1ab1eb42 | ||
|
|
4d81aa0fe6 | ||
|
|
691da783fc | ||
|
|
a93cd05aeb | ||
|
|
ee44981995 | ||
|
|
5e1d25e791 | ||
|
|
f8a0627fd2 | ||
|
|
f8232911cb | ||
|
|
aed100df47 | ||
|
|
9d3c043f8a | ||
|
|
2547fbb8f4 | ||
|
|
2993e17278 | ||
|
|
0c5815c176 | ||
|
|
97e69c6343 | ||
|
|
51c304a6fa | ||
|
|
47a2cf4e5d | ||
|
|
a12b0e6ec3 | ||
|
|
2be22055cc | ||
|
|
2fb55ba3ed | ||
|
|
9ad2bb35f9 | ||
|
|
481f7ab61a | ||
|
|
717894300d | ||
|
|
f82f0d3b05 | ||
|
|
927e37b35b | ||
|
|
be5e0aafeb | ||
|
|
488fc6888f | ||
|
|
8e81f6bdf4 | ||
|
|
09c8a2600e | ||
|
|
0a7902c4b4 | ||
|
|
02dd7e7264 | ||
|
|
91bc63004a | ||
|
|
f3e04114eb | ||
|
|
ef2885ed0e | ||
|
|
3b3b9d97ba | ||
|
|
be38ae6c00 | ||
|
|
0589624d11 | ||
|
|
dd7a894b51 | ||
|
|
73efd963b9 | ||
|
|
250ec840ef | ||
|
|
ba4677857a | ||
|
|
e3159745fb | ||
|
|
b7eaa238e0 | ||
|
|
fb71f9087d | ||
|
|
9618cd2128 | ||
|
|
9468d95506 | ||
|
|
4997bc27fd | ||
|
|
73982fbd60 | ||
|
|
7904a6a20f | ||
|
|
ade4aeb3f7 | ||
|
|
e46108fa15 | ||
|
|
7346a46975 | ||
|
|
5baa4c7ef7 | ||
|
|
4ce79cc386 | ||
|
|
11fee0f200 | ||
|
|
26b357df72 | ||
|
|
4a531888b5 | ||
|
|
262bcc4d8b | ||
|
|
088ff3744a | ||
|
|
01b1ce679c | ||
|
|
267fd0eadc | ||
|
|
814d72cb3f | ||
|
|
b75f6e5853 | ||
|
|
dd6d12de23 | ||
|
|
f86a163692 | ||
|
|
e0fe7cb1eb | ||
|
|
47cbd4b620 | ||
|
|
0a6f1abd33 | ||
|
|
92b84168e1 | ||
|
|
fae625a795 | ||
|
|
c49b66f0e4 | ||
|
|
3cc7f7407d |
@@ -0,0 +1,44 @@
|
||||
# Configuration for Label Actions - https://github.com/dessant/label-actions
|
||||
|
||||
# Automatically close issues and pull requests when the `status: duplicate` label is applied
|
||||
'status: duplicate':
|
||||
issues:
|
||||
# Post a comment
|
||||
comment: >
|
||||
This issue is a duplicate. Please direct all discussion to the original issue.
|
||||
# Close the issue
|
||||
close: true
|
||||
# Remove other status labels
|
||||
unlabel:
|
||||
- 'status: pending triage'
|
||||
# Set a close reason
|
||||
close-reason: 'not planned'
|
||||
prs:
|
||||
# Post a comment
|
||||
comment: >
|
||||
This pull request is a duplicate. Please direct all discussion to the original pull request.
|
||||
# Remove other status labels
|
||||
unlabel:
|
||||
- 'status: pending triage'
|
||||
# Close the pull request
|
||||
close: true
|
||||
# Set a close reason
|
||||
close-reason: 'not planned'
|
||||
|
||||
'status: rejected':
|
||||
issues:
|
||||
# Close the issue
|
||||
close: true
|
||||
# Remove other status labels
|
||||
unlabel:
|
||||
- 'status: pending triage'
|
||||
# Set a close reason
|
||||
close-reason: 'not planned'
|
||||
prs:
|
||||
# Close the pull request
|
||||
close: true
|
||||
# Remove other status labels
|
||||
unlabel:
|
||||
- 'status: pending triage'
|
||||
# Set a close reason
|
||||
close-reason: 'not planned'
|
||||
@@ -2,6 +2,15 @@ name: Build and Upload nightly game builds
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
build-defines:
|
||||
type: string
|
||||
description: Build defines to use
|
||||
default: '-DGITHUB_BUILD'
|
||||
save-artifact:
|
||||
type: boolean
|
||||
description: Save the build artifact to Github Actions (sends to itch otherwise)
|
||||
default: false
|
||||
push:
|
||||
paths-ignore:
|
||||
- '**/Dockerfile'
|
||||
@@ -53,13 +62,20 @@ jobs:
|
||||
- name: Build game
|
||||
if: ${{ matrix.target == 'windows' }}
|
||||
run: |
|
||||
haxelib run lime build windows -v -release -DGITHUB_BUILD
|
||||
haxelib run lime build windows -v -release ${{ github.event.inputs.build-defines }}
|
||||
timeout-minutes: 120
|
||||
- name: Build game
|
||||
if: ${{ matrix.target != 'windows' }}
|
||||
run: |
|
||||
haxelib run lime build ${{ matrix.target }} -v -release --times -DGITHUB_BUILD
|
||||
haxelib run lime build ${{ matrix.target }} -v -release --times ${{ github.event.inputs.build-defines }}
|
||||
timeout-minutes: 120
|
||||
- name: Save build artifact to Github Actions
|
||||
if: ${{ github.event.inputs.save-artifact }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-${{ matrix.target }}
|
||||
path: export/release/${{matrix.target}}/bin/
|
||||
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: ./.github/actions/upload-itch
|
||||
@@ -125,9 +141,15 @@ jobs:
|
||||
|
||||
- name: Build game
|
||||
run: |
|
||||
haxelib run lime build ${{ matrix.target }} -v -release --times -DGITHUB_BUILD
|
||||
haxelib run lime build ${{ matrix.target }} -v -release --times ${{ github.event.inputs.build-defines }}
|
||||
timeout-minutes: 120
|
||||
|
||||
- name: Save build artifact to Github Actions
|
||||
if: ${{ github.event.inputs.save-artifact }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-${{ matrix.target }}
|
||||
path: export/release/${{matrix.target}}/bin/
|
||||
- name: Upload build artifacts
|
||||
uses: ./.github/actions/upload-itch
|
||||
with:
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# Perform actions when labels are applied to issues, discussions, or pull requests
|
||||
# See .github/label-actions.yml
|
||||
name: 'Label Actions'
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- labeled
|
||||
- unlabeled
|
||||
pull_request_target:
|
||||
types:
|
||||
- labeled
|
||||
- unlabeled
|
||||
discussion:
|
||||
types:
|
||||
- labeled
|
||||
- unlabeled
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
discussions: write
|
||||
|
||||
jobs:
|
||||
action:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/label-actions@v4
|
||||
@@ -0,0 +1,52 @@
|
||||
name: "Issue Labeler"
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- edited
|
||||
|
||||
jobs:
|
||||
# When an issue is opened, perform a similarity check for potential duplicates.
|
||||
# If some are found, add a label and comment listing the potential duplicate issues.
|
||||
potential-duplicate:
|
||||
name: Detect potential duplicate issues
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: wow-actions/potential-duplicates@v1
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Issue title filter work with anymatch https://www.npmjs.com/package/anymatch.
|
||||
# Any matched issue will stop detection immediately.
|
||||
# You can specify multi filters in each line.
|
||||
filter: ''
|
||||
# Exclude keywords in title before detecting.
|
||||
exclude: ''
|
||||
# Label to set, when potential duplicates are detected.
|
||||
label: 'potential duplicate'
|
||||
# Get issues with state to compare. Supported state: 'all', 'closed', 'open'.
|
||||
state: all
|
||||
# If similarity is higher than this threshold([0,1]), issue will be marked as duplicate.
|
||||
# Turn this up if the detection is too sensitive
|
||||
threshold: 0.6
|
||||
# Reactions to be add to comment when potential duplicates are detected.
|
||||
# Available reactions: "-1", "+1", "confused", "laugh", "heart", "hooray", "rocket", "eyes"
|
||||
# reactions: '-1'
|
||||
# Comment to post when potential duplicates are detected.
|
||||
comment: >
|
||||
Potential duplicates: {{#issues}}
|
||||
- [#{{ number }}] {{ title }} ({{ accuracy }}%)
|
||||
{{/issues}}
|
||||
# When an issue is opened, detect if it has an empty body or incomplete issue form.
|
||||
# If it does, close the issue immediately.
|
||||
empty-issues:
|
||||
name: Close empty issues
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Run empty issues closer action
|
||||
uses: rickstaa/empty-issues-closer-action@v1
|
||||
env:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
close_comment: Closing this issue because it appears to be empty. Please update the issue for it to be reopened.
|
||||
open_comment: Reopening this issue because the author provided more information.
|
||||
@@ -3,6 +3,7 @@ on:
|
||||
- pull_request_target
|
||||
|
||||
jobs:
|
||||
# Apply labels to pull requests based on which files were edited
|
||||
labeler:
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -13,6 +14,7 @@ jobs:
|
||||
uses: actions/labeler@v5
|
||||
with:
|
||||
sync-labels: true
|
||||
# Apply labels to pull requests based on how many lines were edited
|
||||
changed-lines-count-labeler:
|
||||
permissions:
|
||||
contents: read
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
[submodule "assets"]
|
||||
path = assets
|
||||
url = https://github.com/FunkinCrew/Funkin-Assets-secret
|
||||
url = https://github.com/FunkinCrew/Funkin-assets-secret
|
||||
[submodule "art"]
|
||||
path = art
|
||||
url = https://github.com/FunkinCrew/Funkin-Art-secret
|
||||
url = https://github.com/FunkinCrew/Funkin-art-secret
|
||||
|
||||
Vendored
+10
@@ -96,6 +96,11 @@
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (Tracy)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DFEATURE_DEBUG_TRACY", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "Linux / Debug",
|
||||
"target": "linux",
|
||||
@@ -106,6 +111,11 @@
|
||||
"target": "hl",
|
||||
"args": ["-debug"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (Discord)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS", "-DFEATURE_DISCORD_RPC"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (FlxAnimate Test)",
|
||||
"target": "windows",
|
||||
|
||||
+6
-4
@@ -9,19 +9,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Added a new Character Select screen to switch between playable characters in Freeplay
|
||||
- Modding isn't 100% there but we're working on it!
|
||||
- Added Pico as a playable character! Unlock him by completing Weekend 1 (if you haven't already done that)
|
||||
- The songs from Weekend 1 have moved; you must now switch to Pico in Freeplay to access them
|
||||
- Added 10 new Pico remixes! Access them by selecting Pico from in the Character Select screen
|
||||
- The songs from Weekend 1 have moved; you must now switch to Pico via Character Select screen in Freeplay to access them
|
||||
- Added 11 new Pico remixes! Access them by selecting Pico from in the Character Select screen
|
||||
- Bopeebo (Pico Mix)
|
||||
- Fresh (Pico Mix)
|
||||
- DadBattle (Pico Mix)
|
||||
- Spookeez (Pico Mix)
|
||||
- South (Pico Mix)
|
||||
- Pico (Pico Mix)
|
||||
- Philly Nice (Pico Mix)
|
||||
- Blammed (Pico Mix)
|
||||
- Blammed (Pico Mix)
|
||||
- Eggnog (Pico Mix)
|
||||
- Ugh (Pico Mix)
|
||||
- Guns (Pico Mix)
|
||||
- Added 1 new Boyfriend remix! Access it by selecting Pico from in the Character Select screen
|
||||
- Added 1 new Boyfriend remix! Access it by completing Weekend 1 as Pico and then selecting Boyfriend from in the Character Select screen
|
||||
- Darnell (BF Mix)
|
||||
- Added 2 new Erect remixes! Access them by switching difficulty on the song
|
||||
- Cocoa Erect
|
||||
@@ -39,6 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- These display on Pico remixes, as well as when playing Weekend 1.
|
||||
- Implemented support for scripted Note Kinds. You can use HScript define a different note style to display for these notes as well as custom behavior. (community feature by lemz1)
|
||||
- Implemented support for Numeric and Selector options in the Options menu. (community feature by FlooferLand)
|
||||
- Implemented new animations for Tankman and Pico
|
||||
## Changed
|
||||
- Girlfriend and Nene now perform previously unused animations when you achieve a large combo, or drop a large combo.
|
||||
- The pixel character icons in the Freeplay menu now display an animation!
|
||||
|
||||
@@ -19,7 +19,7 @@ Please check out our [Contributor's guide](./CONTRIBUTORS.md) on how you can act
|
||||
|
||||
# Credits and Special Thanks
|
||||
|
||||
Full credits can be found in-game, or wherever the credits.json file is.
|
||||
Full credits can be found in-game, or in the `credits.json` file which is located [here](https://github.com/FunkinCrew/funkin.assets/blob/main/exclude/data/credits.json).
|
||||
|
||||
## Programming
|
||||
- [ninjamuffin99](https://twitter.com/ninja_muffin99) - Lead Programmer
|
||||
|
||||
+1
-1
Submodule art updated: 0dee03f11a...fbd3e3df77
+1
-1
Submodule assets updated: bc7009b424...b2404b6b1c
+2
-5
@@ -41,9 +41,6 @@ There are several useful build flags you can add to a build to affect how it wor
|
||||
- `-DFEATURE_STAGE_EDITOR` to forcibly enable the experimental stage editor.
|
||||
- `-DFEATURE_GHOST_TAPPING` to forcibly enable an experimental gameplay change to the anti-mash system.
|
||||
|
||||
# Troubleshooting - GO THROUGH THESE STEPS BEFORE OPENING ISSUES ON GITHUB!
|
||||
# Troubleshooting
|
||||
|
||||
- During the cloning process, you may experience an error along the lines of `error: RPC failed; curl 92 HTTP/2 stream 0 was not closed cleanly: PROTOCOL_ERROR (err 1)` due to poor connectivity. A common fix is to run ` git config --global http.postBuffer 4096M`.
|
||||
- Make sure your game directory has an `assets` folder! If it's missing, copy the path to your `funkin` folder and run `cd the\path\you\copied`. Then follow the guide starting from **Step 4**.
|
||||
- Check that your `assets` folder is not empty! If it is, go back to **Step 4** and follow the guide from there.
|
||||
- The compilation process often fails due to having the wrong versions of the required libraries. Many errors can be resolved by deleting the `.haxelib` folder and following the guide starting from **Step 5**.
|
||||
If you experience any issues during the compilation process, DO NOT open an issue on GitHub. Instead, check the [Troubleshooting Guide](TROUBLESHOOTING.md) for steps on how to resolve common problems.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Troubleshooting Common Issues
|
||||
# Troubleshooting Common Compilation Issues
|
||||
|
||||
- Weird macro error with a very tall call stack: Restart Visual Studio Code
|
||||
- NOTE: This is caused by Polymod somewhere, and seems to only occur when there is another compile error somewhere in the program. There is a bounty up for it.
|
||||
@@ -13,3 +13,11 @@
|
||||
|
||||
- `LINK : fatal error LNK1201: error writing to program database ''; check for insufficient disk space, invalid path, or insufficient privilege`
|
||||
- This error occurs if the PDB file located in your `export` folder is in use or exceeds 4 GB. Try deleting the `export` folder and building again from scratch.
|
||||
|
||||
- `error: RPC failed; curl 92 HTTP/2 stream 0 was not closed cleanly: PROTOCOL_ERROR (err 1)`
|
||||
- This error can happen during cloning as a result of poor network connectivity. A common fix is to run ` git config --global http.postBuffer 4096M` in your terminal.
|
||||
|
||||
- Repository is missing an `assets` folder, or `assets` folder is empty.
|
||||
- You did not clone the repository correctly! Copy the path to your `funkin` folder and run `cd the\path\you\copied`. Then follow the compilation guide starting from **Step 4**.
|
||||
|
||||
- Other compilation issues may be caused by installing bad library versions. Try deleting the `.haxelib` folder and following the guide starting from **Step 5**.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"name": "EliteMasterEric"
|
||||
}
|
||||
],
|
||||
"api_version": "0.1.0",
|
||||
"api_version": "0.5.0",
|
||||
"mod_version": "1.0.0",
|
||||
"license": "Apache-2.0"
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"name": "EliteMasterEric"
|
||||
}
|
||||
],
|
||||
"api_version": "0.1.0",
|
||||
"api_version": "0.5.0",
|
||||
"mod_version": "1.0.0",
|
||||
"license": "Apache-2.0"
|
||||
}
|
||||
|
||||
@@ -7,13 +7,6 @@
|
||||
"ref": "a1eab7b9bf507b87200a3341719054fe427f3b15",
|
||||
"url": "https://github.com/FunkinCrew/FlxPartialSound.git"
|
||||
},
|
||||
{
|
||||
"name": "discord_rpc",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "2d83fa863ef0c1eace5f1cf67c3ac315d1a3a8a5",
|
||||
"url": "https://github.com/FunkinCrew/linc_discord-rpc"
|
||||
},
|
||||
{
|
||||
"name": "flixel",
|
||||
"type": "git",
|
||||
@@ -25,7 +18,7 @@
|
||||
"name": "flixel-addons",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "9c6fb47968e894eb36bf10e94725cd7640c49281",
|
||||
"ref": "7424db4e9164ff46f224a7c47de1b732d2542dd7",
|
||||
"url": "https://github.com/FunkinCrew/flixel-addons"
|
||||
},
|
||||
{
|
||||
@@ -35,13 +28,6 @@
|
||||
"ref": "951a0103a17bfa55eed86703ce50b4fb0d7590bc",
|
||||
"url": "https://github.com/FunkinCrew/flixel-text-input"
|
||||
},
|
||||
{
|
||||
"name": "flixel-ui",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "27f1ba626f80a6282fa8a187115e79a4a2133dc2",
|
||||
"url": "https://github.com/HaxeFlixel/flixel-ui"
|
||||
},
|
||||
{
|
||||
"name": "flxanimate",
|
||||
"type": "git",
|
||||
@@ -77,14 +63,14 @@
|
||||
"name": "haxeui-core",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "22f7c5a8ffca90d4677cffd6e570f53761709fbc",
|
||||
"ref": "c9d96b168ea2a19274ad7c766ab1a34b57baa793",
|
||||
"url": "https://github.com/haxeui/haxeui-core"
|
||||
},
|
||||
{
|
||||
"name": "haxeui-flixel",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "28bb710d0ae5d94b5108787593052165be43b980",
|
||||
"ref": "013b9d4e56bfe9a034e028a8d685f0b274cb73c4",
|
||||
"url": "https://github.com/haxeui/haxeui-flixel"
|
||||
},
|
||||
{
|
||||
@@ -105,8 +91,8 @@
|
||||
"name": "hxcpp",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "904ea40643b050a5a154c5e4c33a83fd2aec18b1",
|
||||
"url": "https://github.com/HaxeFoundation/hxcpp"
|
||||
"ref": "c6bac3d6c7d683f25104296b2f4c50f8c90b8349",
|
||||
"url": "https://github.com/cortex-engine/hxcpp"
|
||||
},
|
||||
{
|
||||
"name": "hxcpp-debug-server",
|
||||
@@ -115,6 +101,13 @@
|
||||
"ref": "147294123f983e35f50a966741474438069a7a8f",
|
||||
"url": "https://github.com/FunkinCrew/hxcpp-debugger"
|
||||
},
|
||||
{
|
||||
"name": "hxdiscord_rpc",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "82c47ecc1a454b7dd644e4fcac7e91155f176dec",
|
||||
"url": "https://github.com/FunkinCrew/hxdiscord_rpc"
|
||||
},
|
||||
{
|
||||
"name": "hxjsonast",
|
||||
"type": "git",
|
||||
@@ -212,4 +205,4 @@
|
||||
"url": "https://github.com/fponticelli/thx.semver"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+46
-5
@@ -25,7 +25,7 @@ class Project extends HXProject {
|
||||
* REMEMBER TO CHANGE THIS WHEN THE GAME UPDATES!
|
||||
* You only have to change it here, the rest of the game will query this value.
|
||||
*/
|
||||
static final VERSION:String = "0.5.0";
|
||||
static final VERSION:String = "0.5.1";
|
||||
|
||||
/**
|
||||
* The game's name. Used as the default window title.
|
||||
@@ -149,6 +149,21 @@ class Project extends HXProject {
|
||||
*/
|
||||
static final FEATURE_DEBUG_FUNCTIONS:FeatureFlag = "FEATURE_DEBUG_FUNCTIONS";
|
||||
|
||||
/**
|
||||
* `-DFEATURE_DEBUG_TRACY`
|
||||
* If this flag is enabled, the game will have the necessary hooks for the Tracy profiler.
|
||||
* Only enable this if you're using the correct fork of Haxe to support this.
|
||||
* @see https://github.com/HaxeFoundation/hxcpp/pull/1153
|
||||
*/
|
||||
static final FEATURE_DEBUG_TRACY:FeatureFlag = "FEATURE_DEBUG_TRACY";
|
||||
|
||||
/**
|
||||
* `-DFEATURE_LOG_TRACE`
|
||||
* If this flag is enabled, the game will print debug traces to the console.
|
||||
* Disable to improve performance a bunch.
|
||||
*/
|
||||
static final FEATURE_LOG_TRACE:FeatureFlag = "FEATURE_LOG_TRACE";
|
||||
|
||||
/**
|
||||
* `-DFEATURE_DISCORD_RPC`
|
||||
* If this flag is enabled, the game will enable the Discord Remote Procedure Call library.
|
||||
@@ -199,6 +214,12 @@ class Project extends HXProject {
|
||||
*/
|
||||
static final FEATURE_OPEN_URL:FeatureFlag = "FEATURE_OPEN_URL";
|
||||
|
||||
/**
|
||||
* `-DFEATURE_SCREENSHOTS`
|
||||
* If this flag is enabled, the game will support the screenshots feature.
|
||||
*/
|
||||
static final FEATURE_SCREENSHOTS:FeatureFlag = "FEATURE_SCREENSHOTS";
|
||||
|
||||
/**
|
||||
* `-DFEATURE_CHART_EDITOR`
|
||||
* If this flag is enabled, the Chart Editor will be accessible from the debug menu.
|
||||
@@ -453,14 +474,14 @@ class Project extends HXProject {
|
||||
|
||||
// Should be true on debug builds or if GITHUB_BUILD is enabled.
|
||||
FEATURE_DEBUG_FUNCTIONS.apply(this, isDebug() || GITHUB_BUILD.isEnabled(this));
|
||||
FEATURE_LOG_TRACE.apply(this, isDebug());
|
||||
|
||||
// Should default to true on workspace builds and false on release builds.
|
||||
REDIRECT_ASSETS_FOLDER.apply(this, isDebug() && isDesktop());
|
||||
|
||||
// Should be true on release, non-tester builds.
|
||||
// Should be true on desktop, release, non-tester builds.
|
||||
// We don't want testers to accidentally leak songs to their Discord friends!
|
||||
// TODO: Re-enable this.
|
||||
FEATURE_DISCORD_RPC.apply(this, false && !FEATURE_DEBUG_FUNCTIONS.isEnabled(this));
|
||||
FEATURE_DISCORD_RPC.apply(this, isDesktop() && !FEATURE_DEBUG_FUNCTIONS.isEnabled(this));
|
||||
|
||||
// Should be true only on web builds.
|
||||
// Audio context issues only exist there.
|
||||
@@ -478,6 +499,10 @@ class Project extends HXProject {
|
||||
// Should be true except on web builds.
|
||||
// Chart editor doesn't work there.
|
||||
FEATURE_CHART_EDITOR.apply(this, !isWeb());
|
||||
|
||||
// Should be true except on web builds.
|
||||
// Screenshots doesn't work there.
|
||||
FEATURE_SCREENSHOTS.apply(this, !isWeb());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -516,6 +541,16 @@ class Project extends HXProject {
|
||||
|
||||
// Cleaner looking compiler errors.
|
||||
setHaxedef("message.reporting", "pretty");
|
||||
|
||||
if (FEATURE_DEBUG_TRACY.isEnabled(this)) {
|
||||
setHaxedef("HXCPP_TELEMETRY"); // Enable telemetry
|
||||
setHaxedef("HXCPP_TRACY"); // Enable Tracy telemetry
|
||||
setHaxedef("HXCPP_TRACY_MEMORY"); // Track memory allocations
|
||||
setHaxedef("HXCPP_TRACY_ON_DEMAND"); // Only collect telemetry when Tracy is open and reachable
|
||||
// setHaxedef("HXCPP_TRACY_INCLUDE_CALLSTACKS"); // Inspect callstacks per zone, inflating telemetry data
|
||||
|
||||
setHaxedef("absolute-paths"); // Fix source locations so Tracy can see them
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -618,7 +653,7 @@ class Project extends HXProject {
|
||||
}
|
||||
|
||||
if (FEATURE_DISCORD_RPC.isEnabled(this)) {
|
||||
addHaxelib('discord_rpc'); // Discord API
|
||||
addHaxelib('hxdiscord_rpc'); // Discord API
|
||||
}
|
||||
|
||||
if (FEATURE_NEWGROUNDS.isEnabled(this)) {
|
||||
@@ -941,6 +976,8 @@ class Project extends HXProject {
|
||||
var commitHash:String = process.stdout.readLine();
|
||||
var commitHashSplice:String = commitHash.substr(0, 7);
|
||||
|
||||
process.close();
|
||||
|
||||
return commitHashSplice;
|
||||
}
|
||||
|
||||
@@ -955,6 +992,8 @@ class Project extends HXProject {
|
||||
|
||||
var branchName:String = branchProcess.stdout.readLine();
|
||||
|
||||
branchProcess.close();
|
||||
|
||||
return branchName;
|
||||
}
|
||||
|
||||
@@ -979,6 +1018,8 @@ class Project extends HXProject {
|
||||
}
|
||||
}
|
||||
|
||||
branchProcess.close();
|
||||
|
||||
return output.length > 0;
|
||||
}
|
||||
|
||||
|
||||
+2
-9
@@ -2,6 +2,7 @@ package;
|
||||
|
||||
import flixel.FlxGame;
|
||||
import flixel.FlxState;
|
||||
import funkin.Preferences;
|
||||
import funkin.util.logging.CrashHandler;
|
||||
import funkin.ui.debug.MemoryCounter;
|
||||
import funkin.save.Save;
|
||||
@@ -22,12 +23,6 @@ class Main extends Sprite
|
||||
var gameHeight:Int = 720; // Height of the game in pixels (might be less / more in actual pixels depending on your zoom).
|
||||
var initialState:Class<FlxState> = funkin.InitState; // The FlxState the game starts with.
|
||||
var zoom:Float = -1; // If -1, zoom is automatically calculated to fit the window dimensions.
|
||||
#if (web || CHEEMS)
|
||||
var framerate:Int = 60; // How many frames per second the game should run at.
|
||||
#else
|
||||
// TODO: This should probably be in the options menu?
|
||||
var framerate:Int = 144; // How many frames per second the game should run at.
|
||||
#end
|
||||
var skipSplash:Bool = true; // Whether to skip the flixel splash screen that appears in release mode.
|
||||
var startFullscreen:Bool = false; // Whether to start the game in fullscreen on desktop targets
|
||||
|
||||
@@ -109,7 +104,7 @@ class Main extends Sprite
|
||||
|
||||
// George recommends binding the save before FlxGame is created.
|
||||
Save.load();
|
||||
var game:FlxGame = new FlxGame(gameWidth, gameHeight, initialState, framerate, framerate, skipSplash, startFullscreen);
|
||||
var game:FlxGame = new FlxGame(gameWidth, gameHeight, initialState, Preferences.framerate, Preferences.framerate, skipSplash, startFullscreen);
|
||||
|
||||
// FlxG.game._customSoundTray wants just the class, it calls new from
|
||||
// create() in there, which gets called when it's added to stage
|
||||
@@ -123,8 +118,6 @@ class Main extends Sprite
|
||||
game.debugger.interaction.addTool(new funkin.util.TrackerToolButtonUtil());
|
||||
#end
|
||||
|
||||
addChild(fpsCounter);
|
||||
|
||||
#if hxcpp_debug_server
|
||||
trace('hxcpp_debug_server is enabled! You can now connect to the game with a debugger.');
|
||||
#else
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Pulled from Tracey profiler PR
|
||||
* @see https://github.com/HaxeFoundation/haxe/pull/11772
|
||||
*/
|
||||
|
||||
package cpp.vm.tracy;
|
||||
|
||||
#if (!HXCPP_TRACY)
|
||||
#error "This class cannot be used without -D HXCPP_TRACY"
|
||||
#end
|
||||
enum abstract PlotFormatType(cpp.UInt8) from cpp.UInt8 to cpp.UInt8
|
||||
{
|
||||
var Number = 0;
|
||||
var Memory = 1;
|
||||
var Percentage = 2;
|
||||
}
|
||||
|
||||
@:include('hx/TelemetryTracy.h')
|
||||
extern class Native_TracyProfiler
|
||||
{
|
||||
/**
|
||||
Mark a frame. Call this at the end of each frame loop.
|
||||
**/
|
||||
@:native('::__hxcpp_tracy_framemark')
|
||||
public static function frameMark():Void;
|
||||
|
||||
/**
|
||||
Print a message into Tracy's log.
|
||||
**/
|
||||
@:native('::__hxcpp_tracy_message')
|
||||
public static function message(_msg:String, ?_color:Int = 0x000000):Void;
|
||||
|
||||
/**
|
||||
Tracy can collect additional information about the profiled application,
|
||||
which will be available in the trace description.
|
||||
This can include data such as the source repository revision,
|
||||
the application’s environment (dev/prod), etc.
|
||||
**/
|
||||
@:native('::__hxcpp_tracy_message_app_info')
|
||||
public static function messageAppInfo(_info:String):Void;
|
||||
|
||||
/**
|
||||
Plot a named value to tracy. This will generate a graph in the profiler for you.
|
||||
**/
|
||||
@:native('::__hxcpp_tracy_plot')
|
||||
public static function plot(_name:String, _val:cpp.Float32):Void;
|
||||
|
||||
/**
|
||||
Configure how values are plotted and displayed.
|
||||
**/
|
||||
@:native('::__hxcpp_tracy_plot_config')
|
||||
public static function plotConfig(_name:String, _format:PlotFormatType, ?_step:Bool = false, ?_fill:Bool = false, ?_color:Int = 0x000000):Void;
|
||||
|
||||
/**
|
||||
Set a name for the current thread this function is called in. Supply an optional groupHint so threads become grouped in Tracy's UI.
|
||||
**/
|
||||
@:native('::__hxcpp_tracy_set_thread_name_and_group')
|
||||
public static function setThreadName(_name:String, ?_groupHint:Int = 1):Void;
|
||||
|
||||
/**
|
||||
Create a custom named scoped zone in your code.
|
||||
**/
|
||||
@:native('HXCPP_TRACY_ZONE')
|
||||
public static function zoneScoped(_name:String):Void;
|
||||
}
|
||||
|
||||
#if (scriptable || cppia)
|
||||
class Cppia_TracyProfiler
|
||||
{
|
||||
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.frameMark)
|
||||
public static function frameMark()
|
||||
Native_TracyProfiler.frameMark();
|
||||
|
||||
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.message)
|
||||
public static function message(_msg:String, ?_color:Int = 0x000000)
|
||||
Native_TracyProfiler.message(_msg, _color);
|
||||
|
||||
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.messageAppInfo)
|
||||
public static function messageAppInfo(_info:String)
|
||||
Native_TracyProfiler.messageAppInfo(_info);
|
||||
|
||||
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.plot)
|
||||
public static function plot(_name:String, _val:Float)
|
||||
Native_TracyProfiler.plot(_name, _val);
|
||||
|
||||
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.plotConfig)
|
||||
public static function plotConfig(_name:String, _format:PlotFormatType, ?_step:Bool = false, ?_fill:Bool = false, ?_color:Int = 0x000000)
|
||||
Native_TracyProfiler.plotConfig(_name, _format, _step, _fill, _color);
|
||||
|
||||
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.setThreadName)
|
||||
public static function setThreadName(_name:String, ?_groupHint:Int = 1)
|
||||
Native_TracyProfiler.setThreadName(_name, _groupHint);
|
||||
|
||||
@:inheritDoc(cpp.vm.tracy.Native_TracyProfiler.zoneScoped)
|
||||
public static function zoneScoped(_name:String)
|
||||
Native_TracyProfiler.zoneScoped(_name);
|
||||
}
|
||||
|
||||
typedef TracyProfiler = Cppia_TracyProfiler;
|
||||
#else
|
||||
typedef TracyProfiler = Native_TracyProfiler;
|
||||
#end
|
||||
+136
-13
@@ -1,38 +1,161 @@
|
||||
package funkin;
|
||||
|
||||
import openfl.utils.Future;
|
||||
|
||||
/**
|
||||
* A wrapper around `openfl.utils.Assets` which disallows access to the harmful functions.
|
||||
* Later we'll add Funkin-specific caching to this.
|
||||
*/
|
||||
class Assets
|
||||
{
|
||||
public static function getText(path:String):String
|
||||
/**
|
||||
* Get the file system path for an asset
|
||||
* @param path The asset path to load from, relative to the assets folder
|
||||
* @return The path to the asset on the file system
|
||||
*/
|
||||
public static function getPath(path:String):String
|
||||
{
|
||||
return openfl.utils.Assets.getText(path);
|
||||
}
|
||||
|
||||
public static function getMusic(path:String):openfl.media.Sound
|
||||
{
|
||||
return openfl.utils.Assets.getMusic(path);
|
||||
}
|
||||
|
||||
public static function getBitmapData(path:String):openfl.display.BitmapData
|
||||
{
|
||||
return openfl.utils.Assets.getBitmapData(path);
|
||||
return openfl.utils.Assets.getPath(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load bytes from an asset
|
||||
* May cause stutters or throw an error if the asset is not cached
|
||||
* @param path The asset path to load from
|
||||
* @return The byte contents of the file
|
||||
*/
|
||||
public static function getBytes(path:String):haxe.io.Bytes
|
||||
{
|
||||
return openfl.utils.Assets.getBytes(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load bytes from an asset asynchronously
|
||||
* @param path The asset path to load from
|
||||
* @return A future which promises to return the byte contents of the file
|
||||
*/
|
||||
public static function loadBytes(path:String):Future<openfl.utils.ByteArray>
|
||||
{
|
||||
return openfl.utils.Assets.loadBytes(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load text from an asset.
|
||||
* May cause stutters or throw an error if the asset is not cached
|
||||
* @param path The asset path to load from
|
||||
* @return The text contents of the file
|
||||
*/
|
||||
public static function getText(path:String):String
|
||||
{
|
||||
return openfl.utils.Assets.getText(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load text from an asset asynchronously
|
||||
* @param path The asset path to load from
|
||||
* @return A future which promises to return the text contents of the file
|
||||
*/
|
||||
public static function loadText(path:String):Future<String>
|
||||
{
|
||||
return openfl.utils.Assets.loadText(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a Sound file from an asset
|
||||
* May cause stutters or throw an error if the asset is not cached
|
||||
* @param path The asset path to load from
|
||||
* @return The loaded sound
|
||||
*/
|
||||
public static function getSound(path:String):openfl.media.Sound
|
||||
{
|
||||
return openfl.utils.Assets.getSound(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a Sound file from an asset asynchronously
|
||||
* @param path The asset path to load from
|
||||
* @return A future which promises to return the loaded sound
|
||||
*/
|
||||
public static function loadSound(path:String):Future<openfl.media.Sound>
|
||||
{
|
||||
return openfl.utils.Assets.loadSound(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a Sound file from an asset, with optimizations specific to long-duration music
|
||||
* May cause stutters or throw an error if the asset is not cached
|
||||
* @param path The asset path to load from
|
||||
* @return The loaded sound
|
||||
*/
|
||||
public static function getMusic(path:String):openfl.media.Sound
|
||||
{
|
||||
return openfl.utils.Assets.getMusic(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a Sound file from an asset asynchronously, with optimizations specific to long-duration music
|
||||
* @param path The asset path to load from
|
||||
* @return A future which promises to return the loaded sound
|
||||
*/
|
||||
public static function loadMusic(path:String):Future<openfl.media.Sound>
|
||||
{
|
||||
return openfl.utils.Assets.loadMusic(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a Bitmap from an asset
|
||||
* May cause stutters or throw an error if the asset is not cached
|
||||
* @param path The asset path to load from
|
||||
* @return The loaded Bitmap image
|
||||
*/
|
||||
public static function getBitmapData(path:String):openfl.display.BitmapData
|
||||
{
|
||||
return openfl.utils.Assets.getBitmapData(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a Bitmap from an asset asynchronously
|
||||
* @param path The asset path to load from
|
||||
* @return The future which promises to return the loaded Bitmap image
|
||||
*/
|
||||
public static function loadBitmapData(path:String):Future<openfl.display.BitmapData>
|
||||
{
|
||||
return openfl.utils.Assets.loadBitmapData(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the given asset of the given type exists.
|
||||
* @param path The asset path to check
|
||||
* @param type The asset type to check
|
||||
* @return Whether the asset exists
|
||||
*/
|
||||
public static function exists(path:String, ?type:openfl.utils.AssetType):Bool
|
||||
{
|
||||
return openfl.utils.Assets.exists(path, type);
|
||||
}
|
||||
|
||||
public static function list(type:openfl.utils.AssetType):Array<String>
|
||||
/**
|
||||
* Retrieve a list of all assets of the given type
|
||||
* @param type The asset type to check
|
||||
* @return A list of asset paths
|
||||
*/
|
||||
public static function list(?type:openfl.utils.AssetType):Array<String>
|
||||
{
|
||||
return openfl.utils.Assets.list(type);
|
||||
}
|
||||
|
||||
public static function hasLibrary(name:String):Bool
|
||||
{
|
||||
return openfl.utils.Assets.hasLibrary(name);
|
||||
}
|
||||
|
||||
public static function getLibrary(name:String):lime.utils.AssetLibrary
|
||||
{
|
||||
return openfl.utils.Assets.getLibrary(name);
|
||||
}
|
||||
|
||||
public static function loadLibrary(name:String):Future<openfl.utils.AssetLibrary>
|
||||
{
|
||||
return openfl.utils.Assets.loadLibrary(name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,6 +275,13 @@ class Conductor
|
||||
return Save.instance.options.audioVisualOffset;
|
||||
}
|
||||
|
||||
public var combinedOffset(get, never):Float;
|
||||
|
||||
function get_combinedOffset():Float
|
||||
{
|
||||
return instrumentalOffset + audioVisualOffset + inputOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of beats in a measure. May be fractional depending on the time signature.
|
||||
*/
|
||||
@@ -397,9 +404,12 @@ class Conductor
|
||||
*/
|
||||
public function update(?songPos:Float, applyOffsets:Bool = true, forceDispatch:Bool = false)
|
||||
{
|
||||
var currentTime:Float = (FlxG.sound.music != null) ? FlxG.sound.music.time : 0.0;
|
||||
var currentLength:Float = (FlxG.sound.music != null) ? FlxG.sound.music.length : 0.0;
|
||||
|
||||
if (songPos == null)
|
||||
{
|
||||
songPos = (FlxG.sound.music != null) ? FlxG.sound.music.time : 0.0;
|
||||
songPos = currentTime;
|
||||
}
|
||||
|
||||
// Take into account instrumental and file format song offsets.
|
||||
@@ -409,8 +419,17 @@ class Conductor
|
||||
var oldBeat:Float = this.currentBeat;
|
||||
var oldStep:Float = this.currentStep;
|
||||
|
||||
// If the song is playing, limit the song position to the length of the song or beginning of the song.
|
||||
if (FlxG.sound.music != null && FlxG.sound.music.playing)
|
||||
{
|
||||
this.songPosition = Math.min(currentLength, Math.max(0, songPos));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.songPosition = songPos;
|
||||
}
|
||||
|
||||
// Set the song position we are at (for purposes of calculating note positions, etc).
|
||||
this.songPosition = songPos;
|
||||
|
||||
currentTimeChange = timeChanges[0];
|
||||
if (this.songPosition > 0.0)
|
||||
@@ -430,7 +449,8 @@ class Conductor
|
||||
else if (currentTimeChange != null && this.songPosition > 0.0)
|
||||
{
|
||||
// roundDecimal prevents representing 8 as 7.9999999
|
||||
this.currentStepTime = FlxMath.roundDecimal((currentTimeChange.beatTime * Constants.STEPS_PER_BEAT) + (this.songPosition - currentTimeChange.timeStamp) / stepLengthMs, 6);
|
||||
this.currentStepTime = FlxMath.roundDecimal((currentTimeChange.beatTime * Constants.STEPS_PER_BEAT)
|
||||
+ (this.songPosition - currentTimeChange.timeStamp) / stepLengthMs, 6);
|
||||
this.currentBeatTime = currentStepTime / Constants.STEPS_PER_BEAT;
|
||||
this.currentMeasureTime = currentStepTime / stepsPerMeasure;
|
||||
this.currentStep = Math.floor(currentStepTime);
|
||||
|
||||
+22
-20
@@ -1,42 +1,42 @@
|
||||
package funkin;
|
||||
|
||||
import funkin.data.freeplay.player.PlayerRegistry;
|
||||
import funkin.ui.debug.charting.ChartEditorState;
|
||||
import funkin.ui.transition.LoadingState;
|
||||
import flixel.FlxState;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
import flixel.addons.transition.FlxTransitionSprite.GraphicTransTileDiamond;
|
||||
import flixel.addons.transition.TransitionData;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.FlxState;
|
||||
import flixel.graphics.FlxGraphic;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.math.FlxRect;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.system.debug.log.LogStyle;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.util.macro.MacroUtil;
|
||||
import funkin.util.WindowUtil;
|
||||
import funkin.play.PlayStatePlaylist;
|
||||
import openfl.display.BitmapData;
|
||||
import funkin.data.story.level.LevelRegistry;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.data.freeplay.style.FreeplayStyleRegistry;
|
||||
import funkin.data.event.SongEventRegistry;
|
||||
import funkin.data.stage.StageRegistry;
|
||||
import funkin.data.dialogue.conversation.ConversationRegistry;
|
||||
import funkin.data.dialogue.dialoguebox.DialogueBoxRegistry;
|
||||
import funkin.data.dialogue.speaker.SpeakerRegistry;
|
||||
import funkin.data.freeplay.album.AlbumRegistry;
|
||||
import funkin.data.freeplay.player.PlayerRegistry;
|
||||
import funkin.data.freeplay.style.FreeplayStyleRegistry;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.data.event.SongEventRegistry;
|
||||
import funkin.data.stage.StageRegistry;
|
||||
import funkin.data.story.level.LevelRegistry;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import funkin.play.notes.notekind.NoteKindManager;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.play.PlayStatePlaylist;
|
||||
import funkin.ui.debug.charting.ChartEditorState;
|
||||
import funkin.ui.title.TitleState;
|
||||
import funkin.ui.transition.LoadingState;
|
||||
import funkin.util.CLIUtil;
|
||||
import funkin.util.CLIUtil.CLIParams;
|
||||
import funkin.util.macro.MacroUtil;
|
||||
import funkin.util.TimerUtil;
|
||||
import funkin.util.TrackerUtil;
|
||||
import funkin.util.WindowUtil;
|
||||
import openfl.display.BitmapData;
|
||||
#if FEATURE_DISCORD_RPC
|
||||
import Discord.DiscordClient;
|
||||
import funkin.api.discord.DiscordClient;
|
||||
#end
|
||||
|
||||
/**
|
||||
@@ -125,10 +125,10 @@ class InitState extends FlxState
|
||||
// DISCORD API SETUP
|
||||
//
|
||||
#if FEATURE_DISCORD_RPC
|
||||
DiscordClient.initialize();
|
||||
DiscordClient.instance.init();
|
||||
|
||||
Application.current.onExit.add(function(exitCode) {
|
||||
DiscordClient.shutdown();
|
||||
lime.app.Application.current.onExit.add(function(exitCode) {
|
||||
DiscordClient.instance.shutdown();
|
||||
});
|
||||
#end
|
||||
|
||||
@@ -148,10 +148,12 @@ class InitState extends FlxState
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
funkin.util.plugins.MemoryGCPlugin.initialize();
|
||||
#end
|
||||
#if FEATURE_SCREENSHOTS
|
||||
funkin.util.plugins.ScreenshotPlugin.initialize();
|
||||
#end
|
||||
funkin.util.plugins.EvacuateDebugPlugin.initialize();
|
||||
funkin.util.plugins.ForceCrashPlugin.initialize();
|
||||
funkin.util.plugins.ReloadAssetsDebugPlugin.initialize();
|
||||
funkin.util.plugins.ScreenshotPlugin.initialize();
|
||||
funkin.util.plugins.VolumePlugin.initialize();
|
||||
funkin.util.plugins.WatchPlugin.initialize();
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package funkin;
|
||||
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import openfl.utils.AssetType;
|
||||
import openfl.utils.Assets as OpenFlAssets;
|
||||
|
||||
/**
|
||||
* A core class which handles determining asset paths.
|
||||
@@ -44,11 +43,11 @@ class Paths
|
||||
if (currentLevel != null)
|
||||
{
|
||||
var levelPath:String = getLibraryPathForce(file, currentLevel);
|
||||
if (OpenFlAssets.exists(levelPath, type)) return levelPath;
|
||||
if (Assets.exists(levelPath, type)) return levelPath;
|
||||
}
|
||||
|
||||
var levelPath:String = getLibraryPathForce(file, 'shared');
|
||||
if (OpenFlAssets.exists(levelPath, type)) return levelPath;
|
||||
if (Assets.exists(levelPath, type)) return levelPath;
|
||||
|
||||
return getPreloadPath(file);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,35 @@ import funkin.save.Save;
|
||||
*/
|
||||
class Preferences
|
||||
{
|
||||
/**
|
||||
* FPS
|
||||
* @default `60`
|
||||
*/
|
||||
public static var framerate(get, set):Int;
|
||||
|
||||
static function get_framerate():Int
|
||||
{
|
||||
#if web
|
||||
return 60;
|
||||
#else
|
||||
return Save?.instance?.options?.framerate ?? 60;
|
||||
#end
|
||||
}
|
||||
|
||||
static function set_framerate(value:Int):Int
|
||||
{
|
||||
#if web
|
||||
return 60;
|
||||
#else
|
||||
var save:Save = Save.instance;
|
||||
save.options.framerate = value;
|
||||
save.flush();
|
||||
FlxG.updateFramerate = value;
|
||||
FlxG.drawFramerate = value;
|
||||
return value;
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether some particularly fowl language is displayed.
|
||||
* @default `true`
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
package funkin.api.discord;
|
||||
|
||||
import Sys.sleep;
|
||||
#if FEATURE_DISCORD_RPC
|
||||
import discord_rpc.DiscordRpc;
|
||||
#end
|
||||
|
||||
class DiscordClient
|
||||
{
|
||||
#if FEATURE_DISCORD_RPC
|
||||
public function new()
|
||||
{
|
||||
trace("Discord Client starting...");
|
||||
DiscordRpc.start(
|
||||
{
|
||||
clientID: "814588678700924999",
|
||||
onReady: onReady,
|
||||
onError: onError,
|
||||
onDisconnected: onDisconnected
|
||||
});
|
||||
trace("Discord Client started.");
|
||||
|
||||
while (true)
|
||||
{
|
||||
DiscordRpc.process();
|
||||
sleep(2);
|
||||
// trace("Discord Client Update");
|
||||
}
|
||||
|
||||
DiscordRpc.shutdown();
|
||||
}
|
||||
|
||||
public static function shutdown()
|
||||
{
|
||||
DiscordRpc.shutdown();
|
||||
}
|
||||
|
||||
static function onReady()
|
||||
{
|
||||
DiscordRpc.presence(
|
||||
{
|
||||
details: "In the Menus",
|
||||
state: null,
|
||||
largeImageKey: 'icon',
|
||||
largeImageText: "Friday Night Funkin'"
|
||||
});
|
||||
}
|
||||
|
||||
static function onError(_code:Int, _message:String)
|
||||
{
|
||||
trace('Error! $_code : $_message');
|
||||
}
|
||||
|
||||
static function onDisconnected(_code:Int, _message:String)
|
||||
{
|
||||
trace('Disconnected! $_code : $_message');
|
||||
}
|
||||
|
||||
public static function initialize()
|
||||
{
|
||||
var DiscordDaemon = sys.thread.Thread.create(() -> {
|
||||
new DiscordClient();
|
||||
});
|
||||
trace("Discord Client initialized");
|
||||
}
|
||||
|
||||
public static function changePresence(details:String, ?state:String, ?smallImageKey:String, ?hasStartTimestamp:Bool, ?endTimestamp:Float)
|
||||
{
|
||||
var startTimestamp:Float = if (hasStartTimestamp) Date.now().getTime() else 0;
|
||||
|
||||
if (endTimestamp > 0)
|
||||
{
|
||||
endTimestamp = startTimestamp + endTimestamp;
|
||||
}
|
||||
|
||||
DiscordRpc.presence(
|
||||
{
|
||||
details: details,
|
||||
state: state,
|
||||
largeImageKey: 'icon',
|
||||
largeImageText: "Friday Night Funkin'",
|
||||
smallImageKey: smallImageKey,
|
||||
// Obtained times are in milliseconds so they are divided so Discord can use it
|
||||
startTimestamp: Std.int(startTimestamp / 1000),
|
||||
endTimestamp: Std.int(endTimestamp / 1000)
|
||||
});
|
||||
|
||||
// trace('Discord RPC Updated. Arguments: $details, $state, $smallImageKey, $hasStartTimestamp, $endTimestamp');
|
||||
}
|
||||
#end
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
package funkin.api.discord;
|
||||
|
||||
#if FEATURE_DISCORD_RPC
|
||||
import hxdiscord_rpc.Discord;
|
||||
import hxdiscord_rpc.Types;
|
||||
import sys.thread.Thread;
|
||||
|
||||
class DiscordClient
|
||||
{
|
||||
static final CLIENT_ID:String = "816168432860790794";
|
||||
|
||||
public static var instance(get, never):DiscordClient;
|
||||
static var _instance:Null<DiscordClient> = null;
|
||||
|
||||
static function get_instance():DiscordClient
|
||||
{
|
||||
if (DiscordClient._instance == null) _instance = new DiscordClient();
|
||||
if (DiscordClient._instance == null) throw "Could not initialize singleton DiscordClient!";
|
||||
return DiscordClient._instance;
|
||||
}
|
||||
|
||||
var handlers:DiscordEventHandlers;
|
||||
|
||||
private function new()
|
||||
{
|
||||
trace('[DISCORD] Initializing event handlers...');
|
||||
|
||||
handlers = DiscordEventHandlers.create();
|
||||
|
||||
handlers.ready = cpp.Function.fromStaticFunction(onReady);
|
||||
handlers.disconnected = cpp.Function.fromStaticFunction(onDisconnected);
|
||||
handlers.errored = cpp.Function.fromStaticFunction(onError);
|
||||
}
|
||||
|
||||
public function init():Void
|
||||
{
|
||||
trace('[DISCORD] Initializing connection...');
|
||||
|
||||
// Discord.initialize(CLIENT_ID, handlers, true, null);
|
||||
Discord.Initialize(CLIENT_ID, cpp.RawPointer.addressOf(handlers), 1, null);
|
||||
|
||||
createDaemon();
|
||||
}
|
||||
|
||||
var daemon:Thread = null;
|
||||
|
||||
function createDaemon():Void
|
||||
{
|
||||
daemon = Thread.create(doDaemonWork);
|
||||
}
|
||||
|
||||
function doDaemonWork():Void
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
trace('[DISCORD] Performing client update...');
|
||||
|
||||
#if DISCORD_DISABLE_IO_THREAD
|
||||
Discord.updateConnection();
|
||||
#end
|
||||
|
||||
Discord.runCallbacks();
|
||||
Sys.sleep(2);
|
||||
}
|
||||
}
|
||||
|
||||
public function shutdown():Void
|
||||
{
|
||||
trace('[DISCORD] Shutting down...');
|
||||
|
||||
Discord.shutdown();
|
||||
}
|
||||
|
||||
public function setPresence(params:DiscordClientPresenceParams):Void
|
||||
{
|
||||
trace('[DISCORD] Updating presence... (${params})');
|
||||
|
||||
Discord.updatePresence(buildPresence(params));
|
||||
}
|
||||
|
||||
function buildPresence(params:DiscordClientPresenceParams):DiscordRichPresence
|
||||
{
|
||||
var presence = DiscordRichPresence.create();
|
||||
|
||||
// Presence should always be playing the game.
|
||||
presence.type = DiscordActivityType_Playing;
|
||||
|
||||
// Text when hovering over the large image. We just leave this as the game name.
|
||||
presence.largeImageText = "Friday Night Funkin'";
|
||||
|
||||
// State should be generally what the person is doing, like "In the Menus" or "Pico (Pico Mix) [Freeplay Hard]"
|
||||
presence.state = cast(params.state, Null<String>);
|
||||
// Details should be what the person is specifically doing, including stuff like timestamps (maybe something like "03:24 elapsed").
|
||||
presence.details = cast(params.details, Null<String>);
|
||||
|
||||
// The large image displaying what the user is doing.
|
||||
// This should probably be album art.
|
||||
// IMPORTANT NOTE: This can be an asset key uploaded to Discord's developer panel OR any URL you like.
|
||||
presence.largeImageKey = cast(params.largeImageKey, Null<String>) ?? "album-volume1";
|
||||
|
||||
trace('[DISCORD] largeImageKey: ${presence.largeImageKey}');
|
||||
|
||||
// TODO: Make this use the song's album art.
|
||||
// presence.largeImageKey = "icon";
|
||||
// presence.largeImageKey = "https://f4.bcbits.com/img/a0746694746_16.jpg";
|
||||
|
||||
// The small inset image for what the user is doing.
|
||||
// This can be the opponent's health icon?
|
||||
// NOTE: Like largeImageKey, this can be a URL, or an asset key.
|
||||
presence.smallImageKey = cast(params.smallImageKey, Null<String>);
|
||||
|
||||
// NOTE: In previous versions, this showed as "Elapsed", but now shows as playtime and doesn't look good
|
||||
// presence.startTimestamp = time - 10;
|
||||
// presence.endTimestamp = time + 30;
|
||||
|
||||
final button1:DiscordButton = DiscordButton.create();
|
||||
button1.label = "Play on Web";
|
||||
button1.url = Constants.URL_NEWGROUNDS;
|
||||
presence.buttons[0] = button1;
|
||||
|
||||
final button2:DiscordButton = DiscordButton.create();
|
||||
button2.label = "Download";
|
||||
button2.url = Constants.URL_ITCH;
|
||||
presence.buttons[1] = button2;
|
||||
|
||||
return presence;
|
||||
}
|
||||
|
||||
// TODO: WHAT THE FUCK get this pointer bullfuckery out of here
|
||||
private static function onReady(request:cpp.RawConstPointer<DiscordUser>):Void
|
||||
{
|
||||
trace('[DISCORD] Client has connected!');
|
||||
|
||||
final username:String = request[0].username;
|
||||
final globalName:String = request[0].username;
|
||||
final discriminator:Int = Std.parseInt(request[0].discriminator);
|
||||
|
||||
if (discriminator != 0)
|
||||
{
|
||||
trace('[DISCORD] User: ${username}#${discriminator} (${globalName})');
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[DISCORD] User: @${username} (${globalName})');
|
||||
}
|
||||
}
|
||||
|
||||
private static function onDisconnected(errorCode:Int, message:cpp.ConstCharStar):Void
|
||||
{
|
||||
trace('[DISCORD] Client has disconnected! ($errorCode) "${cast (message, String)}"');
|
||||
}
|
||||
|
||||
private static function onError(errorCode:Int, message:cpp.ConstCharStar):Void
|
||||
{
|
||||
trace('[DISCORD] Client has received an error! ($errorCode) "${cast (message, String)}"');
|
||||
}
|
||||
|
||||
// public var type(get, set):DiscordActivityType;
|
||||
// public var state(get, set):String;
|
||||
// public var details(get, set):String;
|
||||
// public var startTimestamp(get, set):Int;
|
||||
// public var endTimestamp(get, set):Int;
|
||||
// public var largeImageKey(get, set):String;
|
||||
// public var largeImageText(get, set):String;
|
||||
// public var smallImageKey(get, set):String;
|
||||
// public var smallImageText(get, set):String;
|
||||
//
|
||||
//
|
||||
// public var partyId(get, set)
|
||||
// public var partySize(get, set)
|
||||
// public var partyMax(get, set)
|
||||
// public var partyPrivacy(get, set)
|
||||
//
|
||||
// public var buttons(get, set)
|
||||
//
|
||||
// public var matchSecret(get, set)
|
||||
// public var joinSecret(get, set)
|
||||
// public var spectateSecret(get, set)
|
||||
}
|
||||
|
||||
typedef DiscordClientPresenceParams =
|
||||
{
|
||||
/**
|
||||
* The first row of text below the game title.
|
||||
*/
|
||||
var state:String;
|
||||
|
||||
/**
|
||||
* The second row of text below the game title.
|
||||
* Use `null` to display no text.
|
||||
*/
|
||||
var details:Null<String>;
|
||||
|
||||
/**
|
||||
* A large, 4-row high image to the left of the content.
|
||||
*/
|
||||
var ?largeImageKey:String;
|
||||
|
||||
/**
|
||||
* A small, inset image to the bottom right of `largeImageKey`.
|
||||
*/
|
||||
var ?smallImageKey:String;
|
||||
}
|
||||
#end
|
||||
@@ -390,7 +390,7 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
}
|
||||
else
|
||||
{
|
||||
var music = FunkinSound.load(pathToUse, params?.startingVolume ?? 1.0, params.loop ?? true, false, true, params.onComplete);
|
||||
var music = FunkinSound.load(pathToUse, params?.startingVolume ?? 1.0, params.loop ?? true, false, true, params.persist ?? false, params.onComplete);
|
||||
if (music != null)
|
||||
{
|
||||
FlxG.sound.music = music;
|
||||
@@ -430,12 +430,13 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
* @param autoDestroy Whether to destroy this sound when it finishes playing.
|
||||
* Leave this value set to `false` if you want to re-use this `FunkinSound` instance.
|
||||
* @param autoPlay Whether to play the sound immediately or wait for a `play()` call.
|
||||
* @param persist Whether to keep this `FunkinSound` between states, or destroy it.
|
||||
* @param onComplete Called when the sound finished playing.
|
||||
* @param onLoad Called when the sound finished loading. Called immediately for succesfully loaded embedded sounds.
|
||||
* @return A `FunkinSound` object, or `null` if the sound could not be loaded.
|
||||
*/
|
||||
public static function load(embeddedSound:FlxSoundAsset, volume:Float = 1.0, looped:Bool = false, autoDestroy:Bool = false, autoPlay:Bool = false,
|
||||
?onComplete:Void->Void, ?onLoad:Void->Void):Null<FunkinSound>
|
||||
persist:Bool = false, ?onComplete:Void->Void, ?onLoad:Void->Void):Null<FunkinSound>
|
||||
{
|
||||
@:privateAccess
|
||||
if (SoundMixer.__soundChannels.length >= SoundMixer.MAX_ACTIVE_CHANNELS)
|
||||
@@ -462,7 +463,7 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
if (autoPlay) sound.play();
|
||||
sound.volume = volume;
|
||||
sound.group = FlxG.sound.defaultSoundGroup;
|
||||
sound.persist = true;
|
||||
sound.persist = persist;
|
||||
|
||||
// Make sure to add the sound to the list.
|
||||
// If it's already in, it won't get re-added.
|
||||
@@ -513,7 +514,7 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
});
|
||||
|
||||
soundRequest.future.onComplete(function(partialSound) {
|
||||
var snd = FunkinSound.load(partialSound, volume, looped, autoDestroy, autoPlay, onComplete, onLoad);
|
||||
var snd = FunkinSound.load(partialSound, volume, looped, autoDestroy, autoPlay, false, onComplete, onLoad);
|
||||
promise.complete(snd);
|
||||
});
|
||||
}
|
||||
@@ -543,18 +544,19 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
*/
|
||||
public static function playOnce(key:String, volume:Float = 1.0, ?onComplete:Void->Void, ?onLoad:Void->Void):Null<FunkinSound>
|
||||
{
|
||||
var result = FunkinSound.load(key, volume, false, true, true, onComplete, onLoad);
|
||||
var result:Null<FunkinSound> = FunkinSound.load(key, volume, false, true, true, false, onComplete, onLoad);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop all sounds in the pool and allow them to be recycled.
|
||||
*/
|
||||
public static function stopAllAudio(musicToo:Bool = false):Void
|
||||
public static function stopAllAudio(musicToo:Bool = false, persistToo:Bool = false):Void
|
||||
{
|
||||
for (sound in pool)
|
||||
{
|
||||
if (sound == null) continue;
|
||||
if (!persistToo && sound.persist) continue;
|
||||
if (!musicToo && sound == FlxG.sound.music) continue;
|
||||
sound.destroy();
|
||||
}
|
||||
@@ -628,6 +630,11 @@ typedef FunkinSoundPlayMusicParams =
|
||||
|
||||
var ?partialParams:PartialSoundParams;
|
||||
|
||||
/**
|
||||
* Whether the sound should be destroyed on state switches
|
||||
*/
|
||||
var ?persist:Bool;
|
||||
|
||||
var ?onComplete:Void->Void;
|
||||
var ?onLoad:Void->Void;
|
||||
}
|
||||
|
||||
@@ -264,12 +264,32 @@ class PlayerCharSelectData
|
||||
*/
|
||||
@:optional
|
||||
public var position:Null<Int>;
|
||||
|
||||
/**
|
||||
* The GF name to assign for this character.
|
||||
*/
|
||||
@:optional
|
||||
public var gf:PlayerCharSelectGFData;
|
||||
}
|
||||
|
||||
typedef PlayerCharSelectGFData =
|
||||
{
|
||||
@:optional
|
||||
public var assetPath:String;
|
||||
|
||||
@:optional
|
||||
public var animInfoPath:String;
|
||||
|
||||
@:optional
|
||||
@:default(false)
|
||||
public var visualizer:Bool;
|
||||
}
|
||||
|
||||
typedef PlayerResultsData =
|
||||
{
|
||||
var music:PlayerResultsMusicData;
|
||||
|
||||
var perfectGold:Array<PlayerResultsAnimationData>;
|
||||
var perfect:Array<PlayerResultsAnimationData>;
|
||||
var excellent:Array<PlayerResultsAnimationData>;
|
||||
var great:Array<PlayerResultsAnimationData>;
|
||||
|
||||
@@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.0.2]
|
||||
### Added
|
||||
- Added the ability to specify `flipX` and `flipY` on stage props to horizontally or vertically flip, respectively.
|
||||
|
||||
## [1.0.1]
|
||||
### Added
|
||||
- Added the ability to specify a hexadecimal color in the `assetPath` field instead of a texture key.
|
||||
|
||||
@@ -118,6 +118,22 @@ typedef StageDataProp =
|
||||
@:default(false)
|
||||
var isPixel:Bool;
|
||||
|
||||
/**
|
||||
* If set to true, the prop will be flipped horizontally.
|
||||
* @default false
|
||||
*/
|
||||
@:optional
|
||||
@:default(false)
|
||||
var flipX:Bool;
|
||||
|
||||
/**
|
||||
* If set to true, the prop will be flipped vertically.
|
||||
* @default false
|
||||
*/
|
||||
@:optional
|
||||
@:default(false)
|
||||
var flipY:Bool;
|
||||
|
||||
/**
|
||||
* Either the scale of the prop as a float, or the [w, h] scale as an array of two floats.
|
||||
* Pro tip: On pixel-art levels, save the sprite small and set this value to 6 or so to save memory.
|
||||
|
||||
@@ -11,9 +11,9 @@ class StageRegistry extends BaseRegistry<Stage, StageData>
|
||||
* Handle breaking changes by incrementing this value
|
||||
* and adding migration to the `migrateStageData()` function.
|
||||
*/
|
||||
public static final STAGE_DATA_VERSION:thx.semver.Version = "1.0.0";
|
||||
public static final STAGE_DATA_VERSION:thx.semver.Version = "1.0.2";
|
||||
|
||||
public static final STAGE_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
|
||||
public static final STAGE_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <=1.0.2";
|
||||
|
||||
public static var instance(get, never):StageRegistry;
|
||||
static var _instance:Null<StageRegistry> = null;
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package funkin.effects;
|
||||
|
||||
import flixel.addons.effects.FlxTrail;
|
||||
import funkin.play.stage.Bopper;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.system.FlxAssets;
|
||||
|
||||
/**
|
||||
* An offshoot of FlxTrail, but accomodates the way Funkin
|
||||
* does offsets for characters. example, fixes Spirits trail
|
||||
*/
|
||||
class FunkTrail extends FlxTrail
|
||||
{
|
||||
/**
|
||||
* Creates a new FunkTrail effect for a specific FlxSprite.
|
||||
*
|
||||
* @param Target The FlxSprite the trail is attached to.
|
||||
* @param Graphic The image to use for the trailsprites. Optional, uses the sprite's graphic if null.
|
||||
* @param Length The amount of trailsprites to create.
|
||||
* @param Delay How often to update the trail. 0 updates every frame.
|
||||
* @param Alpha The alpha value for the very first trailsprite.
|
||||
* @param Diff How much lower the alpha of the next trailsprite is.
|
||||
*/
|
||||
public function new(Target:FlxSprite, ?Graphic:FlxGraphicAsset, Length:Int = 10, Delay:Int = 3, Alpha:Float = 0.4, Diff:Float = 0.05)
|
||||
{
|
||||
super(Target, Graphic, Length, Delay, Alpha, Diff);
|
||||
}
|
||||
|
||||
override public function update(elapsed:Float):Void
|
||||
{
|
||||
if (target is Bopper)
|
||||
{
|
||||
var targ:Bopper = cast target;
|
||||
@:privateAccess effectOffset.set((targ.animOffsets[0] - targ.globalOffsets[0]) * targ.scale.x,
|
||||
(targ.animOffsets[1] - targ.globalOffsets[1]) * targ.scale.y);
|
||||
}
|
||||
|
||||
super.update(elapsed);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import flxanimate.frames.FlxAnimateFrames;
|
||||
import flixel.graphics.frames.FlxFrame;
|
||||
import flixel.system.FlxAssets.FlxGraphicAsset;
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.utils.Assets;
|
||||
import flixel.math.FlxPoint;
|
||||
import flxanimate.animate.FlxKeyFrame;
|
||||
|
||||
@@ -184,7 +183,7 @@ class FlxAtlasSprite extends FlxAnimate
|
||||
|
||||
// Move to the first frame of the animation.
|
||||
// goToFrameLabel(id);
|
||||
trace('Playing animation $id');
|
||||
// trace('Playing animation $id');
|
||||
if ((id == null || id == "") || this.anim.symbolDictionary.exists(id) || (this.anim.getByName(id) != null))
|
||||
{
|
||||
this.anim.play(id, restart, false, startFrame);
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.addons.display.FlxRuntimeShader;
|
||||
import funkin.Paths;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
class AdjustColorShader extends FlxRuntimeShader
|
||||
{
|
||||
@@ -52,4 +50,9 @@ class AdjustColorShader extends FlxRuntimeShader
|
||||
|
||||
return this.contrast;
|
||||
}
|
||||
|
||||
public override function toString():String
|
||||
{
|
||||
return 'AdjustColorShader(${this.hue}, ${this.saturation}, ${this.brightness}, ${this.contrast})';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.addons.display.FlxRuntimeShader;
|
||||
import funkin.Paths;
|
||||
import openfl.utils.Assets;
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.display.ShaderInput;
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.addons.display.FlxRuntimeShader;
|
||||
import funkin.Paths;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
/**
|
||||
* Note... not actually gaussian!
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.addons.display.FlxRuntimeShader;
|
||||
import funkin.Paths;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
class Grayscale extends FlxRuntimeShader
|
||||
{
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.addons.display.FlxRuntimeShader;
|
||||
import funkin.Paths;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
class HSVShader extends FlxRuntimeShader
|
||||
{
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.addons.display.FlxRuntimeShader;
|
||||
import openfl.utils.Assets;
|
||||
import funkin.Paths;
|
||||
import flixel.math.FlxPoint;
|
||||
|
||||
class MosaicEffect extends FlxRuntimeShader
|
||||
|
||||
@@ -2,7 +2,6 @@ package funkin.graphics.shaders;
|
||||
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.display.BlendMode;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
class RuntimeCustomBlendShader extends RuntimePostEffectShader
|
||||
{
|
||||
|
||||
@@ -5,7 +5,6 @@ import openfl.display.BitmapData;
|
||||
import openfl.display.ShaderParameter;
|
||||
import openfl.display.ShaderParameterType;
|
||||
import flixel.util.FlxColor;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
typedef Light =
|
||||
{
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package funkin.graphics.video;
|
||||
|
||||
#if hxCodec
|
||||
import hxcodec.flixel.FlxVideoSprite;
|
||||
|
||||
/**
|
||||
* Not to be confused with FlxVideo, this is a hxcodec based video class
|
||||
* We override it simply to correct/control our volume easier.
|
||||
*/
|
||||
class FunkinVideoSprite extends FlxVideoSprite
|
||||
{
|
||||
public var volume(default, set):Float = 1;
|
||||
|
||||
public function new(x:Float = 0, y:Float = 0)
|
||||
{
|
||||
super(x, y);
|
||||
|
||||
set_volume(1);
|
||||
}
|
||||
|
||||
override public function update(elapsed:Float):Void
|
||||
{
|
||||
super.update(elapsed);
|
||||
set_volume(volume);
|
||||
}
|
||||
|
||||
function set_volume(value:Float):Float
|
||||
{
|
||||
volume = value;
|
||||
bitmap.volume = Std.int((FlxG.sound.muted ? 0 : 1) * (FlxG.sound.logToLinear(FlxG.sound.volume) * 100) * volume);
|
||||
return volume;
|
||||
}
|
||||
}
|
||||
#end
|
||||
@@ -3,6 +3,7 @@ package;
|
||||
#if !macro
|
||||
// Only import these when we aren't in a macro.
|
||||
import funkin.util.Constants;
|
||||
import funkin.Assets;
|
||||
import funkin.Paths;
|
||||
import funkin.Preferences;
|
||||
import flixel.FlxG; // This one in particular causes a compile error if you're using macros.
|
||||
|
||||
@@ -59,7 +59,9 @@ class Controls extends FlxActionSet
|
||||
var _back = new FunkinAction(Action.BACK);
|
||||
var _pause = new FunkinAction(Action.PAUSE);
|
||||
var _reset = new FunkinAction(Action.RESET);
|
||||
#if FEATURE_SCREENSHOTS
|
||||
var _window_screenshot = new FunkinAction(Action.WINDOW_SCREENSHOT);
|
||||
#end
|
||||
var _window_fullscreen = new FunkinAction(Action.WINDOW_FULLSCREEN);
|
||||
var _freeplay_favorite = new FunkinAction(Action.FREEPLAY_FAVORITE);
|
||||
var _freeplay_left = new FunkinAction(Action.FREEPLAY_LEFT);
|
||||
@@ -67,8 +69,12 @@ class Controls extends FlxActionSet
|
||||
var _freeplay_char_select = new FunkinAction(Action.FREEPLAY_CHAR_SELECT);
|
||||
var _cutscene_advance = new FunkinAction(Action.CUTSCENE_ADVANCE);
|
||||
var _debug_menu = new FunkinAction(Action.DEBUG_MENU);
|
||||
#if FEATURE_CHART_EDITOR
|
||||
var _debug_chart = new FunkinAction(Action.DEBUG_CHART);
|
||||
#end
|
||||
#if FEATURE_STAGE_EDITOR
|
||||
var _debug_stage = new FunkinAction(Action.DEBUG_STAGE);
|
||||
#end
|
||||
var _volume_up = new FunkinAction(Action.VOLUME_UP);
|
||||
var _volume_down = new FunkinAction(Action.VOLUME_DOWN);
|
||||
var _volume_mute = new FunkinAction(Action.VOLUME_MUTE);
|
||||
@@ -243,10 +249,12 @@ class Controls extends FlxActionSet
|
||||
inline function get_WINDOW_FULLSCREEN()
|
||||
return _window_fullscreen.check();
|
||||
|
||||
#if FEATURE_SCREENSHOTS
|
||||
public var WINDOW_SCREENSHOT(get, never):Bool;
|
||||
|
||||
inline function get_WINDOW_SCREENSHOT()
|
||||
return _window_screenshot.check();
|
||||
#end
|
||||
|
||||
public var FREEPLAY_FAVORITE(get, never):Bool;
|
||||
|
||||
@@ -278,15 +286,19 @@ class Controls extends FlxActionSet
|
||||
inline function get_DEBUG_MENU()
|
||||
return _debug_menu.check();
|
||||
|
||||
#if FEATURE_CHART_EDITOR
|
||||
public var DEBUG_CHART(get, never):Bool;
|
||||
|
||||
inline function get_DEBUG_CHART()
|
||||
return _debug_chart.check();
|
||||
#end
|
||||
|
||||
#if FEATURE_STAGE_EDITOR
|
||||
public var DEBUG_STAGE(get, never):Bool;
|
||||
|
||||
inline function get_DEBUG_STAGE()
|
||||
return _debug_stage.check();
|
||||
#end
|
||||
|
||||
public var VOLUME_UP(get, never):Bool;
|
||||
|
||||
@@ -319,7 +331,7 @@ class Controls extends FlxActionSet
|
||||
add(_back);
|
||||
add(_pause);
|
||||
add(_reset);
|
||||
add(_window_screenshot);
|
||||
#if FEATURE_SCREENSHOTS add(_window_screenshot); #end
|
||||
add(_window_fullscreen);
|
||||
add(_freeplay_favorite);
|
||||
add(_freeplay_left);
|
||||
@@ -327,8 +339,8 @@ class Controls extends FlxActionSet
|
||||
add(_freeplay_char_select);
|
||||
add(_cutscene_advance);
|
||||
add(_debug_menu);
|
||||
add(_debug_chart);
|
||||
add(_debug_stage);
|
||||
#if FEATURE_CHART_EDITOR add(_debug_chart); #end
|
||||
#if FEATURE_STAGE_EDITOR add(_debug_stage); #end
|
||||
add(_volume_up);
|
||||
add(_volume_down);
|
||||
add(_volume_mute);
|
||||
@@ -444,7 +456,7 @@ class Controls extends FlxActionSet
|
||||
case BACK: _back;
|
||||
case PAUSE: _pause;
|
||||
case RESET: _reset;
|
||||
case WINDOW_SCREENSHOT: _window_screenshot;
|
||||
#if FEATURE_SCREENSHOTS case WINDOW_SCREENSHOT: _window_screenshot; #end
|
||||
case WINDOW_FULLSCREEN: _window_fullscreen;
|
||||
case FREEPLAY_FAVORITE: _freeplay_favorite;
|
||||
case FREEPLAY_LEFT: _freeplay_left;
|
||||
@@ -452,8 +464,8 @@ class Controls extends FlxActionSet
|
||||
case FREEPLAY_CHAR_SELECT: _freeplay_char_select;
|
||||
case CUTSCENE_ADVANCE: _cutscene_advance;
|
||||
case DEBUG_MENU: _debug_menu;
|
||||
case DEBUG_CHART: _debug_chart;
|
||||
case DEBUG_STAGE: _debug_stage;
|
||||
#if FEATURE_CHART_EDITOR case DEBUG_CHART: _debug_chart; #end
|
||||
#if FEATURE_STAGE_EDITOR case DEBUG_STAGE: _debug_stage; #end
|
||||
case VOLUME_UP: _volume_up;
|
||||
case VOLUME_DOWN: _volume_down;
|
||||
case VOLUME_MUTE: _volume_mute;
|
||||
@@ -516,8 +528,10 @@ class Controls extends FlxActionSet
|
||||
func(_pause, JUST_PRESSED);
|
||||
case RESET:
|
||||
func(_reset, JUST_PRESSED);
|
||||
#if FEATURE_SCREENSHOTS
|
||||
case WINDOW_SCREENSHOT:
|
||||
func(_window_screenshot, JUST_PRESSED);
|
||||
#end
|
||||
case WINDOW_FULLSCREEN:
|
||||
func(_window_fullscreen, JUST_PRESSED);
|
||||
case FREEPLAY_FAVORITE:
|
||||
@@ -532,10 +546,14 @@ class Controls extends FlxActionSet
|
||||
func(_cutscene_advance, JUST_PRESSED);
|
||||
case DEBUG_MENU:
|
||||
func(_debug_menu, JUST_PRESSED);
|
||||
#if FEATURE_CHART_EDITOR
|
||||
case DEBUG_CHART:
|
||||
func(_debug_chart, JUST_PRESSED);
|
||||
#end
|
||||
#if FEATURE_STAGE_EDITOR
|
||||
case DEBUG_STAGE:
|
||||
func(_debug_stage, JUST_PRESSED);
|
||||
#end
|
||||
case VOLUME_UP:
|
||||
func(_volume_up, JUST_PRESSED);
|
||||
case VOLUME_DOWN:
|
||||
@@ -744,7 +762,9 @@ class Controls extends FlxActionSet
|
||||
bindKeys(Control.BACK, getDefaultKeybinds(scheme, Control.BACK));
|
||||
bindKeys(Control.PAUSE, getDefaultKeybinds(scheme, Control.PAUSE));
|
||||
bindKeys(Control.RESET, getDefaultKeybinds(scheme, Control.RESET));
|
||||
#if FEATURE_SCREENSHOTS
|
||||
bindKeys(Control.WINDOW_SCREENSHOT, getDefaultKeybinds(scheme, Control.WINDOW_SCREENSHOT));
|
||||
#end
|
||||
bindKeys(Control.WINDOW_FULLSCREEN, getDefaultKeybinds(scheme, Control.WINDOW_FULLSCREEN));
|
||||
bindKeys(Control.FREEPLAY_FAVORITE, getDefaultKeybinds(scheme, Control.FREEPLAY_FAVORITE));
|
||||
bindKeys(Control.FREEPLAY_LEFT, getDefaultKeybinds(scheme, Control.FREEPLAY_LEFT));
|
||||
@@ -752,8 +772,12 @@ class Controls extends FlxActionSet
|
||||
bindKeys(Control.FREEPLAY_CHAR_SELECT, getDefaultKeybinds(scheme, Control.FREEPLAY_CHAR_SELECT));
|
||||
bindKeys(Control.CUTSCENE_ADVANCE, getDefaultKeybinds(scheme, Control.CUTSCENE_ADVANCE));
|
||||
bindKeys(Control.DEBUG_MENU, getDefaultKeybinds(scheme, Control.DEBUG_MENU));
|
||||
#if FEATURE_CHART_EDITOR
|
||||
bindKeys(Control.DEBUG_CHART, getDefaultKeybinds(scheme, Control.DEBUG_CHART));
|
||||
#end
|
||||
#if FEATURE_STAGE_EDITOR
|
||||
bindKeys(Control.DEBUG_STAGE, getDefaultKeybinds(scheme, Control.DEBUG_STAGE));
|
||||
#end
|
||||
bindKeys(Control.VOLUME_UP, getDefaultKeybinds(scheme, Control.VOLUME_UP));
|
||||
bindKeys(Control.VOLUME_DOWN, getDefaultKeybinds(scheme, Control.VOLUME_DOWN));
|
||||
bindKeys(Control.VOLUME_MUTE, getDefaultKeybinds(scheme, Control.VOLUME_MUTE));
|
||||
@@ -781,15 +805,15 @@ class Controls extends FlxActionSet
|
||||
case Control.PAUSE: return [P, ENTER, ESCAPE];
|
||||
case Control.RESET: return [R];
|
||||
case Control.WINDOW_FULLSCREEN: return [F11]; // We use F for other things LOL.
|
||||
case Control.WINDOW_SCREENSHOT: return [F3];
|
||||
#if FEATURE_SCREENSHOTS case Control.WINDOW_SCREENSHOT: return [F3]; #end
|
||||
case Control.FREEPLAY_FAVORITE: return [F]; // Favorite a song on the menu
|
||||
case Control.FREEPLAY_LEFT: return [Q]; // Switch tabs on the menu
|
||||
case Control.FREEPLAY_RIGHT: return [E]; // Switch tabs on the menu
|
||||
case Control.FREEPLAY_CHAR_SELECT: return [TAB];
|
||||
case Control.CUTSCENE_ADVANCE: return [Z, ENTER];
|
||||
case Control.DEBUG_MENU: return [GRAVEACCENT];
|
||||
case Control.DEBUG_CHART: return [];
|
||||
case Control.DEBUG_STAGE: return [];
|
||||
#if FEATURE_CHART_EDITOR case Control.DEBUG_CHART: return []; #end
|
||||
#if FEATURE_STAGE_EDITOR case Control.DEBUG_STAGE: return []; #end
|
||||
case Control.VOLUME_UP: return [PLUS, NUMPADPLUS];
|
||||
case Control.VOLUME_DOWN: return [MINUS, NUMPADMINUS];
|
||||
case Control.VOLUME_MUTE: return [ZERO, NUMPADZERO];
|
||||
@@ -809,7 +833,7 @@ class Controls extends FlxActionSet
|
||||
case Control.BACK: return [H, X];
|
||||
case Control.PAUSE: return [ONE];
|
||||
case Control.RESET: return [R];
|
||||
case Control.WINDOW_SCREENSHOT: return [F3];
|
||||
#if FEATURE_SCREENSHOTS case Control.WINDOW_SCREENSHOT: return [F3]; #end
|
||||
case Control.WINDOW_FULLSCREEN: return [F11];
|
||||
case Control.FREEPLAY_FAVORITE: return [F]; // Favorite a song on the menu
|
||||
case Control.FREEPLAY_LEFT: return [Q]; // Switch tabs on the menu
|
||||
@@ -817,8 +841,8 @@ class Controls extends FlxActionSet
|
||||
case Control.FREEPLAY_CHAR_SELECT: return [TAB];
|
||||
case Control.CUTSCENE_ADVANCE: return [G, Z];
|
||||
case Control.DEBUG_MENU: return [GRAVEACCENT];
|
||||
case Control.DEBUG_CHART: return [];
|
||||
case Control.DEBUG_STAGE: return [];
|
||||
#if FEATURE_CHART_EDITOR case Control.DEBUG_CHART: return []; #end
|
||||
#if FEATURE_STAGE_EDITOR case Control.DEBUG_STAGE: return []; #end
|
||||
case Control.VOLUME_UP: return [PLUS];
|
||||
case Control.VOLUME_DOWN: return [MINUS];
|
||||
case Control.VOLUME_MUTE: return [ZERO];
|
||||
@@ -838,7 +862,7 @@ class Controls extends FlxActionSet
|
||||
case Control.BACK: return [ESCAPE];
|
||||
case Control.PAUSE: return [ONE];
|
||||
case Control.RESET: return [R];
|
||||
case Control.WINDOW_SCREENSHOT: return [];
|
||||
#if FEATURE_SCREENSHOTS case Control.WINDOW_SCREENSHOT: return []; #end
|
||||
case Control.WINDOW_FULLSCREEN: return [];
|
||||
case Control.FREEPLAY_FAVORITE: return [];
|
||||
case Control.FREEPLAY_LEFT: return [];
|
||||
@@ -846,8 +870,8 @@ class Controls extends FlxActionSet
|
||||
case Control.FREEPLAY_CHAR_SELECT: return [];
|
||||
case Control.CUTSCENE_ADVANCE: return [ENTER];
|
||||
case Control.DEBUG_MENU: return [];
|
||||
case Control.DEBUG_CHART: return [];
|
||||
case Control.DEBUG_STAGE: return [];
|
||||
#if FEATURE_CHART_EDITOR case Control.DEBUG_CHART: return []; #end
|
||||
#if FEATURE_STAGE_EDITOR case Control.DEBUG_STAGE: return []; #end
|
||||
case Control.VOLUME_UP: return [NUMPADPLUS];
|
||||
case Control.VOLUME_DOWN: return [NUMPADMINUS];
|
||||
case Control.VOLUME_MUTE: return [NUMPADZERO];
|
||||
@@ -952,7 +976,9 @@ class Controls extends FlxActionSet
|
||||
Control.PAUSE => getDefaultGamepadBinds(Control.PAUSE),
|
||||
Control.RESET => getDefaultGamepadBinds(Control.RESET),
|
||||
Control.WINDOW_FULLSCREEN => getDefaultGamepadBinds(Control.WINDOW_FULLSCREEN),
|
||||
#if FEATURE_SCREENSHOTS
|
||||
Control.WINDOW_SCREENSHOT => getDefaultGamepadBinds(Control.WINDOW_SCREENSHOT),
|
||||
#end
|
||||
Control.CUTSCENE_ADVANCE => getDefaultGamepadBinds(Control.CUTSCENE_ADVANCE),
|
||||
Control.FREEPLAY_FAVORITE => getDefaultGamepadBinds(Control.FREEPLAY_FAVORITE),
|
||||
Control.FREEPLAY_LEFT => getDefaultGamepadBinds(Control.FREEPLAY_LEFT),
|
||||
@@ -961,8 +987,12 @@ class Controls extends FlxActionSet
|
||||
Control.VOLUME_DOWN => getDefaultGamepadBinds(Control.VOLUME_DOWN),
|
||||
Control.VOLUME_MUTE => getDefaultGamepadBinds(Control.VOLUME_MUTE),
|
||||
Control.DEBUG_MENU => getDefaultGamepadBinds(Control.DEBUG_MENU),
|
||||
#if FEATURE_CHART_EDITOR
|
||||
Control.DEBUG_CHART => getDefaultGamepadBinds(Control.DEBUG_CHART),
|
||||
#end
|
||||
#if FEATURE_STAGE_EDITOR
|
||||
Control.DEBUG_STAGE => getDefaultGamepadBinds(Control.DEBUG_STAGE),
|
||||
#end
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -996,8 +1026,10 @@ class Controls extends FlxActionSet
|
||||
return [FlxGamepadInputID.BACK]; // Back (i.e. Select)
|
||||
case Control.WINDOW_FULLSCREEN:
|
||||
[];
|
||||
#if FEATURE_SCREENSHOTS
|
||||
case Control.WINDOW_SCREENSHOT:
|
||||
[];
|
||||
#end
|
||||
case Control.CUTSCENE_ADVANCE:
|
||||
return [A];
|
||||
case Control.FREEPLAY_FAVORITE:
|
||||
@@ -1014,10 +1046,14 @@ class Controls extends FlxActionSet
|
||||
[];
|
||||
case Control.DEBUG_MENU:
|
||||
[];
|
||||
#if FEATURE_CHART_EDITOR
|
||||
case Control.DEBUG_CHART:
|
||||
[];
|
||||
#end
|
||||
#if FEATURE_STAGE_EDITOR
|
||||
case Control.DEBUG_STAGE:
|
||||
[];
|
||||
#end
|
||||
default:
|
||||
// Fallthrough.
|
||||
}
|
||||
@@ -1582,7 +1618,7 @@ enum Control
|
||||
FREEPLAY_RIGHT;
|
||||
FREEPLAY_CHAR_SELECT;
|
||||
// WINDOW
|
||||
WINDOW_SCREENSHOT;
|
||||
#if FEATURE_SCREENSHOTS WINDOW_SCREENSHOT; #end
|
||||
WINDOW_FULLSCREEN;
|
||||
// VOLUME
|
||||
VOLUME_UP;
|
||||
@@ -1590,8 +1626,8 @@ enum Control
|
||||
VOLUME_MUTE;
|
||||
// DEBUG
|
||||
DEBUG_MENU;
|
||||
DEBUG_CHART;
|
||||
DEBUG_STAGE;
|
||||
#if FEATURE_CHART_EDITOR DEBUG_CHART; #end
|
||||
#if FEATURE_STAGE_EDITOR DEBUG_STAGE; #end
|
||||
}
|
||||
|
||||
enum abstract Action(String) to String from String
|
||||
@@ -1628,7 +1664,9 @@ enum abstract Action(String) to String from String
|
||||
var RESET = "reset";
|
||||
// WINDOW
|
||||
var WINDOW_FULLSCREEN = "window_fullscreen";
|
||||
#if FEATURE_SCREENSHOTS
|
||||
var WINDOW_SCREENSHOT = "window_screenshot";
|
||||
#end
|
||||
// CUTSCENE
|
||||
var CUTSCENE_ADVANCE = "cutscene_advance";
|
||||
// FREEPLAY
|
||||
@@ -1642,8 +1680,12 @@ enum abstract Action(String) to String from String
|
||||
var VOLUME_MUTE = "volume_mute";
|
||||
// DEBUG
|
||||
var DEBUG_MENU = "debug_menu";
|
||||
#if FEATURE_CHART_EDITOR
|
||||
var DEBUG_CHART = "debug_chart";
|
||||
#end
|
||||
#if FEATURE_STAGE_EDITOR
|
||||
var DEBUG_STAGE = "debug_stage";
|
||||
#end
|
||||
}
|
||||
|
||||
enum Device
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package funkin.input;
|
||||
|
||||
import haxe.ui.backend.flixel.CursorHelper;
|
||||
import openfl.utils.Assets;
|
||||
import lime.app.Future;
|
||||
import openfl.display.BitmapData;
|
||||
|
||||
|
||||
@@ -27,11 +27,18 @@ import polymod.Polymod;
|
||||
class PolymodHandler
|
||||
{
|
||||
/**
|
||||
* The API version that mods should comply with.
|
||||
* Indicates which mods are compatible with this version of the game.
|
||||
* The API version for the current version of the game. Since 0.5.0, we've just made this the game version!
|
||||
* Minor updates rarely impact mods but major versions often do.
|
||||
*/
|
||||
static final API_VERSION:String = "0.5.0"; // Constants.VERSION;
|
||||
// static final API_VERSION:String = Constants.VERSION;
|
||||
|
||||
/**
|
||||
* The Semantic Versioning rule
|
||||
* Indicates which mods are compatible with this version of the game.
|
||||
* Using more complex rules allows mods from older compatible versions to stay functioning,
|
||||
* while preventing mods made for future versions from being installed.
|
||||
*/
|
||||
static final API_VERSION_RULE:String = ">=0.5.0 <0.6.0";
|
||||
|
||||
/**
|
||||
* Where relative to the executable that mods are located.
|
||||
@@ -131,7 +138,7 @@ class PolymodHandler
|
||||
// Framework being used to load assets.
|
||||
framework: OPENFL,
|
||||
// The current version of our API.
|
||||
apiVersionRule: API_VERSION,
|
||||
apiVersionRule: API_VERSION_RULE,
|
||||
// Call this function any time an error occurs.
|
||||
errorCallback: PolymodErrorHandler.onPolymodError,
|
||||
// Enforce semantic version patterns for each mod.
|
||||
@@ -228,6 +235,8 @@ class PolymodHandler
|
||||
static function buildImports():Void
|
||||
{
|
||||
// Add default imports for common classes.
|
||||
Polymod.addDefaultImport(funkin.Assets);
|
||||
Polymod.addDefaultImport(funkin.Paths);
|
||||
|
||||
// Add import aliases for certain classes.
|
||||
// NOTE: Scripted classes are automatically aliased to their parent class.
|
||||
@@ -258,7 +267,7 @@ class PolymodHandler
|
||||
Polymod.blacklistImport('cpp.Lib');
|
||||
|
||||
// `Unserializer`
|
||||
// Unserializerr.DEFAULT_RESOLVER.resolveClass() can access blacklisted packages
|
||||
// Unserializer.DEFAULT_RESOLVER.resolveClass() can access blacklisted packages
|
||||
Polymod.blacklistImport('Unserializer');
|
||||
|
||||
// `lime.system.CFFI`
|
||||
@@ -336,7 +345,7 @@ class PolymodHandler
|
||||
var modMetadata:Array<ModMetadata> = Polymod.scan(
|
||||
{
|
||||
modRoot: MOD_FOLDER,
|
||||
apiVersionRule: API_VERSION,
|
||||
apiVersionRule: API_VERSION_RULE,
|
||||
fileSystem: modFileSystem,
|
||||
errorCallback: PolymodErrorHandler.onPolymodError
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package funkin.modding.base;
|
||||
|
||||
/**
|
||||
* A script that can be tied to an FlxSprite.
|
||||
* Create a scripted class that extends FlxSprite to use this.
|
||||
* A script that can be tied to a FunkinSprite.
|
||||
* Create a scripted class that extends FunkinSprite to use this.
|
||||
*/
|
||||
@:hscriptClass
|
||||
class ScriptedFunkinSprite extends funkin.graphics.FunkinSprite implements HScriptedClass {}
|
||||
|
||||
@@ -103,7 +103,7 @@ class NoteScriptEvent extends ScriptEvent
|
||||
public var comboCount(default, null):Int;
|
||||
|
||||
/**
|
||||
* Whether to play the record scratch sound (if this eventn type is `NOTE_MISS`).
|
||||
* Whether to play the record scratch sound (if this event type is `NOTE_MISS`).
|
||||
*/
|
||||
public var playSound(default, default):Bool;
|
||||
|
||||
|
||||
@@ -125,7 +125,6 @@ class ModuleHandler
|
||||
for (key => value in moduleCache)
|
||||
{
|
||||
ScriptEventDispatcher.callEvent(value, event);
|
||||
moduleCache.remove(key);
|
||||
}
|
||||
|
||||
moduleCache.clear();
|
||||
|
||||
@@ -11,7 +11,6 @@ import funkin.modding.events.ScriptEvent.CountdownScriptEvent;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.util.EaseUtil;
|
||||
import funkin.audio.FunkinSound;
|
||||
import openfl.utils.Assets;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ import funkin.ui.freeplay.FreeplayState;
|
||||
import funkin.ui.MusicBeatSubState;
|
||||
import funkin.ui.story.StoryMenuState;
|
||||
import funkin.util.MathUtil;
|
||||
import openfl.utils.Assets;
|
||||
import funkin.effects.RetroCameraFade;
|
||||
import flixel.math.FlxPoint;
|
||||
|
||||
|
||||
+178
-68
@@ -15,8 +15,8 @@ import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.ui.FlxBar;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxTimer;
|
||||
import flixel.util.FlxStringUtil;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.api.newgrounds.NGio;
|
||||
import funkin.audio.FunkinSound;
|
||||
import funkin.audio.VoicesGroup;
|
||||
@@ -44,12 +44,12 @@ import funkin.play.cutscene.dialogue.Conversation;
|
||||
import funkin.play.cutscene.VanillaCutscenes;
|
||||
import funkin.play.cutscene.VideoCutscene;
|
||||
import funkin.play.notes.NoteDirection;
|
||||
import funkin.play.notes.notekind.NoteKindManager;
|
||||
import funkin.play.notes.NoteSplash;
|
||||
import funkin.play.notes.NoteSprite;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
import funkin.play.notes.Strumline;
|
||||
import funkin.play.notes.SustainTrail;
|
||||
import funkin.play.notes.notekind.NoteKindManager;
|
||||
import funkin.play.scoring.Scoring;
|
||||
import funkin.play.song.Song;
|
||||
import funkin.play.stage.Stage;
|
||||
@@ -68,7 +68,7 @@ import openfl.display.BitmapData;
|
||||
import openfl.geom.Rectangle;
|
||||
import openfl.Lib;
|
||||
#if FEATURE_DISCORD_RPC
|
||||
import Discord.DiscordClient;
|
||||
import funkin.api.discord.DiscordClient;
|
||||
#end
|
||||
|
||||
/**
|
||||
@@ -447,10 +447,8 @@ class PlayState extends MusicBeatSubState
|
||||
|
||||
#if FEATURE_DISCORD_RPC
|
||||
// Discord RPC variables
|
||||
var storyDifficultyText:String = '';
|
||||
var iconRPC:String = '';
|
||||
var detailsText:String = '';
|
||||
var detailsPausedText:String = '';
|
||||
var discordRPCAlbum:String = '';
|
||||
var discordRPCIcon:String = '';
|
||||
#end
|
||||
|
||||
/**
|
||||
@@ -687,7 +685,11 @@ class PlayState extends MusicBeatSubState
|
||||
}
|
||||
|
||||
Conductor.instance.mapTimeChanges(currentChart.timeChanges);
|
||||
Conductor.instance.update((Conductor.instance.beatLengthMs * -5) + startTimestamp);
|
||||
var pre:Float = (Conductor.instance.beatLengthMs * -5) + startTimestamp;
|
||||
|
||||
trace('Attempting to start at ' + pre);
|
||||
|
||||
Conductor.instance.update(pre);
|
||||
|
||||
// The song is now loaded. We can continue to initialize the play state.
|
||||
initCameras();
|
||||
@@ -813,6 +815,7 @@ class PlayState extends MusicBeatSubState
|
||||
}
|
||||
else
|
||||
{
|
||||
this.remove(currentStage);
|
||||
FlxG.switchState(() -> new MainMenuState());
|
||||
}
|
||||
return false;
|
||||
@@ -857,7 +860,7 @@ class PlayState extends MusicBeatSubState
|
||||
// Reset music properly.
|
||||
if (FlxG.sound.music != null)
|
||||
{
|
||||
FlxG.sound.music.time = startTimestamp - Conductor.instance.instrumentalOffset;
|
||||
FlxG.sound.music.time = startTimestamp - Conductor.instance.combinedOffset;
|
||||
FlxG.sound.music.pitch = playbackRate;
|
||||
FlxG.sound.music.pause();
|
||||
}
|
||||
@@ -874,7 +877,7 @@ class PlayState extends MusicBeatSubState
|
||||
}
|
||||
}
|
||||
vocals.pause();
|
||||
vocals.time = 0;
|
||||
vocals.time = 0 - Conductor.instance.combinedOffset;
|
||||
|
||||
if (FlxG.sound.music != null) FlxG.sound.music.volume = 1;
|
||||
vocals.volume = 1;
|
||||
@@ -915,7 +918,11 @@ class PlayState extends MusicBeatSubState
|
||||
{
|
||||
// Do NOT apply offsets at this point, because they already got applied the previous frame!
|
||||
Conductor.instance.update(Conductor.instance.songPosition + elapsed * 1000, false);
|
||||
if (Conductor.instance.songPosition >= (startTimestamp)) startSong();
|
||||
if (Conductor.instance.songPosition >= (startTimestamp + Conductor.instance.combinedOffset))
|
||||
{
|
||||
trace("started song at " + Conductor.instance.songPosition);
|
||||
startSong();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -957,6 +964,7 @@ class PlayState extends MusicBeatSubState
|
||||
// It's a reference to Gitaroo Man, which doesn't let you pause the game.
|
||||
if (!isSubState && event.gitaroo)
|
||||
{
|
||||
this.remove(currentStage);
|
||||
FlxG.switchState(() -> new GitarooPause(
|
||||
{
|
||||
targetSong: currentSong,
|
||||
@@ -984,7 +992,15 @@ class PlayState extends MusicBeatSubState
|
||||
}
|
||||
|
||||
#if FEATURE_DISCORD_RPC
|
||||
DiscordClient.changePresence(detailsPausedText, currentSong.song + ' (' + storyDifficultyText + ')', iconRPC);
|
||||
DiscordClient.instance.setPresence(
|
||||
{
|
||||
details: 'Paused - ${buildDiscordRPCDetails()}',
|
||||
|
||||
state: buildDiscordRPCState(),
|
||||
|
||||
largeImageKey: discordRPCAlbum,
|
||||
smallImageKey: discordRPCIcon
|
||||
});
|
||||
#end
|
||||
}
|
||||
}
|
||||
@@ -1073,8 +1089,14 @@ class PlayState extends MusicBeatSubState
|
||||
}
|
||||
|
||||
#if FEATURE_DISCORD_RPC
|
||||
// Game Over doesn't get his own variable because it's only used here
|
||||
DiscordClient.changePresence('Game Over - ' + detailsText, currentSong.song + ' (' + storyDifficultyText + ')', iconRPC);
|
||||
DiscordClient.instance.setPresence(
|
||||
{
|
||||
details: 'Game Over - ${buildDiscordRPCDetails()}',
|
||||
state: buildDiscordRPCState(),
|
||||
|
||||
largeImageKey: discordRPCAlbum,
|
||||
smallImageKey: discordRPCIcon
|
||||
});
|
||||
#end
|
||||
}
|
||||
else if (isPlayerDying)
|
||||
@@ -1285,14 +1307,29 @@ class PlayState extends MusicBeatSubState
|
||||
Countdown.resumeCountdown();
|
||||
|
||||
#if FEATURE_DISCORD_RPC
|
||||
if (startTimer.finished)
|
||||
if (Conductor.instance.songPosition > 0)
|
||||
{
|
||||
DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC, true,
|
||||
currentSongLengthMs - Conductor.instance.songPosition);
|
||||
// DiscordClient.changePresence(detailsText, '${currentChart.songName} ($discordRPCDifficulty)', discordRPCIcon, true,
|
||||
// currentSongLengthMs - Conductor.instance.songPosition);
|
||||
DiscordClient.instance.setPresence(
|
||||
{
|
||||
state: buildDiscordRPCState(),
|
||||
details: buildDiscordRPCDetails(),
|
||||
|
||||
largeImageKey: discordRPCAlbum,
|
||||
smallImageKey: discordRPCIcon
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC);
|
||||
DiscordClient.instance.setPresence(
|
||||
{
|
||||
state: buildDiscordRPCState(),
|
||||
details: buildDiscordRPCDetails(),
|
||||
|
||||
largeImageKey: discordRPCAlbum,
|
||||
smallImageKey: discordRPCIcon
|
||||
});
|
||||
}
|
||||
#end
|
||||
|
||||
@@ -1318,16 +1355,32 @@ class PlayState extends MusicBeatSubState
|
||||
#end
|
||||
|
||||
#if FEATURE_DISCORD_RPC
|
||||
if (health > Constants.HEALTH_MIN && !paused && FlxG.autoPause)
|
||||
if (health > Constants.HEALTH_MIN && !isGamePaused && FlxG.autoPause)
|
||||
{
|
||||
if (Conductor.instance.songPosition > 0.0) DiscordClient.changePresence(detailsText, currentSong.song
|
||||
+ ' ('
|
||||
+ storyDifficultyText
|
||||
+ ')', iconRPC, true,
|
||||
currentSongLengthMs
|
||||
- Conductor.instance.songPosition);
|
||||
if (Conductor.instance.songPosition > 0.0)
|
||||
{
|
||||
DiscordClient.instance.setPresence(
|
||||
{
|
||||
state: buildDiscordRPCState(),
|
||||
details: buildDiscordRPCDetails(),
|
||||
|
||||
largeImageKey: discordRPCAlbum,
|
||||
smallImageKey: discordRPCIcon
|
||||
});
|
||||
}
|
||||
else
|
||||
DiscordClient.changePresence(detailsText, currentSong.song + ' (' + storyDifficultyText + ')', iconRPC);
|
||||
{
|
||||
DiscordClient.instance.setPresence(
|
||||
{
|
||||
state: buildDiscordRPCState(),
|
||||
details: buildDiscordRPCDetails(),
|
||||
|
||||
largeImageKey: discordRPCAlbum,
|
||||
smallImageKey: discordRPCIcon
|
||||
});
|
||||
// DiscordClient.changePresence(detailsText, '${currentChart.songName} ($discordRPCDifficulty)', discordRPCIcon, true,
|
||||
// currentSongLengthMs - Conductor.instance.songPosition);
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
@@ -1344,8 +1397,17 @@ class PlayState extends MusicBeatSubState
|
||||
#end
|
||||
|
||||
#if FEATURE_DISCORD_RPC
|
||||
if (health > Constants.HEALTH_MIN && !paused && FlxG.autoPause) DiscordClient.changePresence(detailsPausedText,
|
||||
currentSong.song + ' (' + storyDifficultyText + ')', iconRPC);
|
||||
if (health > Constants.HEALTH_MIN && !isGamePaused && FlxG.autoPause)
|
||||
{
|
||||
DiscordClient.instance.setPresence(
|
||||
{
|
||||
state: buildDiscordRPCState(),
|
||||
details: buildDiscordRPCDetails(),
|
||||
|
||||
largeImageKey: discordRPCAlbum,
|
||||
smallImageKey: discordRPCIcon
|
||||
});
|
||||
}
|
||||
#end
|
||||
|
||||
super.onFocusLost();
|
||||
@@ -1358,6 +1420,7 @@ class PlayState extends MusicBeatSubState
|
||||
{
|
||||
funkin.modding.PolymodHandler.forceReloadAssets();
|
||||
lastParams.targetSong = SongRegistry.instance.fetchEntry(currentSong.id);
|
||||
this.remove(currentStage);
|
||||
LoadingState.loadPlayState(lastParams);
|
||||
}
|
||||
|
||||
@@ -1391,15 +1454,18 @@ class PlayState extends MusicBeatSubState
|
||||
// activeNotes.sort(SortUtil.byStrumtime, FlxSort.DESCENDING);
|
||||
}
|
||||
|
||||
if (!startingSong
|
||||
&& FlxG.sound.music != null
|
||||
&& (Math.abs(FlxG.sound.music.time - (Conductor.instance.songPosition + Conductor.instance.instrumentalOffset)) > 100
|
||||
|| Math.abs(vocals.checkSyncError(Conductor.instance.songPosition + Conductor.instance.instrumentalOffset)) > 100))
|
||||
if (FlxG.sound.music != null)
|
||||
{
|
||||
trace("VOCALS NEED RESYNC");
|
||||
if (vocals != null) trace(vocals.checkSyncError(Conductor.instance.songPosition + Conductor.instance.instrumentalOffset));
|
||||
trace(FlxG.sound.music.time - (Conductor.instance.songPosition + Conductor.instance.instrumentalOffset));
|
||||
resyncVocals();
|
||||
var correctSync:Float = Math.min(FlxG.sound.music.length, Math.max(0, Conductor.instance.songPosition - Conductor.instance.combinedOffset));
|
||||
|
||||
if (!startingSong && (Math.abs(FlxG.sound.music.time - correctSync) > 5 || Math.abs(vocals.checkSyncError(correctSync)) > 5))
|
||||
{
|
||||
trace("VOCALS NEED RESYNC");
|
||||
if (vocals != null) trace(vocals.checkSyncError(correctSync));
|
||||
trace(FlxG.sound.music.time);
|
||||
trace(correctSync);
|
||||
resyncVocals();
|
||||
}
|
||||
}
|
||||
|
||||
// Only bop camera if zoom level is below 135%
|
||||
@@ -1612,7 +1678,7 @@ class PlayState extends MusicBeatSubState
|
||||
|
||||
if (girlfriend != null)
|
||||
{
|
||||
girlfriend.characterType = CharacterType.GF;
|
||||
// Don't need to do anything.
|
||||
}
|
||||
else if (currentCharacterData.girlfriend != '')
|
||||
{
|
||||
@@ -1630,8 +1696,6 @@ class PlayState extends MusicBeatSubState
|
||||
|
||||
if (dad != null)
|
||||
{
|
||||
dad.characterType = CharacterType.DAD;
|
||||
|
||||
//
|
||||
// OPPONENT HEALTH ICON
|
||||
//
|
||||
@@ -1641,6 +1705,11 @@ class PlayState extends MusicBeatSubState
|
||||
iconP2.zIndex = 850;
|
||||
add(iconP2);
|
||||
iconP2.cameras = [camHUD];
|
||||
|
||||
#if FEATURE_DISCORD_RPC
|
||||
discordRPCAlbum = 'album-${currentChart.album}';
|
||||
discordRPCIcon = 'icon-${currentCharacterData.opponent}';
|
||||
#end
|
||||
}
|
||||
|
||||
//
|
||||
@@ -1650,8 +1719,6 @@ class PlayState extends MusicBeatSubState
|
||||
|
||||
if (boyfriend != null)
|
||||
{
|
||||
boyfriend.characterType = CharacterType.BF;
|
||||
|
||||
//
|
||||
// PLAYER HEALTH ICON
|
||||
//
|
||||
@@ -1758,29 +1825,53 @@ class PlayState extends MusicBeatSubState
|
||||
function initDiscord():Void
|
||||
{
|
||||
#if FEATURE_DISCORD_RPC
|
||||
storyDifficultyText = difficultyString();
|
||||
iconRPC = currentSong.player2;
|
||||
|
||||
// To avoid having duplicate images in Discord assets
|
||||
switch (iconRPC)
|
||||
{
|
||||
case 'senpai-angry':
|
||||
iconRPC = 'senpai';
|
||||
case 'monster-christmas':
|
||||
iconRPC = 'monster';
|
||||
case 'mom-car':
|
||||
iconRPC = 'mom';
|
||||
}
|
||||
|
||||
// String that contains the mode defined here so it isn't necessary to call changePresence for each mode
|
||||
detailsText = isStoryMode ? 'Story Mode: Week $storyWeek' : 'Freeplay';
|
||||
detailsPausedText = 'Paused - $detailsText';
|
||||
// Determine the details strings once and reuse them.
|
||||
|
||||
// Updating Discord Rich Presence.
|
||||
DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC);
|
||||
DiscordClient.instance.setPresence(
|
||||
{
|
||||
state: buildDiscordRPCState(),
|
||||
details: buildDiscordRPCDetails(),
|
||||
|
||||
largeImageKey: discordRPCAlbum,
|
||||
smallImageKey: discordRPCIcon
|
||||
});
|
||||
#end
|
||||
}
|
||||
|
||||
function buildDiscordRPCDetails():String
|
||||
{
|
||||
if (PlayStatePlaylist.isStoryMode)
|
||||
{
|
||||
return 'Story Mode: ${PlayStatePlaylist.campaignTitle}';
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isChartingMode)
|
||||
{
|
||||
return 'Chart Editor [Playtest]';
|
||||
}
|
||||
else if (isPracticeMode)
|
||||
{
|
||||
return 'Freeplay [Practice]';
|
||||
}
|
||||
else if (isBotPlayMode)
|
||||
{
|
||||
return 'Freeplay [Bot Play]';
|
||||
}
|
||||
else
|
||||
{
|
||||
return 'Freeplay';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buildDiscordRPCState():String
|
||||
{
|
||||
var discordRPCDifficulty = PlayState.instance.currentDifficulty.replace('-', ' ').toTitleCase();
|
||||
return '${currentChart.songName} [${discordRPCDifficulty}]';
|
||||
}
|
||||
|
||||
function initPreciseInputs():Void
|
||||
{
|
||||
PreciseInputManager.instance.onInputPressed.add(onKeyPress);
|
||||
@@ -1956,7 +2047,7 @@ class PlayState extends MusicBeatSubState
|
||||
};
|
||||
// A negative instrumental offset means the song skips the first few milliseconds of the track.
|
||||
// This just gets added into the startTimestamp behavior so we don't need to do anything extra.
|
||||
FlxG.sound.music.play(true, startTimestamp - Conductor.instance.instrumentalOffset);
|
||||
FlxG.sound.music.play(true, Math.max(0, startTimestamp - Conductor.instance.combinedOffset));
|
||||
FlxG.sound.music.pitch = playbackRate;
|
||||
|
||||
// Prevent the volume from being wrong.
|
||||
@@ -1968,16 +2059,27 @@ class PlayState extends MusicBeatSubState
|
||||
vocals.play();
|
||||
vocals.volume = 1.0;
|
||||
vocals.pitch = playbackRate;
|
||||
vocals.time = FlxG.sound.music.time;
|
||||
// trace('${FlxG.sound.music.time}');
|
||||
// trace('${vocals.time}');
|
||||
resyncVocals();
|
||||
|
||||
#if FEATURE_DISCORD_RPC
|
||||
// Updating Discord Rich Presence (with Time Left)
|
||||
DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC, true, currentSongLengthMs);
|
||||
DiscordClient.instance.setPresence(
|
||||
{
|
||||
state: buildDiscordRPCState(),
|
||||
details: buildDiscordRPCDetails(),
|
||||
|
||||
largeImageKey: discordRPCAlbum,
|
||||
smallImageKey: discordRPCIcon
|
||||
});
|
||||
// DiscordClient.changePresence(detailsText, '${currentChart.songName} ($discordRPCDifficulty)', discordRPCIcon, true, currentSongLengthMs);
|
||||
#end
|
||||
|
||||
if (startTimestamp > 0)
|
||||
{
|
||||
// FlxG.sound.music.time = startTimestamp - Conductor.instance.instrumentalOffset;
|
||||
// FlxG.sound.music.time = startTimestamp - Conductor.instance.combinedOffset;
|
||||
handleSkippedNotes();
|
||||
}
|
||||
|
||||
@@ -1993,7 +2095,9 @@ class PlayState extends MusicBeatSubState
|
||||
|
||||
// Skip this if the music is paused (GameOver, Pause menu, start-of-song offset, etc.)
|
||||
if (!(FlxG.sound.music?.playing ?? false)) return;
|
||||
var timeToPlayAt:Float = Conductor.instance.songPosition - Conductor.instance.instrumentalOffset;
|
||||
|
||||
var timeToPlayAt:Float = Math.min(FlxG.sound.music.length, Math.max(0, Conductor.instance.songPosition - Conductor.instance.combinedOffset));
|
||||
trace('Resyncing vocals to ${timeToPlayAt}');
|
||||
FlxG.sound.music.pause();
|
||||
vocals.pause();
|
||||
|
||||
@@ -2373,9 +2477,9 @@ class PlayState extends MusicBeatSubState
|
||||
if (targetNote == null) continue;
|
||||
|
||||
// Judge and hit the note.
|
||||
trace('Hit note! ${targetNote.noteData}');
|
||||
// trace('Hit note! ${targetNote.noteData}');
|
||||
goodNoteHit(targetNote, input);
|
||||
trace('Score: ${songScore}');
|
||||
// trace('Score: ${songScore}');
|
||||
|
||||
notesInDirection.remove(targetNote);
|
||||
|
||||
@@ -2566,7 +2670,7 @@ class PlayState extends MusicBeatSubState
|
||||
*/
|
||||
function debugKeyShit():Void
|
||||
{
|
||||
#if FEATURE_CHART_EDITOR
|
||||
#if FEATURE_STAGE_EDITOR
|
||||
// Open the stage editor overlaying the current state.
|
||||
if (controls.DEBUG_STAGE)
|
||||
{
|
||||
@@ -2575,7 +2679,9 @@ class PlayState extends MusicBeatSubState
|
||||
persistentUpdate = false;
|
||||
openSubState(new StageOffsetSubState());
|
||||
}
|
||||
#end
|
||||
|
||||
#if FEATURE_CHART_EDITOR
|
||||
// Redirect to the chart editor playing the current song.
|
||||
if (controls.DEBUG_CHART)
|
||||
{
|
||||
@@ -2583,11 +2689,13 @@ class PlayState extends MusicBeatSubState
|
||||
persistentUpdate = false;
|
||||
if (isChartingMode)
|
||||
{
|
||||
// Close the playtest substate.
|
||||
FlxG.sound.music?.pause();
|
||||
this.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.remove(currentStage);
|
||||
FlxG.switchState(() -> new ChartEditorState(
|
||||
{
|
||||
targetSongId: currentSong.id,
|
||||
@@ -2937,6 +3045,7 @@ class PlayState extends MusicBeatSubState
|
||||
{
|
||||
targetVariation = targetSong.getFirstValidVariation(PlayStatePlaylist.campaignDifficulty) ?? Constants.DEFAULT_VARIATION;
|
||||
}
|
||||
this.remove(currentStage);
|
||||
LoadingState.loadPlayState(
|
||||
{
|
||||
targetSong: targetSong,
|
||||
@@ -2954,6 +3063,7 @@ class PlayState extends MusicBeatSubState
|
||||
{
|
||||
targetVariation = targetSong.getFirstValidVariation(PlayStatePlaylist.campaignDifficulty) ?? Constants.DEFAULT_VARIATION;
|
||||
}
|
||||
this.remove(currentStage);
|
||||
LoadingState.loadPlayState(
|
||||
{
|
||||
targetSong: targetSong,
|
||||
@@ -3177,7 +3287,7 @@ class PlayState extends MusicBeatSubState
|
||||
/**
|
||||
* Resets the camera's zoom level and focus point.
|
||||
*/
|
||||
public function resetCamera(?resetZoom:Bool = true, ?cancelTweens:Bool = true):Void
|
||||
public function resetCamera(?resetZoom:Bool = true, ?cancelTweens:Bool = true, ?snap:Bool = true):Void
|
||||
{
|
||||
// Cancel camera tweens if any are active.
|
||||
if (cancelTweens)
|
||||
@@ -3194,7 +3304,7 @@ class PlayState extends MusicBeatSubState
|
||||
}
|
||||
|
||||
// Snap the camera to the follow point immediately.
|
||||
FlxG.camera.focusOn(cameraFollowPoint.getPosition());
|
||||
if (snap) FlxG.camera.focusOn(cameraFollowPoint.getPosition());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -74,6 +74,8 @@ class ResultState extends MusicBeatSubState
|
||||
|
||||
var playerCharacterId:Null<String>;
|
||||
|
||||
var introMusicAudio:Null<FunkinSound>;
|
||||
|
||||
var rankBg:FunkinSprite;
|
||||
final cameraBG:FunkinCamera;
|
||||
final cameraScroll:FunkinCamera;
|
||||
@@ -413,7 +415,8 @@ class ResultState extends MusicBeatSubState
|
||||
if (Assets.exists(introMusic))
|
||||
{
|
||||
// Play the intro music.
|
||||
FunkinSound.load(introMusic, 1.0, false, true, true, () -> {
|
||||
introMusicAudio = FunkinSound.load(introMusic, 1.0, false, true, true, () -> {
|
||||
introMusicAudio = null;
|
||||
FunkinSound.playMusic(getMusicPath(playerCharacter, rank),
|
||||
{
|
||||
startingVolume: 1.0,
|
||||
@@ -472,9 +475,12 @@ class ResultState extends MusicBeatSubState
|
||||
{
|
||||
ease: FlxEase.quartOut,
|
||||
onUpdate: _ -> {
|
||||
clearPercentLerp = Math.round(clearPercentLerp);
|
||||
clearPercentCounter.curNumber = Math.round(clearPercentCounter.curNumber);
|
||||
// Only play the tick sound if the number increased.
|
||||
if (clearPercentLerp != clearPercentCounter.curNumber)
|
||||
{
|
||||
trace('$clearPercentLerp and ${clearPercentCounter.curNumber}');
|
||||
clearPercentLerp = clearPercentCounter.curNumber;
|
||||
FunkinSound.playOnce(Paths.sound('scrollMenu'));
|
||||
}
|
||||
@@ -724,9 +730,34 @@ class ResultState extends MusicBeatSubState
|
||||
|
||||
if (controls.PAUSE)
|
||||
{
|
||||
if (FlxG.sound.music != null)
|
||||
if (introMusicAudio != null) {
|
||||
@:nullSafety(Off)
|
||||
introMusicAudio.onComplete = null;
|
||||
|
||||
FlxTween.tween(introMusicAudio, {volume: 0}, 0.8, {
|
||||
onComplete: _ -> {
|
||||
if (introMusicAudio != null) {
|
||||
introMusicAudio.stop();
|
||||
introMusicAudio.destroy();
|
||||
introMusicAudio = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
FlxTween.tween(introMusicAudio, {pitch: 3}, 0.1,
|
||||
{
|
||||
onComplete: _ -> {
|
||||
FlxTween.tween(introMusicAudio, {pitch: 0.5}, 0.4);
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (FlxG.sound.music != null)
|
||||
{
|
||||
FlxTween.tween(FlxG.sound.music, {volume: 0}, 0.8);
|
||||
FlxTween.tween(FlxG.sound.music, {volume: 0}, 0.8, {
|
||||
onComplete: _ -> {
|
||||
FlxG.sound.music.stop();
|
||||
FlxG.sound.music.destroy();
|
||||
}
|
||||
});
|
||||
FlxTween.tween(FlxG.sound.music, {pitch: 3}, 0.1,
|
||||
{
|
||||
onComplete: _ -> {
|
||||
|
||||
@@ -147,7 +147,7 @@ class AnimateAtlasCharacter extends BaseCharacter
|
||||
|
||||
if (getAnimationData() != null && getAnimationData().looped)
|
||||
{
|
||||
playAnimation(prefix, true, false);
|
||||
playAnimation(currentAnimName, true, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -233,15 +233,9 @@ class BaseCharacter extends Bopper
|
||||
*/
|
||||
public function resetCharacter(resetCamera:Bool = true):Void
|
||||
{
|
||||
// Reset the animation offsets. This will modify x and y to be the absolute position of the character.
|
||||
// this.animOffsets = [0, 0];
|
||||
|
||||
// Now we can set the x and y to be their original values without having to account for animOffsets.
|
||||
// Set the x and y to be their original values.
|
||||
this.resetPosition();
|
||||
|
||||
// Then reapply animOffsets...
|
||||
// applyAnimationOffsets(getCurrentAnimation());
|
||||
|
||||
this.dance(true); // Force to avoid the old animation playing with the wrong offset at the start of the song.
|
||||
// Make sure we are playing the idle animation
|
||||
// ...then update the hitbox so that this.width and this.height are correct.
|
||||
@@ -305,7 +299,7 @@ class BaseCharacter extends Bopper
|
||||
{
|
||||
super.onAnimationFinished(animationName);
|
||||
|
||||
trace('${characterId} has finished animation: ${animationName}');
|
||||
// trace('${characterId} has finished animation: ${animationName}');
|
||||
if ((animationName.endsWith(Constants.ANIMATION_END_SUFFIX) && !animationName.startsWith('idle') && !animationName.startsWith('dance'))
|
||||
|| animationName.startsWith('combo')
|
||||
|| animationName.startsWith('drop'))
|
||||
@@ -323,6 +317,11 @@ class BaseCharacter extends Bopper
|
||||
this.cameraFocusPoint = new FlxPoint(charCenterX + _data.cameraOffsets[0], charCenterY + _data.cameraOffsets[1]);
|
||||
}
|
||||
|
||||
public function getHealthIconId():String
|
||||
{
|
||||
return _data?.healthIcon?.id ?? Constants.DEFAULT_HEALTH_ICON;
|
||||
}
|
||||
|
||||
public function initHealthIcon(isOpponent:Bool):Void
|
||||
{
|
||||
if (!isOpponent)
|
||||
@@ -332,7 +331,7 @@ class BaseCharacter extends Bopper
|
||||
trace('[WARN] Player 1 health icon not found!');
|
||||
return;
|
||||
}
|
||||
PlayState.instance.iconP1.configure(_data.healthIcon);
|
||||
PlayState.instance.iconP1.configure(_data?.healthIcon);
|
||||
PlayState.instance.iconP1.flipX = !PlayState.instance.iconP1.flipX; // BF is looking the other way.
|
||||
}
|
||||
else
|
||||
@@ -342,7 +341,7 @@ class BaseCharacter extends Bopper
|
||||
trace('[WARN] Player 2 health icon not found!');
|
||||
return;
|
||||
}
|
||||
PlayState.instance.iconP2.configure(_data.healthIcon);
|
||||
PlayState.instance.iconP2.configure(_data?.healthIcon);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,7 +401,7 @@ class BaseCharacter extends Bopper
|
||||
FlxG.watch.addQuick('singTimeSec-${characterId}', singTimeSec);
|
||||
if (holdTimer > singTimeSec && shouldStopSinging)
|
||||
{
|
||||
trace('holdTimer reached ${holdTimer}sec (> ${singTimeSec}), stopping sing animation');
|
||||
// trace('holdTimer reached ${holdTimer}sec (> ${singTimeSec}), stopping sing animation');
|
||||
holdTimer = 0;
|
||||
|
||||
var currentAnimation:String = getCurrentAnimation();
|
||||
@@ -636,7 +635,7 @@ class BaseCharacter extends Bopper
|
||||
var anim:String = 'sing${dir.nameUpper}${miss ? 'miss' : ''}${suffix != '' ? '-${suffix}' : ''}';
|
||||
|
||||
// restart even if already playing, because the character might sing the same note twice.
|
||||
trace('Playing ${anim}...');
|
||||
// trace('Playing ${anim}...');
|
||||
playAnimation(anim, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import funkin.play.character.ScriptedCharacter.ScriptedSparrowCharacter;
|
||||
import funkin.util.assets.DataAssets;
|
||||
import funkin.util.VersionUtil;
|
||||
import haxe.Json;
|
||||
import openfl.utils.Assets;
|
||||
import flixel.graphics.frames.FlxFrame;
|
||||
|
||||
class CharacterDataParser
|
||||
{
|
||||
@@ -281,41 +281,78 @@ class CharacterDataParser
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Hardcode this.
|
||||
* Returns the idle frame of a character.
|
||||
*/
|
||||
public static function getCharPixelIconAsset(char:String):String
|
||||
public static function getCharPixelIconAsset(char:String):FlxFrame
|
||||
{
|
||||
var icon:String = char;
|
||||
var charPath:String = "freeplay/icons/";
|
||||
|
||||
switch (icon)
|
||||
// FunkinCrew please dont skin me alive for copying pixelated icon and changing it a tiny bit
|
||||
switch (char)
|
||||
{
|
||||
case "bf-christmas" | "bf-car" | "bf-pixel" | "bf-holding-gf":
|
||||
icon = "bf";
|
||||
case "bf-christmas" | "bf-car" | "bf-pixel" | "bf-holding-gf" | "bf-dark":
|
||||
charPath += "bfpixel";
|
||||
case "monster-christmas":
|
||||
icon = "monster";
|
||||
charPath += "monsterpixel";
|
||||
case "mom" | "mom-car":
|
||||
icon = "mommy";
|
||||
charPath += "mommypixel";
|
||||
case "pico-blazin" | "pico-playable" | "pico-speaker":
|
||||
icon = "pico";
|
||||
case "gf-christmas" | "gf-car" | "gf-pixel" | "gf-tankmen":
|
||||
icon = "gf";
|
||||
charPath += "picopixel";
|
||||
case "gf-christmas" | "gf-car" | "gf-pixel" | "gf-tankmen" | "gf-dark":
|
||||
charPath += "gfpixel";
|
||||
case "dad":
|
||||
icon = "daddy";
|
||||
charPath += "dadpixel";
|
||||
case "darnell-blazin":
|
||||
icon = "darnell";
|
||||
charPath += "darnellpixel";
|
||||
case "senpai-angry":
|
||||
icon = "senpai";
|
||||
charPath += "senpaipixel";
|
||||
case "spooky-dark":
|
||||
icon = "spooky";
|
||||
charPath += "spookypixel";
|
||||
case "tankman-atlas":
|
||||
icon = "tankman";
|
||||
charPath += "tankmanpixel";
|
||||
case "pico-christmas" | "pico-dark":
|
||||
charPath += "picopixel";
|
||||
default:
|
||||
charPath += '${char}pixel';
|
||||
}
|
||||
|
||||
var path = Paths.image("freeplay/icons/" + icon + "pixel");
|
||||
if (Assets.exists(path)) return path;
|
||||
if (!Assets.exists(Paths.image(charPath)))
|
||||
{
|
||||
trace('[WARN] Character ${char} has no freeplay icon.');
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: Hardcode some additional behavior or a fallback.
|
||||
return null;
|
||||
var isAnimated = Assets.exists(Paths.file('images/$charPath.xml'));
|
||||
var frame:FlxFrame = null;
|
||||
|
||||
if (isAnimated)
|
||||
{
|
||||
var frames = Paths.getSparrowAtlas(charPath);
|
||||
|
||||
var idleFrame:FlxFrame = frames.frames.find(function(frame:FlxFrame):Bool {
|
||||
return frame.name.startsWith('idle');
|
||||
});
|
||||
|
||||
if (idleFrame == null)
|
||||
{
|
||||
trace('[WARN] Character ${char} has no idle in their freeplay icon.');
|
||||
return null;
|
||||
}
|
||||
|
||||
// so, haxe.ui.backend.AssetsImpl uses the parent width and height, which makes the image go crazy when rendered
|
||||
// so this is a work around so that it uses the actual width and height
|
||||
var imageGraphic = flixel.graphics.FlxGraphic.fromFrame(idleFrame);
|
||||
|
||||
var imageFrame = flixel.graphics.frames.FlxImageFrame.fromImage(imageGraphic);
|
||||
frame = imageFrame.frame;
|
||||
}
|
||||
else
|
||||
{
|
||||
var imageFrame = flixel.graphics.frames.FlxImageFrame.fromImage(Paths.image(charPath));
|
||||
frame = imageFrame.frame;
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,7 +5,6 @@ import flixel.FlxSprite;
|
||||
import flixel.math.FlxMath;
|
||||
import flixel.math.FlxPoint;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import openfl.utils.Assets;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.util.MathUtil;
|
||||
|
||||
@@ -33,7 +32,7 @@ class HealthIcon extends FunkinSprite
|
||||
* The character this icon is representing.
|
||||
* Setting this variable will automatically update the graphic.
|
||||
*/
|
||||
public var characterId(default, set):Null<String>;
|
||||
public var characterId(default, set):String = Constants.DEFAULT_HEALTH_ICON;
|
||||
|
||||
/**
|
||||
* Whether this health icon should automatically update its state based on the character's health.
|
||||
@@ -116,7 +115,7 @@ class HealthIcon extends FunkinSprite
|
||||
*/
|
||||
static final POSITION_OFFSET:Int = 26;
|
||||
|
||||
public function new(char:String = 'bf', playerId:Int = 0)
|
||||
public function new(char:Null<String>, playerId:Int = 0)
|
||||
{
|
||||
super(0, 0);
|
||||
this.playerId = playerId;
|
||||
@@ -131,7 +130,7 @@ class HealthIcon extends FunkinSprite
|
||||
snapToTargetSize();
|
||||
}
|
||||
|
||||
function set_characterId(value:Null<String>):Null<String>
|
||||
function set_characterId(value:Null<String>):String
|
||||
{
|
||||
if (value == characterId) return value;
|
||||
|
||||
@@ -412,20 +411,9 @@ class HealthIcon extends FunkinSprite
|
||||
}
|
||||
}
|
||||
|
||||
function correctCharacterId(charId:Null<String>):String
|
||||
function iconExists(charId:String):Bool
|
||||
{
|
||||
if (charId == null)
|
||||
{
|
||||
return Constants.DEFAULT_HEALTH_ICON;
|
||||
}
|
||||
|
||||
if (!Assets.exists(Paths.image('icons/icon-$charId')))
|
||||
{
|
||||
FlxG.log.warn('No icon for character: $charId : using default placeholder face instead!');
|
||||
return Constants.DEFAULT_HEALTH_ICON;
|
||||
}
|
||||
|
||||
return charId;
|
||||
return Assets.exists(Paths.image('icons/icon-$charId'));
|
||||
}
|
||||
|
||||
function isNewSpritesheet(charId:String):Bool
|
||||
@@ -435,11 +423,11 @@ class HealthIcon extends FunkinSprite
|
||||
|
||||
function loadCharacter(charId:Null<String>):Void
|
||||
{
|
||||
if (charId == null || correctCharacterId(charId) != charId)
|
||||
if (charId == null || !iconExists(charId))
|
||||
{
|
||||
// This will recursively trigger loadCharacter to be called again.
|
||||
characterId = correctCharacterId(charId);
|
||||
return;
|
||||
FlxG.log.warn('No icon for character: $charId : using default placeholder face instead!');
|
||||
characterId = Constants.DEFAULT_HEALTH_ICON;
|
||||
charId = characterId;
|
||||
}
|
||||
|
||||
isLegacyStyle = !isNewSpritesheet(charId);
|
||||
|
||||
@@ -8,7 +8,6 @@ import funkin.graphics.FunkinSprite;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.util.TimerUtil;
|
||||
import funkin.util.EaseUtil;
|
||||
import openfl.utils.Assets;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
|
||||
@@ -95,7 +94,6 @@ class PopUpStuff extends FlxTypedGroup<FunkinSprite>
|
||||
if (numScore == null) continue;
|
||||
|
||||
numScore.x = (FlxG.width * 0.507) - (36 * daLoop) - 65;
|
||||
trace('numScore($daLoop) = ${numScore.x}');
|
||||
numScore.y = (FlxG.camera.height * 0.44);
|
||||
|
||||
numScore.x += offsets[0];
|
||||
|
||||
@@ -11,7 +11,7 @@ import flixel.util.FlxTimer;
|
||||
import funkin.graphics.video.FlxVideo;
|
||||
#end
|
||||
#if hxCodec
|
||||
import hxcodec.flixel.FlxVideoSprite;
|
||||
import funkin.graphics.video.FunkinVideoSprite;
|
||||
#end
|
||||
|
||||
/**
|
||||
@@ -26,7 +26,7 @@ class VideoCutscene
|
||||
static var vid:FlxVideo;
|
||||
#end
|
||||
#if hxCodec
|
||||
static var vid:FlxVideoSprite;
|
||||
static var vid:FunkinVideoSprite;
|
||||
#end
|
||||
|
||||
/**
|
||||
@@ -138,7 +138,7 @@ class VideoCutscene
|
||||
static function playVideoNative(filePath:String):Void
|
||||
{
|
||||
// Video displays OVER the FlxState.
|
||||
vid = new FlxVideoSprite(0, 0);
|
||||
vid = new FunkinVideoSprite(0, 0);
|
||||
|
||||
if (vid != null)
|
||||
{
|
||||
|
||||
@@ -127,7 +127,8 @@ class FocusCameraSongEvent extends SongEvent
|
||||
switch (ease)
|
||||
{
|
||||
case 'CLASSIC': // Old-school. No ease. Just set follow point.
|
||||
PlayState.instance.resetCamera(false, true);
|
||||
PlayState.instance.resetCamera(false, false, false);
|
||||
PlayState.instance.cancelCameraFollowTween();
|
||||
PlayState.instance.cameraFollowPoint.setPosition(targetX, targetY);
|
||||
case 'INSTANT': // Instant ease. Duration is automatically 0.
|
||||
PlayState.instance.tweenCameraToPosition(targetX, targetY, 0);
|
||||
|
||||
@@ -85,7 +85,8 @@ class StrumlineNote extends FlxSprite
|
||||
noteStyle.applyStrumlineFrames(this);
|
||||
noteStyle.applyStrumlineAnimations(this, this.direction);
|
||||
|
||||
this.setGraphicSize(Std.int(Strumline.STRUMLINE_SIZE * noteStyle.getStrumlineScale()));
|
||||
var scale = noteStyle.getStrumlineScale();
|
||||
this.scale.set(scale, scale);
|
||||
this.updateHitbox();
|
||||
noteStyle.applyStrumlineOffsets(this);
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@ class SustainTrail extends FlxSprite
|
||||
public var bottomClip:Float = 0.9;
|
||||
|
||||
public var isPixel:Bool;
|
||||
public var noteStyleOffsets:Array<Float>;
|
||||
|
||||
var graphicWidth:Float = 0;
|
||||
var graphicHeight:Float = 0;
|
||||
@@ -107,6 +108,7 @@ class SustainTrail extends FlxSprite
|
||||
this.noteDirection = noteDirection;
|
||||
|
||||
setupHoldNoteGraphic(noteStyle);
|
||||
noteStyleOffsets = noteStyle.getHoldNoteOffsets();
|
||||
|
||||
indices = new DrawData<Int>(12, true, TRIANGLE_VERTEX_INDICES);
|
||||
|
||||
@@ -137,7 +139,6 @@ class SustainTrail extends FlxSprite
|
||||
|
||||
zoom = 1.0;
|
||||
zoom *= noteStyle.fetchHoldNoteScale();
|
||||
zoom *= 0.7;
|
||||
|
||||
// CALCULATE SIZE
|
||||
graphicWidth = graphic.width / 8 * zoom; // amount of notes * 2
|
||||
@@ -202,7 +203,7 @@ class SustainTrail extends FlxSprite
|
||||
{
|
||||
width = graphicWidth;
|
||||
height = graphicHeight;
|
||||
offset.set(0, 0);
|
||||
offset.set(noteStyleOffsets[0], noteStyleOffsets[1]);
|
||||
origin.set(width * 0.5, height * 0.5);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,12 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
* The note style to use if this one doesn't have a certain asset.
|
||||
* This can be recursive, ehe.
|
||||
*/
|
||||
final fallback:Null<NoteStyle>;
|
||||
var fallback(get, never):Null<NoteStyle>;
|
||||
|
||||
function get_fallback():Null<NoteStyle> {
|
||||
if (_data == null || _data.fallback == null) return null;
|
||||
return NoteStyleRegistry.instance.fetchEntry(_data.fallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param id The ID of the JSON file to parse.
|
||||
@@ -43,9 +48,6 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
{
|
||||
this.id = id;
|
||||
_data = _fetchData(id);
|
||||
|
||||
var fallbackID = _data.fallback;
|
||||
if (fallbackID != null) this.fallback = NoteStyleRegistry.instance.fetchEntry(fallbackID);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,7 +95,8 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
buildNoteAnimations(target);
|
||||
|
||||
// Set the scale.
|
||||
target.setGraphicSize(Strumline.STRUMLINE_SIZE * getNoteScale());
|
||||
var scale = getNoteScale();
|
||||
target.scale.set(scale, scale);
|
||||
target.updateHitbox();
|
||||
}
|
||||
|
||||
@@ -102,7 +105,11 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
function buildNoteFrames(force:Bool = false):Null<FlxAtlasFrames>
|
||||
{
|
||||
var noteAssetPath = getNoteAssetPath();
|
||||
if (noteAssetPath == null) return null;
|
||||
if (noteAssetPath == null)
|
||||
{
|
||||
FlxG.log.warn('Note asset path not found: ${id}');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!FunkinSprite.isTextureCached(Paths.image(noteAssetPath)))
|
||||
{
|
||||
@@ -135,7 +142,7 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
if (raw)
|
||||
{
|
||||
var rawPath:Null<String> = _data?.assets?.note?.assetPath;
|
||||
if (rawPath == null && fallback != null) return fallback.getNoteAssetPath(true);
|
||||
if (rawPath == null) return fallback?.getNoteAssetPath(true);
|
||||
return rawPath;
|
||||
}
|
||||
|
||||
@@ -173,12 +180,13 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
|
||||
public function isNoteAnimated():Bool
|
||||
{
|
||||
return _data.assets?.note?.animated ?? false;
|
||||
// LOL is double ?? bad practice?
|
||||
return _data.assets?.note?.animated ?? fallback?.isNoteAnimated() ?? false;
|
||||
}
|
||||
|
||||
public function getNoteScale():Float
|
||||
{
|
||||
return _data.assets?.note?.scale ?? 1.0;
|
||||
return _data.assets?.note?.scale ?? fallback?.getNoteScale() ?? 1.0;
|
||||
}
|
||||
|
||||
function fetchNoteAnimationData(dir:NoteDirection):Null<AnimationData>
|
||||
@@ -191,16 +199,15 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
case RIGHT: _data.assets?.note?.data?.right?.toNamed();
|
||||
};
|
||||
|
||||
return (result == null && fallback != null) ? fallback.fetchNoteAnimationData(dir) : result;
|
||||
return result ?? fallback?.fetchNoteAnimationData(dir);
|
||||
}
|
||||
|
||||
public function getHoldNoteAssetPath(raw:Bool = false):Null<String>
|
||||
{
|
||||
if (raw)
|
||||
{
|
||||
// TODO: figure out why ?. didn't work here
|
||||
var rawPath:Null<String> = (_data?.assets?.holdNote == null) ? null : _data?.assets?.holdNote?.assetPath;
|
||||
return (rawPath == null && fallback != null) ? fallback.getHoldNoteAssetPath(true) : rawPath;
|
||||
var rawPath:Null<String> = _data?.assets?.holdNote?.assetPath;
|
||||
return rawPath ?? fallback?.getHoldNoteAssetPath(true);
|
||||
}
|
||||
|
||||
// library:path
|
||||
@@ -212,16 +219,17 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
|
||||
public function isHoldNotePixel():Bool
|
||||
{
|
||||
var data = _data?.assets?.holdNote;
|
||||
if (data == null && fallback != null) return fallback.isHoldNotePixel();
|
||||
return data?.isPixel ?? false;
|
||||
return _data?.assets?.holdNote?.isPixel ?? fallback?.isHoldNotePixel() ?? false;
|
||||
}
|
||||
|
||||
public function fetchHoldNoteScale():Float
|
||||
{
|
||||
var data = _data?.assets?.holdNote;
|
||||
if (data == null && fallback != null) return fallback.fetchHoldNoteScale();
|
||||
return data?.scale ?? 1.0;
|
||||
return _data?.assets?.holdNote?.scale ?? fallback?.fetchHoldNoteScale() ?? 1.0;
|
||||
}
|
||||
|
||||
public function getHoldNoteOffsets():Array<Float>
|
||||
{
|
||||
return _data?.assets?.holdNote?.offsets ?? fallback?.getHoldNoteOffsets() ?? [0.0, 0.0];
|
||||
}
|
||||
|
||||
public function applyStrumlineFrames(target:StrumlineNote):Void
|
||||
@@ -246,9 +254,7 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
{
|
||||
if (raw)
|
||||
{
|
||||
var rawPath:Null<String> = _data?.assets?.noteStrumline?.assetPath;
|
||||
if (rawPath == null && fallback != null) return fallback.getStrumlineAssetPath(true);
|
||||
return rawPath;
|
||||
return _data?.assets?.noteStrumline?.assetPath ?? fallback?.getStrumlineAssetPath(true);
|
||||
}
|
||||
|
||||
// library:path
|
||||
@@ -270,11 +276,19 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
FlxAnimationUtil.addAtlasAnimations(target, getStrumlineAnimationData(dir));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the animation data for the strumline.
|
||||
* NOTE: This function only queries the fallback note style if all the animations are missing for a given direction.
|
||||
*
|
||||
* @param dir The direction to fetch the animation data for.
|
||||
* @return The animation data for the strumline in that direction.
|
||||
*/
|
||||
function getStrumlineAnimationData(dir:NoteDirection):Array<AnimationData>
|
||||
{
|
||||
var result:Array<Null<AnimationData>> = switch (dir)
|
||||
{
|
||||
case NoteDirection.LEFT: [
|
||||
case NoteDirection.LEFT:
|
||||
[
|
||||
_data.assets.noteStrumline?.data?.leftStatic?.toNamed('static'),
|
||||
_data.assets.noteStrumline?.data?.leftPress?.toNamed('press'),
|
||||
_data.assets.noteStrumline?.data?.leftConfirm?.toNamed('confirm'),
|
||||
@@ -301,33 +315,39 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
default: [];
|
||||
};
|
||||
|
||||
return thx.Arrays.filterNull(result);
|
||||
// New variable so we can change the type.
|
||||
var filteredResult:Array<AnimationData> = thx.Arrays.filterNull(result);
|
||||
|
||||
if (filteredResult.length == 0) return fallback?.getStrumlineAnimationData(dir) ?? [];
|
||||
|
||||
return filteredResult;
|
||||
}
|
||||
|
||||
public function getStrumlineOffsets():Array<Float>
|
||||
{
|
||||
return _data?.assets?.noteStrumline?.offsets ?? fallback?.getStrumlineOffsets() ?? [0.0, 0.0];
|
||||
}
|
||||
|
||||
public function applyStrumlineOffsets(target:StrumlineNote):Void
|
||||
{
|
||||
var offsets = _data?.assets?.noteStrumline?.offsets ?? [0.0, 0.0];
|
||||
var offsets = getStrumlineOffsets();
|
||||
target.x += offsets[0];
|
||||
target.y += offsets[1];
|
||||
}
|
||||
|
||||
public function getStrumlineScale():Float
|
||||
{
|
||||
return _data?.assets?.noteStrumline?.scale ?? 1.0;
|
||||
return _data?.assets?.noteStrumline?.scale ?? fallback?.getStrumlineScale() ?? 1.0;
|
||||
}
|
||||
|
||||
public function isNoteSplashEnabled():Bool
|
||||
{
|
||||
var data = _data?.assets?.noteSplash?.data;
|
||||
if (data == null) return fallback?.isNoteSplashEnabled() ?? false;
|
||||
return data.enabled ?? false;
|
||||
return _data?.assets?.noteSplash?.data?.enabled ?? fallback?.isNoteSplashEnabled() ?? false;
|
||||
}
|
||||
|
||||
public function isHoldNoteCoverEnabled():Bool
|
||||
{
|
||||
var data = _data?.assets?.holdNoteCover?.data;
|
||||
if (data == null) return fallback?.isHoldNoteCoverEnabled() ?? false;
|
||||
return data.enabled ?? false;
|
||||
return _data?.assets?.holdNoteCover?.data?.enabled ?? fallback?.isHoldNoteCoverEnabled() ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -438,20 +458,20 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
{
|
||||
case THREE:
|
||||
var result = _data.assets.countdownThree?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isCountdownSpritePixel(step);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isCountdownSpritePixel(step) ?? false;
|
||||
return result;
|
||||
case TWO:
|
||||
var result = _data.assets.countdownTwo?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isCountdownSpritePixel(step);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isCountdownSpritePixel(step) ?? false;
|
||||
return result;
|
||||
case ONE:
|
||||
var result = _data.assets.countdownOne?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isCountdownSpritePixel(step);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isCountdownSpritePixel(step) ?? false;
|
||||
return result;
|
||||
case GO:
|
||||
var result = _data.assets.countdownGo?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isCountdownSpritePixel(step);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isCountdownSpritePixel(step) ?? false;
|
||||
return result;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -463,20 +483,20 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
{
|
||||
case THREE:
|
||||
var result = _data.assets.countdownThree?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getCountdownSpriteOffsets(step);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getCountdownSpriteOffsets(step) ?? [0, 0];
|
||||
return result;
|
||||
case TWO:
|
||||
var result = _data.assets.countdownTwo?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getCountdownSpriteOffsets(step);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getCountdownSpriteOffsets(step) ?? [0, 0];
|
||||
return result;
|
||||
case ONE:
|
||||
var result = _data.assets.countdownOne?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getCountdownSpriteOffsets(step);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getCountdownSpriteOffsets(step) ?? [0, 0];
|
||||
return result;
|
||||
case GO:
|
||||
var result = _data.assets.countdownGo?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getCountdownSpriteOffsets(step);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getCountdownSpriteOffsets(step) ?? [0, 0];
|
||||
return result;
|
||||
default:
|
||||
return [0, 0];
|
||||
}
|
||||
@@ -501,7 +521,7 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
null;
|
||||
}
|
||||
|
||||
return (rawPath == null && fallback != null) ? fallback.getCountdownSoundPath(step, true) : rawPath;
|
||||
return (rawPath == null) ? fallback?.getCountdownSoundPath(step, true) : rawPath;
|
||||
}
|
||||
|
||||
// library:path
|
||||
@@ -565,20 +585,20 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
{
|
||||
case "sick":
|
||||
var result = _data.assets.judgementSick?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isJudgementSpritePixel(rating);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isJudgementSpritePixel(rating) ?? false;
|
||||
return result;
|
||||
case "good":
|
||||
var result = _data.assets.judgementGood?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isJudgementSpritePixel(rating);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isJudgementSpritePixel(rating) ?? false;
|
||||
return result;
|
||||
case "bad":
|
||||
var result = _data.assets.judgementBad?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isJudgementSpritePixel(rating);
|
||||
return result ?? false;
|
||||
case "GO":
|
||||
if (result == null) result = fallback?.isJudgementSpritePixel(rating) ?? false;
|
||||
return result;
|
||||
case "shit":
|
||||
var result = _data.assets.judgementShit?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isJudgementSpritePixel(rating);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isJudgementSpritePixel(rating) ?? false;
|
||||
return result;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -616,20 +636,20 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
{
|
||||
case "sick":
|
||||
var result = _data.assets.judgementSick?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getJudgementSpriteOffsets(rating);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getJudgementSpriteOffsets(rating) ?? [0, 0];
|
||||
return result;
|
||||
case "good":
|
||||
var result = _data.assets.judgementGood?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getJudgementSpriteOffsets(rating);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getJudgementSpriteOffsets(rating) ?? [0, 0];
|
||||
return result;
|
||||
case "bad":
|
||||
var result = _data.assets.judgementBad?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getJudgementSpriteOffsets(rating);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getJudgementSpriteOffsets(rating) ?? [0, 0];
|
||||
return result;
|
||||
case "shit":
|
||||
var result = _data.assets.judgementShit?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getJudgementSpriteOffsets(rating);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getJudgementSpriteOffsets(rating) ?? [0, 0];
|
||||
return result;
|
||||
default:
|
||||
return [0, 0];
|
||||
}
|
||||
@@ -730,44 +750,44 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
{
|
||||
case 0:
|
||||
var result = _data.assets.comboNumber0?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isComboNumSpritePixel(digit) ?? false;
|
||||
return result;
|
||||
case 1:
|
||||
var result = _data.assets.comboNumber1?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isComboNumSpritePixel(digit) ?? false;
|
||||
return result;
|
||||
case 2:
|
||||
var result = _data.assets.comboNumber2?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isComboNumSpritePixel(digit) ?? false;
|
||||
return result;
|
||||
case 3:
|
||||
var result = _data.assets.comboNumber3?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isComboNumSpritePixel(digit) ?? false;
|
||||
return result;
|
||||
case 4:
|
||||
var result = _data.assets.comboNumber4?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isComboNumSpritePixel(digit) ?? false;
|
||||
return result;
|
||||
case 5:
|
||||
var result = _data.assets.comboNumber5?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isComboNumSpritePixel(digit) ?? false;
|
||||
return result;
|
||||
case 6:
|
||||
var result = _data.assets.comboNumber6?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isComboNumSpritePixel(digit) ?? false;
|
||||
return result;
|
||||
case 7:
|
||||
var result = _data.assets.comboNumber7?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isComboNumSpritePixel(digit) ?? false;
|
||||
return result;
|
||||
case 8:
|
||||
var result = _data.assets.comboNumber8?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isComboNumSpritePixel(digit) ?? false;
|
||||
return result;
|
||||
case 9:
|
||||
var result = _data.assets.comboNumber9?.isPixel;
|
||||
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||
return result ?? false;
|
||||
if (result == null) result = fallback?.isComboNumSpritePixel(digit) ?? false;
|
||||
return result;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -817,44 +837,44 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
{
|
||||
case 0:
|
||||
var result = _data.assets.comboNumber0?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getComboNumSpriteOffsets(digit) ?? [0, 0];
|
||||
return result;
|
||||
case 1:
|
||||
var result = _data.assets.comboNumber1?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getComboNumSpriteOffsets(digit) ?? [0, 0];
|
||||
return result;
|
||||
case 2:
|
||||
var result = _data.assets.comboNumber2?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getComboNumSpriteOffsets(digit) ?? [0, 0];
|
||||
return result;
|
||||
case 3:
|
||||
var result = _data.assets.comboNumber3?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getComboNumSpriteOffsets(digit) ?? [0, 0];
|
||||
return result;
|
||||
case 4:
|
||||
var result = _data.assets.comboNumber4?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getComboNumSpriteOffsets(digit) ?? [0, 0];
|
||||
return result;
|
||||
case 5:
|
||||
var result = _data.assets.comboNumber5?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getComboNumSpriteOffsets(digit) ?? [0, 0];
|
||||
return result;
|
||||
case 6:
|
||||
var result = _data.assets.comboNumber6?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getComboNumSpriteOffsets(digit) ?? [0, 0];
|
||||
return result;
|
||||
case 7:
|
||||
var result = _data.assets.comboNumber7?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getComboNumSpriteOffsets(digit) ?? [0, 0];
|
||||
return result;
|
||||
case 8:
|
||||
var result = _data.assets.comboNumber8?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getComboNumSpriteOffsets(digit) ?? [0, 0];
|
||||
return result;
|
||||
case 9:
|
||||
var result = _data.assets.comboNumber9?.offsets;
|
||||
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||
return result ?? [0, 0];
|
||||
if (result == null) result = fallback?.getComboNumSpriteOffsets(digit) ?? [0, 0];
|
||||
return result;
|
||||
default:
|
||||
return [0, 0];
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import funkin.modding.IScriptedClass.IPlayStateScriptedClass;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.ui.freeplay.charselect.PlayableCharacter;
|
||||
import funkin.util.SortUtil;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
/**
|
||||
* This is a data structure managing information about the current song.
|
||||
@@ -157,6 +156,11 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
{
|
||||
for (vari in _data.playData.songVariations)
|
||||
{
|
||||
if (!validateVariationId(vari)) {
|
||||
trace(' [WARN] Variation id "$vari" is invalid, skipping...');
|
||||
continue;
|
||||
}
|
||||
|
||||
var variMeta:Null<SongMetadata> = fetchVariationMetadata(id, vari);
|
||||
if (variMeta != null)
|
||||
{
|
||||
@@ -408,7 +412,6 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
if (possibleVariations == null)
|
||||
{
|
||||
possibleVariations = getVariationsByCharacter(currentCharacter);
|
||||
possibleVariations.sort(SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_VARIATION_LIST));
|
||||
}
|
||||
if (diffId == null) diffId = listDifficulties(null, possibleVariations)[0];
|
||||
|
||||
@@ -429,7 +432,12 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
*/
|
||||
public function getVariationsByCharacter(?char:PlayableCharacter):Array<String>
|
||||
{
|
||||
if (char == null) return variations;
|
||||
if (char == null)
|
||||
{
|
||||
var result = variations;
|
||||
result.sort(SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_VARIATION_LIST));
|
||||
return result;
|
||||
}
|
||||
|
||||
var result = [];
|
||||
trace('Evaluating variations for ${this.id} ${char.id}: ${this.variations}');
|
||||
@@ -446,6 +454,8 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
}
|
||||
}
|
||||
|
||||
result.sort(SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_VARIATION_LIST));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -466,18 +476,11 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
|
||||
if (variationIds.length == 0) return [];
|
||||
|
||||
// The difficulties array contains entries like 'normal', 'nightmare-erect', and 'normal-pico',
|
||||
// so we have to map it to the actual difficulty names.
|
||||
// We also filter out difficulties that don't match the variation or that don't exist.
|
||||
|
||||
var diffFiltered:Array<String> = difficulties.keys()
|
||||
.array()
|
||||
.map(function(diffId:String):Null<String> {
|
||||
var difficulty:Null<SongDifficulty> = difficulties.get(diffId);
|
||||
if (difficulty == null) return null;
|
||||
if (variationIds.length > 0 && !variationIds.contains(difficulty.variation)) return null;
|
||||
return difficulty.difficulty;
|
||||
})
|
||||
var diffFiltered:Array<String> = variationIds.map(function(variationId:String):Array<String> {
|
||||
var metadata = _metadata.get(variationId);
|
||||
return metadata?.playData?.difficulties ?? [];
|
||||
})
|
||||
.flatten()
|
||||
.filterNull()
|
||||
.distinct();
|
||||
|
||||
@@ -490,11 +493,15 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
return false;
|
||||
});
|
||||
|
||||
diffFiltered.sort(SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_DIFFICULTY_LIST));
|
||||
diffFiltered.sort(SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_DIFFICULTY_LIST_FULL));
|
||||
|
||||
return diffFiltered;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: This line of code makes me sad, but you can't really fix it without a breaking migration.
|
||||
* @return `easy`, `erect`, `normal-pico`, etc.
|
||||
*/
|
||||
public function listSuffixedDifficulties(variationIds:Array<String>, ?showLocked:Bool, ?showHidden:Bool):Array<String>
|
||||
{
|
||||
var result = [];
|
||||
@@ -510,6 +517,8 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
}
|
||||
}
|
||||
|
||||
result.sort(SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_DIFFICULTY_LIST_FULL));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -630,6 +639,19 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
var meta:Null<SongMetadata> = SongRegistry.instance.parseEntryMetadataWithMigration(id, vari, version);
|
||||
return meta;
|
||||
}
|
||||
|
||||
static final VARIATION_REGEX = ~/^[a-z][a-z0-9]+$/;
|
||||
|
||||
/**
|
||||
* Validate that the variation ID is valid.
|
||||
* Auto-accept if it's one of the base game default variations.
|
||||
* Reject if the ID starts with a number, or contains invalid characters.
|
||||
*/
|
||||
static function validateVariationId(variation:String):Bool {
|
||||
if (Constants.DEFAULT_VARIATION_LIST.contains(variation)) return true;
|
||||
|
||||
return VARIATION_REGEX.match(variation);
|
||||
}
|
||||
}
|
||||
|
||||
class SongDifficulty
|
||||
|
||||
@@ -150,11 +150,8 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
|
||||
*/
|
||||
public function resetPosition()
|
||||
{
|
||||
var oldAnimOffsets = [animOffsets[0], animOffsets[1]];
|
||||
animOffsets = [0, 0];
|
||||
this.x = originalPosition.x;
|
||||
this.y = originalPosition.y;
|
||||
animOffsets = oldAnimOffsets;
|
||||
}
|
||||
|
||||
function update_shouldAlternate():Void
|
||||
|
||||
@@ -258,6 +258,9 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
|
||||
|
||||
propSprite.zIndex = dataProp.zIndex;
|
||||
|
||||
propSprite.flipX = dataProp.flipX;
|
||||
propSprite.flipY = dataProp.flipY;
|
||||
|
||||
switch (dataProp.animType)
|
||||
{
|
||||
case 'packer':
|
||||
@@ -440,12 +443,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
|
||||
character.x = stageCharData.position[0] - character.characterOrigin.x;
|
||||
character.y = stageCharData.position[1] - character.characterOrigin.y;
|
||||
|
||||
@:privateAccess(funkin.play.stage.Bopper)
|
||||
{
|
||||
// Undo animOffsets before saving original position.
|
||||
character.originalPosition.x = character.x + character.animOffsets[0];
|
||||
character.originalPosition.y = character.y + character.animOffsets[1];
|
||||
}
|
||||
character.originalPosition.set(character.x, character.y);
|
||||
|
||||
var finalScale = character.getBaseScale() * stageCharData.scale;
|
||||
character.setScale(finalScale); // Don't use scale.set for characters!
|
||||
@@ -464,6 +462,9 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
|
||||
#end
|
||||
}
|
||||
|
||||
// Set the characters type
|
||||
character.characterType = charType;
|
||||
|
||||
// Add the character to the scene.
|
||||
this.add(character);
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import thx.semver.Version;
|
||||
@:nullSafety
|
||||
class Save
|
||||
{
|
||||
public static final SAVE_DATA_VERSION:thx.semver.Version = "2.0.5";
|
||||
public static final SAVE_DATA_VERSION:thx.semver.Version = "2.0.4";
|
||||
public static final SAVE_DATA_VERSION_RULE:thx.semver.VersionRule = "2.0.x";
|
||||
|
||||
// We load this version's saves from a new save path, to maintain SOME level of backwards compatibility.
|
||||
@@ -34,19 +34,19 @@ class Save
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = new Save(FlxG.save.data);
|
||||
return _instance = load();
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
|
||||
var data:RawSaveData;
|
||||
|
||||
public static function load():Void
|
||||
public static function load():Save
|
||||
{
|
||||
trace("[SAVE] Loading save...");
|
||||
|
||||
// Bind save data.
|
||||
loadFromSlot(1);
|
||||
return loadFromSlot(1);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,7 +65,9 @@ class Save
|
||||
public static function getDefault():RawSaveData
|
||||
{
|
||||
return {
|
||||
version: Save.SAVE_DATA_VERSION,
|
||||
// Version number is an abstract(Array) internally.
|
||||
// This means it copies by reference, so merging save data overides the version number lol.
|
||||
version: thx.Dynamics.clone(Save.SAVE_DATA_VERSION),
|
||||
|
||||
volume: 1.0,
|
||||
mute: false,
|
||||
@@ -89,6 +91,7 @@ class Save
|
||||
options:
|
||||
{
|
||||
// Reasonable defaults.
|
||||
framerate: 60,
|
||||
naughtyness: true,
|
||||
downscroll: false,
|
||||
flashingLights: true,
|
||||
@@ -433,7 +436,9 @@ class Save
|
||||
{
|
||||
if (!data.unlocks.charactersSeen.contains(character))
|
||||
{
|
||||
trace('Character seen: ' + character);
|
||||
data.unlocks.charactersSeen.push(character);
|
||||
trace('New characters seen list: ' + data.unlocks.charactersSeen);
|
||||
flush();
|
||||
}
|
||||
}
|
||||
@@ -598,11 +603,14 @@ class Save
|
||||
return;
|
||||
}
|
||||
|
||||
var newCompletion = (newScoreData.tallies.sick + newScoreData.tallies.good) / newScoreData.tallies.totalNotes;
|
||||
var previousCompletion = (previousScoreData.tallies.sick + previousScoreData.tallies.good) / previousScoreData.tallies.totalNotes;
|
||||
|
||||
// Set the high score and the high rank separately.
|
||||
var newScore:SaveScoreData =
|
||||
{
|
||||
score: (previousScoreData.score > newScoreData.score) ? previousScoreData.score : newScoreData.score,
|
||||
tallies: (previousRank > newRank) ? previousScoreData.tallies : newScoreData.tallies
|
||||
tallies: (previousRank > newRank || previousCompletion > newCompletion) ? previousScoreData.tallies : newScoreData.tallies
|
||||
};
|
||||
|
||||
song.set(difficultyId, newScore);
|
||||
@@ -832,7 +840,7 @@ class Save
|
||||
* If you set slot to `2`, it will load an independe
|
||||
* @param slot
|
||||
*/
|
||||
static function loadFromSlot(slot:Int):Void
|
||||
static function loadFromSlot(slot:Int):Save
|
||||
{
|
||||
trace("[SAVE] Loading save from slot " + slot + "...");
|
||||
|
||||
@@ -850,12 +858,14 @@ class Save
|
||||
trace('[SAVE] Found legacy save data, converting...');
|
||||
var gameSave = SaveDataMigrator.migrateFromLegacy(legacySaveData);
|
||||
FlxG.save.mergeData(gameSave.data, true);
|
||||
return gameSave;
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[SAVE] No legacy save data found.');
|
||||
var gameSave = new Save();
|
||||
FlxG.save.mergeData(gameSave.data, true);
|
||||
return gameSave;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -863,6 +873,8 @@ class Save
|
||||
trace('[SAVE] Found existing save data.');
|
||||
var gameSave = SaveDataMigrator.migrate(FlxG.save.data);
|
||||
FlxG.save.mergeData(gameSave.data, true);
|
||||
|
||||
return gameSave;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1133,6 +1145,12 @@ typedef SaveScoreTallyData =
|
||||
*/
|
||||
typedef SaveDataOptions =
|
||||
{
|
||||
/**
|
||||
* FPS
|
||||
* @default `60`
|
||||
*/
|
||||
var framerate:Int;
|
||||
|
||||
/**
|
||||
* Whether some particularly fowl language is displayed.
|
||||
* @default `true`
|
||||
|
||||
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [2.0.4] - 2024-09-12
|
||||
Note to self: Only update to 2.1.0 when migration is needed.
|
||||
### Added
|
||||
- `unlocks.charactersSeen:Array<String>` to `Save`
|
||||
- `unlocks.oldChar:Bool` to `Save`
|
||||
|
||||
## [2.0.5] - 2024-05-21
|
||||
### Fixed
|
||||
- Resolved an issue where HTML5 wouldn't store the semantic version properly, causing the game to fail to load the save.
|
||||
|
||||
@@ -37,17 +37,25 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler
|
||||
return _conductorInUse = value;
|
||||
}
|
||||
|
||||
public function new(bgColor:FlxColor = FlxColor.TRANSPARENT)
|
||||
{
|
||||
super();
|
||||
this.bgColor = bgColor;
|
||||
}
|
||||
|
||||
var controls(get, never):Controls;
|
||||
|
||||
inline function get_controls():Controls
|
||||
return PlayerSettings.player1.controls;
|
||||
|
||||
public function new(bgColor:FlxColor = FlxColor.TRANSPARENT)
|
||||
{
|
||||
super();
|
||||
this.bgColor = bgColor;
|
||||
|
||||
initCallbacks();
|
||||
}
|
||||
|
||||
function initCallbacks()
|
||||
{
|
||||
subStateOpened.add(onOpenSubStateComplete);
|
||||
subStateClosed.add(onCloseSubStateComplete);
|
||||
}
|
||||
|
||||
override function create():Void
|
||||
{
|
||||
super.create();
|
||||
|
||||
@@ -50,8 +50,13 @@ class PixelatedIcon extends FlxFilteredSprite
|
||||
if (!openfl.utils.Assets.exists(Paths.image(charPath)))
|
||||
{
|
||||
trace('[WARN] Character ${char} has no freeplay icon.');
|
||||
this.visible = false;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.visible = true;
|
||||
}
|
||||
|
||||
var isAnimated = openfl.utils.Assets.exists(Paths.file('images/$charPath.xml'));
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import funkin.modding.IScriptedClass.IBPMSyncedScriptedClass;
|
||||
import flixel.math.FlxMath;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.vis.dsp.SpectralAnalyzer;
|
||||
import funkin.data.freeplay.player.PlayerRegistry;
|
||||
|
||||
class CharSelectGF extends FlxAtlasSprite implements IBPMSyncedScriptedClass
|
||||
{
|
||||
@@ -27,7 +28,8 @@ class CharSelectGF extends FlxAtlasSprite implements IBPMSyncedScriptedClass
|
||||
|
||||
var analyzer:SpectralAnalyzer;
|
||||
|
||||
var curGF:GFChar = GF;
|
||||
var currentGFPath:Null<String>;
|
||||
var enableVisualizer:Bool = false;
|
||||
|
||||
public function new()
|
||||
{
|
||||
@@ -97,7 +99,7 @@ class CharSelectGF extends FlxAtlasSprite implements IBPMSyncedScriptedClass
|
||||
|
||||
function drawFFT()
|
||||
{
|
||||
if (curGF == NENE)
|
||||
if (enableVisualizer)
|
||||
{
|
||||
var levels = analyzer.getLevels();
|
||||
var frame = anim.curSymbol.timeline.get("VIZ_bars").get(anim.curFrame);
|
||||
@@ -172,28 +174,33 @@ class CharSelectGF extends FlxAtlasSprite implements IBPMSyncedScriptedClass
|
||||
*/
|
||||
public function switchGF(bf:String):Void
|
||||
{
|
||||
var prevGF:GFChar = curGF;
|
||||
switch (bf)
|
||||
{
|
||||
case "pico":
|
||||
curGF = NENE;
|
||||
case "bf":
|
||||
curGF = GF;
|
||||
default:
|
||||
curGF = GF;
|
||||
}
|
||||
var previousGFPath = currentGFPath;
|
||||
|
||||
var bfObj = PlayerRegistry.instance.fetchEntry(bf);
|
||||
var gfData = bfObj?.getCharSelectData()?.gf;
|
||||
currentGFPath = gfData?.assetPath != null ? Paths.animateAtlas(gfData?.assetPath) : null;
|
||||
|
||||
// We don't need to update any anims if we didn't change GF
|
||||
if (prevGF != curGF)
|
||||
trace('currentGFPath(${currentGFPath})');
|
||||
if (currentGFPath == null)
|
||||
{
|
||||
loadAtlas(Paths.animateAtlas("charSelect/" + curGF + "Chill"));
|
||||
this.visible = false;
|
||||
return;
|
||||
}
|
||||
else if (previousGFPath != currentGFPath)
|
||||
{
|
||||
this.visible = true;
|
||||
loadAtlas(currentGFPath);
|
||||
|
||||
animInInfo = FramesJSFLParser.parse(Paths.file("images/charSelect/" + curGF + "AnimInfo/" + curGF + "In.txt"));
|
||||
animOutInfo = FramesJSFLParser.parse(Paths.file("images/charSelect/" + curGF + "AnimInfo/" + curGF + "Out.txt"));
|
||||
enableVisualizer = gfData?.visualizer ?? false;
|
||||
|
||||
var animInfoPath = Paths.file('images/${gfData?.animInfoPath}');
|
||||
|
||||
animInInfo = FramesJSFLParser.parse(animInfoPath + '/In.txt');
|
||||
animOutInfo = FramesJSFLParser.parse(animInfoPath + '/Out.txt');
|
||||
}
|
||||
|
||||
playAnimation("idle", true, false, false);
|
||||
// addFrameCallback(getNextFrameLabel("idle"), () -> playAnimation("idle", true, false, false));
|
||||
|
||||
updateHitbox();
|
||||
}
|
||||
@@ -213,9 +220,3 @@ enum FadeStatus
|
||||
FADE_OUT;
|
||||
FADE_IN;
|
||||
}
|
||||
|
||||
enum abstract GFChar(String) from String to String
|
||||
{
|
||||
var GF = "gf";
|
||||
var NENE = "nene";
|
||||
}
|
||||
|
||||
@@ -47,7 +47,6 @@ class CharSelectPlayer extends FlxAtlasSprite implements IBPMSyncedScriptedClass
|
||||
//
|
||||
if (getCurrentAnimation() == "idle")
|
||||
{
|
||||
trace('Player beat hit');
|
||||
playAnimation("idle", true, false, false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -71,6 +71,7 @@ class CharSelectSubState extends MusicBeatSubState
|
||||
var availableChars:Map<Int, String> = new Map<Int, String>();
|
||||
var pressedSelect:Bool = false;
|
||||
var selectTimer:FlxTimer = new FlxTimer();
|
||||
var allowInput:Bool = false;
|
||||
|
||||
var selectSound:FunkinSound;
|
||||
var unlockSound:FunkinSound;
|
||||
@@ -430,6 +431,8 @@ class CharSelectSubState extends MusicBeatSubState
|
||||
overrideExisting: true,
|
||||
restartTrack: true,
|
||||
onLoad: function() {
|
||||
allowInput = true;
|
||||
|
||||
@:privateAccess
|
||||
gfChill.analyzer = new SpectralAnalyzer(FlxG.sound.music._channel.__audioSource, 7, 0.1);
|
||||
#if desktop
|
||||
@@ -573,6 +576,8 @@ class CharSelectSubState extends MusicBeatSubState
|
||||
overrideExisting: true,
|
||||
restartTrack: true,
|
||||
onLoad: function() {
|
||||
allowInput = true;
|
||||
|
||||
@:privateAccess
|
||||
gfChill.analyzer = new SpectralAnalyzer(FlxG.sound.music._channel.__audioSource, 7, 0.1);
|
||||
#if desktop
|
||||
@@ -642,6 +647,7 @@ class CharSelectSubState extends MusicBeatSubState
|
||||
|
||||
function goToFreeplay():Void
|
||||
{
|
||||
allowInput = false;
|
||||
autoFollow = false;
|
||||
|
||||
FlxTween.tween(cursor, {alpha: 0}, 0.8, {ease: FlxEase.expoOut});
|
||||
@@ -695,7 +701,7 @@ class CharSelectSubState extends MusicBeatSubState
|
||||
|
||||
syncAudio(elapsed);
|
||||
|
||||
if (!pressedSelect)
|
||||
if (allowInput && !pressedSelect)
|
||||
{
|
||||
if (controls.UI_UP) holdTmrUp += elapsed;
|
||||
if (controls.UI_UP_R)
|
||||
@@ -786,10 +792,9 @@ class CharSelectSubState extends MusicBeatSubState
|
||||
&& availableChars.exists(getCurrentSelected())
|
||||
&& Save.instance.charactersSeen.contains(availableChars[getCurrentSelected()]))
|
||||
{
|
||||
gfChill.visible = true;
|
||||
curChar = availableChars.get(getCurrentSelected());
|
||||
|
||||
if (!pressedSelect && controls.ACCEPT)
|
||||
if (allowInput && !pressedSelect && controls.ACCEPT)
|
||||
{
|
||||
cursorConfirmed.visible = true;
|
||||
cursorConfirmed.x = cursor.x - 2;
|
||||
@@ -817,7 +822,7 @@ class CharSelectSubState extends MusicBeatSubState
|
||||
});
|
||||
}
|
||||
|
||||
if (pressedSelect && controls.BACK)
|
||||
if (allowInput && pressedSelect && controls.BACK)
|
||||
{
|
||||
cursorConfirmed.visible = false;
|
||||
grpCursors.visible = true;
|
||||
@@ -847,7 +852,7 @@ class CharSelectSubState extends MusicBeatSubState
|
||||
|
||||
gfChill.visible = false;
|
||||
|
||||
if (controls.ACCEPT)
|
||||
if (allowInput && controls.ACCEPT)
|
||||
{
|
||||
cursorDenied.visible = true;
|
||||
cursorDenied.x = cursor.x - 2;
|
||||
|
||||
@@ -4,7 +4,7 @@ package funkin.ui.charSelect;
|
||||
import funkin.graphics.video.FlxVideo;
|
||||
#end
|
||||
#if hxCodec
|
||||
import hxcodec.flixel.FlxVideoSprite;
|
||||
import funkin.graphics.video.FunkinVideoSprite;
|
||||
#end
|
||||
import funkin.ui.MusicBeatSubState;
|
||||
import funkin.audio.FunkinSound;
|
||||
@@ -72,12 +72,12 @@ class IntroSubState extends MusicBeatSubState
|
||||
#end
|
||||
|
||||
#if hxCodec
|
||||
var vid:FlxVideoSprite;
|
||||
var vid:FunkinVideoSprite;
|
||||
|
||||
function playVideoNative(filePath:String):Void
|
||||
{
|
||||
// Video displays OVER the FlxState.
|
||||
vid = new FlxVideoSprite(0, 0);
|
||||
vid = new FunkinVideoSprite(0, 0);
|
||||
|
||||
vid.scrollFactor.set();
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ import haxe.ui.core.Screen;
|
||||
import haxe.ui.events.UIEvent;
|
||||
import haxe.ui.RuntimeComponentBuilder;
|
||||
import lime.utils.Assets as LimeAssets;
|
||||
import openfl.Assets;
|
||||
import openfl.events.Event;
|
||||
import openfl.events.IOErrorEvent;
|
||||
import openfl.geom.Rectangle;
|
||||
@@ -239,6 +238,11 @@ class DebugBoundingState extends FlxState
|
||||
{
|
||||
movingCharacter = false;
|
||||
}
|
||||
|
||||
if (FlxG.mouse.justReleased)
|
||||
{
|
||||
movingCharacter = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -700,7 +700,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
|
||||
function get_isCursorOverHaxeUI():Bool
|
||||
{
|
||||
return Screen.instance.hasSolidComponentUnderPoint(FlxG.mouse.screenX, FlxG.mouse.screenY);
|
||||
return Screen.instance.hasSolidComponentUnderPoint(FlxG.mouse.viewX, FlxG.mouse.viewY);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2274,8 +2274,25 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
this.openBackupAvailableDialog(welcomeDialog);
|
||||
}
|
||||
}
|
||||
|
||||
#if FEATURE_DISCORD_RPC
|
||||
updateDiscordRPC();
|
||||
#end
|
||||
}
|
||||
|
||||
#if FEATURE_DISCORD_RPC
|
||||
function updateDiscordRPC():Void
|
||||
{
|
||||
funkin.api.discord.DiscordClient.instance.setPresence(
|
||||
{
|
||||
// TODO: Make this display the song name and update when it changes.
|
||||
// state: '${currentSongName} [${selectedDifficulty}]',
|
||||
state: null,
|
||||
details: 'Chart Editor [Charting]'
|
||||
});
|
||||
}
|
||||
#end
|
||||
|
||||
function setupWelcomeMusic()
|
||||
{
|
||||
this.welcomeMusic.loadEmbedded(Paths.music('chartEditorLoop/chartEditorLoop'));
|
||||
@@ -3840,7 +3857,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
// Handle scroll anchor
|
||||
if (scrollAnchorScreenPos != null)
|
||||
{
|
||||
var currentScreenPos = new FlxPoint(FlxG.mouse.screenX, FlxG.mouse.screenY);
|
||||
var currentScreenPos = new FlxPoint(FlxG.mouse.viewX, FlxG.mouse.viewX);
|
||||
var distance = currentScreenPos - scrollAnchorScreenPos;
|
||||
|
||||
var verticalDistance = distance.y;
|
||||
@@ -4121,8 +4138,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
var overlapsRenderedEvents:Bool = FlxG.mouse.overlaps(renderedEvents);
|
||||
|
||||
// Cursor position relative to the grid.
|
||||
var cursorX:Float = FlxG.mouse.screenX - gridTiledSprite.x;
|
||||
var cursorY:Float = FlxG.mouse.screenY - gridTiledSprite.y;
|
||||
var cursorX:Float = FlxG.mouse.viewX - gridTiledSprite.x;
|
||||
var cursorY:Float = FlxG.mouse.viewY - gridTiledSprite.y;
|
||||
|
||||
var overlapsSelectionBorder:Bool = overlapsGrid
|
||||
&& ((cursorX % 40) < (GRID_SELECTION_BORDER_WIDTH / 2)
|
||||
@@ -4137,7 +4154,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
{
|
||||
if (scrollAnchorScreenPos == null)
|
||||
{
|
||||
scrollAnchorScreenPos = new FlxPoint(FlxG.mouse.screenX, FlxG.mouse.screenY);
|
||||
scrollAnchorScreenPos = new FlxPoint(FlxG.mouse.viewX, FlxG.mouse.viewY);
|
||||
selectionBoxStartPos = null;
|
||||
}
|
||||
else
|
||||
@@ -4159,11 +4176,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
else if (notePreview != null && FlxG.mouse.overlaps(notePreview) && !isCursorOverHaxeUI)
|
||||
{
|
||||
// Clicked note preview
|
||||
notePreviewScrollAreaStartPos = new FlxPoint(FlxG.mouse.screenX, FlxG.mouse.screenY);
|
||||
notePreviewScrollAreaStartPos = new FlxPoint(FlxG.mouse.viewX, FlxG.mouse.viewY);
|
||||
}
|
||||
else if (!isCursorOverHaxeUI && (!overlapsGrid || overlapsSelectionBorder))
|
||||
{
|
||||
selectionBoxStartPos = new FlxPoint(FlxG.mouse.screenX, FlxG.mouse.screenY);
|
||||
selectionBoxStartPos = new FlxPoint(FlxG.mouse.viewX, FlxG.mouse.viewY);
|
||||
// Drawing selection box.
|
||||
targetCursorMode = Crosshair;
|
||||
}
|
||||
@@ -4188,7 +4205,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
{
|
||||
// Clicked on the playhead scroll area.
|
||||
// Move the playhead to the cursor position.
|
||||
this.playheadPositionInPixels = FlxG.mouse.screenY - (GRID_INITIAL_Y_POS);
|
||||
this.playheadPositionInPixels = FlxG.mouse.viewY - (GRID_INITIAL_Y_POS);
|
||||
moveSongToScrollPosition();
|
||||
|
||||
// Cursor should be a grabby hand.
|
||||
@@ -4251,8 +4268,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
}
|
||||
else
|
||||
{
|
||||
// Minimum of 0.
|
||||
return 0;
|
||||
// Minimum of -1.
|
||||
return -1;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4313,27 +4330,27 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
// Clicking and dragging.
|
||||
|
||||
// Scroll the screen if the mouse is above or below the grid.
|
||||
if (FlxG.mouse.screenY < MENU_BAR_HEIGHT)
|
||||
if (FlxG.mouse.viewY < MENU_BAR_HEIGHT)
|
||||
{
|
||||
// Scroll up.
|
||||
var diff:Float = MENU_BAR_HEIGHT - FlxG.mouse.screenY;
|
||||
var diff:Float = MENU_BAR_HEIGHT - FlxG.mouse.viewY;
|
||||
scrollPositionInPixels -= diff * 0.5; // Too fast!
|
||||
moveSongToScrollPosition();
|
||||
}
|
||||
else if (FlxG.mouse.screenY > (playbarHeadLayout?.y ?? 0.0))
|
||||
else if (FlxG.mouse.viewY > (playbarHeadLayout?.y ?? 0.0))
|
||||
{
|
||||
// Scroll down.
|
||||
var diff:Float = FlxG.mouse.screenY - (playbarHeadLayout?.y ?? 0.0);
|
||||
var diff:Float = FlxG.mouse.viewY - (playbarHeadLayout?.y ?? 0.0);
|
||||
scrollPositionInPixels += diff * 0.5; // Too fast!
|
||||
moveSongToScrollPosition();
|
||||
}
|
||||
|
||||
// Render the selection box.
|
||||
var selectionRect:FlxRect = new FlxRect();
|
||||
selectionRect.x = Math.min(FlxG.mouse.screenX, selectionBoxStartPos.x);
|
||||
selectionRect.y = Math.min(FlxG.mouse.screenY, selectionBoxStartPos.y);
|
||||
selectionRect.width = Math.abs(FlxG.mouse.screenX - selectionBoxStartPos.x);
|
||||
selectionRect.height = Math.abs(FlxG.mouse.screenY - selectionBoxStartPos.y);
|
||||
selectionRect.x = Math.min(FlxG.mouse.viewX, selectionBoxStartPos.x);
|
||||
selectionRect.y = Math.min(FlxG.mouse.viewY, selectionBoxStartPos.y);
|
||||
selectionRect.width = Math.abs(FlxG.mouse.viewX - selectionBoxStartPos.x);
|
||||
selectionRect.height = Math.abs(FlxG.mouse.viewY - selectionBoxStartPos.y);
|
||||
setSelectionBoxBounds(selectionRect);
|
||||
|
||||
targetCursorMode = Crosshair;
|
||||
@@ -4461,8 +4478,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
// Player is clicking and holding on note preview to scrub around.
|
||||
targetCursorMode = Grabbing;
|
||||
|
||||
var clickedPosInPixels:Float = FlxMath.remapToRange(FlxG.mouse.screenY, (notePreview?.y ?? 0.0),
|
||||
(notePreview?.y ?? 0.0) + (notePreview?.height ?? 0.0), 0, songLengthInPixels);
|
||||
var clickedPosInPixels:Float = FlxMath.remapToRange(FlxG.mouse.viewY, (notePreview?.y ?? 0.0), (notePreview?.y ?? 0.0) + (notePreview?.height ?? 0.0),
|
||||
0, songLengthInPixels);
|
||||
|
||||
scrollPositionInPixels = clickedPosInPixels;
|
||||
moveSongToScrollPosition();
|
||||
@@ -4520,17 +4537,17 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
targetCursorMode = Grabbing;
|
||||
|
||||
// Scroll the screen if the mouse is above or below the grid.
|
||||
if (FlxG.mouse.screenY < MENU_BAR_HEIGHT)
|
||||
if (FlxG.mouse.viewY < MENU_BAR_HEIGHT)
|
||||
{
|
||||
// Scroll up.
|
||||
var diff:Float = MENU_BAR_HEIGHT - FlxG.mouse.screenY;
|
||||
var diff:Float = MENU_BAR_HEIGHT - FlxG.mouse.viewY;
|
||||
scrollPositionInPixels -= diff * 0.5; // Too fast!
|
||||
moveSongToScrollPosition();
|
||||
}
|
||||
else if (FlxG.mouse.screenY > (playbarHeadLayout?.y ?? 0.0))
|
||||
else if (FlxG.mouse.viewY > (playbarHeadLayout?.y ?? 0.0))
|
||||
{
|
||||
// Scroll down.
|
||||
var diff:Float = FlxG.mouse.screenY - (playbarHeadLayout?.y ?? 0.0);
|
||||
var diff:Float = FlxG.mouse.viewY - (playbarHeadLayout?.y ?? 0.0);
|
||||
scrollPositionInPixels += diff * 0.5; // Too fast!
|
||||
moveSongToScrollPosition();
|
||||
}
|
||||
@@ -4811,11 +4828,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
// Show the context menu connected to the note.
|
||||
if (useSingleNoteContextMenu)
|
||||
{
|
||||
this.openNoteContextMenu(FlxG.mouse.screenX, FlxG.mouse.screenY, highlightedNote.noteData);
|
||||
this.openNoteContextMenu(FlxG.mouse.viewX, FlxG.mouse.viewY, highlightedNote.noteData);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.openSelectionContextMenu(FlxG.mouse.screenX, FlxG.mouse.screenY);
|
||||
this.openSelectionContextMenu(FlxG.mouse.viewX, FlxG.mouse.viewY);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -4835,11 +4852,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
|| (isHighlightedEventSelected && currentEventSelection.length == 1);
|
||||
if (useSingleEventContextMenu)
|
||||
{
|
||||
this.openEventContextMenu(FlxG.mouse.screenX, FlxG.mouse.screenY, highlightedEvent.eventData);
|
||||
this.openEventContextMenu(FlxG.mouse.viewX, FlxG.mouse.viewY, highlightedEvent.eventData);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.openSelectionContextMenu(FlxG.mouse.screenX, FlxG.mouse.screenY);
|
||||
this.openSelectionContextMenu(FlxG.mouse.viewX, FlxG.mouse.viewY);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -4860,11 +4877,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
// Show the context menu connected to the note.
|
||||
if (useSingleNoteContextMenu)
|
||||
{
|
||||
this.openHoldNoteContextMenu(FlxG.mouse.screenX, FlxG.mouse.screenY, highlightedHoldNote.noteData);
|
||||
this.openHoldNoteContextMenu(FlxG.mouse.viewX, FlxG.mouse.viewY, highlightedHoldNote.noteData);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.openSelectionContextMenu(FlxG.mouse.screenX, FlxG.mouse.screenY);
|
||||
this.openSelectionContextMenu(FlxG.mouse.viewX, FlxG.mouse.viewY);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -5139,10 +5156,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
}
|
||||
|
||||
var songPos:Float = Conductor.instance.songPosition + Conductor.instance.instrumentalOffset;
|
||||
var songPosMilliseconds:String = Std.string(Math.floor(Math.abs(songPos) % 1000)).lpad('0', 2).substr(0, 2);
|
||||
var songPosSeconds:String = Std.string(Math.floor((Math.abs(songPos) / 1000) % 60)).lpad('0', 2);
|
||||
var songPosMinutes:String = Std.string(Math.floor((Math.abs(songPos) / 1000) / 60)).lpad('0', 2);
|
||||
if (songPos < 0) songPosMinutes = '-' + songPosMinutes;
|
||||
var songPosString:String = '${songPosMinutes}:${songPosSeconds}';
|
||||
var songPosString:String = '${songPosMinutes}:${songPosSeconds}:${songPosMilliseconds}';
|
||||
|
||||
if (playbarSongPos.value != songPosString) playbarSongPos.value = songPosString;
|
||||
|
||||
@@ -5614,7 +5632,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Ignoring keybinds for View menu items because we are in live input mode (${currentLiveInputStyle}).');
|
||||
// trace('Ignoring keybinds for View menu items because we are in live input mode (${currentLiveInputStyle}).');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package funkin.ui.debug.charting.components;
|
||||
import funkin.data.event.SongEventRegistry;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.utils.Assets;
|
||||
import flixel.FlxObject;
|
||||
import flixel.FlxBasic;
|
||||
import flixel.FlxSprite;
|
||||
|
||||
@@ -95,7 +95,7 @@ class ChartEditorCharacterIconSelectorMenu extends ChartEditorBaseMenu
|
||||
}
|
||||
|
||||
var LIMIT = 6;
|
||||
charButton.icon = CharacterDataParser.getCharPixelIconAsset(charId);
|
||||
charButton.icon = haxe.ui.util.Variant.fromImageData(CharacterDataParser.getCharPixelIconAsset(charId));
|
||||
charButton.text = charData.name.length > LIMIT ? '${charData.name.substr(0, LIMIT)}.' : '${charData.name}';
|
||||
|
||||
charButton.onClick = _ -> {
|
||||
|
||||
@@ -13,7 +13,6 @@ import funkin.audio.waveform.WaveformSprite;
|
||||
import flixel.util.FlxColor;
|
||||
import haxe.io.Bytes;
|
||||
import haxe.io.Path;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
/**
|
||||
* Functions for loading audio for the chart editor.
|
||||
|
||||
@@ -221,7 +221,7 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox
|
||||
var charDataOpponent:Null<CharacterData> = CharacterDataParser.fetchCharacterData(chartEditorState.currentSongMetadata.playData.characters.opponent);
|
||||
if (charDataOpponent != null)
|
||||
{
|
||||
buttonCharacterOpponent.icon = CharacterDataParser.getCharPixelIconAsset(chartEditorState.currentSongMetadata.playData.characters.opponent);
|
||||
buttonCharacterOpponent.icon = haxe.ui.util.Variant.fromImageData(CharacterDataParser.getCharPixelIconAsset(chartEditorState.currentSongMetadata.playData.characters.opponent));
|
||||
buttonCharacterOpponent.text = charDataOpponent.name.length > LIMIT ? '${charDataOpponent.name.substr(0, LIMIT)}.' : '${charDataOpponent.name}';
|
||||
}
|
||||
else
|
||||
@@ -233,7 +233,7 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox
|
||||
var charDataGirlfriend:Null<CharacterData> = CharacterDataParser.fetchCharacterData(chartEditorState.currentSongMetadata.playData.characters.girlfriend);
|
||||
if (charDataGirlfriend != null)
|
||||
{
|
||||
buttonCharacterGirlfriend.icon = CharacterDataParser.getCharPixelIconAsset(chartEditorState.currentSongMetadata.playData.characters.girlfriend);
|
||||
buttonCharacterGirlfriend.icon = haxe.ui.util.Variant.fromImageData(CharacterDataParser.getCharPixelIconAsset(chartEditorState.currentSongMetadata.playData.characters.girlfriend));
|
||||
buttonCharacterGirlfriend.text = charDataGirlfriend.name.length > LIMIT ? '${charDataGirlfriend.name.substr(0, LIMIT)}.' : '${charDataGirlfriend.name}';
|
||||
}
|
||||
else
|
||||
@@ -245,7 +245,7 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox
|
||||
var charDataPlayer:Null<CharacterData> = CharacterDataParser.fetchCharacterData(chartEditorState.currentSongMetadata.playData.characters.player);
|
||||
if (charDataPlayer != null)
|
||||
{
|
||||
buttonCharacterPlayer.icon = CharacterDataParser.getCharPixelIconAsset(chartEditorState.currentSongMetadata.playData.characters.player);
|
||||
buttonCharacterPlayer.icon = haxe.ui.util.Variant.fromImageData(CharacterDataParser.getCharPixelIconAsset(chartEditorState.currentSongMetadata.playData.characters.player));
|
||||
buttonCharacterPlayer.text = charDataPlayer.name.length > LIMIT ? '${charDataPlayer.name.substr(0, LIMIT)}.' : '${charDataPlayer.name}';
|
||||
}
|
||||
else
|
||||
|
||||
@@ -16,6 +16,7 @@ import funkin.ui.debug.latency.CoolStatsGraph;
|
||||
import openfl.events.KeyboardEvent;
|
||||
import funkin.input.PreciseInputManager;
|
||||
import funkin.play.notes.Strumline;
|
||||
import funkin.ui.mainmenu.MainMenuState;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
import funkin.data.notestyle.NoteStyleData;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
@@ -188,6 +189,12 @@ class LatencyState extends MusicBeatSubState
|
||||
}
|
||||
|
||||
override public function close():Void
|
||||
{
|
||||
cleanup();
|
||||
super.close();
|
||||
}
|
||||
|
||||
function cleanup():Void
|
||||
{
|
||||
PreciseInputManager.instance.onInputPressed.remove(preciseInputPressed);
|
||||
PreciseInputManager.instance.onInputReleased.remove(preciseInputReleased);
|
||||
@@ -200,7 +207,6 @@ class LatencyState extends MusicBeatSubState
|
||||
|
||||
FlxG.state.persistentDraw = prevPersistentDraw;
|
||||
FlxG.state.persistentUpdate = prevPersistentUpdate;
|
||||
super.close();
|
||||
}
|
||||
|
||||
function regenNoteData()
|
||||
@@ -311,7 +317,9 @@ class LatencyState extends MusicBeatSubState
|
||||
|
||||
if (controls.BACK)
|
||||
{
|
||||
close();
|
||||
// close();
|
||||
cleanup();
|
||||
FlxG.switchState(() -> new MainMenuState());
|
||||
}
|
||||
|
||||
super.update(elapsed);
|
||||
|
||||
@@ -11,7 +11,6 @@ import funkin.data.freeplay.album.AlbumRegistry;
|
||||
import funkin.util.assets.FlxAnimationUtil;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.util.SortUtil;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
/**
|
||||
* The graphic for the album roll in the FreeplayState.
|
||||
|
||||
@@ -51,10 +51,12 @@ import funkin.ui.transition.LoadingState;
|
||||
import funkin.ui.transition.StickerSubState;
|
||||
import funkin.util.MathUtil;
|
||||
import funkin.util.SortUtil;
|
||||
import lime.utils.Assets;
|
||||
import openfl.display.BlendMode;
|
||||
import funkin.data.freeplay.style.FreeplayStyleRegistry;
|
||||
import funkin.data.song.SongData.SongMusicData;
|
||||
#if FEATURE_DISCORD_RPC
|
||||
import funkin.api.discord.DiscordClient;
|
||||
#end
|
||||
|
||||
/**
|
||||
* Parameters used to initialize the FreeplayState.
|
||||
@@ -145,11 +147,37 @@ class FreeplayState extends MusicBeatSubState
|
||||
|
||||
var songs:Array<Null<FreeplaySongData>> = [];
|
||||
|
||||
// List of available difficulties for the current song, without `-variation` at the end (no duplicates or nulls).
|
||||
var diffIdsCurrent:Array<String> = [];
|
||||
// List of available difficulties for the total song list, without `-variation` at the end (no duplicates or nulls).
|
||||
var diffIdsTotal:Array<String> = [];
|
||||
// List of available difficulties for the current song, with `-variation` at the end (no duplicates or nulls).
|
||||
var suffixedDiffIdsCurrent:Array<String> = [];
|
||||
// List of available difficulties for the total song list, with `-variation` at the end (no duplicates or nulls).
|
||||
var suffixedDiffIdsTotal:Array<String> = [];
|
||||
|
||||
var curSelected:Int = 0;
|
||||
var currentDifficulty:String = Constants.DEFAULT_DIFFICULTY;
|
||||
var currentSuffixedDifficulty:String = Constants.DEFAULT_DIFFICULTY;
|
||||
var currentUnsuffixedDifficulty(get, never):String;
|
||||
|
||||
function get_currentUnsuffixedDifficulty():String
|
||||
{
|
||||
if (Constants.DEFAULT_DIFFICULTY_LIST_FULL.contains(currentSuffixedDifficulty)) return currentSuffixedDifficulty;
|
||||
|
||||
// Else, we need to strip the suffix.
|
||||
return currentSuffixedDifficulty.substring(0, currentSuffixedDifficulty.lastIndexOf('-'));
|
||||
}
|
||||
|
||||
var currentVariation(get, never):String;
|
||||
|
||||
function get_currentVariation():String
|
||||
{
|
||||
if (Constants.DEFAULT_DIFFICULTY_LIST.contains(currentSuffixedDifficulty)) return Constants.DEFAULT_VARIATION;
|
||||
if (Constants.DEFAULT_DIFFICULTY_LIST_ERECT.contains(currentSuffixedDifficulty)) return 'erect';
|
||||
|
||||
// Else, we need to isolate the suffix.
|
||||
return currentSuffixedDifficulty.substring(currentSuffixedDifficulty.lastIndexOf('-') + 1, currentSuffixedDifficulty.length);
|
||||
}
|
||||
|
||||
public var fp:FreeplayScore;
|
||||
|
||||
@@ -313,7 +341,7 @@ class FreeplayState extends MusicBeatSubState
|
||||
|
||||
#if FEATURE_DISCORD_RPC
|
||||
// Updating Discord Rich Presence
|
||||
DiscordClient.changePresence('In the Menus', null);
|
||||
DiscordClient.instance.setPresence({state: 'In the Menus', details: null});
|
||||
#end
|
||||
|
||||
var isDebug:Bool = false;
|
||||
@@ -357,11 +385,15 @@ class FreeplayState extends MusicBeatSubState
|
||||
trace('Available Difficulties: $availableDifficultiesForSong');
|
||||
if (availableDifficultiesForSong.length == 0) continue;
|
||||
|
||||
songs.push(new FreeplaySongData(levelId, songId, song, displayedVariations));
|
||||
songs.push(new FreeplaySongData(levelId, songId, song, currentCharacter, displayedVariations));
|
||||
for (difficulty in unsuffixedDifficulties)
|
||||
{
|
||||
diffIdsTotal.pushUnique(difficulty);
|
||||
}
|
||||
for (difficulty in availableDifficultiesForSong)
|
||||
{
|
||||
suffixedDiffIdsTotal.pushUnique(difficulty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -454,7 +486,7 @@ class FreeplayState extends MusicBeatSubState
|
||||
wait: 0.1
|
||||
});
|
||||
|
||||
for (diffId in diffIdsTotal)
|
||||
for (diffId in suffixedDiffIdsTotal)
|
||||
{
|
||||
var diffSprite:DifficultySprite = new DifficultySprite(diffId);
|
||||
diffSprite.difficultyId = diffId;
|
||||
@@ -468,7 +500,7 @@ class FreeplayState extends MusicBeatSubState
|
||||
for (diffSprite in grpDifficulties.group.members)
|
||||
{
|
||||
if (diffSprite == null) continue;
|
||||
if (diffSprite.difficultyId == currentDifficulty) diffSprite.visible = true;
|
||||
if (diffSprite.difficultyId == currentSuffixedDifficulty) diffSprite.visible = true;
|
||||
}
|
||||
|
||||
albumRoll.albumId = null;
|
||||
@@ -586,13 +618,13 @@ class FreeplayState extends MusicBeatSubState
|
||||
}
|
||||
};
|
||||
|
||||
exitMovers.set([fp, txtCompletion, fnfHighscoreSpr, txtCompletion, clearBoxSprite],
|
||||
exitMovers.set([fp, txtCompletion, fnfHighscoreSpr, clearBoxSprite],
|
||||
{
|
||||
x: FlxG.width,
|
||||
speed: 0.3
|
||||
});
|
||||
|
||||
exitMoversCharSel.set([fp, txtCompletion, fnfHighscoreSpr, txtCompletion, clearBoxSprite],
|
||||
exitMoversCharSel.set([fp, txtCompletion, fnfHighscoreSpr, clearBoxSprite],
|
||||
{
|
||||
y: -270,
|
||||
speed: 0.8,
|
||||
@@ -744,17 +776,14 @@ class FreeplayState extends MusicBeatSubState
|
||||
{
|
||||
var tempSongs:Array<Null<FreeplaySongData>> = songs;
|
||||
|
||||
// Remember just the difficulty because it's important for song sorting.
|
||||
currentDifficulty = rememberedDifficulty;
|
||||
|
||||
if (filterStuff != null) tempSongs = sortSongs(tempSongs, filterStuff);
|
||||
|
||||
// Filter further by current selected difficulty.
|
||||
if (currentDifficulty != null)
|
||||
if (currentSuffixedDifficulty != null)
|
||||
{
|
||||
tempSongs = tempSongs.filter(song -> {
|
||||
if (song == null) return true; // Random
|
||||
return song.songDifficulties.contains(currentDifficulty);
|
||||
return song.suffixedSongDifficulties.contains(currentSuffixedDifficulty);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1345,7 +1374,6 @@ class FreeplayState extends MusicBeatSubState
|
||||
var dyTouch:Float = 0;
|
||||
var velTouch:Float = 0;
|
||||
|
||||
var veloctiyLoopShit:Float = 0;
|
||||
var touchTimer:Float = 0;
|
||||
|
||||
var initTouchPos:FlxPoint = new FlxPoint();
|
||||
@@ -1376,7 +1404,7 @@ class FreeplayState extends MusicBeatSubState
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
if (FlxG.keys.justPressed.P)
|
||||
{
|
||||
FlxG.switchState(FreeplayState.build(
|
||||
FlxG.switchState(() -> FreeplayState.build(
|
||||
{
|
||||
{
|
||||
character: currentCharacterId == "pico" ? Constants.DEFAULT_CHARACTER : "pico",
|
||||
@@ -1757,16 +1785,18 @@ class FreeplayState extends MusicBeatSubState
|
||||
{
|
||||
touchTimer = 0;
|
||||
|
||||
var currentDifficultyIndex:Int = diffIdsCurrent.indexOf(currentDifficulty);
|
||||
var currentDifficultyIndex:Int = suffixedDiffIdsCurrent.indexOf(currentSuffixedDifficulty);
|
||||
|
||||
if (currentDifficultyIndex == -1) currentDifficultyIndex = diffIdsCurrent.indexOf(Constants.DEFAULT_DIFFICULTY);
|
||||
if (currentDifficultyIndex == -1) currentDifficultyIndex = suffixedDiffIdsCurrent.indexOf(Constants.DEFAULT_DIFFICULTY);
|
||||
|
||||
currentDifficultyIndex += change;
|
||||
|
||||
if (currentDifficultyIndex < 0) currentDifficultyIndex = diffIdsCurrent.length - 1;
|
||||
if (currentDifficultyIndex >= diffIdsCurrent.length) currentDifficultyIndex = 0;
|
||||
if (currentDifficultyIndex < 0) currentDifficultyIndex = suffixedDiffIdsCurrent.length - 1;
|
||||
if (currentDifficultyIndex >= suffixedDiffIdsCurrent.length) currentDifficultyIndex = 0;
|
||||
|
||||
currentDifficulty = diffIdsCurrent[currentDifficultyIndex];
|
||||
currentSuffixedDifficulty = suffixedDiffIdsCurrent[currentDifficultyIndex];
|
||||
|
||||
trace('Switching to difficulty: ${currentSuffixedDifficulty}');
|
||||
|
||||
var daSong:Null<FreeplaySongData> = grpCapsules.members[curSelected].songData;
|
||||
if (daSong != null)
|
||||
@@ -1777,21 +1807,20 @@ class FreeplayState extends MusicBeatSubState
|
||||
FlxG.log.warn('WARN: could not find song with id (${daSong.songId})');
|
||||
return;
|
||||
}
|
||||
var targetVariation:String = targetSong.getFirstValidVariation(currentDifficulty) ?? '';
|
||||
|
||||
// TODO: This line of code makes me sad, but you can't really fix it without a breaking migration.
|
||||
var suffixedDifficulty = (targetVariation != Constants.DEFAULT_VARIATION
|
||||
&& targetVariation != 'erect') ? '$currentDifficulty-${targetVariation}' : currentDifficulty;
|
||||
var suffixedDifficulty = suffixedDiffIdsCurrent[currentDifficultyIndex];
|
||||
var songScore:Null<SaveScoreData> = Save.instance.getSongScore(daSong.songId, suffixedDifficulty);
|
||||
trace(songScore);
|
||||
intendedScore = songScore?.score ?? 0;
|
||||
intendedCompletion = songScore == null ? 0.0 : ((songScore.tallies.sick + songScore.tallies.good) / songScore.tallies.totalNotes);
|
||||
rememberedDifficulty = suffixedDifficulty;
|
||||
currentSuffixedDifficulty = suffixedDifficulty;
|
||||
}
|
||||
else
|
||||
{
|
||||
intendedScore = 0;
|
||||
intendedCompletion = 0.0;
|
||||
rememberedDifficulty = currentDifficulty;
|
||||
rememberedDifficulty = currentSuffixedDifficulty;
|
||||
}
|
||||
|
||||
if (intendedCompletion == Math.POSITIVE_INFINITY || intendedCompletion == Math.NEGATIVE_INFINITY || Math.isNaN(intendedCompletion))
|
||||
@@ -1806,7 +1835,7 @@ class FreeplayState extends MusicBeatSubState
|
||||
for (diffSprite in grpDifficulties.group.members)
|
||||
{
|
||||
if (diffSprite == null) continue;
|
||||
if (diffSprite.difficultyId == currentDifficulty)
|
||||
if (diffSprite.difficultyId == currentSuffixedDifficulty)
|
||||
{
|
||||
if (change != 0)
|
||||
{
|
||||
@@ -1833,7 +1862,9 @@ class FreeplayState extends MusicBeatSubState
|
||||
if (songCapsule == null) continue;
|
||||
if (songCapsule.songData != null)
|
||||
{
|
||||
songCapsule.songData.currentDifficulty = currentDifficulty;
|
||||
songCapsule.songData.currentVariation = currentVariation;
|
||||
songCapsule.songData.currentUnsuffixedDifficulty = currentUnsuffixedDifficulty;
|
||||
songCapsule.songData.currentSuffixedDifficulty = currentSuffixedDifficulty;
|
||||
songCapsule.init(null, null, songCapsule.songData);
|
||||
songCapsule.checkClip();
|
||||
}
|
||||
@@ -1859,7 +1890,7 @@ class FreeplayState extends MusicBeatSubState
|
||||
albumRoll.setDifficultyStars(daSong?.difficultyRating);
|
||||
}
|
||||
|
||||
// Clears the cache of songs, frees up memory, they' ll have to be loaded in later tho function clearDaCache(actualSongTho:String)
|
||||
// Clears the cache of songs to free up memory, they'll have to be loaded in later tho
|
||||
function clearDaCache(actualSongTho:String):Void
|
||||
{
|
||||
for (song in songs)
|
||||
@@ -1921,8 +1952,9 @@ class FreeplayState extends MusicBeatSubState
|
||||
return;
|
||||
}
|
||||
var targetSong:Song = targetSongNullable;
|
||||
var targetDifficultyId:String = currentDifficulty;
|
||||
var targetVariation:Null<String> = targetSong.getFirstValidVariation(targetDifficultyId, currentCharacter);
|
||||
var targetDifficultyId:String = currentUnsuffixedDifficulty;
|
||||
var targetVariation:Null<String> = currentVariation;
|
||||
trace('target song: ${targetSongId} (${targetVariation})');
|
||||
var targetLevelId:Null<String> = cap?.songData?.levelId;
|
||||
PlayStatePlaylist.campaignId = targetLevelId ?? null;
|
||||
|
||||
@@ -2002,8 +2034,8 @@ class FreeplayState extends MusicBeatSubState
|
||||
return;
|
||||
}
|
||||
var targetSong:Song = targetSongNullable;
|
||||
var targetDifficultyId:String = currentDifficulty;
|
||||
var targetVariation:Null<String> = targetSong.getFirstValidVariation(targetDifficultyId, currentCharacter);
|
||||
var targetDifficultyId:String = currentUnsuffixedDifficulty;
|
||||
var targetVariation:Null<String> = currentVariation;
|
||||
var targetLevelId:Null<String> = cap?.songData?.levelId;
|
||||
PlayStatePlaylist.campaignId = targetLevelId ?? null;
|
||||
|
||||
@@ -2069,7 +2101,7 @@ class FreeplayState extends MusicBeatSubState
|
||||
|
||||
if (rememberedDifficulty != null)
|
||||
{
|
||||
currentDifficulty = rememberedDifficulty;
|
||||
currentSuffixedDifficulty = rememberedDifficulty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2087,10 +2119,11 @@ class FreeplayState extends MusicBeatSubState
|
||||
var daSongCapsule:SongMenuItem = grpCapsules.members[curSelected];
|
||||
if (daSongCapsule.songData != null)
|
||||
{
|
||||
var songScore:Null<SaveScoreData> = Save.instance.getSongScore(daSongCapsule.songData.songId, currentDifficulty);
|
||||
var songScore:Null<SaveScoreData> = Save.instance.getSongScore(daSongCapsule.songData.songId, currentSuffixedDifficulty);
|
||||
intendedScore = songScore?.score ?? 0;
|
||||
intendedCompletion = songScore == null ? 0.0 : ((songScore.tallies.sick + songScore.tallies.good) / songScore.tallies.totalNotes);
|
||||
diffIdsCurrent = daSongCapsule.songData.songDifficulties;
|
||||
suffixedDiffIdsCurrent = daSongCapsule.songData.suffixedSongDifficulties;
|
||||
rememberedSongId = daSongCapsule.songData.songId;
|
||||
changeDiff();
|
||||
}
|
||||
@@ -2099,6 +2132,7 @@ class FreeplayState extends MusicBeatSubState
|
||||
intendedScore = 0;
|
||||
intendedCompletion = 0.0;
|
||||
diffIdsCurrent = diffIdsTotal;
|
||||
suffixedDiffIdsCurrent = suffixedDiffIdsTotal;
|
||||
rememberedSongId = null;
|
||||
rememberedDifficulty = Constants.DEFAULT_DIFFICULTY;
|
||||
albumRoll.albumId = null;
|
||||
@@ -2143,17 +2177,18 @@ class FreeplayState extends MusicBeatSubState
|
||||
if (previewSongId == null) return;
|
||||
|
||||
var previewSong:Null<Song> = SongRegistry.instance.fetchEntry(previewSongId);
|
||||
var currentVariation = previewSong?.getVariationsByCharacter(currentCharacter) ?? Constants.DEFAULT_VARIATION_LIST;
|
||||
var songDifficulty:Null<SongDifficulty> = previewSong?.getDifficulty(currentDifficulty,
|
||||
previewSong?.getVariationsByCharacter(currentCharacter) ?? Constants.DEFAULT_VARIATION_LIST);
|
||||
if (previewSong == null) return;
|
||||
// var currentVariation = previewSong.getVariationsByCharacter(currentCharacter) ?? Constants.DEFAULT_VARIATION_LIST;
|
||||
var targetDifficultyId:String = currentUnsuffixedDifficulty;
|
||||
var targetVariation:Null<String> = currentVariation;
|
||||
var songDifficulty:Null<SongDifficulty> = previewSong.getDifficulty(targetDifficultyId, targetVariation ?? Constants.DEFAULT_VARIATION);
|
||||
|
||||
var baseInstrumentalId:String = previewSong?.getBaseInstrumentalId(currentDifficulty, songDifficulty?.variation ?? Constants.DEFAULT_VARIATION) ?? '';
|
||||
var altInstrumentalIds:Array<String> = previewSong?.listAltInstrumentalIds(currentDifficulty,
|
||||
var baseInstrumentalId:String = previewSong.getBaseInstrumentalId(targetDifficultyId, songDifficulty?.variation ?? Constants.DEFAULT_VARIATION) ?? '';
|
||||
var altInstrumentalIds:Array<String> = previewSong.listAltInstrumentalIds(targetDifficultyId,
|
||||
songDifficulty?.variation ?? Constants.DEFAULT_VARIATION) ?? [];
|
||||
|
||||
var instSuffix:String = baseInstrumentalId;
|
||||
|
||||
// TODO: Make this a UI element.
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
if (altInstrumentalIds.length > 0 && FlxG.keys.pressed.CONTROL)
|
||||
{
|
||||
@@ -2312,6 +2347,7 @@ class FreeplaySongData
|
||||
public var songId(default, null):String = '';
|
||||
|
||||
public var songDifficulties(default, null):Array<String> = [];
|
||||
public var suffixedSongDifficulties(default, null):Array<String> = [];
|
||||
|
||||
public var songName(default, null):String = '';
|
||||
public var songCharacter(default, null):String = '';
|
||||
@@ -2319,22 +2355,25 @@ class FreeplaySongData
|
||||
public var difficultyRating(default, null):Int = 0;
|
||||
public var albumId(default, null):Null<String> = null;
|
||||
|
||||
public var currentDifficulty(default, set):String = Constants.DEFAULT_DIFFICULTY;
|
||||
public var currentCharacter:PlayableCharacter;
|
||||
public var currentVariation:String = Constants.DEFAULT_VARIATION;
|
||||
public var currentSuffixedDifficulty(default, set):String = Constants.DEFAULT_DIFFICULTY;
|
||||
public var currentUnsuffixedDifficulty:String = Constants.DEFAULT_DIFFICULTY;
|
||||
|
||||
public var scoringRank:Null<ScoringRank> = null;
|
||||
|
||||
var displayedVariations:Array<String> = [Constants.DEFAULT_VARIATION];
|
||||
|
||||
function set_currentDifficulty(value:String):String
|
||||
function set_currentSuffixedDifficulty(value:String):String
|
||||
{
|
||||
if (currentDifficulty == value) return value;
|
||||
if (currentSuffixedDifficulty == value) return value;
|
||||
|
||||
currentDifficulty = value;
|
||||
currentSuffixedDifficulty = value;
|
||||
updateValues(displayedVariations);
|
||||
return value;
|
||||
}
|
||||
|
||||
public function new(levelId:String, songId:String, song:Song, ?displayedVariations:Array<String>)
|
||||
public function new(levelId:String, songId:String, song:Song, currentCharacter:PlayableCharacter, ?displayedVariations:Array<String>)
|
||||
{
|
||||
this.levelId = levelId;
|
||||
this.songId = songId;
|
||||
@@ -2342,6 +2381,7 @@ class FreeplaySongData
|
||||
|
||||
this.isFav = Save.instance.isSongFavorited(songId);
|
||||
|
||||
this.currentCharacter = currentCharacter;
|
||||
if (displayedVariations != null) this.displayedVariations = displayedVariations;
|
||||
|
||||
updateValues(displayedVariations);
|
||||
@@ -2368,15 +2408,18 @@ class FreeplaySongData
|
||||
function updateValues(variations:Array<String>):Void
|
||||
{
|
||||
this.songDifficulties = song.listDifficulties(null, variations, false, false);
|
||||
if (!this.songDifficulties.contains(currentDifficulty))
|
||||
this.suffixedSongDifficulties = song.listSuffixedDifficulties(variations, false, false);
|
||||
if (!this.songDifficulties.contains(currentUnsuffixedDifficulty))
|
||||
{
|
||||
currentDifficulty = Constants.DEFAULT_DIFFICULTY;
|
||||
currentSuffixedDifficulty = Constants.DEFAULT_DIFFICULTY;
|
||||
// This method gets called again by the setter-method
|
||||
// or the difficulty didn't change, so there's no need to continue.
|
||||
return;
|
||||
}
|
||||
|
||||
var songDifficulty:SongDifficulty = song.getDifficulty(currentDifficulty, null, variations);
|
||||
var targetVariation:Null<String> = currentVariation;
|
||||
|
||||
var songDifficulty:SongDifficulty = song.getDifficulty(currentUnsuffixedDifficulty, targetVariation);
|
||||
if (songDifficulty == null) return;
|
||||
this.songStartingBpm = songDifficulty.getStartingBPM();
|
||||
this.songName = songDifficulty.songName;
|
||||
@@ -2392,10 +2435,7 @@ class FreeplaySongData
|
||||
this.albumId = songDifficulty.album;
|
||||
}
|
||||
|
||||
// TODO: This line of code makes me sad, but you can't really fix it without a breaking migration.
|
||||
// `easy`, `erect`, `normal-pico`, etc.
|
||||
var suffixedDifficulty = (songDifficulty.variation != Constants.DEFAULT_VARIATION
|
||||
&& songDifficulty.variation != 'erect') ? '$currentDifficulty-${songDifficulty.variation}' : currentDifficulty;
|
||||
var suffixedDifficulty = currentSuffixedDifficulty;
|
||||
|
||||
this.scoringRank = Save.instance.getSongRank(songId, suffixedDifficulty);
|
||||
|
||||
|
||||
@@ -678,7 +678,7 @@ class SongMenuItem extends FlxSpriteGroup
|
||||
public function confirm():Void
|
||||
{
|
||||
if (songText != null) songText.flickerText();
|
||||
if (pixelIcon != null)
|
||||
if (pixelIcon != null && pixelIcon.visible)
|
||||
{
|
||||
pixelIcon.animation.play('confirm');
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import funkin.graphics.adobeanimate.FlxAtlasSprite;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.ui.freeplay.charselect.PlayableCharacter;
|
||||
import funkin.ui.MusicBeatSubState;
|
||||
import lime.utils.Assets;
|
||||
import openfl.display.BlendMode;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ import funkin.graphics.adobeanimate.FlxAtlasSprite;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.ui.freeplay.charselect.PlayableCharacter;
|
||||
import funkin.ui.MusicBeatSubState;
|
||||
import lime.utils.Assets;
|
||||
import openfl.display.BlendMode;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
|
||||
@@ -177,20 +176,21 @@ class BoyfriendCard extends BackingCard
|
||||
}
|
||||
|
||||
var beatFreq:Int = 1;
|
||||
var beatFreqList:Array<Int> = [1,2,4,8];
|
||||
var beatFreqList:Array<Int> = [1, 2, 4, 8];
|
||||
|
||||
public override function beatHit():Void {
|
||||
public override function beatHit():Void
|
||||
{
|
||||
// increases the amount of beats that need to go by to pulse the glow because itd flash like craazy at high bpms.....
|
||||
beatFreq = beatFreqList[Math.floor(Conductor.instance.bpm/140)];
|
||||
beatFreq = beatFreqList[Math.floor(Conductor.instance.bpm / 140)];
|
||||
|
||||
if(Conductor.instance.currentBeat % beatFreq != 0) return;
|
||||
if (Conductor.instance.currentBeat % beatFreq != 0) return;
|
||||
FlxTween.cancelTweensOf(glow);
|
||||
FlxTween.cancelTweensOf(glowDark);
|
||||
|
||||
glow.alpha = 0.8;
|
||||
FlxTween.tween(glow, {alpha: 0}, 16/24, {ease: FlxEase.quartOut});
|
||||
FlxTween.tween(glow, {alpha: 0}, 16 / 24, {ease: FlxEase.quartOut});
|
||||
glowDark.alpha = 0;
|
||||
FlxTween.tween(glowDark, {alpha: 0.6}, 18/24, {ease: FlxEase.quartOut});
|
||||
FlxTween.tween(glowDark, {alpha: 0.6}, 18 / 24, {ease: FlxEase.quartOut});
|
||||
}
|
||||
|
||||
public override function introDone():Void
|
||||
|
||||
@@ -20,7 +20,6 @@ import funkin.graphics.adobeanimate.FlxAtlasSprite;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.ui.freeplay.charselect.PlayableCharacter;
|
||||
import funkin.ui.MusicBeatSubState;
|
||||
import lime.utils.Assets;
|
||||
import openfl.display.BlendMode;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
import funkin.graphics.shaders.AdjustColorShader;
|
||||
|
||||
@@ -20,7 +20,6 @@ import funkin.graphics.adobeanimate.FlxAtlasSprite;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.ui.freeplay.charselect.PlayableCharacter;
|
||||
import funkin.ui.MusicBeatSubState;
|
||||
import lime.utils.Assets;
|
||||
import openfl.display.BlendMode;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
import funkin.graphics.shaders.AdjustColorShader;
|
||||
@@ -233,20 +232,21 @@ class PicoCard extends BackingCard
|
||||
}
|
||||
|
||||
var beatFreq:Int = 1;
|
||||
var beatFreqList:Array<Int> = [1,2,4,8];
|
||||
var beatFreqList:Array<Int> = [1, 2, 4, 8];
|
||||
|
||||
public override function beatHit():Void {
|
||||
public override function beatHit():Void
|
||||
{
|
||||
// increases the amount of beats that need to go by to pulse the glow because itd flash like craazy at high bpms.....
|
||||
beatFreq = beatFreqList[Math.floor(Conductor.instance.bpm/140)];
|
||||
beatFreq = beatFreqList[Math.floor(Conductor.instance.bpm / 140)];
|
||||
|
||||
if(Conductor.instance.currentBeat % beatFreq != 0) return;
|
||||
if (Conductor.instance.currentBeat % beatFreq != 0) return;
|
||||
FlxTween.cancelTweensOf(glow);
|
||||
FlxTween.cancelTweensOf(glowDark);
|
||||
|
||||
glow.alpha = 1;
|
||||
FlxTween.tween(glow, {alpha: 0}, 16/24, {ease: FlxEase.quartOut});
|
||||
FlxTween.tween(glow, {alpha: 0}, 16 / 24, {ease: FlxEase.quartOut});
|
||||
glowDark.alpha = 0;
|
||||
FlxTween.tween(glowDark, {alpha: 1}, 18/24, {ease: FlxEase.quartOut});
|
||||
FlxTween.tween(glowDark, {alpha: 1}, 18 / 24, {ease: FlxEase.quartOut});
|
||||
}
|
||||
|
||||
public override function introDone():Void
|
||||
|
||||
@@ -113,7 +113,9 @@ class PlayableCharacter implements IRegistryEntry<PlayerData>
|
||||
|
||||
switch (rank)
|
||||
{
|
||||
case PERFECT | PERFECT_GOLD:
|
||||
case PERFECT_GOLD:
|
||||
return _data.results.perfectGold;
|
||||
case PERFECT:
|
||||
return _data.results.perfect;
|
||||
case EXCELLENT:
|
||||
return _data.results.excellent;
|
||||
|
||||
@@ -28,7 +28,7 @@ import funkin.ui.story.StoryMenuState;
|
||||
import funkin.ui.Prompt;
|
||||
import funkin.util.WindowUtil;
|
||||
#if FEATURE_DISCORD_RPC
|
||||
import Discord.DiscordClient;
|
||||
import funkin.api.discord.DiscordClient;
|
||||
#end
|
||||
#if newgrounds
|
||||
import funkin.ui.NgPrompt;
|
||||
@@ -55,8 +55,7 @@ class MainMenuState extends MusicBeatState
|
||||
override function create():Void
|
||||
{
|
||||
#if FEATURE_DISCORD_RPC
|
||||
// Updating Discord Rich Presence
|
||||
DiscordClient.changePresence("In the Menus", null);
|
||||
DiscordClient.instance.setPresence({state: "In the Menus", details: null});
|
||||
#end
|
||||
|
||||
FlxG.cameras.reset(new FunkinCamera('mainMenu'));
|
||||
@@ -123,7 +122,7 @@ class MainMenuState extends MusicBeatState
|
||||
}));
|
||||
});
|
||||
|
||||
#if CAN_OPEN_LINKS
|
||||
#if FEATURE_OPEN_URL
|
||||
// In order to prevent popup blockers from triggering,
|
||||
// we need to open the link as an immediate result of a keypress event,
|
||||
// so we can't wait for the flicker animation to complete.
|
||||
@@ -234,7 +233,7 @@ class MainMenuState extends MusicBeatState
|
||||
camFollow.setPosition(selected.getGraphicMidpoint().x, selected.getGraphicMidpoint().y);
|
||||
}
|
||||
|
||||
#if CAN_OPEN_LINKS
|
||||
#if FEATURE_OPEN_URL
|
||||
function selectDonate()
|
||||
{
|
||||
WindowUtil.openURL(Constants.URL_ITCH);
|
||||
@@ -344,6 +343,8 @@ class MainMenuState extends MusicBeatState
|
||||
}
|
||||
}
|
||||
|
||||
Conductor.instance.update();
|
||||
|
||||
// Open the debug menu, defaults to ` / ~
|
||||
// This includes stuff like the Chart Editor, so it should be present on all builds.
|
||||
if (controls.DEBUG_MENU)
|
||||
@@ -356,10 +357,11 @@ class MainMenuState extends MusicBeatState
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
// Ctrl+Alt+Shift+P = Character Unlock screen
|
||||
// Ctrl+Alt+Shift+W = Meet requirements for Pico Unlock
|
||||
// Ctrl+Alt+Shift+L = Revoke requirements for Pico Unlock
|
||||
// Ctrl+Alt+Shift+M = Revoke requirements for Pico Unlock
|
||||
// Ctrl+Alt+Shift+R = Score/Rank conflict test
|
||||
// Ctrl+Alt+Shift+N = Mark all characters as not seen
|
||||
// Ctrl+Alt+Shift+E = Dump save data
|
||||
// Ctrl+Alt+Shift+L = Force crash and create a log dump
|
||||
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.P)
|
||||
{
|
||||
@@ -369,7 +371,7 @@ class MainMenuState extends MusicBeatState
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.W)
|
||||
{
|
||||
FunkinSound.playOnce(Paths.sound('confirmMenu'));
|
||||
// Give the user a score of 1 point on Weekend 1 story mode.
|
||||
// Give the user a score of 1 point on Weekend 1 story mode (Easy difficulty).
|
||||
// This makes the level count as cleared and displays the songs in Freeplay.
|
||||
funkin.save.Save.instance.setLevelScore('weekend1', 'easy',
|
||||
{
|
||||
@@ -389,27 +391,30 @@ class MainMenuState extends MusicBeatState
|
||||
});
|
||||
}
|
||||
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.L)
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.M)
|
||||
{
|
||||
FunkinSound.playOnce(Paths.sound('confirmMenu'));
|
||||
// Give the user a score of 0 points on Weekend 1 story mode.
|
||||
// Give the user a score of 0 points on Weekend 1 story mode (all difficulties).
|
||||
// This makes the level count as uncleared and no longer displays the songs in Freeplay.
|
||||
funkin.save.Save.instance.setLevelScore('weekend1', 'easy',
|
||||
{
|
||||
score: 1,
|
||||
tallies:
|
||||
{
|
||||
sick: 0,
|
||||
good: 0,
|
||||
bad: 0,
|
||||
shit: 0,
|
||||
missed: 0,
|
||||
combo: 0,
|
||||
maxCombo: 0,
|
||||
totalNotesHit: 0,
|
||||
totalNotes: 0,
|
||||
}
|
||||
});
|
||||
for (diff in ['easy', 'normal', 'hard'])
|
||||
{
|
||||
funkin.save.Save.instance.setLevelScore('weekend1', diff,
|
||||
{
|
||||
score: 0,
|
||||
tallies:
|
||||
{
|
||||
sick: 0,
|
||||
good: 0,
|
||||
bad: 0,
|
||||
shit: 0,
|
||||
missed: 0,
|
||||
combo: 0,
|
||||
maxCombo: 0,
|
||||
totalNotesHit: 0,
|
||||
totalNotes: 0,
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.R)
|
||||
|
||||
@@ -28,10 +28,14 @@ class ControlsMenu extends funkin.ui.options.OptionsState.Page
|
||||
[NOTE_UP, NOTE_DOWN, NOTE_LEFT, NOTE_RIGHT],
|
||||
[UI_UP, UI_DOWN, UI_LEFT, UI_RIGHT, ACCEPT, BACK],
|
||||
[CUTSCENE_ADVANCE],
|
||||
[FREEPLAY_FAVORITE, FREEPLAY_LEFT, FREEPLAY_RIGHT],
|
||||
[WINDOW_FULLSCREEN, WINDOW_SCREENSHOT],
|
||||
[FREEPLAY_FAVORITE, FREEPLAY_LEFT, FREEPLAY_RIGHT, FREEPLAY_CHAR_SELECT],
|
||||
[WINDOW_FULLSCREEN, #if FEATURE_SCREENSHOTS WINDOW_SCREENSHOT, #end],
|
||||
[VOLUME_UP, VOLUME_DOWN, VOLUME_MUTE],
|
||||
[DEBUG_MENU, DEBUG_CHART]
|
||||
[
|
||||
DEBUG_MENU,
|
||||
#if FEATURE_CHART_EDITOR DEBUG_CHART, #end
|
||||
#if FEATURE_STAGE_EDITOR DEBUG_STAGE, #end
|
||||
]
|
||||
];
|
||||
|
||||
var itemGroups:Array<Array<InputItem>> = [for (i in 0...controlGroups.length) []];
|
||||
@@ -137,7 +141,8 @@ class ControlsMenu extends funkin.ui.options.OptionsState.Page
|
||||
|
||||
if (currentHeader != null && name.indexOf(currentHeader) == 0) name = name.substr(currentHeader.length);
|
||||
|
||||
var label = labels.add(new AtlasText(100, y, name, AtlasFont.BOLD));
|
||||
var formatName = name.replace('_', ' ');
|
||||
var label = labels.add(new AtlasText(100, y, formatName, AtlasFont.BOLD));
|
||||
label.alpha = 0.6;
|
||||
for (i in 0...COLUMNS)
|
||||
createItem(label.x + 550 + i * 400, y, control, i);
|
||||
|
||||
@@ -6,7 +6,6 @@ import flixel.system.FlxAssets;
|
||||
import flixel.tweens.FlxEase;
|
||||
import openfl.display.Bitmap;
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.utils.Assets;
|
||||
import funkin.util.MathUtil;
|
||||
|
||||
/**
|
||||
@@ -80,10 +79,12 @@ class FunkinSoundTray extends FlxSoundTray
|
||||
y = MathUtil.coolLerp(y, lerpYPos, 0.1);
|
||||
alpha = MathUtil.coolLerp(alpha, alphaTarget, 0.25);
|
||||
|
||||
var shouldHide = (FlxG.sound.muted == false && FlxG.sound.volume > 0);
|
||||
|
||||
// Animate sound tray thing
|
||||
if (_timer > 0)
|
||||
{
|
||||
_timer -= (MS / 1000);
|
||||
if (shouldHide) _timer -= (MS / 1000);
|
||||
alphaTarget = 1;
|
||||
}
|
||||
else if (y >= -height)
|
||||
@@ -122,7 +123,7 @@ class FunkinSoundTray extends FlxSoundTray
|
||||
active = true;
|
||||
var globalVolume:Int = Math.round(FlxG.sound.logToLinear(FlxG.sound.volume) * 10);
|
||||
|
||||
if (FlxG.sound.muted)
|
||||
if (FlxG.sound.muted || FlxG.sound.volume == 0)
|
||||
{
|
||||
globalVolume = 0;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package funkin.ui.options;
|
||||
|
||||
import funkin.ui.transition.LoadingState;
|
||||
import funkin.ui.debug.latency.LatencyState;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.FlxSubState;
|
||||
@@ -185,7 +186,11 @@ class OptionsMenu extends Page
|
||||
createItem("PREFERENCES", function() switchPage(Preferences));
|
||||
createItem("CONTROLS", function() switchPage(Controls));
|
||||
createItem("INPUT OFFSETS", function() {
|
||||
#if web
|
||||
LoadingState.transitionToState(() -> new LatencyState());
|
||||
#else
|
||||
FlxG.state.openSubState(new LatencyState());
|
||||
#end
|
||||
});
|
||||
|
||||
#if newgrounds
|
||||
|
||||
@@ -77,6 +77,10 @@ class PreferencesMenu extends Page
|
||||
createPrefItemCheckbox('Unlocked Framerate', 'Enable to unlock the framerate', function(value:Bool):Void {
|
||||
Preferences.unlockedFramerate = value;
|
||||
}, Preferences.unlockedFramerate);
|
||||
#else
|
||||
createPrefItemNumber('FPS', 'The maximum framerate that the game targets', function(value:Float) {
|
||||
Preferences.framerate = Std.int(value);
|
||||
}, null, Preferences.framerate, 30, 300, 5, 0);
|
||||
#end
|
||||
}
|
||||
|
||||
@@ -87,7 +91,6 @@ class PreferencesMenu extends Page
|
||||
// Indent the selected item.
|
||||
items.forEach(function(daItem:TextMenuItem) {
|
||||
var thyOffset:Int = 0;
|
||||
|
||||
// Initializing thy text width (if thou text present)
|
||||
var thyTextWidth:Int = 0;
|
||||
if (Std.isOfType(daItem, EnumPreferenceItem)) thyTextWidth = cast(daItem, EnumPreferenceItem).lefthandText.getWidth();
|
||||
|
||||
@@ -24,6 +24,9 @@ import funkin.ui.transition.LoadingState;
|
||||
import funkin.ui.transition.StickerSubState;
|
||||
import funkin.util.MathUtil;
|
||||
import openfl.utils.Assets;
|
||||
#if FEATURE_DISCORD_RPC
|
||||
import funkin.api.discord.DiscordClient;
|
||||
#end
|
||||
|
||||
class StoryMenuState extends MusicBeatState
|
||||
{
|
||||
@@ -218,7 +221,7 @@ class StoryMenuState extends MusicBeatState
|
||||
|
||||
#if FEATURE_DISCORD_RPC
|
||||
// Updating Discord Rich Presence
|
||||
DiscordClient.changePresence('In the Menus', null);
|
||||
DiscordClient.instance.setPresence({state: 'In the Menus', details: null});
|
||||
#end
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ package funkin.ui.title;
|
||||
import funkin.graphics.video.FlxVideo;
|
||||
#end
|
||||
#if hxCodec
|
||||
import hxcodec.flixel.FlxVideoSprite;
|
||||
import funkin.graphics.video.FunkinVideoSprite;
|
||||
#end
|
||||
import funkin.ui.MusicBeatState;
|
||||
|
||||
@@ -62,12 +62,12 @@ class AttractState extends MusicBeatState
|
||||
#end
|
||||
|
||||
#if hxCodec
|
||||
var vid:FlxVideoSprite;
|
||||
var vid:FunkinVideoSprite;
|
||||
|
||||
function playVideoNative(filePath:String):Void
|
||||
{
|
||||
// Video displays OVER the FlxState.
|
||||
vid = new FlxVideoSprite(0, 0);
|
||||
vid = new FunkinVideoSprite(0, 0);
|
||||
|
||||
if (vid != null)
|
||||
{
|
||||
|
||||
@@ -232,7 +232,8 @@ class TitleState extends MusicBeatState
|
||||
{
|
||||
startingVolume: 0.0,
|
||||
overrideExisting: true,
|
||||
restartTrack: true
|
||||
restartTrack: false,
|
||||
persist: true
|
||||
});
|
||||
// Fade from 0.0 to 1 over 4 seconds
|
||||
if (shouldFadeIn) FlxG.sound.music.fadeIn(4.0, 0.0, 1.0);
|
||||
@@ -273,11 +274,6 @@ class TitleState extends MusicBeatState
|
||||
}
|
||||
#end
|
||||
|
||||
if (Save.instance.charactersSeen.contains("pico"))
|
||||
{
|
||||
Save.instance.charactersSeen.remove("pico");
|
||||
Save.instance.oldChar = false;
|
||||
}
|
||||
Conductor.instance.update();
|
||||
|
||||
/* if (FlxG.onMobile)
|
||||
@@ -490,7 +486,12 @@ class TitleState extends MusicBeatState
|
||||
case 13:
|
||||
addMoreText('Friday');
|
||||
case 14:
|
||||
addMoreText('Night');
|
||||
// easter egg for when the game is trending with the wrong spelling
|
||||
// the random intro text would be "trending--only on x"
|
||||
|
||||
if (curWacky[0] == "trending") addMoreText('Nigth');
|
||||
else
|
||||
addMoreText('Night');
|
||||
case 15:
|
||||
addMoreText('Funkin');
|
||||
case 16:
|
||||
|
||||
@@ -3,24 +3,23 @@ package funkin.ui.transition;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.math.FlxMath;
|
||||
import flixel.tweens.FlxEase;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.util.FlxTimer;
|
||||
import flixel.util.typeLimit.NextState;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.graphics.shaders.ScreenWipeShader;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.play.PlayStatePlaylist;
|
||||
import funkin.play.song.Song.SongDifficulty;
|
||||
import funkin.ui.MusicBeatState;
|
||||
import haxe.io.Path;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import lime.app.Future;
|
||||
import lime.app.Promise;
|
||||
import lime.utils.AssetLibrary;
|
||||
import lime.utils.AssetManifest;
|
||||
import lime.utils.Assets as LimeAssets;
|
||||
import openfl.filters.ShaderFilter;
|
||||
import openfl.utils.Assets;
|
||||
import flixel.util.typeLimit.NextState;
|
||||
import openfl.utils.Assets as OpenFLAssets;
|
||||
|
||||
class LoadingState extends MusicBeatSubState
|
||||
{
|
||||
@@ -98,7 +97,7 @@ class LoadingState extends MusicBeatSubState
|
||||
|
||||
function checkLoadSong(path:String):Void
|
||||
{
|
||||
if (!Assets.cache.hasSound(path))
|
||||
if (!OpenFLAssets.cache.hasSound(path))
|
||||
{
|
||||
var library = Assets.getLibrary('songs');
|
||||
var symbolPath = path.split(':').pop();
|
||||
@@ -277,7 +276,7 @@ class LoadingState extends MusicBeatSubState
|
||||
#if NO_PRELOAD_ALL
|
||||
static function isSoundLoaded(path:String):Bool
|
||||
{
|
||||
return Assets.cache.hasSound(path);
|
||||
return OpenFLAssets.cache.hasSound(path);
|
||||
}
|
||||
|
||||
static function isLibraryLoaded(library:String):Bool
|
||||
@@ -314,7 +313,6 @@ class LoadingState extends MusicBeatSubState
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/popup/pixel/num7'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/popup/pixel/num8'));
|
||||
FunkinSprite.cacheTexture(Paths.image('ui/popup/pixel/num9'));
|
||||
|
||||
FunkinSprite.cacheTexture(Paths.image('notes', 'shared'));
|
||||
FunkinSprite.cacheTexture(Paths.image('noteSplashes', 'shared'));
|
||||
FunkinSprite.cacheTexture(Paths.image('noteStrumline', 'shared'));
|
||||
@@ -453,6 +451,11 @@ class LoadingState extends MusicBeatSubState
|
||||
|
||||
return promise.future;
|
||||
}
|
||||
|
||||
public static function transitionToState(state:NextState, stopMusic:Bool = false):Void
|
||||
{
|
||||
FlxG.switchState(() -> new LoadingState(state, stopMusic));
|
||||
}
|
||||
}
|
||||
|
||||
class MultiCallback
|
||||
|
||||
@@ -2,7 +2,6 @@ package funkin.ui.transition;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import haxe.Json;
|
||||
import lime.utils.Assets;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
// import flxtyped group
|
||||
import funkin.ui.MusicBeatSubState;
|
||||
@@ -56,7 +55,7 @@ class StickerSubState extends MusicBeatSubState
|
||||
// make sure that ONLY plays mp3/ogg files
|
||||
// if there's no mp3/ogg file, then it regenerates/reloads the random folder
|
||||
|
||||
var assetsInList = openfl.utils.Assets.list();
|
||||
var assetsInList = Assets.list();
|
||||
|
||||
var soundFilterFunc = function(a:String) {
|
||||
return a.startsWith('assets/shared/sounds/stickersounds/');
|
||||
@@ -84,7 +83,7 @@ class StickerSubState extends MusicBeatSubState
|
||||
var filterFunc = function(a:String) {
|
||||
return a.startsWith('assets/shared/sounds/stickersounds/' + soundSelection + '/');
|
||||
};
|
||||
var assetsInList3 = openfl.utils.Assets.list();
|
||||
var assetsInList3 = Assets.list();
|
||||
sounds = assetsInList3.filter(filterFunc);
|
||||
for (i in 0...sounds.length)
|
||||
{
|
||||
|
||||
@@ -78,7 +78,12 @@ class Constants
|
||||
/**
|
||||
* Link to download the game on Itch.io.
|
||||
*/
|
||||
public static final URL_ITCH:String = 'https://ninja-muffin24.itch.io/funkin/purchase';
|
||||
public static final URL_ITCH:String = 'https://ninja-muffin24.itch.io/funkin';
|
||||
|
||||
/**
|
||||
* Link to play the game on Newgrounds.
|
||||
*/
|
||||
public static final URL_NEWGROUNDS:String = 'https://www.newgrounds.com/portal/view/770371';
|
||||
|
||||
/**
|
||||
* Link to the game's page on Kickstarter.
|
||||
@@ -186,6 +191,11 @@ class Constants
|
||||
*/
|
||||
public static final DEFAULT_DIFFICULTY_LIST:Array<String> = ['easy', 'normal', 'hard'];
|
||||
|
||||
/**
|
||||
* Default list of difficulties for Erect mode.
|
||||
*/
|
||||
public static final DEFAULT_DIFFICULTY_LIST_ERECT:Array<String> = ['erect', 'nightmare'];
|
||||
|
||||
/**
|
||||
* List of all difficulties used by the base game.
|
||||
* Includes Erect and Nightmare.
|
||||
@@ -481,10 +491,6 @@ class Constants
|
||||
public static final JUDGEMENT_BAD_COMBO_BREAK:Bool = true;
|
||||
public static final JUDGEMENT_SHIT_COMBO_BREAK:Bool = true;
|
||||
|
||||
// % Sick
|
||||
public static final RANK_PERFECT_PLAT_THRESHOLD:Float = 1.0; // % Sick
|
||||
public static final RANK_PERFECT_GOLD_THRESHOLD:Float = 0.85; // % Sick
|
||||
|
||||
// % Hit
|
||||
public static final RANK_PERFECT_THRESHOLD:Float = 1.00;
|
||||
public static final RANK_EXCELLENT_THRESHOLD:Float = 0.90;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user