mirror of
https://github.com/FunkinCrew/Funkin.git
synced 2024-11-30 19:37:04 -05:00
Merge branch 'develop' into bugfix/animation-editor
This commit is contained in:
commit
8cad10263e
171 changed files with 12596 additions and 3231 deletions
2
.gitattributes
vendored
2
.gitattributes
vendored
|
@ -1 +1,3 @@
|
||||||
* text=auto eol=lf
|
* text=auto eol=lf
|
||||||
|
*.hxc linguist-language=Haxe
|
||||||
|
*.hxp linguist-language=Haxe
|
||||||
|
|
50
.github/ISSUE_TEMPLATE/bug.md
vendored
50
.github/ISSUE_TEMPLATE/bug.md
vendored
|
@ -1,50 +0,0 @@
|
||||||
---
|
|
||||||
name: Bug Report
|
|
||||||
about: Report a bug or critical performance issue
|
|
||||||
title: 'Bug Report: [DESCRIBE YOUR BUG IN DETAIL HERE]'
|
|
||||||
labels: bug
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- FILL THIS ISSUE THING OUT AS MUCH AS POSSIBLE
|
|
||||||
OR ELSE YOUR ISSUE WILL BE LESS LIKELY TO BE SOLVED!
|
|
||||||
|
|
||||||
Do not post about issues from other FNF mod engines!
|
|
||||||
We cannot and probably won't solve those!
|
|
||||||
You can hopefully go to their respective GitHub issues and report them there, thank you :)
|
|
||||||
|
|
||||||
Please check for duplicates or similar issues, as well performing simple troubleshooting steps (such as clearing cookies, clearing AppData, trying another browser) before submitting an issue.
|
|
||||||
|
|
||||||
From Joel On Software:
|
|
||||||
|
|
||||||
"It’s pretty easy to remember the rule for a good bug report. Every good bug report needs exactly three things.
|
|
||||||
|
|
||||||
1. Steps to reproduce,
|
|
||||||
2. What you expected to see, and
|
|
||||||
3. What you saw instead."
|
|
||||||
|
|
||||||
-->
|
|
||||||
|
|
||||||
## Describe the bug
|
|
||||||
<!-- A clear and concise description of what the bug is. -->
|
|
||||||
|
|
||||||
## To Reproduce
|
|
||||||
<!-- Describe in DETAIL how to reproduce the bug/issue you are running into. -->
|
|
||||||
## Expected behavior
|
|
||||||
<!-- A clear and concise description of what you expected to happen. -->
|
|
||||||
|
|
||||||
## Screenshots/Video
|
|
||||||
<!-- If applicable, add screenshots/video to help explain your problem.
|
|
||||||
Remember to mark the area in the application thats impacted. -->
|
|
||||||
|
|
||||||
## Desktop
|
|
||||||
- OS:
|
|
||||||
<!-- [e.g. Windows 10, 11, Mac, Linux Mint, Ubuntu, Arch (btw)] -->
|
|
||||||
- Browser
|
|
||||||
<!-- [e.g. chrome, safari, firefox, edge, operaGX] -->
|
|
||||||
- Version:
|
|
||||||
<!-- [e.g. 0.4.0, 0.3.3, this can be found in the bottom left corner of the main menu!] -->
|
|
||||||
|
|
||||||
## Additional context
|
|
||||||
<!-- Add any other context about the problem here. -->
|
|
||||||
|
|
||||||
<!-- If you're game is FROZEN and you're playing a web version, press F12 to open up browser dev window, and go to console, and copy-paste whatever red error you're getting -->
|
|
62
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
62
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
name: Bug Report
|
||||||
|
description: Report a bug or an issue in the game.
|
||||||
|
labels: ["type: minor bug", "status: pending triage"]
|
||||||
|
title: "Bug Report: "
|
||||||
|
body:
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Issue Checklist
|
||||||
|
options:
|
||||||
|
- label: I have properly named the issue
|
||||||
|
- label: I have checked the issues/discussions pages to see if the issue has been previously reported
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: What platform are you using?
|
||||||
|
options:
|
||||||
|
- Newgrounds (Web)
|
||||||
|
- Itch.io (Web)
|
||||||
|
- Itch.io (Downloadable Build) - Windows
|
||||||
|
- Itch.io (Downloadable Build) - MacOS
|
||||||
|
- Itch.io (Downloadable Build) - Linux
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: If you are playing on a browser, which one are you using?
|
||||||
|
options:
|
||||||
|
- Google Chrome
|
||||||
|
- Microsoft Edge
|
||||||
|
- Firefox
|
||||||
|
- Opera
|
||||||
|
- Safari
|
||||||
|
- Other (Specify below)
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Version
|
||||||
|
description: What version are you using?
|
||||||
|
placeholder: ex. 0.4.1
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: "## Describe your bug."
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: "### Please do not report issues from other engines. These must be reported in their respective repositories."
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: "#### Provide as many details as you can."
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Context (Provide images, videos, etc.)
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Steps to reproduce (or crash logs, errors, etc.)
|
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
blank_issues_enabled: false
|
70
.github/ISSUE_TEMPLATE/crash.yml
vendored
Normal file
70
.github/ISSUE_TEMPLATE/crash.yml
vendored
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
name: Crash Report
|
||||||
|
description: Report a crash that occurred while playing the game.
|
||||||
|
labels: ["type: major bug", "status: pending triage"]
|
||||||
|
title: "Crash Report: "
|
||||||
|
body:
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Issue Checklist
|
||||||
|
options:
|
||||||
|
- label: I have properly named the issue
|
||||||
|
- label: I have checked the issues/discussions pages to see if the issue has been previously reported
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: What platform are you using?
|
||||||
|
options:
|
||||||
|
- Newgrounds (Web)
|
||||||
|
- Itch.io (Web)
|
||||||
|
- Itch.io (Downloadable Build) - Windows
|
||||||
|
- Itch.io (Downloadable Build) - MacOS
|
||||||
|
- Itch.io (Downloadable Build) - Linux
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: If you are playing on a browser, which one are you using?
|
||||||
|
options:
|
||||||
|
- Google Chrome
|
||||||
|
- Microsoft Edge
|
||||||
|
- Firefox
|
||||||
|
- Opera
|
||||||
|
- Safari
|
||||||
|
- Other (Specify below)
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Version
|
||||||
|
description: What version are you using?
|
||||||
|
placeholder: ex. 0.4.1
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: "## Describe your issue."
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: "### Please do not report issues from other engines. These must be reported in their respective repositories."
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: "#### Provide as many details as you can."
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Context (Provide screenshots or videos of the crash happening)
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Steps to reproduce
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Crash logs (can be found in the logs folder where Funkin.exe is)
|
||||||
|
validations:
|
||||||
|
required: true
|
8
.github/ISSUE_TEMPLATE/enhancement.md
vendored
8
.github/ISSUE_TEMPLATE/enhancement.md
vendored
|
@ -1,8 +0,0 @@
|
||||||
---
|
|
||||||
name: Enhancement
|
|
||||||
about: Suggest a new feature
|
|
||||||
title: 'Enhancement: '
|
|
||||||
labels: enhancement
|
|
||||||
---
|
|
||||||
#### Please check for duplicates or similar issues before creating this issue.
|
|
||||||
## What is your suggestion, and why should it be implemented?
|
|
15
.github/ISSUE_TEMPLATE/enhancement.yml
vendored
Normal file
15
.github/ISSUE_TEMPLATE/enhancement.yml
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
name: Enhancement
|
||||||
|
description: Suggest a new feature.
|
||||||
|
labels: ["type: enhancement", "status: pending triage"]
|
||||||
|
title: "Enhancement: "
|
||||||
|
body:
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Issue Checklist
|
||||||
|
options:
|
||||||
|
- label: I have properly named the enhancement
|
||||||
|
- label: I have checked the issues/discussions pages to see if the enhancement has been previously suggested
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: What is your suggestion, and why should it be implemented?
|
10
.github/PULL_REQUEST_TEMPLATE/bug.md
vendored
10
.github/PULL_REQUEST_TEMPLATE/bug.md
vendored
|
@ -1,10 +0,0 @@
|
||||||
---
|
|
||||||
name: Bug Fix
|
|
||||||
about: Fix a bug or critical performance issue
|
|
||||||
title: 'Bug Fix: '
|
|
||||||
labels: bug
|
|
||||||
---
|
|
||||||
#### Please check for duplicates or similar PRs before creating this issue.
|
|
||||||
## Does this PR close any issue(s)? If so, link them below.
|
|
||||||
|
|
||||||
## Briefly describe the issue(s) fixed.
|
|
10
.github/PULL_REQUEST_TEMPLATE/enhancement.md
vendored
10
.github/PULL_REQUEST_TEMPLATE/enhancement.md
vendored
|
@ -1,10 +0,0 @@
|
||||||
---
|
|
||||||
name: Enhancement
|
|
||||||
about: Add a new feature
|
|
||||||
title: 'Enhancement: '
|
|
||||||
labels: enhancement
|
|
||||||
---
|
|
||||||
#### Please check for duplicates or similar PRs before creating this issue.
|
|
||||||
## Does this PR close any issue(s)? If so, link them below.
|
|
||||||
|
|
||||||
## What do your change(s) add, and why should they be implemented?
|
|
22
.github/actions/setup-haxe/action.yml
vendored
22
.github/actions/setup-haxe/action.yml
vendored
|
@ -44,7 +44,7 @@ runs:
|
||||||
g++ \
|
g++ \
|
||||||
libx11-dev libxi-dev libxext-dev libxinerama-dev libxrandr-dev \
|
libx11-dev libxi-dev libxext-dev libxinerama-dev libxrandr-dev \
|
||||||
libgl-dev libgl1-mesa-dev \
|
libgl-dev libgl1-mesa-dev \
|
||||||
libasound2-dev
|
libasound2-dev libpulse-dev
|
||||||
ln -s /usr/lib/x86_64-linux-gnu/libffi.so.8 /usr/lib/x86_64-linux-gnu/libffi.so.6 || true
|
ln -s /usr/lib/x86_64-linux-gnu/libffi.so.8 /usr/lib/x86_64-linux-gnu/libffi.so.6 || true
|
||||||
- name: Install linux-specific dependencies
|
- name: Install linux-specific dependencies
|
||||||
if: ${{ runner.os == 'Linux' && contains(inputs.targets, 'linux') }}
|
if: ${{ runner.os == 'Linux' && contains(inputs.targets, 'linux') }}
|
||||||
|
@ -56,12 +56,17 @@ runs:
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
echo "TIMER_HAXELIB=$(date +%s)" >> "$GITHUB_ENV"
|
echo "TIMER_HAXELIB=$(date +%s)" >> "$GITHUB_ENV"
|
||||||
haxelib --debug --never install haxelib 4.1.0 --global
|
haxelib fixrepo --global || true
|
||||||
haxelib --debug --never deleterepo || true
|
haxelib --debug --never --global install haxelib 4.1.0
|
||||||
|
haxelib --debug --global set haxelib 4.1.0
|
||||||
|
haxelib --global remove haxelib git || true
|
||||||
|
haxelib --global remove hmm || true
|
||||||
|
rm -rf .haxelib
|
||||||
|
haxelib --debug --never --global git haxelib https://github.com/FunkinCrew/haxelib.git funkin-patches --skip-dependencies
|
||||||
|
haxelib --debug --never --global git hmm https://github.com/FunkinCrew/hmm funkin-patches
|
||||||
haxelib --debug --never newrepo
|
haxelib --debug --never newrepo
|
||||||
|
haxelib version
|
||||||
echo "HAXEPATH=$(haxelib config)" >> "$GITHUB_ENV"
|
echo "HAXEPATH=$(haxelib config)" >> "$GITHUB_ENV"
|
||||||
haxelib --debug --never git haxelib https://github.com/HaxeFoundation/haxelib.git master
|
|
||||||
haxelib --debug --global install hmm
|
|
||||||
echo "TIMER_DEPS=$(date +%s)" >> "$GITHUB_ENV"
|
echo "TIMER_DEPS=$(date +%s)" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
- name: Restore cached dependencies
|
- name: Restore cached dependencies
|
||||||
|
@ -75,7 +80,12 @@ runs:
|
||||||
name: Prep git for dependency install
|
name: Prep git for dependency install
|
||||||
uses: gacts/run-and-post-run@v1
|
uses: gacts/run-and-post-run@v1
|
||||||
with:
|
with:
|
||||||
run: git config --global 'url.https://x-access-token:${{ inputs.gh-token }}@github.com/.insteadOf' https://github.com/
|
run: |
|
||||||
|
git config --global --name-only --get-regexp 'url\.https\:\/\/x-access-token:.+@github\.com\/\.insteadOf' \
|
||||||
|
| xargs -I {} git config --global --unset {}
|
||||||
|
|
||||||
|
git config -l --show-scope --show-origin
|
||||||
|
git config --global 'url.https://x-access-token:${{ inputs.gh-token }}@github.com/.insteadOf' https://github.com/
|
||||||
post: git config --global --unset 'url.https://x-access-token:${{ inputs.gh-token }}@github.com/.insteadOf'
|
post: git config --global --unset 'url.https://x-access-token:${{ inputs.gh-token }}@github.com/.insteadOf'
|
||||||
|
|
||||||
- if: ${{ steps.cache-hmm.outputs.cache-hit != 'true' }}
|
- if: ${{ steps.cache-hmm.outputs.cache-hit != 'true' }}
|
||||||
|
|
12
.github/changed-lines-count-labeler.yml
vendored
Normal file
12
.github/changed-lines-count-labeler.yml
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# Add 'small' to any changes below 10 lines
|
||||||
|
small:
|
||||||
|
max: 9
|
||||||
|
|
||||||
|
# Add 'medium' to any changes between 10 and 100 lines
|
||||||
|
medium:
|
||||||
|
min: 10
|
||||||
|
max: 99
|
||||||
|
|
||||||
|
# Add 'large' to any changes for more than 100 lines
|
||||||
|
large:
|
||||||
|
min: 100
|
1
.github/labeler.yml
vendored
1
.github/labeler.yml
vendored
|
@ -1,7 +1,6 @@
|
||||||
# Add Documentation tag to PR's changing markdown files, or anyhting in the docs folder
|
# Add Documentation tag to PR's changing markdown files, or anyhting in the docs folder
|
||||||
Documentation:
|
Documentation:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- docs/*
|
- docs/*
|
||||||
- '**/*.md'
|
- '**/*.md'
|
||||||
|
|
6
.github/pull_request_template.md
vendored
Normal file
6
.github/pull_request_template.md
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<!-- Please check for duplicates or similar PRs before submitting this PR. -->
|
||||||
|
## Does this PR close any issues? If so, link them below.
|
||||||
|
|
||||||
|
## Briefly describe the issue(s) fixed.
|
||||||
|
|
||||||
|
## Include any relevant screenshots or videos.
|
8
.github/workflows/build-game.yml
vendored
8
.github/workflows/build-game.yml
vendored
|
@ -45,7 +45,11 @@ jobs:
|
||||||
uses: ./.github/actions/setup-haxe
|
uses: ./.github/actions/setup-haxe
|
||||||
with:
|
with:
|
||||||
gh-token: ${{ steps.app_token.outputs.token }}
|
gh-token: ${{ steps.app_token.outputs.token }}
|
||||||
|
- name: Setup HXCPP dev commit
|
||||||
|
run: |
|
||||||
|
cd .haxelib/hxcpp/git/tools/hxcpp
|
||||||
|
haxe compile.hxml
|
||||||
|
cd ../../../../..
|
||||||
- name: Build game
|
- name: Build game
|
||||||
if: ${{ matrix.target == 'windows' }}
|
if: ${{ matrix.target == 'windows' }}
|
||||||
run: |
|
run: |
|
||||||
|
@ -107,7 +111,9 @@ jobs:
|
||||||
name: Install dependencies
|
name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
git config --global 'url.https://x-access-token:${{ steps.app_token.outputs.token }}@github.com/.insteadOf' https://github.com/
|
git config --global 'url.https://x-access-token:${{ steps.app_token.outputs.token }}@github.com/.insteadOf' https://github.com/
|
||||||
|
git config --global advice.detachedHead false
|
||||||
haxelib --global run hmm install -q
|
haxelib --global run hmm install -q
|
||||||
|
cd .haxelib/hxcpp/git/tools/hxcpp && haxe compile.hxml
|
||||||
|
|
||||||
- if: ${{ matrix.target != 'html5' }}
|
- if: ${{ matrix.target != 'html5' }}
|
||||||
name: Restore hxcpp cache
|
name: Restore hxcpp cache
|
||||||
|
|
15
.github/workflows/labeler.yml
vendored
15
.github/workflows/labeler.yml
vendored
|
@ -9,6 +9,19 @@ jobs:
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/labeler@v5
|
- name: Set basic labels
|
||||||
|
uses: actions/labeler@v5
|
||||||
with:
|
with:
|
||||||
sync-labels: true
|
sync-labels: true
|
||||||
|
changed-lines-count-labeler:
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: An action for automatically labelling pull requests based on the changed lines count
|
||||||
|
steps:
|
||||||
|
- name: Set change count labels
|
||||||
|
uses: vkirilichev/changed-lines-count-labeler@v0.2
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
configuration-path: .github/changed-lines-count-labeler.yml
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -13,3 +13,4 @@ shitAudio/
|
||||||
node_modules/
|
node_modules/
|
||||||
package.json
|
package.json
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
.aider*
|
||||||
|
|
30
.vscode/settings.json
vendored
30
.vscode/settings.json
vendored
|
@ -94,12 +94,12 @@
|
||||||
{
|
{
|
||||||
"label": "Windows / Debug",
|
"label": "Windows / Debug",
|
||||||
"target": "windows",
|
"target": "windows",
|
||||||
"args": ["-debug", "-DFORCE_DEBUG_VERSION"]
|
"args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Linux / Debug",
|
"label": "Linux / Debug",
|
||||||
"target": "linux",
|
"target": "linux",
|
||||||
"args": ["-debug", "-DFORCE_DEBUG_VERSION"]
|
"args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "HashLink / Debug",
|
"label": "HashLink / Debug",
|
||||||
|
@ -109,7 +109,7 @@
|
||||||
{
|
{
|
||||||
"label": "Windows / Debug (FlxAnimate Test)",
|
"label": "Windows / Debug (FlxAnimate Test)",
|
||||||
"target": "windows",
|
"target": "windows",
|
||||||
"args": ["-debug", "-DANIMATE", "-DFORCE_DEBUG_VERSION"]
|
"args": ["-debug", "-DANIMATE", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "HashLink / Debug (FlxAnimate Test)",
|
"label": "HashLink / Debug (FlxAnimate Test)",
|
||||||
|
@ -119,7 +119,7 @@
|
||||||
{
|
{
|
||||||
"label": "Windows / Debug (Straight to Freeplay)",
|
"label": "Windows / Debug (Straight to Freeplay)",
|
||||||
"target": "windows",
|
"target": "windows",
|
||||||
"args": ["-debug", "-DFREEPLAY", "-DFORCE_DEBUG_VERSION"]
|
"args": ["-debug", "-DFREEPLAY", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "HashLink / Debug (Straight to Freeplay)",
|
"label": "HashLink / Debug (Straight to Freeplay)",
|
||||||
|
@ -132,13 +132,13 @@
|
||||||
"args": [
|
"args": [
|
||||||
"-debug",
|
"-debug",
|
||||||
"-DSONG=bopeebo -DDIFFICULTY=normal",
|
"-DSONG=bopeebo -DDIFFICULTY=normal",
|
||||||
"-DFORCE_DEBUG_VERSION"
|
"-DFEATURE_DEBUG_FUNCTIONS"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Windows / Debug (Straight to Play - 2hot)",
|
"label": "Windows / Debug (Straight to Play - 2hot)",
|
||||||
"target": "windows",
|
"target": "windows",
|
||||||
"args": ["-debug", "-DSONG=2hot", "-DFORCE_DEBUG_VERSION"]
|
"args": ["-debug", "-DSONG=2hot", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "HashLink / Debug (Straight to Play - Bopeebo Normal)",
|
"label": "HashLink / Debug (Straight to Play - Bopeebo Normal)",
|
||||||
|
@ -148,7 +148,7 @@
|
||||||
{
|
{
|
||||||
"label": "Windows / Debug (Conversation Test)",
|
"label": "Windows / Debug (Conversation Test)",
|
||||||
"target": "windows",
|
"target": "windows",
|
||||||
"args": ["-debug", "-DDIALOGUE", "-DFORCE_DEBUG_VERSION"]
|
"args": ["-debug", "-DDIALOGUE", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "HashLink / Debug (Conversation Test)",
|
"label": "HashLink / Debug (Conversation Test)",
|
||||||
|
@ -163,7 +163,7 @@
|
||||||
{
|
{
|
||||||
"label": "Windows / Debug (Straight to Chart Editor)",
|
"label": "Windows / Debug (Straight to Chart Editor)",
|
||||||
"target": "windows",
|
"target": "windows",
|
||||||
"args": ["-debug", "-DCHARTING", "-DFORCE_DEBUG_VERSION"]
|
"args": ["-debug", "-DCHARTING", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "HashLink / Debug (Straight to Chart Editor)",
|
"label": "HashLink / Debug (Straight to Chart Editor)",
|
||||||
|
@ -173,12 +173,12 @@
|
||||||
{
|
{
|
||||||
"label": "Windows / Debug (Straight to Animation Editor)",
|
"label": "Windows / Debug (Straight to Animation Editor)",
|
||||||
"target": "windows",
|
"target": "windows",
|
||||||
"args": ["-debug", "-DANIMDEBUG", "-DFORCE_DEBUG_VERSION"]
|
"args": ["-debug", "-DANIMDEBUG", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Windows / Debug (Debug hxCodec)",
|
"label": "Windows / Debug (Debug hxCodec)",
|
||||||
"target": "windows",
|
"target": "windows",
|
||||||
"args": ["-debug", "-DHXC_LIBVLC_LOGGING", "-DFORCE_DEBUG_VERSION"]
|
"args": ["-debug", "-DHXC_LIBVLC_LOGGING", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "HashLink / Debug (Straight to Animation Editor)",
|
"label": "HashLink / Debug (Straight to Animation Editor)",
|
||||||
|
@ -188,7 +188,7 @@
|
||||||
{
|
{
|
||||||
"label": "Windows / Debug (Latency Test)",
|
"label": "Windows / Debug (Latency Test)",
|
||||||
"target": "windows",
|
"target": "windows",
|
||||||
"args": ["-debug", "-DLATENCY", "-DFORCE_DEBUG_VERSION"]
|
"args": ["-debug", "-DLATENCY", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "HashLink / Debug (Latency Test)",
|
"label": "HashLink / Debug (Latency Test)",
|
||||||
|
@ -198,7 +198,7 @@
|
||||||
{
|
{
|
||||||
"label": "Windows / Debug (Waveform Test)",
|
"label": "Windows / Debug (Waveform Test)",
|
||||||
"target": "windows",
|
"target": "windows",
|
||||||
"args": ["-debug", "-DWAVEFORM", "-DFORCE_DEBUG_VERSION"]
|
"args": ["-debug", "-DWAVEFORM", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Windows / Release",
|
"label": "Windows / Release",
|
||||||
|
@ -218,17 +218,17 @@
|
||||||
{
|
{
|
||||||
"label": "HTML5 / Debug",
|
"label": "HTML5 / Debug",
|
||||||
"target": "html5",
|
"target": "html5",
|
||||||
"args": ["-debug", "-DFORCE_DEBUG_VERSION"]
|
"args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "HTML5 / Debug (Watch)",
|
"label": "HTML5 / Debug (Watch)",
|
||||||
"target": "html5",
|
"target": "html5",
|
||||||
"args": ["-debug", "-watch", "-DFORCE_DEBUG_VERSION"]
|
"args": ["-debug", "-watch", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "macOS / Debug",
|
"label": "macOS / Debug",
|
||||||
"target": "mac",
|
"target": "mac",
|
||||||
"args": ["-debug", "-DFORCE_DEBUG_VERSION"]
|
"args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "macOS / Release",
|
"label": "macOS / Release",
|
||||||
|
|
87
CHANGELOG.md
87
CHANGELOG.md
|
@ -4,6 +4,83 @@ All notable changes will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [0.5.0] - 2024-09-12
|
||||||
|
### Added
|
||||||
|
- Added a new Character Select screen to switch between playable characters in Freeplay
|
||||||
|
- Modding isn't 100% there but we're working on it!
|
||||||
|
- Added Pico as a playable character! Unlock him by completing Weekend 1 (if you haven't already done that)
|
||||||
|
- The songs from Weekend 1 have moved; you must now switch to Pico in Freeplay to access them
|
||||||
|
- Added 10 new Pico remixes! Access them by selecting Pico from in the Character Select screen
|
||||||
|
- Bopeebo (Pico Mix)
|
||||||
|
- Fresh (Pico Mix)
|
||||||
|
- DadBattle (Pico Mix)
|
||||||
|
- Spookeez (Pico Mix)
|
||||||
|
- South (Pico Mix)
|
||||||
|
- Philly Nice (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
|
||||||
|
- Darnell (BF Mix)
|
||||||
|
- Added 2 new Erect remixes! Access them by switching difficulty on the song
|
||||||
|
- Cocoa Erect
|
||||||
|
- Ugh Erect
|
||||||
|
- Implemented support for a new Instrumental Selector in Freeplay
|
||||||
|
- Beating a Pico remix lets you use that instrumental when playing as Boyfriend
|
||||||
|
- Added the first batch of Erect Stages! These graphical overhauls of the original stages will be used when playing Erect remixes and Pico remixes
|
||||||
|
- Week 1 Erect Stage
|
||||||
|
- Week 2 Erect Stage
|
||||||
|
- Week 3 Erect Stage
|
||||||
|
- Week 4 Erect Stage
|
||||||
|
- Week 5 Erect Stage
|
||||||
|
- Weekend 1 Erect Stage
|
||||||
|
- Implemented alternate animations and music for Pico in the results screen.
|
||||||
|
- These display on Pico remixes, as well as when playing Weekend 1.
|
||||||
|
- Implemented support for scripted Note Kinds. You can use HScript define a different note style to display for these notes as well as custom behavior. (community feature by lemz1)
|
||||||
|
- Implemented support for Numeric and Selector options in the Options menu. (community feature by FlooferLand)
|
||||||
|
## 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!
|
||||||
|
- Altered how Week 6 displays sprites to make things look more retro.
|
||||||
|
- Character offsets are now independent of the character's scale.
|
||||||
|
- This should resolve issues with offsets when porting characters from older mods.
|
||||||
|
- Pixel character offsets have been modified to compensate.
|
||||||
|
- Note style data can now specify custom combo count graphics, judgement graphics, countdown graphics, and countdown audio. (community feature by anysad)
|
||||||
|
- These were previously using hardcoded values based on whether the stage was `school` or `schoolEvil`.
|
||||||
|
- The `danceEvery` property of characters and stage props can now use values with a precision of `0.25`, to play their idle animation up to four times per beat.
|
||||||
|
- Reworked the JSON merging system in Polymod; you can now include JSONPatch files under `_merge` in your mod folder to add, modify, or remove values in a JSON without replacing it entirely!
|
||||||
|
- Cutscenes now automatically pause when tabbing out (community fix by AbnormalPoof)
|
||||||
|
- Characters will now respect the `danceEvery` property (community fix by gamerbross)
|
||||||
|
- The F5 function now reloads the current song's chart data from disc (community feature by gamerbross)
|
||||||
|
- Refactored the compilation guide and added common troubleshooting steps (community fix by Hundrec)
|
||||||
|
- Made several layout improvements and fixes to the Animation Offsets editor in the Debug menu (community fix by gamerbross)
|
||||||
|
- Fixed a bug where the Back sound would be not played when leaving the Story menu and Options menu (community fix by AppleHair)
|
||||||
|
- Animation offsets no longer directly modify the `x` and `y` position of props, which makes props work better with tweens (community fix by Sword352)
|
||||||
|
- The YEAH! events in Tutorial now use chart events rather than being hard-coded (community fix by anysad)
|
||||||
|
- The player's Score now displays commas in it (community fix by loggo)
|
||||||
|
## Fixed
|
||||||
|
- Fixed an issue where songs with no notes would crash on the Results screen.
|
||||||
|
- Fixed an issue where the old icon easter egg would not work properly on pixel levels.
|
||||||
|
- Fixed an issue where you could play notes during the Thorns cutscene.
|
||||||
|
- Fixed an issue where the Heart icon when favoriting a song in Freeplay would be malformed.
|
||||||
|
- Fixed an issue where Pico's death animation displays a faint blue background (community fix by doggogit)
|
||||||
|
- Fixed an issue where mod songs would not play a preview in the Freeplay menu (community fix by KarimAkra)
|
||||||
|
- Fixed an issue where the Memory Usage counter could overflow and display a negative number (community fix by KarimAkra)
|
||||||
|
- Fixed an issue where pressing the Chart Editor keybind while playtesting a chart would reset the chart editor (community fix by gamerbross)
|
||||||
|
- Fixed a crash bug when pressing F5 after seeing the sticker transition (community fix by gamerbross)
|
||||||
|
- Fixed an issue where the Story Mode menu couldn't be scrolled with a mouse (community fix by JVNpixels)
|
||||||
|
- Fixed an issue causing the song to majorly desync sometimes (community fix by Burgerballs)
|
||||||
|
- Fixed an issue where the Freeplay song preview would not respect the instrumental ID specified in the song metadata (community fix by AppleHair)
|
||||||
|
- Fixed an issue where Tankman's icon wouldn't display in the Chart Editor (community fix by hundrec)
|
||||||
|
- Fixed an issue where pausing the game during a camera zoom would zoom the pause menu. (community fix by gamerbros)
|
||||||
|
- Fixed an issue where certain UI elements would not flash at a consistent rate (community fix by cyn0x8)
|
||||||
|
- Fixed an issue where the game would not use the placeholder health icon as a fallback (community fix by gamerbross)
|
||||||
|
- Fixed an issue where the chart editor could get stuck creating a hold note when using Live Inputs (community fix by gamerbross)
|
||||||
|
- Fixed an issue where character graphics could not be placed in week folders (community fix by 7oltan)
|
||||||
|
- Fixed a crash issue when a Freeplay song has no `Normal` difficulty (community fix by Applehair and gamerbross)
|
||||||
|
- Fixed an issue in Story Mode where a song that isn't valid for the current variation could be selected (community fix by Applehair)
|
||||||
|
|
||||||
## [0.4.1] - 2024-06-12
|
## [0.4.1] - 2024-06-12
|
||||||
### Added
|
### Added
|
||||||
- Pressing ESCAPE on the title screen on desktop now exits the game, allowing you to exit the game while in fullscreen on desktop
|
- Pressing ESCAPE on the title screen on desktop now exits the game, allowing you to exit the game while in fullscreen on desktop
|
||||||
|
@ -81,19 +158,9 @@ which would remove their rank if they had a lower one.
|
||||||
- Fixed a crash on Linux caused by an old version of hxCodec (thanks Noobz4Life!)
|
- Fixed a crash on Linux caused by an old version of hxCodec (thanks Noobz4Life!)
|
||||||
- Optimized animation handling for characters (thanks richTrash21!)
|
- Optimized animation handling for characters (thanks richTrash21!)
|
||||||
- Made improvements to compiling documentation (thanks gedehari!)
|
- Made improvements to compiling documentation (thanks gedehari!)
|
||||||
- Fixed a bug where pressing the volume keys would stop the Toy commercial (thanks gamerbross!)
|
|
||||||
- Fixed a bug where the Chart Editor Playtest would crash when losing (thanks gamerbross!)
|
|
||||||
- Removed a large number of unused imports to optimize builds (thanks Ethan-makes-music!)
|
|
||||||
- Fixed a bug where hold notes would be positioned wrong on downscroll (thanks MaybeMaru!)
|
|
||||||
- Additional fixes to the Loading bar on HTML5 (thanks lemz1!)
|
|
||||||
- Fixed a crash in Freeplay caused by a level referencing an invalid song (thanks gamerbross!)
|
|
||||||
- Improved debug logging for unscripted stages (thanks gamerbross!)
|
|
||||||
- Fixed a bug where changing difficulties in Story mode wouldn't update the score (thanks sectorA!)
|
|
||||||
- Fixed an issue where the Chart Editor would use an incorrect instrumental on imported Legacy songs (thanks gamerbross!)
|
- Fixed an issue where the Chart Editor would use an incorrect instrumental on imported Legacy songs (thanks gamerbross!)
|
||||||
- Fixed a camera bug in the Main Menu (thanks richTrash21!)
|
- Fixed a camera bug in the Main Menu (thanks richTrash21!)
|
||||||
- Fixed several bugs with the TitleState, including missing music when returning from the Main Menu (thanks gamerbross!)
|
|
||||||
- Fixed a bug where opening the game from the command line would crash the preloader (thanks NotHyper474!)
|
- Fixed a bug where opening the game from the command line would crash the preloader (thanks NotHyper474!)
|
||||||
- Fixed a bug where hold notes would display improperly in the Chart Editor when downscroll was enabled for gameplay (thanks gamerbross!)
|
|
||||||
- Fixed a bug where characters would sometimes use the wrong scale value (thanks PurSnake!)
|
- Fixed a bug where characters would sometimes use the wrong scale value (thanks PurSnake!)
|
||||||
- Additional bug fixes and optimizations.
|
- Additional bug fixes and optimizations.
|
||||||
|
|
||||||
|
|
269
Project.xml
269
Project.xml
|
@ -1,269 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<project xmlns="http://lime.openfl.org/project/1.0.4" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://lime.openfl.org/project/1.0.4 http://lime.openfl.org/xsd/project-1.0.4.xsd">
|
|
||||||
<!-- _________________________ Application Settings _________________________ -->
|
|
||||||
<app title="Friday Night Funkin'" file="Funkin" packageName="com.funkin.fnf" package="com.funkin.fnf" main="Main" version="0.4.1" company="ninjamuffin99" />
|
|
||||||
<!--Switch Export with Unique ApplicationID and Icon-->
|
|
||||||
<set name="APP_ID" value="0x0100f6c013bbc000" />
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Define the OpenFL sprite which displays the preloader.
|
|
||||||
You can't replace the preloader's logic here, sadly, but you can extend it.
|
|
||||||
Basic preloading logic is done by `openfl.display.Preloader`.
|
|
||||||
-->
|
|
||||||
<app preloader="funkin.ui.transition.preload.FunkinPreloader" />
|
|
||||||
|
|
||||||
<!--Minimum without FLX_NO_GAMEPAD: 11.8, without FLX_NO_NATIVE_CURSOR: 11.2-->
|
|
||||||
<set name="SWF_VERSION" value="11.8" />
|
|
||||||
<!-- ____________________________ Window Settings ___________________________ -->
|
|
||||||
<!--These window settings apply to all targets-->
|
|
||||||
<window width="1280" height="720" fps="60" background="#000000" hardware="true" vsync="false" />
|
|
||||||
<!--HTML5-specific-->
|
|
||||||
<window if="html5" resizable="true" />
|
|
||||||
<!--Desktop-specific-->
|
|
||||||
<window if="desktop" orientation="landscape" fullscreen="false" resizable="true" vsync="false" />
|
|
||||||
<!--Mobile-specific-->
|
|
||||||
<window if="mobile" orientation="landscape" fullscreen="true" width="0" height="0" resizable="false" />
|
|
||||||
<!-- _____________________________ Path Settings ____________________________ -->
|
|
||||||
|
|
||||||
<set name="BUILD_DIR" value="export/debug" if="debug" />
|
|
||||||
<set name="BUILD_DIR" value="export/release" unless="debug" />
|
|
||||||
<set name="BUILD_DIR" value="export/32bit" if="32bit" />
|
|
||||||
<source path="source" />
|
|
||||||
<assets path="assets/preload" rename="assets" exclude="*.ogg|*.wav" if="web" />
|
|
||||||
<assets path="assets/preload" rename="assets" exclude="*.mp3|*.wav" unless="web" />
|
|
||||||
<define name="PRELOAD_ALL" unless="web" />
|
|
||||||
<define name="NO_PRELOAD_ALL" unless="PRELOAD_ALL" />
|
|
||||||
<section if="PRELOAD_ALL">
|
|
||||||
<library name="songs" preload="true" />
|
|
||||||
<library name="shared" preload="true" />
|
|
||||||
<library name="tutorial" preload="true" />
|
|
||||||
<library name="week1" preload="true" />
|
|
||||||
<library name="week2" preload="true" />
|
|
||||||
<library name="week3" preload="true" />
|
|
||||||
<library name="week4" preload="true" />
|
|
||||||
<library name="week5" preload="true" />
|
|
||||||
<library name="week6" preload="true" />
|
|
||||||
<library name="week7" preload="true" />
|
|
||||||
<library name="weekend1" preload="true" />
|
|
||||||
<library name="videos" preload="true" />
|
|
||||||
</section>
|
|
||||||
<section if="NO_PRELOAD_ALL">
|
|
||||||
<library name="songs" preload="false" />
|
|
||||||
<library name="shared" preload="false" />
|
|
||||||
<library name="tutorial" preload="false" />
|
|
||||||
<library name="week1" preload="false" />
|
|
||||||
<library name="week2" preload="false" />
|
|
||||||
<library name="week3" preload="false" />
|
|
||||||
<library name="week4" preload="false" />
|
|
||||||
<library name="week5" preload="false" />
|
|
||||||
<library name="week6" preload="false" />
|
|
||||||
<library name="week7" preload="false" />
|
|
||||||
<library name="weekend1" preload="false" />
|
|
||||||
<library name="videos" preload="false" />
|
|
||||||
</section>
|
|
||||||
<library name="art" preload="false" />
|
|
||||||
<assets path="assets/songs" library="songs" exclude="*.fla|*.ogg|*.wav" if="web" />
|
|
||||||
<assets path="assets/songs" library="songs" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
|
||||||
<!-- Videos go in their own library because web never needs to preload them, they can just be streamed. -->
|
|
||||||
<assets path="assets/videos" library="videos" />
|
|
||||||
<assets path="assets/shared" library="shared" exclude="*.fla|*.ogg|*.wav" if="web" />
|
|
||||||
<assets path="assets/shared" library="shared" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
|
||||||
<assets path="assets/tutorial" library="tutorial" exclude="*.fla|*.ogg|*.wav" if="web" />
|
|
||||||
<assets path="assets/tutorial" library="tutorial" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
|
||||||
<assets path="assets/week1" library="week1" exclude="*.fla|*.ogg|*.wav" if="web" />
|
|
||||||
<assets path="assets/week1" library="week1" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
|
||||||
<assets path="assets/week2" library="week2" exclude="*.fla|*.ogg|*.wav" if="web" />
|
|
||||||
<assets path="assets/week2" library="week2" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
|
||||||
<assets path="assets/week3" library="week3" exclude="*.fla|*.ogg|*.wav" if="web" />
|
|
||||||
<assets path="assets/week3" library="week3" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
|
||||||
<assets path="assets/week4" library="week4" exclude="*.fla|*.ogg|*.wav" if="web" />
|
|
||||||
<assets path="assets/week4" library="week4" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
|
||||||
<assets path="assets/week5" library="week5" exclude="*.fla|*.ogg|*.wav" if="web" />
|
|
||||||
<assets path="assets/week5" library="week5" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
|
||||||
<assets path="assets/week6" library="week6" exclude="*.fla|*.ogg|*.wav" if="web" />
|
|
||||||
<assets path="assets/week6" library="week6" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
|
||||||
<assets path="assets/week7" library="week7" exclude="*.fla|*.ogg|*.wav" if="web" />
|
|
||||||
<assets path="assets/week7" library="week7" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
|
||||||
<assets path="assets/weekend1" library="weekend1" exclude="*.fla|*.ogg|*.wav" if="web" />
|
|
||||||
<assets path="assets/weekend1" library="weekend1" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
|
||||||
<!-- <assets path='example_mods' rename='mods' embed='false'/> -->
|
|
||||||
<!--
|
|
||||||
AUTOMATICALLY MOVING EXAMPLE MODS INTO THE BUILD CAUSES ISSUES
|
|
||||||
Currently, this line will add the mod files to the library manifest,
|
|
||||||
which causes issues if the mod is not enabled.
|
|
||||||
If we can exclude the `mods` folder from the manifest, we can re-enable this line.
|
|
||||||
<assets path='example_mods' rename='mods' embed='false' exclude="*.md" />
|
|
||||||
-->
|
|
||||||
<assets path="art/readme.txt" rename="do NOT readme.txt" library="art"/>
|
|
||||||
<assets path="CHANGELOG.md" rename="changelog.txt" library="art"/>
|
|
||||||
<!-- NOTE FOR FUTURE SELF SINCE FONTS ARE ALWAYS FUCKY
|
|
||||||
TO FIX ONE OF THEM, I CONVERTED IT TO OTF. DUNNO IF YOU NEED TO
|
|
||||||
THEN UHHH I USED THE NAME OF THE FONT WITH SETFORMAT() ON THE TEXT!!!
|
|
||||||
NOT USING A DIRECT THING TO THE ASSET!!!
|
|
||||||
-->
|
|
||||||
<assets path="assets/fonts" embed="true" />
|
|
||||||
|
|
||||||
<!-- If compiled via github actions, show debug version number. -->
|
|
||||||
<define name="FORCE_DEBUG_VERSION" if="GITHUB_BUILD" />
|
|
||||||
<define name="NO_REDIRECT_ASSETS_FOLDER" if="GITHUB_BUILD" />
|
|
||||||
<define name="TOUCH_HERE_TO_PLAY" if="web" />
|
|
||||||
|
|
||||||
<!-- _______________________________ Libraries ______________________________ -->
|
|
||||||
<haxelib name="lime" /> <!-- Game engine backend -->
|
|
||||||
<haxelib name="openfl" /> <!-- Game engine backend -->
|
|
||||||
<haxelib name="flixel" /> <!-- Game engine -->
|
|
||||||
|
|
||||||
<haxedev set="webgl" />
|
|
||||||
|
|
||||||
<haxelib name="flixel-addons" /> <!-- Additional utilities for Flixel -->
|
|
||||||
<haxelib name="hscript" /> <!-- Scripting -->
|
|
||||||
<haxelib name="flixel-ui" /> <!-- UI framework (DEPRECATED) -->
|
|
||||||
<haxelib name="haxeui-core" /> <!-- UI framework -->
|
|
||||||
<haxelib name="haxeui-flixel" /> <!-- Integrate HaxeUI with Flixel -->
|
|
||||||
<haxelib name="flixel-text-input" /> <!-- Improved text field rendering for HaxeUI -->
|
|
||||||
<haxelib name="polymod" /> <!-- Modding framework -->
|
|
||||||
<haxelib name="flxanimate" /> <!-- Texture atlas rendering -->
|
|
||||||
<haxelib name="hxCodec" if="desktop" unless="hl" /> <!-- Video playback -->
|
|
||||||
<haxelib name="funkin.vis"/>
|
|
||||||
<haxelib name="grig.audio" />
|
|
||||||
|
|
||||||
<haxelib name="FlxPartialSound" /> <!-- Loading partial sound data -->
|
|
||||||
|
|
||||||
<haxelib name="json2object" /> <!-- JSON parsing -->
|
|
||||||
<haxelib name="thx.core" /> <!-- General utility library, "the lodash of Haxe" -->
|
|
||||||
<haxelib name="thx.semver" /> <!-- Version string handling -->
|
|
||||||
|
|
||||||
<haxelib name="hxcpp-debug-server" if="desktop debug" /> <!-- VSCode debug support -->
|
|
||||||
|
|
||||||
<!--Disable the Flixel core focus lost screen-->
|
|
||||||
<haxedef name="FLX_NO_FOCUS_LOST_SCREEN" />
|
|
||||||
<!--Disable the Flixel core debugger. Automatically gets set whenever you compile in release mode!-->
|
|
||||||
<haxedef name="FLX_NO_DEBUG" unless="debug || FORCE_DEBUG_VERSION" />
|
|
||||||
<!--Enable this for Nape release builds for a serious peformance improvement-->
|
|
||||||
<haxedef name="NAPE_RELEASE_BUILD" unless="debug" />
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Hide deprecation warnings until they're fixed.
|
|
||||||
TODO: REMOVE THIS!!!!
|
|
||||||
<haxeflag name="-w" value="-WDeprecated" />
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!-- Haxe 4.3.0+: Enable pretty syntax errors and stuff. -->
|
|
||||||
<haxedef name="message.reporting" value="pretty" />
|
|
||||||
|
|
||||||
<!-- _________________________________ Custom _______________________________ -->
|
|
||||||
<!-- Disable trace() calls in release builds to bump up performance.
|
|
||||||
<haxeflag name="- -no-traces" unless="debug" />-->
|
|
||||||
<!-- HScript relies heavily on Reflection, which means we can't use DCE. -->
|
|
||||||
<haxeflag name="-dce no" />
|
|
||||||
<!-- Ensure all Funkin' classes are available at runtime. -->
|
|
||||||
<haxeflag name="--macro" value="include('funkin')" />
|
|
||||||
<!-- Ensure all UI components are available at runtime. -->
|
|
||||||
<haxeflag name="--macro" value="include('haxe.ui.backend.flixel.components')" />
|
|
||||||
<haxeflag name="--macro" value="include('haxe.ui.containers.dialogs')" />
|
|
||||||
<haxeflag name="--macro" value="include('haxe.ui.containers.menus')" />
|
|
||||||
<haxeflag name="--macro" value="include('haxe.ui.containers.properties')" />
|
|
||||||
<haxeflag name="--macro" value="include('haxe.ui.core')" />
|
|
||||||
<haxeflag name="--macro" value="include('haxe.ui.components')" />
|
|
||||||
<haxeflag name="--macro" value="include('haxe.ui.containers')" />
|
|
||||||
<!--
|
|
||||||
Ensure additional class packages are available at runtime (some only really used by scripts).
|
|
||||||
Ignore packages we can't include.
|
|
||||||
-->
|
|
||||||
<haxeflag name="--macro" value="include('flixel', true, [ 'flixel.addons.editors.spine.*', 'flixel.addons.nape.*', 'flixel.system.macros.*' ])" />
|
|
||||||
<!-- Necessary to provide stack traces for HScript. -->
|
|
||||||
<haxedef name="hscriptPos" />
|
|
||||||
<haxedef name="safeMode"/>
|
|
||||||
<haxedef name="HXCPP_CHECK_POINTER" />
|
|
||||||
<haxedef name="HXCPP_STACK_LINE" />
|
|
||||||
<haxedef name="HXCPP_STACK_TRACE" />
|
|
||||||
<!-- This macro allows addition of new functionality to existing Flixel. -->
|
|
||||||
<haxeflag name="--macro" value="addMetadata('@:build(funkin.util.macro.FlxMacro.buildFlxBasic())', 'flixel.FlxBasic')" />
|
|
||||||
<!--Place custom nodes like icons here (higher priority to override the HaxeFlixel icon)-->
|
|
||||||
<icon path="art/icon16.png" size="16" />
|
|
||||||
<icon path="art/icon32.png" size="32" />
|
|
||||||
<icon path="art/icon64.png" size="64" />
|
|
||||||
<icon path="art/iconOG.png" />
|
|
||||||
<haxedef name="CAN_OPEN_LINKS" unless="switch" />
|
|
||||||
<haxedef name="CAN_CHEAT" if="switch debug" />
|
|
||||||
<!-- I don't remember what this is for. -->
|
|
||||||
<haxedef name="haxeui_no_mouse_reset" />
|
|
||||||
<!-- Clicking outside a dialog should deselect the current focused component. -->
|
|
||||||
<haxedef name="haxeui_focus_out_on_click" />
|
|
||||||
<!-- Required to use haxe.ui.backend.flixel.UIState with build macros. -->
|
|
||||||
<haxedef name="haxeui_dont_impose_base_class" />
|
|
||||||
<haxedef name="HARDCODED_CREDITS" />
|
|
||||||
|
|
||||||
<!-- Skip the Intro -->
|
|
||||||
<section if="debug">
|
|
||||||
<!-- Starts the game at the specified week, at the first song -->
|
|
||||||
<!-- <haxedef name="week" value="1" if="debug"/> -->
|
|
||||||
<!-- Starts the game at the specified song -->
|
|
||||||
<!-- <haxedef name="song" value="bopeebo" if="debug"/> -->
|
|
||||||
<!-- Difficulty, only used for week or song, defaults to 1 -->
|
|
||||||
<!-- <haxedef name="dif" value="2" if="debug"/> -->
|
|
||||||
</section>
|
|
||||||
<section if="newgrounds">
|
|
||||||
<!-- Enables Ng.core.verbose -->
|
|
||||||
<!-- <haxedef name="NG_VERBOSE" /> -->
|
|
||||||
<!-- Enables a NG debug session, so medals don't permently unlock -->
|
|
||||||
<!-- <haxedef name="NG_DEBUG" /> -->
|
|
||||||
<!-- pretends that the saved session Id was expired, forcing the reconnect prompt -->
|
|
||||||
<!-- <haxedef name="NG_FORCE_EXPIRED_SESSION" if="debug" /> -->
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Uncomment this to wipe your input settings. -->
|
|
||||||
<!-- <haxedef name="CLEAR_INPUT_SAVE"/> -->
|
|
||||||
|
|
||||||
<section if="debug" unless="NO_REDIRECT_ASSETS_FOLDER || html5 || GITHUB_BUILD">
|
|
||||||
<!--
|
|
||||||
Use the parent assets folder rather than the exported one
|
|
||||||
No more will we accidentally undo our changes!
|
|
||||||
-->
|
|
||||||
<haxedef name="REDIRECT_ASSETS_FOLDER" />
|
|
||||||
</section>
|
|
||||||
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<!--
|
|
||||||
This flag enables the popup/crashlog error handler.
|
|
||||||
However, it also messes with breakpoints on some platforms.
|
|
||||||
-->
|
|
||||||
<haxedef name="openfl-enable-handle-error" />
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Run a script before and after building. -->
|
|
||||||
<prebuild haxe="source/Prebuild.hx"/> -->
|
|
||||||
<postbuild haxe="source/Postbuild.hx"/> -->
|
|
||||||
|
|
||||||
<!-- Enable this on platforms which do not support dropping files onto the window. -->
|
|
||||||
<haxedef name="FILE_DROP_UNSUPPORTED" if="mac" />
|
|
||||||
<section unless="FILE_DROP_UNSUPPORTED">
|
|
||||||
<haxedef name="FILE_DROP_SUPPORTED" />
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Enable this on platforms which do not support the edsior views. -->
|
|
||||||
<haxedef name="CHART_EDITOR_UNSUPPORTED" if="web" />
|
|
||||||
<haxedef name="CHART_EDITOR_SUPPORTED" unless="web"/>
|
|
||||||
|
|
||||||
<!-- Options for Polymod -->
|
|
||||||
<section if="polymod">
|
|
||||||
<!-- Turns on additional debug logging. -->
|
|
||||||
<haxedef name="POLYMOD_DEBUG" value="true" if="debug" />
|
|
||||||
<!-- The file extension to use for script files. -->
|
|
||||||
<haxedef name="POLYMOD_SCRIPT_EXT" value=".hscript" />
|
|
||||||
<!-- Which asset library to use for scripts. -->
|
|
||||||
<haxedef name="POLYMOD_SCRIPT_LIBRARY" value="scripts" />
|
|
||||||
<!-- The base path from which scripts should be accessed. -->
|
|
||||||
<haxedef name="POLYMOD_ROOT_PATH" value="scripts/" />
|
|
||||||
<!-- Determines the subdirectory of the mod folder used for file appending. -->
|
|
||||||
<haxedef name="POLYMOD_APPEND_FOLDER" value="_append" />
|
|
||||||
<!-- Determines the subdirectory of the mod folder used for file merges. -->
|
|
||||||
<haxedef name="POLYMOD_MERGE_FOLDER" value="_merge" />
|
|
||||||
<!-- Determines the file in the mod folder used for metadata. -->
|
|
||||||
<haxedef name="POLYMOD_MOD_METADATA_FILE" value="_polymod_meta.json" />
|
|
||||||
<!-- Determines the file in the mod folder used for the icon. -->
|
|
||||||
<haxedef name="POLYMOD_MOD_ICON_FILE" value="_polymod_icon.png" />
|
|
||||||
</section>
|
|
||||||
</project>
|
|
2
art
2
art
|
@ -1 +1 @@
|
||||||
Subproject commit faeba700c5526bd4fd57ccc927d875c82b9d3553
|
Subproject commit bfca2ea98d11a0f4dee4a27b9390951fbc5701ea
|
2
assets
2
assets
|
@ -1 +1 @@
|
||||||
Subproject commit 2e1594ee4c04c7148628bae471bdd061c9deb6b7
|
Subproject commit bc7009b4242691faa5c4552f7ca8a2f28e8cb1d2
|
|
@ -83,7 +83,7 @@ apt-fast install -y --no-install-recommends \
|
||||||
libc6-dev libffi-dev \
|
libc6-dev libffi-dev \
|
||||||
libx11-dev libxi-dev libxext-dev libxinerama-dev libxrandr-dev \
|
libx11-dev libxi-dev libxext-dev libxinerama-dev libxrandr-dev \
|
||||||
libgl-dev libgl1-mesa-dev \
|
libgl-dev libgl1-mesa-dev \
|
||||||
libasound2-dev \
|
libasound2-dev libpulse-dev \
|
||||||
libvlc-dev libvlccore-dev
|
libvlc-dev libvlccore-dev
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
@ -137,8 +137,8 @@ ENV PATH="$HAXEPATH:$PATH"
|
||||||
RUN <<EOF
|
RUN <<EOF
|
||||||
HOME=/etc haxelib setup "$HAXEPATH/lib"
|
HOME=/etc haxelib setup "$HAXEPATH/lib"
|
||||||
haxelib --global --never install haxelib $haxelib_version
|
haxelib --global --never install haxelib $haxelib_version
|
||||||
haxelib --global --never git haxelib https://github.com/HaxeFoundation/haxelib.git master
|
haxelib --global --never git haxelib https://github.com/FunkinCrew/haxelib.git funkin-patches --skip-dependencies
|
||||||
haxelib --global --never install hmm
|
haxelib --global --never git hmm https://github.com/FunkinCrew/hmm funkin-patches
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# hxcpp
|
# hxcpp
|
||||||
|
|
|
@ -327,7 +327,8 @@
|
||||||
"INLINE",
|
"INLINE",
|
||||||
"DYNAMIC",
|
"DYNAMIC",
|
||||||
"FINAL"
|
"FINAL"
|
||||||
]
|
],
|
||||||
|
"severity": "IGNORE"
|
||||||
},
|
},
|
||||||
"type": "ModifierOrder"
|
"type": "ModifierOrder"
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,14 +2,19 @@
|
||||||
|
|
||||||
0. Setup
|
0. Setup
|
||||||
- Download Haxe from [Haxe.org](https://haxe.org)
|
- Download Haxe from [Haxe.org](https://haxe.org)
|
||||||
1. Cloning the Repository: Make sure when you clone, you clone the submodules to get the assets repo:
|
- Download Git from [git-scm.com](https://www.git-scm.com)
|
||||||
- `git clone --recurse-submodules https://github.com/FunkinCrew/funkin.git`
|
- Do NOT download the repository using the Download ZIP button on GitHub or you may run into errors!
|
||||||
- If you accidentally cloned without the `assets` submodule (aka didn't follow the step above), you can run `git submodule update --init --recursive` to get the assets in a foolproof way.
|
- Instead, open a command prompt and do the following steps...
|
||||||
2. Install `hmm` (run `haxelib --global install hmm` and then `haxelib --global run hmm setup`)
|
1. Run `cd the\directory\you\want\the\source\code\in` to specify which folder the command prompt is working in.
|
||||||
3. Download Git from [git-scm.com](https://www.git-scm.com)
|
- For example, `cd C:\Users\YOURNAME\Documents` would instruct the command prompt to perform the next steps in your Documents folder.
|
||||||
4. Install all haxelibs of the current branch by running `hmm install`
|
2. Run `git clone https://github.com/FunkinCrew/funkin.git` to clone the base repository.
|
||||||
5. Setup lime: `haxelib run lime setup`
|
3. Run `cd funkin` to enter the cloned repository's directory.
|
||||||
6. Platform setup
|
4. Run `git submodule update --init --recursive` to download the game's assets.
|
||||||
|
- NOTE: By performing this operation, you are downloading Content which is proprietary and protected by national and international copyright and trademark laws. See [the LICENSE.md file for the Funkin.assets](https://github.com/FunkinCrew/funkin.assets/blob/main/LICENSE.md) repo for more information.
|
||||||
|
5. Run `haxelib --global install hmm` and then `haxelib --global run hmm setup` to install hmm.json
|
||||||
|
6. Run `hmm install` to install all haxelibs of the current branch
|
||||||
|
7. Run `haxelib run lime setup` to set up lime
|
||||||
|
8. Perform additional platform setup
|
||||||
- For Windows, download the [Visual Studio Build Tools](https://aka.ms/vs/17/release/vs_BuildTools.exe)
|
- For Windows, download the [Visual Studio Build Tools](https://aka.ms/vs/17/release/vs_BuildTools.exe)
|
||||||
- When prompted, select "Individual Components" and make sure to download the following:
|
- When prompted, select "Individual Components" and make sure to download the following:
|
||||||
- MSVC v143 VS 2022 C++ x64/x86 build tools
|
- MSVC v143 VS 2022 C++ x64/x86 build tools
|
||||||
|
@ -17,10 +22,25 @@
|
||||||
- Mac: [`lime setup mac` Documentation](https://lime.openfl.org/docs/advanced-setup/macos/)
|
- Mac: [`lime setup mac` Documentation](https://lime.openfl.org/docs/advanced-setup/macos/)
|
||||||
- Linux: [`lime setup linux` Documentation](https://lime.openfl.org/docs/advanced-setup/linux/)
|
- Linux: [`lime setup linux` Documentation](https://lime.openfl.org/docs/advanced-setup/linux/)
|
||||||
- HTML5: Compiles without any extra setup
|
- HTML5: Compiles without any extra setup
|
||||||
7. If you are targeting for native, you may need to run `lime rebuild PLATFORM` and `lime rebuild PLATFORM -debug`
|
9. If you are targeting for native, you may need to run `lime rebuild <PLATFORM>` and `lime rebuild <PLATFORM> -debug`
|
||||||
8. `lime test PLATFORM` ! Add `-debug` to enable several debug features such as time travel (`PgUp`/`PgDn` in Play State).
|
10. `lime test <PLATFORM>` to build and launch the game for your platform (for example, `lime test windows`)
|
||||||
|
|
||||||
|
## Build Flags
|
||||||
|
|
||||||
|
There are several useful build flags you can add to a build to affect how it works. A full list can be found in `project.hxp`, but here's information on some of them:
|
||||||
|
|
||||||
|
- `-debug` to build the game in debug mode. This automatically enables several useful debug features.
|
||||||
|
- This includes enabling in-game debug functions, disables compile-time optimizations, enabling asset redirection (see below), and enabling the VSCode debug server (which can slow the game on some machines but allows for powerful debugging through breakpoints).
|
||||||
|
- `-DGITHUB_BUILD` will enable in-game debug functions (such as the ability to time travel in a song by pressing `PgUp`/`PgDn`), without enabling the other stuff
|
||||||
|
- `-DFEATURE_POLYMOD_MODS` or `-DNO_FEATURE_POLYMOD_MODS` to forcibly enable or disable modding support.
|
||||||
|
- `-DREDIRECT_ASSETS_FOLDER` or `-DNO_REDIRECT_ASSETS_FOLDER` to forcibly enable or disable asset redirection.
|
||||||
|
- This feature causes the game to load exported assets from the project's assets folder rather than the exported one. Great for fast iteration, but the game
|
||||||
|
- `-DFEATURE_DISCORD_RPC` or `-DNO_FEATURE_DISCORD_RPC` to forcibly enable or disable support for Discord Rich Presence.
|
||||||
|
- `-DFEATURE_VIDEO_PLAYBACK` or `-DNO_FEATURE_VIDEO_PLAYBACK` to forcibly enable or disable video cutscene support.
|
||||||
|
- `-DFEATURE_CHART_EDITOR` or `-DNO_FEATURE_CHART_EDITOR` to forcibly enable or disable the chart editor in the Debug menu.
|
||||||
|
- `-DFEATURE_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
|
# 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`.
|
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
|
- 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.
|
- 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`
|
- `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.
|
- 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**.
|
||||||
|
|
88
hmm.json
88
hmm.json
|
@ -1,51 +1,46 @@
|
||||||
{
|
{
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
{
|
||||||
|
"name": "FlxPartialSound",
|
||||||
|
"type": "git",
|
||||||
|
"dir": null,
|
||||||
|
"ref": "a1eab7b9bf507b87200a3341719054fe427f3b15",
|
||||||
|
"url": "https://github.com/FunkinCrew/FlxPartialSound.git"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "discord_rpc",
|
"name": "discord_rpc",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "2d83fa863ef0c1eace5f1cf67c3ac315d1a3a8a5",
|
"ref": "2d83fa863ef0c1eace5f1cf67c3ac315d1a3a8a5",
|
||||||
"url": "https://github.com/Aidan63/linc_discord-rpc"
|
"url": "https://github.com/FunkinCrew/linc_discord-rpc"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "flixel",
|
"name": "flixel",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "a7d8e3bad89a0a3506a4714121f73d8e34522c49",
|
"ref": "f2b090d6c608471e730b051c8ee22b8b378964b1",
|
||||||
"url": "https://github.com/FunkinCrew/flixel"
|
"url": "https://github.com/FunkinCrew/flixel"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "flixel-addons",
|
"name": "flixel-addons",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "a523c3b56622f0640933944171efed46929e360e",
|
"ref": "9c6fb47968e894eb36bf10e94725cd7640c49281",
|
||||||
"url": "https://github.com/FunkinCrew/flixel-addons"
|
"url": "https://github.com/FunkinCrew/flixel-addons"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "flixel-text-input",
|
"name": "flixel-text-input",
|
||||||
"type": "haxelib",
|
|
||||||
"version": "1.1.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "flixel-ui",
|
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "d0afed7293c71ffdb1184751317fc709b44c9056",
|
"ref": "951a0103a17bfa55eed86703ce50b4fb0d7590bc",
|
||||||
"url": "https://github.com/HaxeFlixel/flixel-ui"
|
"url": "https://github.com/FunkinCrew/flixel-text-input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "flxanimate",
|
"name": "flxanimate",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "17e0d59fdbc2b6283a5c0e4df41f1c7f27b71c49",
|
"ref": "0654797e5eb7cd7de0c1b2dbaa1efe5a1e1d9412",
|
||||||
"url": "https://github.com/FunkinCrew/flxanimate"
|
"url": "https://github.com/Dot-Stuff/flxanimate"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "FlxPartialSound",
|
|
||||||
"type": "git",
|
|
||||||
"dir": null,
|
|
||||||
"ref": "f986332ba5ab02abd386ce662578baf04904604a",
|
|
||||||
"url": "https://github.com/FunkinCrew/FlxPartialSound.git"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "format",
|
"name": "format",
|
||||||
|
@ -56,7 +51,7 @@
|
||||||
"name": "funkin.vis",
|
"name": "funkin.vis",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "38261833590773cb1de34ac5d11e0825696fc340",
|
"ref": "22b1ce089dd924f15cdc4632397ef3504d464e90",
|
||||||
"url": "https://github.com/FunkinCrew/funkVis"
|
"url": "https://github.com/FunkinCrew/funkVis"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -75,20 +70,22 @@
|
||||||
"name": "haxeui-core",
|
"name": "haxeui-core",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "0212d8fdfcafeb5f0d5a41e1ddba8ff21d0e183b",
|
"ref": "22f7c5a8ffca90d4677cffd6e570f53761709fbc",
|
||||||
"url": "https://github.com/haxeui/haxeui-core"
|
"url": "https://github.com/haxeui/haxeui-core"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "haxeui-flixel",
|
"name": "haxeui-flixel",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "63a906a6148958dbfde8c7b48d90b0693767fd95",
|
"ref": "28bb710d0ae5d94b5108787593052165be43b980",
|
||||||
"url": "https://github.com/haxeui/haxeui-flixel"
|
"url": "https://github.com/haxeui/haxeui-flixel"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "hscript",
|
"name": "hscript",
|
||||||
"type": "haxelib",
|
"type": "git",
|
||||||
"version": "2.5.0"
|
"dir": null,
|
||||||
|
"ref": "12785398e2f07082f05034cb580682e5671442a2",
|
||||||
|
"url": "https://github.com/FunkinCrew/hscript"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "hxCodec",
|
"name": "hxCodec",
|
||||||
|
@ -99,8 +96,10 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "hxcpp",
|
"name": "hxcpp",
|
||||||
"type": "haxelib",
|
"type": "git",
|
||||||
"version": "4.3.2"
|
"dir": null,
|
||||||
|
"ref": "904ea40643b050a5a154c5e4c33a83fd2aec18b1",
|
||||||
|
"url": "https://github.com/HaxeFoundation/hxcpp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "hxcpp-debug-server",
|
"name": "hxcpp-debug-server",
|
||||||
|
@ -109,10 +108,17 @@
|
||||||
"ref": "147294123f983e35f50a966741474438069a7a8f",
|
"ref": "147294123f983e35f50a966741474438069a7a8f",
|
||||||
"url": "https://github.com/FunkinCrew/hxcpp-debugger"
|
"url": "https://github.com/FunkinCrew/hxcpp-debugger"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "hxjsonast",
|
||||||
|
"type": "git",
|
||||||
|
"dir": null,
|
||||||
|
"ref": "20e72cc68c823496359775ac1f06500e67f189d5",
|
||||||
|
"url": "https://github.com/nadako/hxjsonast/"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "hxp",
|
"name": "hxp",
|
||||||
"type": "haxelib",
|
"type": "haxelib",
|
||||||
"version": "1.2.2"
|
"version": "1.3.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "json2object",
|
"name": "json2object",
|
||||||
|
@ -121,11 +127,25 @@
|
||||||
"ref": "a8c26f18463c98da32f744c214fe02273e1823fa",
|
"ref": "a8c26f18463c98da32f744c214fe02273e1823fa",
|
||||||
"url": "https://github.com/FunkinCrew/json2object"
|
"url": "https://github.com/FunkinCrew/json2object"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "jsonpatch",
|
||||||
|
"type": "git",
|
||||||
|
"dir": null,
|
||||||
|
"ref": "f9b83215acd586dc28754b4ae7f69d4c06c3b4d3",
|
||||||
|
"url": "https://github.com/EliteMasterEric/jsonpatch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "jsonpath",
|
||||||
|
"type": "git",
|
||||||
|
"dir": null,
|
||||||
|
"ref": "7a24193717b36393458c15c0435bb7c4470ecdda",
|
||||||
|
"url": "https://github.com/EliteMasterEric/jsonpath"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "lime",
|
"name": "lime",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "872ff6db2f2d27c0243d4ff76802121ded550dd7",
|
"ref": "fe3368f611a84a19afc03011353945ae4da8fffd",
|
||||||
"url": "https://github.com/FunkinCrew/lime"
|
"url": "https://github.com/FunkinCrew/lime"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -160,29 +180,29 @@
|
||||||
"name": "openfl",
|
"name": "openfl",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "228c1b5063911e2ad75cef6e3168ef0a4b9f9134",
|
"ref": "8306425c497766739510ab29e876059c96f77bd2",
|
||||||
"url": "https://github.com/FunkinCrew/openfl"
|
"url": "https://github.com/FunkinCrew/openfl"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "polymod",
|
"name": "polymod",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "bfbe30d81601b3543d80dce580108ad6b7e182c7",
|
"ref": "0fbdf27fe124549730accd540cec8a183f8652c0",
|
||||||
"url": "https://github.com/larsiusprime/polymod"
|
"url": "https://github.com/larsiusprime/polymod"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "thx.core",
|
"name": "thx.core",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "22605ff44f01971d599641790d6bae4869f7d9f4",
|
"ref": "76d87418fadd92eb8e1b61f004cff27d656e53dd",
|
||||||
"url": "https://github.com/FunkinCrew/thx.core"
|
"url": "https://github.com/fponticelli/thx.core"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "thx.semver",
|
"name": "thx.semver",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "cf8d213589a2c7ce4a59b0fdba9e8ff36bc029fa",
|
"ref": "bdb191fe7cf745c02a980749906dbf22719e200b",
|
||||||
"url": "https://github.com/FunkinCrew/thx.semver"
|
"url": "https://github.com/fponticelli/thx.semver"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
1109
project.hxp
Normal file
1109
project.hxp
Normal file
File diff suppressed because it is too large
Load diff
|
@ -22,7 +22,7 @@ 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 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 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.
|
var zoom:Float = -1; // If -1, zoom is automatically calculated to fit the window dimensions.
|
||||||
#if web
|
#if (web || CHEEMS)
|
||||||
var framerate:Int = 60; // How many frames per second the game should run at.
|
var framerate:Int = 60; // How many frames per second the game should run at.
|
||||||
#else
|
#else
|
||||||
// TODO: This should probably be in the options menu?
|
// TODO: This should probably be in the options menu?
|
||||||
|
@ -66,6 +66,12 @@ class Main extends Sprite
|
||||||
|
|
||||||
function init(?event:Event):Void
|
function init(?event:Event):Void
|
||||||
{
|
{
|
||||||
|
#if web
|
||||||
|
// set this variable (which is a function) from the lime version at lime/_internal/backend/html5/HTML5Application.hx
|
||||||
|
// The framerate cap will more thoroughly initialize via Preferences in InitState.hx
|
||||||
|
funkin.Preferences.lockedFramerateFunction = untyped js.Syntax.code("window.requestAnimationFrame");
|
||||||
|
#end
|
||||||
|
|
||||||
if (hasEventListener(Event.ADDED_TO_STAGE))
|
if (hasEventListener(Event.ADDED_TO_STAGE))
|
||||||
{
|
{
|
||||||
removeEventListener(Event.ADDED_TO_STAGE, init);
|
removeEventListener(Event.ADDED_TO_STAGE, init);
|
||||||
|
@ -113,7 +119,7 @@ class Main extends Sprite
|
||||||
|
|
||||||
addChild(game);
|
addChild(game);
|
||||||
|
|
||||||
#if debug
|
#if FEATURE_DEBUG_FUNCTIONS
|
||||||
game.debugger.interaction.addTool(new funkin.util.TrackerToolButtonUtil());
|
game.debugger.interaction.addTool(new funkin.util.TrackerToolButtonUtil());
|
||||||
#end
|
#end
|
||||||
|
|
||||||
|
|
38
source/funkin/Assets.hx
Normal file
38
source/funkin/Assets.hx
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
package funkin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getBytes(path:String):haxe.io.Bytes
|
||||||
|
{
|
||||||
|
return openfl.utils.Assets.getBytes(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
||||||
|
{
|
||||||
|
return openfl.utils.Assets.list(type);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package funkin;
|
package funkin;
|
||||||
|
|
||||||
|
import funkin.data.freeplay.player.PlayerRegistry;
|
||||||
import funkin.ui.debug.charting.ChartEditorState;
|
import funkin.ui.debug.charting.ChartEditorState;
|
||||||
import funkin.ui.transition.LoadingState;
|
import funkin.ui.transition.LoadingState;
|
||||||
import flixel.FlxState;
|
import flixel.FlxState;
|
||||||
|
@ -18,6 +19,7 @@ import funkin.play.PlayStatePlaylist;
|
||||||
import openfl.display.BitmapData;
|
import openfl.display.BitmapData;
|
||||||
import funkin.data.story.level.LevelRegistry;
|
import funkin.data.story.level.LevelRegistry;
|
||||||
import funkin.data.notestyle.NoteStyleRegistry;
|
import funkin.data.notestyle.NoteStyleRegistry;
|
||||||
|
import funkin.data.freeplay.style.FreeplayStyleRegistry;
|
||||||
import funkin.data.event.SongEventRegistry;
|
import funkin.data.event.SongEventRegistry;
|
||||||
import funkin.data.stage.StageRegistry;
|
import funkin.data.stage.StageRegistry;
|
||||||
import funkin.data.dialogue.conversation.ConversationRegistry;
|
import funkin.data.dialogue.conversation.ConversationRegistry;
|
||||||
|
@ -26,13 +28,14 @@ import funkin.data.dialogue.speaker.SpeakerRegistry;
|
||||||
import funkin.data.freeplay.album.AlbumRegistry;
|
import funkin.data.freeplay.album.AlbumRegistry;
|
||||||
import funkin.data.song.SongRegistry;
|
import funkin.data.song.SongRegistry;
|
||||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||||
|
import funkin.play.notes.notekind.NoteKindManager;
|
||||||
import funkin.modding.module.ModuleHandler;
|
import funkin.modding.module.ModuleHandler;
|
||||||
import funkin.ui.title.TitleState;
|
import funkin.ui.title.TitleState;
|
||||||
import funkin.util.CLIUtil;
|
import funkin.util.CLIUtil;
|
||||||
import funkin.util.CLIUtil.CLIParams;
|
import funkin.util.CLIUtil.CLIParams;
|
||||||
import funkin.util.TimerUtil;
|
import funkin.util.TimerUtil;
|
||||||
import funkin.util.TrackerUtil;
|
import funkin.util.TrackerUtil;
|
||||||
#if discord_rpc
|
#if FEATURE_DISCORD_RPC
|
||||||
import Discord.DiscordClient;
|
import Discord.DiscordClient;
|
||||||
#end
|
#end
|
||||||
|
|
||||||
|
@ -121,7 +124,7 @@ class InitState extends FlxState
|
||||||
//
|
//
|
||||||
// DISCORD API SETUP
|
// DISCORD API SETUP
|
||||||
//
|
//
|
||||||
#if discord_rpc
|
#if FEATURE_DISCORD_RPC
|
||||||
DiscordClient.initialize();
|
DiscordClient.initialize();
|
||||||
|
|
||||||
Application.current.onExit.add(function(exitCode) {
|
Application.current.onExit.add(function(exitCode) {
|
||||||
|
@ -142,7 +145,7 @@ class InitState extends FlxState
|
||||||
// Plugins provide a useful interface for globally active Flixel objects,
|
// Plugins provide a useful interface for globally active Flixel objects,
|
||||||
// that receive update events regardless of the current state.
|
// that receive update events regardless of the current state.
|
||||||
// TODO: Move scripted Module behavior to a Flixel plugin.
|
// TODO: Move scripted Module behavior to a Flixel plugin.
|
||||||
#if debug
|
#if FEATURE_DEBUG_FUNCTIONS
|
||||||
funkin.util.plugins.MemoryGCPlugin.initialize();
|
funkin.util.plugins.MemoryGCPlugin.initialize();
|
||||||
#end
|
#end
|
||||||
funkin.util.plugins.EvacuateDebugPlugin.initialize();
|
funkin.util.plugins.EvacuateDebugPlugin.initialize();
|
||||||
|
@ -164,9 +167,11 @@ class InitState extends FlxState
|
||||||
SongRegistry.instance.loadEntries();
|
SongRegistry.instance.loadEntries();
|
||||||
LevelRegistry.instance.loadEntries();
|
LevelRegistry.instance.loadEntries();
|
||||||
NoteStyleRegistry.instance.loadEntries();
|
NoteStyleRegistry.instance.loadEntries();
|
||||||
|
PlayerRegistry.instance.loadEntries();
|
||||||
ConversationRegistry.instance.loadEntries();
|
ConversationRegistry.instance.loadEntries();
|
||||||
DialogueBoxRegistry.instance.loadEntries();
|
DialogueBoxRegistry.instance.loadEntries();
|
||||||
SpeakerRegistry.instance.loadEntries();
|
SpeakerRegistry.instance.loadEntries();
|
||||||
|
FreeplayStyleRegistry.instance.loadEntries();
|
||||||
AlbumRegistry.instance.loadEntries();
|
AlbumRegistry.instance.loadEntries();
|
||||||
StageRegistry.instance.loadEntries();
|
StageRegistry.instance.loadEntries();
|
||||||
|
|
||||||
|
@ -174,6 +179,8 @@ class InitState extends FlxState
|
||||||
// Move it to use a BaseRegistry.
|
// Move it to use a BaseRegistry.
|
||||||
CharacterDataParser.loadCharacterCache();
|
CharacterDataParser.loadCharacterCache();
|
||||||
|
|
||||||
|
NoteKindManager.loadScripts();
|
||||||
|
|
||||||
ModuleHandler.buildModuleCallbacks();
|
ModuleHandler.buildModuleCallbacks();
|
||||||
ModuleHandler.loadModuleCache();
|
ModuleHandler.loadModuleCache();
|
||||||
ModuleHandler.callOnCreate();
|
ModuleHandler.callOnCreate();
|
||||||
|
@ -218,9 +225,10 @@ class InitState extends FlxState
|
||||||
// -DRESULTS
|
// -DRESULTS
|
||||||
FlxG.switchState(() -> new funkin.play.ResultState(
|
FlxG.switchState(() -> new funkin.play.ResultState(
|
||||||
{
|
{
|
||||||
storyMode: false,
|
storyMode: true,
|
||||||
title: "Cum Song Erect by Kawai Sprite",
|
title: "Cum Song Erect by Kawai Sprite",
|
||||||
songId: "cum",
|
songId: "cum",
|
||||||
|
characterId: "pico-playable",
|
||||||
difficultyId: "nightmare",
|
difficultyId: "nightmare",
|
||||||
isNewHighscore: true,
|
isNewHighscore: true,
|
||||||
scoreData:
|
scoreData:
|
||||||
|
@ -236,8 +244,13 @@ class InitState extends FlxState
|
||||||
combo: 69,
|
combo: 69,
|
||||||
maxCombo: 69,
|
maxCombo: 69,
|
||||||
totalNotesHit: 140,
|
totalNotesHit: 140,
|
||||||
totalNotes: 200 // 0,
|
totalNotes: 190
|
||||||
}
|
}
|
||||||
|
// 2400 total notes = 7% = LOSS
|
||||||
|
// 240 total notes = 79% = GOOD
|
||||||
|
// 230 total notes = 82% = GREAT
|
||||||
|
// 210 total notes = 91% = EXCELLENT
|
||||||
|
// 190 total notes = PERFECT
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
#elseif ANIMDEBUG
|
#elseif ANIMDEBUG
|
||||||
|
@ -363,11 +376,16 @@ class InitState extends FlxState
|
||||||
//
|
//
|
||||||
// FLIXEL DEBUG SETUP
|
// FLIXEL DEBUG SETUP
|
||||||
//
|
//
|
||||||
#if (debug || FORCE_DEBUG_VERSION)
|
#if FEATURE_DEBUG_FUNCTIONS
|
||||||
// Make errors and warnings less annoying.
|
trace('Initializing Flixel debugger...');
|
||||||
// Forcing this always since I have never been happy to have the debugger to pop up
|
|
||||||
|
#if !debug
|
||||||
|
// Make errors less annoying on release builds.
|
||||||
LogStyle.ERROR.openConsole = false;
|
LogStyle.ERROR.openConsole = false;
|
||||||
LogStyle.ERROR.errorSound = null;
|
LogStyle.ERROR.errorSound = null;
|
||||||
|
#end
|
||||||
|
|
||||||
|
// Make errors and warnings less annoying.
|
||||||
LogStyle.WARNING.openConsole = false;
|
LogStyle.WARNING.openConsole = false;
|
||||||
LogStyle.WARNING.errorSound = null;
|
LogStyle.WARNING.errorSound = null;
|
||||||
|
|
||||||
|
|
|
@ -11,10 +11,17 @@ class Paths
|
||||||
{
|
{
|
||||||
static var currentLevel:Null<String> = null;
|
static var currentLevel:Null<String> = null;
|
||||||
|
|
||||||
public static function setCurrentLevel(name:String):Void
|
public static function setCurrentLevel(name:Null<String>):Void
|
||||||
|
{
|
||||||
|
if (name == null)
|
||||||
|
{
|
||||||
|
currentLevel = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
currentLevel = name.toLowerCase();
|
currentLevel = name.toLowerCase();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static function stripLibrary(path:String):String
|
public static function stripLibrary(path:String):String
|
||||||
{
|
{
|
||||||
|
|
|
@ -128,6 +128,48 @@ class Preferences
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static var unlockedFramerate(get, set):Bool;
|
||||||
|
|
||||||
|
static function get_unlockedFramerate():Bool
|
||||||
|
{
|
||||||
|
return Save?.instance?.options?.unlockedFramerate;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function set_unlockedFramerate(value:Bool):Bool
|
||||||
|
{
|
||||||
|
if (value != Save.instance.options.unlockedFramerate)
|
||||||
|
{
|
||||||
|
#if web
|
||||||
|
toggleFramerateCap(value);
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
|
var save:Save = Save.instance;
|
||||||
|
save.options.unlockedFramerate = value;
|
||||||
|
save.flush();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if web
|
||||||
|
// We create a haxe version of this just for readability.
|
||||||
|
// We use these to override `window.requestAnimationFrame` in Javascript to uncap the framerate / "animation" request rate
|
||||||
|
// Javascript is crazy since u can just do stuff like that lol
|
||||||
|
|
||||||
|
public static function unlockedFramerateFunction(callback, element)
|
||||||
|
{
|
||||||
|
var currTime = Date.now().getTime();
|
||||||
|
var timeToCall = 0;
|
||||||
|
var id = js.Browser.window.setTimeout(function() {
|
||||||
|
callback(currTime + timeToCall);
|
||||||
|
}, timeToCall);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lime already implements their own little framerate cap, so we can just use that
|
||||||
|
// This also gets set in the init function in Main.hx, since we need to definitely override it
|
||||||
|
public static var lockedFramerateFunction = untyped js.Syntax.code("window.requestAnimationFrame");
|
||||||
|
#end
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the user's preferences from the save data and apply them.
|
* Loads the user's preferences from the save data and apply them.
|
||||||
*/
|
*/
|
||||||
|
@ -137,6 +179,17 @@ class Preferences
|
||||||
FlxG.autoPause = Preferences.autoPause;
|
FlxG.autoPause = Preferences.autoPause;
|
||||||
// Apply the debugDisplay setting (enables the FPS and RAM display).
|
// Apply the debugDisplay setting (enables the FPS and RAM display).
|
||||||
toggleDebugDisplay(Preferences.debugDisplay);
|
toggleDebugDisplay(Preferences.debugDisplay);
|
||||||
|
#if web
|
||||||
|
toggleFramerateCap(Preferences.unlockedFramerate);
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
|
static function toggleFramerateCap(unlocked:Bool):Void
|
||||||
|
{
|
||||||
|
#if web
|
||||||
|
var framerateFunction = unlocked ? unlockedFramerateFunction : lockedFramerateFunction;
|
||||||
|
untyped js.Syntax.code("window.requestAnimationFrame = framerateFunction;");
|
||||||
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
static function toggleDebugDisplay(show:Bool):Void
|
static function toggleDebugDisplay(show:Bool):Void
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
package funkin.api.discord;
|
package funkin.api.discord;
|
||||||
|
|
||||||
import Sys.sleep;
|
import Sys.sleep;
|
||||||
#if discord_rpc
|
#if FEATURE_DISCORD_RPC
|
||||||
import discord_rpc.DiscordRpc;
|
import discord_rpc.DiscordRpc;
|
||||||
#end
|
#end
|
||||||
|
|
||||||
class DiscordClient
|
class DiscordClient
|
||||||
{
|
{
|
||||||
#if discord_rpc
|
#if FEATURE_DISCORD_RPC
|
||||||
public function new()
|
public function new()
|
||||||
{
|
{
|
||||||
trace("Discord Client starting...");
|
trace("Discord Client starting...");
|
||||||
|
|
|
@ -24,7 +24,7 @@ class NGUnsafe
|
||||||
NG.core.calls.event.logEvent(event).send();
|
NG.core.calls.event.logEvent(event).send();
|
||||||
trace('should have logged: ' + event);
|
trace('should have logged: ' + event);
|
||||||
#else
|
#else
|
||||||
#if debug
|
#if FEATURE_DEBUG_FUNCTIONS
|
||||||
trace('event:$event - not logged, missing NG.io lib');
|
trace('event:$event - not logged, missing NG.io lib');
|
||||||
#end
|
#end
|
||||||
#end
|
#end
|
||||||
|
@ -39,7 +39,7 @@ class NGUnsafe
|
||||||
if (!medal.unlocked) medal.sendUnlock();
|
if (!medal.unlocked) medal.sendUnlock();
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
#if debug
|
#if FEATURE_DEBUG_FUNCTIONS
|
||||||
trace('medal:$id - not unlocked, missing NG.io lib');
|
trace('medal:$id - not unlocked, missing NG.io lib');
|
||||||
#end
|
#end
|
||||||
#end
|
#end
|
||||||
|
@ -63,7 +63,7 @@ class NGUnsafe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
#if debug
|
#if FEATURE_DEBUG_FUNCTIONS
|
||||||
trace('Song:$song, Score:$score - not posted, missing NG.io lib');
|
trace('Song:$song, Score:$score - not posted, missing NG.io lib');
|
||||||
#end
|
#end
|
||||||
#end
|
#end
|
||||||
|
|
|
@ -239,7 +239,7 @@ class NGio
|
||||||
NG.core.calls.event.logEvent(event).send();
|
NG.core.calls.event.logEvent(event).send();
|
||||||
trace('should have logged: ' + event);
|
trace('should have logged: ' + event);
|
||||||
#else
|
#else
|
||||||
#if debug
|
#if FEATURE_DEBUG_FUNCTIONS
|
||||||
trace('event:$event - not logged, missing NG.io lib');
|
trace('event:$event - not logged, missing NG.io lib');
|
||||||
#end
|
#end
|
||||||
#end
|
#end
|
||||||
|
@ -254,7 +254,7 @@ class NGio
|
||||||
if (!medal.unlocked) medal.sendUnlock();
|
if (!medal.unlocked) medal.sendUnlock();
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
#if debug
|
#if FEATURE_DEBUG_FUNCTIONS
|
||||||
trace('medal:$id - not unlocked, missing NG.io lib');
|
trace('medal:$id - not unlocked, missing NG.io lib');
|
||||||
#end
|
#end
|
||||||
#end
|
#end
|
||||||
|
@ -278,7 +278,7 @@ class NGio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
#if debug
|
#if FEATURE_DEBUG_FUNCTIONS
|
||||||
trace('Song:$song, Score:$score - not posted, missing NG.io lib');
|
trace('Song:$song, Score:$score - not posted, missing NG.io lib');
|
||||||
#end
|
#end
|
||||||
#end
|
#end
|
||||||
|
|
|
@ -340,6 +340,8 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||||
if (songMusicData != null)
|
if (songMusicData != null)
|
||||||
{
|
{
|
||||||
Conductor.instance.mapTimeChanges(songMusicData.timeChanges);
|
Conductor.instance.mapTimeChanges(songMusicData.timeChanges);
|
||||||
|
|
||||||
|
if (songMusicData.looped != null && params.loop == null) params.loop = songMusicData.looped;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -388,7 +390,7 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var music = FunkinSound.load(pathToUse, params?.startingVolume ?? 1.0, params.loop ?? true, false, true);
|
var music = FunkinSound.load(pathToUse, params?.startingVolume ?? 1.0, params.loop ?? true, false, true, params.onComplete);
|
||||||
if (music != null)
|
if (music != null)
|
||||||
{
|
{
|
||||||
FlxG.sound.music = music;
|
FlxG.sound.music = music;
|
||||||
|
@ -396,6 +398,8 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||||
// Prevent repeat update() and onFocus() calls.
|
// Prevent repeat update() and onFocus() calls.
|
||||||
FlxG.sound.list.remove(FlxG.sound.music);
|
FlxG.sound.list.remove(FlxG.sound.music);
|
||||||
|
|
||||||
|
if (FlxG.sound.music != null && params.onLoad != null) params.onLoad();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -491,8 +495,10 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||||
var promise:lime.app.Promise<Null<FunkinSound>> = new lime.app.Promise<Null<FunkinSound>>();
|
var promise:lime.app.Promise<Null<FunkinSound>> = new lime.app.Promise<Null<FunkinSound>>();
|
||||||
|
|
||||||
// split the path and get only after first :
|
// split the path and get only after first :
|
||||||
// we are bypassing the openfl/lime asset library fuss
|
// we are bypassing the openfl/lime asset library fuss on web only
|
||||||
|
#if web
|
||||||
path = Paths.stripLibrary(path);
|
path = Paths.stripLibrary(path);
|
||||||
|
#end
|
||||||
|
|
||||||
var soundRequest = FlxPartialSound.partialLoadFromFile(path, start, end);
|
var soundRequest = FlxPartialSound.partialLoadFromFile(path, start, end);
|
||||||
|
|
||||||
|
@ -533,11 +539,12 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||||
* Play a sound effect once, then destroy it.
|
* Play a sound effect once, then destroy it.
|
||||||
* @param key
|
* @param key
|
||||||
* @param volume
|
* @param volume
|
||||||
* @return static function construct():FunkinSound
|
* @return A `FunkinSound` object, or `null` if the sound could not be loaded.
|
||||||
*/
|
*/
|
||||||
public static function playOnce(key:String, volume:Float = 1.0, ?onComplete:Void->Void, ?onLoad:Void->Void):Void
|
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, onComplete, onLoad);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -562,6 +569,14 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||||
|
|
||||||
return sound;
|
return sound;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produces a string representation suitable for debugging.
|
||||||
|
*/
|
||||||
|
public override function toString():String
|
||||||
|
{
|
||||||
|
return 'FunkinSound(${this._label})';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -113,6 +113,11 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
|
||||||
public function play(forceRestart:Bool = false, startTime:Float = 0.0, ?endTime:Float)
|
public function play(forceRestart:Bool = false, startTime:Float = 0.0, ?endTime:Float)
|
||||||
{
|
{
|
||||||
forEachAlive(function(sound:FunkinSound) {
|
forEachAlive(function(sound:FunkinSound) {
|
||||||
|
if (sound.length < startTime)
|
||||||
|
{
|
||||||
|
// trace('Queuing sound (${sound.toString()} past its length! Skipping...)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
sound.play(forceRestart, startTime, endTime);
|
sound.play(forceRestart, startTime, endTime);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
||||||
public function initAnalyzer()
|
public function initAnalyzer()
|
||||||
{
|
{
|
||||||
@:privateAccess
|
@:privateAccess
|
||||||
analyzer = new SpectralAnalyzer(snd._channel.__source, 7, 0.1, 40);
|
analyzer = new SpectralAnalyzer(snd._channel.__audioSource, 7, 0.1, 40);
|
||||||
|
|
||||||
#if desktop
|
#if desktop
|
||||||
// On desktop it uses FFT stuff that isn't as optimized as the direct browser stuff we use on HTML5
|
// On desktop it uses FFT stuff that isn't as optimized as the direct browser stuff we use on HTML5
|
||||||
|
@ -102,7 +102,9 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
||||||
var animFrame:Int = Math.round(levels[i].value * 5);
|
var animFrame:Int = Math.round(levels[i].value * 5);
|
||||||
|
|
||||||
#if desktop
|
#if desktop
|
||||||
animFrame = Math.round(animFrame * FlxG.sound.volume);
|
// Web version scales with the Flixel volume level.
|
||||||
|
// This line brings platform parity but looks worse.
|
||||||
|
// animFrame = Math.round(animFrame * FlxG.sound.volume);
|
||||||
#end
|
#end
|
||||||
|
|
||||||
animFrame = Math.floor(Math.min(5, animFrame));
|
animFrame = Math.floor(Math.min(5, animFrame));
|
||||||
|
|
|
@ -117,7 +117,7 @@ class VisShit
|
||||||
{
|
{
|
||||||
// Math.pow3
|
// Math.pow3
|
||||||
@:privateAccess
|
@:privateAccess
|
||||||
var buf = snd._channel.__source.buffer;
|
var buf = snd._channel.__audioSource.buffer;
|
||||||
|
|
||||||
// @:privateAccess
|
// @:privateAccess
|
||||||
audioData = cast buf.data; // jank and hacky lol! kinda busted on HTML5 also!!
|
audioData = cast buf.data; // jank and hacky lol! kinda busted on HTML5 also!!
|
||||||
|
|
|
@ -16,7 +16,7 @@ class WaveformDataParser
|
||||||
|
|
||||||
// Method 1. This only works if the sound has been played before.
|
// Method 1. This only works if the sound has been played before.
|
||||||
@:privateAccess
|
@:privateAccess
|
||||||
var soundBuffer:Null<lime.media.AudioBuffer> = sound?._channel?.__source?.buffer;
|
var soundBuffer:Null<lime.media.AudioBuffer> = sound?._channel?.__audioSource?.buffer;
|
||||||
|
|
||||||
if (soundBuffer == null)
|
if (soundBuffer == null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -263,7 +263,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||||
* @param version The entry's version (use `fetchEntryVersion(id)`).
|
* @param version The entry's version (use `fetchEntryVersion(id)`).
|
||||||
* @return The created entry.
|
* @return The created entry.
|
||||||
*/
|
*/
|
||||||
public function parseEntryDataWithMigration(id:String, version:thx.semver.Version):Null<J>
|
public function parseEntryDataWithMigration(id:String, version:Null<thx.semver.Version>):Null<J>
|
||||||
{
|
{
|
||||||
if (version == null)
|
if (version == null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -46,7 +46,7 @@ class SongEventRegistry
|
||||||
|
|
||||||
if (event != null)
|
if (event != null)
|
||||||
{
|
{
|
||||||
trace(' Loaded built-in song event: (${event.id})');
|
trace(' Loaded built-in song event: ${event.id}');
|
||||||
eventCache.set(event.id, event);
|
eventCache.set(event.id, event);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -59,9 +59,9 @@ class SongEventRegistry
|
||||||
static function registerScriptedEvents()
|
static function registerScriptedEvents()
|
||||||
{
|
{
|
||||||
var scriptedEventClassNames:Array<String> = ScriptedSongEvent.listScriptClasses();
|
var scriptedEventClassNames:Array<String> = ScriptedSongEvent.listScriptClasses();
|
||||||
|
trace('Instantiating ${scriptedEventClassNames.length} scripted song events...');
|
||||||
if (scriptedEventClassNames == null || scriptedEventClassNames.length == 0) return;
|
if (scriptedEventClassNames == null || scriptedEventClassNames.length == 0) return;
|
||||||
|
|
||||||
trace('Instantiating ${scriptedEventClassNames.length} scripted song events...');
|
|
||||||
for (eventCls in scriptedEventClassNames)
|
for (eventCls in scriptedEventClassNames)
|
||||||
{
|
{
|
||||||
var event:SongEvent = ScriptedSongEvent.init(eventCls, "UKNOWN");
|
var event:SongEvent = ScriptedSongEvent.init(eventCls, "UKNOWN");
|
||||||
|
|
9
source/funkin/data/freeplay/player/CHANGELOG.md
Normal file
9
source/funkin/data/freeplay/player/CHANGELOG.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# Freeplay Playable Character Data Schema Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.0.0]
|
||||||
|
Initial release.
|
380
source/funkin/data/freeplay/player/PlayerData.hx
Normal file
380
source/funkin/data/freeplay/player/PlayerData.hx
Normal file
|
@ -0,0 +1,380 @@
|
||||||
|
package funkin.data.freeplay.player;
|
||||||
|
|
||||||
|
import funkin.data.animation.AnimationData;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
|
class PlayerData
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The sematic version number of the player data JSON format.
|
||||||
|
* Supports fancy comparisons like NPM does it's neat.
|
||||||
|
*/
|
||||||
|
@:default(funkin.data.freeplay.player.PlayerRegistry.PLAYER_DATA_VERSION)
|
||||||
|
public var version:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A readable name for this playable character.
|
||||||
|
*/
|
||||||
|
public var name:String = 'Unknown';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The character IDs this character is associated with.
|
||||||
|
* Only songs that use these characters will show up in Freeplay.
|
||||||
|
*/
|
||||||
|
@:default([])
|
||||||
|
public var ownedChars:Array<String> = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to show songs with character IDs that aren't associated with any specific character.
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
@:default(false)
|
||||||
|
public var showUnownedChars:Bool = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Which freeplay style to use for this character.
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
@:default("bf")
|
||||||
|
public var freeplayStyle:String = Constants.DEFAULT_FREEPLAY_STYLE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data for displaying this character in the Freeplay menu.
|
||||||
|
* If null, display no DJ.
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
public var freeplayDJ:Null<PlayerFreeplayDJData> = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data for displaying this character in the Character Select menu.
|
||||||
|
* If null, exclude from Character Select.
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
public var charSelect:Null<PlayerCharSelectData> = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data for displaying this character in the results screen.
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
public var results:Null<PlayerResultsData> = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this character is unlocked by default.
|
||||||
|
* Use a ScriptedPlayableCharacter to add custom logic.
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
@:default(true)
|
||||||
|
public var unlocked:Bool = true;
|
||||||
|
|
||||||
|
public function new()
|
||||||
|
{
|
||||||
|
this.version = PlayerRegistry.PLAYER_DATA_VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert this StageData into a JSON string.
|
||||||
|
*/
|
||||||
|
public function serialize(pretty:Bool = true):String
|
||||||
|
{
|
||||||
|
// Update generatedBy and version before writing.
|
||||||
|
updateVersionToLatest();
|
||||||
|
|
||||||
|
var writer = new json2object.JsonWriter<PlayerData>();
|
||||||
|
return writer.write(this, pretty ? ' ' : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateVersionToLatest():Void
|
||||||
|
{
|
||||||
|
this.version = PlayerRegistry.PLAYER_DATA_VERSION;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PlayerFreeplayDJData
|
||||||
|
{
|
||||||
|
var assetPath:String;
|
||||||
|
var animations:Array<AnimationData>;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
@:default("BOYFRIEND")
|
||||||
|
var text1:String;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
@:default("HOT BLOODED IN MORE WAYS THAN ONE")
|
||||||
|
var text2:String;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
@:default("PROTECT YO NUTS")
|
||||||
|
var text3:String;
|
||||||
|
|
||||||
|
@:jignored
|
||||||
|
var animationMap:Map<String, AnimationData>;
|
||||||
|
|
||||||
|
@:jignored
|
||||||
|
var prefixToOffsetsMap:Map<String, Array<Float>>;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
var charSelect:Null<PlayerFreeplayDJCharSelectData>;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
var cartoon:Null<PlayerFreeplayDJCartoonData>;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
var fistPump:Null<PlayerFreeplayDJFistPumpData>;
|
||||||
|
|
||||||
|
public function new()
|
||||||
|
{
|
||||||
|
animationMap = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapAnimations()
|
||||||
|
{
|
||||||
|
if (animationMap == null) animationMap = new Map();
|
||||||
|
if (prefixToOffsetsMap == null) prefixToOffsetsMap = new Map();
|
||||||
|
|
||||||
|
animationMap.clear();
|
||||||
|
prefixToOffsetsMap.clear();
|
||||||
|
for (anim in animations)
|
||||||
|
{
|
||||||
|
animationMap.set(anim.name, anim);
|
||||||
|
prefixToOffsetsMap.set(anim.prefix, anim.offsets);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAtlasPath():String
|
||||||
|
{
|
||||||
|
return Paths.animateAtlas(assetPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFreeplayDJText(index:Int):String
|
||||||
|
{
|
||||||
|
switch (index)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
return text1;
|
||||||
|
case 2:
|
||||||
|
return text2;
|
||||||
|
case 3:
|
||||||
|
return text3;
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAnimationPrefix(name:String):Null<String>
|
||||||
|
{
|
||||||
|
if (animationMap.size() == 0) mapAnimations();
|
||||||
|
|
||||||
|
var anim = animationMap.get(name);
|
||||||
|
if (anim == null) return null;
|
||||||
|
return anim.prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAnimationOffsetsByPrefix(?prefix:String):Array<Float>
|
||||||
|
{
|
||||||
|
if (prefixToOffsetsMap.size() == 0) mapAnimations();
|
||||||
|
if (prefix == null) return [0, 0];
|
||||||
|
return prefixToOffsetsMap.get(prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAnimationOffsets(name:String):Array<Float>
|
||||||
|
{
|
||||||
|
return getAnimationOffsetsByPrefix(getAnimationPrefix(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: These should really be frame labels, ehe.
|
||||||
|
|
||||||
|
public function getCartoonSoundClickFrame():Int
|
||||||
|
{
|
||||||
|
return cartoon?.soundClickFrame ?? 80;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCartoonSoundCartoonFrame():Int
|
||||||
|
{
|
||||||
|
return cartoon?.soundCartoonFrame ?? 85;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCartoonLoopBlinkFrame():Int
|
||||||
|
{
|
||||||
|
return cartoon?.loopBlinkFrame ?? 112;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCartoonLoopFrame():Int
|
||||||
|
{
|
||||||
|
return cartoon?.loopFrame ?? 166;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCartoonChannelChangeFrame():Int
|
||||||
|
{
|
||||||
|
return cartoon?.channelChangeFrame ?? 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFistPumpIntroStartFrame():Int
|
||||||
|
{
|
||||||
|
return fistPump?.introStartFrame ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFistPumpIntroEndFrame():Int
|
||||||
|
{
|
||||||
|
return fistPump?.introEndFrame ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFistPumpLoopStartFrame():Int
|
||||||
|
{
|
||||||
|
return fistPump?.loopStartFrame ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFistPumpLoopEndFrame():Int
|
||||||
|
{
|
||||||
|
return fistPump?.loopEndFrame ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFistPumpIntroBadStartFrame():Int
|
||||||
|
{
|
||||||
|
return fistPump?.introBadStartFrame ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFistPumpIntroBadEndFrame():Int
|
||||||
|
{
|
||||||
|
return fistPump?.introBadEndFrame ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFistPumpLoopBadStartFrame():Int
|
||||||
|
{
|
||||||
|
return fistPump?.loopBadStartFrame ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFistPumpLoopBadEndFrame():Int
|
||||||
|
{
|
||||||
|
return fistPump?.loopBadEndFrame ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCharSelectTransitionDelay():Float
|
||||||
|
{
|
||||||
|
return charSelect?.transitionDelay ?? 0.25;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PlayerCharSelectData
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* A zero-indexed number for the character's preferred position in the grid.
|
||||||
|
* 0 = top left, 4 = center, 8 = bottom right
|
||||||
|
* In the event of a conflict, the first character alphabetically gets it,
|
||||||
|
* and others get shifted over.
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
public var position:Null<Int>;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef PlayerResultsData =
|
||||||
|
{
|
||||||
|
var music:PlayerResultsMusicData;
|
||||||
|
|
||||||
|
var perfect:Array<PlayerResultsAnimationData>;
|
||||||
|
var excellent:Array<PlayerResultsAnimationData>;
|
||||||
|
var great:Array<PlayerResultsAnimationData>;
|
||||||
|
var good:Array<PlayerResultsAnimationData>;
|
||||||
|
var loss:Array<PlayerResultsAnimationData>;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef PlayerResultsMusicData =
|
||||||
|
{
|
||||||
|
@:optional
|
||||||
|
var PERFECT_GOLD:String;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
var PERFECT:String;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
var EXCELLENT:String;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
var GREAT:String;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
var GOOD:String;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
var SHIT:String;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef PlayerResultsAnimationData =
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* `sparrow` or `animate` or whatever
|
||||||
|
*/
|
||||||
|
var renderType:String;
|
||||||
|
|
||||||
|
var assetPath:String;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
@:default([0, 0])
|
||||||
|
var offsets:Array<Float>;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
@:default(500)
|
||||||
|
var zIndex:Int;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
@:default(0.0)
|
||||||
|
var delay:Float;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
@:default(1.0)
|
||||||
|
var scale:Float;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
@:default('')
|
||||||
|
var startFrameLabel:Null<String>;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
@:default(true)
|
||||||
|
var looped:Bool;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
var loopFrame:Null<Int>;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
var loopFrameLabel:Null<String>;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef PlayerFreeplayDJCharSelectData =
|
||||||
|
{
|
||||||
|
var transitionDelay:Float;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef PlayerFreeplayDJCartoonData =
|
||||||
|
{
|
||||||
|
var soundClickFrame:Int;
|
||||||
|
var soundCartoonFrame:Int;
|
||||||
|
var loopBlinkFrame:Int;
|
||||||
|
var loopFrame:Int;
|
||||||
|
var channelChangeFrame:Int;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef PlayerFreeplayDJFistPumpData =
|
||||||
|
{
|
||||||
|
@:default(0)
|
||||||
|
var introStartFrame:Int;
|
||||||
|
|
||||||
|
@:default(4)
|
||||||
|
var introEndFrame:Int;
|
||||||
|
|
||||||
|
@:default(4)
|
||||||
|
var loopStartFrame:Int;
|
||||||
|
|
||||||
|
@:default(-1)
|
||||||
|
var loopEndFrame:Int;
|
||||||
|
|
||||||
|
@:default(0)
|
||||||
|
var introBadStartFrame:Int;
|
||||||
|
|
||||||
|
@:default(4)
|
||||||
|
var introBadEndFrame:Int;
|
||||||
|
|
||||||
|
@:default(4)
|
||||||
|
var loopBadStartFrame:Int;
|
||||||
|
|
||||||
|
@:default(-1)
|
||||||
|
var loopBadEndFrame:Int;
|
||||||
|
};
|
208
source/funkin/data/freeplay/player/PlayerRegistry.hx
Normal file
208
source/funkin/data/freeplay/player/PlayerRegistry.hx
Normal file
|
@ -0,0 +1,208 @@
|
||||||
|
package funkin.data.freeplay.player;
|
||||||
|
|
||||||
|
import funkin.data.freeplay.player.PlayerData;
|
||||||
|
import funkin.ui.freeplay.charselect.PlayableCharacter;
|
||||||
|
import funkin.ui.freeplay.charselect.ScriptedPlayableCharacter;
|
||||||
|
import funkin.save.Save;
|
||||||
|
|
||||||
|
class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData>
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The current version string for the stage data format.
|
||||||
|
* Handle breaking changes by incrementing this value
|
||||||
|
* and adding migration to the `migratePlayerData()` function.
|
||||||
|
*/
|
||||||
|
public static final PLAYER_DATA_VERSION:thx.semver.Version = "1.0.0";
|
||||||
|
|
||||||
|
public static final PLAYER_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
|
||||||
|
|
||||||
|
public static var instance(get, never):PlayerRegistry;
|
||||||
|
static var _instance:Null<PlayerRegistry> = null;
|
||||||
|
|
||||||
|
static function get_instance():PlayerRegistry
|
||||||
|
{
|
||||||
|
if (_instance == null) _instance = new PlayerRegistry();
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A mapping between stage character IDs and Freeplay playable character IDs.
|
||||||
|
*/
|
||||||
|
var ownedCharacterIds:Map<String, String> = [];
|
||||||
|
|
||||||
|
public function new()
|
||||||
|
{
|
||||||
|
super('PLAYER', 'players', PLAYER_DATA_VERSION_RULE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override function loadEntries():Void
|
||||||
|
{
|
||||||
|
super.loadEntries();
|
||||||
|
|
||||||
|
for (playerId in listEntryIds())
|
||||||
|
{
|
||||||
|
var player = fetchEntry(playerId);
|
||||||
|
if (player == null) continue;
|
||||||
|
|
||||||
|
var currentPlayerCharIds = player.getOwnedCharacterIds();
|
||||||
|
for (characterId in currentPlayerCharIds)
|
||||||
|
{
|
||||||
|
ownedCharacterIds.set(characterId, playerId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log('Loaded ${countEntries()} playable characters with ${ownedCharacterIds.size()} associations.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function countUnlockedCharacters():Int
|
||||||
|
{
|
||||||
|
var count = 0;
|
||||||
|
|
||||||
|
for (charId in listEntryIds())
|
||||||
|
{
|
||||||
|
var player = fetchEntry(charId);
|
||||||
|
if (player == null) continue;
|
||||||
|
|
||||||
|
if (player.isUnlocked()) count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasNewCharacter():Bool
|
||||||
|
{
|
||||||
|
var charactersSeen = Save.instance.charactersSeen.clone();
|
||||||
|
|
||||||
|
for (charId in listEntryIds())
|
||||||
|
{
|
||||||
|
var player = fetchEntry(charId);
|
||||||
|
if (player == null) continue;
|
||||||
|
|
||||||
|
if (!player.isUnlocked()) continue;
|
||||||
|
if (charactersSeen.contains(charId)) continue;
|
||||||
|
|
||||||
|
// This character is unlocked but we haven't seen them in Freeplay yet.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallthrough case.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function listNewCharacters():Array<String>
|
||||||
|
{
|
||||||
|
var charactersSeen = Save.instance.charactersSeen.clone();
|
||||||
|
var result = [];
|
||||||
|
|
||||||
|
for (charId in listEntryIds())
|
||||||
|
{
|
||||||
|
var player = fetchEntry(charId);
|
||||||
|
if (player == null) continue;
|
||||||
|
|
||||||
|
if (!player.isUnlocked()) continue;
|
||||||
|
if (charactersSeen.contains(charId)) continue;
|
||||||
|
|
||||||
|
// This character is unlocked but we haven't seen them in Freeplay yet.
|
||||||
|
result.push(charId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the playable character associated with a given stage character.
|
||||||
|
* @param characterId The stage character ID.
|
||||||
|
* @return The playable character.
|
||||||
|
*/
|
||||||
|
public function getCharacterOwnerId(characterId:Null<String>):Null<String>
|
||||||
|
{
|
||||||
|
if (characterId == null) return null;
|
||||||
|
return ownedCharacterIds[characterId];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the given stage character is associated with a specific playable character.
|
||||||
|
* If so, the level should only appear if that character is selected in Freeplay.
|
||||||
|
* @param characterId The stage character ID.
|
||||||
|
* @return Whether the character is owned by any one character.
|
||||||
|
*/
|
||||||
|
public function isCharacterOwned(characterId:String):Bool
|
||||||
|
{
|
||||||
|
return ownedCharacterIds.exists(characterId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
||||||
|
*/
|
||||||
|
public function parseEntryData(id:String):Null<PlayerData>
|
||||||
|
{
|
||||||
|
// JsonParser does not take type parameters,
|
||||||
|
// otherwise this function would be in BaseRegistry.
|
||||||
|
var parser = new json2object.JsonParser<PlayerData>();
|
||||||
|
parser.ignoreUnknownVariables = false;
|
||||||
|
|
||||||
|
switch (loadEntryFile(id))
|
||||||
|
{
|
||||||
|
case {fileName: fileName, contents: contents}:
|
||||||
|
parser.fromJson(contents, fileName);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parser.errors.length > 0)
|
||||||
|
{
|
||||||
|
printErrors(parser.errors, id);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return parser.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse and validate the JSON data and produce the corresponding data object.
|
||||||
|
*
|
||||||
|
* NOTE: Must be implemented on the implementation class.
|
||||||
|
* @param contents The JSON as a string.
|
||||||
|
* @param fileName An optional file name for error reporting.
|
||||||
|
*/
|
||||||
|
public function parseEntryDataRaw(contents:String, ?fileName:String):Null<PlayerData>
|
||||||
|
{
|
||||||
|
var parser = new json2object.JsonParser<PlayerData>();
|
||||||
|
parser.ignoreUnknownVariables = false;
|
||||||
|
parser.fromJson(contents, fileName);
|
||||||
|
|
||||||
|
if (parser.errors.length > 0)
|
||||||
|
{
|
||||||
|
printErrors(parser.errors, fileName);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return parser.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createScriptedEntry(clsName:String):PlayableCharacter
|
||||||
|
{
|
||||||
|
return ScriptedPlayableCharacter.init(clsName, "unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
function getScriptedClassNames():Array<String>
|
||||||
|
{
|
||||||
|
return ScriptedPlayableCharacter.listScriptClasses();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of all the playable characters from the base game, in order.
|
||||||
|
*/
|
||||||
|
public function listBaseGamePlayerIds():Array<String>
|
||||||
|
{
|
||||||
|
return ["bf", "pico"];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of all installed playable characters that are not from the base game.
|
||||||
|
*/
|
||||||
|
public function listModdedPlayerIds():Array<String>
|
||||||
|
{
|
||||||
|
return listEntryIds().filter(function(id:String):Bool {
|
||||||
|
return listBaseGamePlayerIds().indexOf(id) == -1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
9
source/funkin/data/freeplay/style/CHANGELOG.md
Normal file
9
source/funkin/data/freeplay/style/CHANGELOG.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# Freeplay Style Data Schema Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.0.0]
|
||||||
|
Initial release.
|
48
source/funkin/data/freeplay/style/FreeplayStyleData.hx
Normal file
48
source/funkin/data/freeplay/style/FreeplayStyleData.hx
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
package funkin.data.freeplay.style;
|
||||||
|
|
||||||
|
import funkin.data.animation.AnimationData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A type definition for the data for an album of songs.
|
||||||
|
* It includes things like what graphics to display in Freeplay.
|
||||||
|
* @see https://lib.haxe.org/p/json2object/
|
||||||
|
*/
|
||||||
|
typedef FreeplayStyleData =
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Semantic version for style data.
|
||||||
|
*/
|
||||||
|
public var version:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asset key for the background image.
|
||||||
|
*/
|
||||||
|
public var bgAsset:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asset key for the difficulty selector image.
|
||||||
|
*/
|
||||||
|
public var selectorAsset:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asset key for the numbers shown at the top right of the screen.
|
||||||
|
*/
|
||||||
|
public var numbersAsset:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asset key for the freeplay capsules.
|
||||||
|
*/
|
||||||
|
public var capsuleAsset:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Color data for the capsule text outline.
|
||||||
|
* the order of this array goes as follows: [DESELECTED, SELECTED]
|
||||||
|
*/
|
||||||
|
public var capsuleTextColors:Array<String>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delay time after confirming a song selection, before entering PlayState.
|
||||||
|
* Useful for letting longer animations play out.
|
||||||
|
*/
|
||||||
|
public var startDelay:Float;
|
||||||
|
}
|
84
source/funkin/data/freeplay/style/FreeplayStyleRegistry.hx
Normal file
84
source/funkin/data/freeplay/style/FreeplayStyleRegistry.hx
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
package funkin.data.freeplay.style;
|
||||||
|
|
||||||
|
import funkin.ui.freeplay.FreeplayStyle;
|
||||||
|
import funkin.data.freeplay.style.FreeplayStyleData;
|
||||||
|
import funkin.ui.freeplay.ScriptedFreeplayStyle;
|
||||||
|
|
||||||
|
class FreeplayStyleRegistry extends BaseRegistry<FreeplayStyle, FreeplayStyleData>
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The current version string for the style data format.
|
||||||
|
* Handle breaking changes by incrementing this value
|
||||||
|
* and adding migration to the `migrateStyleData()` function.
|
||||||
|
*/
|
||||||
|
public static final FREEPLAYSTYLE_DATA_VERSION:thx.semver.Version = '1.0.0';
|
||||||
|
|
||||||
|
public static final FREEPLAYSTYLE_DATA_VERSION_RULE:thx.semver.VersionRule = '1.0.x';
|
||||||
|
|
||||||
|
public static final instance:FreeplayStyleRegistry = new FreeplayStyleRegistry();
|
||||||
|
|
||||||
|
public function new()
|
||||||
|
{
|
||||||
|
super('FREEPLAYSTYLE', 'ui/freeplay/styles', FREEPLAYSTYLE_DATA_VERSION_RULE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
||||||
|
* @param id The ID of the entry to load.
|
||||||
|
* @return The parsed data object.
|
||||||
|
*/
|
||||||
|
public function parseEntryData(id:String):Null<FreeplayStyleData>
|
||||||
|
{
|
||||||
|
// JsonParser does not take type parameters,
|
||||||
|
// otherwise this function would be in BaseRegistry.
|
||||||
|
var parser:json2object.JsonParser<FreeplayStyleData> = new json2object.JsonParser<FreeplayStyleData>();
|
||||||
|
parser.ignoreUnknownVariables = false;
|
||||||
|
|
||||||
|
switch (loadEntryFile(id))
|
||||||
|
{
|
||||||
|
case {fileName: fileName, contents: contents}:
|
||||||
|
parser.fromJson(contents, fileName);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parser.errors.length > 0)
|
||||||
|
{
|
||||||
|
printErrors(parser.errors, id);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return parser.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse and validate the JSON data and produce the corresponding data object.
|
||||||
|
*
|
||||||
|
* NOTE: Must be implemented on the implementation class.
|
||||||
|
* @param contents The JSON as a string.
|
||||||
|
* @param fileName An optional file name for error reporting.
|
||||||
|
* @return The parsed data object.
|
||||||
|
*/
|
||||||
|
public function parseEntryDataRaw(contents:String, ?fileName:String):Null<FreeplayStyleData>
|
||||||
|
{
|
||||||
|
var parser:json2object.JsonParser<FreeplayStyleData> = new json2object.JsonParser<FreeplayStyleData>();
|
||||||
|
parser.ignoreUnknownVariables = false;
|
||||||
|
parser.fromJson(contents, fileName);
|
||||||
|
|
||||||
|
if (parser.errors.length > 0)
|
||||||
|
{
|
||||||
|
printErrors(parser.errors, fileName);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return parser.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createScriptedEntry(clsName:String):FreeplayStyle
|
||||||
|
{
|
||||||
|
return ScriptedFreeplayStyle.init(clsName, 'unknown');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getScriptedClassNames():Array<String>
|
||||||
|
{
|
||||||
|
return ScriptedFreeplayStyle.listScriptClasses();
|
||||||
|
}
|
||||||
|
}
|
31
source/funkin/data/notestyle/CHANGELOG.md
Normal file
31
source/funkin/data/notestyle/CHANGELOG.md
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# Note Style Data Schema Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.1.0]
|
||||||
|
### Added
|
||||||
|
- Added several new `assets`:
|
||||||
|
- `countdownThree`
|
||||||
|
- `countdownTwo`
|
||||||
|
- `countdownOne`
|
||||||
|
- `countdownGo`
|
||||||
|
- `judgementSick`
|
||||||
|
- `judgementGood`
|
||||||
|
- `judgementBad`
|
||||||
|
- `judgementShit`
|
||||||
|
- `comboNumber0`
|
||||||
|
- `comboNumber1`
|
||||||
|
- `comboNumber2`
|
||||||
|
- `comboNumber3`
|
||||||
|
- `comboNumber4`
|
||||||
|
- `comboNumber5`
|
||||||
|
- `comboNumber6`
|
||||||
|
- `comboNumber7`
|
||||||
|
- `comboNumber8`
|
||||||
|
- `comboNumber9`
|
||||||
|
|
||||||
|
## [1.0.0]
|
||||||
|
Initial version.
|
|
@ -74,6 +74,84 @@ typedef NoteStyleAssetsData =
|
||||||
*/
|
*/
|
||||||
@:optional
|
@:optional
|
||||||
var holdNoteCover:NoteStyleAssetData<NoteStyleData_HoldNoteCover>;
|
var holdNoteCover:NoteStyleAssetData<NoteStyleData_HoldNoteCover>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The THREE sound (and an optional pre-READY graphic).
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
var countdownThree:NoteStyleAssetData<NoteStyleData_Countdown>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The TWO sound and READY graphic.
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
var countdownTwo:NoteStyleAssetData<NoteStyleData_Countdown>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ONE sound and SET graphic.
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
var countdownOne:NoteStyleAssetData<NoteStyleData_Countdown>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GO sound and GO! graphic.
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
var countdownGo:NoteStyleAssetData<NoteStyleData_Countdown>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The SICK! judgement.
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
var judgementSick:NoteStyleAssetData<NoteStyleData_Judgement>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GOOD! judgement.
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
var judgementGood:NoteStyleAssetData<NoteStyleData_Judgement>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The BAD! judgement.
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
var judgementBad:NoteStyleAssetData<NoteStyleData_Judgement>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The SHIT! judgement.
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
var judgementShit:NoteStyleAssetData<NoteStyleData_Judgement>;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
var comboNumber0:NoteStyleAssetData<NoteStyleData_ComboNum>;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
var comboNumber1:NoteStyleAssetData<NoteStyleData_ComboNum>;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
var comboNumber2:NoteStyleAssetData<NoteStyleData_ComboNum>;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
var comboNumber3:NoteStyleAssetData<NoteStyleData_ComboNum>;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
var comboNumber4:NoteStyleAssetData<NoteStyleData_ComboNum>;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
var comboNumber5:NoteStyleAssetData<NoteStyleData_ComboNum>;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
var comboNumber6:NoteStyleAssetData<NoteStyleData_ComboNum>;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
var comboNumber7:NoteStyleAssetData<NoteStyleData_ComboNum>;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
var comboNumber8:NoteStyleAssetData<NoteStyleData_ComboNum>;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
var comboNumber9:NoteStyleAssetData<NoteStyleData_ComboNum>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -109,10 +187,19 @@ typedef NoteStyleAssetData<T> =
|
||||||
@:optional
|
@:optional
|
||||||
var isPixel:Bool;
|
var isPixel:Bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, animations will be played on the graphic.
|
||||||
|
* @default `false` to save performance.
|
||||||
|
*/
|
||||||
|
@:default(false)
|
||||||
|
@:optional
|
||||||
|
var animated:Bool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The structure of this data depends on the asset.
|
* The structure of this data depends on the asset.
|
||||||
*/
|
*/
|
||||||
var data:T;
|
@:optional
|
||||||
|
var data:Null<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef NoteStyleData_Note =
|
typedef NoteStyleData_Note =
|
||||||
|
@ -123,7 +210,14 @@ typedef NoteStyleData_Note =
|
||||||
var right:UnnamedAnimationData;
|
var right:UnnamedAnimationData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef NoteStyleData_Countdown =
|
||||||
|
{
|
||||||
|
var audioPath:String;
|
||||||
|
}
|
||||||
|
|
||||||
typedef NoteStyleData_HoldNote = {}
|
typedef NoteStyleData_HoldNote = {}
|
||||||
|
typedef NoteStyleData_Judgement = {}
|
||||||
|
typedef NoteStyleData_ComboNum = {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data on animations for each direction of the strumline.
|
* Data on animations for each direction of the strumline.
|
||||||
|
|
|
@ -11,9 +11,9 @@ class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData>
|
||||||
* Handle breaking changes by incrementing this value
|
* Handle breaking changes by incrementing this value
|
||||||
* and adding migration to the `migrateNoteStyleData()` function.
|
* and adding migration to the `migrateNoteStyleData()` function.
|
||||||
*/
|
*/
|
||||||
public static final NOTE_STYLE_DATA_VERSION:thx.semver.Version = "1.0.0";
|
public static final NOTE_STYLE_DATA_VERSION:thx.semver.Version = "1.1.0";
|
||||||
|
|
||||||
public static final NOTE_STYLE_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
|
public static final NOTE_STYLE_DATA_VERSION_RULE:thx.semver.VersionRule = "1.1.x";
|
||||||
|
|
||||||
public static var instance(get, never):NoteStyleRegistry;
|
public static var instance(get, never):NoteStyleRegistry;
|
||||||
static var _instance:Null<NoteStyleRegistry> = null;
|
static var _instance:Null<NoteStyleRegistry> = null;
|
||||||
|
|
|
@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [2.2.4]
|
||||||
|
### Added
|
||||||
|
- Added `playData.characters.opponentVocals` to specify which vocal track(s) to play for the opponent.
|
||||||
|
- If the value isn't present, it will use the `playData.characters.opponent`, but if it is present, it will be used (even if it's empty, in which case no vocals will be used for the opponent)
|
||||||
|
- Added `playData.characters.playerVocals` to specify which vocal track(s) to play for the player.
|
||||||
|
- If the value isn't present, it will use the `playData.characters.player`, but if it is present, it will be used (even if it's empty, in which case no vocals will be used for the player)
|
||||||
|
- Added `offsets.altVocals` field to apply vocal offsets when alternate instrumentals are used.
|
||||||
|
|
||||||
|
|
||||||
## [2.2.3]
|
## [2.2.3]
|
||||||
### Added
|
### Added
|
||||||
- Added `charter` field to denote authorship of a chart.
|
- Added `charter` field to denote authorship of a chart.
|
||||||
|
|
|
@ -257,18 +257,27 @@ class SongOffsets implements ICloneable<SongOffsets>
|
||||||
public var altInstrumentals:Map<String, Float>;
|
public var altInstrumentals:Map<String, Float>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The offset, in milliseconds, to apply to the song's vocals, relative to the chart.
|
* The offset, in milliseconds, to apply to the song's vocals, relative to the song's base instrumental.
|
||||||
* These are applied ON TOP OF the instrumental offset.
|
* These are applied ON TOP OF the instrumental offset.
|
||||||
*/
|
*/
|
||||||
@:optional
|
@:optional
|
||||||
@:default([])
|
@:default([])
|
||||||
public var vocals:Map<String, Float>;
|
public var vocals:Map<String, Float>;
|
||||||
|
|
||||||
public function new(instrumental:Float = 0.0, ?altInstrumentals:Map<String, Float>, ?vocals:Map<String, Float>)
|
/**
|
||||||
|
* The offset, in milliseconds, to apply to the songs vocals, relative to each alternate instrumental.
|
||||||
|
* This is useful for the circumstance where, for example, an alt instrumental has a few seconds of lead in before the song starts.
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
@:default([])
|
||||||
|
public var altVocals:Map<String, Map<String, Float>>;
|
||||||
|
|
||||||
|
public function new(instrumental:Float = 0.0, ?altInstrumentals:Map<String, Float>, ?vocals:Map<String, Float>, ?altVocals:Map<String, Map<String, Float>>)
|
||||||
{
|
{
|
||||||
this.instrumental = instrumental;
|
this.instrumental = instrumental;
|
||||||
this.altInstrumentals = altInstrumentals == null ? new Map<String, Float>() : altInstrumentals;
|
this.altInstrumentals = altInstrumentals == null ? new Map<String, Float>() : altInstrumentals;
|
||||||
this.vocals = vocals == null ? new Map<String, Float>() : vocals;
|
this.vocals = vocals == null ? new Map<String, Float>() : vocals;
|
||||||
|
this.altVocals = altVocals == null ? new Map<String, Map<String, Float>>() : altVocals;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getInstrumentalOffset(?instrumental:String):Float
|
public function getInstrumentalOffset(?instrumental:String):Float
|
||||||
|
@ -293,12 +302,20 @@ class SongOffsets implements ICloneable<SongOffsets>
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getVocalOffset(charId:String):Float
|
public function getVocalOffset(charId:String, ?instrumental:String):Float
|
||||||
|
{
|
||||||
|
if (instrumental == null)
|
||||||
{
|
{
|
||||||
if (!this.vocals.exists(charId)) return 0.0;
|
if (!this.vocals.exists(charId)) return 0.0;
|
||||||
|
|
||||||
return this.vocals.get(charId);
|
return this.vocals.get(charId);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!this.altVocals.exists(instrumental)) return 0.0;
|
||||||
|
if (!this.altVocals.get(instrumental).exists(charId)) return 0.0;
|
||||||
|
return this.altVocals.get(instrumental).get(charId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function setVocalOffset(charId:String, value:Float):Float
|
public function setVocalOffset(charId:String, value:Float):Float
|
||||||
{
|
{
|
||||||
|
@ -320,7 +337,7 @@ class SongOffsets implements ICloneable<SongOffsets>
|
||||||
*/
|
*/
|
||||||
public function toString():String
|
public function toString():String
|
||||||
{
|
{
|
||||||
return 'SongOffsets(${this.instrumental}ms, ${this.altInstrumentals}, ${this.vocals})';
|
return 'SongOffsets(${this.instrumental}ms, ${this.altInstrumentals}, ${this.vocals}, ${this.altVocals})';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -529,12 +546,26 @@ class SongCharacterData implements ICloneable<SongCharacterData>
|
||||||
@:default([])
|
@:default([])
|
||||||
public var altInstrumentals:Array<String> = [];
|
public var altInstrumentals:Array<String> = [];
|
||||||
|
|
||||||
public function new(player:String = '', girlfriend:String = '', opponent:String = '', instrumental:String = '')
|
@:optional
|
||||||
|
public var opponentVocals:Null<Array<String>> = null;
|
||||||
|
|
||||||
|
@:optional
|
||||||
|
public var playerVocals:Null<Array<String>> = null;
|
||||||
|
|
||||||
|
public function new(player:String = '', girlfriend:String = '', opponent:String = '', instrumental:String = '', ?altInstrumentals:Array<String>,
|
||||||
|
?opponentVocals:Array<String>, ?playerVocals:Array<String>)
|
||||||
{
|
{
|
||||||
this.player = player;
|
this.player = player;
|
||||||
this.girlfriend = girlfriend;
|
this.girlfriend = girlfriend;
|
||||||
this.opponent = opponent;
|
this.opponent = opponent;
|
||||||
this.instrumental = instrumental;
|
this.instrumental = instrumental;
|
||||||
|
|
||||||
|
this.altInstrumentals = altInstrumentals;
|
||||||
|
this.opponentVocals = opponentVocals;
|
||||||
|
this.playerVocals = playerVocals;
|
||||||
|
|
||||||
|
if (opponentVocals == null) this.opponentVocals = [opponent];
|
||||||
|
if (playerVocals == null) this.playerVocals = [player];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function clone():SongCharacterData
|
public function clone():SongCharacterData
|
||||||
|
@ -722,18 +753,6 @@ class SongEventDataRaw implements ICloneable<SongEventDataRaw>
|
||||||
{
|
{
|
||||||
return new SongEventDataRaw(this.time, this.eventKind, this.value);
|
return new SongEventDataRaw(this.time, this.eventKind, this.value);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wrap SongEventData in an abstract so we can overload operators.
|
|
||||||
*/
|
|
||||||
@:forward(time, eventKind, value, activated, getStepTime, clone)
|
|
||||||
abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataRaw
|
|
||||||
{
|
|
||||||
public function new(time:Float, eventKind:String, value:Dynamic = null)
|
|
||||||
{
|
|
||||||
this = new SongEventDataRaw(time, eventKind, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function valueAsStruct(?defaultKey:String = "key"):Dynamic
|
public function valueAsStruct(?defaultKey:String = "key"):Dynamic
|
||||||
{
|
{
|
||||||
|
@ -757,27 +776,27 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline function getHandler():Null<SongEvent>
|
public function getHandler():Null<SongEvent>
|
||||||
{
|
{
|
||||||
return SongEventRegistry.getEvent(this.eventKind);
|
return SongEventRegistry.getEvent(this.eventKind);
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline function getSchema():Null<SongEventSchema>
|
public function getSchema():Null<SongEventSchema>
|
||||||
{
|
{
|
||||||
return SongEventRegistry.getEventSchema(this.eventKind);
|
return SongEventRegistry.getEventSchema(this.eventKind);
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline function getDynamic(key:String):Null<Dynamic>
|
public function getDynamic(key:String):Null<Dynamic>
|
||||||
{
|
{
|
||||||
return this.value == null ? null : Reflect.field(this.value, key);
|
return this.value == null ? null : Reflect.field(this.value, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline function getBool(key:String):Null<Bool>
|
public function getBool(key:String):Null<Bool>
|
||||||
{
|
{
|
||||||
return this.value == null ? null : cast Reflect.field(this.value, key);
|
return this.value == null ? null : cast Reflect.field(this.value, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline function getInt(key:String):Null<Int>
|
public function getInt(key:String):Null<Int>
|
||||||
{
|
{
|
||||||
if (this.value == null) return null;
|
if (this.value == null) return null;
|
||||||
var result = Reflect.field(this.value, key);
|
var result = Reflect.field(this.value, key);
|
||||||
|
@ -787,7 +806,7 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
|
||||||
return cast result;
|
return cast result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline function getFloat(key:String):Null<Float>
|
public function getFloat(key:String):Null<Float>
|
||||||
{
|
{
|
||||||
if (this.value == null) return null;
|
if (this.value == null) return null;
|
||||||
var result = Reflect.field(this.value, key);
|
var result = Reflect.field(this.value, key);
|
||||||
|
@ -797,17 +816,17 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
|
||||||
return cast result;
|
return cast result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline function getString(key:String):String
|
public function getString(key:String):String
|
||||||
{
|
{
|
||||||
return this.value == null ? null : cast Reflect.field(this.value, key);
|
return this.value == null ? null : cast Reflect.field(this.value, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline function getArray(key:String):Array<Dynamic>
|
public function getArray(key:String):Array<Dynamic>
|
||||||
{
|
{
|
||||||
return this.value == null ? null : cast Reflect.field(this.value, key);
|
return this.value == null ? null : cast Reflect.field(this.value, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline function getBoolArray(key:String):Array<Bool>
|
public function getBoolArray(key:String):Array<Bool>
|
||||||
{
|
{
|
||||||
return this.value == null ? null : cast Reflect.field(this.value, key);
|
return this.value == null ? null : cast Reflect.field(this.value, key);
|
||||||
}
|
}
|
||||||
|
@ -839,6 +858,19 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap SongEventData in an abstract so we can overload operators.
|
||||||
|
*/
|
||||||
|
@:forward(time, eventKind, value, activated, getStepTime, clone, getHandler, getSchema, getDynamic, getBool, getInt, getFloat, getString, getArray,
|
||||||
|
getBoolArray, buildTooltip, valueAsStruct)
|
||||||
|
abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataRaw
|
||||||
|
{
|
||||||
|
public function new(time:Float, eventKind:String, value:Dynamic = null)
|
||||||
|
{
|
||||||
|
this = new SongEventDataRaw(time, eventKind, value);
|
||||||
|
}
|
||||||
|
|
||||||
public function clone():SongEventData
|
public function clone():SongEventData
|
||||||
{
|
{
|
||||||
|
@ -951,12 +983,18 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
|
||||||
return this.kind = value;
|
return this.kind = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function new(time:Float, data:Int, length:Float = 0, kind:String = '')
|
@:alias("p")
|
||||||
|
@:default([])
|
||||||
|
@:optional
|
||||||
|
public var params:Array<NoteParamData>;
|
||||||
|
|
||||||
|
public function new(time:Float, data:Int, length:Float = 0, kind:String = '', ?params:Array<NoteParamData>)
|
||||||
{
|
{
|
||||||
this.time = time;
|
this.time = time;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.length = length;
|
this.length = length;
|
||||||
this.kind = kind;
|
this.kind = kind;
|
||||||
|
this.params = params ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1051,9 +1089,19 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
|
||||||
_stepLength = null;
|
_stepLength = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function cloneParams():Array<NoteParamData>
|
||||||
|
{
|
||||||
|
var params:Array<NoteParamData> = [];
|
||||||
|
for (param in this.params)
|
||||||
|
{
|
||||||
|
params.push(param.clone());
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
public function clone():SongNoteDataRaw
|
public function clone():SongNoteDataRaw
|
||||||
{
|
{
|
||||||
return new SongNoteDataRaw(this.time, this.data, this.length, this.kind);
|
return new SongNoteDataRaw(this.time, this.data, this.length, this.kind, cloneParams());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toString():String
|
public function toString():String
|
||||||
|
@ -1069,9 +1117,9 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
|
||||||
@:forward
|
@:forward
|
||||||
abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw
|
abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw
|
||||||
{
|
{
|
||||||
public function new(time:Float, data:Int, length:Float = 0, kind:String = '')
|
public function new(time:Float, data:Int, length:Float = 0, kind:String = '', ?params:Array<NoteParamData>)
|
||||||
{
|
{
|
||||||
this = new SongNoteDataRaw(time, data, length, kind);
|
this = new SongNoteDataRaw(time, data, length, kind, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function buildDirectionName(data:Int, strumlineSize:Int = 4):String
|
public static function buildDirectionName(data:Int, strumlineSize:Int = 4):String
|
||||||
|
@ -1115,7 +1163,7 @@ abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw
|
||||||
if (other.kind == '' || this.kind == null) return false;
|
if (other.kind == '' || this.kind == null) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.time == other.time && this.data == other.data && this.length == other.length;
|
return this.time == other.time && this.data == other.data && this.length == other.length && this.params == other.params;
|
||||||
}
|
}
|
||||||
|
|
||||||
@:op(A != B)
|
@:op(A != B)
|
||||||
|
@ -1134,7 +1182,7 @@ abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw
|
||||||
if (other.kind == '') return true;
|
if (other.kind == '') return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.time != other.time || this.data != other.data || this.length != other.length;
|
return this.time != other.time || this.data != other.data || this.length != other.length || this.params != other.params;
|
||||||
}
|
}
|
||||||
|
|
||||||
@:op(A > B)
|
@:op(A > B)
|
||||||
|
@ -1171,7 +1219,7 @@ abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw
|
||||||
|
|
||||||
public function clone():SongNoteData
|
public function clone():SongNoteData
|
||||||
{
|
{
|
||||||
return new SongNoteData(this.time, this.data, this.length, this.kind);
|
return new SongNoteData(this.time, this.data, this.length, this.kind, this.params);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1183,3 +1231,30 @@ abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw
|
||||||
+ (this.kind != '' ? ' [kind: ${this.kind}])' : ')');
|
+ (this.kind != '' ? ' [kind: ${this.kind}])' : ')');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class NoteParamData implements ICloneable<NoteParamData>
|
||||||
|
{
|
||||||
|
@:alias("n")
|
||||||
|
public var name:String;
|
||||||
|
|
||||||
|
@:alias("v")
|
||||||
|
@:jcustomparse(funkin.data.DataParse.dynamicValue)
|
||||||
|
@:jcustomwrite(funkin.data.DataWrite.dynamicValue)
|
||||||
|
public var value:Dynamic;
|
||||||
|
|
||||||
|
public function new(name:String, value:Dynamic)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function clone():NoteParamData
|
||||||
|
{
|
||||||
|
return new NoteParamData(this.name, this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString():String
|
||||||
|
{
|
||||||
|
return 'NoteParamData(${this.name}, ${this.value})';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
||||||
* Handle breaking changes by incrementing this value
|
* Handle breaking changes by incrementing this value
|
||||||
* and adding migration to the `migrateStageData()` function.
|
* and adding migration to the `migrateStageData()` function.
|
||||||
*/
|
*/
|
||||||
public static final SONG_METADATA_VERSION:thx.semver.Version = "2.2.3";
|
public static final SONG_METADATA_VERSION:thx.semver.Version = "2.2.4";
|
||||||
|
|
||||||
public static final SONG_METADATA_VERSION_RULE:thx.semver.VersionRule = "2.2.x";
|
public static final SONG_METADATA_VERSION_RULE:thx.semver.VersionRule = "2.2.x";
|
||||||
|
|
||||||
|
|
|
@ -199,6 +199,8 @@ class FNFLegacyImporter
|
||||||
{
|
{
|
||||||
// Handle the dumb logic for mustHitSection.
|
// Handle the dumb logic for mustHitSection.
|
||||||
var noteData = note.data;
|
var noteData = note.data;
|
||||||
|
if (noteData < 0) continue; // Exclude Psych event notes.
|
||||||
|
if (noteData > (STRUMLINE_SIZE * 2)) noteData = noteData % (2 * STRUMLINE_SIZE); // Handle other engine event notes.
|
||||||
|
|
||||||
// Flip notes if mustHitSection is FALSE (not true lol).
|
// Flip notes if mustHitSection is FALSE (not true lol).
|
||||||
if (!mustHitSection)
|
if (!mustHitSection)
|
||||||
|
|
|
@ -140,12 +140,12 @@ typedef StageDataProp =
|
||||||
* If not zero, this prop will play an animation every X beats of the song.
|
* If not zero, this prop will play an animation every X beats of the song.
|
||||||
* This requires animations to be defined. If `danceLeft` and `danceRight` are defined,
|
* This requires animations to be defined. If `danceLeft` and `danceRight` are defined,
|
||||||
* they will alternated between, otherwise the `idle` animation will be used.
|
* they will alternated between, otherwise the `idle` animation will be used.
|
||||||
*
|
* Supports up to 0.25 precision.
|
||||||
* @default 0
|
* @default 0.0
|
||||||
*/
|
*/
|
||||||
@:default(0)
|
@:default(0.0)
|
||||||
@:optional
|
@:optional
|
||||||
var danceEvery:Int;
|
var danceEvery:Float;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* How much the prop scrolls relative to the camera. Used to create a parallax effect.
|
* How much the prop scrolls relative to the camera. Used to create a parallax effect.
|
||||||
|
|
|
@ -93,8 +93,8 @@ class StageRegistry extends BaseRegistry<Stage, StageData>
|
||||||
public function listBaseGameStageIds():Array<String>
|
public function listBaseGameStageIds():Array<String>
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
"mainStage", "spookyMansion", "phillyTrain", "limoRide", "mallXmas", "mallEvil", "school", "schoolEvil", "tankmanBattlefield", "phillyStreets",
|
"mainStage", "mainStageErect", "spookyMansion", "phillyTrain", "phillyTrainErect", "limoRide", "limoRideErect", "mallXmas", "mallXmasErect", "mallEvil",
|
||||||
"phillyBlazin",
|
"school", "schoolEvil", "tankmanBattlefield", "phillyStreets", "phillyStreetsErect", "phillyBlazin",
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -91,11 +91,13 @@ typedef LevelPropData =
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The frequency to bop at, in beats.
|
* The frequency to bop at, in beats.
|
||||||
* @default 1 = every beat, 2 = every other beat, etc.
|
* 1 = every beat, 2 = every other beat, etc.
|
||||||
|
* Supports up to 0.25 precision.
|
||||||
|
* @default 1.0
|
||||||
*/
|
*/
|
||||||
@:default(1)
|
@:default(1.0)
|
||||||
@:optional
|
@:optional
|
||||||
var danceEvery:Int;
|
var danceEvery:Float;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The offset on the position to render the prop at.
|
* The offset on the position to render the prop at.
|
||||||
|
|
106
source/funkin/effects/RetroCameraFade.hx
Normal file
106
source/funkin/effects/RetroCameraFade.hx
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
package funkin.effects;
|
||||||
|
|
||||||
|
import flixel.util.FlxTimer;
|
||||||
|
import flixel.FlxCamera;
|
||||||
|
import openfl.filters.ColorMatrixFilter;
|
||||||
|
|
||||||
|
class RetroCameraFade
|
||||||
|
{
|
||||||
|
// im lazy, but we only use this for week 6
|
||||||
|
// and also sorta yoinked for djflixel, lol !
|
||||||
|
public static function fadeWhite(camera:FlxCamera, camSteps:Int = 5, time:Float = 1):Void
|
||||||
|
{
|
||||||
|
var steps:Int = 0;
|
||||||
|
var stepsTotal:Int = camSteps;
|
||||||
|
|
||||||
|
new FlxTimer().start(time / stepsTotal, _ -> {
|
||||||
|
var V:Float = (1 / stepsTotal) * steps;
|
||||||
|
if (steps == stepsTotal) V = 1;
|
||||||
|
|
||||||
|
var matrix = [
|
||||||
|
1, 0, 0, 0, V * 255,
|
||||||
|
0, 1, 0, 0, V * 255,
|
||||||
|
0, 0, 1, 0, V * 255,
|
||||||
|
0, 0, 0, 1, 0
|
||||||
|
];
|
||||||
|
camera.filters = [new ColorMatrixFilter(matrix)];
|
||||||
|
steps++;
|
||||||
|
}, stepsTotal + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function fadeFromWhite(camera:FlxCamera, camSteps:Int = 5, time:Float = 1):Void
|
||||||
|
{
|
||||||
|
var steps:Int = camSteps;
|
||||||
|
var stepsTotal:Int = camSteps;
|
||||||
|
|
||||||
|
var matrixDerp = [
|
||||||
|
1, 0, 0, 0, 1.0 * 255,
|
||||||
|
0, 1, 0, 0, 1.0 * 255,
|
||||||
|
0, 0, 1, 0, 1.0 * 255,
|
||||||
|
0, 0, 0, 1, 0
|
||||||
|
];
|
||||||
|
camera.filters = [new ColorMatrixFilter(matrixDerp)];
|
||||||
|
|
||||||
|
new FlxTimer().start(time / stepsTotal, _ -> {
|
||||||
|
var V:Float = (1 / stepsTotal) * steps;
|
||||||
|
if (steps == stepsTotal) V = 1;
|
||||||
|
|
||||||
|
var matrix = [
|
||||||
|
1, 0, 0, 0, V * 255,
|
||||||
|
0, 1, 0, 0, V * 255,
|
||||||
|
0, 0, 1, 0, V * 255,
|
||||||
|
0, 0, 0, 1, 0
|
||||||
|
];
|
||||||
|
camera.filters = [new ColorMatrixFilter(matrix)];
|
||||||
|
steps--;
|
||||||
|
}, camSteps);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function fadeToBlack(camera:FlxCamera, camSteps:Int = 5, time:Float = 1):Void
|
||||||
|
{
|
||||||
|
var steps:Int = 0;
|
||||||
|
var stepsTotal:Int = camSteps;
|
||||||
|
|
||||||
|
new FlxTimer().start(time / stepsTotal, _ -> {
|
||||||
|
var V:Float = (1 / stepsTotal) * steps;
|
||||||
|
if (steps == stepsTotal) V = 1;
|
||||||
|
|
||||||
|
var matrix = [
|
||||||
|
1, 0, 0, 0, -V * 255,
|
||||||
|
0, 1, 0, 0, -V * 255,
|
||||||
|
0, 0, 1, 0, -V * 255,
|
||||||
|
0, 0, 0, 1, 0
|
||||||
|
];
|
||||||
|
camera.filters = [new ColorMatrixFilter(matrix)];
|
||||||
|
steps++;
|
||||||
|
}, camSteps);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function fadeBlack(camera:FlxCamera, camSteps:Int = 5, time:Float = 1):Void
|
||||||
|
{
|
||||||
|
var steps:Int = camSteps;
|
||||||
|
var stepsTotal:Int = camSteps;
|
||||||
|
|
||||||
|
var matrixDerp = [
|
||||||
|
1, 0, 0, 0, -1.0 * 255,
|
||||||
|
0, 1, 0, 0, -1.0 * 255,
|
||||||
|
0, 0, 1, 0, -1.0 * 255,
|
||||||
|
0, 0, 0, 1, 0
|
||||||
|
];
|
||||||
|
camera.filters = [new ColorMatrixFilter(matrixDerp)];
|
||||||
|
|
||||||
|
new FlxTimer().start(time / stepsTotal, _ -> {
|
||||||
|
var V:Float = (1 / stepsTotal) * steps;
|
||||||
|
if (steps == stepsTotal) V = 1;
|
||||||
|
|
||||||
|
var matrix = [
|
||||||
|
1, 0, 0, 0, -V * 255,
|
||||||
|
0, 1, 0, 0, -V * 255,
|
||||||
|
0, 0, 1, 0, -V * 255,
|
||||||
|
0, 0, 0, 1, 0
|
||||||
|
];
|
||||||
|
camera.filters = [new ColorMatrixFilter(matrix)];
|
||||||
|
steps--;
|
||||||
|
}, camSteps + 1);
|
||||||
|
}
|
||||||
|
}
|
419
source/funkin/graphics/FlxFilteredSprite.hx
Normal file
419
source/funkin/graphics/FlxFilteredSprite.hx
Normal file
|
@ -0,0 +1,419 @@
|
||||||
|
package funkin.graphics;
|
||||||
|
|
||||||
|
import flixel.FlxBasic;
|
||||||
|
import flixel.FlxCamera;
|
||||||
|
import flixel.FlxG;
|
||||||
|
import flixel.FlxSprite;
|
||||||
|
import flixel.graphics.FlxGraphic;
|
||||||
|
import flixel.graphics.frames.FlxFrame;
|
||||||
|
import flixel.math.FlxMatrix;
|
||||||
|
import flixel.math.FlxPoint;
|
||||||
|
import flixel.math.FlxRect;
|
||||||
|
import flixel.util.FlxColor;
|
||||||
|
import lime.graphics.cairo.Cairo;
|
||||||
|
import openfl.display.BitmapData;
|
||||||
|
import openfl.display.BlendMode;
|
||||||
|
import openfl.display.DisplayObjectRenderer;
|
||||||
|
import openfl.display.Graphics;
|
||||||
|
import openfl.display.OpenGLRenderer;
|
||||||
|
import openfl.display._internal.Context3DGraphics;
|
||||||
|
import openfl.display3D.Context3D;
|
||||||
|
import openfl.display3D.Context3DClearMask;
|
||||||
|
import openfl.filters.BitmapFilter;
|
||||||
|
import openfl.filters.BlurFilter;
|
||||||
|
import openfl.geom.ColorTransform;
|
||||||
|
import openfl.geom.Matrix;
|
||||||
|
import openfl.geom.Point;
|
||||||
|
import openfl.geom.Rectangle;
|
||||||
|
#if (js && html5)
|
||||||
|
import lime._internal.graphics.ImageCanvasUtil;
|
||||||
|
import openfl.display.CanvasRenderer;
|
||||||
|
import openfl.display._internal.CanvasGraphics as GfxRenderer;
|
||||||
|
#else
|
||||||
|
import openfl.display.CairoRenderer;
|
||||||
|
import openfl.display._internal.CairoGraphics as GfxRenderer;
|
||||||
|
#end
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A modified `FlxSprite` that supports filters.
|
||||||
|
* The name's pretty much self-explanatory.
|
||||||
|
*/
|
||||||
|
@:access(openfl.geom.Rectangle)
|
||||||
|
@:access(openfl.filters.BitmapFilter)
|
||||||
|
@:access(flixel.graphics.frames.FlxFrame)
|
||||||
|
class FlxFilteredSprite extends FlxSprite
|
||||||
|
{
|
||||||
|
@:noCompletion var _renderer:FlxAnimateFilterRenderer = new FlxAnimateFilterRenderer();
|
||||||
|
|
||||||
|
@:noCompletion var _filterMatrix:FlxMatrix;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An `Array` of shader filters (aka `BitmapFilter`).
|
||||||
|
*/
|
||||||
|
public var filters(default, set):Array<BitmapFilter>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* a flag to update the image with the filters.
|
||||||
|
* Useful when trying to render a shader at all times.
|
||||||
|
*/
|
||||||
|
public var filterDirty:Bool = false;
|
||||||
|
|
||||||
|
@:noCompletion var filtered:Bool;
|
||||||
|
|
||||||
|
@:noCompletion var _blankFrame:FlxFrame;
|
||||||
|
|
||||||
|
var _filterBmp1:BitmapData;
|
||||||
|
var _filterBmp2:BitmapData;
|
||||||
|
|
||||||
|
override public function update(elapsed:Float)
|
||||||
|
{
|
||||||
|
super.update(elapsed);
|
||||||
|
if (!filterDirty && filters != null)
|
||||||
|
{
|
||||||
|
for (filter in filters)
|
||||||
|
{
|
||||||
|
if (filter.__renderDirty)
|
||||||
|
{
|
||||||
|
filterDirty = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@:noCompletion
|
||||||
|
override function initVars():Void
|
||||||
|
{
|
||||||
|
super.initVars();
|
||||||
|
_filterMatrix = new FlxMatrix();
|
||||||
|
filters = null;
|
||||||
|
filtered = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function draw():Void
|
||||||
|
{
|
||||||
|
checkEmptyFrame();
|
||||||
|
|
||||||
|
if (alpha == 0 || _frame.type == FlxFrameType.EMPTY) return;
|
||||||
|
|
||||||
|
if (dirty) // rarely
|
||||||
|
calcFrame(useFramePixels);
|
||||||
|
|
||||||
|
if (filterDirty) filterFrame();
|
||||||
|
|
||||||
|
for (camera in cameras)
|
||||||
|
{
|
||||||
|
if (!camera.visible || !camera.exists || !isOnScreen(camera)) continue;
|
||||||
|
|
||||||
|
getScreenPosition(_point, camera).subtractPoint(offset);
|
||||||
|
|
||||||
|
if (isSimpleRender(camera)) drawSimple(camera);
|
||||||
|
else
|
||||||
|
drawComplex(camera);
|
||||||
|
|
||||||
|
#if FLX_DEBUG
|
||||||
|
FlxBasic.visibleCount++;
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
|
#if FLX_DEBUG
|
||||||
|
if (FlxG.debugger.drawDebug) drawDebug();
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
|
@:noCompletion
|
||||||
|
override function drawComplex(camera:FlxCamera):Void
|
||||||
|
{
|
||||||
|
_frame.prepareMatrix(_matrix, FlxFrameAngle.ANGLE_0, checkFlipX(), checkFlipY());
|
||||||
|
_matrix.concat(_filterMatrix);
|
||||||
|
_matrix.translate(-origin.x, -origin.y);
|
||||||
|
_matrix.scale(scale.x, scale.y);
|
||||||
|
|
||||||
|
if (bakedRotationAngle <= 0)
|
||||||
|
{
|
||||||
|
updateTrig();
|
||||||
|
|
||||||
|
if (angle != 0) _matrix.rotateWithTrig(_cosAngle, _sinAngle);
|
||||||
|
}
|
||||||
|
|
||||||
|
_point.add(origin.x, origin.y);
|
||||||
|
_matrix.translate(_point.x, _point.y);
|
||||||
|
|
||||||
|
if (isPixelPerfectRender(camera))
|
||||||
|
{
|
||||||
|
_matrix.tx = Math.floor(_matrix.tx);
|
||||||
|
_matrix.ty = Math.floor(_matrix.ty);
|
||||||
|
}
|
||||||
|
|
||||||
|
camera.drawPixels((filtered) ? _blankFrame : _frame, framePixels, _matrix, colorTransform, blend, antialiasing, shader);
|
||||||
|
}
|
||||||
|
|
||||||
|
@:noCompletion
|
||||||
|
function filterFrame()
|
||||||
|
{
|
||||||
|
filterDirty = false;
|
||||||
|
_filterMatrix.identity();
|
||||||
|
|
||||||
|
if (filters != null && filters.length > 0)
|
||||||
|
{
|
||||||
|
_flashRect.setEmpty();
|
||||||
|
|
||||||
|
for (filter in filters)
|
||||||
|
{
|
||||||
|
_flashRect.__expand(-filter.__leftExtension,
|
||||||
|
-filter.__topExtension, filter.__leftExtension
|
||||||
|
+ filter.__rightExtension,
|
||||||
|
filter.__topExtension
|
||||||
|
+ filter.__bottomExtension);
|
||||||
|
}
|
||||||
|
_flashRect.width += frameWidth;
|
||||||
|
_flashRect.height += frameHeight;
|
||||||
|
if (_blankFrame == null) _blankFrame = new FlxFrame(null);
|
||||||
|
|
||||||
|
if (_blankFrame.parent == null || _flashRect.width > _blankFrame.parent.width || _flashRect.height > _blankFrame.parent.height)
|
||||||
|
{
|
||||||
|
if (_blankFrame.parent != null)
|
||||||
|
{
|
||||||
|
_blankFrame.parent.destroy();
|
||||||
|
_filterBmp1.dispose();
|
||||||
|
_filterBmp2.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_blankFrame.parent = FlxGraphic.fromRectangle(Math.ceil(_flashRect.width * 1.25), Math.ceil(_flashRect.height * 1.25), 0, true);
|
||||||
|
_filterBmp1 = new BitmapData(_blankFrame.parent.width, _blankFrame.parent.height, 0);
|
||||||
|
_filterBmp2 = new BitmapData(_blankFrame.parent.width, _blankFrame.parent.height, 0);
|
||||||
|
}
|
||||||
|
_blankFrame.offset.copyFrom(_frame.offset);
|
||||||
|
_blankFrame.parent.bitmap = _renderer.applyFilter(_blankFrame.parent.bitmap, _filterBmp1, _filterBmp2, frame.parent.bitmap, filters, _flashRect,
|
||||||
|
frame.frame.copyToFlash());
|
||||||
|
_blankFrame.frame = FlxRect.get(0, 0, _blankFrame.parent.bitmap.width, _blankFrame.parent.bitmap.height);
|
||||||
|
_filterMatrix.translate(_flashRect.x, _flashRect.y);
|
||||||
|
_frame = _blankFrame.copyTo();
|
||||||
|
filtered = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
resetFrame();
|
||||||
|
filtered = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@:noCompletion
|
||||||
|
function set_filters(value:Array<BitmapFilter>)
|
||||||
|
{
|
||||||
|
if (filters != value) filterDirty = true;
|
||||||
|
|
||||||
|
return filters = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@:noCompletion
|
||||||
|
override function set_frame(value:FlxFrame)
|
||||||
|
{
|
||||||
|
if (value != frame) filterDirty = true;
|
||||||
|
|
||||||
|
return super.set_frame(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function destroy()
|
||||||
|
{
|
||||||
|
super.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@:noCompletion
|
||||||
|
@:access(openfl.display.OpenGLRenderer)
|
||||||
|
@:access(openfl.filters.BitmapFilter)
|
||||||
|
@:access(openfl.geom.Rectangle)
|
||||||
|
@:access(openfl.display.Stage)
|
||||||
|
@:access(openfl.display.Graphics)
|
||||||
|
@:access(openfl.display.Shader)
|
||||||
|
@:access(openfl.display.BitmapData)
|
||||||
|
@:access(openfl.geom.ColorTransform)
|
||||||
|
@:access(openfl.display.DisplayObject)
|
||||||
|
@:access(openfl.display3D.Context3D)
|
||||||
|
@:access(openfl.display.CanvasRenderer)
|
||||||
|
@:access(openfl.display.CairoRenderer)
|
||||||
|
@:access(openfl.display3D.Context3D)
|
||||||
|
class FlxAnimateFilterRenderer
|
||||||
|
{
|
||||||
|
var renderer:OpenGLRenderer;
|
||||||
|
var context:Context3D;
|
||||||
|
|
||||||
|
public function new()
|
||||||
|
{
|
||||||
|
// context = new openfl.display3D.Context3D(null);
|
||||||
|
renderer = new OpenGLRenderer(FlxG.game.stage.context3D);
|
||||||
|
renderer.__worldTransform = new Matrix();
|
||||||
|
renderer.__worldColorTransform = new ColorTransform();
|
||||||
|
}
|
||||||
|
|
||||||
|
@:noCompletion function setRenderer(renderer:DisplayObjectRenderer, rect:Rectangle)
|
||||||
|
{
|
||||||
|
@:privateAccess
|
||||||
|
if (true)
|
||||||
|
{
|
||||||
|
var displayObject = FlxG.game;
|
||||||
|
var pixelRatio = FlxG.game.stage.__renderer.__pixelRatio;
|
||||||
|
|
||||||
|
var offsetX = rect.x > 0 ? Math.ceil(rect.x) : Math.floor(rect.x);
|
||||||
|
var offsetY = rect.y > 0 ? Math.ceil(rect.y) : Math.floor(rect.y);
|
||||||
|
if (renderer.__worldTransform == null)
|
||||||
|
{
|
||||||
|
renderer.__worldTransform = new Matrix();
|
||||||
|
renderer.__worldColorTransform = new ColorTransform();
|
||||||
|
}
|
||||||
|
if (displayObject.__cacheBitmapColorTransform == null) displayObject.__cacheBitmapColorTransform = new ColorTransform();
|
||||||
|
|
||||||
|
renderer.__stage = displayObject.stage;
|
||||||
|
|
||||||
|
renderer.__allowSmoothing = true;
|
||||||
|
renderer.__setBlendMode(NORMAL);
|
||||||
|
renderer.__worldAlpha = 1 / displayObject.__worldAlpha;
|
||||||
|
|
||||||
|
renderer.__worldTransform.identity();
|
||||||
|
renderer.__worldTransform.invert();
|
||||||
|
renderer.__worldTransform.concat(new Matrix());
|
||||||
|
renderer.__worldTransform.tx -= offsetX;
|
||||||
|
renderer.__worldTransform.ty -= offsetY;
|
||||||
|
renderer.__worldTransform.scale(pixelRatio, pixelRatio);
|
||||||
|
|
||||||
|
renderer.__pixelRatio = pixelRatio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applyFilter(target:BitmapData = null, target1:BitmapData = null, target2:BitmapData = null, bmp:BitmapData, filters:Array<BitmapFilter>,
|
||||||
|
rect:Rectangle, bmpRect:Rectangle)
|
||||||
|
{
|
||||||
|
if (filters == null || filters.length == 0) return bmp;
|
||||||
|
|
||||||
|
renderer.__setBlendMode(NORMAL);
|
||||||
|
renderer.__worldAlpha = 1;
|
||||||
|
|
||||||
|
if (renderer.__worldTransform == null)
|
||||||
|
{
|
||||||
|
renderer.__worldTransform = new Matrix();
|
||||||
|
renderer.__worldColorTransform = new ColorTransform();
|
||||||
|
}
|
||||||
|
renderer.__worldTransform.identity();
|
||||||
|
renderer.__worldColorTransform.__identity();
|
||||||
|
|
||||||
|
var bitmap:BitmapData = (target == null) ? new BitmapData(Math.ceil(rect.width * 1.25), Math.ceil(rect.height * 1.25), true, 0) : target;
|
||||||
|
|
||||||
|
var bitmap2 = (target1 == null) ? new BitmapData(Math.ceil(rect.width * 1.25), Math.ceil(rect.height * 1.25), true, 0) : target1,
|
||||||
|
bitmap3 = (target2 == null) ? bitmap2.clone() : target2;
|
||||||
|
renderer.__setRenderTarget(bitmap);
|
||||||
|
|
||||||
|
bmp.__renderTransform.translate(Math.abs(rect.x) - bmpRect.x, Math.abs(rect.y) - bmpRect.y);
|
||||||
|
bmpRect.x = Math.abs(rect.x);
|
||||||
|
bmpRect.y = Math.abs(rect.y);
|
||||||
|
|
||||||
|
var bestResolution = renderer.__context3D.__backBufferWantsBestResolution;
|
||||||
|
renderer.__context3D.__backBufferWantsBestResolution = false;
|
||||||
|
renderer.__scissorRect(bmpRect);
|
||||||
|
renderer.__renderFilterPass(bmp, renderer.__defaultDisplayShader, true);
|
||||||
|
renderer.__scissorRect();
|
||||||
|
|
||||||
|
renderer.__context3D.__backBufferWantsBestResolution = bestResolution;
|
||||||
|
|
||||||
|
bmp.__renderTransform.identity();
|
||||||
|
|
||||||
|
var shader, cacheBitmap = null;
|
||||||
|
for (filter in filters)
|
||||||
|
{
|
||||||
|
if (filter.__preserveObject)
|
||||||
|
{
|
||||||
|
renderer.__setRenderTarget(bitmap3);
|
||||||
|
renderer.__renderFilterPass(bitmap, renderer.__defaultDisplayShader, filter.__smooth);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i in 0...filter.__numShaderPasses)
|
||||||
|
{
|
||||||
|
shader = filter.__initShader(renderer, i, (filter.__preserveObject) ? bitmap3 : null);
|
||||||
|
renderer.__setBlendMode(filter.__shaderBlendMode);
|
||||||
|
renderer.__setRenderTarget(bitmap2);
|
||||||
|
renderer.__renderFilterPass(bitmap, shader, filter.__smooth);
|
||||||
|
|
||||||
|
cacheBitmap = bitmap;
|
||||||
|
bitmap = bitmap2;
|
||||||
|
bitmap2 = cacheBitmap;
|
||||||
|
}
|
||||||
|
filter.__renderDirty = false;
|
||||||
|
}
|
||||||
|
if (target1 == null) bitmap2.dispose();
|
||||||
|
if (target2 == null) bitmap3.dispose();
|
||||||
|
|
||||||
|
// var gl = renderer.__gl;
|
||||||
|
|
||||||
|
// var renderBuffer = bitmap.getTexture(renderer.__context3D);
|
||||||
|
// @:privateAccess
|
||||||
|
// gl.readPixels(0, 0, bitmap.width, bitmap.height, renderBuffer.__format, gl.UNSIGNED_BYTE, bitmap.image.data);
|
||||||
|
// bitmap.image.version = 0;
|
||||||
|
// @:privateAccess
|
||||||
|
// bitmap.__textureVersion = -1;
|
||||||
|
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applyBlend(blend:BlendMode, bitmap:BitmapData)
|
||||||
|
{
|
||||||
|
bitmap.__update(false, true);
|
||||||
|
var bmp = new BitmapData(bitmap.width, bitmap.height, 0);
|
||||||
|
|
||||||
|
#if (js && html5)
|
||||||
|
ImageCanvasUtil.convertToCanvas(bmp.image);
|
||||||
|
@:privateAccess
|
||||||
|
var renderer = new CanvasRenderer(bmp.image.buffer.__srcContext);
|
||||||
|
#else
|
||||||
|
var renderer = new CairoRenderer(new Cairo(bmp.getSurface()));
|
||||||
|
#end
|
||||||
|
|
||||||
|
// setRenderer(renderer, bmp.rect);
|
||||||
|
|
||||||
|
var m = new Matrix();
|
||||||
|
var c = new ColorTransform();
|
||||||
|
renderer.__allowSmoothing = true;
|
||||||
|
renderer.__overrideBlendMode = blend;
|
||||||
|
renderer.__worldTransform = m;
|
||||||
|
renderer.__worldAlpha = 1;
|
||||||
|
renderer.__worldColorTransform = c;
|
||||||
|
|
||||||
|
renderer.__setBlendMode(blend);
|
||||||
|
#if (js && html5)
|
||||||
|
bmp.__drawCanvas(bitmap, renderer);
|
||||||
|
#else
|
||||||
|
bmp.__drawCairo(bitmap, renderer);
|
||||||
|
#end
|
||||||
|
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function graphicstoBitmapData(gfx:Graphics)
|
||||||
|
{
|
||||||
|
if (gfx.__bounds == null) return null;
|
||||||
|
// var cacheRTT = renderer.__context3D.__state.renderToTexture;
|
||||||
|
// var cacheRTTDepthStencil = renderer.__context3D.__state.renderToTextureDepthStencil;
|
||||||
|
// var cacheRTTAntiAlias = renderer.__context3D.__state.renderToTextureAntiAlias;
|
||||||
|
// var cacheRTTSurfaceSelector = renderer.__context3D.__state.renderToTextureSurfaceSelector;
|
||||||
|
|
||||||
|
// var bmp = new BitmapData(Math.ceil(gfx.__width), Math.ceil(gfx.__height), 0);
|
||||||
|
// renderer.__context3D.setRenderToTexture(bmp.getTexture(renderer.__context3D));
|
||||||
|
// gfx.__owner.__renderTransform.identity();
|
||||||
|
// gfx.__renderTransform.identity();
|
||||||
|
// Context3DGraphics.render(gfx, renderer);
|
||||||
|
GfxRenderer.render(gfx, cast renderer.__softwareRenderer);
|
||||||
|
var bmp = gfx.__bitmap;
|
||||||
|
|
||||||
|
gfx.__bitmap = null;
|
||||||
|
|
||||||
|
// if (cacheRTT != null)
|
||||||
|
// {
|
||||||
|
// renderer.__context3D.setRenderToTexture(cacheRTT, cacheRTTDepthStencil, cacheRTTAntiAlias, cacheRTTSurfaceSelector);
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// renderer.__context3D.setRenderToBackBuffer();
|
||||||
|
// }
|
||||||
|
|
||||||
|
return bmp;
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,10 @@ import flixel.tweens.FlxTween;
|
||||||
import openfl.display3D.textures.TextureBase;
|
import openfl.display3D.textures.TextureBase;
|
||||||
import funkin.graphics.framebuffer.FixedBitmapData;
|
import funkin.graphics.framebuffer.FixedBitmapData;
|
||||||
import openfl.display.BitmapData;
|
import openfl.display.BitmapData;
|
||||||
|
import flixel.math.FlxRect;
|
||||||
|
import flixel.math.FlxPoint;
|
||||||
|
import flixel.graphics.frames.FlxFrame;
|
||||||
|
import flixel.FlxCamera;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An FlxSprite with additional functionality.
|
* An FlxSprite with additional functionality.
|
||||||
|
@ -269,6 +273,103 @@ class FunkinSprite extends FlxSprite
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@:access(flixel.FlxCamera)
|
||||||
|
override function getBoundingBox(camera:FlxCamera):FlxRect
|
||||||
|
{
|
||||||
|
getScreenPosition(_point, camera);
|
||||||
|
|
||||||
|
_rect.set(_point.x, _point.y, width, height);
|
||||||
|
_rect = camera.transformRect(_rect);
|
||||||
|
|
||||||
|
if (isPixelPerfectRender(camera))
|
||||||
|
{
|
||||||
|
_rect.width = _rect.width / this.scale.x;
|
||||||
|
_rect.height = _rect.height / this.scale.y;
|
||||||
|
_rect.x = _rect.x / this.scale.x;
|
||||||
|
_rect.y = _rect.y / this.scale.y;
|
||||||
|
_rect.floor();
|
||||||
|
_rect.x = _rect.x * this.scale.x;
|
||||||
|
_rect.y = _rect.y * this.scale.y;
|
||||||
|
_rect.width = _rect.width * this.scale.x;
|
||||||
|
_rect.height = _rect.height * this.scale.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the screen position of this object.
|
||||||
|
*
|
||||||
|
* @param result Optional arg for the returning point
|
||||||
|
* @param camera The desired "screen" coordinate space. If `null`, `FlxG.camera` is used.
|
||||||
|
* @return The screen position of this object.
|
||||||
|
*/
|
||||||
|
public override function getScreenPosition(?result:FlxPoint, ?camera:FlxCamera):FlxPoint
|
||||||
|
{
|
||||||
|
if (result == null) result = FlxPoint.get();
|
||||||
|
|
||||||
|
if (camera == null) camera = FlxG.camera;
|
||||||
|
|
||||||
|
result.set(x, y);
|
||||||
|
if (pixelPerfectPosition)
|
||||||
|
{
|
||||||
|
_rect.width = _rect.width / this.scale.x;
|
||||||
|
_rect.height = _rect.height / this.scale.y;
|
||||||
|
_rect.x = _rect.x / this.scale.x;
|
||||||
|
_rect.y = _rect.y / this.scale.y;
|
||||||
|
_rect.round();
|
||||||
|
_rect.x = _rect.x * this.scale.x;
|
||||||
|
_rect.y = _rect.y * this.scale.y;
|
||||||
|
_rect.width = _rect.width * this.scale.x;
|
||||||
|
_rect.height = _rect.height * this.scale.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.subtract(camera.scroll.x * scrollFactor.x, camera.scroll.y * scrollFactor.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function drawSimple(camera:FlxCamera):Void
|
||||||
|
{
|
||||||
|
getScreenPosition(_point, camera).subtractPoint(offset);
|
||||||
|
if (isPixelPerfectRender(camera))
|
||||||
|
{
|
||||||
|
_point.x = _point.x / this.scale.x;
|
||||||
|
_point.y = _point.y / this.scale.y;
|
||||||
|
_point.round();
|
||||||
|
|
||||||
|
_point.x = _point.x * this.scale.x;
|
||||||
|
_point.y = _point.y * this.scale.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
_point.copyToFlash(_flashPoint);
|
||||||
|
camera.copyPixels(_frame, framePixels, _flashRect, _flashPoint, colorTransform, blend, antialiasing);
|
||||||
|
}
|
||||||
|
|
||||||
|
override function drawComplex(camera:FlxCamera):Void
|
||||||
|
{
|
||||||
|
_frame.prepareMatrix(_matrix, FlxFrameAngle.ANGLE_0, checkFlipX(), checkFlipY());
|
||||||
|
_matrix.translate(-origin.x, -origin.y);
|
||||||
|
_matrix.scale(scale.x, scale.y);
|
||||||
|
|
||||||
|
if (bakedRotationAngle <= 0)
|
||||||
|
{
|
||||||
|
updateTrig();
|
||||||
|
|
||||||
|
if (angle != 0) _matrix.rotateWithTrig(_cosAngle, _sinAngle);
|
||||||
|
}
|
||||||
|
|
||||||
|
getScreenPosition(_point, camera).subtractPoint(offset);
|
||||||
|
_point.add(origin.x, origin.y);
|
||||||
|
_matrix.translate(_point.x, _point.y);
|
||||||
|
|
||||||
|
if (isPixelPerfectRender(camera))
|
||||||
|
{
|
||||||
|
_matrix.tx = Math.round(_matrix.tx / this.scale.x) * this.scale.x;
|
||||||
|
_matrix.ty = Math.round(_matrix.ty / this.scale.y) * this.scale.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
camera.drawPixels(_frame, framePixels, _matrix, colorTransform, blend, antialiasing, shader);
|
||||||
|
}
|
||||||
|
|
||||||
public override function destroy():Void
|
public override function destroy():Void
|
||||||
{
|
{
|
||||||
frames = null;
|
frames = null;
|
||||||
|
|
|
@ -4,8 +4,12 @@ import flixel.util.FlxSignal.FlxTypedSignal;
|
||||||
import flxanimate.FlxAnimate;
|
import flxanimate.FlxAnimate;
|
||||||
import flxanimate.FlxAnimate.Settings;
|
import flxanimate.FlxAnimate.Settings;
|
||||||
import flxanimate.frames.FlxAnimateFrames;
|
import flxanimate.frames.FlxAnimateFrames;
|
||||||
|
import flixel.graphics.frames.FlxFrame;
|
||||||
|
import flixel.system.FlxAssets.FlxGraphicAsset;
|
||||||
import openfl.display.BitmapData;
|
import openfl.display.BitmapData;
|
||||||
import openfl.utils.Assets;
|
import openfl.utils.Assets;
|
||||||
|
import flixel.math.FlxPoint;
|
||||||
|
import flxanimate.animate.FlxKeyFrame;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A sprite which provides convenience functions for rendering a texture atlas with animations.
|
* A sprite which provides convenience functions for rendering a texture atlas with animations.
|
||||||
|
@ -18,16 +22,21 @@ class FlxAtlasSprite extends FlxAnimate
|
||||||
FrameRate: 24.0,
|
FrameRate: 24.0,
|
||||||
Reversed: false,
|
Reversed: false,
|
||||||
// ?OnComplete:Void -> Void,
|
// ?OnComplete:Void -> Void,
|
||||||
ShowPivot: #if debug false #else false #end,
|
ShowPivot: false,
|
||||||
Antialiasing: true,
|
Antialiasing: true,
|
||||||
ScrollFactor: null,
|
ScrollFactor: null,
|
||||||
// Offset: new FlxPoint(0, 0), // This is just FlxSprite.offset
|
// Offset: new FlxPoint(0, 0), // This is just FlxSprite.offset
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signal dispatched when an animation finishes playing.
|
* Signal dispatched when an animation advances to the next frame.
|
||||||
*/
|
*/
|
||||||
public var onAnimationFinish:FlxTypedSignal<String->Void> = new FlxTypedSignal<String->Void>();
|
public var onAnimationFrame:FlxTypedSignal<String->Int->Void> = new FlxTypedSignal();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal dispatched when a non-looping animation finishes playing.
|
||||||
|
*/
|
||||||
|
public var onAnimationComplete:FlxTypedSignal<String->Void> = new FlxTypedSignal();
|
||||||
|
|
||||||
var currentAnimation:String;
|
var currentAnimation:String;
|
||||||
|
|
||||||
|
@ -42,19 +51,28 @@ class FlxAtlasSprite extends FlxAnimate
|
||||||
throw 'Null path specified for FlxAtlasSprite!';
|
throw 'Null path specified for FlxAtlasSprite!';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate asset path.
|
||||||
|
if (!Assets.exists('${path}/Animation.json'))
|
||||||
|
{
|
||||||
|
throw 'FlxAtlasSprite does not have an Animation.json file at the specified path (${path})';
|
||||||
|
}
|
||||||
|
|
||||||
super(x, y, path, settings);
|
super(x, y, path, settings);
|
||||||
|
|
||||||
if (this.anim.curInstance == null)
|
if (this.anim.stageInstance == null)
|
||||||
{
|
{
|
||||||
throw 'FlxAtlasSprite not initialized properly. Are you sure the path (${path}) exists?';
|
throw 'FlxAtlasSprite not initialized properly. Are you sure the path (${path}) exists?';
|
||||||
}
|
}
|
||||||
|
|
||||||
onAnimationFinish.add(cleanupAnimation);
|
onAnimationComplete.add(cleanupAnimation);
|
||||||
|
|
||||||
// This defaults the sprite to play the first animation in the atlas,
|
// This defaults the sprite to play the first animation in the atlas,
|
||||||
// then pauses it. This ensures symbols are intialized properly.
|
// then pauses it. This ensures symbols are intialized properly.
|
||||||
this.anim.play('');
|
this.anim.play('');
|
||||||
this.anim.pause();
|
this.anim.pause();
|
||||||
|
|
||||||
|
this.anim.onComplete.add(_onAnimationComplete);
|
||||||
|
this.anim.onFrame.add(_onAnimationFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -62,9 +80,13 @@ class FlxAtlasSprite extends FlxAnimate
|
||||||
*/
|
*/
|
||||||
public function listAnimations():Array<String>
|
public function listAnimations():Array<String>
|
||||||
{
|
{
|
||||||
if (this.anim == null) return [];
|
var mainSymbol = this.anim.symbolDictionary[this.anim.stageInstance.symbol.name];
|
||||||
return this.anim.getFrameLabels();
|
if (mainSymbol == null)
|
||||||
// return [""];
|
{
|
||||||
|
FlxG.log.error('FlxAtlasSprite does not have its main symbol!');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return mainSymbol.getFrameLabels().map(keyFrame -> keyFrame.name).filterNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -73,7 +95,7 @@ class FlxAtlasSprite extends FlxAnimate
|
||||||
*/
|
*/
|
||||||
public function hasAnimation(id:String):Bool
|
public function hasAnimation(id:String):Bool
|
||||||
{
|
{
|
||||||
return getLabelIndex(id) != -1;
|
return getLabelIndex(id) != -1 || anim.symbolDictionary.exists(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -84,22 +106,13 @@ class FlxAtlasSprite extends FlxAnimate
|
||||||
return this.currentAnimation;
|
return this.currentAnimation;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
var _completeAnim:Bool = false;
|
||||||
* `anim.finished` always returns false on looping animations,
|
|
||||||
* but this function will return true if we are on the last frame of the looping animation.
|
|
||||||
*/
|
|
||||||
public function isLoopFinished():Bool
|
|
||||||
{
|
|
||||||
if (this.anim == null) return false;
|
|
||||||
if (!this.anim.isPlaying) return false;
|
|
||||||
|
|
||||||
// Reverse animation finished.
|
var fr:FlxKeyFrame = null;
|
||||||
if (this.anim.reversed && this.anim.curFrame == 0) return true;
|
|
||||||
// Forward animation finished.
|
|
||||||
if (!this.anim.reversed && this.anim.curFrame >= (this.anim.length - 1)) return true;
|
|
||||||
|
|
||||||
return false;
|
var looping:Bool = false;
|
||||||
}
|
|
||||||
|
public var ignoreExclusionPref:Array<String> = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plays an animation.
|
* Plays an animation.
|
||||||
|
@ -107,61 +120,86 @@ class FlxAtlasSprite extends FlxAnimate
|
||||||
* @param restart Whether to restart the animation if it is already playing.
|
* @param restart Whether to restart the animation if it is already playing.
|
||||||
* @param ignoreOther Whether to ignore all other animation inputs, until this one is done playing
|
* @param ignoreOther Whether to ignore all other animation inputs, until this one is done playing
|
||||||
* @param loop Whether to loop the animation
|
* @param loop Whether to loop the animation
|
||||||
|
* @param startFrame The frame to start the animation on
|
||||||
* NOTE: `loop` and `ignoreOther` are not compatible with each other!
|
* NOTE: `loop` and `ignoreOther` are not compatible with each other!
|
||||||
*/
|
*/
|
||||||
public function playAnimation(id:String, restart:Bool = false, ignoreOther:Bool = false, ?loop:Bool = false):Void
|
public function playAnimation(id:String, restart:Bool = false, ignoreOther:Bool = false, loop:Bool = false, startFrame:Int = 0):Void
|
||||||
{
|
{
|
||||||
if (loop == null) loop = false;
|
|
||||||
|
|
||||||
// Skip if not allowed to play animations.
|
// Skip if not allowed to play animations.
|
||||||
if ((!canPlayOtherAnims && !ignoreOther)) return;
|
if ((!canPlayOtherAnims))
|
||||||
|
{
|
||||||
|
if (this.currentAnimation == id && restart) {}
|
||||||
|
else if (ignoreExclusionPref != null && ignoreExclusionPref.length > 0)
|
||||||
|
{
|
||||||
|
var detected:Bool = false;
|
||||||
|
for (entry in ignoreExclusionPref)
|
||||||
|
{
|
||||||
|
if (StringTools.startsWith(id, entry))
|
||||||
|
{
|
||||||
|
detected = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!detected) return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anim == null) return;
|
||||||
|
|
||||||
if (id == null || id == '') id = this.currentAnimation;
|
if (id == null || id == '') id = this.currentAnimation;
|
||||||
|
|
||||||
if (this.currentAnimation == id && !restart)
|
if (this.currentAnimation == id && !restart)
|
||||||
{
|
{
|
||||||
if (anim.isPlaying)
|
if (!anim.isPlaying)
|
||||||
{
|
{
|
||||||
// Skip if animation is already playing.
|
if (fr != null) anim.curFrame = fr.index + startFrame;
|
||||||
return;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
anim.curFrame = startFrame;
|
||||||
|
|
||||||
// Resume animation if it's paused.
|
// Resume animation if it's paused.
|
||||||
anim.play('', false, false);
|
anim.resume();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip if the animation doesn't exist
|
return;
|
||||||
if (!hasAnimation(id))
|
}
|
||||||
|
else if (!hasAnimation(id))
|
||||||
{
|
{
|
||||||
|
// Skip if the animation doesn't exist
|
||||||
trace('Animation ' + id + ' not found');
|
trace('Animation ' + id + ' not found');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
anim.callback = function(_, frame:Int) {
|
this.currentAnimation = id;
|
||||||
var offset = loop ? 0 : -1;
|
anim.onComplete.removeAll();
|
||||||
|
anim.onComplete.add(function() {
|
||||||
|
_onAnimationComplete();
|
||||||
|
});
|
||||||
|
|
||||||
var frameLabel = anim.getFrameLabel(id);
|
looping = loop;
|
||||||
if (frame == (frameLabel.duration + offset) + frameLabel.index)
|
|
||||||
{
|
|
||||||
if (loop)
|
|
||||||
{
|
|
||||||
playAnimation(id, true, false, true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
onAnimationFinish.dispatch(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Prevent other animations from playing if `ignoreOther` is true.
|
// Prevent other animations from playing if `ignoreOther` is true.
|
||||||
if (ignoreOther) canPlayOtherAnims = false;
|
if (ignoreOther) canPlayOtherAnims = false;
|
||||||
|
|
||||||
// Move to the first frame of the animation.
|
// Move to the first frame of the animation.
|
||||||
|
// goToFrameLabel(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);
|
||||||
|
|
||||||
|
this.currentAnimation = anim.curSymbol.name;
|
||||||
|
|
||||||
|
fr = null;
|
||||||
|
}
|
||||||
|
// Only call goToFrameLabel if there is a frame label with that name. This prevents annoying warnings!
|
||||||
|
if (getFrameLabelNames().indexOf(id) != -1)
|
||||||
|
{
|
||||||
goToFrameLabel(id);
|
goToFrameLabel(id);
|
||||||
this.currentAnimation = id;
|
fr = anim.getFrameLabel(id);
|
||||||
|
anim.curFrame += startFrame;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override public function update(elapsed:Float)
|
override public function update(elapsed:Float)
|
||||||
|
@ -169,6 +207,29 @@ class FlxAtlasSprite extends FlxAnimate
|
||||||
super.update(elapsed);
|
super.update(elapsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the animation has finished playing.
|
||||||
|
* Never true if animation is configured to loop.
|
||||||
|
*/
|
||||||
|
public function isAnimationFinished():Bool
|
||||||
|
{
|
||||||
|
return this.anim.finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the animation has reached the last frame.
|
||||||
|
* Can be true even if animation is configured to loop.
|
||||||
|
*/
|
||||||
|
public function isLoopComplete():Bool
|
||||||
|
{
|
||||||
|
if (this.anim == null) return false;
|
||||||
|
if (!this.anim.isPlaying) return false;
|
||||||
|
|
||||||
|
if (fr != null) return (anim.reversed && anim.curFrame < fr.index || !anim.reversed && anim.curFrame >= (fr.index + fr.duration));
|
||||||
|
|
||||||
|
return (anim.reversed && anim.curFrame == 0 || !(anim.reversed) && (anim.curFrame) >= (anim.length - 1));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops the current animation.
|
* Stops the current animation.
|
||||||
*/
|
*/
|
||||||
|
@ -192,6 +253,18 @@ class FlxAtlasSprite extends FlxAnimate
|
||||||
this.anim.goToFrameLabel(label);
|
this.anim.goToFrameLabel(label);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getFrameLabelNames(?layer:haxe.extern.EitherType<Int, String> = null)
|
||||||
|
{
|
||||||
|
var labels = this.anim.getFrameLabels(layer);
|
||||||
|
var array = [];
|
||||||
|
for (label in labels)
|
||||||
|
{
|
||||||
|
array.push(label.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
function getNextFrameLabel(label:String):String
|
function getNextFrameLabel(label:String):String
|
||||||
{
|
{
|
||||||
return listAnimations()[(getLabelIndex(label) + 1) % listAnimations().length];
|
return listAnimations()[(getLabelIndex(label) + 1) % listAnimations().length];
|
||||||
|
@ -213,4 +286,95 @@ class FlxAtlasSprite extends FlxAnimate
|
||||||
// this.currentAnimation = null;
|
// this.currentAnimation = null;
|
||||||
this.anim.pause();
|
this.anim.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _onAnimationFrame(frame:Int):Void
|
||||||
|
{
|
||||||
|
if (currentAnimation != null)
|
||||||
|
{
|
||||||
|
onAnimationFrame.dispatch(currentAnimation, frame);
|
||||||
|
|
||||||
|
if (isLoopComplete())
|
||||||
|
{
|
||||||
|
anim.pause();
|
||||||
|
_onAnimationComplete();
|
||||||
|
|
||||||
|
if (looping)
|
||||||
|
{
|
||||||
|
anim.curFrame = (fr != null) ? fr.index : 0;
|
||||||
|
anim.resume();
|
||||||
|
}
|
||||||
|
else if (fr != null && anim.curFrame != anim.length - 1)
|
||||||
|
{
|
||||||
|
anim.curFrame--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _onAnimationComplete():Void
|
||||||
|
{
|
||||||
|
if (currentAnimation != null)
|
||||||
|
{
|
||||||
|
onAnimationComplete.dispatch(currentAnimation);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
onAnimationComplete.dispatch('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var prevFrames:Map<Int, FlxFrame> = [];
|
||||||
|
|
||||||
|
public function replaceFrameGraphic(index:Int, ?graphic:FlxGraphicAsset):Void
|
||||||
|
{
|
||||||
|
if (graphic == null || !Assets.exists(graphic))
|
||||||
|
{
|
||||||
|
var prevFrame:Null<FlxFrame> = prevFrames.get(index);
|
||||||
|
if (prevFrame == null) return;
|
||||||
|
|
||||||
|
prevFrame.copyTo(frames.getByIndex(index));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var prevFrame:FlxFrame = prevFrames.get(index) ?? frames.getByIndex(index).copyTo();
|
||||||
|
prevFrames.set(index, prevFrame);
|
||||||
|
|
||||||
|
var frame = FlxG.bitmap.add(graphic).imageFrame.frame;
|
||||||
|
frame.copyTo(frames.getByIndex(index));
|
||||||
|
|
||||||
|
// Additional sizing fix.
|
||||||
|
@:privateAccess
|
||||||
|
if (true)
|
||||||
|
{
|
||||||
|
var frame = frames.getByIndex(index);
|
||||||
|
frame.tileMatrix[0] = prevFrame.frame.width / frame.frame.width;
|
||||||
|
frame.tileMatrix[3] = prevFrame.frame.height / frame.frame.height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBasePosition():Null<FlxPoint>
|
||||||
|
{
|
||||||
|
// var stagePos = new FlxPoint(anim.stageInstance.matrix.tx, anim.stageInstance.matrix.ty);
|
||||||
|
var instancePos = new FlxPoint(anim.curInstance.matrix.tx, anim.curInstance.matrix.ty);
|
||||||
|
var firstElement = anim.curSymbol.timeline?.get(0)?.get(0)?.get(0);
|
||||||
|
if (firstElement == null) return instancePos;
|
||||||
|
var firstElementPos = new FlxPoint(firstElement.matrix.tx, firstElement.matrix.ty);
|
||||||
|
|
||||||
|
return instancePos + firstElementPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPivotPosition():Null<FlxPoint>
|
||||||
|
{
|
||||||
|
return anim.curInstance.symbol.transformationPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override function destroy():Void
|
||||||
|
{
|
||||||
|
for (prevFrameId in prevFrames.keys())
|
||||||
|
{
|
||||||
|
replaceFrameGraphic(prevFrameId, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.destroy();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
55
source/funkin/graphics/shaders/AdjustColorShader.hx
Normal file
55
source/funkin/graphics/shaders/AdjustColorShader.hx
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
package funkin.graphics.shaders;
|
||||||
|
|
||||||
|
import flixel.addons.display.FlxRuntimeShader;
|
||||||
|
import funkin.Paths;
|
||||||
|
import openfl.utils.Assets;
|
||||||
|
|
||||||
|
class AdjustColorShader extends FlxRuntimeShader
|
||||||
|
{
|
||||||
|
public var hue(default, set):Float;
|
||||||
|
public var saturation(default, set):Float;
|
||||||
|
public var brightness(default, set):Float;
|
||||||
|
public var contrast(default, set):Float;
|
||||||
|
|
||||||
|
public function new()
|
||||||
|
{
|
||||||
|
super(Assets.getText(Paths.frag('adjustColor')));
|
||||||
|
// FlxG.debugger.addTrackerProfile(new TrackerProfile(HSVShader, ['hue', 'saturation', 'brightness', 'contrast']));
|
||||||
|
hue = 0;
|
||||||
|
saturation = 0;
|
||||||
|
brightness = 0;
|
||||||
|
contrast = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_hue(value:Float):Float
|
||||||
|
{
|
||||||
|
this.setFloat('hue', value);
|
||||||
|
this.hue = value;
|
||||||
|
|
||||||
|
return this.hue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_saturation(value:Float):Float
|
||||||
|
{
|
||||||
|
this.setFloat('saturation', value);
|
||||||
|
this.saturation = value;
|
||||||
|
|
||||||
|
return this.saturation;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_brightness(value:Float):Float
|
||||||
|
{
|
||||||
|
this.setFloat('brightness', value);
|
||||||
|
this.brightness = value;
|
||||||
|
|
||||||
|
return this.brightness;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_contrast(value:Float):Float
|
||||||
|
{
|
||||||
|
this.setFloat('contrast', value);
|
||||||
|
this.contrast = value;
|
||||||
|
|
||||||
|
return this.contrast;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,25 @@
|
||||||
package funkin.graphics.shaders;
|
package funkin.graphics.shaders;
|
||||||
|
|
||||||
import flixel.system.FlxAssets.FlxShader;
|
import flixel.system.FlxAssets.FlxShader;
|
||||||
|
import flixel.util.FlxColor;
|
||||||
|
|
||||||
class AngleMask extends FlxShader
|
class AngleMask extends FlxShader
|
||||||
{
|
{
|
||||||
|
public var extraColor(default, set):FlxColor = 0xFFFFFFFF;
|
||||||
|
|
||||||
|
function set_extraColor(value:FlxColor):FlxColor
|
||||||
|
{
|
||||||
|
extraTint.value = [value.redFloat, value.greenFloat, value.blueFloat];
|
||||||
|
this.extraColor = value;
|
||||||
|
|
||||||
|
return this.extraColor;
|
||||||
|
}
|
||||||
|
|
||||||
@:glFragmentSource('
|
@:glFragmentSource('
|
||||||
#pragma header
|
#pragma header
|
||||||
|
|
||||||
|
uniform vec3 extraTint;
|
||||||
|
|
||||||
uniform vec2 endPosition;
|
uniform vec2 endPosition;
|
||||||
vec2 hash22(vec2 p) {
|
vec2 hash22(vec2 p) {
|
||||||
vec3 p3 = fract(vec3(p.xyx) * vec3(.1031, .1030, .0973));
|
vec3 p3 = fract(vec3(p.xyx) * vec3(.1031, .1030, .0973));
|
||||||
|
@ -69,6 +82,7 @@ class AngleMask extends FlxShader
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec4 col = antialias(openfl_TextureCoordv);
|
vec4 col = antialias(openfl_TextureCoordv);
|
||||||
|
col.xyz = col.xyz * extraTint.xyz;
|
||||||
// col.xyz = gamma(col.xyz);
|
// col.xyz = gamma(col.xyz);
|
||||||
gl_FragColor = col;
|
gl_FragColor = col;
|
||||||
}')
|
}')
|
||||||
|
@ -77,5 +91,6 @@ class AngleMask extends FlxShader
|
||||||
super();
|
super();
|
||||||
|
|
||||||
endPosition.value = [90, 100]; // 100 AS DEFAULT WORKS NICELY FOR FREEPLAY?
|
endPosition.value = [90, 100]; // 100 AS DEFAULT WORKS NICELY FOR FREEPLAY?
|
||||||
|
extraTint.value = [1, 1, 1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
51
source/funkin/graphics/shaders/BlueFade.hx
Normal file
51
source/funkin/graphics/shaders/BlueFade.hx
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package funkin.graphics.shaders;
|
||||||
|
|
||||||
|
import flixel.system.FlxAssets.FlxShader;
|
||||||
|
import flixel.tweens.FlxEase;
|
||||||
|
import flixel.tweens.FlxTween;
|
||||||
|
|
||||||
|
class BlueFade extends FlxShader
|
||||||
|
{
|
||||||
|
public var fadeVal(default, set):Float;
|
||||||
|
|
||||||
|
function set_fadeVal(val:Float):Float
|
||||||
|
{
|
||||||
|
fadeAmt.value = [val];
|
||||||
|
fadeVal = val;
|
||||||
|
// trace(fadeVal);
|
||||||
|
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fade(startAmt:Float = 0, targetAmt:Float = 1, duration:Float, _options:TweenOptions):Void
|
||||||
|
{
|
||||||
|
fadeVal = startAmt;
|
||||||
|
FlxTween.tween(this, {fadeVal: targetAmt}, duration, _options);
|
||||||
|
}
|
||||||
|
|
||||||
|
@:glFragmentSource('
|
||||||
|
#pragma header
|
||||||
|
|
||||||
|
// Value from (0, 1)
|
||||||
|
uniform float fadeAmt;
|
||||||
|
|
||||||
|
// fade the image to blue as it fades to black
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec4 tex = flixel_texture2D(bitmap, openfl_TextureCoordv);
|
||||||
|
|
||||||
|
vec4 finalColor = mix(vec4(vec4(0.0, 0.0, tex.b, tex.a) * fadeAmt), vec4(tex * fadeAmt), fadeAmt);
|
||||||
|
|
||||||
|
// Output to screen
|
||||||
|
gl_FragColor = finalColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
')
|
||||||
|
public function new()
|
||||||
|
{
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.fadeVal = 1;
|
||||||
|
}
|
||||||
|
}
|
23
source/funkin/graphics/shaders/MosaicEffect.hx
Normal file
23
source/funkin/graphics/shaders/MosaicEffect.hx
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package funkin.graphics.shaders;
|
||||||
|
|
||||||
|
import flixel.addons.display.FlxRuntimeShader;
|
||||||
|
import openfl.utils.Assets;
|
||||||
|
import funkin.Paths;
|
||||||
|
import flixel.math.FlxPoint;
|
||||||
|
|
||||||
|
class MosaicEffect extends FlxRuntimeShader
|
||||||
|
{
|
||||||
|
public var blockSize:FlxPoint = FlxPoint.get(1.0, 1.0);
|
||||||
|
|
||||||
|
public function new()
|
||||||
|
{
|
||||||
|
super(Assets.getText(Paths.frag('mosaic')));
|
||||||
|
setBlockSize(1.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setBlockSize(w:Float, h:Float)
|
||||||
|
{
|
||||||
|
blockSize.set(w, h);
|
||||||
|
setFloatArray("uBlocksize", [w, h]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package funkin.graphics.shaders;
|
||||||
|
|
||||||
import flixel.FlxCamera;
|
import flixel.FlxCamera;
|
||||||
import flixel.FlxG;
|
import flixel.FlxG;
|
||||||
|
import flixel.graphics.frames.FlxFrame;
|
||||||
import flixel.addons.display.FlxRuntimeShader;
|
import flixel.addons.display.FlxRuntimeShader;
|
||||||
import lime.graphics.opengl.GLProgram;
|
import lime.graphics.opengl.GLProgram;
|
||||||
import lime.utils.Log;
|
import lime.utils.Log;
|
||||||
|
@ -32,6 +33,9 @@ class RuntimePostEffectShader extends FlxRuntimeShader
|
||||||
// equals (camera.viewLeft, camera.viewTop, camera.viewRight, camera.viewBottom)
|
// equals (camera.viewLeft, camera.viewTop, camera.viewRight, camera.viewBottom)
|
||||||
uniform vec4 uCameraBounds;
|
uniform vec4 uCameraBounds;
|
||||||
|
|
||||||
|
// equals (frame.left, frame.top, frame.right, frame.bottom)
|
||||||
|
uniform vec4 uFrameBounds;
|
||||||
|
|
||||||
// screen coord -> world coord conversion
|
// screen coord -> world coord conversion
|
||||||
// returns world coord in px
|
// returns world coord in px
|
||||||
vec2 screenToWorld(vec2 screenCoord) {
|
vec2 screenToWorld(vec2 screenCoord) {
|
||||||
|
@ -56,6 +60,25 @@ class RuntimePostEffectShader extends FlxRuntimeShader
|
||||||
return (worldCoord - offset) / scale;
|
return (worldCoord - offset) / scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// screen coord -> frame coord conversion
|
||||||
|
// returns normalized frame coord
|
||||||
|
vec2 screenToFrame(vec2 screenCoord) {
|
||||||
|
float left = uFrameBounds.x;
|
||||||
|
float top = uFrameBounds.y;
|
||||||
|
float right = uFrameBounds.z;
|
||||||
|
float bottom = uFrameBounds.w;
|
||||||
|
float width = right - left;
|
||||||
|
float height = bottom - top;
|
||||||
|
|
||||||
|
float clampedX = clamp(screenCoord.x, left, right);
|
||||||
|
float clampedY = clamp(screenCoord.y, top, bottom);
|
||||||
|
|
||||||
|
return vec2(
|
||||||
|
(clampedX - left) / (width),
|
||||||
|
(clampedY - top) / (height)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// internally used to get the maximum `openfl_TextureCoordv`
|
// internally used to get the maximum `openfl_TextureCoordv`
|
||||||
vec2 bitmapCoordScale() {
|
vec2 bitmapCoordScale() {
|
||||||
return openfl_TextureCoordv / screenCoord;
|
return openfl_TextureCoordv / screenCoord;
|
||||||
|
@ -80,6 +103,8 @@ class RuntimePostEffectShader extends FlxRuntimeShader
|
||||||
{
|
{
|
||||||
super(fragmentSource, null, glVersion);
|
super(fragmentSource, null, glVersion);
|
||||||
uScreenResolution.value = [FlxG.width, FlxG.height];
|
uScreenResolution.value = [FlxG.width, FlxG.height];
|
||||||
|
uCameraBounds.value = [0, 0, FlxG.width, FlxG.height];
|
||||||
|
uFrameBounds.value = [0, 0, FlxG.width, FlxG.height];
|
||||||
}
|
}
|
||||||
|
|
||||||
// basically `updateViewInfo(FlxG.width, FlxG.height, FlxG.camera)` is good
|
// basically `updateViewInfo(FlxG.width, FlxG.height, FlxG.camera)` is good
|
||||||
|
@ -89,6 +114,12 @@ class RuntimePostEffectShader extends FlxRuntimeShader
|
||||||
uCameraBounds.value = [camera.viewLeft, camera.viewTop, camera.viewRight, camera.viewBottom];
|
uCameraBounds.value = [camera.viewLeft, camera.viewTop, camera.viewRight, camera.viewBottom];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function updateFrameInfo(frame:FlxFrame)
|
||||||
|
{
|
||||||
|
// NOTE: uv.width is actually the right pos and uv.height is the bottom pos
|
||||||
|
uFrameBounds.value = [frame.uv.x, frame.uv.y, frame.uv.width, frame.uv.height];
|
||||||
|
}
|
||||||
|
|
||||||
override function __createGLProgram(vertexSource:String, fragmentSource:String):GLProgram
|
override function __createGLProgram(vertexSource:String, fragmentSource:String):GLProgram
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
|
@ -4,6 +4,7 @@ import flixel.system.FlxAssets.FlxShader;
|
||||||
import openfl.display.BitmapData;
|
import openfl.display.BitmapData;
|
||||||
import openfl.display.ShaderParameter;
|
import openfl.display.ShaderParameter;
|
||||||
import openfl.display.ShaderParameterType;
|
import openfl.display.ShaderParameterType;
|
||||||
|
import flixel.util.FlxColor;
|
||||||
import openfl.utils.Assets;
|
import openfl.utils.Assets;
|
||||||
|
|
||||||
typedef Light =
|
typedef Light =
|
||||||
|
@ -32,6 +33,14 @@ class RuntimeRainShader extends RuntimePostEffectShader
|
||||||
return time = value;
|
return time = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var spriteMode(default, set):Bool = false;
|
||||||
|
|
||||||
|
function set_spriteMode(value:Bool):Bool
|
||||||
|
{
|
||||||
|
this.setBool('uSpriteMode', value);
|
||||||
|
return spriteMode = value;
|
||||||
|
}
|
||||||
|
|
||||||
// The scale of the rain depends on the world coordinate system, so higher resolution makes
|
// The scale of the rain depends on the world coordinate system, so higher resolution makes
|
||||||
// the raindrops smaller. This parameter can be used to adjust the total scale of the scene.
|
// the raindrops smaller. This parameter can be used to adjust the total scale of the scene.
|
||||||
// The size of the raindrops is proportional to the value of this parameter.
|
// The size of the raindrops is proportional to the value of this parameter.
|
||||||
|
@ -86,6 +95,14 @@ class RuntimeRainShader extends RuntimePostEffectShader
|
||||||
return mask = value;
|
return mask = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var rainColor(default, set):FlxColor;
|
||||||
|
|
||||||
|
function set_rainColor(color:FlxColor):FlxColor
|
||||||
|
{
|
||||||
|
this.setFloatArray("uRainColor", [color.red / 255, color.green / 255, color.blue / 255]);
|
||||||
|
return rainColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
public var lightMap(default, set):BitmapData;
|
public var lightMap(default, set):BitmapData;
|
||||||
|
|
||||||
function set_lightMap(value:BitmapData):BitmapData
|
function set_lightMap(value:BitmapData):BitmapData
|
||||||
|
@ -105,6 +122,7 @@ class RuntimeRainShader extends RuntimePostEffectShader
|
||||||
public function new()
|
public function new()
|
||||||
{
|
{
|
||||||
super(Assets.getText(Paths.frag('rain')));
|
super(Assets.getText(Paths.frag('rain')));
|
||||||
|
this.rainColor = 0xFF6680cc;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(elapsed:Float):Void
|
public function update(elapsed:Float):Void
|
||||||
|
|
48
source/funkin/graphics/shaders/TextureSwap.hx
Normal file
48
source/funkin/graphics/shaders/TextureSwap.hx
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
package funkin.graphics.shaders;
|
||||||
|
|
||||||
|
import flixel.system.FlxAssets.FlxShader;
|
||||||
|
import flixel.util.FlxColor;
|
||||||
|
import openfl.display.BitmapData;
|
||||||
|
|
||||||
|
class TextureSwap extends FlxShader
|
||||||
|
{
|
||||||
|
public var swappedImage(default, set):BitmapData;
|
||||||
|
public var amount(default, set):Float;
|
||||||
|
|
||||||
|
function set_swappedImage(_bitmapData:BitmapData):BitmapData
|
||||||
|
{
|
||||||
|
image.input = _bitmapData;
|
||||||
|
|
||||||
|
return _bitmapData;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_amount(val:Float):Float
|
||||||
|
{
|
||||||
|
fadeAmount.value = [val];
|
||||||
|
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
@:glFragmentSource('
|
||||||
|
#pragma header
|
||||||
|
|
||||||
|
uniform sampler2D image;
|
||||||
|
uniform float fadeAmount;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec4 tex = flixel_texture2D(bitmap, openfl_TextureCoordv);
|
||||||
|
vec4 tex2 = flixel_texture2D(image, openfl_TextureCoordv);
|
||||||
|
|
||||||
|
vec4 finalColor = mix(tex, vec4(tex2.rgb, tex.a), fadeAmount);
|
||||||
|
|
||||||
|
gl_FragColor = finalColor;
|
||||||
|
}
|
||||||
|
')
|
||||||
|
public function new()
|
||||||
|
{
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.amount = 1;
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,6 +31,7 @@ class Controls extends FlxActionSet
|
||||||
* Uses FlxActions to funnel various inputs to a single action.
|
* Uses FlxActions to funnel various inputs to a single action.
|
||||||
*/
|
*/
|
||||||
var _ui_up = new FunkinAction(Action.UI_UP);
|
var _ui_up = new FunkinAction(Action.UI_UP);
|
||||||
|
|
||||||
var _ui_left = new FunkinAction(Action.UI_LEFT);
|
var _ui_left = new FunkinAction(Action.UI_LEFT);
|
||||||
var _ui_right = new FunkinAction(Action.UI_RIGHT);
|
var _ui_right = new FunkinAction(Action.UI_RIGHT);
|
||||||
var _ui_down = new FunkinAction(Action.UI_DOWN);
|
var _ui_down = new FunkinAction(Action.UI_DOWN);
|
||||||
|
@ -63,6 +64,7 @@ class Controls extends FlxActionSet
|
||||||
var _freeplay_favorite = new FunkinAction(Action.FREEPLAY_FAVORITE);
|
var _freeplay_favorite = new FunkinAction(Action.FREEPLAY_FAVORITE);
|
||||||
var _freeplay_left = new FunkinAction(Action.FREEPLAY_LEFT);
|
var _freeplay_left = new FunkinAction(Action.FREEPLAY_LEFT);
|
||||||
var _freeplay_right = new FunkinAction(Action.FREEPLAY_RIGHT);
|
var _freeplay_right = new FunkinAction(Action.FREEPLAY_RIGHT);
|
||||||
|
var _freeplay_char_select = new FunkinAction(Action.FREEPLAY_CHAR_SELECT);
|
||||||
var _cutscene_advance = new FunkinAction(Action.CUTSCENE_ADVANCE);
|
var _cutscene_advance = new FunkinAction(Action.CUTSCENE_ADVANCE);
|
||||||
var _debug_menu = new FunkinAction(Action.DEBUG_MENU);
|
var _debug_menu = new FunkinAction(Action.DEBUG_MENU);
|
||||||
var _debug_chart = new FunkinAction(Action.DEBUG_CHART);
|
var _debug_chart = new FunkinAction(Action.DEBUG_CHART);
|
||||||
|
@ -261,6 +263,11 @@ class Controls extends FlxActionSet
|
||||||
inline function get_FREEPLAY_RIGHT()
|
inline function get_FREEPLAY_RIGHT()
|
||||||
return _freeplay_right.check();
|
return _freeplay_right.check();
|
||||||
|
|
||||||
|
public var FREEPLAY_CHAR_SELECT(get, never):Bool;
|
||||||
|
|
||||||
|
inline function get_FREEPLAY_CHAR_SELECT()
|
||||||
|
return _freeplay_char_select.check();
|
||||||
|
|
||||||
public var CUTSCENE_ADVANCE(get, never):Bool;
|
public var CUTSCENE_ADVANCE(get, never):Bool;
|
||||||
|
|
||||||
inline function get_CUTSCENE_ADVANCE()
|
inline function get_CUTSCENE_ADVANCE()
|
||||||
|
@ -317,6 +324,7 @@ class Controls extends FlxActionSet
|
||||||
add(_freeplay_favorite);
|
add(_freeplay_favorite);
|
||||||
add(_freeplay_left);
|
add(_freeplay_left);
|
||||||
add(_freeplay_right);
|
add(_freeplay_right);
|
||||||
|
add(_freeplay_char_select);
|
||||||
add(_cutscene_advance);
|
add(_cutscene_advance);
|
||||||
add(_debug_menu);
|
add(_debug_menu);
|
||||||
add(_debug_chart);
|
add(_debug_chart);
|
||||||
|
@ -325,19 +333,18 @@ class Controls extends FlxActionSet
|
||||||
add(_volume_down);
|
add(_volume_down);
|
||||||
add(_volume_mute);
|
add(_volume_mute);
|
||||||
|
|
||||||
for (action in digitalActions) {
|
for (action in digitalActions)
|
||||||
if (Std.isOfType(action, FunkinAction)) {
|
{
|
||||||
|
if (Std.isOfType(action, FunkinAction))
|
||||||
|
{
|
||||||
var funkinAction:FunkinAction = cast action;
|
var funkinAction:FunkinAction = cast action;
|
||||||
byName[funkinAction.name] = funkinAction;
|
byName[funkinAction.name] = funkinAction;
|
||||||
if (funkinAction.namePressed != null)
|
if (funkinAction.namePressed != null) byName[funkinAction.namePressed] = funkinAction;
|
||||||
byName[funkinAction.namePressed] = funkinAction;
|
if (funkinAction.nameReleased != null) byName[funkinAction.nameReleased] = funkinAction;
|
||||||
if (funkinAction.nameReleased != null)
|
|
||||||
byName[funkinAction.nameReleased] = funkinAction;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scheme == null)
|
if (scheme == null) scheme = None;
|
||||||
scheme = None;
|
|
||||||
|
|
||||||
setKeyboardScheme(scheme, false);
|
setKeyboardScheme(scheme, false);
|
||||||
}
|
}
|
||||||
|
@ -349,47 +356,50 @@ class Controls extends FlxActionSet
|
||||||
|
|
||||||
public function check(name:Action, trigger:FlxInputState = JUST_PRESSED, gamepadOnly:Bool = false):Bool
|
public function check(name:Action, trigger:FlxInputState = JUST_PRESSED, gamepadOnly:Bool = false):Bool
|
||||||
{
|
{
|
||||||
#if debug
|
#if FEATURE_DEBUG_FUNCTIONS
|
||||||
if (!byName.exists(name))
|
if (!byName.exists(name)) throw 'Invalid name: $name';
|
||||||
throw 'Invalid name: $name';
|
|
||||||
#end
|
#end
|
||||||
|
|
||||||
var action = byName[name];
|
var action = byName[name];
|
||||||
if (gamepadOnly)
|
if (gamepadOnly) return action.checkFiltered(trigger, GAMEPAD);
|
||||||
return action.checkFiltered(trigger, GAMEPAD);
|
|
||||||
else
|
else
|
||||||
return action.checkFiltered(trigger);
|
return action.checkFiltered(trigger);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getKeysForAction(name:Action):Array<FlxKey> {
|
public function getKeysForAction(name:Action):Array<FlxKey>
|
||||||
#if debug
|
{
|
||||||
if (!byName.exists(name))
|
#if FEATURE_DEBUG_FUNCTIONS
|
||||||
throw 'Invalid name: $name';
|
if (!byName.exists(name)) throw 'Invalid name: $name';
|
||||||
#end
|
#end
|
||||||
|
|
||||||
// TODO: Revert to `.map().filter()` once HashLink doesn't complain anymore.
|
// TODO: Revert to `.map().filter()` once HashLink doesn't complain anymore.
|
||||||
var result:Array<FlxKey> = [];
|
var result:Array<FlxKey> = [];
|
||||||
for (input in byName[name].inputs) {
|
for (input in byName[name].inputs)
|
||||||
|
{
|
||||||
if (input.device == KEYBOARD) result.push(input.inputID);
|
if (input.device == KEYBOARD) result.push(input.inputID);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getButtonsForAction(name:Action):Array<FlxGamepadInputID> {
|
public function getButtonsForAction(name:Action):Array<FlxGamepadInputID>
|
||||||
#if debug
|
{
|
||||||
if (!byName.exists(name))
|
#if FEATURE_DEBUG_FUNCTIONS
|
||||||
throw 'Invalid name: $name';
|
if (!byName.exists(name)) throw 'Invalid name: $name';
|
||||||
#end
|
#end
|
||||||
|
|
||||||
var result:Array<FlxGamepadInputID> = [];
|
var result:Array<FlxGamepadInputID> = [];
|
||||||
for (input in byName[name].inputs) {
|
for (input in byName[name].inputs)
|
||||||
|
{
|
||||||
if (input.device == GAMEPAD) result.push(input.inputID);
|
if (input.device == GAMEPAD) result.push(input.inputID);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDialogueName(action:FlxActionDigital):String
|
public function getDialogueName(action:FlxActionDigital, ?ignoreSurrounding:Bool = false):String
|
||||||
{
|
{
|
||||||
var input = action.inputs[0];
|
var input = action.inputs[0];
|
||||||
|
if (ignoreSurrounding == false)
|
||||||
|
{
|
||||||
return switch (input.device)
|
return switch (input.device)
|
||||||
{
|
{
|
||||||
case KEYBOARD: return '[${(input.inputID : FlxKey)}]';
|
case KEYBOARD: return '[${(input.inputID : FlxKey)}]';
|
||||||
|
@ -397,15 +407,30 @@ class Controls extends FlxActionSet
|
||||||
case device: throw 'unhandled device: $device';
|
case device: throw 'unhandled device: $device';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
public function getDialogueNameFromToken(token:String):String
|
|
||||||
{
|
{
|
||||||
return getDialogueName(getActionFromControl(Control.createByName(token.toUpperCase())));
|
return switch (input.device)
|
||||||
|
{
|
||||||
|
case KEYBOARD: return '${(input.inputID : FlxKey)}';
|
||||||
|
case GAMEPAD: return '${(input.inputID : FlxGamepadInputID)}';
|
||||||
|
case device: throw 'unhandled device: $device';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDialogueNameFromToken(token:String, ?ignoreSurrounding:Bool = false):String
|
||||||
|
{
|
||||||
|
return getDialogueName(getActionFromControl(Control.createByName(token.toUpperCase())), ignoreSurrounding);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDialogueNameFromControl(control:Control, ?ignoreSurrounding:Bool = false):String
|
||||||
|
{
|
||||||
|
return getDialogueName(getActionFromControl(control), ignoreSurrounding);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getActionFromControl(control:Control):FlxActionDigital
|
function getActionFromControl(control:Control):FlxActionDigital
|
||||||
{
|
{
|
||||||
return switch(control)
|
return switch (control)
|
||||||
{
|
{
|
||||||
case UI_UP: _ui_up;
|
case UI_UP: _ui_up;
|
||||||
case UI_DOWN: _ui_down;
|
case UI_DOWN: _ui_down;
|
||||||
|
@ -424,6 +449,7 @@ class Controls extends FlxActionSet
|
||||||
case FREEPLAY_FAVORITE: _freeplay_favorite;
|
case FREEPLAY_FAVORITE: _freeplay_favorite;
|
||||||
case FREEPLAY_LEFT: _freeplay_left;
|
case FREEPLAY_LEFT: _freeplay_left;
|
||||||
case FREEPLAY_RIGHT: _freeplay_right;
|
case FREEPLAY_RIGHT: _freeplay_right;
|
||||||
|
case FREEPLAY_CHAR_SELECT: _freeplay_char_select;
|
||||||
case CUTSCENE_ADVANCE: _cutscene_advance;
|
case CUTSCENE_ADVANCE: _cutscene_advance;
|
||||||
case DEBUG_MENU: _debug_menu;
|
case DEBUG_MENU: _debug_menu;
|
||||||
case DEBUG_CHART: _debug_chart;
|
case DEBUG_CHART: _debug_chart;
|
||||||
|
@ -448,7 +474,7 @@ class Controls extends FlxActionSet
|
||||||
*/
|
*/
|
||||||
function forEachBound(control:Control, func:FlxActionDigital->FlxInputState->Void)
|
function forEachBound(control:Control, func:FlxActionDigital->FlxInputState->Void)
|
||||||
{
|
{
|
||||||
switch(control)
|
switch (control)
|
||||||
{
|
{
|
||||||
case UI_UP:
|
case UI_UP:
|
||||||
func(_ui_up, PRESSED);
|
func(_ui_up, PRESSED);
|
||||||
|
@ -500,6 +526,8 @@ class Controls extends FlxActionSet
|
||||||
func(_freeplay_left, JUST_PRESSED);
|
func(_freeplay_left, JUST_PRESSED);
|
||||||
case FREEPLAY_RIGHT:
|
case FREEPLAY_RIGHT:
|
||||||
func(_freeplay_right, JUST_PRESSED);
|
func(_freeplay_right, JUST_PRESSED);
|
||||||
|
case FREEPLAY_CHAR_SELECT:
|
||||||
|
func(_freeplay_char_select, JUST_PRESSED);
|
||||||
case CUTSCENE_ADVANCE:
|
case CUTSCENE_ADVANCE:
|
||||||
func(_cutscene_advance, JUST_PRESSED);
|
func(_cutscene_advance, JUST_PRESSED);
|
||||||
case DEBUG_MENU:
|
case DEBUG_MENU:
|
||||||
|
@ -519,10 +547,9 @@ class Controls extends FlxActionSet
|
||||||
|
|
||||||
public function replaceBinding(control:Control, device:Device, toAdd:Int, toRemove:Int)
|
public function replaceBinding(control:Control, device:Device, toAdd:Int, toRemove:Int)
|
||||||
{
|
{
|
||||||
if (toAdd == toRemove)
|
if (toAdd == toRemove) return;
|
||||||
return;
|
|
||||||
|
|
||||||
switch(device)
|
switch (device)
|
||||||
{
|
{
|
||||||
case Keys:
|
case Keys:
|
||||||
forEachBound(control, function(action, state) replaceKey(action, toAdd, toRemove, state));
|
forEachBound(control, function(action, state) replaceKey(action, toAdd, toRemove, state));
|
||||||
|
@ -534,7 +561,8 @@ class Controls extends FlxActionSet
|
||||||
|
|
||||||
function replaceKey(action:FlxActionDigital, toAdd:FlxKey, toRemove:FlxKey, state:FlxInputState)
|
function replaceKey(action:FlxActionDigital, toAdd:FlxKey, toRemove:FlxKey, state:FlxInputState)
|
||||||
{
|
{
|
||||||
if (action.inputs.length == 0) {
|
if (action.inputs.length == 0)
|
||||||
|
{
|
||||||
// Add the keybind, don't replace.
|
// Add the keybind, don't replace.
|
||||||
addKeys(action, [toAdd], state);
|
addKeys(action, [toAdd], state);
|
||||||
return;
|
return;
|
||||||
|
@ -548,34 +576,44 @@ class Controls extends FlxActionSet
|
||||||
|
|
||||||
if (input.device == KEYBOARD && input.inputID == toRemove)
|
if (input.device == KEYBOARD && input.inputID == toRemove)
|
||||||
{
|
{
|
||||||
if (toAdd == FlxKey.NONE) {
|
if (toAdd == FlxKey.NONE)
|
||||||
|
{
|
||||||
// Remove the keybind, don't replace.
|
// Remove the keybind, don't replace.
|
||||||
action.inputs.remove(input);
|
action.inputs.remove(input);
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
// Replace the keybind.
|
// Replace the keybind.
|
||||||
@:privateAccess
|
@:privateAccess
|
||||||
action.inputs[i].inputID = toAdd;
|
action.inputs[i].inputID = toAdd;
|
||||||
}
|
}
|
||||||
hasReplaced = true;
|
hasReplaced = true;
|
||||||
} else if (input.device == KEYBOARD && input.inputID == toAdd) {
|
}
|
||||||
|
else if (input.device == KEYBOARD && input.inputID == toAdd)
|
||||||
|
{
|
||||||
// This key is already bound!
|
// This key is already bound!
|
||||||
if (hasReplaced) {
|
if (hasReplaced)
|
||||||
|
{
|
||||||
// Remove the duplicate keybind, don't replace.
|
// Remove the duplicate keybind, don't replace.
|
||||||
action.inputs.remove(input);
|
action.inputs.remove(input);
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
hasReplaced = true;
|
hasReplaced = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasReplaced) {
|
if (!hasReplaced)
|
||||||
|
{
|
||||||
addKeys(action, [toAdd], state);
|
addKeys(action, [toAdd], state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function replaceButton(action:FlxActionDigital, deviceID:Int, toAdd:FlxGamepadInputID, toRemove:FlxGamepadInputID, state:FlxInputState)
|
function replaceButton(action:FlxActionDigital, deviceID:Int, toAdd:FlxGamepadInputID, toRemove:FlxGamepadInputID, state:FlxInputState)
|
||||||
{
|
{
|
||||||
if (action.inputs.length == 0) {
|
if (action.inputs.length == 0)
|
||||||
|
{
|
||||||
addButtons(action, [toAdd], state, deviceID);
|
addButtons(action, [toAdd], state, deviceID);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -594,7 +632,8 @@ class Controls extends FlxActionSet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasReplaced) {
|
if (!hasReplaced)
|
||||||
|
{
|
||||||
addButtons(action, [toAdd], state, deviceID);
|
addButtons(action, [toAdd], state, deviceID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -606,18 +645,16 @@ class Controls extends FlxActionSet
|
||||||
var action = controls.byName[name];
|
var action = controls.byName[name];
|
||||||
for (input in action.inputs)
|
for (input in action.inputs)
|
||||||
{
|
{
|
||||||
if (device == null || isDevice(input, device))
|
if (device == null || isDevice(input, device)) byName[name].add(cast input);
|
||||||
byName[name].add(cast input);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(device)
|
switch (device)
|
||||||
{
|
{
|
||||||
case null:
|
case null:
|
||||||
// add all
|
// add all
|
||||||
for (gamepad in controls.gamepadsAdded)
|
for (gamepad in controls.gamepadsAdded)
|
||||||
if (gamepadsAdded.indexOf(gamepad) == -1)
|
if (gamepadsAdded.indexOf(gamepad) == -1) gamepadsAdded.push(gamepad);
|
||||||
gamepadsAdded.push(gamepad);
|
|
||||||
|
|
||||||
mergeKeyboardScheme(controls.keyboardScheme);
|
mergeKeyboardScheme(controls.keyboardScheme);
|
||||||
|
|
||||||
|
@ -637,7 +674,7 @@ class Controls extends FlxActionSet
|
||||||
{
|
{
|
||||||
if (scheme != None)
|
if (scheme != None)
|
||||||
{
|
{
|
||||||
switch(keyboardScheme)
|
switch (keyboardScheme)
|
||||||
{
|
{
|
||||||
case None:
|
case None:
|
||||||
keyboardScheme = scheme;
|
keyboardScheme = scheme;
|
||||||
|
@ -672,7 +709,8 @@ class Controls extends FlxActionSet
|
||||||
|
|
||||||
static function addKeys(action:FlxActionDigital, keys:Array<FlxKey>, state:FlxInputState)
|
static function addKeys(action:FlxActionDigital, keys:Array<FlxKey>, state:FlxInputState)
|
||||||
{
|
{
|
||||||
for (key in keys) {
|
for (key in keys)
|
||||||
|
{
|
||||||
if (key == FlxKey.NONE) continue; // Ignore unbound keys.
|
if (key == FlxKey.NONE) continue; // Ignore unbound keys.
|
||||||
action.addKey(key, state);
|
action.addKey(key, state);
|
||||||
}
|
}
|
||||||
|
@ -684,15 +722,13 @@ class Controls extends FlxActionSet
|
||||||
while (i-- > 0)
|
while (i-- > 0)
|
||||||
{
|
{
|
||||||
var input = action.inputs[i];
|
var input = action.inputs[i];
|
||||||
if (input.device == KEYBOARD && keys.indexOf(cast input.inputID) != -1)
|
if (input.device == KEYBOARD && keys.indexOf(cast input.inputID) != -1) action.remove(input);
|
||||||
action.remove(input);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setKeyboardScheme(scheme:KeyboardScheme, reset = true)
|
public function setKeyboardScheme(scheme:KeyboardScheme, reset = true)
|
||||||
{
|
{
|
||||||
if (reset)
|
if (reset) removeKeyboard();
|
||||||
removeKeyboard();
|
|
||||||
|
|
||||||
keyboardScheme = scheme;
|
keyboardScheme = scheme;
|
||||||
|
|
||||||
|
@ -713,6 +749,7 @@ class Controls extends FlxActionSet
|
||||||
bindKeys(Control.FREEPLAY_FAVORITE, getDefaultKeybinds(scheme, Control.FREEPLAY_FAVORITE));
|
bindKeys(Control.FREEPLAY_FAVORITE, getDefaultKeybinds(scheme, Control.FREEPLAY_FAVORITE));
|
||||||
bindKeys(Control.FREEPLAY_LEFT, getDefaultKeybinds(scheme, Control.FREEPLAY_LEFT));
|
bindKeys(Control.FREEPLAY_LEFT, getDefaultKeybinds(scheme, Control.FREEPLAY_LEFT));
|
||||||
bindKeys(Control.FREEPLAY_RIGHT, getDefaultKeybinds(scheme, Control.FREEPLAY_RIGHT));
|
bindKeys(Control.FREEPLAY_RIGHT, getDefaultKeybinds(scheme, Control.FREEPLAY_RIGHT));
|
||||||
|
bindKeys(Control.FREEPLAY_CHAR_SELECT, getDefaultKeybinds(scheme, Control.FREEPLAY_CHAR_SELECT));
|
||||||
bindKeys(Control.CUTSCENE_ADVANCE, getDefaultKeybinds(scheme, Control.CUTSCENE_ADVANCE));
|
bindKeys(Control.CUTSCENE_ADVANCE, getDefaultKeybinds(scheme, Control.CUTSCENE_ADVANCE));
|
||||||
bindKeys(Control.DEBUG_MENU, getDefaultKeybinds(scheme, Control.DEBUG_MENU));
|
bindKeys(Control.DEBUG_MENU, getDefaultKeybinds(scheme, Control.DEBUG_MENU));
|
||||||
bindKeys(Control.DEBUG_CHART, getDefaultKeybinds(scheme, Control.DEBUG_CHART));
|
bindKeys(Control.DEBUG_CHART, getDefaultKeybinds(scheme, Control.DEBUG_CHART));
|
||||||
|
@ -724,10 +761,13 @@ class Controls extends FlxActionSet
|
||||||
bindMobileLol();
|
bindMobileLol();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDefaultKeybinds(scheme:KeyboardScheme, control:Control):Array<FlxKey> {
|
function getDefaultKeybinds(scheme:KeyboardScheme, control:Control):Array<FlxKey>
|
||||||
switch (scheme) {
|
{
|
||||||
|
switch (scheme)
|
||||||
|
{
|
||||||
case Solo:
|
case Solo:
|
||||||
switch (control) {
|
switch (control)
|
||||||
|
{
|
||||||
case Control.UI_UP: return [W, FlxKey.UP];
|
case Control.UI_UP: return [W, FlxKey.UP];
|
||||||
case Control.UI_DOWN: return [S, FlxKey.DOWN];
|
case Control.UI_DOWN: return [S, FlxKey.DOWN];
|
||||||
case Control.UI_LEFT: return [A, FlxKey.LEFT];
|
case Control.UI_LEFT: return [A, FlxKey.LEFT];
|
||||||
|
@ -745,6 +785,7 @@ class Controls extends FlxActionSet
|
||||||
case Control.FREEPLAY_FAVORITE: return [F]; // Favorite a song on the menu
|
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_LEFT: return [Q]; // Switch tabs on the menu
|
||||||
case Control.FREEPLAY_RIGHT: return [E]; // 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.CUTSCENE_ADVANCE: return [Z, ENTER];
|
||||||
case Control.DEBUG_MENU: return [GRAVEACCENT];
|
case Control.DEBUG_MENU: return [GRAVEACCENT];
|
||||||
case Control.DEBUG_CHART: return [];
|
case Control.DEBUG_CHART: return [];
|
||||||
|
@ -754,7 +795,8 @@ class Controls extends FlxActionSet
|
||||||
case Control.VOLUME_MUTE: return [ZERO, NUMPADZERO];
|
case Control.VOLUME_MUTE: return [ZERO, NUMPADZERO];
|
||||||
}
|
}
|
||||||
case Duo(true):
|
case Duo(true):
|
||||||
switch (control) {
|
switch (control)
|
||||||
|
{
|
||||||
case Control.UI_UP: return [W];
|
case Control.UI_UP: return [W];
|
||||||
case Control.UI_DOWN: return [S];
|
case Control.UI_DOWN: return [S];
|
||||||
case Control.UI_LEFT: return [A];
|
case Control.UI_LEFT: return [A];
|
||||||
|
@ -772,6 +814,7 @@ class Controls extends FlxActionSet
|
||||||
case Control.FREEPLAY_FAVORITE: return [F]; // Favorite a song on the menu
|
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_LEFT: return [Q]; // Switch tabs on the menu
|
||||||
case Control.FREEPLAY_RIGHT: return [E]; // 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 [G, Z];
|
case Control.CUTSCENE_ADVANCE: return [G, Z];
|
||||||
case Control.DEBUG_MENU: return [GRAVEACCENT];
|
case Control.DEBUG_MENU: return [GRAVEACCENT];
|
||||||
case Control.DEBUG_CHART: return [];
|
case Control.DEBUG_CHART: return [];
|
||||||
|
@ -779,10 +822,10 @@ class Controls extends FlxActionSet
|
||||||
case Control.VOLUME_UP: return [PLUS];
|
case Control.VOLUME_UP: return [PLUS];
|
||||||
case Control.VOLUME_DOWN: return [MINUS];
|
case Control.VOLUME_DOWN: return [MINUS];
|
||||||
case Control.VOLUME_MUTE: return [ZERO];
|
case Control.VOLUME_MUTE: return [ZERO];
|
||||||
|
|
||||||
}
|
}
|
||||||
case Duo(false):
|
case Duo(false):
|
||||||
switch (control) {
|
switch (control)
|
||||||
|
{
|
||||||
case Control.UI_UP: return [FlxKey.UP];
|
case Control.UI_UP: return [FlxKey.UP];
|
||||||
case Control.UI_DOWN: return [FlxKey.DOWN];
|
case Control.UI_DOWN: return [FlxKey.DOWN];
|
||||||
case Control.UI_LEFT: return [FlxKey.LEFT];
|
case Control.UI_LEFT: return [FlxKey.LEFT];
|
||||||
|
@ -800,6 +843,7 @@ class Controls extends FlxActionSet
|
||||||
case Control.FREEPLAY_FAVORITE: return [];
|
case Control.FREEPLAY_FAVORITE: return [];
|
||||||
case Control.FREEPLAY_LEFT: return [];
|
case Control.FREEPLAY_LEFT: return [];
|
||||||
case Control.FREEPLAY_RIGHT: return [];
|
case Control.FREEPLAY_RIGHT: return [];
|
||||||
|
case Control.FREEPLAY_CHAR_SELECT: return [];
|
||||||
case Control.CUTSCENE_ADVANCE: return [ENTER];
|
case Control.CUTSCENE_ADVANCE: return [ENTER];
|
||||||
case Control.DEBUG_MENU: return [];
|
case Control.DEBUG_MENU: return [];
|
||||||
case Control.DEBUG_CHART: return [];
|
case Control.DEBUG_CHART: return [];
|
||||||
|
@ -807,7 +851,6 @@ class Controls extends FlxActionSet
|
||||||
case Control.VOLUME_UP: return [NUMPADPLUS];
|
case Control.VOLUME_UP: return [NUMPADPLUS];
|
||||||
case Control.VOLUME_DOWN: return [NUMPADMINUS];
|
case Control.VOLUME_DOWN: return [NUMPADMINUS];
|
||||||
case Control.VOLUME_MUTE: return [NUMPADZERO];
|
case Control.VOLUME_MUTE: return [NUMPADZERO];
|
||||||
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
// Fallthrough.
|
// Fallthrough.
|
||||||
|
@ -834,8 +877,7 @@ class Controls extends FlxActionSet
|
||||||
#end
|
#end
|
||||||
|
|
||||||
#if android
|
#if android
|
||||||
forEachBound(Control.BACK, function(action, pres)
|
forEachBound(Control.BACK, function(action, pres) {
|
||||||
{
|
|
||||||
action.add(new FlxActionInputDigitalAndroid(FlxAndroidKey.BACK, JUST_PRESSED));
|
action.add(new FlxActionInputDigitalAndroid(FlxAndroidKey.BACK, JUST_PRESSED));
|
||||||
});
|
});
|
||||||
#end
|
#end
|
||||||
|
@ -849,8 +891,7 @@ class Controls extends FlxActionSet
|
||||||
while (i-- > 0)
|
while (i-- > 0)
|
||||||
{
|
{
|
||||||
var input = action.inputs[i];
|
var input = action.inputs[i];
|
||||||
if (input.device == KEYBOARD)
|
if (input.device == KEYBOARD) action.remove(input);
|
||||||
action.remove(input);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -862,11 +903,13 @@ class Controls extends FlxActionSet
|
||||||
fromSaveData(padData, Gamepad(id));
|
fromSaveData(padData, Gamepad(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getGamepadIds():Array<Int> {
|
public function getGamepadIds():Array<Int>
|
||||||
|
{
|
||||||
return gamepadsAdded;
|
return gamepadsAdded;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getGamepads():Array<FlxGamepad> {
|
public function getGamepads():Array<FlxGamepad>
|
||||||
|
{
|
||||||
return [for (id in gamepadsAdded) FlxG.gamepads.getByID(id)];
|
return [for (id in gamepadsAdded) FlxG.gamepads.getByID(id)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -886,8 +929,7 @@ class Controls extends FlxActionSet
|
||||||
while (i-- > 0)
|
while (i-- > 0)
|
||||||
{
|
{
|
||||||
var input = action.inputs[i];
|
var input = action.inputs[i];
|
||||||
if (isGamepad(input, deviceID))
|
if (isGamepad(input, deviceID)) action.remove(input);
|
||||||
action.remove(input);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -924,32 +966,58 @@ class Controls extends FlxActionSet
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDefaultGamepadBinds(control:Control):Array<FlxGamepadInputID> {
|
function getDefaultGamepadBinds(control:Control):Array<FlxGamepadInputID>
|
||||||
switch(control) {
|
{
|
||||||
case Control.ACCEPT: return [#if switch B #else A #end];
|
switch (control)
|
||||||
case Control.BACK: return [#if switch A #else B #end];
|
{
|
||||||
case Control.UI_UP: return [DPAD_UP, LEFT_STICK_DIGITAL_UP];
|
case Control.ACCEPT:
|
||||||
case Control.UI_DOWN: return [DPAD_DOWN, LEFT_STICK_DIGITAL_DOWN];
|
return [#if switch B #else A #end];
|
||||||
case Control.UI_LEFT: return [DPAD_LEFT, LEFT_STICK_DIGITAL_LEFT];
|
case Control.BACK:
|
||||||
case Control.UI_RIGHT: return [DPAD_RIGHT, LEFT_STICK_DIGITAL_RIGHT];
|
return [#if switch A #else B #end];
|
||||||
case Control.NOTE_UP: return [DPAD_UP, Y, LEFT_STICK_DIGITAL_UP, RIGHT_STICK_DIGITAL_UP];
|
case Control.UI_UP:
|
||||||
case Control.NOTE_DOWN: return [DPAD_DOWN, A, LEFT_STICK_DIGITAL_DOWN, RIGHT_STICK_DIGITAL_DOWN];
|
return [DPAD_UP, LEFT_STICK_DIGITAL_UP];
|
||||||
case Control.NOTE_LEFT: return [DPAD_LEFT, X, LEFT_STICK_DIGITAL_LEFT, RIGHT_STICK_DIGITAL_LEFT];
|
case Control.UI_DOWN:
|
||||||
case Control.NOTE_RIGHT: return [DPAD_RIGHT, B, LEFT_STICK_DIGITAL_RIGHT, RIGHT_STICK_DIGITAL_RIGHT];
|
return [DPAD_DOWN, LEFT_STICK_DIGITAL_DOWN];
|
||||||
case Control.PAUSE: return [START];
|
case Control.UI_LEFT:
|
||||||
case Control.RESET: return [FlxGamepadInputID.BACK]; // Back (i.e. Select)
|
return [DPAD_LEFT, LEFT_STICK_DIGITAL_LEFT];
|
||||||
case Control.WINDOW_FULLSCREEN: [];
|
case Control.UI_RIGHT:
|
||||||
case Control.WINDOW_SCREENSHOT: [];
|
return [DPAD_RIGHT, LEFT_STICK_DIGITAL_RIGHT];
|
||||||
case Control.CUTSCENE_ADVANCE: return [A];
|
case Control.NOTE_UP:
|
||||||
case Control.FREEPLAY_FAVORITE: [FlxGamepadInputID.BACK]; // Back (i.e. Select)
|
return [DPAD_UP, Y, LEFT_STICK_DIGITAL_UP, RIGHT_STICK_DIGITAL_UP];
|
||||||
case Control.FREEPLAY_LEFT: [LEFT_SHOULDER];
|
case Control.NOTE_DOWN:
|
||||||
case Control.FREEPLAY_RIGHT: [RIGHT_SHOULDER];
|
return [DPAD_DOWN, A, LEFT_STICK_DIGITAL_DOWN, RIGHT_STICK_DIGITAL_DOWN];
|
||||||
case Control.VOLUME_UP: [];
|
case Control.NOTE_LEFT:
|
||||||
case Control.VOLUME_DOWN: [];
|
return [DPAD_LEFT, X, LEFT_STICK_DIGITAL_LEFT, RIGHT_STICK_DIGITAL_LEFT];
|
||||||
case Control.VOLUME_MUTE: [];
|
case Control.NOTE_RIGHT:
|
||||||
case Control.DEBUG_MENU: [];
|
return [DPAD_RIGHT, B, LEFT_STICK_DIGITAL_RIGHT, RIGHT_STICK_DIGITAL_RIGHT];
|
||||||
case Control.DEBUG_CHART: [];
|
case Control.PAUSE:
|
||||||
case Control.DEBUG_STAGE: [];
|
return [START];
|
||||||
|
case Control.RESET:
|
||||||
|
return [FlxGamepadInputID.BACK]; // Back (i.e. Select)
|
||||||
|
case Control.WINDOW_FULLSCREEN:
|
||||||
|
[];
|
||||||
|
case Control.WINDOW_SCREENSHOT:
|
||||||
|
[];
|
||||||
|
case Control.CUTSCENE_ADVANCE:
|
||||||
|
return [A];
|
||||||
|
case Control.FREEPLAY_FAVORITE:
|
||||||
|
[FlxGamepadInputID.BACK]; // Back (i.e. Select)
|
||||||
|
case Control.FREEPLAY_LEFT:
|
||||||
|
[LEFT_SHOULDER];
|
||||||
|
case Control.FREEPLAY_RIGHT:
|
||||||
|
[RIGHT_SHOULDER];
|
||||||
|
case Control.VOLUME_UP:
|
||||||
|
[];
|
||||||
|
case Control.VOLUME_DOWN:
|
||||||
|
[];
|
||||||
|
case Control.VOLUME_MUTE:
|
||||||
|
[];
|
||||||
|
case Control.DEBUG_MENU:
|
||||||
|
[];
|
||||||
|
case Control.DEBUG_CHART:
|
||||||
|
[];
|
||||||
|
case Control.DEBUG_STAGE:
|
||||||
|
[];
|
||||||
default:
|
default:
|
||||||
// Fallthrough.
|
// Fallthrough.
|
||||||
}
|
}
|
||||||
|
@ -967,8 +1035,7 @@ class Controls extends FlxActionSet
|
||||||
|
|
||||||
public function touchShit(control:Control, id)
|
public function touchShit(control:Control, id)
|
||||||
{
|
{
|
||||||
forEachBound(control, function(action, state)
|
forEachBound(control, function(action, state) {
|
||||||
{
|
|
||||||
// action
|
// action
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -984,7 +1051,8 @@ class Controls extends FlxActionSet
|
||||||
|
|
||||||
inline static function addButtons(action:FlxActionDigital, buttons:Array<FlxGamepadInputID>, state, id)
|
inline static function addButtons(action:FlxActionDigital, buttons:Array<FlxGamepadInputID>, state, id)
|
||||||
{
|
{
|
||||||
for (button in buttons) {
|
for (button in buttons)
|
||||||
|
{
|
||||||
if (button == FlxGamepadInputID.NONE) continue; // Ignore unbound keys.
|
if (button == FlxGamepadInputID.NONE) continue; // Ignore unbound keys.
|
||||||
action.addGamepad(button, state, id);
|
action.addGamepad(button, state, id);
|
||||||
}
|
}
|
||||||
|
@ -996,29 +1064,25 @@ class Controls extends FlxActionSet
|
||||||
while (i-- > 0)
|
while (i-- > 0)
|
||||||
{
|
{
|
||||||
var input = action.inputs[i];
|
var input = action.inputs[i];
|
||||||
if (isGamepad(input, gamepadID) && buttons.indexOf(cast input.inputID) != -1)
|
if (isGamepad(input, gamepadID) && buttons.indexOf(cast input.inputID) != -1) action.remove(input);
|
||||||
action.remove(input);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getInputsFor(control:Control, device:Device, ?list:Array<Int>):Array<Int>
|
public function getInputsFor(control:Control, device:Device, ?list:Array<Int>):Array<Int>
|
||||||
{
|
{
|
||||||
if (list == null)
|
if (list == null) list = [];
|
||||||
list = [];
|
|
||||||
|
|
||||||
switch(device)
|
switch (device)
|
||||||
{
|
{
|
||||||
case Keys:
|
case Keys:
|
||||||
for (input in getActionFromControl(control).inputs)
|
for (input in getActionFromControl(control).inputs)
|
||||||
{
|
{
|
||||||
if (input.device == KEYBOARD)
|
if (input.device == KEYBOARD) list.push(input.inputID);
|
||||||
list.push(input.inputID);
|
|
||||||
}
|
}
|
||||||
case Gamepad(id):
|
case Gamepad(id):
|
||||||
for (input in getActionFromControl(control).inputs)
|
for (input in getActionFromControl(control).inputs)
|
||||||
{
|
{
|
||||||
if (isGamepad(input, id))
|
if (isGamepad(input, id)) list.push(input.inputID);
|
||||||
list.push(input.inputID);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
|
@ -1026,7 +1090,7 @@ class Controls extends FlxActionSet
|
||||||
|
|
||||||
public function removeDevice(device:Device)
|
public function removeDevice(device:Device)
|
||||||
{
|
{
|
||||||
switch(device)
|
switch (device)
|
||||||
{
|
{
|
||||||
case Keys:
|
case Keys:
|
||||||
setKeyboardScheme(None);
|
setKeyboardScheme(None);
|
||||||
|
@ -1040,27 +1104,32 @@ class Controls extends FlxActionSet
|
||||||
* An EMPTY array means the control is uninitialized and needs to be reset to default.
|
* An EMPTY array means the control is uninitialized and needs to be reset to default.
|
||||||
* An array with a single FlxKey.NONE means the control was intentionally unbound by the user.
|
* An array with a single FlxKey.NONE means the control was intentionally unbound by the user.
|
||||||
*/
|
*/
|
||||||
public function fromSaveData(data:Dynamic, device:Device)
|
public function fromSaveData(data:Dynamic, device:Device):Void
|
||||||
{
|
{
|
||||||
for (control in Control.createAll())
|
for (control in Control.createAll())
|
||||||
{
|
{
|
||||||
var inputs:Array<Int> = Reflect.field(data, control.getName());
|
var inputs:Array<Int> = Reflect.field(data, control.getName());
|
||||||
inputs = inputs.distinct();
|
inputs = inputs?.distinct();
|
||||||
if (inputs != null)
|
if (inputs != null)
|
||||||
{
|
{
|
||||||
if (inputs.length == 0) {
|
if (inputs.length == 0)
|
||||||
|
{
|
||||||
trace('Control ${control} is missing bindings, resetting to default.');
|
trace('Control ${control} is missing bindings, resetting to default.');
|
||||||
switch(device)
|
switch (device)
|
||||||
{
|
{
|
||||||
case Keys:
|
case Keys:
|
||||||
bindKeys(control, getDefaultKeybinds(Solo, control));
|
bindKeys(control, getDefaultKeybinds(Solo, control));
|
||||||
case Gamepad(id):
|
case Gamepad(id):
|
||||||
bindButtons(control, id, getDefaultGamepadBinds(control));
|
bindButtons(control, id, getDefaultGamepadBinds(control));
|
||||||
}
|
}
|
||||||
} else if (inputs == [FlxKey.NONE]) {
|
}
|
||||||
|
else if (inputs == [FlxKey.NONE])
|
||||||
|
{
|
||||||
trace('Control ${control} is unbound, leaving it be.');
|
trace('Control ${control} is unbound, leaving it be.');
|
||||||
} else {
|
}
|
||||||
switch(device)
|
else
|
||||||
|
{
|
||||||
|
switch (device)
|
||||||
{
|
{
|
||||||
case Keys:
|
case Keys:
|
||||||
bindKeys(control, inputs.copy());
|
bindKeys(control, inputs.copy());
|
||||||
|
@ -1068,9 +1137,11 @@ class Controls extends FlxActionSet
|
||||||
bindButtons(control, id, inputs.copy());
|
bindButtons(control, id, inputs.copy());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
trace('Control ${control} is missing bindings, resetting to default.');
|
trace('Control ${control} is missing bindings, resetting to default.');
|
||||||
switch(device)
|
switch (device)
|
||||||
{
|
{
|
||||||
case Keys:
|
case Keys:
|
||||||
bindKeys(control, getDefaultKeybinds(Solo, control));
|
bindKeys(control, getDefaultKeybinds(Solo, control));
|
||||||
|
@ -1095,9 +1166,12 @@ class Controls extends FlxActionSet
|
||||||
var inputs = getInputsFor(control, device);
|
var inputs = getInputsFor(control, device);
|
||||||
isEmpty = isEmpty && inputs.length == 0;
|
isEmpty = isEmpty && inputs.length == 0;
|
||||||
|
|
||||||
if (inputs.length == 0) {
|
if (inputs.length == 0)
|
||||||
|
{
|
||||||
inputs = [FlxKey.NONE];
|
inputs = [FlxKey.NONE];
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
inputs = inputs.distinct();
|
inputs = inputs.distinct();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1109,7 +1183,7 @@ class Controls extends FlxActionSet
|
||||||
|
|
||||||
static function isDevice(input:FlxActionInput, device:Device)
|
static function isDevice(input:FlxActionInput, device:Device)
|
||||||
{
|
{
|
||||||
return switch(device)
|
return switch (device)
|
||||||
{
|
{
|
||||||
case Keys: input.device == KEYBOARD;
|
case Keys: input.device == KEYBOARD;
|
||||||
case Gamepad(id): isGamepad(input, id);
|
case Gamepad(id): isGamepad(input, id);
|
||||||
|
@ -1141,7 +1215,8 @@ typedef Swipes =
|
||||||
* - Combining `pressed` and `released` inputs into one action.
|
* - Combining `pressed` and `released` inputs into one action.
|
||||||
* - Filtering by input method (`KEYBOARD`, `MOUSE`, `GAMEPAD`, etc).
|
* - Filtering by input method (`KEYBOARD`, `MOUSE`, `GAMEPAD`, etc).
|
||||||
*/
|
*/
|
||||||
class FunkinAction extends FlxActionDigital {
|
class FunkinAction extends FlxActionDigital
|
||||||
|
{
|
||||||
public var namePressed(default, null):Null<String>;
|
public var namePressed(default, null):Null<String>;
|
||||||
public var nameReleased(default, null):Null<String>;
|
public var nameReleased(default, null):Null<String>;
|
||||||
|
|
||||||
|
@ -1158,83 +1233,102 @@ class FunkinAction extends FlxActionDigital {
|
||||||
/**
|
/**
|
||||||
* Input checks default to whether the input was just pressed, on any input device.
|
* Input checks default to whether the input was just pressed, on any input device.
|
||||||
*/
|
*/
|
||||||
public override function check():Bool {
|
public override function check():Bool
|
||||||
|
{
|
||||||
return checkFiltered(JUST_PRESSED);
|
return checkFiltered(JUST_PRESSED);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether the input is currently being held.
|
* Check whether the input is currently being held.
|
||||||
*/
|
*/
|
||||||
public function checkPressed():Bool {
|
public function checkPressed():Bool
|
||||||
|
{
|
||||||
return checkFiltered(PRESSED);
|
return checkFiltered(PRESSED);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether the input is currently being held, and was not held last frame.
|
* Check whether the input is currently being held, and was not held last frame.
|
||||||
*/
|
*/
|
||||||
public function checkJustPressed():Bool {
|
public function checkJustPressed():Bool
|
||||||
|
{
|
||||||
return checkFiltered(JUST_PRESSED);
|
return checkFiltered(JUST_PRESSED);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether the input is not currently being held.
|
* Check whether the input is not currently being held.
|
||||||
*/
|
*/
|
||||||
public function checkReleased():Bool {
|
public function checkReleased():Bool
|
||||||
|
{
|
||||||
return checkFiltered(RELEASED);
|
return checkFiltered(RELEASED);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether the input is not currently being held, and was held last frame.
|
* Check whether the input is not currently being held, and was held last frame.
|
||||||
*/
|
*/
|
||||||
public function checkJustReleased():Bool {
|
public function checkJustReleased():Bool
|
||||||
|
{
|
||||||
return checkFiltered(JUST_RELEASED);
|
return checkFiltered(JUST_RELEASED);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether the input is currently being held by a gamepad device.
|
* Check whether the input is currently being held by a gamepad device.
|
||||||
*/
|
*/
|
||||||
public function checkPressedGamepad():Bool {
|
public function checkPressedGamepad():Bool
|
||||||
|
{
|
||||||
return checkFiltered(PRESSED, GAMEPAD);
|
return checkFiltered(PRESSED, GAMEPAD);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether the input is currently being held by a gamepad device, and was not held last frame.
|
* Check whether the input is currently being held by a gamepad device, and was not held last frame.
|
||||||
*/
|
*/
|
||||||
public function checkJustPressedGamepad():Bool {
|
public function checkJustPressedGamepad():Bool
|
||||||
|
{
|
||||||
return checkFiltered(JUST_PRESSED, GAMEPAD);
|
return checkFiltered(JUST_PRESSED, GAMEPAD);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether the input is not currently being held by a gamepad device.
|
* Check whether the input is not currently being held by a gamepad device.
|
||||||
*/
|
*/
|
||||||
public function checkReleasedGamepad():Bool {
|
public function checkReleasedGamepad():Bool
|
||||||
|
{
|
||||||
return checkFiltered(RELEASED, GAMEPAD);
|
return checkFiltered(RELEASED, GAMEPAD);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether the input is not currently being held by a gamepad device, and was held last frame.
|
* Check whether the input is not currently being held by a gamepad device, and was held last frame.
|
||||||
*/
|
*/
|
||||||
public function checkJustReleasedGamepad():Bool {
|
public function checkJustReleasedGamepad():Bool
|
||||||
|
{
|
||||||
return checkFiltered(JUST_RELEASED, GAMEPAD);
|
return checkFiltered(JUST_RELEASED, GAMEPAD);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function checkMultiFiltered(?filterTriggers:Array<FlxInputState>, ?filterDevices:Array<FlxInputDevice>):Bool {
|
public function checkMultiFiltered(?filterTriggers:Array<FlxInputState>, ?filterDevices:Array<FlxInputDevice>):Bool
|
||||||
if (filterTriggers == null) {
|
{
|
||||||
|
if (filterTriggers == null)
|
||||||
|
{
|
||||||
filterTriggers = [PRESSED, JUST_PRESSED];
|
filterTriggers = [PRESSED, JUST_PRESSED];
|
||||||
}
|
}
|
||||||
if (filterDevices == null) {
|
if (filterDevices == null)
|
||||||
|
{
|
||||||
filterDevices = [];
|
filterDevices = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform checkFiltered for each combination.
|
// Perform checkFiltered for each combination.
|
||||||
for (i in filterTriggers) {
|
for (i in filterTriggers)
|
||||||
if (filterDevices.length == 0) {
|
{
|
||||||
if (checkFiltered(i)) {
|
if (filterDevices.length == 0)
|
||||||
|
{
|
||||||
|
if (checkFiltered(i))
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
for (j in filterDevices) {
|
else
|
||||||
if (checkFiltered(i, j)) {
|
{
|
||||||
|
for (j in filterDevices)
|
||||||
|
{
|
||||||
|
if (checkFiltered(i, j))
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1249,14 +1343,16 @@ class FunkinAction extends FlxActionDigital {
|
||||||
* @param filterTrigger Optionally filter by trigger condition (`JUST_PRESSED`, `PRESSED`, `JUST_RELEASED`, `RELEASED`).
|
* @param filterTrigger Optionally filter by trigger condition (`JUST_PRESSED`, `PRESSED`, `JUST_RELEASED`, `RELEASED`).
|
||||||
* @param filterDevice Optionally filter by device (`KEYBOARD`, `MOUSE`, `GAMEPAD`, `OTHER`).
|
* @param filterDevice Optionally filter by device (`KEYBOARD`, `MOUSE`, `GAMEPAD`, `OTHER`).
|
||||||
*/
|
*/
|
||||||
public function checkFiltered(?filterTrigger:FlxInputState, ?filterDevice:FlxInputDevice):Bool {
|
public function checkFiltered(?filterTrigger:FlxInputState, ?filterDevice:FlxInputDevice):Bool
|
||||||
|
{
|
||||||
// The normal
|
// The normal
|
||||||
|
|
||||||
// Make sure we only update the inputs once per frame.
|
// Make sure we only update the inputs once per frame.
|
||||||
var key = '${filterTrigger}:${filterDevice}';
|
var key = '${filterTrigger}:${filterDevice}';
|
||||||
var cacheEntry = cache.get(key);
|
var cacheEntry = cache.get(key);
|
||||||
|
|
||||||
if (cacheEntry != null && cacheEntry.timestamp == FlxG.game.ticks) {
|
if (cacheEntry != null && cacheEntry.timestamp == FlxG.game.ticks)
|
||||||
|
{
|
||||||
return cacheEntry.value;
|
return cacheEntry.value;
|
||||||
}
|
}
|
||||||
// Use a for loop instead so we can remove inputs while iterating.
|
// Use a for loop instead so we can remove inputs while iterating.
|
||||||
|
@ -1280,12 +1376,14 @@ class FunkinAction extends FlxActionDigital {
|
||||||
input.update();
|
input.update();
|
||||||
|
|
||||||
// Check whether the input is the right trigger.
|
// Check whether the input is the right trigger.
|
||||||
if (filterTrigger != null && input.trigger != filterTrigger) {
|
if (filterTrigger != null && input.trigger != filterTrigger)
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check whether the input is the right device.
|
// Check whether the input is the right device.
|
||||||
if (filterDevice != null && input.device != filterDevice) {
|
if (filterDevice != null && input.device != filterDevice)
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1391,12 +1489,12 @@ class FlxActionInputDigitalMobileSwipeGameplay extends FlxActionInputDigital
|
||||||
{
|
{
|
||||||
var degAngle = FlxAngle.asDegrees(swp.touchAngle);
|
var degAngle = FlxAngle.asDegrees(swp.touchAngle);
|
||||||
|
|
||||||
switch(trigger)
|
switch (trigger)
|
||||||
{
|
{
|
||||||
case JUST_PRESSED:
|
case JUST_PRESSED:
|
||||||
if (swp.touchLength >= activateLength)
|
if (swp.touchLength >= activateLength)
|
||||||
{
|
{
|
||||||
switch(inputID)
|
switch (inputID)
|
||||||
{
|
{
|
||||||
case FlxDirectionFlags.UP:
|
case FlxDirectionFlags.UP:
|
||||||
if (degAngle >= 45 && degAngle <= 90 + 45) return properTouch(swp);
|
if (degAngle >= 45 && degAngle <= 90 + 45) return properTouch(swp);
|
||||||
|
@ -1440,7 +1538,7 @@ class FlxActionInputDigitalAndroid extends FlxActionInputDigital
|
||||||
|
|
||||||
override public function check(Action:FlxAction):Bool
|
override public function check(Action:FlxAction):Bool
|
||||||
{
|
{
|
||||||
return switch(trigger)
|
return switch (trigger)
|
||||||
{
|
{
|
||||||
#if android
|
#if android
|
||||||
case PRESSED: FlxG.android.checkStatus(inputID, PRESSED) || FlxG.android.checkStatus(inputID, PRESSED);
|
case PRESSED: FlxG.android.checkStatus(inputID, PRESSED) || FlxG.android.checkStatus(inputID, PRESSED);
|
||||||
|
@ -1482,6 +1580,7 @@ enum Control
|
||||||
FREEPLAY_FAVORITE;
|
FREEPLAY_FAVORITE;
|
||||||
FREEPLAY_LEFT;
|
FREEPLAY_LEFT;
|
||||||
FREEPLAY_RIGHT;
|
FREEPLAY_RIGHT;
|
||||||
|
FREEPLAY_CHAR_SELECT;
|
||||||
// WINDOW
|
// WINDOW
|
||||||
WINDOW_SCREENSHOT;
|
WINDOW_SCREENSHOT;
|
||||||
WINDOW_FULLSCREEN;
|
WINDOW_FULLSCREEN;
|
||||||
|
@ -1536,6 +1635,7 @@ enum abstract Action(String) to String from String
|
||||||
var FREEPLAY_FAVORITE = "freeplay_favorite";
|
var FREEPLAY_FAVORITE = "freeplay_favorite";
|
||||||
var FREEPLAY_LEFT = "freeplay_left";
|
var FREEPLAY_LEFT = "freeplay_left";
|
||||||
var FREEPLAY_RIGHT = "freeplay_right";
|
var FREEPLAY_RIGHT = "freeplay_right";
|
||||||
|
var FREEPLAY_CHAR_SELECT = "freeplay_char_select";
|
||||||
// VOLUME
|
// VOLUME
|
||||||
var VOLUME_UP = "volume_up";
|
var VOLUME_UP = "volume_up";
|
||||||
var VOLUME_DOWN = "volume_down";
|
var VOLUME_DOWN = "volume_down";
|
||||||
|
|
|
@ -73,6 +73,22 @@ interface INoteScriptedClass extends IScriptedClass
|
||||||
public function onNoteMiss(event:NoteScriptEvent):Void;
|
public function onNoteMiss(event:NoteScriptEvent):Void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a set of callbacks available to scripted classes which represent sprites synced with the BPM.
|
||||||
|
*/
|
||||||
|
interface IBPMSyncedScriptedClass extends IScriptedClass
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Called once every step of the song.
|
||||||
|
*/
|
||||||
|
public function onStepHit(event:SongTimeScriptEvent):Void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called once every beat of the song.
|
||||||
|
*/
|
||||||
|
public function onBeatHit(event:SongTimeScriptEvent):Void;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Developer note:
|
* Developer note:
|
||||||
*
|
*
|
||||||
|
@ -86,7 +102,7 @@ interface INoteScriptedClass extends IScriptedClass
|
||||||
/**
|
/**
|
||||||
* Defines a set of callbacks available to scripted classes that involve the lifecycle of the Play State.
|
* Defines a set of callbacks available to scripted classes that involve the lifecycle of the Play State.
|
||||||
*/
|
*/
|
||||||
interface IPlayStateScriptedClass extends INoteScriptedClass
|
interface IPlayStateScriptedClass extends INoteScriptedClass extends IBPMSyncedScriptedClass
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Called when the game is paused.
|
* Called when the game is paused.
|
||||||
|
@ -136,16 +152,6 @@ interface IPlayStateScriptedClass extends INoteScriptedClass
|
||||||
*/
|
*/
|
||||||
public function onSongEvent(event:SongEventScriptEvent):Void;
|
public function onSongEvent(event:SongEventScriptEvent):Void;
|
||||||
|
|
||||||
/**
|
|
||||||
* Called once every step of the song.
|
|
||||||
*/
|
|
||||||
public function onStepHit(event:SongTimeScriptEvent):Void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called once every beat of the song.
|
|
||||||
*/
|
|
||||||
public function onBeatHit(event:SongTimeScriptEvent):Void;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the countdown of the song starts.
|
* Called when the countdown of the song starts.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -7,7 +7,9 @@ import funkin.data.dialogue.speaker.SpeakerRegistry;
|
||||||
import funkin.data.event.SongEventRegistry;
|
import funkin.data.event.SongEventRegistry;
|
||||||
import funkin.data.story.level.LevelRegistry;
|
import funkin.data.story.level.LevelRegistry;
|
||||||
import funkin.data.notestyle.NoteStyleRegistry;
|
import funkin.data.notestyle.NoteStyleRegistry;
|
||||||
|
import funkin.play.notes.notekind.NoteKindManager;
|
||||||
import funkin.data.song.SongRegistry;
|
import funkin.data.song.SongRegistry;
|
||||||
|
import funkin.data.freeplay.player.PlayerRegistry;
|
||||||
import funkin.data.stage.StageRegistry;
|
import funkin.data.stage.StageRegistry;
|
||||||
import funkin.data.freeplay.album.AlbumRegistry;
|
import funkin.data.freeplay.album.AlbumRegistry;
|
||||||
import funkin.modding.module.ModuleHandler;
|
import funkin.modding.module.ModuleHandler;
|
||||||
|
@ -26,11 +28,10 @@ class PolymodHandler
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The API version that mods should comply with.
|
* The API version that mods should comply with.
|
||||||
* Format this with Semantic Versioning; <MAJOR>.<MINOR>.<PATCH>.
|
* Indicates which mods are compatible with this version of the game.
|
||||||
* Bug fixes increment the patch version, new features increment the minor version.
|
* Minor updates rarely impact mods but major versions often do.
|
||||||
* Changes that break old mods increment the major version.
|
|
||||||
*/
|
*/
|
||||||
static final API_VERSION:String = '0.1.0';
|
static final API_VERSION:String = "0.5.0"; // Constants.VERSION;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Where relative to the executable that mods are located.
|
* Where relative to the executable that mods are located.
|
||||||
|
@ -176,7 +177,7 @@ class PolymodHandler
|
||||||
loadedModIds.push(mod.id);
|
loadedModIds.push(mod.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if debug
|
#if FEATURE_DEBUG_FUNCTIONS
|
||||||
var fileList:Array<String> = Polymod.listModFiles(PolymodAssetType.IMAGE);
|
var fileList:Array<String> = Polymod.listModFiles(PolymodAssetType.IMAGE);
|
||||||
trace('Installed mods have replaced ${fileList.length} images.');
|
trace('Installed mods have replaced ${fileList.length} images.');
|
||||||
for (item in fileList)
|
for (item in fileList)
|
||||||
|
@ -232,6 +233,12 @@ class PolymodHandler
|
||||||
// NOTE: Scripted classes are automatically aliased to their parent class.
|
// NOTE: Scripted classes are automatically aliased to their parent class.
|
||||||
Polymod.addImportAlias('flixel.math.FlxPoint', flixel.math.FlxPoint.FlxBasePoint);
|
Polymod.addImportAlias('flixel.math.FlxPoint', flixel.math.FlxPoint.FlxBasePoint);
|
||||||
|
|
||||||
|
Polymod.addImportAlias('funkin.data.event.SongEventSchema', funkin.data.event.SongEventSchema.SongEventSchemaRaw);
|
||||||
|
|
||||||
|
// `lime.utils.Assets` literally just has a private `resolveClass` function for some reason? so we replace it with our own.
|
||||||
|
Polymod.addImportAlias('lime.utils.Assets', funkin.Assets);
|
||||||
|
Polymod.addImportAlias('openfl.utils.Assets', funkin.Assets);
|
||||||
|
|
||||||
// Add blacklisting for prohibited classes and packages.
|
// Add blacklisting for prohibited classes and packages.
|
||||||
|
|
||||||
// `Sys`
|
// `Sys`
|
||||||
|
@ -250,8 +257,28 @@ class PolymodHandler
|
||||||
// Lib.load() can load malicious DLLs
|
// Lib.load() can load malicious DLLs
|
||||||
Polymod.blacklistImport('cpp.Lib');
|
Polymod.blacklistImport('cpp.Lib');
|
||||||
|
|
||||||
|
// `Unserializer`
|
||||||
|
// Unserializerr.DEFAULT_RESOLVER.resolveClass() can access blacklisted packages
|
||||||
|
Polymod.blacklistImport('Unserializer');
|
||||||
|
|
||||||
|
// `lime.system.CFFI`
|
||||||
|
// Can load and execute compiled binaries.
|
||||||
|
Polymod.blacklistImport('lime.system.CFFI');
|
||||||
|
|
||||||
|
// `lime.system.JNI`
|
||||||
|
// Can load and execute compiled binaries.
|
||||||
|
Polymod.blacklistImport('lime.system.JNI');
|
||||||
|
|
||||||
|
// `lime.system.System`
|
||||||
|
// System.load() can load malicious DLLs
|
||||||
|
Polymod.blacklistImport('lime.system.System');
|
||||||
|
|
||||||
|
// `openfl.desktop.NativeProcess`
|
||||||
|
// Can load native processes on the host operating system.
|
||||||
|
Polymod.blacklistImport('openfl.desktop.NativeProcess');
|
||||||
|
|
||||||
// `polymod.*`
|
// `polymod.*`
|
||||||
// You can probably unblacklist a module
|
// Contains functions which may allow for un-blacklisting other modules.
|
||||||
for (cls in ClassMacro.listClassesInPackage('polymod'))
|
for (cls in ClassMacro.listClassesInPackage('polymod'))
|
||||||
{
|
{
|
||||||
if (cls == null) continue;
|
if (cls == null) continue;
|
||||||
|
@ -260,6 +287,7 @@ class PolymodHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
// `sys.*`
|
// `sys.*`
|
||||||
|
// Access to system utilities such as the file system.
|
||||||
for (cls in ClassMacro.listClassesInPackage('sys'))
|
for (cls in ClassMacro.listClassesInPackage('sys'))
|
||||||
{
|
{
|
||||||
if (cls == null) continue;
|
if (cls == null) continue;
|
||||||
|
@ -369,16 +397,20 @@ class PolymodHandler
|
||||||
|
|
||||||
// These MUST be imported at the top of the file and not referred to by fully qualified name,
|
// These MUST be imported at the top of the file and not referred to by fully qualified name,
|
||||||
// to ensure build macros work properly.
|
// to ensure build macros work properly.
|
||||||
|
SongEventRegistry.loadEventCache();
|
||||||
|
|
||||||
SongRegistry.instance.loadEntries();
|
SongRegistry.instance.loadEntries();
|
||||||
LevelRegistry.instance.loadEntries();
|
LevelRegistry.instance.loadEntries();
|
||||||
NoteStyleRegistry.instance.loadEntries();
|
NoteStyleRegistry.instance.loadEntries();
|
||||||
SongEventRegistry.loadEventCache();
|
PlayerRegistry.instance.loadEntries();
|
||||||
ConversationRegistry.instance.loadEntries();
|
ConversationRegistry.instance.loadEntries();
|
||||||
DialogueBoxRegistry.instance.loadEntries();
|
DialogueBoxRegistry.instance.loadEntries();
|
||||||
SpeakerRegistry.instance.loadEntries();
|
SpeakerRegistry.instance.loadEntries();
|
||||||
AlbumRegistry.instance.loadEntries();
|
AlbumRegistry.instance.loadEntries();
|
||||||
StageRegistry.instance.loadEntries();
|
StageRegistry.instance.loadEntries();
|
||||||
|
|
||||||
CharacterDataParser.loadCharacterCache(); // TODO: Migrate characters to BaseRegistry.
|
CharacterDataParser.loadCharacterCache(); // TODO: Migrate characters to BaseRegistry.
|
||||||
|
NoteKindManager.loadScripts();
|
||||||
ModuleHandler.loadModuleCache();
|
ModuleHandler.loadModuleCache();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
package funkin.modding.base;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A script that can be tied to an FlxUIState.
|
|
||||||
* Create a scripted class that extends FlxUIState to use this.
|
|
||||||
*/
|
|
||||||
@:hscriptClass
|
|
||||||
class ScriptedFlxUIState extends flixel.addons.ui.FlxUIState implements HScriptedClass {}
|
|
|
@ -151,7 +151,8 @@ class HitNoteScriptEvent extends NoteScriptEvent
|
||||||
public var hitDiff:Float = 0;
|
public var hitDiff:Float = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the hit causes a notesplash
|
* Whether this note hit causes a note splash to display.
|
||||||
|
* Defaults to true only on "sick" notes.
|
||||||
*/
|
*/
|
||||||
public var doesNotesplash:Bool = false;
|
public var doesNotesplash:Bool = false;
|
||||||
|
|
||||||
|
|
|
@ -94,6 +94,21 @@ class ScriptEventDispatcher
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Std.isOfType(target, IBPMSyncedScriptedClass))
|
||||||
|
{
|
||||||
|
var t:IBPMSyncedScriptedClass = cast(target, IBPMSyncedScriptedClass);
|
||||||
|
switch (event.type)
|
||||||
|
{
|
||||||
|
case SONG_BEAT_HIT:
|
||||||
|
t.onBeatHit(cast event);
|
||||||
|
return;
|
||||||
|
case SONG_STEP_HIT:
|
||||||
|
t.onStepHit(cast event);
|
||||||
|
return;
|
||||||
|
default: // Continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (Std.isOfType(target, IPlayStateScriptedClass))
|
if (Std.isOfType(target, IPlayStateScriptedClass))
|
||||||
{
|
{
|
||||||
var t:IPlayStateScriptedClass = cast(target, IPlayStateScriptedClass);
|
var t:IPlayStateScriptedClass = cast(target, IPlayStateScriptedClass);
|
||||||
|
@ -102,12 +117,6 @@ class ScriptEventDispatcher
|
||||||
case NOTE_GHOST_MISS:
|
case NOTE_GHOST_MISS:
|
||||||
t.onNoteGhostMiss(cast event);
|
t.onNoteGhostMiss(cast event);
|
||||||
return;
|
return;
|
||||||
case SONG_BEAT_HIT:
|
|
||||||
t.onBeatHit(cast event);
|
|
||||||
return;
|
|
||||||
case SONG_STEP_HIT:
|
|
||||||
t.onStepHit(cast event);
|
|
||||||
return;
|
|
||||||
case SONG_START:
|
case SONG_START:
|
||||||
t.onSongStart(event);
|
t.onSongStart(event);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -20,7 +20,7 @@ enum abstract ScriptEventType(String) from String to String
|
||||||
var DESTROY = 'DESTROY';
|
var DESTROY = 'DESTROY';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the relevent object is added to the game state.
|
* Called when the relevant object is added to the game state.
|
||||||
* This assumes all data is loaded and ready to go.
|
* This assumes all data is loaded and ready to go.
|
||||||
*
|
*
|
||||||
* This event is not cancelable.
|
* This event is not cancelable.
|
||||||
|
|
|
@ -9,7 +9,11 @@ import funkin.modding.module.ModuleHandler;
|
||||||
import funkin.modding.events.ScriptEvent;
|
import funkin.modding.events.ScriptEvent;
|
||||||
import funkin.modding.events.ScriptEvent.CountdownScriptEvent;
|
import funkin.modding.events.ScriptEvent.CountdownScriptEvent;
|
||||||
import flixel.util.FlxTimer;
|
import flixel.util.FlxTimer;
|
||||||
|
import funkin.util.EaseUtil;
|
||||||
import funkin.audio.FunkinSound;
|
import funkin.audio.FunkinSound;
|
||||||
|
import openfl.utils.Assets;
|
||||||
|
import funkin.data.notestyle.NoteStyleRegistry;
|
||||||
|
import funkin.play.notes.notestyle.NoteStyle;
|
||||||
|
|
||||||
class Countdown
|
class Countdown
|
||||||
{
|
{
|
||||||
|
@ -18,6 +22,24 @@ class Countdown
|
||||||
*/
|
*/
|
||||||
public static var countdownStep(default, null):CountdownStep = BEFORE;
|
public static var countdownStep(default, null):CountdownStep = BEFORE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Which alternate graphic/sound on countdown to use.
|
||||||
|
* This is set via the current notestyle.
|
||||||
|
* For example, in Week 6 it is `pixel`.
|
||||||
|
*/
|
||||||
|
public static var soundSuffix:String = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Which alternate graphic on countdown to use.
|
||||||
|
* You can set this via script.
|
||||||
|
* For example, in Week 6 it is `-pixel`.
|
||||||
|
*/
|
||||||
|
public static var graphicSuffix:String = '';
|
||||||
|
|
||||||
|
static var noteStyle:NoteStyle;
|
||||||
|
|
||||||
|
static var fallbackNoteStyle:Null<NoteStyle>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The currently running countdown. This will be null if there is no countdown running.
|
* The currently running countdown. This will be null if there is no countdown running.
|
||||||
*/
|
*/
|
||||||
|
@ -29,7 +51,7 @@ class Countdown
|
||||||
* This will automatically stop and restart the countdown if it is already running.
|
* This will automatically stop and restart the countdown if it is already running.
|
||||||
* @returns `false` if the countdown was cancelled by a script.
|
* @returns `false` if the countdown was cancelled by a script.
|
||||||
*/
|
*/
|
||||||
public static function performCountdown(isPixelStyle:Bool):Bool
|
public static function performCountdown():Bool
|
||||||
{
|
{
|
||||||
countdownStep = BEFORE;
|
countdownStep = BEFORE;
|
||||||
var cancelled:Bool = propagateCountdownEvent(countdownStep);
|
var cancelled:Bool = propagateCountdownEvent(countdownStep);
|
||||||
|
@ -64,10 +86,10 @@ class Countdown
|
||||||
// PlayState.instance.dispatchEvent(new SongTimeScriptEvent(SONG_BEAT_HIT, 0, 0));
|
// PlayState.instance.dispatchEvent(new SongTimeScriptEvent(SONG_BEAT_HIT, 0, 0));
|
||||||
|
|
||||||
// Countdown graphic.
|
// Countdown graphic.
|
||||||
showCountdownGraphic(countdownStep, isPixelStyle);
|
showCountdownGraphic(countdownStep);
|
||||||
|
|
||||||
// Countdown sound.
|
// Countdown sound.
|
||||||
playCountdownSound(countdownStep, isPixelStyle);
|
playCountdownSound(countdownStep);
|
||||||
|
|
||||||
// Event handling bullshit.
|
// Event handling bullshit.
|
||||||
var cancelled:Bool = propagateCountdownEvent(countdownStep);
|
var cancelled:Bool = propagateCountdownEvent(countdownStep);
|
||||||
|
@ -117,7 +139,7 @@ class Countdown
|
||||||
*
|
*
|
||||||
* If you want to call this from a module, it's better to use the event system and cancel the onCountdownStep event.
|
* If you want to call this from a module, it's better to use the event system and cancel the onCountdownStep event.
|
||||||
*/
|
*/
|
||||||
public static function pauseCountdown()
|
public static function pauseCountdown():Void
|
||||||
{
|
{
|
||||||
if (countdownTimer != null && !countdownTimer.finished)
|
if (countdownTimer != null && !countdownTimer.finished)
|
||||||
{
|
{
|
||||||
|
@ -130,7 +152,7 @@ class Countdown
|
||||||
*
|
*
|
||||||
* If you want to call this from a module, it's better to use the event system and cancel the onCountdownStep event.
|
* If you want to call this from a module, it's better to use the event system and cancel the onCountdownStep event.
|
||||||
*/
|
*/
|
||||||
public static function resumeCountdown()
|
public static function resumeCountdown():Void
|
||||||
{
|
{
|
||||||
if (countdownTimer != null && !countdownTimer.finished)
|
if (countdownTimer != null && !countdownTimer.finished)
|
||||||
{
|
{
|
||||||
|
@ -143,7 +165,7 @@ class Countdown
|
||||||
*
|
*
|
||||||
* If you want to call this from a module, it's better to use the event system and cancel the onCountdownStart event.
|
* If you want to call this from a module, it's better to use the event system and cancel the onCountdownStart event.
|
||||||
*/
|
*/
|
||||||
public static function stopCountdown()
|
public static function stopCountdown():Void
|
||||||
{
|
{
|
||||||
if (countdownTimer != null)
|
if (countdownTimer != null)
|
||||||
{
|
{
|
||||||
|
@ -156,7 +178,7 @@ class Countdown
|
||||||
/**
|
/**
|
||||||
* Stops the current countdown, then starts the song for you.
|
* Stops the current countdown, then starts the song for you.
|
||||||
*/
|
*/
|
||||||
public static function skipCountdown()
|
public static function skipCountdown():Void
|
||||||
{
|
{
|
||||||
stopCountdown();
|
stopCountdown();
|
||||||
// This will trigger PlayState.startSong()
|
// This will trigger PlayState.startSong()
|
||||||
|
@ -176,114 +198,69 @@ class Countdown
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the graphic to use for this step of the countdown.
|
* Reset the countdown configuration to the default.
|
||||||
* TODO: Make this less dumb. Unhardcode it? Use modules? Use notestyles?
|
|
||||||
*
|
|
||||||
* This is public so modules can do lol funny shit.
|
|
||||||
*/
|
*/
|
||||||
public static function showCountdownGraphic(index:CountdownStep, isPixelStyle:Bool):Void
|
public static function reset()
|
||||||
{
|
{
|
||||||
var spritePath:String = null;
|
noteStyle = null;
|
||||||
|
|
||||||
if (isPixelStyle)
|
|
||||||
{
|
|
||||||
switch (index)
|
|
||||||
{
|
|
||||||
case TWO:
|
|
||||||
spritePath = 'weeb/pixelUI/ready-pixel';
|
|
||||||
case ONE:
|
|
||||||
spritePath = 'weeb/pixelUI/set-pixel';
|
|
||||||
case GO:
|
|
||||||
spritePath = 'weeb/pixelUI/date-pixel';
|
|
||||||
default:
|
|
||||||
// null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
switch (index)
|
|
||||||
{
|
|
||||||
case TWO:
|
|
||||||
spritePath = 'ready';
|
|
||||||
case ONE:
|
|
||||||
spritePath = 'set';
|
|
||||||
case GO:
|
|
||||||
spritePath = 'go';
|
|
||||||
default:
|
|
||||||
// null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spritePath == null) return;
|
/**
|
||||||
|
* Retrieve the note style data (if we haven't already)
|
||||||
|
* @param noteStyleId The id of the note style to fetch. Defaults to the one used by the current PlayState.
|
||||||
|
* @param force Fetch the note style from the registry even if we've already fetched it.
|
||||||
|
*/
|
||||||
|
static function fetchNoteStyle(?noteStyleId:String, force:Bool = false):Void
|
||||||
|
{
|
||||||
|
if (noteStyle != null && !force) return;
|
||||||
|
|
||||||
var countdownSprite:FunkinSprite = FunkinSprite.create(spritePath);
|
if (noteStyleId == null) noteStyleId = PlayState.instance?.currentChart?.noteStyle;
|
||||||
countdownSprite.scrollFactor.set(0, 0);
|
|
||||||
|
|
||||||
if (isPixelStyle) countdownSprite.setGraphicSize(Std.int(countdownSprite.width * Constants.PIXEL_ART_SCALE));
|
noteStyle = NoteStyleRegistry.instance.fetchEntry(noteStyleId);
|
||||||
|
if (noteStyle == null) noteStyle = NoteStyleRegistry.instance.fetchDefault();
|
||||||
|
}
|
||||||
|
|
||||||
countdownSprite.antialiasing = !isPixelStyle;
|
/**
|
||||||
|
* Retrieves the graphic to use for this step of the countdown.
|
||||||
|
*/
|
||||||
|
public static function showCountdownGraphic(index:CountdownStep):Void
|
||||||
|
{
|
||||||
|
fetchNoteStyle();
|
||||||
|
|
||||||
countdownSprite.updateHitbox();
|
var countdownSprite = noteStyle.buildCountdownSprite(index);
|
||||||
countdownSprite.screenCenter();
|
if (countdownSprite == null) return;
|
||||||
|
|
||||||
|
var fadeEase = FlxEase.cubeInOut;
|
||||||
|
if (noteStyle.isCountdownSpritePixel(index)) fadeEase = EaseUtil.stepped(8);
|
||||||
|
|
||||||
// Fade sprite in, then out, then destroy it.
|
// Fade sprite in, then out, then destroy it.
|
||||||
FlxTween.tween(countdownSprite, {y: countdownSprite.y += 100, alpha: 0}, Conductor.instance.beatLengthMs / 1000,
|
FlxTween.tween(countdownSprite, {alpha: 0}, Conductor.instance.beatLengthMs / 1000,
|
||||||
{
|
{
|
||||||
ease: FlxEase.cubeInOut,
|
ease: fadeEase,
|
||||||
onComplete: function(twn:FlxTween) {
|
onComplete: function(twn:FlxTween) {
|
||||||
countdownSprite.destroy();
|
countdownSprite.destroy();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
countdownSprite.cameras = [PlayState.instance.camHUD];
|
||||||
PlayState.instance.add(countdownSprite);
|
PlayState.instance.add(countdownSprite);
|
||||||
|
countdownSprite.screenCenter();
|
||||||
|
|
||||||
|
var offsets = noteStyle.getCountdownSpriteOffsets(index);
|
||||||
|
countdownSprite.x += offsets[0];
|
||||||
|
countdownSprite.y += offsets[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the sound file to use for this step of the countdown.
|
* Retrieves the sound file to use for this step of the countdown.
|
||||||
* TODO: Make this less dumb. Unhardcode it? Use modules? Use notestyles?
|
|
||||||
*
|
|
||||||
* This is public so modules can do lol funny shit.
|
|
||||||
*/
|
*/
|
||||||
public static function playCountdownSound(index:CountdownStep, isPixelStyle:Bool):Void
|
public static function playCountdownSound(step:CountdownStep):FunkinSound
|
||||||
{
|
{
|
||||||
var soundPath:String = null;
|
fetchNoteStyle();
|
||||||
|
var path = noteStyle.getCountdownSoundPath(step);
|
||||||
|
if (path == null) return null;
|
||||||
|
|
||||||
if (isPixelStyle)
|
return FunkinSound.playOnce(path, Constants.COUNTDOWN_VOLUME);
|
||||||
{
|
|
||||||
switch (index)
|
|
||||||
{
|
|
||||||
case THREE:
|
|
||||||
soundPath = 'intro3-pixel';
|
|
||||||
case TWO:
|
|
||||||
soundPath = 'intro2-pixel';
|
|
||||||
case ONE:
|
|
||||||
soundPath = 'intro1-pixel';
|
|
||||||
case GO:
|
|
||||||
soundPath = 'introGo-pixel';
|
|
||||||
default:
|
|
||||||
// null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
switch (index)
|
|
||||||
{
|
|
||||||
case THREE:
|
|
||||||
soundPath = 'intro3';
|
|
||||||
case TWO:
|
|
||||||
soundPath = 'intro2';
|
|
||||||
case ONE:
|
|
||||||
soundPath = 'intro1';
|
|
||||||
case GO:
|
|
||||||
soundPath = 'introGo';
|
|
||||||
default:
|
|
||||||
// null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (soundPath == null) return;
|
|
||||||
|
|
||||||
FunkinSound.playOnce(Paths.sound(soundPath), Constants.COUNTDOWN_VOLUME);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function decrement(step:CountdownStep):CountdownStep
|
public static function decrement(step:CountdownStep):CountdownStep
|
||||||
|
|
|
@ -16,6 +16,8 @@ import funkin.ui.MusicBeatSubState;
|
||||||
import funkin.ui.story.StoryMenuState;
|
import funkin.ui.story.StoryMenuState;
|
||||||
import funkin.util.MathUtil;
|
import funkin.util.MathUtil;
|
||||||
import openfl.utils.Assets;
|
import openfl.utils.Assets;
|
||||||
|
import funkin.effects.RetroCameraFade;
|
||||||
|
import flixel.math.FlxPoint;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A substate which renders over the PlayState when the player dies.
|
* A substate which renders over the PlayState when the player dies.
|
||||||
|
@ -144,6 +146,7 @@ class GameOverSubState extends MusicBeatSubState
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
boyfriend = PlayState.instance.currentStage.getBoyfriend(true);
|
boyfriend = PlayState.instance.currentStage.getBoyfriend(true);
|
||||||
|
boyfriend.canPlayOtherAnims = true;
|
||||||
boyfriend.isDead = true;
|
boyfriend.isDead = true;
|
||||||
add(boyfriend);
|
add(boyfriend);
|
||||||
boyfriend.resetCharacter();
|
boyfriend.resetCharacter();
|
||||||
|
@ -166,8 +169,8 @@ class GameOverSubState extends MusicBeatSubState
|
||||||
|
|
||||||
// Assign a camera follow point to the boyfriend's position.
|
// Assign a camera follow point to the boyfriend's position.
|
||||||
cameraFollowPoint = new FlxObject(PlayState.instance.cameraFollowPoint.x, PlayState.instance.cameraFollowPoint.y, 1, 1);
|
cameraFollowPoint = new FlxObject(PlayState.instance.cameraFollowPoint.x, PlayState.instance.cameraFollowPoint.y, 1, 1);
|
||||||
cameraFollowPoint.x = boyfriend.getGraphicMidpoint().x;
|
cameraFollowPoint.x = getMidPointOld(boyfriend).x;
|
||||||
cameraFollowPoint.y = boyfriend.getGraphicMidpoint().y;
|
cameraFollowPoint.y = getMidPointOld(boyfriend).y;
|
||||||
var offsets:Array<Float> = boyfriend.getDeathCameraOffsets();
|
var offsets:Array<Float> = boyfriend.getDeathCameraOffsets();
|
||||||
cameraFollowPoint.x += offsets[0];
|
cameraFollowPoint.x += offsets[0];
|
||||||
cameraFollowPoint.y += offsets[1];
|
cameraFollowPoint.y += offsets[1];
|
||||||
|
@ -178,6 +181,21 @@ class GameOverSubState extends MusicBeatSubState
|
||||||
targetCameraZoom = (PlayState?.instance?.currentStage?.camZoom ?? 1.0) * boyfriend.getDeathCameraZoom();
|
targetCameraZoom = (PlayState?.instance?.currentStage?.camZoom ?? 1.0) * boyfriend.getDeathCameraZoom();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FlxSprite.getMidpoint(); calculations changed in this git commit
|
||||||
|
* https://github.com/HaxeFlixel/flixel/commit/1553b5af0871462fcefedc091b7885437d6c36d2
|
||||||
|
* https://github.com/HaxeFlixel/flixel/pull/3125
|
||||||
|
*
|
||||||
|
* So we use this to do the old math that gets the midpoint of our graphics
|
||||||
|
* Luckily, we don't use getGraphicMidpoint() much in the code, so it's fine being in GameoverSubState here.
|
||||||
|
* @return FlxPoint
|
||||||
|
*/
|
||||||
|
function getMidPointOld(spr:FlxSprite, ?point:FlxPoint):FlxPoint
|
||||||
|
{
|
||||||
|
if (point == null) point = FlxPoint.get();
|
||||||
|
return point.set(spr.x + spr.frameWidth * 0.5 * spr.scale.x, spr.y + spr.frameHeight * 0.5 * spr.scale.y);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Forcibly reset the camera zoom level to that of the current stage.
|
* Forcibly reset the camera zoom level to that of the current stage.
|
||||||
* This prevents camera zoom events from adversely affecting the game over state.
|
* This prevents camera zoom events from adversely affecting the game over state.
|
||||||
|
@ -331,8 +349,11 @@ class GameOverSubState extends MusicBeatSubState
|
||||||
// After the animation finishes...
|
// After the animation finishes...
|
||||||
new FlxTimer().start(0.7, function(tmr:FlxTimer) {
|
new FlxTimer().start(0.7, function(tmr:FlxTimer) {
|
||||||
// ...fade out the graphics. Then after that happens...
|
// ...fade out the graphics. Then after that happens...
|
||||||
FlxG.camera.fade(FlxColor.BLACK, 2, false, function() {
|
|
||||||
|
var resetPlaying = function(pixel:Bool = false) {
|
||||||
// ...close the GameOverSubState.
|
// ...close the GameOverSubState.
|
||||||
|
if (pixel) RetroCameraFade.fadeBlack(FlxG.camera, 10, 1);
|
||||||
|
else
|
||||||
FlxG.camera.fade(FlxColor.BLACK, 1, true, null, true);
|
FlxG.camera.fade(FlxColor.BLACK, 1, true, null, true);
|
||||||
PlayState.instance.needsReset = true;
|
PlayState.instance.needsReset = true;
|
||||||
|
|
||||||
|
@ -350,7 +371,22 @@ class GameOverSubState extends MusicBeatSubState
|
||||||
|
|
||||||
// Close the substate.
|
// Close the substate.
|
||||||
close();
|
close();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (musicSuffix == '-pixel')
|
||||||
|
{
|
||||||
|
RetroCameraFade.fadeToBlack(FlxG.camera, 10, 2);
|
||||||
|
new FlxTimer().start(2, _ -> {
|
||||||
|
FlxG.camera.filters = [];
|
||||||
|
resetPlaying(true);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FlxG.camera.fade(FlxColor.BLACK, 2, false, function() {
|
||||||
|
resetPlaying();
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -306,7 +306,7 @@ class PauseSubState extends MusicBeatSubState
|
||||||
metadataDifficulty.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT);
|
metadataDifficulty.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT);
|
||||||
if (PlayState.instance?.currentDifficulty != null)
|
if (PlayState.instance?.currentDifficulty != null)
|
||||||
{
|
{
|
||||||
metadataDifficulty.text += PlayState.instance.currentDifficulty.toTitleCase();
|
metadataDifficulty.text += PlayState.instance.currentDifficulty.replace('-', ' ').toTitleCase();
|
||||||
}
|
}
|
||||||
metadataDifficulty.scrollFactor.set(0, 0);
|
metadataDifficulty.scrollFactor.set(0, 0);
|
||||||
metadata.add(metadataDifficulty);
|
metadata.add(metadataDifficulty);
|
||||||
|
@ -430,7 +430,7 @@ class PauseSubState extends MusicBeatSubState
|
||||||
resume(this);
|
resume(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if (debug || FORCE_DEBUG_VERSION)
|
#if FEATURE_DEBUG_FUNCTIONS
|
||||||
// to pause the game and get screenshots easy, press H on pause menu!
|
// to pause the game and get screenshots easy, press H on pause menu!
|
||||||
if (FlxG.keys.justPressed.H)
|
if (FlxG.keys.justPressed.H)
|
||||||
{
|
{
|
||||||
|
|
|
@ -16,6 +16,7 @@ import flixel.tweens.FlxTween;
|
||||||
import flixel.ui.FlxBar;
|
import flixel.ui.FlxBar;
|
||||||
import flixel.util.FlxColor;
|
import flixel.util.FlxColor;
|
||||||
import flixel.util.FlxTimer;
|
import flixel.util.FlxTimer;
|
||||||
|
import flixel.util.FlxStringUtil;
|
||||||
import funkin.api.newgrounds.NGio;
|
import funkin.api.newgrounds.NGio;
|
||||||
import funkin.audio.FunkinSound;
|
import funkin.audio.FunkinSound;
|
||||||
import funkin.audio.VoicesGroup;
|
import funkin.audio.VoicesGroup;
|
||||||
|
@ -48,6 +49,7 @@ import funkin.play.notes.NoteSprite;
|
||||||
import funkin.play.notes.notestyle.NoteStyle;
|
import funkin.play.notes.notestyle.NoteStyle;
|
||||||
import funkin.play.notes.Strumline;
|
import funkin.play.notes.Strumline;
|
||||||
import funkin.play.notes.SustainTrail;
|
import funkin.play.notes.SustainTrail;
|
||||||
|
import funkin.play.notes.notekind.NoteKindManager;
|
||||||
import funkin.play.scoring.Scoring;
|
import funkin.play.scoring.Scoring;
|
||||||
import funkin.play.song.Song;
|
import funkin.play.song.Song;
|
||||||
import funkin.play.stage.Stage;
|
import funkin.play.stage.Stage;
|
||||||
|
@ -65,7 +67,7 @@ import lime.ui.Haptic;
|
||||||
import openfl.display.BitmapData;
|
import openfl.display.BitmapData;
|
||||||
import openfl.geom.Rectangle;
|
import openfl.geom.Rectangle;
|
||||||
import openfl.Lib;
|
import openfl.Lib;
|
||||||
#if discord_rpc
|
#if FEATURE_DISCORD_RPC
|
||||||
import Discord.DiscordClient;
|
import Discord.DiscordClient;
|
||||||
#end
|
#end
|
||||||
|
|
||||||
|
@ -443,7 +445,7 @@ class PlayState extends MusicBeatSubState
|
||||||
*/
|
*/
|
||||||
public var vocals:VoicesGroup;
|
public var vocals:VoicesGroup;
|
||||||
|
|
||||||
#if discord_rpc
|
#if FEATURE_DISCORD_RPC
|
||||||
// Discord RPC variables
|
// Discord RPC variables
|
||||||
var storyDifficultyText:String = '';
|
var storyDifficultyText:String = '';
|
||||||
var iconRPC:String = '';
|
var iconRPC:String = '';
|
||||||
|
@ -502,7 +504,13 @@ class PlayState extends MusicBeatSubState
|
||||||
public var camGame:FlxCamera;
|
public var camGame:FlxCamera;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The camera which contains, and controls visibility of, a video cutscene.
|
* Simple helper debug variable, to be able to move the camera around for debug purposes
|
||||||
|
* without worrying about the camera tweening back to the follow point.
|
||||||
|
*/
|
||||||
|
public var debugUnbindCameraZoom:Bool = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The camera which contains, and controls visibility of, a video cutscene, dialogue, pause menu and sticker transition.
|
||||||
*/
|
*/
|
||||||
public var camCutscene:FlxCamera;
|
public var camCutscene:FlxCamera;
|
||||||
|
|
||||||
|
@ -577,7 +585,8 @@ class PlayState extends MusicBeatSubState
|
||||||
|
|
||||||
// TODO: Refactor or document
|
// TODO: Refactor or document
|
||||||
var generatedMusic:Bool = false;
|
var generatedMusic:Bool = false;
|
||||||
var perfectMode:Bool = false;
|
|
||||||
|
var skipEndingTransition:Bool = false;
|
||||||
|
|
||||||
static final BACKGROUND_COLOR:FlxColor = FlxColor.BLACK;
|
static final BACKGROUND_COLOR:FlxColor = FlxColor.BLACK;
|
||||||
|
|
||||||
|
@ -665,7 +674,7 @@ class PlayState extends MusicBeatSubState
|
||||||
// Prepare the current song's instrumental and vocals to be played.
|
// Prepare the current song's instrumental and vocals to be played.
|
||||||
if (!overrideMusic && currentChart != null)
|
if (!overrideMusic && currentChart != null)
|
||||||
{
|
{
|
||||||
currentChart.cacheInst();
|
currentChart.cacheInst(currentInstrumental);
|
||||||
currentChart.cacheVocals();
|
currentChart.cacheVocals();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -674,7 +683,7 @@ class PlayState extends MusicBeatSubState
|
||||||
|
|
||||||
if (currentChart.offsets != null)
|
if (currentChart.offsets != null)
|
||||||
{
|
{
|
||||||
Conductor.instance.instrumentalOffset = currentChart.offsets.getInstrumentalOffset();
|
Conductor.instance.instrumentalOffset = currentChart.offsets.getInstrumentalOffset(currentInstrumental);
|
||||||
}
|
}
|
||||||
|
|
||||||
Conductor.instance.mapTimeChanges(currentChart.timeChanges);
|
Conductor.instance.mapTimeChanges(currentChart.timeChanges);
|
||||||
|
@ -693,14 +702,9 @@ class PlayState extends MusicBeatSubState
|
||||||
initMinimalMode();
|
initMinimalMode();
|
||||||
}
|
}
|
||||||
initStrumlines();
|
initStrumlines();
|
||||||
|
initPopups();
|
||||||
|
|
||||||
// Initialize the judgements and combo meter.
|
#if FEATURE_DISCORD_RPC
|
||||||
comboPopUps = new PopUpStuff();
|
|
||||||
comboPopUps.zIndex = 900;
|
|
||||||
add(comboPopUps);
|
|
||||||
comboPopUps.cameras = [camHUD];
|
|
||||||
|
|
||||||
#if discord_rpc
|
|
||||||
// Initialize Discord Rich Presence.
|
// Initialize Discord Rich Presence.
|
||||||
initDiscord();
|
initDiscord();
|
||||||
#end
|
#end
|
||||||
|
@ -740,7 +744,7 @@ class PlayState extends MusicBeatSubState
|
||||||
rightWatermarkText.cameras = [camHUD];
|
rightWatermarkText.cameras = [camHUD];
|
||||||
|
|
||||||
// Initialize some debug stuff.
|
// Initialize some debug stuff.
|
||||||
#if (debug || FORCE_DEBUG_VERSION)
|
#if FEATURE_DEBUG_FUNCTIONS
|
||||||
// Display the version number (and git commit hash) in the bottom right corner.
|
// Display the version number (and git commit hash) in the bottom right corner.
|
||||||
this.rightWatermarkText.text = Constants.VERSION;
|
this.rightWatermarkText.text = Constants.VERSION;
|
||||||
|
|
||||||
|
@ -862,7 +866,7 @@ class PlayState extends MusicBeatSubState
|
||||||
{
|
{
|
||||||
// Stop the vocals if they already exist.
|
// Stop the vocals if they already exist.
|
||||||
if (vocals != null) vocals.stop();
|
if (vocals != null) vocals.stop();
|
||||||
vocals = currentChart.buildVocals();
|
vocals = currentChart.buildVocals(currentInstrumental);
|
||||||
|
|
||||||
if (vocals.members.length == 0)
|
if (vocals.members.length == 0)
|
||||||
{
|
{
|
||||||
|
@ -899,7 +903,7 @@ class PlayState extends MusicBeatSubState
|
||||||
health = Constants.HEALTH_STARTING;
|
health = Constants.HEALTH_STARTING;
|
||||||
songScore = 0;
|
songScore = 0;
|
||||||
Highscore.tallies.combo = 0;
|
Highscore.tallies.combo = 0;
|
||||||
Countdown.performCountdown(currentStageId.startsWith('school'));
|
Countdown.performCountdown();
|
||||||
|
|
||||||
needsReset = false;
|
needsReset = false;
|
||||||
}
|
}
|
||||||
|
@ -974,12 +978,12 @@ class PlayState extends MusicBeatSubState
|
||||||
|
|
||||||
FlxTransitionableState.skipNextTransIn = true;
|
FlxTransitionableState.skipNextTransIn = true;
|
||||||
FlxTransitionableState.skipNextTransOut = true;
|
FlxTransitionableState.skipNextTransOut = true;
|
||||||
pauseSubState.camera = camHUD;
|
pauseSubState.camera = camCutscene;
|
||||||
openSubState(pauseSubState);
|
openSubState(pauseSubState);
|
||||||
// boyfriendPos.put(); // TODO: Why is this here?
|
// boyfriendPos.put(); // TODO: Why is this here?
|
||||||
}
|
}
|
||||||
|
|
||||||
#if discord_rpc
|
#if FEATURE_DISCORD_RPC
|
||||||
DiscordClient.changePresence(detailsPausedText, currentSong.song + ' (' + storyDifficultyText + ')', iconRPC);
|
DiscordClient.changePresence(detailsPausedText, currentSong.song + ' (' + storyDifficultyText + ')', iconRPC);
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
|
@ -994,7 +998,7 @@ class PlayState extends MusicBeatSubState
|
||||||
{
|
{
|
||||||
cameraBopMultiplier = FlxMath.lerp(1.0, cameraBopMultiplier, 0.95); // Lerp bop multiplier back to 1.0x
|
cameraBopMultiplier = FlxMath.lerp(1.0, cameraBopMultiplier, 0.95); // Lerp bop multiplier back to 1.0x
|
||||||
var zoomPlusBop = currentCameraZoom * cameraBopMultiplier; // Apply camera bop multiplier.
|
var zoomPlusBop = currentCameraZoom * cameraBopMultiplier; // Apply camera bop multiplier.
|
||||||
FlxG.camera.zoom = zoomPlusBop; // Actually apply the zoom to the camera.
|
if (!debugUnbindCameraZoom) FlxG.camera.zoom = zoomPlusBop; // Actually apply the zoom to the camera.
|
||||||
|
|
||||||
camHUD.zoom = FlxMath.lerp(defaultHUDCameraZoom, camHUD.zoom, 0.95);
|
camHUD.zoom = FlxMath.lerp(defaultHUDCameraZoom, camHUD.zoom, 0.95);
|
||||||
}
|
}
|
||||||
|
@ -1038,7 +1042,7 @@ class PlayState extends MusicBeatSubState
|
||||||
|
|
||||||
// Disable updates, preventing animations in the background from playing.
|
// Disable updates, preventing animations in the background from playing.
|
||||||
persistentUpdate = false;
|
persistentUpdate = false;
|
||||||
#if (debug || FORCE_DEBUG_VERSION)
|
#if FEATURE_DEBUG_FUNCTIONS
|
||||||
if (FlxG.keys.pressed.THREE)
|
if (FlxG.keys.pressed.THREE)
|
||||||
{
|
{
|
||||||
// TODO: Change the key or delete this?
|
// TODO: Change the key or delete this?
|
||||||
|
@ -1049,7 +1053,7 @@ class PlayState extends MusicBeatSubState
|
||||||
{
|
{
|
||||||
#end
|
#end
|
||||||
persistentDraw = false;
|
persistentDraw = false;
|
||||||
#if (debug || FORCE_DEBUG_VERSION)
|
#if FEATURE_DEBUG_FUNCTIONS
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
|
||||||
|
@ -1068,7 +1072,7 @@ class PlayState extends MusicBeatSubState
|
||||||
moveToGameOver();
|
moveToGameOver();
|
||||||
}
|
}
|
||||||
|
|
||||||
#if discord_rpc
|
#if FEATURE_DISCORD_RPC
|
||||||
// Game Over doesn't get his own variable because it's only used here
|
// Game Over doesn't get his own variable because it's only used here
|
||||||
DiscordClient.changePresence('Game Over - ' + detailsText, currentSong.song + ' (' + storyDifficultyText + ')', iconRPC);
|
DiscordClient.changePresence('Game Over - ' + detailsText, currentSong.song + ' (' + storyDifficultyText + ')', iconRPC);
|
||||||
#end
|
#end
|
||||||
|
@ -1164,6 +1168,9 @@ class PlayState extends MusicBeatSubState
|
||||||
// super.dispatchEvent(event) dispatches event to module scripts.
|
// super.dispatchEvent(event) dispatches event to module scripts.
|
||||||
super.dispatchEvent(event);
|
super.dispatchEvent(event);
|
||||||
|
|
||||||
|
// Dispatch event to note kind scripts
|
||||||
|
NoteKindManager.callEvent(event);
|
||||||
|
|
||||||
// Dispatch event to stage script.
|
// Dispatch event to stage script.
|
||||||
ScriptEventDispatcher.callEvent(currentStage, event);
|
ScriptEventDispatcher.callEvent(currentStage, event);
|
||||||
|
|
||||||
|
@ -1175,8 +1182,6 @@ class PlayState extends MusicBeatSubState
|
||||||
|
|
||||||
// Dispatch event to conversation script.
|
// Dispatch event to conversation script.
|
||||||
ScriptEventDispatcher.callEvent(currentConversation, event);
|
ScriptEventDispatcher.callEvent(currentConversation, event);
|
||||||
|
|
||||||
// TODO: Dispatch event to note scripts
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1279,7 +1284,7 @@ class PlayState extends MusicBeatSubState
|
||||||
// Resume the countdown.
|
// Resume the countdown.
|
||||||
Countdown.resumeCountdown();
|
Countdown.resumeCountdown();
|
||||||
|
|
||||||
#if discord_rpc
|
#if FEATURE_DISCORD_RPC
|
||||||
if (startTimer.finished)
|
if (startTimer.finished)
|
||||||
{
|
{
|
||||||
DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC, true,
|
DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC, true,
|
||||||
|
@ -1301,12 +1306,18 @@ class PlayState extends MusicBeatSubState
|
||||||
super.closeSubState();
|
super.closeSubState();
|
||||||
}
|
}
|
||||||
|
|
||||||
#if discord_rpc
|
|
||||||
/**
|
/**
|
||||||
* Function called when the game window gains focus.
|
* Function called when the game window gains focus.
|
||||||
*/
|
*/
|
||||||
public override function onFocus():Void
|
public override function onFocus():Void
|
||||||
{
|
{
|
||||||
|
if (VideoCutscene.isPlaying() && FlxG.autoPause && isGamePaused) VideoCutscene.pauseVideo();
|
||||||
|
#if html5
|
||||||
|
else
|
||||||
|
VideoCutscene.resumeVideo();
|
||||||
|
#end
|
||||||
|
|
||||||
|
#if FEATURE_DISCORD_RPC
|
||||||
if (health > Constants.HEALTH_MIN && !paused && FlxG.autoPause)
|
if (health > Constants.HEALTH_MIN && !paused && FlxG.autoPause)
|
||||||
{
|
{
|
||||||
if (Conductor.instance.songPosition > 0.0) DiscordClient.changePresence(detailsText, currentSong.song
|
if (Conductor.instance.songPosition > 0.0) DiscordClient.changePresence(detailsText, currentSong.song
|
||||||
|
@ -1318,6 +1329,7 @@ class PlayState extends MusicBeatSubState
|
||||||
else
|
else
|
||||||
DiscordClient.changePresence(detailsText, currentSong.song + ' (' + storyDifficultyText + ')', iconRPC);
|
DiscordClient.changePresence(detailsText, currentSong.song + ' (' + storyDifficultyText + ')', iconRPC);
|
||||||
}
|
}
|
||||||
|
#end
|
||||||
|
|
||||||
super.onFocus();
|
super.onFocus();
|
||||||
}
|
}
|
||||||
|
@ -1327,72 +1339,26 @@ class PlayState extends MusicBeatSubState
|
||||||
*/
|
*/
|
||||||
public override function onFocusLost():Void
|
public override function onFocusLost():Void
|
||||||
{
|
{
|
||||||
|
#if html5
|
||||||
|
if (FlxG.autoPause) VideoCutscene.pauseVideo();
|
||||||
|
#end
|
||||||
|
|
||||||
|
#if FEATURE_DISCORD_RPC
|
||||||
if (health > Constants.HEALTH_MIN && !paused && FlxG.autoPause) DiscordClient.changePresence(detailsPausedText,
|
if (health > Constants.HEALTH_MIN && !paused && FlxG.autoPause) DiscordClient.changePresence(detailsPausedText,
|
||||||
currentSong.song + ' (' + storyDifficultyText + ')', iconRPC);
|
currentSong.song + ' (' + storyDifficultyText + ')', iconRPC);
|
||||||
|
#end
|
||||||
|
|
||||||
super.onFocusLost();
|
super.onFocusLost();
|
||||||
}
|
}
|
||||||
#end
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes any references to the current stage, then clears the stage cache,
|
|
||||||
* then reloads all the stages.
|
|
||||||
*
|
|
||||||
* This is useful for when you want to edit a stage without reloading the whole game.
|
|
||||||
* Reloading works on both the JSON and the HXC, if applicable.
|
|
||||||
*
|
|
||||||
* Call this by pressing F5 on a debug build.
|
* Call this by pressing F5 on a debug build.
|
||||||
*/
|
*/
|
||||||
override function debug_refreshModules():Void
|
override function reloadAssets():Void
|
||||||
{
|
{
|
||||||
// Prevent further gameplay updates, which will try to reference dead objects.
|
funkin.modding.PolymodHandler.forceReloadAssets();
|
||||||
criticalFailure = true;
|
lastParams.targetSong = SongRegistry.instance.fetchEntry(currentSong.id);
|
||||||
|
LoadingState.loadPlayState(lastParams);
|
||||||
// Remove the current stage. If the stage gets deleted while it's still in use,
|
|
||||||
// it'll probably crash the game or something.
|
|
||||||
if (this.currentStage != null)
|
|
||||||
{
|
|
||||||
remove(currentStage);
|
|
||||||
var event:ScriptEvent = new ScriptEvent(DESTROY, false);
|
|
||||||
ScriptEventDispatcher.callEvent(currentStage, event);
|
|
||||||
currentStage = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!overrideMusic)
|
|
||||||
{
|
|
||||||
// Stop the instrumental.
|
|
||||||
if (FlxG.sound.music != null)
|
|
||||||
{
|
|
||||||
FlxG.sound.music.destroy();
|
|
||||||
FlxG.sound.music = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop the vocals.
|
|
||||||
if (vocals != null && vocals.exists)
|
|
||||||
{
|
|
||||||
vocals.destroy();
|
|
||||||
vocals = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Stop the instrumental.
|
|
||||||
if (FlxG.sound.music != null)
|
|
||||||
{
|
|
||||||
FlxG.sound.music.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop the vocals.
|
|
||||||
if (vocals != null && vocals.exists)
|
|
||||||
{
|
|
||||||
vocals.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
super.debug_refreshModules();
|
|
||||||
|
|
||||||
var event:ScriptEvent = new ScriptEvent(CREATE, false);
|
|
||||||
ScriptEventDispatcher.callEvent(currentSong, event);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override function stepHit():Bool
|
override function stepHit():Bool
|
||||||
|
@ -1404,17 +1370,6 @@ class PlayState extends MusicBeatSubState
|
||||||
|
|
||||||
if (isGamePaused) return false;
|
if (isGamePaused) return false;
|
||||||
|
|
||||||
if (!startingSong
|
|
||||||
&& FlxG.sound.music != null
|
|
||||||
&& (Math.abs(FlxG.sound.music.time - (Conductor.instance.songPosition + Conductor.instance.instrumentalOffset)) > 200
|
|
||||||
|| Math.abs(vocals.checkSyncError(Conductor.instance.songPosition + Conductor.instance.instrumentalOffset)) > 200))
|
|
||||||
{
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (iconP1 != null) iconP1.onStepHit(Std.int(Conductor.instance.currentStep));
|
if (iconP1 != null) iconP1.onStepHit(Std.int(Conductor.instance.currentStep));
|
||||||
if (iconP2 != null) iconP2.onStepHit(Std.int(Conductor.instance.currentStep));
|
if (iconP2 != null) iconP2.onStepHit(Std.int(Conductor.instance.currentStep));
|
||||||
|
|
||||||
|
@ -1436,6 +1391,17 @@ class PlayState extends MusicBeatSubState
|
||||||
// activeNotes.sort(SortUtil.byStrumtime, FlxSort.DESCENDING);
|
// 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))
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
// Only bop camera if zoom level is below 135%
|
// Only bop camera if zoom level is below 135%
|
||||||
if (Preferences.zoomCamera
|
if (Preferences.zoomCamera
|
||||||
&& FlxG.camera.zoom < (1.35 * FlxCamera.defaultZoom)
|
&& FlxG.camera.zoom < (1.35 * FlxCamera.defaultZoom)
|
||||||
|
@ -1488,9 +1454,6 @@ class PlayState extends MusicBeatSubState
|
||||||
if (playerStrumline != null) playerStrumline.onBeatHit();
|
if (playerStrumline != null) playerStrumline.onBeatHit();
|
||||||
if (opponentStrumline != null) opponentStrumline.onBeatHit();
|
if (opponentStrumline != null) opponentStrumline.onBeatHit();
|
||||||
|
|
||||||
// Make the characters dance on the beat
|
|
||||||
danceOnBeat();
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1501,25 +1464,12 @@ class PlayState extends MusicBeatSubState
|
||||||
super.destroy();
|
super.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public override function initConsoleHelpers():Void
|
||||||
* Handles characters dancing to the beat of the current song.
|
|
||||||
*
|
|
||||||
* TODO: Move some of this logic into `Bopper.hx`, or individual character scripts.
|
|
||||||
*/
|
|
||||||
function danceOnBeat():Void
|
|
||||||
{
|
{
|
||||||
if (currentStage == null) return;
|
FlxG.console.registerFunction("debugUnbindCameraZoom", () -> {
|
||||||
|
debugUnbindCameraZoom = !debugUnbindCameraZoom;
|
||||||
// TODO: Add HEY! song events to Tutorial.
|
});
|
||||||
if (Conductor.instance.currentBeat % 16 == 15
|
};
|
||||||
&& currentStage.getDad().characterId == 'gf'
|
|
||||||
&& Conductor.instance.currentBeat > 16
|
|
||||||
&& Conductor.instance.currentBeat < 48)
|
|
||||||
{
|
|
||||||
currentStage.getBoyfriend().playAnimation('hey', true);
|
|
||||||
currentStage.getDad().playAnimation('cheer', true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the game and HUD cameras.
|
* Initializes the game and HUD cameras.
|
||||||
|
@ -1621,7 +1571,7 @@ class PlayState extends MusicBeatSubState
|
||||||
// Add the stage to the scene.
|
// Add the stage to the scene.
|
||||||
this.add(currentStage);
|
this.add(currentStage);
|
||||||
|
|
||||||
#if (debug || FORCE_DEBUG_VERSION)
|
#if FEATURE_DEBUG_FUNCTIONS
|
||||||
FlxG.console.registerObject('stage', currentStage);
|
FlxG.console.registerObject('stage', currentStage);
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
|
@ -1724,7 +1674,7 @@ class PlayState extends MusicBeatSubState
|
||||||
{
|
{
|
||||||
currentStage.addCharacter(girlfriend, GF);
|
currentStage.addCharacter(girlfriend, GF);
|
||||||
|
|
||||||
#if (debug || FORCE_DEBUG_VERSION)
|
#if FEATURE_DEBUG_FUNCTIONS
|
||||||
FlxG.console.registerObject('gf', girlfriend);
|
FlxG.console.registerObject('gf', girlfriend);
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
|
@ -1733,7 +1683,7 @@ class PlayState extends MusicBeatSubState
|
||||||
{
|
{
|
||||||
currentStage.addCharacter(boyfriend, BF);
|
currentStage.addCharacter(boyfriend, BF);
|
||||||
|
|
||||||
#if (debug || FORCE_DEBUG_VERSION)
|
#if FEATURE_DEBUG_FUNCTIONS
|
||||||
FlxG.console.registerObject('bf', boyfriend);
|
FlxG.console.registerObject('bf', boyfriend);
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
|
@ -1744,7 +1694,7 @@ class PlayState extends MusicBeatSubState
|
||||||
// Camera starts at dad.
|
// Camera starts at dad.
|
||||||
cameraFollowPoint.setPosition(dad.cameraFocusPoint.x, dad.cameraFocusPoint.y);
|
cameraFollowPoint.setPosition(dad.cameraFocusPoint.x, dad.cameraFocusPoint.y);
|
||||||
|
|
||||||
#if (debug || FORCE_DEBUG_VERSION)
|
#if FEATURE_DEBUG_FUNCTIONS
|
||||||
FlxG.console.registerObject('dad', dad);
|
FlxG.console.registerObject('dad', dad);
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
|
@ -1783,11 +1733,23 @@ class PlayState extends MusicBeatSubState
|
||||||
opponentStrumline.zIndex = 1000;
|
opponentStrumline.zIndex = 1000;
|
||||||
opponentStrumline.cameras = [camHUD];
|
opponentStrumline.cameras = [camHUD];
|
||||||
|
|
||||||
if (!PlayStatePlaylist.isStoryMode)
|
|
||||||
{
|
|
||||||
playerStrumline.fadeInArrows();
|
playerStrumline.fadeInArrows();
|
||||||
opponentStrumline.fadeInArrows();
|
opponentStrumline.fadeInArrows();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the judgement and combo popups.
|
||||||
|
*/
|
||||||
|
function initPopups():Void
|
||||||
|
{
|
||||||
|
var noteStyleId:String = currentChart.noteStyle;
|
||||||
|
var noteStyle:NoteStyle = NoteStyleRegistry.instance.fetchEntry(noteStyleId);
|
||||||
|
if (noteStyle == null) noteStyle = NoteStyleRegistry.instance.fetchDefault();
|
||||||
|
// Initialize the judgements and combo meter.
|
||||||
|
comboPopUps = new PopUpStuff(noteStyle);
|
||||||
|
comboPopUps.zIndex = 900;
|
||||||
|
add(comboPopUps);
|
||||||
|
comboPopUps.cameras = [camHUD];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1795,7 +1757,7 @@ class PlayState extends MusicBeatSubState
|
||||||
*/
|
*/
|
||||||
function initDiscord():Void
|
function initDiscord():Void
|
||||||
{
|
{
|
||||||
#if discord_rpc
|
#if FEATURE_DISCORD_RPC
|
||||||
storyDifficultyText = difficultyString();
|
storyDifficultyText = difficultyString();
|
||||||
iconRPC = currentSong.player2;
|
iconRPC = currentSong.player2;
|
||||||
|
|
||||||
|
@ -1842,7 +1804,7 @@ class PlayState extends MusicBeatSubState
|
||||||
{
|
{
|
||||||
// Stop the vocals if they already exist.
|
// Stop the vocals if they already exist.
|
||||||
if (vocals != null) vocals.stop();
|
if (vocals != null) vocals.stop();
|
||||||
vocals = currentChart.buildVocals();
|
vocals = currentChart.buildVocals(currentInstrumental);
|
||||||
|
|
||||||
if (vocals.members.length == 0)
|
if (vocals.members.length == 0)
|
||||||
{
|
{
|
||||||
|
@ -1920,11 +1882,10 @@ class PlayState extends MusicBeatSubState
|
||||||
public function startCountdown():Void
|
public function startCountdown():Void
|
||||||
{
|
{
|
||||||
// If Countdown.performCountdown returns false, then the countdown was canceled by a script.
|
// If Countdown.performCountdown returns false, then the countdown was canceled by a script.
|
||||||
var result:Bool = Countdown.performCountdown(currentStageId.startsWith('school'));
|
var result:Bool = Countdown.performCountdown();
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
|
|
||||||
isInCutscene = false;
|
isInCutscene = false;
|
||||||
camCutscene.visible = false;
|
|
||||||
|
|
||||||
// TODO: Maybe tween in the camera after any cutscenes.
|
// TODO: Maybe tween in the camera after any cutscenes.
|
||||||
camHUD.visible = true;
|
camHUD.visible = true;
|
||||||
|
@ -1990,7 +1951,9 @@ class PlayState extends MusicBeatSubState
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FlxG.sound.music.onComplete = endSong.bind(false);
|
FlxG.sound.music.onComplete = function() {
|
||||||
|
endSong(skipEndingTransition);
|
||||||
|
};
|
||||||
// A negative instrumental offset means the song skips the first few milliseconds of the track.
|
// 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.
|
// 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, startTimestamp - Conductor.instance.instrumentalOffset);
|
||||||
|
@ -2007,7 +1970,7 @@ class PlayState extends MusicBeatSubState
|
||||||
vocals.pitch = playbackRate;
|
vocals.pitch = playbackRate;
|
||||||
resyncVocals();
|
resyncVocals();
|
||||||
|
|
||||||
#if discord_rpc
|
#if FEATURE_DISCORD_RPC
|
||||||
// Updating Discord Rich Presence (with Time Left)
|
// Updating Discord Rich Presence (with Time Left)
|
||||||
DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC, true, currentSongLengthMs);
|
DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC, true, currentSongLengthMs);
|
||||||
#end
|
#end
|
||||||
|
@ -2029,14 +1992,17 @@ class PlayState extends MusicBeatSubState
|
||||||
if (vocals == null) return;
|
if (vocals == null) return;
|
||||||
|
|
||||||
// Skip this if the music is paused (GameOver, Pause menu, start-of-song offset, etc.)
|
// Skip this if the music is paused (GameOver, Pause menu, start-of-song offset, etc.)
|
||||||
if (!FlxG.sound.music.playing) return;
|
if (!(FlxG.sound.music?.playing ?? false)) return;
|
||||||
|
|
||||||
|
var timeToPlayAt:Float = Conductor.instance.songPosition - Conductor.instance.instrumentalOffset;
|
||||||
|
FlxG.sound.music.pause();
|
||||||
vocals.pause();
|
vocals.pause();
|
||||||
|
|
||||||
FlxG.sound.music.play(FlxG.sound.music.time);
|
FlxG.sound.music.time = timeToPlayAt;
|
||||||
|
FlxG.sound.music.play(false, timeToPlayAt);
|
||||||
|
|
||||||
vocals.time = FlxG.sound.music.time;
|
vocals.time = timeToPlayAt;
|
||||||
vocals.play(false, FlxG.sound.music.time);
|
vocals.play(false, timeToPlayAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2051,7 +2017,9 @@ class PlayState extends MusicBeatSubState
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
scoreText.text = 'Score:' + songScore;
|
// TODO: Add an option for this maybe?
|
||||||
|
var commaSeparated:Bool = true;
|
||||||
|
scoreText.text = 'Score: ${FlxStringUtil.formatMoney(songScore, false, commaSeparated)}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2267,10 +2235,14 @@ class PlayState extends MusicBeatSubState
|
||||||
// Calling event.cancelEvent() skips all the other logic! Neat!
|
// Calling event.cancelEvent() skips all the other logic! Neat!
|
||||||
if (event.eventCanceled) continue;
|
if (event.eventCanceled) continue;
|
||||||
|
|
||||||
|
// Skip handling the miss in botplay!
|
||||||
|
if (!isBotPlayMode)
|
||||||
|
{
|
||||||
// Judge the miss.
|
// Judge the miss.
|
||||||
// NOTE: This is what handles the scoring.
|
// NOTE: This is what handles the scoring.
|
||||||
trace('Missed note! ${note.noteData}');
|
trace('Missed note! ${note.noteData}');
|
||||||
onNoteMiss(note, event.playSound, event.healthChange);
|
onNoteMiss(note, event.playSound, event.healthChange);
|
||||||
|
}
|
||||||
|
|
||||||
note.handledMiss = true;
|
note.handledMiss = true;
|
||||||
}
|
}
|
||||||
|
@ -2367,9 +2339,16 @@ class PlayState extends MusicBeatSubState
|
||||||
|
|
||||||
playerStrumline.pressKey(input.noteDirection);
|
playerStrumline.pressKey(input.noteDirection);
|
||||||
|
|
||||||
|
// Don't credit or penalize inputs in Bot Play.
|
||||||
|
if (isBotPlayMode) continue;
|
||||||
|
|
||||||
var notesInDirection:Array<NoteSprite> = notesByDirection[input.noteDirection];
|
var notesInDirection:Array<NoteSprite> = notesByDirection[input.noteDirection];
|
||||||
|
|
||||||
if (!Constants.GHOST_TAPPING && notesInDirection.length == 0)
|
#if FEATURE_GHOST_TAPPING
|
||||||
|
if ((!playerStrumline.mayGhostTap()) && notesInDirection.length == 0)
|
||||||
|
#else
|
||||||
|
if (notesInDirection.length == 0)
|
||||||
|
#end
|
||||||
{
|
{
|
||||||
// Pressed a wrong key with no notes nearby.
|
// Pressed a wrong key with no notes nearby.
|
||||||
// Perform a ghost miss (anti-spam).
|
// Perform a ghost miss (anti-spam).
|
||||||
|
@ -2379,16 +2358,6 @@ class PlayState extends MusicBeatSubState
|
||||||
playerStrumline.playPress(input.noteDirection);
|
playerStrumline.playPress(input.noteDirection);
|
||||||
trace('PENALTY Score: ${songScore}');
|
trace('PENALTY Score: ${songScore}');
|
||||||
}
|
}
|
||||||
else if (Constants.GHOST_TAPPING && (!playerStrumline.mayGhostTap()) && notesInDirection.length == 0)
|
|
||||||
{
|
|
||||||
// Pressed a wrong key with notes visible on-screen.
|
|
||||||
// Perform a ghost miss (anti-spam).
|
|
||||||
ghostNoteMiss(input.noteDirection, notesInRange.length > 0);
|
|
||||||
|
|
||||||
// Play the strumline animation.
|
|
||||||
playerStrumline.playPress(input.noteDirection);
|
|
||||||
trace('PENALTY Score: ${songScore}');
|
|
||||||
}
|
|
||||||
else if (notesInDirection.length == 0)
|
else if (notesInDirection.length == 0)
|
||||||
{
|
{
|
||||||
// Press a key with no penalty.
|
// Press a key with no penalty.
|
||||||
|
@ -2598,13 +2567,7 @@ class PlayState extends MusicBeatSubState
|
||||||
*/
|
*/
|
||||||
function debugKeyShit():Void
|
function debugKeyShit():Void
|
||||||
{
|
{
|
||||||
#if !debug
|
#if FEATURE_CHART_EDITOR
|
||||||
perfectMode = false;
|
|
||||||
#else
|
|
||||||
if (FlxG.keys.justPressed.H) camHUD.visible = !camHUD.visible;
|
|
||||||
#end
|
|
||||||
|
|
||||||
#if CHART_EDITOR_SUPPORTED
|
|
||||||
// Open the stage editor overlaying the current state.
|
// Open the stage editor overlaying the current state.
|
||||||
if (controls.DEBUG_STAGE)
|
if (controls.DEBUG_STAGE)
|
||||||
{
|
{
|
||||||
|
@ -2619,14 +2582,25 @@ class PlayState extends MusicBeatSubState
|
||||||
{
|
{
|
||||||
disableKeys = true;
|
disableKeys = true;
|
||||||
persistentUpdate = false;
|
persistentUpdate = false;
|
||||||
|
if (isChartingMode)
|
||||||
|
{
|
||||||
|
FlxG.sound.music?.pause();
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
FlxG.switchState(() -> new ChartEditorState(
|
FlxG.switchState(() -> new ChartEditorState(
|
||||||
{
|
{
|
||||||
targetSongId: currentSong.id,
|
targetSongId: currentSong.id,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#end
|
#end
|
||||||
|
|
||||||
#if (debug || FORCE_DEBUG_VERSION)
|
#if FEATURE_DEBUG_FUNCTIONS
|
||||||
|
// H: Hide the HUD.
|
||||||
|
if (FlxG.keys.justPressed.H) camHUD.visible = !camHUD.visible;
|
||||||
|
|
||||||
// 1: End the song immediately.
|
// 1: End the song immediately.
|
||||||
if (FlxG.keys.justPressed.ONE) endSong(true);
|
if (FlxG.keys.justPressed.ONE) endSong(true);
|
||||||
|
|
||||||
|
@ -2640,7 +2614,7 @@ class PlayState extends MusicBeatSubState
|
||||||
// 9: Toggle the old icon.
|
// 9: Toggle the old icon.
|
||||||
if (FlxG.keys.justPressed.NINE) iconP1.toggleOldIcon();
|
if (FlxG.keys.justPressed.NINE) iconP1.toggleOldIcon();
|
||||||
|
|
||||||
#if (debug || FORCE_DEBUG_VERSION)
|
#if FEATURE_DEBUG_FUNCTIONS
|
||||||
// PAGEUP: Skip forward two sections.
|
// PAGEUP: Skip forward two sections.
|
||||||
// SHIFT+PAGEUP: Skip forward twenty sections.
|
// SHIFT+PAGEUP: Skip forward twenty sections.
|
||||||
if (FlxG.keys.justPressed.PAGEUP) changeSection(FlxG.keys.pressed.SHIFT ? 20 : 2);
|
if (FlxG.keys.justPressed.PAGEUP) changeSection(FlxG.keys.pressed.SHIFT ? 20 : 2);
|
||||||
|
@ -2959,11 +2933,16 @@ class PlayState extends MusicBeatSubState
|
||||||
FunkinSound.playOnce(Paths.sound('Lights_Shut_off'), function() {
|
FunkinSound.playOnce(Paths.sound('Lights_Shut_off'), function() {
|
||||||
// no camFollow so it centers on horror tree
|
// no camFollow so it centers on horror tree
|
||||||
var targetSong:Song = SongRegistry.instance.fetchEntry(targetSongId);
|
var targetSong:Song = SongRegistry.instance.fetchEntry(targetSongId);
|
||||||
|
var targetVariation:String = currentVariation;
|
||||||
|
if (!targetSong.hasDifficulty(PlayStatePlaylist.campaignDifficulty, currentVariation))
|
||||||
|
{
|
||||||
|
targetVariation = targetSong.getFirstValidVariation(PlayStatePlaylist.campaignDifficulty) ?? Constants.DEFAULT_VARIATION;
|
||||||
|
}
|
||||||
LoadingState.loadPlayState(
|
LoadingState.loadPlayState(
|
||||||
{
|
{
|
||||||
targetSong: targetSong,
|
targetSong: targetSong,
|
||||||
targetDifficulty: PlayStatePlaylist.campaignDifficulty,
|
targetDifficulty: PlayStatePlaylist.campaignDifficulty,
|
||||||
targetVariation: currentVariation,
|
targetVariation: targetVariation,
|
||||||
cameraFollowPoint: cameraFollowPoint.getPosition(),
|
cameraFollowPoint: cameraFollowPoint.getPosition(),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -2971,11 +2950,16 @@ class PlayState extends MusicBeatSubState
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var targetSong:Song = SongRegistry.instance.fetchEntry(targetSongId);
|
var targetSong:Song = SongRegistry.instance.fetchEntry(targetSongId);
|
||||||
|
var targetVariation:String = currentVariation;
|
||||||
|
if (!targetSong.hasDifficulty(PlayStatePlaylist.campaignDifficulty, currentVariation))
|
||||||
|
{
|
||||||
|
targetVariation = targetSong.getFirstValidVariation(PlayStatePlaylist.campaignDifficulty) ?? Constants.DEFAULT_VARIATION;
|
||||||
|
}
|
||||||
LoadingState.loadPlayState(
|
LoadingState.loadPlayState(
|
||||||
{
|
{
|
||||||
targetSong: targetSong,
|
targetSong: targetSong,
|
||||||
targetDifficulty: PlayStatePlaylist.campaignDifficulty,
|
targetDifficulty: PlayStatePlaylist.campaignDifficulty,
|
||||||
targetVariation: currentVariation,
|
targetVariation: targetVariation,
|
||||||
cameraFollowPoint: cameraFollowPoint.getPosition(),
|
cameraFollowPoint: cameraFollowPoint.getPosition(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -3061,6 +3045,7 @@ class PlayState extends MusicBeatSubState
|
||||||
|
|
||||||
GameOverSubState.reset();
|
GameOverSubState.reset();
|
||||||
PauseSubState.reset();
|
PauseSubState.reset();
|
||||||
|
Countdown.reset();
|
||||||
|
|
||||||
// Clear the static reference to this state.
|
// Clear the static reference to this state.
|
||||||
instance = null;
|
instance = null;
|
||||||
|
@ -3156,6 +3141,7 @@ class PlayState extends MusicBeatSubState
|
||||||
storyMode: PlayStatePlaylist.isStoryMode,
|
storyMode: PlayStatePlaylist.isStoryMode,
|
||||||
songId: currentChart.song.id,
|
songId: currentChart.song.id,
|
||||||
difficultyId: currentDifficulty,
|
difficultyId: currentDifficulty,
|
||||||
|
characterId: currentChart.characters.player,
|
||||||
title: PlayStatePlaylist.isStoryMode ? ('${PlayStatePlaylist.campaignTitle}') : ('${currentChart.songName} by ${currentChart.songArtist}'),
|
title: PlayStatePlaylist.isStoryMode ? ('${PlayStatePlaylist.campaignTitle}') : ('${currentChart.songName} by ${currentChart.songArtist}'),
|
||||||
prevScoreData: prevScoreData,
|
prevScoreData: prevScoreData,
|
||||||
scoreData:
|
scoreData:
|
||||||
|
@ -3354,7 +3340,7 @@ class PlayState extends MusicBeatSubState
|
||||||
scrollSpeedTweens = [];
|
scrollSpeedTweens = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
#if (debug || FORCE_DEBUG_VERSION)
|
#if FEATURE_DEBUG_FUNCTIONS
|
||||||
/**
|
/**
|
||||||
* Jumps forward or backward a number of sections in the song.
|
* Jumps forward or backward a number of sections in the song.
|
||||||
* Accounts for BPM changes, does not prevent death from skipped notes.
|
* Accounts for BPM changes, does not prevent death from skipped notes.
|
||||||
|
|
|
@ -4,6 +4,8 @@ import funkin.util.MathUtil;
|
||||||
import funkin.ui.story.StoryMenuState;
|
import funkin.ui.story.StoryMenuState;
|
||||||
import funkin.graphics.adobeanimate.FlxAtlasSprite;
|
import funkin.graphics.adobeanimate.FlxAtlasSprite;
|
||||||
import flixel.FlxSprite;
|
import flixel.FlxSprite;
|
||||||
|
import flixel.FlxState;
|
||||||
|
import flixel.FlxSubState;
|
||||||
import funkin.graphics.FunkinSprite;
|
import funkin.graphics.FunkinSprite;
|
||||||
import flixel.effects.FlxFlicker;
|
import flixel.effects.FlxFlicker;
|
||||||
import flixel.graphics.frames.FlxBitmapFont;
|
import flixel.graphics.frames.FlxBitmapFont;
|
||||||
|
@ -14,6 +16,9 @@ import flixel.math.FlxRect;
|
||||||
import flixel.text.FlxBitmapText;
|
import flixel.text.FlxBitmapText;
|
||||||
import funkin.ui.freeplay.FreeplayScore;
|
import funkin.ui.freeplay.FreeplayScore;
|
||||||
import flixel.text.FlxText;
|
import flixel.text.FlxText;
|
||||||
|
import funkin.data.freeplay.player.PlayerRegistry;
|
||||||
|
import funkin.data.freeplay.player.PlayerData;
|
||||||
|
import funkin.ui.freeplay.charselect.PlayableCharacter;
|
||||||
import flixel.util.FlxColor;
|
import flixel.util.FlxColor;
|
||||||
import flixel.tweens.FlxEase;
|
import flixel.tweens.FlxEase;
|
||||||
import funkin.graphics.FunkinCamera;
|
import funkin.graphics.FunkinCamera;
|
||||||
|
@ -55,14 +60,19 @@ class ResultState extends MusicBeatSubState
|
||||||
final highscoreNew:FlxSprite;
|
final highscoreNew:FlxSprite;
|
||||||
final score:ResultScore;
|
final score:ResultScore;
|
||||||
|
|
||||||
var bfPerfect:Null<FlxAtlasSprite> = null;
|
var characterAtlasAnimations:Array<
|
||||||
var heartsPerfect:Null<FlxAtlasSprite> = null;
|
{
|
||||||
var bfExcellent:Null<FlxAtlasSprite> = null;
|
sprite:FlxAtlasSprite,
|
||||||
var bfGreat:Null<FlxAtlasSprite> = null;
|
delay:Float,
|
||||||
var gfGreat:Null<FlxAtlasSprite> = null;
|
forceLoop:Bool
|
||||||
var bfGood:Null<FlxSprite> = null;
|
}> = [];
|
||||||
var gfGood:Null<FlxSprite> = null;
|
var characterSparrowAnimations:Array<
|
||||||
var bfShit:Null<FlxAtlasSprite> = null;
|
{
|
||||||
|
sprite:FunkinSprite,
|
||||||
|
delay:Float
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
var playerCharacterId:Null<String>;
|
||||||
|
|
||||||
var rankBg:FunkinSprite;
|
var rankBg:FunkinSprite;
|
||||||
final cameraBG:FunkinCamera;
|
final cameraBG:FunkinCamera;
|
||||||
|
@ -157,119 +167,99 @@ class ResultState extends MusicBeatSubState
|
||||||
soundSystem.zIndex = 1100;
|
soundSystem.zIndex = 1100;
|
||||||
add(soundSystem);
|
add(soundSystem);
|
||||||
|
|
||||||
switch (rank)
|
// Fetch playable character data. Default to BF on the results screen if we can't find it.
|
||||||
|
playerCharacterId = PlayerRegistry.instance.getCharacterOwnerId(params.characterId);
|
||||||
|
var playerCharacter:Null<PlayableCharacter> = PlayerRegistry.instance.fetchEntry(playerCharacterId ?? 'bf');
|
||||||
|
|
||||||
|
trace('Got playable character: ${playerCharacter?.getName()}');
|
||||||
|
// Query JSON data based on the rank, then use that to build the animation(s) the player sees.
|
||||||
|
var playerAnimationDatas:Array<PlayerResultsAnimationData> = playerCharacter != null ? playerCharacter.getResultsAnimationDatas(rank) : [];
|
||||||
|
|
||||||
|
for (animData in playerAnimationDatas)
|
||||||
{
|
{
|
||||||
case PERFECT | PERFECT_GOLD:
|
if (animData == null) continue;
|
||||||
heartsPerfect = new FlxAtlasSprite(1342, 370, Paths.animateAtlas("resultScreen/results-bf/resultsPERFECT/hearts", "shared"));
|
|
||||||
heartsPerfect.visible = false;
|
|
||||||
heartsPerfect.zIndex = 501;
|
|
||||||
add(heartsPerfect);
|
|
||||||
|
|
||||||
heartsPerfect.anim.onComplete = () -> {
|
var animPath:String = Paths.stripLibrary(animData.assetPath);
|
||||||
if (heartsPerfect != null)
|
var animLibrary:String = Paths.getLibrary(animData.assetPath);
|
||||||
|
var offsets = animData.offsets ?? [0, 0];
|
||||||
|
switch (animData.renderType)
|
||||||
{
|
{
|
||||||
// bfPerfect.anim.curFrame = 137;
|
case 'animateatlas':
|
||||||
heartsPerfect.anim.curFrame = 43;
|
var animation:FlxAtlasSprite = new FlxAtlasSprite(offsets[0], offsets[1], Paths.animateAtlas(animPath, animLibrary));
|
||||||
heartsPerfect.anim.play(); // unpauses this anim, since it's on PlayOnce!
|
animation.zIndex = animData.zIndex ?? 500;
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
bfPerfect = new FlxAtlasSprite(1342, 370, Paths.animateAtlas("resultScreen/results-bf/resultsPERFECT", "shared"));
|
animation.scale.set(animData.scale ?? 1.0, animData.scale ?? 1.0);
|
||||||
bfPerfect.visible = false;
|
|
||||||
bfPerfect.zIndex = 500;
|
|
||||||
add(bfPerfect);
|
|
||||||
|
|
||||||
bfPerfect.anim.onComplete = () -> {
|
if (!(animData.looped ?? true))
|
||||||
if (bfPerfect != null)
|
|
||||||
{
|
{
|
||||||
// bfPerfect.anim.curFrame = 137;
|
// Animation is not looped.
|
||||||
bfPerfect.anim.curFrame = 137;
|
animation.onAnimationComplete.add((_name:String) -> {
|
||||||
bfPerfect.anim.play(); // unpauses this anim, since it's on PlayOnce!
|
trace("AHAHAH 2");
|
||||||
}
|
if (animation != null)
|
||||||
};
|
|
||||||
|
|
||||||
case EXCELLENT:
|
|
||||||
bfExcellent = new FlxAtlasSprite(1329, 429, Paths.animateAtlas("resultScreen/results-bf/resultsEXCELLENT", "shared"));
|
|
||||||
bfExcellent.visible = false;
|
|
||||||
bfExcellent.zIndex = 500;
|
|
||||||
add(bfExcellent);
|
|
||||||
|
|
||||||
bfExcellent.anim.onComplete = () -> {
|
|
||||||
if (bfExcellent != null)
|
|
||||||
{
|
{
|
||||||
bfExcellent.anim.curFrame = 28;
|
animation.anim.pause();
|
||||||
bfExcellent.anim.play(); // unpauses this anim, since it's on PlayOnce!
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
case GREAT:
|
|
||||||
gfGreat = new FlxAtlasSprite(802, 331, Paths.animateAtlas("resultScreen/results-bf/resultsGREAT/gf", "shared"));
|
|
||||||
gfGreat.visible = false;
|
|
||||||
gfGreat.zIndex = 499;
|
|
||||||
add(gfGreat);
|
|
||||||
|
|
||||||
gfGreat.scale.set(0.93, 0.93);
|
|
||||||
|
|
||||||
gfGreat.anim.onComplete = () -> {
|
|
||||||
if (gfGreat != null)
|
|
||||||
{
|
|
||||||
gfGreat.anim.curFrame = 9;
|
|
||||||
gfGreat.anim.play(); // unpauses this anim, since it's on PlayOnce!
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
bfGreat = new FlxAtlasSprite(929, 363, Paths.animateAtlas("resultScreen/results-bf/resultsGREAT/bf", "shared"));
|
|
||||||
bfGreat.visible = false;
|
|
||||||
bfGreat.zIndex = 500;
|
|
||||||
add(bfGreat);
|
|
||||||
|
|
||||||
bfGreat.scale.set(0.93, 0.93);
|
|
||||||
|
|
||||||
bfGreat.anim.onComplete = () -> {
|
|
||||||
if (bfGreat != null)
|
|
||||||
{
|
|
||||||
bfGreat.anim.curFrame = 15;
|
|
||||||
bfGreat.anim.play(); // unpauses this anim, since it's on PlayOnce!
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
case GOOD:
|
|
||||||
gfGood = FunkinSprite.createSparrow(625, 325, 'resultScreen/results-bf/resultsGOOD/resultGirlfriendGOOD');
|
|
||||||
gfGood.animation.addByPrefix("clap", "Girlfriend Good Anim", 24, false);
|
|
||||||
gfGood.visible = false;
|
|
||||||
gfGood.zIndex = 500;
|
|
||||||
gfGood.animation.finishCallback = _ -> {
|
|
||||||
if (gfGood != null)
|
|
||||||
{
|
|
||||||
gfGood.animation.play('clap', true, false, 9);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
add(gfGood);
|
|
||||||
|
|
||||||
bfGood = FunkinSprite.createSparrow(640, -200, 'resultScreen/results-bf/resultsGOOD/resultBoyfriendGOOD');
|
|
||||||
bfGood.animation.addByPrefix("fall", "Boyfriend Good Anim0", 24, false);
|
|
||||||
bfGood.visible = false;
|
|
||||||
bfGood.zIndex = 501;
|
|
||||||
bfGood.animation.finishCallback = function(_) {
|
|
||||||
if (bfGood != null)
|
|
||||||
{
|
|
||||||
bfGood.animation.play('fall', true, false, 14);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
add(bfGood);
|
|
||||||
|
|
||||||
case SHIT:
|
|
||||||
bfShit = new FlxAtlasSprite(0, 20, Paths.animateAtlas("resultScreen/results-bf/resultsSHIT", "shared"));
|
|
||||||
bfShit.visible = false;
|
|
||||||
bfShit.zIndex = 500;
|
|
||||||
add(bfShit);
|
|
||||||
bfShit.onAnimationFinish.add((animName) -> {
|
|
||||||
if (bfShit != null)
|
|
||||||
{
|
|
||||||
bfShit.playAnimation('Loop Start');
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
else if (animData.loopFrameLabel != null)
|
||||||
|
{
|
||||||
|
animation.onAnimationComplete.add((_name:String) -> {
|
||||||
|
trace("AHAHAH 2");
|
||||||
|
if (animation != null)
|
||||||
|
{
|
||||||
|
animation.playAnimation(animData.loopFrameLabel ?? '', true, false, true); // unpauses this anim, since it's on PlayOnce!
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (animData.loopFrame != null)
|
||||||
|
{
|
||||||
|
animation.onAnimationComplete.add((_name:String) -> {
|
||||||
|
if (animation != null)
|
||||||
|
{
|
||||||
|
trace("AHAHAH");
|
||||||
|
animation.anim.curFrame = animData.loopFrame ?? 0;
|
||||||
|
animation.anim.play(); // unpauses this anim, since it's on PlayOnce!
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide until ready to play.
|
||||||
|
animation.visible = false;
|
||||||
|
// Queue to play.
|
||||||
|
characterAtlasAnimations.push(
|
||||||
|
{
|
||||||
|
sprite: animation,
|
||||||
|
delay: animData.delay ?? 0.0,
|
||||||
|
forceLoop: (animData.loopFrame ?? -1) == 0
|
||||||
|
});
|
||||||
|
// Add to the scene.
|
||||||
|
add(animation);
|
||||||
|
case 'sparrow':
|
||||||
|
var animation:FunkinSprite = FunkinSprite.createSparrow(offsets[0], offsets[1], animPath);
|
||||||
|
animation.animation.addByPrefix('idle', '', 24, false, false, false);
|
||||||
|
|
||||||
|
if (animData.loopFrame != null)
|
||||||
|
{
|
||||||
|
animation.animation.finishCallback = (_name:String) -> {
|
||||||
|
if (animation != null)
|
||||||
|
{
|
||||||
|
animation.animation.play('idle', true, false, animData.loopFrame ?? 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide until ready to play.
|
||||||
|
animation.visible = false;
|
||||||
|
// Queue to play.
|
||||||
|
characterSparrowAnimations.push(
|
||||||
|
{
|
||||||
|
sprite: animation,
|
||||||
|
delay: animData.delay ?? 0.0
|
||||||
|
});
|
||||||
|
// Add to the scene.
|
||||||
|
add(animation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var diffSpr:String = 'diff_${params?.difficultyId ?? 'Normal'}';
|
var diffSpr:String = 'diff_${params?.difficultyId ?? 'Normal'}';
|
||||||
difficulty.loadGraphic(Paths.image("resultScreen/" + diffSpr));
|
difficulty.loadGraphic(Paths.image("resultScreen/" + diffSpr));
|
||||||
|
@ -419,28 +409,26 @@ class ResultState extends MusicBeatSubState
|
||||||
// }
|
// }
|
||||||
|
|
||||||
new FlxTimer().start(rank.getMusicDelay(), _ -> {
|
new FlxTimer().start(rank.getMusicDelay(), _ -> {
|
||||||
if (rank.hasMusicIntro())
|
var introMusic:String = Paths.music(getMusicPath(playerCharacter, rank) + '/' + getMusicPath(playerCharacter, rank) + '-intro');
|
||||||
|
if (Assets.exists(introMusic))
|
||||||
{
|
{
|
||||||
// Play the intro music.
|
// Play the intro music.
|
||||||
var introMusic:String = Paths.music(rank.getMusicPath() + '/' + rank.getMusicPath() + '-intro');
|
|
||||||
FunkinSound.load(introMusic, 1.0, false, true, true, () -> {
|
FunkinSound.load(introMusic, 1.0, false, true, true, () -> {
|
||||||
FunkinSound.playMusic(rank.getMusicPath(),
|
FunkinSound.playMusic(getMusicPath(playerCharacter, rank),
|
||||||
{
|
{
|
||||||
startingVolume: 1.0,
|
startingVolume: 1.0,
|
||||||
overrideExisting: true,
|
overrideExisting: true,
|
||||||
restartTrack: true,
|
restartTrack: true
|
||||||
loop: rank.shouldMusicLoop()
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
FunkinSound.playMusic(rank.getMusicPath(),
|
FunkinSound.playMusic(getMusicPath(playerCharacter, rank),
|
||||||
{
|
{
|
||||||
startingVolume: 1.0,
|
startingVolume: 1.0,
|
||||||
overrideExisting: true,
|
overrideExisting: true,
|
||||||
restartTrack: true,
|
restartTrack: true
|
||||||
loop: rank.shouldMusicLoop()
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -456,6 +444,11 @@ class ResultState extends MusicBeatSubState
|
||||||
super.create();
|
super.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getMusicPath(playerCharacter:Null<PlayableCharacter>, rank:ScoringRank):String
|
||||||
|
{
|
||||||
|
return playerCharacter?.getResultsMusicPath(rank) ?? 'resultsNORMAL';
|
||||||
|
}
|
||||||
|
|
||||||
var rankTallyTimer:Null<FlxTimer> = null;
|
var rankTallyTimer:Null<FlxTimer> = null;
|
||||||
var clearPercentTarget:Int = 100;
|
var clearPercentTarget:Int = 100;
|
||||||
var clearPercentLerp:Int = 0;
|
var clearPercentLerp:Int = 0;
|
||||||
|
@ -464,7 +457,9 @@ class ResultState extends MusicBeatSubState
|
||||||
{
|
{
|
||||||
bgFlash.visible = true;
|
bgFlash.visible = true;
|
||||||
FlxTween.tween(bgFlash, {alpha: 0}, 5 / 24);
|
FlxTween.tween(bgFlash, {alpha: 0}, 5 / 24);
|
||||||
var clearPercentFloat = (params.scoreData.tallies.sick + params.scoreData.tallies.good) / params.scoreData.tallies.totalNotes * 100;
|
// NOTE: Only divide if totalNotes > 0 to prevent divide-by-zero errors.
|
||||||
|
var clearPercentFloat = params.scoreData.tallies.totalNotes == 0 ? 0.0 : (params.scoreData.tallies.sick +
|
||||||
|
params.scoreData.tallies.good) / params.scoreData.tallies.totalNotes * 100;
|
||||||
clearPercentTarget = Math.floor(clearPercentFloat);
|
clearPercentTarget = Math.floor(clearPercentFloat);
|
||||||
// Prevent off-by-one errors.
|
// Prevent off-by-one errors.
|
||||||
|
|
||||||
|
@ -585,94 +580,22 @@ class ResultState extends MusicBeatSubState
|
||||||
{
|
{
|
||||||
showSmallClearPercent();
|
showSmallClearPercent();
|
||||||
|
|
||||||
switch (rank)
|
for (atlas in characterAtlasAnimations)
|
||||||
{
|
{
|
||||||
case PERFECT | PERFECT_GOLD:
|
new FlxTimer().start(atlas.delay, _ -> {
|
||||||
if (bfPerfect == null)
|
if (atlas.sprite == null) return;
|
||||||
{
|
atlas.sprite.visible = true;
|
||||||
trace("Could not build PERFECT animation!");
|
atlas.sprite.playAnimation('');
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
bfPerfect.visible = true;
|
|
||||||
bfPerfect.playAnimation('');
|
|
||||||
}
|
|
||||||
new FlxTimer().start(106 / 24, _ -> {
|
|
||||||
if (heartsPerfect == null)
|
|
||||||
{
|
|
||||||
trace("Could not build heartsPerfect animation!");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
heartsPerfect.visible = true;
|
|
||||||
heartsPerfect.playAnimation('');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
case EXCELLENT:
|
|
||||||
if (bfExcellent == null)
|
|
||||||
{
|
|
||||||
trace("Could not build EXCELLENT animation!");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
bfExcellent.visible = true;
|
|
||||||
bfExcellent.playAnimation('');
|
|
||||||
}
|
|
||||||
case GREAT:
|
|
||||||
if (bfGreat == null)
|
|
||||||
{
|
|
||||||
trace("Could not build GREAT animation!");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
bfGreat.visible = true;
|
|
||||||
bfGreat.playAnimation('');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
new FlxTimer().start(6 / 24, _ -> {
|
for (sprite in characterSparrowAnimations)
|
||||||
if (gfGreat == null)
|
|
||||||
{
|
{
|
||||||
trace("Could not build GREAT animation for gf!");
|
new FlxTimer().start(sprite.delay, _ -> {
|
||||||
}
|
if (sprite.sprite == null) return;
|
||||||
else
|
sprite.sprite.visible = true;
|
||||||
{
|
sprite.sprite.animation.play('idle', true);
|
||||||
gfGreat.visible = true;
|
|
||||||
gfGreat.playAnimation('');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
case SHIT:
|
|
||||||
if (bfShit == null)
|
|
||||||
{
|
|
||||||
trace("Could not build SHIT animation!");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
bfShit.visible = true;
|
|
||||||
bfShit.playAnimation('Intro');
|
|
||||||
}
|
|
||||||
case GOOD:
|
|
||||||
if (bfGood == null)
|
|
||||||
{
|
|
||||||
trace("Could not build GOOD animation!");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
bfGood.animation.play('fall');
|
|
||||||
bfGood.visible = true;
|
|
||||||
new FlxTimer().start((1 / 24) * 22, _ -> {
|
|
||||||
// plays about 22 frames (at 24fps timing) after bf spawns in
|
|
||||||
if (gfGood != null)
|
|
||||||
{
|
|
||||||
gfGood.animation.play('clap', true);
|
|
||||||
gfGood.visible = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
trace("Could not build GOOD animation!");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -774,52 +697,6 @@ class ResultState extends MusicBeatSubState
|
||||||
// }));
|
// }));
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// if(heartsPerfect != null){
|
|
||||||
// if (FlxG.keys.justPressed.I)
|
|
||||||
// {
|
|
||||||
// heartsPerfect.y -= 1;
|
|
||||||
// trace(heartsPerfect.x, heartsPerfect.y);
|
|
||||||
// }
|
|
||||||
// if (FlxG.keys.justPressed.J)
|
|
||||||
// {
|
|
||||||
// heartsPerfect.x -= 1;
|
|
||||||
// trace(heartsPerfect.x, heartsPerfect.y);
|
|
||||||
// }
|
|
||||||
// if (FlxG.keys.justPressed.L)
|
|
||||||
// {
|
|
||||||
// heartsPerfect.x += 1;
|
|
||||||
// trace(heartsPerfect.x, heartsPerfect.y);
|
|
||||||
// }
|
|
||||||
// if (FlxG.keys.justPressed.K)
|
|
||||||
// {
|
|
||||||
// heartsPerfect.y += 1;
|
|
||||||
// trace(heartsPerfect.x, heartsPerfect.y);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if(bfGreat != null){
|
|
||||||
// if (FlxG.keys.justPressed.W)
|
|
||||||
// {
|
|
||||||
// bfGreat.y -= 1;
|
|
||||||
// trace(bfGreat.x, bfGreat.y);
|
|
||||||
// }
|
|
||||||
// if (FlxG.keys.justPressed.A)
|
|
||||||
// {
|
|
||||||
// bfGreat.x -= 1;
|
|
||||||
// trace(bfGreat.x, bfGreat.y);
|
|
||||||
// }
|
|
||||||
// if (FlxG.keys.justPressed.D)
|
|
||||||
// {
|
|
||||||
// bfGreat.x += 1;
|
|
||||||
// trace(bfGreat.x, bfGreat.y);
|
|
||||||
// }
|
|
||||||
// if (FlxG.keys.justPressed.S)
|
|
||||||
// {
|
|
||||||
// bfGreat.y += 1;
|
|
||||||
// trace(bfGreat.x, bfGreat.y);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// maskShaderSongName.swagSprX = songName.x;
|
// maskShaderSongName.swagSprX = songName.x;
|
||||||
maskShaderDifficulty.swagSprX = difficulty.x;
|
maskShaderDifficulty.swagSprX = difficulty.x;
|
||||||
|
|
||||||
|
@ -857,24 +734,48 @@ class ResultState extends MusicBeatSubState
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determining the target state(s) to go to.
|
||||||
|
// Default to main menu because that's better than `null`.
|
||||||
|
var targetState:flixel.FlxState = new funkin.ui.mainmenu.MainMenuState();
|
||||||
|
var shouldTween = false;
|
||||||
|
var shouldUseSubstate = false;
|
||||||
|
|
||||||
if (params.storyMode)
|
if (params.storyMode)
|
||||||
{
|
{
|
||||||
openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new StoryMenuState(sticker)));
|
if (PlayerRegistry.instance.hasNewCharacter())
|
||||||
|
{
|
||||||
|
// New character, display the notif.
|
||||||
|
targetState = new StoryMenuState(null);
|
||||||
|
|
||||||
|
var newCharacters = PlayerRegistry.instance.listNewCharacters();
|
||||||
|
|
||||||
|
for (charId in newCharacters)
|
||||||
|
{
|
||||||
|
shouldTween = true;
|
||||||
|
// This works recursively, ehe!
|
||||||
|
targetState = new funkin.ui.charSelect.CharacterUnlockState(charId, targetState);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var rigged:Bool = true;
|
// No new characters.
|
||||||
if (rank > Scoring.calculateRank(params?.prevScoreData)) // if (rigged)
|
shouldTween = false;
|
||||||
|
shouldUseSubstate = true;
|
||||||
|
targetState = new funkin.ui.transition.StickerSubState(null, (sticker) -> new StoryMenuState(sticker));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (rank > Scoring.calculateRank(params?.prevScoreData))
|
||||||
{
|
{
|
||||||
trace('THE RANK IS Higher.....');
|
trace('THE RANK IS Higher.....');
|
||||||
|
|
||||||
FlxTween.tween(rankBg, {alpha: 1}, 0.5,
|
shouldTween = true;
|
||||||
{
|
targetState = FreeplayState.build(
|
||||||
ease: FlxEase.expoOut,
|
|
||||||
onComplete: function(_) {
|
|
||||||
FlxG.switchState(FreeplayState.build(
|
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
|
character: playerCharacterId ?? "bf",
|
||||||
fromResults:
|
fromResults:
|
||||||
{
|
{
|
||||||
oldRank: Scoring.calculateRank(params?.prevScoreData),
|
oldRank: Scoring.calculateRank(params?.prevScoreData),
|
||||||
|
@ -884,26 +785,42 @@ class ResultState extends MusicBeatSubState
|
||||||
playRankAnim: true
|
playRankAnim: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
shouldTween = false;
|
||||||
|
shouldUseSubstate = true;
|
||||||
|
targetState = new funkin.ui.transition.StickerSubState(null, (sticker) -> FreeplayState.build(null, sticker));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldTween)
|
||||||
|
{
|
||||||
|
FlxTween.tween(rankBg, {alpha: 1}, 0.5,
|
||||||
|
{
|
||||||
|
ease: FlxEase.expoOut,
|
||||||
|
onComplete: function(_) {
|
||||||
|
if (shouldUseSubstate && targetState is FlxSubState)
|
||||||
|
{
|
||||||
|
openSubState(cast targetState);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FlxG.switchState(targetState);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
trace('rank is lower...... and/or equal');
|
if (shouldUseSubstate && targetState is FlxSubState)
|
||||||
openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> FreeplayState.build(
|
|
||||||
{
|
{
|
||||||
{
|
openSubState(cast targetState);
|
||||||
fromResults:
|
|
||||||
{
|
|
||||||
oldRank: null,
|
|
||||||
playRankAnim: false,
|
|
||||||
newRank: rank,
|
|
||||||
songId: params.songId,
|
|
||||||
difficultyId: params.difficultyId
|
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
}, sticker)));
|
{
|
||||||
|
FlxG.switchState(targetState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -920,12 +837,22 @@ typedef ResultsStateParams =
|
||||||
var storyMode:Bool;
|
var storyMode:Bool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* A readable title for the song we just played.
|
||||||
* Either "Song Name by Artist Name" or "Week Name"
|
* Either "Song Name by Artist Name" or "Week Name"
|
||||||
*/
|
*/
|
||||||
var title:String;
|
var title:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The internal song ID for the song we just played.
|
||||||
|
*/
|
||||||
var songId:String;
|
var songId:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The character ID for the song we just played.
|
||||||
|
* @default `bf`
|
||||||
|
*/
|
||||||
|
var ?characterId:String;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the displayed score is a new highscore
|
* Whether the displayed score is a new highscore
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -109,8 +109,6 @@ class AnimateAtlasCharacter extends BaseCharacter
|
||||||
var loop:Bool = animData.looped;
|
var loop:Bool = animData.looped;
|
||||||
|
|
||||||
this.mainSprite.playAnimation(prefix, restart, ignoreOther, loop);
|
this.mainSprite.playAnimation(prefix, restart, ignoreOther, loop);
|
||||||
|
|
||||||
animFinished = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override function hasAnimation(name:String):Bool
|
public override function hasAnimation(name:String):Bool
|
||||||
|
@ -124,17 +122,21 @@ class AnimateAtlasCharacter extends BaseCharacter
|
||||||
*/
|
*/
|
||||||
public override function isAnimationFinished():Bool
|
public override function isAnimationFinished():Bool
|
||||||
{
|
{
|
||||||
return animFinished;
|
return mainSprite?.isAnimationFinished() ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadAtlasSprite():FlxAtlasSprite
|
function loadAtlasSprite():FlxAtlasSprite
|
||||||
{
|
{
|
||||||
trace('[ATLASCHAR] Loading sprite atlas for ${characterId}.');
|
trace('[ATLASCHAR] Loading sprite atlas for ${characterId}.');
|
||||||
|
|
||||||
var sprite:FlxAtlasSprite = new FlxAtlasSprite(0, 0, Paths.animateAtlas(_data.assetPath, 'shared'));
|
var animLibrary:String = Paths.getLibrary(_data.assetPath);
|
||||||
|
var animPath:String = Paths.stripLibrary(_data.assetPath);
|
||||||
|
var assetPath:String = Paths.animateAtlas(animPath, animLibrary);
|
||||||
|
|
||||||
sprite.onAnimationFinish.removeAll();
|
var sprite:FlxAtlasSprite = new FlxAtlasSprite(0, 0, assetPath);
|
||||||
sprite.onAnimationFinish.add(this.onAnimationFinished);
|
|
||||||
|
// sprite.onAnimationComplete.removeAll();
|
||||||
|
sprite.onAnimationComplete.add(this.onAnimationFinished);
|
||||||
|
|
||||||
return sprite;
|
return sprite;
|
||||||
}
|
}
|
||||||
|
@ -152,7 +154,6 @@ class AnimateAtlasCharacter extends BaseCharacter
|
||||||
// Make the game hold on the last frame.
|
// Make the game hold on the last frame.
|
||||||
this.mainSprite.cleanupAnimation(prefix);
|
this.mainSprite.cleanupAnimation(prefix);
|
||||||
// currentAnimName = null;
|
// currentAnimName = null;
|
||||||
animFinished = true;
|
|
||||||
|
|
||||||
// Fallback to idle!
|
// Fallback to idle!
|
||||||
// playAnimation('idle', true, false);
|
// playAnimation('idle', true, false);
|
||||||
|
@ -165,6 +166,13 @@ class AnimateAtlasCharacter extends BaseCharacter
|
||||||
|
|
||||||
this.mainSprite = sprite;
|
this.mainSprite = sprite;
|
||||||
|
|
||||||
|
mainSprite.ignoreExclusionPref = ["sing"];
|
||||||
|
|
||||||
|
// This forces the atlas to recalcuate its width and height
|
||||||
|
this.mainSprite.alpha = 0.0001;
|
||||||
|
this.mainSprite.draw();
|
||||||
|
this.mainSprite.alpha = 1.0;
|
||||||
|
|
||||||
var feetPos:FlxPoint = feetPosition;
|
var feetPos:FlxPoint = feetPosition;
|
||||||
this.updateHitbox();
|
this.updateHitbox();
|
||||||
|
|
||||||
|
|
|
@ -118,22 +118,6 @@ class BaseCharacter extends Bopper
|
||||||
*/
|
*/
|
||||||
public var cameraFocusPoint(default, null):FlxPoint = new FlxPoint(0, 0);
|
public var cameraFocusPoint(default, null):FlxPoint = new FlxPoint(0, 0);
|
||||||
|
|
||||||
override function set_animOffsets(value:Array<Float>):Array<Float>
|
|
||||||
{
|
|
||||||
if (animOffsets == null) value = [0, 0];
|
|
||||||
if ((animOffsets[0] == value[0]) && (animOffsets[1] == value[1])) return value;
|
|
||||||
|
|
||||||
// Make sure animOffets are halved when scale is 0.5.
|
|
||||||
var xDiff = (animOffsets[0] * this.scale.x / (this.isPixel ? 6 : 1)) - value[0];
|
|
||||||
var yDiff = (animOffsets[1] * this.scale.y / (this.isPixel ? 6 : 1)) - value[1];
|
|
||||||
|
|
||||||
// Call the super function so that camera focus point is not affected.
|
|
||||||
super.set_x(this.x + xDiff);
|
|
||||||
super.set_y(this.y + yDiff);
|
|
||||||
|
|
||||||
return animOffsets = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the x position changes, other than via changing the animation offset,
|
* If the x position changes, other than via changing the animation offset,
|
||||||
* then we need to update the camera focus point.
|
* then we need to update the camera focus point.
|
||||||
|
@ -164,9 +148,11 @@ class BaseCharacter extends Bopper
|
||||||
|
|
||||||
public function new(id:String, renderType:CharacterRenderType)
|
public function new(id:String, renderType:CharacterRenderType)
|
||||||
{
|
{
|
||||||
super();
|
super(CharacterDataParser.DEFAULT_DANCEEVERY);
|
||||||
this.characterId = id;
|
this.characterId = id;
|
||||||
|
|
||||||
|
ignoreExclusionPref = ["sing"];
|
||||||
|
|
||||||
_data = CharacterDataParser.fetchCharacterData(this.characterId);
|
_data = CharacterDataParser.fetchCharacterData(this.characterId);
|
||||||
if (_data == null)
|
if (_data == null)
|
||||||
{
|
{
|
||||||
|
@ -180,6 +166,7 @@ class BaseCharacter extends Bopper
|
||||||
{
|
{
|
||||||
this.characterName = _data.name;
|
this.characterName = _data.name;
|
||||||
this.name = _data.name;
|
this.name = _data.name;
|
||||||
|
this.danceEvery = _data.danceEvery;
|
||||||
this.singTimeSteps = _data.singTime;
|
this.singTimeSteps = _data.singTime;
|
||||||
this.globalOffsets = _data.offsets;
|
this.globalOffsets = _data.offsets;
|
||||||
this.flipX = _data.flipX;
|
this.flipX = _data.flipX;
|
||||||
|
@ -308,13 +295,26 @@ class BaseCharacter extends Bopper
|
||||||
// so we can query which ones are available.
|
// so we can query which ones are available.
|
||||||
this.comboNoteCounts = findCountAnimations('combo'); // example: combo50
|
this.comboNoteCounts = findCountAnimations('combo'); // example: combo50
|
||||||
this.dropNoteCounts = findCountAnimations('drop'); // example: drop50
|
this.dropNoteCounts = findCountAnimations('drop'); // example: drop50
|
||||||
// trace('${this.animation.getNameList()}');
|
if (comboNoteCounts.length > 0) trace('Combo note counts: ' + this.comboNoteCounts);
|
||||||
// trace('Combo note counts: ' + this.comboNoteCounts);
|
if (dropNoteCounts.length > 0) trace('Drop note counts: ' + this.dropNoteCounts);
|
||||||
// trace('Drop note counts: ' + this.dropNoteCounts);
|
|
||||||
|
|
||||||
super.onCreate(event);
|
super.onCreate(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override function onAnimationFinished(animationName:String):Void
|
||||||
|
{
|
||||||
|
super.onAnimationFinished(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'))
|
||||||
|
{
|
||||||
|
// Force the character to play the idle after the animation ends.
|
||||||
|
this.dance(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function resetCameraFocusPoint():Void
|
function resetCameraFocusPoint():Void
|
||||||
{
|
{
|
||||||
// Calculate the camera focus point
|
// Calculate the camera focus point
|
||||||
|
@ -368,9 +368,18 @@ class BaseCharacter extends Bopper
|
||||||
// and Darnell (this keeps the flame on his lighter flickering).
|
// and Darnell (this keeps the flame on his lighter flickering).
|
||||||
// Works for idle, singLEFT/RIGHT/UP/DOWN, alt singing animations, and anything else really.
|
// Works for idle, singLEFT/RIGHT/UP/DOWN, alt singing animations, and anything else really.
|
||||||
|
|
||||||
if (!getCurrentAnimation().endsWith('-hold') && hasAnimation(getCurrentAnimation() + '-hold') && isAnimationFinished())
|
if (isAnimationFinished()
|
||||||
|
&& !getCurrentAnimation().endsWith(Constants.ANIMATION_HOLD_SUFFIX)
|
||||||
|
&& hasAnimation(getCurrentAnimation() + Constants.ANIMATION_HOLD_SUFFIX))
|
||||||
{
|
{
|
||||||
playAnimation(getCurrentAnimation() + '-hold');
|
playAnimation(getCurrentAnimation() + Constants.ANIMATION_HOLD_SUFFIX);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (isAnimationFinished())
|
||||||
|
{
|
||||||
|
// trace('Not playing hold (${getCurrentAnimation()}) (${isAnimationFinished()}, ${getCurrentAnimation().endsWith(Constants.ANIMATION_HOLD_SUFFIX)}, ${hasAnimation(getCurrentAnimation() + Constants.ANIMATION_HOLD_SUFFIX)})');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle character note hold time.
|
// Handle character note hold time.
|
||||||
|
@ -395,9 +404,26 @@ class BaseCharacter extends Bopper
|
||||||
{
|
{
|
||||||
trace('holdTimer reached ${holdTimer}sec (> ${singTimeSec}), stopping sing animation');
|
trace('holdTimer reached ${holdTimer}sec (> ${singTimeSec}), stopping sing animation');
|
||||||
holdTimer = 0;
|
holdTimer = 0;
|
||||||
|
|
||||||
|
var currentAnimation:String = getCurrentAnimation();
|
||||||
|
// Strip "-hold" from the end.
|
||||||
|
if (currentAnimation.endsWith(Constants.ANIMATION_HOLD_SUFFIX)) currentAnimation = currentAnimation.substring(0,
|
||||||
|
currentAnimation.length - Constants.ANIMATION_HOLD_SUFFIX.length);
|
||||||
|
|
||||||
|
var endAnimation:String = currentAnimation + Constants.ANIMATION_END_SUFFIX;
|
||||||
|
if (hasAnimation(endAnimation))
|
||||||
|
{
|
||||||
|
// Play the '-end' animation, if one exists.
|
||||||
|
trace('${characterId}: playing ${endAnimation}');
|
||||||
|
playAnimation(endAnimation);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Play the idle animation.
|
||||||
dance(true);
|
dance(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
holdTimer = 0;
|
holdTimer = 0;
|
||||||
|
@ -408,7 +434,8 @@ class BaseCharacter extends Bopper
|
||||||
|
|
||||||
public function isSinging():Bool
|
public function isSinging():Bool
|
||||||
{
|
{
|
||||||
return getCurrentAnimation().startsWith('sing');
|
var currentAnimation:String = getCurrentAnimation();
|
||||||
|
return currentAnimation.startsWith('sing') && !currentAnimation.endsWith(Constants.ANIMATION_END_SUFFIX);
|
||||||
}
|
}
|
||||||
|
|
||||||
override function dance(force:Bool = false):Void
|
override function dance(force:Bool = false):Void
|
||||||
|
@ -418,15 +445,14 @@ class BaseCharacter extends Bopper
|
||||||
|
|
||||||
if (!force)
|
if (!force)
|
||||||
{
|
{
|
||||||
|
// Prevent dancing while a singing animation is playing.
|
||||||
if (isSinging()) return;
|
if (isSinging()) return;
|
||||||
|
|
||||||
|
// Prevent dancing while a non-idle special animation is playing.
|
||||||
var currentAnimation:String = getCurrentAnimation();
|
var currentAnimation:String = getCurrentAnimation();
|
||||||
if ((currentAnimation == 'hey' || currentAnimation == 'cheer') && !isAnimationFinished()) return;
|
if (!currentAnimation.startsWith('dance') && !currentAnimation.startsWith('idle') && !isAnimationFinished()) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent dancing while another animation is playing.
|
|
||||||
if (!force && isSinging()) return;
|
|
||||||
|
|
||||||
// Otherwise, fallback to the super dance() method, which handles playing the idle animation.
|
// Otherwise, fallback to the super dance() method, which handles playing the idle animation.
|
||||||
super.dance();
|
super.dance();
|
||||||
}
|
}
|
||||||
|
@ -487,6 +513,9 @@ class BaseCharacter extends Bopper
|
||||||
{
|
{
|
||||||
super.onNoteHit(event);
|
super.onNoteHit(event);
|
||||||
|
|
||||||
|
// If another script cancelled the event, don't do anything.
|
||||||
|
if (event.eventCanceled) return;
|
||||||
|
|
||||||
if (event.note.noteData.getMustHitNote() && characterType == BF)
|
if (event.note.noteData.getMustHitNote() && characterType == BF)
|
||||||
{
|
{
|
||||||
// If the note is from the same strumline, play the sing animation.
|
// If the note is from the same strumline, play the sing animation.
|
||||||
|
@ -499,6 +528,16 @@ class BaseCharacter extends Bopper
|
||||||
this.playSingAnimation(event.note.noteData.getDirection(), false);
|
this.playSingAnimation(event.note.noteData.getDirection(), false);
|
||||||
holdTimer = 0;
|
holdTimer = 0;
|
||||||
}
|
}
|
||||||
|
else if (characterType == GF && event.note.noteData.getMustHitNote())
|
||||||
|
{
|
||||||
|
switch (event.judgement)
|
||||||
|
{
|
||||||
|
case 'sick' | 'good':
|
||||||
|
playComboAnimation(event.comboCount);
|
||||||
|
default:
|
||||||
|
playComboDropAnimation(event.comboCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -509,6 +548,9 @@ class BaseCharacter extends Bopper
|
||||||
{
|
{
|
||||||
super.onNoteMiss(event);
|
super.onNoteMiss(event);
|
||||||
|
|
||||||
|
// If another script cancelled the event, don't do anything.
|
||||||
|
if (event.eventCanceled) return;
|
||||||
|
|
||||||
if (event.note.noteData.getMustHitNote() && characterType == BF)
|
if (event.note.noteData.getMustHitNote() && characterType == BF)
|
||||||
{
|
{
|
||||||
// If the note is from the same strumline, play the sing animation.
|
// If the note is from the same strumline, play the sing animation.
|
||||||
|
@ -521,31 +563,46 @@ class BaseCharacter extends Bopper
|
||||||
}
|
}
|
||||||
else if (event.note.noteData.getMustHitNote() && characterType == GF)
|
else if (event.note.noteData.getMustHitNote() && characterType == GF)
|
||||||
{
|
{
|
||||||
var dropAnim = '';
|
playComboDropAnimation(Highscore.tallies.combo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function playComboAnimation(comboCount:Int):Void
|
||||||
|
{
|
||||||
|
var comboAnim = 'combo${comboCount}';
|
||||||
|
if (hasAnimation(comboAnim))
|
||||||
|
{
|
||||||
|
trace('Playing GF combo animation: ${comboAnim}');
|
||||||
|
this.playAnimation(comboAnim, true, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function playComboDropAnimation(comboCount:Int):Void
|
||||||
|
{
|
||||||
|
var dropAnim:Null<String> = null;
|
||||||
|
|
||||||
// Choose the combo drop anim to play.
|
// Choose the combo drop anim to play.
|
||||||
// If there are several (for example, drop10 and drop50) the highest one will be used.
|
// If there are several (for example, drop10 and drop50) the highest one will be used.
|
||||||
// If the combo count is too low, no animation will be played.
|
// If the combo count is too low, no animation will be played.
|
||||||
for (count in dropNoteCounts)
|
for (count in dropNoteCounts)
|
||||||
{
|
{
|
||||||
if (event.comboCount >= count)
|
if (comboCount >= count)
|
||||||
{
|
{
|
||||||
dropAnim = 'drop${count}';
|
dropAnim = 'drop${count}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dropAnim != '')
|
if (dropAnim != null)
|
||||||
{
|
{
|
||||||
trace('Playing GF combo drop animation: ${dropAnim}');
|
trace('Playing GF combo drop animation: ${dropAnim}');
|
||||||
this.playAnimation(dropAnim, true, true);
|
this.playAnimation(dropAnim, true, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Every time a wrong key is pressed, play the miss animation if we are Boyfriend.
|
* Every time a wrong key is pressed, play the miss animation if we are Boyfriend.
|
||||||
*/
|
*/
|
||||||
public override function onNoteGhostMiss(event:GhostMissNoteScriptEvent)
|
public override function onNoteGhostMiss(event:GhostMissNoteScriptEvent):Void
|
||||||
{
|
{
|
||||||
super.onNoteGhostMiss(event);
|
super.onNoteGhostMiss(event);
|
||||||
|
|
||||||
|
@ -579,12 +636,12 @@ class BaseCharacter extends Bopper
|
||||||
var anim:String = 'sing${dir.nameUpper}${miss ? 'miss' : ''}${suffix != '' ? '-${suffix}' : ''}';
|
var anim:String = 'sing${dir.nameUpper}${miss ? 'miss' : ''}${suffix != '' ? '-${suffix}' : ''}';
|
||||||
|
|
||||||
// restart even if already playing, because the character might sing the same note twice.
|
// restart even if already playing, because the character might sing the same note twice.
|
||||||
|
trace('Playing ${anim}...');
|
||||||
playAnimation(anim, true);
|
playAnimation(anim, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override function playAnimation(name:String, restart:Bool = false, ignoreOther:Bool = false, reversed:Bool = false):Void
|
public override function playAnimation(name:String, restart:Bool = false, ignoreOther:Bool = false, reversed:Bool = false):Void
|
||||||
{
|
{
|
||||||
// FlxG.watch.addQuick('playAnim(${characterName})', name);
|
|
||||||
super.playAnimation(name, restart, ignoreOther, reversed);
|
super.playAnimation(name, restart, ignoreOther, reversed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -305,8 +305,10 @@ class CharacterDataParser
|
||||||
icon = "darnell";
|
icon = "darnell";
|
||||||
case "senpai-angry":
|
case "senpai-angry":
|
||||||
icon = "senpai";
|
icon = "senpai";
|
||||||
case "tankman" | "tankman-atlas":
|
case "spooky-dark":
|
||||||
icon = "tankmen";
|
icon = "spooky";
|
||||||
|
case "tankman-atlas":
|
||||||
|
icon = "tankman";
|
||||||
}
|
}
|
||||||
|
|
||||||
var path = Paths.image("freeplay/icons/" + icon + "pixel");
|
var path = Paths.image("freeplay/icons/" + icon + "pixel");
|
||||||
|
@ -383,21 +385,21 @@ class CharacterDataParser
|
||||||
* Values that are too high will cause the character to hold their singing pose for too long after they're done.
|
* Values that are too high will cause the character to hold their singing pose for too long after they're done.
|
||||||
* @default `8 steps`
|
* @default `8 steps`
|
||||||
*/
|
*/
|
||||||
static final DEFAULT_SINGTIME:Float = 8.0;
|
public static final DEFAULT_SINGTIME:Float = 8.0;
|
||||||
|
|
||||||
static final DEFAULT_DANCEEVERY:Int = 1;
|
public static final DEFAULT_DANCEEVERY:Float = 1.0;
|
||||||
static final DEFAULT_FLIPX:Bool = false;
|
public static final DEFAULT_FLIPX:Bool = false;
|
||||||
static final DEFAULT_FLIPY:Bool = false;
|
public static final DEFAULT_FLIPY:Bool = false;
|
||||||
static final DEFAULT_FRAMERATE:Int = 24;
|
public static final DEFAULT_FRAMERATE:Int = 24;
|
||||||
static final DEFAULT_ISPIXEL:Bool = false;
|
public static final DEFAULT_ISPIXEL:Bool = false;
|
||||||
static final DEFAULT_LOOP:Bool = false;
|
public static final DEFAULT_LOOP:Bool = false;
|
||||||
static final DEFAULT_NAME:String = 'Untitled Character';
|
public static final DEFAULT_NAME:String = 'Untitled Character';
|
||||||
static final DEFAULT_OFFSETS:Array<Float> = [0, 0];
|
public static final DEFAULT_OFFSETS:Array<Float> = [0, 0];
|
||||||
static final DEFAULT_HEALTHICON_OFFSETS:Array<Int> = [0, 25];
|
public static final DEFAULT_HEALTHICON_OFFSETS:Array<Int> = [0, 25];
|
||||||
static final DEFAULT_RENDERTYPE:CharacterRenderType = CharacterRenderType.Sparrow;
|
public static final DEFAULT_RENDERTYPE:CharacterRenderType = CharacterRenderType.Sparrow;
|
||||||
static final DEFAULT_SCALE:Float = 1;
|
public static final DEFAULT_SCALE:Float = 1;
|
||||||
static final DEFAULT_SCROLL:Array<Float> = [0, 0];
|
public static final DEFAULT_SCROLL:Array<Float> = [0, 0];
|
||||||
static final DEFAULT_STARTINGANIM:String = 'idle';
|
public static final DEFAULT_STARTINGANIM:String = 'idle';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set unspecified parameters to their defaults.
|
* Set unspecified parameters to their defaults.
|
||||||
|
@ -665,10 +667,12 @@ typedef CharacterData =
|
||||||
/**
|
/**
|
||||||
* The frequency at which the character will play its idle animation, in beats.
|
* The frequency at which the character will play its idle animation, in beats.
|
||||||
* Increasing this number will make the character dance less often.
|
* Increasing this number will make the character dance less often.
|
||||||
*
|
* Supports up to `0.25` precision.
|
||||||
* @default 1
|
* @default `1.0` on characters
|
||||||
*/
|
*/
|
||||||
var danceEvery:Null<Int>;
|
@:optional
|
||||||
|
@:default(1.0)
|
||||||
|
var danceEvery:Null<Float>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The minimum duration that a character will play a note animation for, in beats.
|
* The minimum duration that a character will play a note animation for, in beats.
|
||||||
|
|
|
@ -41,6 +41,8 @@ class MultiSparrowCharacter extends BaseCharacter
|
||||||
{
|
{
|
||||||
this.isPixel = true;
|
this.isPixel = true;
|
||||||
this.antialiasing = false;
|
this.antialiasing = false;
|
||||||
|
pixelPerfectRender = true;
|
||||||
|
pixelPerfectPosition = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -60,11 +62,13 @@ class MultiSparrowCharacter extends BaseCharacter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var texture:FlxAtlasFrames = Paths.getSparrowAtlas(_data.assetPath, 'shared');
|
var texture:FlxAtlasFrames = Paths.getSparrowAtlas(_data.assetPath);
|
||||||
|
|
||||||
if (texture == null)
|
if (texture == null)
|
||||||
{
|
{
|
||||||
trace('Multi-Sparrow atlas could not load PRIMARY texture: ${_data.assetPath}');
|
trace('Multi-Sparrow atlas could not load PRIMARY texture: ${_data.assetPath}');
|
||||||
|
FlxG.log.error('Multi-Sparrow atlas could not load PRIMARY texture: ${_data.assetPath}');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -74,7 +78,7 @@ class MultiSparrowCharacter extends BaseCharacter
|
||||||
|
|
||||||
for (asset in assetList)
|
for (asset in assetList)
|
||||||
{
|
{
|
||||||
var subTexture:FlxAtlasFrames = Paths.getSparrowAtlas(asset, 'shared');
|
var subTexture:FlxAtlasFrames = Paths.getSparrowAtlas(asset);
|
||||||
// If we don't do this, the unused textures will be removed as soon as they're loaded.
|
// If we don't do this, the unused textures will be removed as soon as they're loaded.
|
||||||
|
|
||||||
if (subTexture == null)
|
if (subTexture == null)
|
||||||
|
|
|
@ -30,7 +30,7 @@ class PackerCharacter extends BaseCharacter
|
||||||
{
|
{
|
||||||
trace('[PACKERCHAR] Loading spritesheet ${_data.assetPath} for ${characterId}');
|
trace('[PACKERCHAR] Loading spritesheet ${_data.assetPath} for ${characterId}');
|
||||||
|
|
||||||
var tex:FlxFramesCollection = Paths.getPackerAtlas(_data.assetPath, 'shared');
|
var tex:FlxFramesCollection = Paths.getPackerAtlas(_data.assetPath);
|
||||||
if (tex == null)
|
if (tex == null)
|
||||||
{
|
{
|
||||||
trace('Could not load Packer sprite: ${_data.assetPath}');
|
trace('Could not load Packer sprite: ${_data.assetPath}');
|
||||||
|
@ -43,6 +43,8 @@ class PackerCharacter extends BaseCharacter
|
||||||
{
|
{
|
||||||
this.isPixel = true;
|
this.isPixel = true;
|
||||||
this.antialiasing = false;
|
this.antialiasing = false;
|
||||||
|
pixelPerfectRender = true;
|
||||||
|
pixelPerfectPosition = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -33,7 +33,7 @@ class SparrowCharacter extends BaseCharacter
|
||||||
{
|
{
|
||||||
trace('[SPARROWCHAR] Loading spritesheet ${_data.assetPath} for ${characterId}');
|
trace('[SPARROWCHAR] Loading spritesheet ${_data.assetPath} for ${characterId}');
|
||||||
|
|
||||||
var tex:FlxFramesCollection = Paths.getSparrowAtlas(_data.assetPath, 'shared');
|
var tex:FlxFramesCollection = Paths.getSparrowAtlas(_data.assetPath);
|
||||||
if (tex == null)
|
if (tex == null)
|
||||||
{
|
{
|
||||||
trace('Could not load Sparrow sprite: ${_data.assetPath}');
|
trace('Could not load Sparrow sprite: ${_data.assetPath}');
|
||||||
|
@ -46,6 +46,8 @@ class SparrowCharacter extends BaseCharacter
|
||||||
{
|
{
|
||||||
this.isPixel = true;
|
this.isPixel = true;
|
||||||
this.antialiasing = false;
|
this.antialiasing = false;
|
||||||
|
pixelPerfectRender = true;
|
||||||
|
pixelPerfectPosition = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -33,7 +33,7 @@ class HealthIcon extends FunkinSprite
|
||||||
* The character this icon is representing.
|
* The character this icon is representing.
|
||||||
* Setting this variable will automatically update the graphic.
|
* 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.
|
* Whether this health icon should automatically update its state based on the character's health.
|
||||||
|
@ -49,7 +49,7 @@ class HealthIcon extends FunkinSprite
|
||||||
* this value allows you to set a relative scale for the icon.
|
* this value allows you to set a relative scale for the icon.
|
||||||
* @default 1x scale = 150px width and height.
|
* @default 1x scale = 150px width and height.
|
||||||
*/
|
*/
|
||||||
public var size:FlxPoint = new FlxPoint(1, 1);
|
public var size:FlxPoint;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply the "bop" animation once every X steps.
|
* Apply the "bop" animation once every X steps.
|
||||||
|
@ -116,18 +116,22 @@ class HealthIcon extends FunkinSprite
|
||||||
*/
|
*/
|
||||||
static final POSITION_OFFSET:Int = 26;
|
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);
|
super(0, 0);
|
||||||
this.playerId = playerId;
|
this.playerId = playerId;
|
||||||
|
this.size = new FlxCallbackPoint(onSetSize);
|
||||||
this.scrollFactor.set();
|
this.scrollFactor.set();
|
||||||
|
size.set(1.0, 1.0);
|
||||||
this.characterId = char;
|
this.characterId = char;
|
||||||
|
|
||||||
initTargetSize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function set_characterId(value:Null<String>):Null<String>
|
function onSetSize(value:FlxPoint):Void
|
||||||
|
{
|
||||||
|
snapToTargetSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_characterId(value:Null<String>):String
|
||||||
{
|
{
|
||||||
if (value == characterId) return value;
|
if (value == characterId) return value;
|
||||||
|
|
||||||
|
@ -150,13 +154,17 @@ class HealthIcon extends FunkinSprite
|
||||||
{
|
{
|
||||||
if (characterId == 'bf-old')
|
if (characterId == 'bf-old')
|
||||||
{
|
{
|
||||||
|
isPixel = PlayState.instance.currentStage.getBoyfriend().isPixel;
|
||||||
PlayState.instance.currentStage.getBoyfriend().initHealthIcon(false);
|
PlayState.instance.currentStage.getBoyfriend().initHealthIcon(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
characterId = 'bf-old';
|
characterId = 'bf-old';
|
||||||
|
isPixel = false;
|
||||||
loadCharacter(characterId);
|
loadCharacter(characterId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lerpIconSize(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -199,6 +207,22 @@ class HealthIcon extends FunkinSprite
|
||||||
super.update(elapsed);
|
super.update(elapsed);
|
||||||
|
|
||||||
if (bopEvery != 0)
|
if (bopEvery != 0)
|
||||||
|
{
|
||||||
|
lerpIconSize();
|
||||||
|
|
||||||
|
// Lerp the health icon back to its normal angle.
|
||||||
|
this.angle = MathUtil.coolLerp(this.angle, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updatePosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the calculation to lerp the icon size. Usually called every frame, but can be forced to the target size.
|
||||||
|
* Mainly forced when changing to old icon to not have a weird lerp related to changing from pixel icon to non-pixel old icon
|
||||||
|
* @param force Force the icon immedialtely to be the target size. Defaults to false.
|
||||||
|
*/
|
||||||
|
function lerpIconSize(force:Bool = false):Void
|
||||||
{
|
{
|
||||||
// Lerp the health icon back to its normal size,
|
// Lerp the health icon back to its normal size,
|
||||||
// while maintaining aspect ratio.
|
// while maintaining aspect ratio.
|
||||||
|
@ -207,22 +231,36 @@ class HealthIcon extends FunkinSprite
|
||||||
// Apply linear interpolation while accounting for frame rate.
|
// Apply linear interpolation while accounting for frame rate.
|
||||||
var targetSize:Int = Std.int(MathUtil.coolLerp(this.width, HEALTH_ICON_SIZE * this.size.x, 0.15));
|
var targetSize:Int = Std.int(MathUtil.coolLerp(this.width, HEALTH_ICON_SIZE * this.size.x, 0.15));
|
||||||
|
|
||||||
|
if (force) targetSize = Std.int(HEALTH_ICON_SIZE * this.size.x);
|
||||||
|
|
||||||
setGraphicSize(targetSize, 0);
|
setGraphicSize(targetSize, 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var targetSize:Int = Std.int(MathUtil.coolLerp(this.height, HEALTH_ICON_SIZE * this.size.y, 0.15));
|
var targetSize:Int = Std.int(MathUtil.coolLerp(this.height, HEALTH_ICON_SIZE * this.size.y, 0.15));
|
||||||
|
|
||||||
|
if (force) targetSize = Std.int(HEALTH_ICON_SIZE * this.size.y);
|
||||||
|
|
||||||
setGraphicSize(0, targetSize);
|
setGraphicSize(0, targetSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lerp the health icon back to its normal angle.
|
|
||||||
this.angle = MathUtil.coolLerp(this.angle, 0, 0.15);
|
|
||||||
|
|
||||||
this.updateHitbox();
|
this.updateHitbox();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updatePosition();
|
/*
|
||||||
|
* Immediately snap the health icon to its target size without lerping.
|
||||||
|
*/
|
||||||
|
public function snapToTargetSize():Void
|
||||||
|
{
|
||||||
|
if (this.width > this.height)
|
||||||
|
{
|
||||||
|
setGraphicSize(Std.int(HEALTH_ICON_SIZE * this.size.x), 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setGraphicSize(0, Std.int(HEALTH_ICON_SIZE * this.size.y));
|
||||||
|
}
|
||||||
|
updateHitbox();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -283,12 +321,6 @@ class HealthIcon extends FunkinSprite
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline function initTargetSize():Void
|
|
||||||
{
|
|
||||||
setGraphicSize(HEALTH_ICON_SIZE);
|
|
||||||
updateHitbox();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateHealthIcon(health:Float):Void
|
function updateHealthIcon(health:Float):Void
|
||||||
{
|
{
|
||||||
// We want to efficiently handle animation playback
|
// We want to efficiently handle animation playback
|
||||||
|
@ -380,20 +412,9 @@ class HealthIcon extends FunkinSprite
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function correctCharacterId(charId:Null<String>):String
|
function iconExists(charId:String):Bool
|
||||||
{
|
{
|
||||||
if (charId == null)
|
return Assets.exists(Paths.image('icons/icon-$charId'));
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isNewSpritesheet(charId:String):Bool
|
function isNewSpritesheet(charId:String):Bool
|
||||||
|
@ -403,15 +424,17 @@ class HealthIcon extends FunkinSprite
|
||||||
|
|
||||||
function loadCharacter(charId:Null<String>):Void
|
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.
|
FlxG.log.warn('No icon for character: $charId : using default placeholder face instead!');
|
||||||
characterId = correctCharacterId(charId);
|
characterId = Constants.DEFAULT_HEALTH_ICON;
|
||||||
return;
|
charId = characterId;
|
||||||
}
|
}
|
||||||
|
|
||||||
isLegacyStyle = !isNewSpritesheet(charId);
|
isLegacyStyle = !isNewSpritesheet(charId);
|
||||||
|
|
||||||
|
trace(' Loading health icon for character: $charId (legacy: $isLegacyStyle)');
|
||||||
|
|
||||||
if (!isLegacyStyle)
|
if (!isLegacyStyle)
|
||||||
{
|
{
|
||||||
loadSparrow('icons/icon-$charId');
|
loadSparrow('icons/icon-$charId');
|
||||||
|
|
|
@ -7,53 +7,60 @@ import flixel.util.FlxDirection;
|
||||||
import funkin.graphics.FunkinSprite;
|
import funkin.graphics.FunkinSprite;
|
||||||
import funkin.play.PlayState;
|
import funkin.play.PlayState;
|
||||||
import funkin.util.TimerUtil;
|
import funkin.util.TimerUtil;
|
||||||
|
import funkin.util.EaseUtil;
|
||||||
|
import openfl.utils.Assets;
|
||||||
|
import funkin.data.notestyle.NoteStyleRegistry;
|
||||||
|
import funkin.play.notes.notestyle.NoteStyle;
|
||||||
|
|
||||||
class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
@:nullSafety
|
||||||
|
class PopUpStuff extends FlxTypedGroup<FunkinSprite>
|
||||||
{
|
{
|
||||||
public var offsets:Array<Int> = [0, 0];
|
/**
|
||||||
|
* The current note style to use. This determines which graphics to display.
|
||||||
|
* For example, Week 6 uses the `pixel` note style, and mods can create their own.
|
||||||
|
*/
|
||||||
|
var noteStyle:NoteStyle;
|
||||||
|
|
||||||
override public function new()
|
/**
|
||||||
|
* Offsets that are applied to all elements, independent of the note style.
|
||||||
|
* Used to allow scripts to reposition the elements.
|
||||||
|
*/
|
||||||
|
var offsets:Array<Int> = [0, 0];
|
||||||
|
|
||||||
|
override public function new(noteStyle:NoteStyle)
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
this.noteStyle = noteStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function displayRating(daRating:String)
|
public function displayRating(daRating:Null<String>)
|
||||||
{
|
{
|
||||||
var perfStart:Float = TimerUtil.start();
|
|
||||||
|
|
||||||
if (daRating == null) daRating = "good";
|
if (daRating == null) daRating = "good";
|
||||||
|
|
||||||
var ratingPath:String = daRating;
|
var rating:Null<FunkinSprite> = noteStyle.buildJudgementSprite(daRating);
|
||||||
|
if (rating == null) return;
|
||||||
if (PlayState.instance.currentStageId.startsWith('school')) ratingPath = "weeb/pixelUI/" + ratingPath + "-pixel";
|
|
||||||
|
|
||||||
var rating:FunkinSprite = FunkinSprite.create(0, 0, ratingPath);
|
|
||||||
rating.scrollFactor.set(0.2, 0.2);
|
|
||||||
|
|
||||||
rating.zIndex = 1000;
|
rating.zIndex = 1000;
|
||||||
rating.x = (FlxG.width * 0.474) + offsets[0];
|
|
||||||
// rating.x -= FlxG.camera.scroll.x * 0.2;
|
rating.x = (FlxG.width * 0.474);
|
||||||
rating.y = (FlxG.camera.height * 0.45 - 60) + offsets[1];
|
rating.x -= rating.width / 2;
|
||||||
|
rating.y = (FlxG.camera.height * 0.45 - 60);
|
||||||
|
rating.y -= rating.height / 2;
|
||||||
|
|
||||||
|
rating.x += offsets[0];
|
||||||
|
rating.y += offsets[1];
|
||||||
|
var styleOffsets = noteStyle.getJudgementSpriteOffsets(daRating);
|
||||||
|
rating.x += styleOffsets[0];
|
||||||
|
rating.y += styleOffsets[1];
|
||||||
|
|
||||||
rating.acceleration.y = 550;
|
rating.acceleration.y = 550;
|
||||||
rating.velocity.y -= FlxG.random.int(140, 175);
|
rating.velocity.y -= FlxG.random.int(140, 175);
|
||||||
rating.velocity.x -= FlxG.random.int(0, 10);
|
rating.velocity.x -= FlxG.random.int(0, 10);
|
||||||
|
|
||||||
add(rating);
|
add(rating);
|
||||||
|
|
||||||
if (PlayState.instance.currentStageId.startsWith('school'))
|
var fadeEase = noteStyle.isJudgementSpritePixel(daRating) ? EaseUtil.stepped(2) : null;
|
||||||
{
|
|
||||||
rating.setGraphicSize(Std.int(rating.width * Constants.PIXEL_ART_SCALE * 0.7));
|
|
||||||
rating.antialiasing = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
rating.setGraphicSize(Std.int(rating.width * 0.65));
|
|
||||||
rating.antialiasing = true;
|
|
||||||
}
|
|
||||||
rating.updateHitbox();
|
|
||||||
|
|
||||||
rating.x -= rating.width / 2;
|
|
||||||
rating.y -= rating.height / 2;
|
|
||||||
|
|
||||||
FlxTween.tween(rating, {alpha: 0}, 0.2,
|
FlxTween.tween(rating, {alpha: 0}, 0.2,
|
||||||
{
|
{
|
||||||
|
@ -61,58 +68,13 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
||||||
remove(rating, true);
|
remove(rating, true);
|
||||||
rating.destroy();
|
rating.destroy();
|
||||||
},
|
},
|
||||||
startDelay: Conductor.instance.beatLengthMs * 0.001
|
startDelay: Conductor.instance.beatLengthMs * 0.001,
|
||||||
|
ease: fadeEase
|
||||||
});
|
});
|
||||||
|
|
||||||
trace('displayRating took: ${TimerUtil.seconds(perfStart)}');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function displayCombo(?combo:Int = 0):Int
|
public function displayCombo(combo:Int = 0):Void
|
||||||
{
|
{
|
||||||
var perfStart:Float = TimerUtil.start();
|
|
||||||
|
|
||||||
if (combo == null) combo = 0;
|
|
||||||
|
|
||||||
var pixelShitPart1:String = "";
|
|
||||||
var pixelShitPart2:String = '';
|
|
||||||
|
|
||||||
if (PlayState.instance.currentStageId.startsWith('school'))
|
|
||||||
{
|
|
||||||
pixelShitPart1 = 'weeb/pixelUI/';
|
|
||||||
pixelShitPart2 = '-pixel';
|
|
||||||
}
|
|
||||||
var comboSpr:FunkinSprite = FunkinSprite.create(pixelShitPart1 + 'combo' + pixelShitPart2);
|
|
||||||
comboSpr.y = (FlxG.camera.height * 0.44) + offsets[1];
|
|
||||||
comboSpr.x = (FlxG.width * 0.507) + offsets[0];
|
|
||||||
// comboSpr.x -= FlxG.camera.scroll.x * 0.2;
|
|
||||||
|
|
||||||
comboSpr.acceleration.y = 600;
|
|
||||||
comboSpr.velocity.y -= 150;
|
|
||||||
comboSpr.velocity.x += FlxG.random.int(1, 10);
|
|
||||||
|
|
||||||
// add(comboSpr);
|
|
||||||
|
|
||||||
if (PlayState.instance.currentStageId.startsWith('school'))
|
|
||||||
{
|
|
||||||
comboSpr.setGraphicSize(Std.int(comboSpr.width * Constants.PIXEL_ART_SCALE * 0.7));
|
|
||||||
comboSpr.antialiasing = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
comboSpr.setGraphicSize(Std.int(comboSpr.width * 0.7));
|
|
||||||
comboSpr.antialiasing = true;
|
|
||||||
}
|
|
||||||
comboSpr.updateHitbox();
|
|
||||||
|
|
||||||
FlxTween.tween(comboSpr, {alpha: 0}, 0.2,
|
|
||||||
{
|
|
||||||
onComplete: function(tween:FlxTween) {
|
|
||||||
remove(comboSpr, true);
|
|
||||||
comboSpr.destroy();
|
|
||||||
},
|
|
||||||
startDelay: Conductor.instance.beatLengthMs * 0.001
|
|
||||||
});
|
|
||||||
|
|
||||||
var seperatedScore:Array<Int> = [];
|
var seperatedScore:Array<Int> = [];
|
||||||
var tempCombo:Int = combo;
|
var tempCombo:Int = combo;
|
||||||
|
|
||||||
|
@ -127,43 +89,40 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
||||||
// seperatedScore.reverse();
|
// seperatedScore.reverse();
|
||||||
|
|
||||||
var daLoop:Int = 1;
|
var daLoop:Int = 1;
|
||||||
for (i in seperatedScore)
|
for (digit in seperatedScore)
|
||||||
{
|
{
|
||||||
var numScore:FunkinSprite = FunkinSprite.create(0, comboSpr.y, pixelShitPart1 + 'num' + Std.int(i) + pixelShitPart2);
|
var numScore:Null<FunkinSprite> = noteStyle.buildComboNumSprite(digit);
|
||||||
|
if (numScore == null) continue;
|
||||||
|
|
||||||
if (PlayState.instance.currentStageId.startsWith('school'))
|
numScore.x = (FlxG.width * 0.507) - (36 * daLoop) - 65;
|
||||||
{
|
trace('numScore($daLoop) = ${numScore.x}');
|
||||||
numScore.setGraphicSize(Std.int(numScore.width * Constants.PIXEL_ART_SCALE * 0.7));
|
numScore.y = (FlxG.camera.height * 0.44);
|
||||||
numScore.antialiasing = false;
|
|
||||||
}
|
numScore.x += offsets[0];
|
||||||
else
|
numScore.y += offsets[1];
|
||||||
{
|
var styleOffsets = noteStyle.getComboNumSpriteOffsets(digit);
|
||||||
numScore.setGraphicSize(Std.int(numScore.width * 0.45));
|
numScore.x += styleOffsets[0];
|
||||||
numScore.antialiasing = true;
|
numScore.y += styleOffsets[1];
|
||||||
}
|
|
||||||
numScore.updateHitbox();
|
|
||||||
|
|
||||||
numScore.x = comboSpr.x - (36 * daLoop) - 65; //- 90;
|
|
||||||
numScore.acceleration.y = FlxG.random.int(250, 300);
|
numScore.acceleration.y = FlxG.random.int(250, 300);
|
||||||
numScore.velocity.y -= FlxG.random.int(130, 150);
|
numScore.velocity.y -= FlxG.random.int(130, 150);
|
||||||
numScore.velocity.x = FlxG.random.float(-5, 5);
|
numScore.velocity.x = FlxG.random.float(-5, 5);
|
||||||
|
|
||||||
add(numScore);
|
add(numScore);
|
||||||
|
|
||||||
|
var fadeEase = noteStyle.isComboNumSpritePixel(digit) ? EaseUtil.stepped(2) : null;
|
||||||
|
|
||||||
FlxTween.tween(numScore, {alpha: 0}, 0.2,
|
FlxTween.tween(numScore, {alpha: 0}, 0.2,
|
||||||
{
|
{
|
||||||
onComplete: function(tween:FlxTween) {
|
onComplete: function(tween:FlxTween) {
|
||||||
remove(numScore, true);
|
remove(numScore, true);
|
||||||
numScore.destroy();
|
numScore.destroy();
|
||||||
},
|
},
|
||||||
startDelay: Conductor.instance.beatLengthMs * 0.002
|
startDelay: Conductor.instance.beatLengthMs * 0.002,
|
||||||
|
ease: fadeEase
|
||||||
});
|
});
|
||||||
|
|
||||||
daLoop++;
|
daLoop++;
|
||||||
}
|
}
|
||||||
|
|
||||||
trace('displayCombo took: ${TimerUtil.seconds(perfStart)}');
|
|
||||||
|
|
||||||
return combo;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,7 +81,6 @@ class VideoCutscene
|
||||||
// Trigger the cutscene. Don't play the song in the background.
|
// Trigger the cutscene. Don't play the song in the background.
|
||||||
PlayState.instance.isInCutscene = true;
|
PlayState.instance.isInCutscene = true;
|
||||||
PlayState.instance.camHUD.visible = false;
|
PlayState.instance.camHUD.visible = false;
|
||||||
PlayState.instance.camCutscene.visible = true;
|
|
||||||
|
|
||||||
// Display a black screen to hide the game while the video is playing.
|
// Display a black screen to hide the game while the video is playing.
|
||||||
blackScreen = new FlxSprite(-200, -200).makeGraphic(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK);
|
blackScreen = new FlxSprite(-200, -200).makeGraphic(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK);
|
||||||
|
@ -145,7 +144,7 @@ class VideoCutscene
|
||||||
{
|
{
|
||||||
vid.zIndex = 0;
|
vid.zIndex = 0;
|
||||||
vid.bitmap.onEndReached.add(finishVideo.bind(0.5));
|
vid.bitmap.onEndReached.add(finishVideo.bind(0.5));
|
||||||
vid.autoPause = false;
|
vid.autoPause = FlxG.autoPause;
|
||||||
|
|
||||||
vid.cameras = [PlayState.instance.camCutscene];
|
vid.cameras = [PlayState.instance.camCutscene];
|
||||||
|
|
||||||
|
@ -305,7 +304,6 @@ class VideoCutscene
|
||||||
vid = null;
|
vid = null;
|
||||||
#end
|
#end
|
||||||
|
|
||||||
PlayState.instance.camCutscene.visible = true;
|
|
||||||
PlayState.instance.camHUD.visible = true;
|
PlayState.instance.camHUD.visible = true;
|
||||||
|
|
||||||
FlxTween.tween(blackScreen, {alpha: 0}, transitionTime,
|
FlxTween.tween(blackScreen, {alpha: 0}, transitionTime,
|
||||||
|
|
|
@ -114,7 +114,7 @@ class ZoomCameraSongEvent extends SongEvent
|
||||||
name: 'zoom',
|
name: 'zoom',
|
||||||
title: 'Zoom Level',
|
title: 'Zoom Level',
|
||||||
defaultValue: 1.0,
|
defaultValue: 1.0,
|
||||||
step: 0.1,
|
step: 0.05,
|
||||||
type: SongEventFieldType.FLOAT,
|
type: SongEventFieldType.FLOAT,
|
||||||
units: 'x'
|
units: 'x'
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package funkin.play.notes;
|
package funkin.play.notes;
|
||||||
|
|
||||||
import funkin.data.song.SongData.SongNoteData;
|
import funkin.data.song.SongData.SongNoteData;
|
||||||
|
import funkin.data.song.SongData.NoteParamData;
|
||||||
import funkin.play.notes.notestyle.NoteStyle;
|
import funkin.play.notes.notestyle.NoteStyle;
|
||||||
import flixel.graphics.frames.FlxAtlasFrames;
|
import flixel.graphics.frames.FlxAtlasFrames;
|
||||||
import flixel.FlxSprite;
|
import flixel.FlxSprite;
|
||||||
|
@ -65,6 +66,22 @@ class NoteSprite extends FunkinSprite
|
||||||
return this.noteData.kind = value;
|
return this.noteData.kind = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array of custom parameters for this note
|
||||||
|
*/
|
||||||
|
public var params(get, set):Array<NoteParamData>;
|
||||||
|
|
||||||
|
function get_params():Array<NoteParamData>
|
||||||
|
{
|
||||||
|
return this.noteData?.params ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_params(value:Array<NoteParamData>):Array<NoteParamData>
|
||||||
|
{
|
||||||
|
if (this.noteData == null) return value;
|
||||||
|
return this.noteData.params = value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The data of the note (i.e. the direction.)
|
* The data of the note (i.e. the direction.)
|
||||||
*/
|
*/
|
||||||
|
@ -74,7 +91,7 @@ class NoteSprite extends FunkinSprite
|
||||||
{
|
{
|
||||||
if (frames == null) return value;
|
if (frames == null) return value;
|
||||||
|
|
||||||
animation.play(DIRECTION_COLORS[value] + 'Scroll');
|
playNoteAnimation(value);
|
||||||
|
|
||||||
this.direction = value;
|
this.direction = value;
|
||||||
return this.direction;
|
return this.direction;
|
||||||
|
@ -135,19 +152,37 @@ class NoteSprite extends FunkinSprite
|
||||||
this.hsvShader = new HSVShader();
|
this.hsvShader = new HSVShader();
|
||||||
|
|
||||||
setupNoteGraphic(noteStyle);
|
setupNoteGraphic(noteStyle);
|
||||||
|
|
||||||
// Disables the update() function for performance.
|
|
||||||
this.active = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupNoteGraphic(noteStyle:NoteStyle):Void
|
/**
|
||||||
|
* Creates frames and animations
|
||||||
|
* @param noteStyle The `NoteStyle` instance
|
||||||
|
*/
|
||||||
|
public function setupNoteGraphic(noteStyle:NoteStyle):Void
|
||||||
{
|
{
|
||||||
noteStyle.buildNoteSprite(this);
|
noteStyle.buildNoteSprite(this);
|
||||||
|
|
||||||
setGraphicSize(Strumline.STRUMLINE_SIZE);
|
|
||||||
updateHitbox();
|
|
||||||
|
|
||||||
this.shader = hsvShader;
|
this.shader = hsvShader;
|
||||||
|
|
||||||
|
// `false` disables the update() function for performance.
|
||||||
|
this.active = noteStyle.isNoteAnimated();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the value of the param with the given name
|
||||||
|
* @param name Name of the param
|
||||||
|
* @return Null<Dynamic>
|
||||||
|
*/
|
||||||
|
public function getParam(name:String):Null<Dynamic>
|
||||||
|
{
|
||||||
|
for (param in params)
|
||||||
|
{
|
||||||
|
if (param.name == name)
|
||||||
|
{
|
||||||
|
return param.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if FLX_DEBUG
|
#if FLX_DEBUG
|
||||||
|
@ -173,6 +208,11 @@ class NoteSprite extends FunkinSprite
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
|
||||||
|
function playNoteAnimation(value:Int):Void
|
||||||
|
{
|
||||||
|
animation.play(DIRECTION_COLORS[value] + 'Scroll');
|
||||||
|
}
|
||||||
|
|
||||||
public function desaturate():Void
|
public function desaturate():Void
|
||||||
{
|
{
|
||||||
this.hsvShader.saturation = 0.2;
|
this.hsvShader.saturation = 0.2;
|
||||||
|
|
|
@ -16,6 +16,7 @@ import funkin.data.song.SongData.SongNoteData;
|
||||||
import funkin.ui.options.PreferencesMenu;
|
import funkin.ui.options.PreferencesMenu;
|
||||||
import funkin.util.SortUtil;
|
import funkin.util.SortUtil;
|
||||||
import funkin.modding.events.ScriptEvent;
|
import funkin.modding.events.ScriptEvent;
|
||||||
|
import funkin.play.notes.notekind.NoteKindManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A group of sprites which handles the receptor, the note splashes, and the notes (with sustains) for a given player.
|
* A group of sprites which handles the receptor, the note splashes, and the notes (with sustains) for a given player.
|
||||||
|
@ -37,7 +38,7 @@ class Strumline extends FlxSpriteGroup
|
||||||
|
|
||||||
static function get_RENDER_DISTANCE_MS():Float
|
static function get_RENDER_DISTANCE_MS():Float
|
||||||
{
|
{
|
||||||
return FlxG.height / 0.45;
|
return FlxG.height / Constants.PIXELS_PER_MS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -93,6 +94,10 @@ class Strumline extends FlxSpriteGroup
|
||||||
|
|
||||||
final noteStyle:NoteStyle;
|
final noteStyle:NoteStyle;
|
||||||
|
|
||||||
|
#if FEATURE_GHOST_TAPPING
|
||||||
|
var ghostTapTimer:Float = 0.0;
|
||||||
|
#end
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The note data for the song. Should NOT be altered after the song starts,
|
* The note data for the song. Should NOT be altered after the song starts,
|
||||||
* so we can easily rewind.
|
* so we can easily rewind.
|
||||||
|
@ -178,21 +183,36 @@ class Strumline extends FlxSpriteGroup
|
||||||
super.update(elapsed);
|
super.update(elapsed);
|
||||||
|
|
||||||
updateNotes();
|
updateNotes();
|
||||||
|
|
||||||
|
#if FEATURE_GHOST_TAPPING
|
||||||
|
updateGhostTapTimer(elapsed);
|
||||||
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if FEATURE_GHOST_TAPPING
|
||||||
/**
|
/**
|
||||||
* Returns `true` if no notes are in range of the strumline and the player can spam without penalty.
|
* Returns `true` if no notes are in range of the strumline and the player can spam without penalty.
|
||||||
*/
|
*/
|
||||||
public function mayGhostTap():Bool
|
public function mayGhostTap():Bool
|
||||||
{
|
{
|
||||||
// TODO: Refine this. Only querying "can be hit" is too tight but "is being rendered" is too loose.
|
// Any notes in range of the strumline.
|
||||||
// Also, if you just hit a note, there should be a (short) period where this is off so you can't spam.
|
if (getNotesMayHit().length > 0)
|
||||||
|
{
|
||||||
// If there are any notes on screen, we can't ghost tap.
|
return false;
|
||||||
return notes.members.filter(function(note:NoteSprite) {
|
|
||||||
return note != null && note.alive && !note.hasBeenHit;
|
|
||||||
}).length == 0;
|
|
||||||
}
|
}
|
||||||
|
// Any hold notes in range of the strumline.
|
||||||
|
if (getHoldNotesHitOrMissed().length > 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note has been hit recently.
|
||||||
|
if (ghostTapTimer > 0.0) return false;
|
||||||
|
|
||||||
|
// **yippee**
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return notes that are within `Constants.HIT_WINDOW` ms of the strumline.
|
* Return notes that are within `Constants.HIT_WINDOW` ms of the strumline.
|
||||||
|
@ -491,6 +511,32 @@ class Strumline extends FlxSpriteGroup
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return notes that are within, or way after, `Constants.HIT_WINDOW` ms of the strumline.
|
||||||
|
* @return An array of `NoteSprite` objects.
|
||||||
|
*/
|
||||||
|
public function getNotesOnScreen():Array<NoteSprite>
|
||||||
|
{
|
||||||
|
return notes.members.filter(function(note:NoteSprite) {
|
||||||
|
return note != null && note.alive && !note.hasBeenHit;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#if FEATURE_GHOST_TAPPING
|
||||||
|
function updateGhostTapTimer(elapsed:Float):Void
|
||||||
|
{
|
||||||
|
// If it's still our turn, don't update the ghost tap timer.
|
||||||
|
if (getNotesOnScreen().length > 0) return;
|
||||||
|
|
||||||
|
ghostTapTimer -= elapsed;
|
||||||
|
|
||||||
|
if (ghostTapTimer <= 0)
|
||||||
|
{
|
||||||
|
ghostTapTimer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the PlayState skips a large amount of time forward or backward.
|
* Called when the PlayState skips a large amount of time forward or backward.
|
||||||
*/
|
*/
|
||||||
|
@ -562,6 +608,10 @@ class Strumline extends FlxSpriteGroup
|
||||||
playStatic(dir);
|
playStatic(dir);
|
||||||
}
|
}
|
||||||
resetScrollSpeed();
|
resetScrollSpeed();
|
||||||
|
|
||||||
|
#if FEATURE_GHOST_TAPPING
|
||||||
|
ghostTapTimer = 0;
|
||||||
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
public function applyNoteData(data:Array<SongNoteData>):Void
|
public function applyNoteData(data:Array<SongNoteData>):Void
|
||||||
|
@ -598,10 +648,13 @@ class Strumline extends FlxSpriteGroup
|
||||||
{
|
{
|
||||||
note.holdNoteSprite.hitNote = true;
|
note.holdNoteSprite.hitNote = true;
|
||||||
note.holdNoteSprite.missedNote = false;
|
note.holdNoteSprite.missedNote = false;
|
||||||
note.holdNoteSprite.alpha = 1.0;
|
|
||||||
|
|
||||||
note.holdNoteSprite.sustainLength = (note.holdNoteSprite.strumTime + note.holdNoteSprite.fullSustainLength) - conductorInUse.songPosition;
|
note.holdNoteSprite.sustainLength = (note.holdNoteSprite.strumTime + note.holdNoteSprite.fullSustainLength) - conductorInUse.songPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if FEATURE_GHOST_TAPPING
|
||||||
|
ghostTapTimer = Constants.GHOST_TAP_DELAY;
|
||||||
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
public function killNote(note:NoteSprite):Void
|
public function killNote(note:NoteSprite):Void
|
||||||
|
@ -709,11 +762,15 @@ class Strumline extends FlxSpriteGroup
|
||||||
|
|
||||||
if (noteSprite != null)
|
if (noteSprite != null)
|
||||||
{
|
{
|
||||||
|
var noteKindStyle:NoteStyle = NoteKindManager.getNoteStyle(note.kind, this.noteStyle.id) ?? this.noteStyle;
|
||||||
|
noteSprite.setupNoteGraphic(noteKindStyle);
|
||||||
|
|
||||||
noteSprite.direction = note.getDirection();
|
noteSprite.direction = note.getDirection();
|
||||||
noteSprite.noteData = note;
|
noteSprite.noteData = note;
|
||||||
|
|
||||||
noteSprite.x = this.x;
|
noteSprite.x = this.x;
|
||||||
noteSprite.x += getXPos(DIRECTIONS[note.getDirection() % KEY_COUNT]);
|
noteSprite.x += getXPos(DIRECTIONS[note.getDirection() % KEY_COUNT]);
|
||||||
|
noteSprite.x -= (noteSprite.width - Strumline.STRUMLINE_SIZE) / 2; // Center it
|
||||||
noteSprite.x -= NUDGE;
|
noteSprite.x -= NUDGE;
|
||||||
// noteSprite.x += INITIAL_OFFSET;
|
// noteSprite.x += INITIAL_OFFSET;
|
||||||
noteSprite.y = -9999;
|
noteSprite.y = -9999;
|
||||||
|
@ -728,6 +785,9 @@ class Strumline extends FlxSpriteGroup
|
||||||
|
|
||||||
if (holdNoteSprite != null)
|
if (holdNoteSprite != null)
|
||||||
{
|
{
|
||||||
|
var noteKindStyle:NoteStyle = NoteKindManager.getNoteStyle(note.kind, this.noteStyle.id) ?? this.noteStyle;
|
||||||
|
holdNoteSprite.setupHoldNoteGraphic(noteKindStyle);
|
||||||
|
|
||||||
holdNoteSprite.parentStrumline = this;
|
holdNoteSprite.parentStrumline = this;
|
||||||
holdNoteSprite.noteData = note;
|
holdNoteSprite.noteData = note;
|
||||||
holdNoteSprite.strumTime = note.time;
|
holdNoteSprite.strumTime = note.time;
|
||||||
|
|
|
@ -75,6 +75,13 @@ class StrumlineNote extends FlxSprite
|
||||||
|
|
||||||
function setup(noteStyle:NoteStyle):Void
|
function setup(noteStyle:NoteStyle):Void
|
||||||
{
|
{
|
||||||
|
if (noteStyle == null)
|
||||||
|
{
|
||||||
|
// If you get an exception on this line, check the debug console.
|
||||||
|
// You probably have a parsing error in your note style's JSON file.
|
||||||
|
throw "FATAL ERROR: Attempted to initialize PlayState with an invalid NoteStyle.";
|
||||||
|
}
|
||||||
|
|
||||||
noteStyle.applyStrumlineFrames(this);
|
noteStyle.applyStrumlineFrames(this);
|
||||||
noteStyle.applyStrumlineAnimations(this, this.direction);
|
noteStyle.applyStrumlineAnimations(this, this.direction);
|
||||||
|
|
||||||
|
|
|
@ -99,7 +99,27 @@ class SustainTrail extends FlxSprite
|
||||||
*/
|
*/
|
||||||
public function new(noteDirection:NoteDirection, sustainLength:Float, noteStyle:NoteStyle)
|
public function new(noteDirection:NoteDirection, sustainLength:Float, noteStyle:NoteStyle)
|
||||||
{
|
{
|
||||||
super(0, 0, noteStyle.getHoldNoteAssetPath());
|
super(0, 0);
|
||||||
|
|
||||||
|
// BASIC SETUP
|
||||||
|
this.sustainLength = sustainLength;
|
||||||
|
this.fullSustainLength = sustainLength;
|
||||||
|
this.noteDirection = noteDirection;
|
||||||
|
|
||||||
|
setupHoldNoteGraphic(noteStyle);
|
||||||
|
|
||||||
|
indices = new DrawData<Int>(12, true, TRIANGLE_VERTEX_INDICES);
|
||||||
|
|
||||||
|
this.active = true; // This NEEDS to be true for the note to be drawn!
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates hold note graphic and applies correct zooming
|
||||||
|
* @param noteStyle The note style
|
||||||
|
*/
|
||||||
|
public function setupHoldNoteGraphic(noteStyle:NoteStyle):Void
|
||||||
|
{
|
||||||
|
loadGraphic(noteStyle.getHoldNoteAssetPath());
|
||||||
|
|
||||||
antialiasing = true;
|
antialiasing = true;
|
||||||
|
|
||||||
|
@ -109,13 +129,14 @@ class SustainTrail extends FlxSprite
|
||||||
endOffset = bottomClip = 1;
|
endOffset = bottomClip = 1;
|
||||||
antialiasing = false;
|
antialiasing = false;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
endOffset = 0.5;
|
||||||
|
bottomClip = 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
zoom = 1.0;
|
||||||
zoom *= noteStyle.fetchHoldNoteScale();
|
zoom *= noteStyle.fetchHoldNoteScale();
|
||||||
|
|
||||||
// BASIC SETUP
|
|
||||||
this.sustainLength = sustainLength;
|
|
||||||
this.fullSustainLength = sustainLength;
|
|
||||||
this.noteDirection = noteDirection;
|
|
||||||
|
|
||||||
zoom *= 0.7;
|
zoom *= 0.7;
|
||||||
|
|
||||||
// CALCULATE SIZE
|
// CALCULATE SIZE
|
||||||
|
@ -131,9 +152,6 @@ class SustainTrail extends FlxSprite
|
||||||
updateColorTransform();
|
updateColorTransform();
|
||||||
|
|
||||||
updateClipping();
|
updateClipping();
|
||||||
indices = new DrawData<Int>(12, true, TRIANGLE_VERTEX_INDICES);
|
|
||||||
|
|
||||||
this.active = true; // This NEEDS to be true for the note to be drawn!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBaseScrollSpeed()
|
function getBaseScrollSpeed()
|
||||||
|
@ -160,7 +178,7 @@ class SustainTrail extends FlxSprite
|
||||||
*/
|
*/
|
||||||
public static inline function sustainHeight(susLength:Float, scroll:Float)
|
public static inline function sustainHeight(susLength:Float, scroll:Float)
|
||||||
{
|
{
|
||||||
return (susLength * 0.45 * scroll);
|
return (susLength * Constants.PIXELS_PER_MS * scroll);
|
||||||
}
|
}
|
||||||
|
|
||||||
function set_sustainLength(s:Float):Float
|
function set_sustainLength(s:Float):Float
|
||||||
|
@ -195,6 +213,11 @@ class SustainTrail extends FlxSprite
|
||||||
*/
|
*/
|
||||||
public function updateClipping(songTime:Float = 0):Void
|
public function updateClipping(songTime:Float = 0):Void
|
||||||
{
|
{
|
||||||
|
if (graphic == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var clipHeight:Float = FlxMath.bound(sustainHeight(sustainLength - (songTime - strumTime), parentStrumline?.scrollSpeed ?? 1.0), 0, graphicHeight);
|
var clipHeight:Float = FlxMath.bound(sustainHeight(sustainLength - (songTime - strumTime), parentStrumline?.scrollSpeed ?? 1.0), 0, graphicHeight);
|
||||||
if (clipHeight <= 0.1)
|
if (clipHeight <= 0.1)
|
||||||
{
|
{
|
||||||
|
|
119
source/funkin/play/notes/notekind/NoteKind.hx
Normal file
119
source/funkin/play/notes/notekind/NoteKind.hx
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
package funkin.play.notes.notekind;
|
||||||
|
|
||||||
|
import funkin.modding.IScriptedClass.INoteScriptedClass;
|
||||||
|
import funkin.modding.events.ScriptEvent;
|
||||||
|
import flixel.math.FlxMath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for note scripts
|
||||||
|
*/
|
||||||
|
class NoteKind implements INoteScriptedClass
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name of the note kind
|
||||||
|
*/
|
||||||
|
public var noteKind:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Description used in chart editor
|
||||||
|
*/
|
||||||
|
public var description:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom note style
|
||||||
|
*/
|
||||||
|
public var noteStyleId:Null<String>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom parameters for the chart editor
|
||||||
|
*/
|
||||||
|
public var params:Array<NoteKindParam>;
|
||||||
|
|
||||||
|
public function new(noteKind:String, description:String = "", ?noteStyleId:String, ?params:Array<NoteKindParam>)
|
||||||
|
{
|
||||||
|
this.noteKind = noteKind;
|
||||||
|
this.description = description;
|
||||||
|
this.noteStyleId = noteStyleId;
|
||||||
|
this.params = params ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString():String
|
||||||
|
{
|
||||||
|
return noteKind;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve all notes of this kind
|
||||||
|
* @return Array<NoteSprite>
|
||||||
|
*/
|
||||||
|
function getNotes():Array<NoteSprite>
|
||||||
|
{
|
||||||
|
var allNotes:Array<NoteSprite> = PlayState.instance.playerStrumline.notes.members.concat(PlayState.instance.opponentStrumline.notes.members);
|
||||||
|
return allNotes.filter(function(note:NoteSprite) {
|
||||||
|
return note != null && note.noteData.kind == this.noteKind;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onScriptEvent(event:ScriptEvent):Void {}
|
||||||
|
|
||||||
|
public function onCreate(event:ScriptEvent):Void {}
|
||||||
|
|
||||||
|
public function onDestroy(event:ScriptEvent):Void {}
|
||||||
|
|
||||||
|
public function onUpdate(event:UpdateScriptEvent):Void {}
|
||||||
|
|
||||||
|
public function onNoteIncoming(event:NoteScriptEvent):Void {}
|
||||||
|
|
||||||
|
public function onNoteHit(event:HitNoteScriptEvent):Void {}
|
||||||
|
|
||||||
|
public function onNoteMiss(event:NoteScriptEvent):Void {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract for setting the type of the `NoteKindParam`
|
||||||
|
* This was supposed to be an enum but polymod kept being annoying
|
||||||
|
*/
|
||||||
|
abstract NoteKindParamType(String) from String to String
|
||||||
|
{
|
||||||
|
public static final STRING:String = 'String';
|
||||||
|
|
||||||
|
public static final INT:String = 'Int';
|
||||||
|
|
||||||
|
public static final FLOAT:String = 'Float';
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef NoteKindParamData =
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* If `min` is null, there is no minimum
|
||||||
|
*/
|
||||||
|
?min:Null<Float>,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If `max` is null, there is no maximum
|
||||||
|
*/
|
||||||
|
?max:Null<Float>,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If `step` is null, it will use 1.0
|
||||||
|
*/
|
||||||
|
?step:Null<Float>,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If `precision` is null, there will be 0 decimal places
|
||||||
|
*/
|
||||||
|
?precision:Null<Int>,
|
||||||
|
|
||||||
|
?defaultValue:Dynamic
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Typedef for creating custom parameters in the chart editor
|
||||||
|
*/
|
||||||
|
typedef NoteKindParam =
|
||||||
|
{
|
||||||
|
name:String,
|
||||||
|
description:String,
|
||||||
|
type:NoteKindParamType,
|
||||||
|
?data:NoteKindParamData
|
||||||
|
}
|
121
source/funkin/play/notes/notekind/NoteKindManager.hx
Normal file
121
source/funkin/play/notes/notekind/NoteKindManager.hx
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
package funkin.play.notes.notekind;
|
||||||
|
|
||||||
|
import funkin.modding.events.ScriptEventDispatcher;
|
||||||
|
import funkin.modding.events.ScriptEvent;
|
||||||
|
import funkin.ui.debug.charting.util.ChartEditorDropdowns;
|
||||||
|
import funkin.data.notestyle.NoteStyleRegistry;
|
||||||
|
import funkin.play.notes.notestyle.NoteStyle;
|
||||||
|
import funkin.play.notes.notekind.ScriptedNoteKind;
|
||||||
|
import funkin.play.notes.notekind.NoteKind.NoteKindParam;
|
||||||
|
|
||||||
|
class NoteKindManager
|
||||||
|
{
|
||||||
|
static var noteKinds:Map<String, NoteKind> = [];
|
||||||
|
|
||||||
|
public static function loadScripts():Void
|
||||||
|
{
|
||||||
|
var scriptedClassName:Array<String> = ScriptedNoteKind.listScriptClasses();
|
||||||
|
if (scriptedClassName.length > 0)
|
||||||
|
{
|
||||||
|
trace('Instantiating ${scriptedClassName.length} scripted note kind(s)...');
|
||||||
|
for (scriptedClass in scriptedClassName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var script:NoteKind = ScriptedNoteKind.init(scriptedClass, "unknown");
|
||||||
|
trace(' Initialized scripted note kind: ${script.noteKind}');
|
||||||
|
noteKinds.set(script.noteKind, script);
|
||||||
|
ChartEditorDropdowns.NOTE_KINDS.set(script.noteKind, script.description);
|
||||||
|
}
|
||||||
|
catch (e)
|
||||||
|
{
|
||||||
|
trace(' FAILED to instantiate scripted note kind: ${scriptedClass}');
|
||||||
|
trace(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls the given event for note kind scripts
|
||||||
|
* @param event The event
|
||||||
|
*/
|
||||||
|
public static function callEvent(event:ScriptEvent):Void
|
||||||
|
{
|
||||||
|
// if it is a note script event,
|
||||||
|
// then only call the event for the specific note kind script
|
||||||
|
if (Std.isOfType(event, NoteScriptEvent))
|
||||||
|
{
|
||||||
|
var noteEvent:NoteScriptEvent = cast(event, NoteScriptEvent);
|
||||||
|
|
||||||
|
var noteKind:NoteKind = noteKinds.get(noteEvent.note.kind);
|
||||||
|
|
||||||
|
if (noteKind != null)
|
||||||
|
{
|
||||||
|
ScriptEventDispatcher.callEvent(noteKind, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // call the event for all note kind scripts
|
||||||
|
{
|
||||||
|
for (noteKind in noteKinds.iterator())
|
||||||
|
{
|
||||||
|
ScriptEventDispatcher.callEvent(noteKind, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the note style from the given note kind
|
||||||
|
* @param noteKind note kind name
|
||||||
|
* @param suffix Used for song note styles
|
||||||
|
* @return NoteStyle
|
||||||
|
*/
|
||||||
|
public static function getNoteStyle(noteKind:String, ?suffix:String):Null<NoteStyle>
|
||||||
|
{
|
||||||
|
var noteStyleId:Null<String> = getNoteStyleId(noteKind, suffix);
|
||||||
|
|
||||||
|
if (noteStyleId == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NoteStyleRegistry.instance.fetchEntry(noteStyleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the note style id from the given note kind
|
||||||
|
* @param noteKind Note kind name
|
||||||
|
* @param suffix Used for song note styles
|
||||||
|
* @return Null<String>
|
||||||
|
*/
|
||||||
|
public static function getNoteStyleId(noteKind:String, ?suffix:String):Null<String>
|
||||||
|
{
|
||||||
|
if (suffix == '')
|
||||||
|
{
|
||||||
|
suffix = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var noteStyleId:Null<String> = noteKinds.get(noteKind)?.noteStyleId;
|
||||||
|
if (noteStyleId != null && suffix != null)
|
||||||
|
{
|
||||||
|
noteStyleId = NoteStyleRegistry.instance.hasEntry('$noteStyleId-$suffix') ? '$noteStyleId-$suffix' : noteStyleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return noteStyleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrive custom params of the given note kind
|
||||||
|
* @param noteKind Name of the note kind
|
||||||
|
* @return Array<NoteKindParam>
|
||||||
|
*/
|
||||||
|
public static function getParams(noteKind:Null<String>):Array<NoteKindParam>
|
||||||
|
{
|
||||||
|
if (noteKind == null)
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return noteKinds.get(noteKind)?.params ?? [];
|
||||||
|
}
|
||||||
|
}
|
9
source/funkin/play/notes/notekind/ScriptedNoteKind.hx
Normal file
9
source/funkin/play/notes/notekind/ScriptedNoteKind.hx
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package funkin.play.notes.notekind;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A script that can be tied to a NoteKind.
|
||||||
|
* Create a scripted class that extends NoteKind,
|
||||||
|
* then call `super('noteKind')` in the constructor to use this.
|
||||||
|
*/
|
||||||
|
@:hscriptClass
|
||||||
|
class ScriptedNoteKind extends NoteKind implements polymod.hscript.HScriptedClass {}
|
|
@ -1,5 +1,6 @@
|
||||||
package funkin.play.notes.notestyle;
|
package funkin.play.notes.notestyle;
|
||||||
|
|
||||||
|
import funkin.play.Countdown;
|
||||||
import flixel.graphics.frames.FlxAtlasFrames;
|
import flixel.graphics.frames.FlxAtlasFrames;
|
||||||
import flixel.graphics.frames.FlxFramesCollection;
|
import flixel.graphics.frames.FlxFramesCollection;
|
||||||
import funkin.data.animation.AnimationData;
|
import funkin.data.animation.AnimationData;
|
||||||
|
@ -16,6 +17,7 @@ using funkin.data.animation.AnimationData.AnimationDataUtil;
|
||||||
* Holds the data for what assets to use for a note style,
|
* Holds the data for what assets to use for a note style,
|
||||||
* and provides convenience methods for building sprites based on them.
|
* and provides convenience methods for building sprites based on them.
|
||||||
*/
|
*/
|
||||||
|
@:nullSafety
|
||||||
class NoteStyle implements IRegistryEntry<NoteStyleData>
|
class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
@ -42,12 +44,8 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||||
this.id = id;
|
this.id = id;
|
||||||
_data = _fetchData(id);
|
_data = _fetchData(id);
|
||||||
|
|
||||||
if (_data == null)
|
var fallbackID = _data.fallback;
|
||||||
{
|
if (fallbackID != null) this.fallback = NoteStyleRegistry.instance.fetchEntry(fallbackID);
|
||||||
throw 'Could not parse note style data for id: $id';
|
|
||||||
}
|
|
||||||
|
|
||||||
this.fallback = NoteStyleRegistry.instance.fetchEntry(getFallbackID());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -72,7 +70,7 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||||
* Get the note style ID of the parent note style.
|
* Get the note style ID of the parent note style.
|
||||||
* @return The string ID, or `null` if there is no parent.
|
* @return The string ID, or `null` if there is no parent.
|
||||||
*/
|
*/
|
||||||
function getFallbackID():Null<String>
|
public function getFallbackID():Null<String>
|
||||||
{
|
{
|
||||||
return _data.fallback;
|
return _data.fallback;
|
||||||
}
|
}
|
||||||
|
@ -80,7 +78,7 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||||
public function buildNoteSprite(target:NoteSprite):Void
|
public function buildNoteSprite(target:NoteSprite):Void
|
||||||
{
|
{
|
||||||
// Apply the note sprite frames.
|
// Apply the note sprite frames.
|
||||||
var atlas:FlxAtlasFrames = buildNoteFrames(false);
|
var atlas:Null<FlxAtlasFrames> = buildNoteFrames(false);
|
||||||
|
|
||||||
if (atlas == null)
|
if (atlas == null)
|
||||||
{
|
{
|
||||||
|
@ -89,29 +87,40 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||||
|
|
||||||
target.frames = atlas;
|
target.frames = atlas;
|
||||||
|
|
||||||
target.scale.x = _data.assets.note.scale;
|
target.antialiasing = !(_data.assets?.note?.isPixel ?? false);
|
||||||
target.scale.y = _data.assets.note.scale;
|
|
||||||
target.antialiasing = !_data.assets.note.isPixel;
|
|
||||||
|
|
||||||
// Apply the animations.
|
// Apply the animations.
|
||||||
buildNoteAnimations(target);
|
buildNoteAnimations(target);
|
||||||
|
|
||||||
|
// Set the scale.
|
||||||
|
target.setGraphicSize(Strumline.STRUMLINE_SIZE * getNoteScale());
|
||||||
|
target.updateHitbox();
|
||||||
}
|
}
|
||||||
|
|
||||||
var noteFrames:FlxAtlasFrames = null;
|
var noteFrames:Null<FlxAtlasFrames> = null;
|
||||||
|
|
||||||
function buildNoteFrames(force:Bool = false):FlxAtlasFrames
|
function buildNoteFrames(force:Bool = false):Null<FlxAtlasFrames>
|
||||||
{
|
{
|
||||||
if (!FunkinSprite.isTextureCached(Paths.image(getNoteAssetPath())))
|
var noteAssetPath = getNoteAssetPath();
|
||||||
|
if (noteAssetPath == null) return null;
|
||||||
|
|
||||||
|
if (!FunkinSprite.isTextureCached(Paths.image(noteAssetPath)))
|
||||||
{
|
{
|
||||||
FlxG.log.warn('Note texture is not cached: ${getNoteAssetPath()}');
|
FlxG.log.warn('Note texture is not cached: ${noteAssetPath}');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Purge the note frames if the cached atlas is invalid.
|
// Purge the note frames if the cached atlas is invalid.
|
||||||
|
@:nullSafety(Off)
|
||||||
|
{
|
||||||
if (noteFrames?.parent?.isDestroyed ?? false) noteFrames = null;
|
if (noteFrames?.parent?.isDestroyed ?? false) noteFrames = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (noteFrames != null && !force) return noteFrames;
|
if (noteFrames != null && !force) return noteFrames;
|
||||||
|
|
||||||
noteFrames = Paths.getSparrowAtlas(getNoteAssetPath(), getNoteAssetLibrary());
|
var noteAssetPath = getNoteAssetPath();
|
||||||
|
if (noteAssetPath == null) return null;
|
||||||
|
|
||||||
|
noteFrames = Paths.getSparrowAtlas(noteAssetPath, getNoteAssetLibrary());
|
||||||
|
|
||||||
if (noteFrames == null)
|
if (noteFrames == null)
|
||||||
{
|
{
|
||||||
|
@ -121,17 +130,18 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||||
return noteFrames;
|
return noteFrames;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNoteAssetPath(raw:Bool = false):String
|
function getNoteAssetPath(raw:Bool = false):Null<String>
|
||||||
{
|
{
|
||||||
if (raw)
|
if (raw)
|
||||||
{
|
{
|
||||||
var rawPath:Null<String> = _data?.assets?.note?.assetPath;
|
var rawPath:Null<String> = _data?.assets?.note?.assetPath;
|
||||||
if (rawPath == null) return fallback.getNoteAssetPath(true);
|
if (rawPath == null && fallback != null) return fallback.getNoteAssetPath(true);
|
||||||
return rawPath;
|
return rawPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
// library:path
|
// library:path
|
||||||
var parts = getNoteAssetPath(true).split(Constants.LIBRARY_SEPARATOR);
|
var parts = getNoteAssetPath(true)?.split(Constants.LIBRARY_SEPARATOR) ?? [];
|
||||||
|
if (parts.length == 0) return null;
|
||||||
if (parts.length == 1) return getNoteAssetPath(true);
|
if (parts.length == 1) return getNoteAssetPath(true);
|
||||||
return parts[1];
|
return parts[1];
|
||||||
}
|
}
|
||||||
|
@ -139,47 +149,63 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||||
function getNoteAssetLibrary():Null<String>
|
function getNoteAssetLibrary():Null<String>
|
||||||
{
|
{
|
||||||
// library:path
|
// library:path
|
||||||
var parts = getNoteAssetPath(true).split(Constants.LIBRARY_SEPARATOR);
|
var parts = getNoteAssetPath(true)?.split(Constants.LIBRARY_SEPARATOR) ?? [];
|
||||||
|
if (parts.length == 0) return null;
|
||||||
if (parts.length == 1) return null;
|
if (parts.length == 1) return null;
|
||||||
return parts[0];
|
return parts[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildNoteAnimations(target:NoteSprite):Void
|
function buildNoteAnimations(target:NoteSprite):Void
|
||||||
{
|
{
|
||||||
var leftData:AnimationData = fetchNoteAnimationData(LEFT);
|
var leftData:Null<AnimationData> = fetchNoteAnimationData(LEFT);
|
||||||
target.animation.addByPrefix('purpleScroll', leftData.prefix, leftData.frameRate, leftData.looped, leftData.flipX, leftData.flipY);
|
if (leftData != null) target.animation.addByPrefix('purpleScroll', leftData.prefix ?? '', leftData.frameRate ?? 24, leftData.looped ?? false,
|
||||||
var downData:AnimationData = fetchNoteAnimationData(DOWN);
|
leftData.flipX, leftData.flipY);
|
||||||
target.animation.addByPrefix('blueScroll', downData.prefix, downData.frameRate, downData.looped, downData.flipX, downData.flipY);
|
var downData:Null<AnimationData> = fetchNoteAnimationData(DOWN);
|
||||||
var upData:AnimationData = fetchNoteAnimationData(UP);
|
if (downData != null) target.animation.addByPrefix('blueScroll', downData.prefix ?? '', downData.frameRate ?? 24, downData.looped ?? false,
|
||||||
target.animation.addByPrefix('greenScroll', upData.prefix, upData.frameRate, upData.looped, upData.flipX, upData.flipY);
|
downData.flipX, downData.flipY);
|
||||||
var rightData:AnimationData = fetchNoteAnimationData(RIGHT);
|
var upData:Null<AnimationData> = fetchNoteAnimationData(UP);
|
||||||
target.animation.addByPrefix('redScroll', rightData.prefix, rightData.frameRate, rightData.looped, rightData.flipX, rightData.flipY);
|
if (upData != null) target.animation.addByPrefix('greenScroll', upData.prefix ?? '', upData.frameRate ?? 24, upData.looped ?? false, upData.flipX,
|
||||||
|
upData.flipY);
|
||||||
|
var rightData:Null<AnimationData> = fetchNoteAnimationData(RIGHT);
|
||||||
|
if (rightData != null) target.animation.addByPrefix('redScroll', rightData.prefix ?? '', rightData.frameRate ?? 24, rightData.looped ?? false,
|
||||||
|
rightData.flipX, rightData.flipY);
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetchNoteAnimationData(dir:NoteDirection):AnimationData
|
public function isNoteAnimated():Bool
|
||||||
|
{
|
||||||
|
return _data.assets?.note?.animated ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNoteScale():Float
|
||||||
|
{
|
||||||
|
return _data.assets?.note?.scale ?? 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchNoteAnimationData(dir:NoteDirection):Null<AnimationData>
|
||||||
{
|
{
|
||||||
var result:Null<AnimationData> = switch (dir)
|
var result:Null<AnimationData> = switch (dir)
|
||||||
{
|
{
|
||||||
case LEFT: _data.assets.note.data.left.toNamed();
|
case LEFT: _data.assets?.note?.data?.left?.toNamed();
|
||||||
case DOWN: _data.assets.note.data.down.toNamed();
|
case DOWN: _data.assets?.note?.data?.down?.toNamed();
|
||||||
case UP: _data.assets.note.data.up.toNamed();
|
case UP: _data.assets?.note?.data?.up?.toNamed();
|
||||||
case RIGHT: _data.assets.note.data.right.toNamed();
|
case RIGHT: _data.assets?.note?.data?.right?.toNamed();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (result == null) ? fallback.fetchNoteAnimationData(dir) : result;
|
return (result == null && fallback != null) ? fallback.fetchNoteAnimationData(dir) : result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getHoldNoteAssetPath(raw:Bool = false):String
|
public function getHoldNoteAssetPath(raw:Bool = false):Null<String>
|
||||||
{
|
{
|
||||||
if (raw)
|
if (raw)
|
||||||
{
|
{
|
||||||
// TODO: figure out why ?. didn't work here
|
// TODO: figure out why ?. didn't work here
|
||||||
var rawPath:Null<String> = (_data?.assets?.holdNote == null) ? null : _data?.assets?.holdNote?.assetPath;
|
var rawPath:Null<String> = (_data?.assets?.holdNote == null) ? null : _data?.assets?.holdNote?.assetPath;
|
||||||
return (rawPath == null) ? fallback.getHoldNoteAssetPath(true) : rawPath;
|
return (rawPath == null && fallback != null) ? fallback.getHoldNoteAssetPath(true) : rawPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
// library:path
|
// library:path
|
||||||
var parts = getHoldNoteAssetPath(true).split(Constants.LIBRARY_SEPARATOR);
|
var parts = getHoldNoteAssetPath(true)?.split(Constants.LIBRARY_SEPARATOR) ?? [];
|
||||||
|
if (parts.length == 0) return null;
|
||||||
if (parts.length == 1) return Paths.image(parts[0]);
|
if (parts.length == 1) return Paths.image(parts[0]);
|
||||||
return Paths.image(parts[1], parts[0]);
|
return Paths.image(parts[1], parts[0]);
|
||||||
}
|
}
|
||||||
|
@ -187,15 +213,15 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||||
public function isHoldNotePixel():Bool
|
public function isHoldNotePixel():Bool
|
||||||
{
|
{
|
||||||
var data = _data?.assets?.holdNote;
|
var data = _data?.assets?.holdNote;
|
||||||
if (data == null) return fallback.isHoldNotePixel();
|
if (data == null && fallback != null) return fallback.isHoldNotePixel();
|
||||||
return data.isPixel;
|
return data?.isPixel ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function fetchHoldNoteScale():Float
|
public function fetchHoldNoteScale():Float
|
||||||
{
|
{
|
||||||
var data = _data?.assets?.holdNote;
|
var data = _data?.assets?.holdNote;
|
||||||
if (data == null) return fallback.fetchHoldNoteScale();
|
if (data == null && fallback != null) return fallback.fetchHoldNoteScale();
|
||||||
return data.scale;
|
return data?.scale ?? 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function applyStrumlineFrames(target:StrumlineNote):Void
|
public function applyStrumlineFrames(target:StrumlineNote):Void
|
||||||
|
@ -203,7 +229,7 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||||
// TODO: Add support for multi-Sparrow.
|
// TODO: Add support for multi-Sparrow.
|
||||||
// Will be less annoying after this is merged: https://github.com/HaxeFlixel/flixel/pull/2772
|
// Will be less annoying after this is merged: https://github.com/HaxeFlixel/flixel/pull/2772
|
||||||
|
|
||||||
var atlas:FlxAtlasFrames = Paths.getSparrowAtlas(getStrumlineAssetPath(), getStrumlineAssetLibrary());
|
var atlas:FlxAtlasFrames = Paths.getSparrowAtlas(getStrumlineAssetPath() ?? '', getStrumlineAssetLibrary());
|
||||||
|
|
||||||
if (atlas == null)
|
if (atlas == null)
|
||||||
{
|
{
|
||||||
|
@ -212,31 +238,30 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||||
|
|
||||||
target.frames = atlas;
|
target.frames = atlas;
|
||||||
|
|
||||||
target.scale.x = _data.assets.noteStrumline.scale;
|
target.scale.set(_data.assets.noteStrumline?.scale ?? 1.0);
|
||||||
target.scale.y = _data.assets.noteStrumline.scale;
|
target.antialiasing = !(_data.assets.noteStrumline?.isPixel ?? false);
|
||||||
target.antialiasing = !_data.assets.noteStrumline.isPixel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStrumlineAssetPath(raw:Bool = false):String
|
function getStrumlineAssetPath(raw:Bool = false):Null<String>
|
||||||
{
|
{
|
||||||
if (raw)
|
if (raw)
|
||||||
{
|
{
|
||||||
var rawPath:Null<String> = _data?.assets?.noteStrumline?.assetPath;
|
var rawPath:Null<String> = _data?.assets?.noteStrumline?.assetPath;
|
||||||
if (rawPath == null) return fallback.getStrumlineAssetPath(true);
|
if (rawPath == null && fallback != null) return fallback.getStrumlineAssetPath(true);
|
||||||
return rawPath;
|
return rawPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
// library:path
|
// library:path
|
||||||
var parts = getStrumlineAssetPath(true).split(Constants.LIBRARY_SEPARATOR);
|
var parts = getStrumlineAssetPath(true)?.split(Constants.LIBRARY_SEPARATOR) ?? [];
|
||||||
if (parts.length == 1) return getStrumlineAssetPath(true);
|
if (parts.length <= 1) return getStrumlineAssetPath(true);
|
||||||
return parts[1];
|
return parts[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStrumlineAssetLibrary():Null<String>
|
function getStrumlineAssetLibrary():Null<String>
|
||||||
{
|
{
|
||||||
// library:path
|
// library:path
|
||||||
var parts = getStrumlineAssetPath(true).split(Constants.LIBRARY_SEPARATOR);
|
var parts = getStrumlineAssetPath(true)?.split(Constants.LIBRARY_SEPARATOR) ?? [];
|
||||||
if (parts.length == 1) return null;
|
if (parts.length <= 1) return null;
|
||||||
return parts[0];
|
return parts[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,60 +272,592 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||||
|
|
||||||
function getStrumlineAnimationData(dir:NoteDirection):Array<AnimationData>
|
function getStrumlineAnimationData(dir:NoteDirection):Array<AnimationData>
|
||||||
{
|
{
|
||||||
var result:Array<AnimationData> = switch (dir)
|
var result:Array<Null<AnimationData>> = switch (dir)
|
||||||
{
|
{
|
||||||
case NoteDirection.LEFT: [
|
case NoteDirection.LEFT: [
|
||||||
_data.assets.noteStrumline.data.leftStatic.toNamed('static'),
|
_data.assets.noteStrumline?.data?.leftStatic?.toNamed('static'),
|
||||||
_data.assets.noteStrumline.data.leftPress.toNamed('press'),
|
_data.assets.noteStrumline?.data?.leftPress?.toNamed('press'),
|
||||||
_data.assets.noteStrumline.data.leftConfirm.toNamed('confirm'),
|
_data.assets.noteStrumline?.data?.leftConfirm?.toNamed('confirm'),
|
||||||
_data.assets.noteStrumline.data.leftConfirmHold.toNamed('confirm-hold'),
|
_data.assets.noteStrumline?.data?.leftConfirmHold?.toNamed('confirm-hold'),
|
||||||
];
|
];
|
||||||
case NoteDirection.DOWN: [
|
case NoteDirection.DOWN: [
|
||||||
_data.assets.noteStrumline.data.downStatic.toNamed('static'),
|
_data.assets.noteStrumline?.data?.downStatic?.toNamed('static'),
|
||||||
_data.assets.noteStrumline.data.downPress.toNamed('press'),
|
_data.assets.noteStrumline?.data?.downPress?.toNamed('press'),
|
||||||
_data.assets.noteStrumline.data.downConfirm.toNamed('confirm'),
|
_data.assets.noteStrumline?.data?.downConfirm?.toNamed('confirm'),
|
||||||
_data.assets.noteStrumline.data.downConfirmHold.toNamed('confirm-hold'),
|
_data.assets.noteStrumline?.data?.downConfirmHold?.toNamed('confirm-hold'),
|
||||||
];
|
];
|
||||||
case NoteDirection.UP: [
|
case NoteDirection.UP: [
|
||||||
_data.assets.noteStrumline.data.upStatic.toNamed('static'),
|
_data.assets.noteStrumline?.data?.upStatic?.toNamed('static'),
|
||||||
_data.assets.noteStrumline.data.upPress.toNamed('press'),
|
_data.assets.noteStrumline?.data?.upPress?.toNamed('press'),
|
||||||
_data.assets.noteStrumline.data.upConfirm.toNamed('confirm'),
|
_data.assets.noteStrumline?.data?.upConfirm?.toNamed('confirm'),
|
||||||
_data.assets.noteStrumline.data.upConfirmHold.toNamed('confirm-hold'),
|
_data.assets.noteStrumline?.data?.upConfirmHold?.toNamed('confirm-hold'),
|
||||||
];
|
];
|
||||||
case NoteDirection.RIGHT: [
|
case NoteDirection.RIGHT: [
|
||||||
_data.assets.noteStrumline.data.rightStatic.toNamed('static'),
|
_data.assets.noteStrumline?.data?.rightStatic?.toNamed('static'),
|
||||||
_data.assets.noteStrumline.data.rightPress.toNamed('press'),
|
_data.assets.noteStrumline?.data?.rightPress?.toNamed('press'),
|
||||||
_data.assets.noteStrumline.data.rightConfirm.toNamed('confirm'),
|
_data.assets.noteStrumline?.data?.rightConfirm?.toNamed('confirm'),
|
||||||
_data.assets.noteStrumline.data.rightConfirmHold.toNamed('confirm-hold'),
|
_data.assets.noteStrumline?.data?.rightConfirmHold?.toNamed('confirm-hold'),
|
||||||
];
|
];
|
||||||
|
default: [];
|
||||||
};
|
};
|
||||||
|
|
||||||
return result;
|
return thx.Arrays.filterNull(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function applyStrumlineOffsets(target:StrumlineNote)
|
public function applyStrumlineOffsets(target:StrumlineNote):Void
|
||||||
{
|
{
|
||||||
target.x += _data.assets.noteStrumline.offsets[0];
|
var offsets = _data?.assets?.noteStrumline?.offsets ?? [0.0, 0.0];
|
||||||
target.y += _data.assets.noteStrumline.offsets[1];
|
target.x += offsets[0];
|
||||||
|
target.y += offsets[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getStrumlineScale():Float
|
public function getStrumlineScale():Float
|
||||||
{
|
{
|
||||||
return _data.assets.noteStrumline.scale;
|
return _data?.assets?.noteStrumline?.scale ?? 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isNoteSplashEnabled():Bool
|
public function isNoteSplashEnabled():Bool
|
||||||
{
|
{
|
||||||
var data = _data?.assets?.noteSplash?.data;
|
var data = _data?.assets?.noteSplash?.data;
|
||||||
if (data == null) return fallback.isNoteSplashEnabled();
|
if (data == null) return fallback?.isNoteSplashEnabled() ?? false;
|
||||||
return data.enabled;
|
return data.enabled ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isHoldNoteCoverEnabled():Bool
|
public function isHoldNoteCoverEnabled():Bool
|
||||||
{
|
{
|
||||||
var data = _data?.assets?.holdNoteCover?.data;
|
var data = _data?.assets?.holdNoteCover?.data;
|
||||||
if (data == null) return fallback.isHoldNoteCoverEnabled();
|
if (data == null) return fallback?.isHoldNoteCoverEnabled() ?? false;
|
||||||
return data.enabled;
|
return data.enabled ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a sprite for the given step of the countdown.
|
||||||
|
* @param step
|
||||||
|
* @return A `FunkinSprite`, or `null` if no graphic is available for this step.
|
||||||
|
*/
|
||||||
|
public function buildCountdownSprite(step:Countdown.CountdownStep):Null<FunkinSprite>
|
||||||
|
{
|
||||||
|
var result = new FunkinSprite();
|
||||||
|
|
||||||
|
switch (step)
|
||||||
|
{
|
||||||
|
case THREE:
|
||||||
|
if (_data.assets.countdownThree == null) return fallback?.buildCountdownSprite(step);
|
||||||
|
var assetPath = buildCountdownSpritePath(step);
|
||||||
|
if (assetPath == null) return null;
|
||||||
|
result.loadTexture(assetPath);
|
||||||
|
result.scale.x = _data.assets.countdownThree?.scale ?? 1.0;
|
||||||
|
result.scale.y = _data.assets.countdownThree?.scale ?? 1.0;
|
||||||
|
case TWO:
|
||||||
|
if (_data.assets.countdownTwo == null) return fallback?.buildCountdownSprite(step);
|
||||||
|
var assetPath = buildCountdownSpritePath(step);
|
||||||
|
if (assetPath == null) return null;
|
||||||
|
result.loadTexture(assetPath);
|
||||||
|
result.scale.x = _data.assets.countdownTwo?.scale ?? 1.0;
|
||||||
|
result.scale.y = _data.assets.countdownTwo?.scale ?? 1.0;
|
||||||
|
case ONE:
|
||||||
|
if (_data.assets.countdownOne == null) return fallback?.buildCountdownSprite(step);
|
||||||
|
var assetPath = buildCountdownSpritePath(step);
|
||||||
|
if (assetPath == null) return null;
|
||||||
|
result.loadTexture(assetPath);
|
||||||
|
result.scale.x = _data.assets.countdownOne?.scale ?? 1.0;
|
||||||
|
result.scale.y = _data.assets.countdownOne?.scale ?? 1.0;
|
||||||
|
case GO:
|
||||||
|
if (_data.assets.countdownGo == null) return fallback?.buildCountdownSprite(step);
|
||||||
|
var assetPath = buildCountdownSpritePath(step);
|
||||||
|
if (assetPath == null) return null;
|
||||||
|
result.loadTexture(assetPath);
|
||||||
|
result.scale.x = _data.assets.countdownGo?.scale ?? 1.0;
|
||||||
|
result.scale.y = _data.assets.countdownGo?.scale ?? 1.0;
|
||||||
|
default:
|
||||||
|
// TODO: Do something here?
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.scrollFactor.set(0, 0);
|
||||||
|
result.antialiasing = !isCountdownSpritePixel(step);
|
||||||
|
result.updateHitbox();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildCountdownSpritePath(step:Countdown.CountdownStep):Null<String>
|
||||||
|
{
|
||||||
|
var basePath:Null<String> = null;
|
||||||
|
switch (step)
|
||||||
|
{
|
||||||
|
case THREE:
|
||||||
|
basePath = _data.assets.countdownThree?.assetPath;
|
||||||
|
case TWO:
|
||||||
|
basePath = _data.assets.countdownTwo?.assetPath;
|
||||||
|
case ONE:
|
||||||
|
basePath = _data.assets.countdownOne?.assetPath;
|
||||||
|
case GO:
|
||||||
|
basePath = _data.assets.countdownGo?.assetPath;
|
||||||
|
default:
|
||||||
|
basePath = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (basePath == null) return fallback?.buildCountdownSpritePath(step);
|
||||||
|
|
||||||
|
var parts = basePath?.split(Constants.LIBRARY_SEPARATOR) ?? [];
|
||||||
|
if (parts.length < 1) return null;
|
||||||
|
if (parts.length == 1) return parts[0];
|
||||||
|
|
||||||
|
return parts[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildCountdownSpriteLibrary(step:Countdown.CountdownStep):Null<String>
|
||||||
|
{
|
||||||
|
var basePath:Null<String> = null;
|
||||||
|
switch (step)
|
||||||
|
{
|
||||||
|
case THREE:
|
||||||
|
basePath = _data.assets.countdownThree?.assetPath;
|
||||||
|
case TWO:
|
||||||
|
basePath = _data.assets.countdownTwo?.assetPath;
|
||||||
|
case ONE:
|
||||||
|
basePath = _data.assets.countdownOne?.assetPath;
|
||||||
|
case GO:
|
||||||
|
basePath = _data.assets.countdownGo?.assetPath;
|
||||||
|
default:
|
||||||
|
basePath = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (basePath == null) return fallback?.buildCountdownSpriteLibrary(step);
|
||||||
|
|
||||||
|
var parts = basePath?.split(Constants.LIBRARY_SEPARATOR) ?? [];
|
||||||
|
if (parts.length <= 1) return null;
|
||||||
|
|
||||||
|
return parts[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isCountdownSpritePixel(step:Countdown.CountdownStep):Bool
|
||||||
|
{
|
||||||
|
switch (step)
|
||||||
|
{
|
||||||
|
case THREE:
|
||||||
|
var result = _data.assets.countdownThree?.isPixel;
|
||||||
|
if (result == null && fallback != null) result = fallback.isCountdownSpritePixel(step);
|
||||||
|
return result ?? false;
|
||||||
|
case TWO:
|
||||||
|
var result = _data.assets.countdownTwo?.isPixel;
|
||||||
|
if (result == null && fallback != null) result = fallback.isCountdownSpritePixel(step);
|
||||||
|
return result ?? false;
|
||||||
|
case ONE:
|
||||||
|
var result = _data.assets.countdownOne?.isPixel;
|
||||||
|
if (result == null && fallback != null) result = fallback.isCountdownSpritePixel(step);
|
||||||
|
return result ?? false;
|
||||||
|
case GO:
|
||||||
|
var result = _data.assets.countdownGo?.isPixel;
|
||||||
|
if (result == null && fallback != null) result = fallback.isCountdownSpritePixel(step);
|
||||||
|
return result ?? false;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCountdownSpriteOffsets(step:Countdown.CountdownStep):Array<Float>
|
||||||
|
{
|
||||||
|
switch (step)
|
||||||
|
{
|
||||||
|
case THREE:
|
||||||
|
var result = _data.assets.countdownThree?.offsets;
|
||||||
|
if (result == null && fallback != null) result = fallback.getCountdownSpriteOffsets(step);
|
||||||
|
return result ?? [0, 0];
|
||||||
|
case TWO:
|
||||||
|
var result = _data.assets.countdownTwo?.offsets;
|
||||||
|
if (result == null && fallback != null) result = fallback.getCountdownSpriteOffsets(step);
|
||||||
|
return result ?? [0, 0];
|
||||||
|
case ONE:
|
||||||
|
var result = _data.assets.countdownOne?.offsets;
|
||||||
|
if (result == null && fallback != null) result = fallback.getCountdownSpriteOffsets(step);
|
||||||
|
return result ?? [0, 0];
|
||||||
|
case GO:
|
||||||
|
var result = _data.assets.countdownGo?.offsets;
|
||||||
|
if (result == null && fallback != null) result = fallback.getCountdownSpriteOffsets(step);
|
||||||
|
return result ?? [0, 0];
|
||||||
|
default:
|
||||||
|
return [0, 0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCountdownSoundPath(step:Countdown.CountdownStep, raw:Bool = false):Null<String>
|
||||||
|
{
|
||||||
|
if (raw)
|
||||||
|
{
|
||||||
|
// TODO: figure out why ?. didn't work here
|
||||||
|
var rawPath:Null<String> = switch (step)
|
||||||
|
{
|
||||||
|
case Countdown.CountdownStep.THREE:
|
||||||
|
_data.assets.countdownThree?.data?.audioPath;
|
||||||
|
case Countdown.CountdownStep.TWO:
|
||||||
|
_data.assets.countdownTwo?.data?.audioPath;
|
||||||
|
case Countdown.CountdownStep.ONE:
|
||||||
|
_data.assets.countdownOne?.data?.audioPath;
|
||||||
|
case Countdown.CountdownStep.GO:
|
||||||
|
_data.assets.countdownGo?.data?.audioPath;
|
||||||
|
default:
|
||||||
|
null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (rawPath == null && fallback != null) ? fallback.getCountdownSoundPath(step, true) : rawPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
// library:path
|
||||||
|
var parts = getCountdownSoundPath(step, true)?.split(Constants.LIBRARY_SEPARATOR) ?? [];
|
||||||
|
if (parts.length == 0) return null;
|
||||||
|
if (parts.length == 1) return Paths.image(parts[0]);
|
||||||
|
return Paths.sound(parts[1], parts[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildJudgementSprite(rating:String):Null<FunkinSprite>
|
||||||
|
{
|
||||||
|
var result = new FunkinSprite();
|
||||||
|
|
||||||
|
switch (rating)
|
||||||
|
{
|
||||||
|
case "sick":
|
||||||
|
if (_data.assets.judgementSick == null) return fallback?.buildJudgementSprite(rating);
|
||||||
|
var assetPath = buildJudgementSpritePath(rating);
|
||||||
|
if (assetPath == null) return null;
|
||||||
|
result.loadTexture(assetPath);
|
||||||
|
result.scale.x = _data.assets.judgementSick?.scale ?? 1.0;
|
||||||
|
result.scale.y = _data.assets.judgementSick?.scale ?? 1.0;
|
||||||
|
case "good":
|
||||||
|
if (_data.assets.judgementGood == null) return fallback?.buildJudgementSprite(rating);
|
||||||
|
var assetPath = buildJudgementSpritePath(rating);
|
||||||
|
if (assetPath == null) return null;
|
||||||
|
result.loadTexture(assetPath);
|
||||||
|
result.scale.x = _data.assets.judgementGood?.scale ?? 1.0;
|
||||||
|
result.scale.y = _data.assets.judgementGood?.scale ?? 1.0;
|
||||||
|
case "bad":
|
||||||
|
if (_data.assets.judgementBad == null) return fallback?.buildJudgementSprite(rating);
|
||||||
|
var assetPath = buildJudgementSpritePath(rating);
|
||||||
|
if (assetPath == null) return null;
|
||||||
|
result.loadTexture(assetPath);
|
||||||
|
result.scale.x = _data.assets.judgementBad?.scale ?? 1.0;
|
||||||
|
result.scale.y = _data.assets.judgementBad?.scale ?? 1.0;
|
||||||
|
case "shit":
|
||||||
|
if (_data.assets.judgementShit == null) return fallback?.buildJudgementSprite(rating);
|
||||||
|
var assetPath = buildJudgementSpritePath(rating);
|
||||||
|
if (assetPath == null) return null;
|
||||||
|
result.loadTexture(assetPath);
|
||||||
|
result.scale.x = _data.assets.judgementShit?.scale ?? 1.0;
|
||||||
|
result.scale.y = _data.assets.judgementShit?.scale ?? 1.0;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.scrollFactor.set(0.2, 0.2);
|
||||||
|
var isPixel = isJudgementSpritePixel(rating);
|
||||||
|
result.antialiasing = !isPixel;
|
||||||
|
result.pixelPerfectRender = isPixel;
|
||||||
|
result.pixelPerfectPosition = isPixel;
|
||||||
|
result.updateHitbox();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isJudgementSpritePixel(rating:String):Bool
|
||||||
|
{
|
||||||
|
switch (rating)
|
||||||
|
{
|
||||||
|
case "sick":
|
||||||
|
var result = _data.assets.judgementSick?.isPixel;
|
||||||
|
if (result == null && fallback != null) result = fallback.isJudgementSpritePixel(rating);
|
||||||
|
return result ?? false;
|
||||||
|
case "good":
|
||||||
|
var result = _data.assets.judgementGood?.isPixel;
|
||||||
|
if (result == null && fallback != null) result = fallback.isJudgementSpritePixel(rating);
|
||||||
|
return result ?? false;
|
||||||
|
case "bad":
|
||||||
|
var result = _data.assets.judgementBad?.isPixel;
|
||||||
|
if (result == null && fallback != null) result = fallback.isJudgementSpritePixel(rating);
|
||||||
|
return result ?? false;
|
||||||
|
case "GO":
|
||||||
|
var result = _data.assets.judgementShit?.isPixel;
|
||||||
|
if (result == null && fallback != null) result = fallback.isJudgementSpritePixel(rating);
|
||||||
|
return result ?? false;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildJudgementSpritePath(rating:String):Null<String>
|
||||||
|
{
|
||||||
|
var basePath:Null<String> = null;
|
||||||
|
switch (rating)
|
||||||
|
{
|
||||||
|
case "sick":
|
||||||
|
basePath = _data.assets.judgementSick?.assetPath;
|
||||||
|
case "good":
|
||||||
|
basePath = _data.assets.judgementGood?.assetPath;
|
||||||
|
case "bad":
|
||||||
|
basePath = _data.assets.judgementBad?.assetPath;
|
||||||
|
case "shit":
|
||||||
|
basePath = _data.assets.judgementShit?.assetPath;
|
||||||
|
default:
|
||||||
|
basePath = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (basePath == null) return fallback?.buildJudgementSpritePath(rating);
|
||||||
|
|
||||||
|
var parts = basePath?.split(Constants.LIBRARY_SEPARATOR) ?? [];
|
||||||
|
if (parts.length < 1) return null;
|
||||||
|
if (parts.length == 1) return parts[0];
|
||||||
|
|
||||||
|
return parts[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getJudgementSpriteOffsets(rating:String):Array<Float>
|
||||||
|
{
|
||||||
|
switch (rating)
|
||||||
|
{
|
||||||
|
case "sick":
|
||||||
|
var result = _data.assets.judgementSick?.offsets;
|
||||||
|
if (result == null && fallback != null) result = fallback.getJudgementSpriteOffsets(rating);
|
||||||
|
return result ?? [0, 0];
|
||||||
|
case "good":
|
||||||
|
var result = _data.assets.judgementGood?.offsets;
|
||||||
|
if (result == null && fallback != null) result = fallback.getJudgementSpriteOffsets(rating);
|
||||||
|
return result ?? [0, 0];
|
||||||
|
case "bad":
|
||||||
|
var result = _data.assets.judgementBad?.offsets;
|
||||||
|
if (result == null && fallback != null) result = fallback.getJudgementSpriteOffsets(rating);
|
||||||
|
return result ?? [0, 0];
|
||||||
|
case "shit":
|
||||||
|
var result = _data.assets.judgementShit?.offsets;
|
||||||
|
if (result == null && fallback != null) result = fallback.getJudgementSpriteOffsets(rating);
|
||||||
|
return result ?? [0, 0];
|
||||||
|
default:
|
||||||
|
return [0, 0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildComboNumSprite(digit:Int):Null<FunkinSprite>
|
||||||
|
{
|
||||||
|
var result = new FunkinSprite();
|
||||||
|
|
||||||
|
switch (digit)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
if (_data.assets.comboNumber0 == null) return fallback?.buildComboNumSprite(digit);
|
||||||
|
var assetPath = buildComboNumSpritePath(digit);
|
||||||
|
if (assetPath == null) return null;
|
||||||
|
result.loadTexture(assetPath);
|
||||||
|
result.scale.x = _data.assets.comboNumber0?.scale ?? 1.0;
|
||||||
|
result.scale.y = _data.assets.comboNumber0?.scale ?? 1.0;
|
||||||
|
case 1:
|
||||||
|
if (_data.assets.comboNumber1 == null) return fallback?.buildComboNumSprite(digit);
|
||||||
|
var assetPath = buildComboNumSpritePath(digit);
|
||||||
|
if (assetPath == null) return null;
|
||||||
|
result.loadTexture(assetPath);
|
||||||
|
result.scale.x = _data.assets.comboNumber1?.scale ?? 1.0;
|
||||||
|
result.scale.y = _data.assets.comboNumber1?.scale ?? 1.0;
|
||||||
|
case 2:
|
||||||
|
if (_data.assets.comboNumber2 == null) return fallback?.buildComboNumSprite(digit);
|
||||||
|
var assetPath = buildComboNumSpritePath(digit);
|
||||||
|
if (assetPath == null) return null;
|
||||||
|
result.loadTexture(assetPath);
|
||||||
|
result.scale.x = _data.assets.comboNumber2?.scale ?? 1.0;
|
||||||
|
result.scale.y = _data.assets.comboNumber2?.scale ?? 1.0;
|
||||||
|
case 3:
|
||||||
|
if (_data.assets.comboNumber3 == null) return fallback?.buildComboNumSprite(digit);
|
||||||
|
var assetPath = buildComboNumSpritePath(digit);
|
||||||
|
if (assetPath == null) return null;
|
||||||
|
result.loadTexture(assetPath);
|
||||||
|
result.scale.x = _data.assets.comboNumber3?.scale ?? 1.0;
|
||||||
|
result.scale.y = _data.assets.comboNumber3?.scale ?? 1.0;
|
||||||
|
case 4:
|
||||||
|
if (_data.assets.comboNumber4 == null) return fallback?.buildComboNumSprite(digit);
|
||||||
|
var assetPath = buildComboNumSpritePath(digit);
|
||||||
|
if (assetPath == null) return null;
|
||||||
|
result.loadTexture(assetPath);
|
||||||
|
result.scale.x = _data.assets.comboNumber4?.scale ?? 1.0;
|
||||||
|
result.scale.y = _data.assets.comboNumber4?.scale ?? 1.0;
|
||||||
|
case 5:
|
||||||
|
if (_data.assets.comboNumber5 == null) return fallback?.buildComboNumSprite(digit);
|
||||||
|
var assetPath = buildComboNumSpritePath(digit);
|
||||||
|
if (assetPath == null) return null;
|
||||||
|
result.loadTexture(assetPath);
|
||||||
|
result.scale.x = _data.assets.comboNumber5?.scale ?? 1.0;
|
||||||
|
result.scale.y = _data.assets.comboNumber5?.scale ?? 1.0;
|
||||||
|
case 6:
|
||||||
|
if (_data.assets.comboNumber6 == null) return fallback?.buildComboNumSprite(digit);
|
||||||
|
var assetPath = buildComboNumSpritePath(digit);
|
||||||
|
if (assetPath == null) return null;
|
||||||
|
result.loadTexture(assetPath);
|
||||||
|
result.scale.x = _data.assets.comboNumber6?.scale ?? 1.0;
|
||||||
|
result.scale.y = _data.assets.comboNumber6?.scale ?? 1.0;
|
||||||
|
case 7:
|
||||||
|
if (_data.assets.comboNumber7 == null) return fallback?.buildComboNumSprite(digit);
|
||||||
|
var assetPath = buildComboNumSpritePath(digit);
|
||||||
|
if (assetPath == null) return null;
|
||||||
|
result.loadTexture(assetPath);
|
||||||
|
result.scale.x = _data.assets.comboNumber7?.scale ?? 1.0;
|
||||||
|
result.scale.y = _data.assets.comboNumber7?.scale ?? 1.0;
|
||||||
|
case 8:
|
||||||
|
if (_data.assets.comboNumber8 == null) return fallback?.buildComboNumSprite(digit);
|
||||||
|
var assetPath = buildComboNumSpritePath(digit);
|
||||||
|
if (assetPath == null) return null;
|
||||||
|
result.loadTexture(assetPath);
|
||||||
|
result.scale.x = _data.assets.comboNumber8?.scale ?? 1.0;
|
||||||
|
result.scale.y = _data.assets.comboNumber8?.scale ?? 1.0;
|
||||||
|
case 9:
|
||||||
|
if (_data.assets.comboNumber9 == null) return fallback?.buildComboNumSprite(digit);
|
||||||
|
var assetPath = buildComboNumSpritePath(digit);
|
||||||
|
if (assetPath == null) return null;
|
||||||
|
result.loadTexture(assetPath);
|
||||||
|
result.scale.x = _data.assets.comboNumber9?.scale ?? 1.0;
|
||||||
|
result.scale.y = _data.assets.comboNumber9?.scale ?? 1.0;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var isPixel = isComboNumSpritePixel(digit);
|
||||||
|
result.antialiasing = !isPixel;
|
||||||
|
result.pixelPerfectRender = isPixel;
|
||||||
|
result.pixelPerfectPosition = isPixel;
|
||||||
|
result.updateHitbox();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isComboNumSpritePixel(digit:Int):Bool
|
||||||
|
{
|
||||||
|
switch (digit)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
var result = _data.assets.comboNumber0?.isPixel;
|
||||||
|
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||||
|
return result ?? false;
|
||||||
|
case 1:
|
||||||
|
var result = _data.assets.comboNumber1?.isPixel;
|
||||||
|
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||||
|
return result ?? false;
|
||||||
|
case 2:
|
||||||
|
var result = _data.assets.comboNumber2?.isPixel;
|
||||||
|
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||||
|
return result ?? false;
|
||||||
|
case 3:
|
||||||
|
var result = _data.assets.comboNumber3?.isPixel;
|
||||||
|
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||||
|
return result ?? false;
|
||||||
|
case 4:
|
||||||
|
var result = _data.assets.comboNumber4?.isPixel;
|
||||||
|
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||||
|
return result ?? false;
|
||||||
|
case 5:
|
||||||
|
var result = _data.assets.comboNumber5?.isPixel;
|
||||||
|
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||||
|
return result ?? false;
|
||||||
|
case 6:
|
||||||
|
var result = _data.assets.comboNumber6?.isPixel;
|
||||||
|
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||||
|
return result ?? false;
|
||||||
|
case 7:
|
||||||
|
var result = _data.assets.comboNumber7?.isPixel;
|
||||||
|
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||||
|
return result ?? false;
|
||||||
|
case 8:
|
||||||
|
var result = _data.assets.comboNumber8?.isPixel;
|
||||||
|
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||||
|
return result ?? false;
|
||||||
|
case 9:
|
||||||
|
var result = _data.assets.comboNumber9?.isPixel;
|
||||||
|
if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit);
|
||||||
|
return result ?? false;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildComboNumSpritePath(digit:Int):Null<String>
|
||||||
|
{
|
||||||
|
var basePath:Null<String> = null;
|
||||||
|
switch (digit)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
basePath = _data.assets.comboNumber0?.assetPath;
|
||||||
|
case 1:
|
||||||
|
basePath = _data.assets.comboNumber1?.assetPath;
|
||||||
|
case 2:
|
||||||
|
basePath = _data.assets.comboNumber2?.assetPath;
|
||||||
|
case 3:
|
||||||
|
basePath = _data.assets.comboNumber3?.assetPath;
|
||||||
|
case 4:
|
||||||
|
basePath = _data.assets.comboNumber4?.assetPath;
|
||||||
|
case 5:
|
||||||
|
basePath = _data.assets.comboNumber5?.assetPath;
|
||||||
|
case 6:
|
||||||
|
basePath = _data.assets.comboNumber6?.assetPath;
|
||||||
|
case 7:
|
||||||
|
basePath = _data.assets.comboNumber7?.assetPath;
|
||||||
|
case 8:
|
||||||
|
basePath = _data.assets.comboNumber8?.assetPath;
|
||||||
|
case 9:
|
||||||
|
basePath = _data.assets.comboNumber9?.assetPath;
|
||||||
|
default:
|
||||||
|
basePath = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (basePath == null) return fallback?.buildComboNumSpritePath(digit);
|
||||||
|
|
||||||
|
var parts = basePath?.split(Constants.LIBRARY_SEPARATOR) ?? [];
|
||||||
|
if (parts.length < 1) return null;
|
||||||
|
if (parts.length == 1) return parts[0];
|
||||||
|
|
||||||
|
return parts[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getComboNumSpriteOffsets(digit:Int):Array<Float>
|
||||||
|
{
|
||||||
|
switch (digit)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
var result = _data.assets.comboNumber0?.offsets;
|
||||||
|
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||||
|
return result ?? [0, 0];
|
||||||
|
case 1:
|
||||||
|
var result = _data.assets.comboNumber1?.offsets;
|
||||||
|
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||||
|
return result ?? [0, 0];
|
||||||
|
case 2:
|
||||||
|
var result = _data.assets.comboNumber2?.offsets;
|
||||||
|
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||||
|
return result ?? [0, 0];
|
||||||
|
case 3:
|
||||||
|
var result = _data.assets.comboNumber3?.offsets;
|
||||||
|
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||||
|
return result ?? [0, 0];
|
||||||
|
case 4:
|
||||||
|
var result = _data.assets.comboNumber4?.offsets;
|
||||||
|
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||||
|
return result ?? [0, 0];
|
||||||
|
case 5:
|
||||||
|
var result = _data.assets.comboNumber5?.offsets;
|
||||||
|
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||||
|
return result ?? [0, 0];
|
||||||
|
case 6:
|
||||||
|
var result = _data.assets.comboNumber6?.offsets;
|
||||||
|
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||||
|
return result ?? [0, 0];
|
||||||
|
case 7:
|
||||||
|
var result = _data.assets.comboNumber7?.offsets;
|
||||||
|
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||||
|
return result ?? [0, 0];
|
||||||
|
case 8:
|
||||||
|
var result = _data.assets.comboNumber8?.offsets;
|
||||||
|
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||||
|
return result ?? [0, 0];
|
||||||
|
case 9:
|
||||||
|
var result = _data.assets.comboNumber9?.offsets;
|
||||||
|
if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit);
|
||||||
|
return result ?? [0, 0];
|
||||||
|
default:
|
||||||
|
return [0, 0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function destroy():Void {}
|
public function destroy():Void {}
|
||||||
|
@ -310,8 +867,17 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||||
return 'NoteStyle($id)';
|
return 'NoteStyle($id)';
|
||||||
}
|
}
|
||||||
|
|
||||||
static function _fetchData(id:String):Null<NoteStyleData>
|
static function _fetchData(id:String):NoteStyleData
|
||||||
{
|
{
|
||||||
return NoteStyleRegistry.instance.parseEntryDataWithMigration(id, NoteStyleRegistry.instance.fetchEntryVersion(id));
|
var result = NoteStyleRegistry.instance.parseEntryDataWithMigration(id, NoteStyleRegistry.instance.fetchEntryVersion(id));
|
||||||
|
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
throw 'Could not parse note style data for id: $id';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -556,41 +556,7 @@ enum abstract ScoringRank(String)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMusicPath():String
|
public function getFreeplayRankIconAsset():String
|
||||||
{
|
|
||||||
switch (abstract)
|
|
||||||
{
|
|
||||||
case PERFECT_GOLD:
|
|
||||||
return 'resultsPERFECT';
|
|
||||||
case PERFECT:
|
|
||||||
return 'resultsPERFECT';
|
|
||||||
case EXCELLENT:
|
|
||||||
return 'resultsEXCELLENT';
|
|
||||||
case GREAT:
|
|
||||||
return 'resultsNORMAL';
|
|
||||||
case GOOD:
|
|
||||||
return 'resultsNORMAL';
|
|
||||||
case SHIT:
|
|
||||||
return 'resultsSHIT';
|
|
||||||
default:
|
|
||||||
return 'resultsNORMAL';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function hasMusicIntro():Bool
|
|
||||||
{
|
|
||||||
switch (abstract)
|
|
||||||
{
|
|
||||||
case EXCELLENT:
|
|
||||||
return true;
|
|
||||||
case SHIT:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFreeplayRankIconAsset():Null<String>
|
|
||||||
{
|
{
|
||||||
switch (abstract)
|
switch (abstract)
|
||||||
{
|
{
|
||||||
|
@ -607,20 +573,7 @@ enum abstract ScoringRank(String)
|
||||||
case SHIT:
|
case SHIT:
|
||||||
return 'LOSS';
|
return 'LOSS';
|
||||||
default:
|
default:
|
||||||
return null;
|
return 'LOSS';
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function shouldMusicLoop():Bool
|
|
||||||
{
|
|
||||||
switch (abstract)
|
|
||||||
{
|
|
||||||
case PERFECT_GOLD | PERFECT | EXCELLENT | GREAT | GOOD:
|
|
||||||
return true;
|
|
||||||
case SHIT:
|
|
||||||
return false;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue