Merge remote-tracking branch 'origin/rewrite/master' into input-offsets
This commit is contained in:
@@ -3,21 +3,46 @@ description: "sets up haxe shit, using HMM!"
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: funkincrew/ci-haxe@v3
|
||||
- name: Install Haxe lol
|
||||
uses: funkincrew/ci-haxe@v3.1.0
|
||||
with:
|
||||
haxe-version: 4.3.3
|
||||
- name: Config haxelib
|
||||
run: |
|
||||
haxelib config
|
||||
haxelib --never install haxelib 4.1.0 --global
|
||||
haxelib --never deleterepo || true
|
||||
haxelib --never newrepo
|
||||
echo "HAXEPATH=$(haxelib config)" >> "$GITHUB_ENV"
|
||||
haxelib --never git haxelib https://github.com/HaxeFoundation/haxelib.git master
|
||||
shell: bash
|
||||
- name: Installing Haxe lol
|
||||
- name: Gather debug info
|
||||
run: |
|
||||
cat << EOF >> "$GITHUB_STEP_SUMMARY"
|
||||
## haxe
|
||||
- version: \`$(haxe -version)\`
|
||||
- exe: \`$(which haxe)\`
|
||||
## haxelib
|
||||
- version: \`$(haxelib version)\`
|
||||
- exe: \`$(which haxelib)\`
|
||||
- path: \`$HAXEPATH\`
|
||||
### local
|
||||
- config: \`$(haxelib config)\`
|
||||
- path: \`$(haxelib path haxelib || true)\`
|
||||
### global
|
||||
- config: \`$(haxelib config --global)\`
|
||||
- path: \`$(haxelib path haxelib --global || true)\`
|
||||
### system
|
||||
- version: \`$(haxelib --system version)\`
|
||||
- local: \`$(haxelib --system config)\`
|
||||
- global: \`$(haxelib --system config --global)\`
|
||||
EOF
|
||||
shell: bash
|
||||
- name: Install hmm
|
||||
# hmm only supports global installs
|
||||
run: |
|
||||
haxe -version
|
||||
haxelib git haxelib https://github.com/HaxeFoundation/haxelib.git master
|
||||
haxelib version
|
||||
haxelib --global install hmm
|
||||
shell: bash
|
||||
- name: dependency install cache
|
||||
- name: Restore cached dependencies
|
||||
id: cache-hmm
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
|
||||
@@ -8,71 +8,80 @@ jobs:
|
||||
runs-on: [self-hosted, linux]
|
||||
container: ubuntu:23.10
|
||||
steps:
|
||||
- name: prepare container
|
||||
- name: Install tools missing in container
|
||||
run: |
|
||||
apt update
|
||||
apt install sudo git curl unzip -y
|
||||
git config --global --add safe.directory $GITHUB_WORKSPACE
|
||||
- name: get token from gh app
|
||||
apt install -y sudo git curl unzip
|
||||
- name: Fix git config on posix runner
|
||||
run: |
|
||||
git config --global --add safe.directory ${{ github.workspace }}
|
||||
- name: Get checkout token
|
||||
uses: actions/create-github-app-token@v1
|
||||
id: app_token
|
||||
with:
|
||||
app-id: ${{ vars.APP_ID }}
|
||||
private-key: ${{ secrets.APP_PEM }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
- name: checkout repo
|
||||
- name: Checkout repo
|
||||
uses: funkincrew/ci-checkout@v6
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
token: ${{ steps.app_token.outputs.token }}
|
||||
- uses: ./.github/actions/setup-haxeshit
|
||||
- name: gather game dependencies
|
||||
- name: Install Haxe, dependencies
|
||||
uses: ./.github/actions/setup-haxeshit
|
||||
- name: Install native dependencies
|
||||
run: |
|
||||
sudo apt-get install -y libx11-dev xorg-dev libgl-dev libxi-dev libxext-dev libasound2-dev libxinerama-dev libxrandr-dev libgl1-mesa-dev
|
||||
- name: build game
|
||||
apt install -y \
|
||||
libx11-dev libxi-dev libxext-dev libxinerama-dev libxrandr-dev \
|
||||
libgl-dev libgl1-mesa-dev \
|
||||
libasound2-dev
|
||||
- name: Build game
|
||||
run: |
|
||||
haxelib run lime build html5 -release --times -DGITHUB_BUILD
|
||||
ls
|
||||
- uses: ./.github/actions/upload-itch
|
||||
- name: Upload build artifacts
|
||||
uses: ./.github/actions/upload-itch
|
||||
with:
|
||||
butler-key: ${{ secrets.BUTLER_API_KEY}}
|
||||
build-dir: export/release/html5/bin
|
||||
target: html5
|
||||
create-nightly-win:
|
||||
runs-on: windows-latest
|
||||
runs-on: [self-hosted, windows]
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- name: get token from gh app
|
||||
- name: Get checkout token
|
||||
uses: actions/create-github-app-token@v1
|
||||
id: app_token
|
||||
with:
|
||||
app-id: ${{ vars.APP_ID }}
|
||||
private-key: ${{ secrets.APP_PEM }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
- name: checkout repo
|
||||
- name: Checkout repo
|
||||
uses: funkincrew/ci-checkout@v6
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
token: ${{ steps.app_token.outputs.token }}
|
||||
- uses: ./.github/actions/setup-haxeshit
|
||||
- name: Make HXCPP cache dir
|
||||
- name: Install Haxe, dependencies
|
||||
uses: ./.github/actions/setup-haxeshit
|
||||
- name: Setup build cache
|
||||
run: |
|
||||
mkdir -p ${{ runner.temp }}\hxcpp_cache
|
||||
mkdir -p ${{ runner.temp }}/hxcpp_cache
|
||||
- name: Restore build cache
|
||||
id: cache-build-win
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
.haxelib
|
||||
export
|
||||
${{ runner.temp }}\hxcpp_cache
|
||||
key: ${{ runner.os }}-build-win-${{ github.ref_name }}-${{ hashFiles('**/hmm.json') }}
|
||||
- name: build game
|
||||
${{ runner.temp }}/hxcpp_cache
|
||||
key: ${{ runner.os }}-build-win-${{ github.ref_name }}
|
||||
- name: Build game
|
||||
run: |
|
||||
haxelib run lime build windows -release -DNO_REDIRECT_ASSETS_FOLDER -DGITHUB_BUILD
|
||||
dir
|
||||
haxelib run lime build windows -v -release -DGITHUB_BUILD
|
||||
env:
|
||||
HXCPP_COMPILE_CACHE: "${{ runner.temp }}\\hxcpp_cache"
|
||||
- uses: ./.github/actions/upload-itch
|
||||
- name: Upload build artifacts
|
||||
uses: ./.github/actions/upload-itch
|
||||
with:
|
||||
butler-key: ${{ secrets.BUTLER_API_KEY }}
|
||||
build-dir: export/release/windows/bin
|
||||
@@ -80,78 +89,42 @@ jobs:
|
||||
create-nightly-mac:
|
||||
runs-on: [self-hosted, macos]
|
||||
steps:
|
||||
- name: prepare container
|
||||
- name: Fix git config on posix runner
|
||||
run: |
|
||||
git config --global --add safe.directory $GITHUB_WORKSPACE
|
||||
- name: get token from gh app
|
||||
git config --global --add safe.directory ${{ github.workspace }}
|
||||
- name: Get checkout token
|
||||
uses: actions/create-github-app-token@v1
|
||||
id: app_token
|
||||
with:
|
||||
app-id: ${{ vars.APP_ID }}
|
||||
private-key: ${{ secrets.APP_PEM }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
- name: checkout repo
|
||||
- name: Checkout repo
|
||||
uses: funkincrew/ci-checkout@v6
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
token: ${{ steps.app_token.outputs.token }}
|
||||
- uses: ./.github/actions/setup-haxeshit
|
||||
- name: Make HXCPP cache dir
|
||||
- name: Install Haxe, dependencies
|
||||
uses: ./.github/actions/setup-haxeshit
|
||||
- name: Setup build cache
|
||||
run: |
|
||||
mkdir -p ${{ runner.temp }}/hxcpp_cache
|
||||
- name: restore build cache
|
||||
- name: Restore build cache
|
||||
id: cache-build-win
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
.haxelib
|
||||
export
|
||||
${{ runner.temp }}/hxcpp_cache
|
||||
key: ${{ runner.os }}-build-mac-${{ github.ref_name }}-${{ hashFiles('**/hmm.json') }}
|
||||
key: ${{ runner.os }}-build-mac-${{ github.ref_name }}
|
||||
- name: Build game
|
||||
run: |
|
||||
haxelib run lime build macos -release --times -DGITHUB_BUILD
|
||||
ls
|
||||
env:
|
||||
HXCPP_COMPILE_CACHE: "${{ runner.temp }}/hxcpp_cache"
|
||||
- uses: ./.github/actions/upload-itch
|
||||
- name: Upload build artifacts
|
||||
uses: ./.github/actions/upload-itch
|
||||
with:
|
||||
butler-key: ${{ secrets.BUTLER_API_KEY}}
|
||||
build-dir: export/release/macos/bin
|
||||
target: macos
|
||||
|
||||
# test-unit-win:
|
||||
# needs: create-nightly-win
|
||||
# runs-on: windows-latest
|
||||
# steps:
|
||||
# - name: get token from gh app
|
||||
# uses: actions/create-github-app-token@v1
|
||||
# id: app_token
|
||||
# with:
|
||||
# app-id: ${{ vars.APP_ID }}
|
||||
# private-key: ${{ secrets.APP_PEM }}
|
||||
# owner: ${{ github.repository_owner }}
|
||||
# - name: checkout repo
|
||||
# uses: funkincrew/ci-checkout@v6
|
||||
# with:
|
||||
# submodules: 'recursive'
|
||||
# token: ${{ steps.app_token.outputs.token }}
|
||||
# - name: Make HXCPP cache dir
|
||||
# run: |
|
||||
# mkdir -p ${{ runner.temp }}\hxcpp_cache
|
||||
# - name: Restore build cache
|
||||
# id: cache-build-win
|
||||
# uses: actions/cache@v4
|
||||
# with:
|
||||
# path: |
|
||||
# .haxelib
|
||||
# export
|
||||
# ${{ runner.temp }}\hxcpp_cache
|
||||
# key: ${{ runner.os }}-test-win-${{ github.ref_name }}-${{ hashFiles('**/hmm.json') }}
|
||||
# - uses: ./.github/actions/setup-haxeshit
|
||||
# - name: Run unit tests
|
||||
# run: |
|
||||
# cd ./tests/unit/
|
||||
# ./start-win-native.bat
|
||||
# env:
|
||||
# HXCPP_COMPILE_CACHE: "${{ runner.temp }}\\hxcpp_cache"
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
.DS_STORE
|
||||
.haxelib/
|
||||
.vs/
|
||||
APIStuff.hx
|
||||
dump/
|
||||
export/
|
||||
RECOVER_*.fla
|
||||
shitAudio/
|
||||
.build_time
|
||||
.swp
|
||||
|
||||
+2
-6
@@ -1,10 +1,6 @@
|
||||
[submodule "assets"]
|
||||
path = assets
|
||||
url = https://github.com/FunkinCrew/Funkin-history-rewrite-assets
|
||||
branch = master
|
||||
update = merge
|
||||
url = https://github.com/FunkinCrew/Funkin-Assets-secret
|
||||
[submodule "art"]
|
||||
path = art
|
||||
url = https://github.com/FunkinCrew/Funkin-history-rewrite-art
|
||||
branch = master
|
||||
update = merge
|
||||
url = https://github.com/FunkinCrew/Funkin-Art-secret
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# Ignore artifacts
|
||||
export
|
||||
|
||||
# Ignore all JSONS in the images folder (including FlxAnimate JSONs)
|
||||
assets/preload/images
|
||||
assets/shared/images
|
||||
assets/weekend1/images
|
||||
|
||||
# Don't ignore data files
|
||||
# TODO: These don't work.
|
||||
!assets/preload/data/
|
||||
Vendored
+27
-12
@@ -71,6 +71,7 @@
|
||||
|
||||
"haxe.displayPort": "auto",
|
||||
"haxe.enableCompilationServer": false,
|
||||
"haxe.enableServerView": true,
|
||||
"haxe.displayServer": {
|
||||
"arguments": ["-v"]
|
||||
},
|
||||
@@ -84,7 +85,7 @@
|
||||
},
|
||||
"projectManager.git.baseFolders": ["./"],
|
||||
|
||||
"haxecheckstyle.sourceFolders": ["src", "Source"],
|
||||
"haxecheckstyle.sourceFolders": ["src", "source"],
|
||||
"haxecheckstyle.externalSourceRoots": [],
|
||||
"haxecheckstyle.configurationFile": "checkstyle.json",
|
||||
"haxecheckstyle.codeSimilarityBufferSize": 100,
|
||||
@@ -93,7 +94,7 @@
|
||||
{
|
||||
"label": "Windows / Debug",
|
||||
"target": "windows",
|
||||
"args": ["-debug"]
|
||||
"args": ["-debug", "-DFORCE_DEBUG_VERSION"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug",
|
||||
@@ -103,7 +104,7 @@
|
||||
{
|
||||
"label": "Windows / Debug (FlxAnimate Test)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DANIMATE"]
|
||||
"args": ["-debug", "-DANIMATE", "-DFORCE_DEBUG_VERSION"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug (FlxAnimate Test)",
|
||||
@@ -113,7 +114,7 @@
|
||||
{
|
||||
"label": "Windows / Debug (Straight to Freeplay)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DFREEPLAY"]
|
||||
"args": ["-debug", "-DFREEPLAY", "-DFORCE_DEBUG_VERSION"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug (Straight to Freeplay)",
|
||||
@@ -123,7 +124,11 @@
|
||||
{
|
||||
"label": "Windows / Debug (Straight to Play - Bopeebo Normal)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DSONG=bopeebo -DDIFFICULTY=normal"]
|
||||
"args": [
|
||||
"-debug",
|
||||
"-DSONG=bopeebo -DDIFFICULTY=normal",
|
||||
"-DFORCE_DEBUG_VERSION"
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug (Straight to Play - Bopeebo Normal)",
|
||||
@@ -133,7 +138,7 @@
|
||||
{
|
||||
"label": "Windows / Debug (Conversation Test)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DDIALOGUE"]
|
||||
"args": ["-debug", "-DDIALOGUE", "-DFORCE_DEBUG_VERSION"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug (Conversation Test)",
|
||||
@@ -143,7 +148,7 @@
|
||||
{
|
||||
"label": "Windows / Debug (Straight to Chart Editor)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DCHARTING"]
|
||||
"args": ["-debug", "-DCHARTING", "-DFORCE_DEBUG_VERSION"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug (Straight to Chart Editor)",
|
||||
@@ -153,7 +158,7 @@
|
||||
{
|
||||
"label": "Windows / Debug (Straight to Animation Editor)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DANIMDEBUG"]
|
||||
"args": ["-debug", "-DANIMDEBUG", "-DFORCE_DEBUG_VERSION"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug (Straight to Animation Editor)",
|
||||
@@ -163,7 +168,7 @@
|
||||
{
|
||||
"label": "Windows / Debug (Latency Test)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DLATENCY"]
|
||||
"args": ["-debug", "-DLATENCY", "-DFORCE_DEBUG_VERSION"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug (Latency Test)",
|
||||
@@ -173,7 +178,17 @@
|
||||
{
|
||||
"label": "Windows / Debug (Waveform Test)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DWAVEFORM"]
|
||||
"args": ["-debug", "-DWAVEFORM", "-DFORCE_DEBUG_VERSION"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Release",
|
||||
"target": "windows",
|
||||
"args": ["-release"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Release (GitHub Actions)",
|
||||
"target": "windows",
|
||||
"args": ["-release", "-DGITHUB_BUILD"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug (Waveform Test)",
|
||||
@@ -183,12 +198,12 @@
|
||||
{
|
||||
"label": "HTML5 / Debug",
|
||||
"target": "html5",
|
||||
"args": ["-debug"]
|
||||
"args": ["-debug", "-DFORCE_DEBUG_VERSION"]
|
||||
},
|
||||
{
|
||||
"label": "HTML5 / Debug (Watch)",
|
||||
"target": "html5",
|
||||
"args": ["-debug", "-watch"]
|
||||
"args": ["-debug", "-watch", "-DFORCE_DEBUG_VERSION"]
|
||||
}
|
||||
],
|
||||
"cmake.configureOnOpen": false,
|
||||
|
||||
+33
-30
@@ -22,8 +22,8 @@
|
||||
<set name="BUILD_DIR" value="export/release" unless="debug" />
|
||||
<set name="BUILD_DIR" value="export/32bit" if="32bit" />
|
||||
<classpath name="source" />
|
||||
<assets path="assets/preload" rename="assets" exclude="*.ogg" if="web" />
|
||||
<assets path="assets/preload" rename="assets" exclude="*.mp3" unless="web" />
|
||||
<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">
|
||||
@@ -53,28 +53,28 @@
|
||||
<library name="weekend1" preload="false" />
|
||||
</section>
|
||||
<library name="art" preload="false" />
|
||||
<assets path="assets/songs" library="songs" exclude="*.fla|*.ogg" if="web" />
|
||||
<assets path="assets/songs" library="songs" exclude="*.fla|*.mp3" unless="web" />
|
||||
<assets path="assets/shared" library="shared" exclude="*.fla|*.ogg" if="web" />
|
||||
<assets path="assets/shared" library="shared" exclude="*.fla|*.mp3" unless="web" />
|
||||
<assets path="assets/tutorial" library="tutorial" exclude="*.fla|*.ogg" if="web" />
|
||||
<assets path="assets/tutorial" library="tutorial" exclude="*.fla|*.mp3" unless="web" />
|
||||
<assets path="assets/week1" library="week1" exclude="*.fla|*.ogg" if="web" />
|
||||
<assets path="assets/week1" library="week1" exclude="*.fla|*.mp3" unless="web" />
|
||||
<assets path="assets/week2" library="week2" exclude="*.fla|*.ogg" if="web" />
|
||||
<assets path="assets/week2" library="week2" exclude="*.fla|*.mp3" unless="web" />
|
||||
<assets path="assets/week3" library="week3" exclude="*.fla|*.ogg" if="web" />
|
||||
<assets path="assets/week3" library="week3" exclude="*.fla|*.mp3" unless="web" />
|
||||
<assets path="assets/week4" library="week4" exclude="*.fla|*.ogg" if="web" />
|
||||
<assets path="assets/week4" library="week4" exclude="*.fla|*.mp3" unless="web" />
|
||||
<assets path="assets/week5" library="week5" exclude="*.fla|*.ogg" if="web" />
|
||||
<assets path="assets/week5" library="week5" exclude="*.fla|*.mp3" unless="web" />
|
||||
<assets path="assets/week6" library="week6" exclude="*.fla|*.ogg" if="web" />
|
||||
<assets path="assets/week6" library="week6" exclude="*.fla|*.mp3" unless="web" />
|
||||
<assets path="assets/week7" library="week7" exclude="*.fla|*.ogg" if="web" />
|
||||
<assets path="assets/week7" library="week7" exclude="*.fla|*.mp3" unless="web" />
|
||||
<assets path="assets/weekend1" library="weekend1" exclude="*.fla|*.ogg" if="web" />
|
||||
<assets path="assets/weekend1" library="weekend1" exclude="*.fla|*.mp3" unless="web" />
|
||||
<assets path="assets/songs" library="songs" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||
<assets path="assets/songs" library="songs" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||
<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
|
||||
@@ -91,8 +91,11 @@
|
||||
NOT USING A DIRECT THING TO THE ASSET!!!
|
||||
-->
|
||||
<assets path="assets/fonts" embed="true" />
|
||||
<!-- If compiled via github actions, enable force debug -->
|
||||
<set name="FORCE_DEBUG_VERSION" if="GITHUB_BUILD"/>
|
||||
|
||||
<!-- 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" />
|
||||
|
||||
<!-- _______________________________ Libraries ______________________________ -->
|
||||
<haxelib name="lime" /> <!-- Game engine backend -->
|
||||
<haxelib name="openfl" /> <!-- Game engine backend -->
|
||||
@@ -108,7 +111,7 @@
|
||||
<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 release" /> <!-- Video playback -->
|
||||
<haxelib name="hxCodec" if="desktop" /> <!-- Video playback -->
|
||||
|
||||
<haxelib name="json2object" /> <!-- JSON parsing -->
|
||||
<haxelib name="thx.semver" /> <!-- Version string handling -->
|
||||
@@ -194,7 +197,7 @@
|
||||
<!-- Uncomment this to wipe your input settings. -->
|
||||
<!-- <haxedef name="CLEAR_INPUT_SAVE"/> -->
|
||||
|
||||
<section if="debug" unless="NO_REDIRECT_ASSETS_FOLDER || html5">
|
||||
<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!
|
||||
@@ -216,9 +219,9 @@
|
||||
<postbuild haxe="source/Postbuild.hx"/> -->
|
||||
|
||||
<!-- Enable this on platforms which do not support dropping files onto the window. -->
|
||||
<set name="FILE_DROP_UNSUPPORTED" if="mac" />
|
||||
<haxedef name="FILE_DROP_UNSUPPORTED" if="mac" />
|
||||
<section unless="FILE_DROP_UNSUPPORTED">
|
||||
<set name="FILE_DROP_SUPPORTED" />
|
||||
<haxedef name="FILE_DROP_SUPPORTED" />
|
||||
</section>
|
||||
|
||||
<!-- Options for Polymod -->
|
||||
|
||||
+1
-1
Submodule assets updated: e76470d667...5267439151
+10
-39
@@ -23,10 +23,10 @@
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"allowSingleArgParens": false,
|
||||
"allowReturn": false,
|
||||
"allowFunction": false,
|
||||
"allowCurlyBody": false
|
||||
"allowSingleArgParens": true,
|
||||
"allowReturn": true,
|
||||
"allowFunction": true,
|
||||
"allowCurlyBody": true
|
||||
},
|
||||
"type": "ArrowFunction"
|
||||
},
|
||||
@@ -79,8 +79,8 @@
|
||||
{
|
||||
"props": {
|
||||
"ignoreExtern": true,
|
||||
"format": "^[A-Z][A-Z0-9]*(_[A-Z0-9_]+)*$",
|
||||
"tokens": []
|
||||
"format": "^[a-z][A-Z][A-Z0-9]*(_[A-Z0-9_]+)*$",
|
||||
"tokens": ["INLINE", "NOTINLINE"]
|
||||
},
|
||||
"type": "ConstantName"
|
||||
},
|
||||
@@ -178,13 +178,7 @@
|
||||
"fieldType": "BOTH",
|
||||
"requireReturn": true,
|
||||
"ignoreOverride": true,
|
||||
"tokens": [
|
||||
"ABSTRACT_DEF",
|
||||
"CLASS_DEF",
|
||||
"ENUM_DEF",
|
||||
"INTERFACE_DEF",
|
||||
"TYPEDEF_DEF"
|
||||
],
|
||||
"tokens": ["ABSTRACT_DEF", "CLASS_DEF", "ENUM_DEF", "INTERFACE_DEF"],
|
||||
"modifier": "PUBLIC",
|
||||
"excludeNames": ["new", "toString"]
|
||||
},
|
||||
@@ -198,10 +192,6 @@
|
||||
},
|
||||
"type": "FileLength"
|
||||
},
|
||||
{
|
||||
"props": {},
|
||||
"type": "Final"
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"option": "upperCase"
|
||||
@@ -375,7 +365,7 @@
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"max": 1
|
||||
"max": 4
|
||||
},
|
||||
"type": "NestedIfDepth"
|
||||
},
|
||||
@@ -387,7 +377,7 @@
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"option": "nullDefault"
|
||||
"option": "questionMark"
|
||||
},
|
||||
"type": "NullableParameter"
|
||||
},
|
||||
@@ -396,7 +386,6 @@
|
||||
"oldFunctionTypePolicy": "none",
|
||||
"unaryOpPolicy": "none",
|
||||
"intervalOpPolicy": "none",
|
||||
|
||||
"newFunctionTypePolicy": "around",
|
||||
"ternaryOpPolicy": "around",
|
||||
"boolOpPolicy": "around",
|
||||
@@ -589,19 +578,7 @@
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"ignoreEnumAbstractValues": true
|
||||
},
|
||||
"type": "Type"
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"tokens": [
|
||||
"ABSTRACT_DEF",
|
||||
"CLASS_DEF",
|
||||
"ENUM_DEF",
|
||||
"INTERFACE_DEF",
|
||||
"TYPEDEF_DEF"
|
||||
]
|
||||
"tokens": ["CLASS_DEF", "ENUM_DEF", "INTERFACE_DEF", "TYPEDEF_DEF"]
|
||||
},
|
||||
"type": "TypeDocComment"
|
||||
},
|
||||
@@ -630,12 +607,6 @@
|
||||
},
|
||||
"type": "UnusedLocalVar"
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"severity": "IGNORE"
|
||||
},
|
||||
"type": "VariableInitialisation"
|
||||
},
|
||||
{
|
||||
"props": {
|
||||
"typeHintPolicy": "enforce_all",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"name": "flixel",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "4d054bd10b05bb1309a0ba3427ffa5378e0b4b99",
|
||||
"ref": "5823c46b4e7410372d58b99f8d5c52fc18b3cb3d",
|
||||
"url": "https://github.com/FunkinCrew/flixel"
|
||||
},
|
||||
{
|
||||
@@ -54,14 +54,14 @@
|
||||
"name": "haxeui-core",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "8a7846b",
|
||||
"ref": "0212d8fdfcafeb5f0d5a41e1ddba8ff21d0e183b",
|
||||
"url": "https://github.com/haxeui/haxeui-core"
|
||||
},
|
||||
{
|
||||
"name": "haxeui-flixel",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "e9f880522e27134b29df4067f82df7d7e5237b70",
|
||||
"ref": "63a906a6148958dbfde8c7b48d90b0693767fd95",
|
||||
"url": "https://github.com/haxeui/haxeui-flixel"
|
||||
},
|
||||
{
|
||||
@@ -73,8 +73,8 @@
|
||||
"name": "hxCodec",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "c8c47e706ad82a423783006ed901b6d93c89a421",
|
||||
"url": "https://github.com/polybiusproxy/hxCodec"
|
||||
"ref": "387e1665d6feb5762358134f168e6ebfe46acec8",
|
||||
"url": "https://github.com/FunkinCrew/hxCodec"
|
||||
},
|
||||
{
|
||||
"name": "hxcpp",
|
||||
@@ -146,7 +146,7 @@
|
||||
"name": "polymod",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "d5a3b8995f64d20b95f844454e8c3b38c3d3a9fa",
|
||||
"ref": "8553b800965f225bb14c7ab8f04bfa9cdec362ac",
|
||||
"url": "https://github.com/larsiusprime/polymod"
|
||||
},
|
||||
{
|
||||
|
||||
+30
-31
@@ -1,31 +1,30 @@
|
||||
{
|
||||
"disableFormatting": false,
|
||||
|
||||
"indentation": {
|
||||
"character": " ",
|
||||
"tabWidth": 2,
|
||||
"indentCaseLabels": true
|
||||
},
|
||||
|
||||
"lineEnds": {
|
||||
"anonFunctionCurly": {
|
||||
"emptyCurly": "break",
|
||||
"leftCurly": "after",
|
||||
"rightCurly": "both"
|
||||
},
|
||||
"leftCurly": "both",
|
||||
"rightCurly": "both"
|
||||
},
|
||||
|
||||
"sameLine": {
|
||||
"ifBody": "same",
|
||||
"ifElse": "next",
|
||||
"doWhile": "next",
|
||||
"tryBody": "next",
|
||||
"tryCatch": "next"
|
||||
},
|
||||
|
||||
"whitespace": {
|
||||
"switchPolicy": "around"
|
||||
}
|
||||
}
|
||||
{
|
||||
"disableFormatting": false,
|
||||
|
||||
"indentation": {
|
||||
"character": " ",
|
||||
"tabWidth": 2,
|
||||
"indentCaseLabels": true
|
||||
},
|
||||
"lineEnds": {
|
||||
"anonFunctionCurly": {
|
||||
"emptyCurly": "break",
|
||||
"leftCurly": "after",
|
||||
"rightCurly": "both"
|
||||
},
|
||||
"leftCurly": "both",
|
||||
"rightCurly": "both"
|
||||
},
|
||||
|
||||
"sameLine": {
|
||||
"ifBody": "same",
|
||||
"ifElse": "next",
|
||||
"doWhile": "next",
|
||||
"tryBody": "next",
|
||||
"tryCatch": "next"
|
||||
},
|
||||
|
||||
"whitespace": {
|
||||
"switchPolicy": "around"
|
||||
}
|
||||
}
|
||||
|
||||
+24
-12
@@ -11,9 +11,11 @@ import openfl.display.Sprite;
|
||||
import openfl.events.Event;
|
||||
import openfl.Lib;
|
||||
import openfl.media.Video;
|
||||
import funkin.util.CLIUtil;
|
||||
import openfl.net.NetStream;
|
||||
|
||||
/**
|
||||
* The main class which initializes HaxeFlixel and starts the game in its initial state.
|
||||
*/
|
||||
class Main extends Sprite
|
||||
{
|
||||
var gameWidth:Int = 1280; // Width of the game in pixels (might be less / more in actual pixels depending on your zoom).
|
||||
@@ -76,35 +78,45 @@ class Main extends Sprite
|
||||
var netStream:NetStream;
|
||||
var overlay:Sprite;
|
||||
|
||||
/**
|
||||
* A frame counter displayed at the top left.
|
||||
*/
|
||||
public static var fpsCounter:FPS;
|
||||
|
||||
/**
|
||||
* A RAM counter displayed at the top left.
|
||||
*/
|
||||
public static var memoryCounter:MemoryCounter;
|
||||
|
||||
function setupGame():Void
|
||||
{
|
||||
/**
|
||||
* The `zoom` argument of FlxGame was removed in the dev branch of Flixel,
|
||||
* since it was considered confusing and unintuitive.
|
||||
* If you want to change how the game scales when you resize the window,
|
||||
* you can use `FlxG.scaleMode`.
|
||||
* -Eric
|
||||
*/
|
||||
|
||||
initHaxeUI();
|
||||
|
||||
// addChild gets called by the user settings code.
|
||||
fpsCounter = new FPS(10, 3, 0xFFFFFF);
|
||||
// addChild(fpsCounter); // Handled by Preferences.init
|
||||
|
||||
#if !html5
|
||||
// addChild gets called by the user settings code.
|
||||
// TODO: disabled on HTML5 (todo: find another method that works?)
|
||||
memoryCounter = new MemoryCounter(10, 13, 0xFFFFFF);
|
||||
// addChild(memoryCounter);
|
||||
#end
|
||||
|
||||
// George recommends binding the save before FlxGame is created.
|
||||
Save.load();
|
||||
var game:FlxGame = new FlxGame(gameWidth, gameHeight, initialState, framerate, framerate, skipSplash, startFullscreen);
|
||||
|
||||
addChild(new FlxGame(gameWidth, gameHeight, initialState, framerate, framerate, skipSplash, startFullscreen));
|
||||
// FlxG.game._customSoundTray wants just the class, it calls new from
|
||||
// create() in there, which gets called when it's added to stage
|
||||
// which is why it needs to be added before addChild(game) here
|
||||
@:privateAccess
|
||||
game._customSoundTray = funkin.ui.options.FunkinSoundTray;
|
||||
|
||||
addChild(game);
|
||||
|
||||
#if hxcpp_debug_server
|
||||
trace('hxcpp_debug_server is enabled! You can now connect to the game with a debugger.');
|
||||
#else
|
||||
trace('hxcpp_debug_server is disabled! This build does not support debugging.');
|
||||
#end
|
||||
}
|
||||
|
||||
|
||||
+33
-4
@@ -1,11 +1,40 @@
|
||||
package source; // Yeah, I know...
|
||||
|
||||
import sys.FileSystem;
|
||||
import sys.io.File;
|
||||
|
||||
/**
|
||||
* A script which executes after the game is built.
|
||||
*/
|
||||
class Postbuild
|
||||
{
|
||||
static function main()
|
||||
{
|
||||
trace('Postbuild');
|
||||
static inline final BUILD_TIME_FILE:String = '.build_time';
|
||||
|
||||
// TODO: Maybe put a 'Build took X seconds' message here?
|
||||
static function main():Void
|
||||
{
|
||||
printBuildTime();
|
||||
}
|
||||
|
||||
static function printBuildTime():Void
|
||||
{
|
||||
// get buildEnd before fs operations since they are blocking
|
||||
var end:Float = Sys.time();
|
||||
if (FileSystem.exists(BUILD_TIME_FILE))
|
||||
{
|
||||
var fi:sys.io.FileInput = File.read(BUILD_TIME_FILE);
|
||||
var start:Float = fi.readDouble();
|
||||
fi.close();
|
||||
|
||||
sys.FileSystem.deleteFile(BUILD_TIME_FILE);
|
||||
|
||||
var buildTime:Float = roundToTwoDecimals(end - start);
|
||||
|
||||
trace('Build took: ${buildTime} seconds');
|
||||
}
|
||||
}
|
||||
|
||||
static function roundToTwoDecimals(value:Float):Float
|
||||
{
|
||||
return Math.round(value * 100) / 100;
|
||||
}
|
||||
}
|
||||
|
||||
+18
-2
@@ -1,9 +1,25 @@
|
||||
package source; // Yeah, I know...
|
||||
|
||||
import sys.io.File;
|
||||
|
||||
/**
|
||||
* A script which executes before the game is built.
|
||||
*/
|
||||
class Prebuild
|
||||
{
|
||||
static function main()
|
||||
static inline final BUILD_TIME_FILE:String = '.build_time';
|
||||
|
||||
static function main():Void
|
||||
{
|
||||
trace('Prebuild');
|
||||
saveBuildTime();
|
||||
trace('Building...');
|
||||
}
|
||||
|
||||
static function saveBuildTime():Void
|
||||
{
|
||||
var fo:sys.io.FileOutput = File.write(BUILD_TIME_FILE);
|
||||
var now:Float = Sys.time();
|
||||
fo.writeDouble(now);
|
||||
fo.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,234 +0,0 @@
|
||||
package flixel.addons.transition;
|
||||
|
||||
import flixel.FlxSubState;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
|
||||
/**
|
||||
* A `FlxSubState` which can perform visual transitions
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* First, extend `FlxTransitionableSubState` as ie, `FooState`.
|
||||
*
|
||||
* Method 1:
|
||||
*
|
||||
* ```haxe
|
||||
* var in:TransitionData = new TransitionData(...); // add your data where "..." is
|
||||
* var out:TransitionData = new TransitionData(...);
|
||||
*
|
||||
* FlxG.switchState(() -> new FooState(in,out));
|
||||
* ```
|
||||
*
|
||||
* Method 2:
|
||||
*
|
||||
* ```haxe
|
||||
* FlxTransitionableSubState.defaultTransIn = new TransitionData(...);
|
||||
* FlxTransitionableSubState.defaultTransOut = new TransitionData(...);
|
||||
*
|
||||
* FlxG.switchState(() -> new FooState());
|
||||
* ```
|
||||
*/
|
||||
class FlxTransitionableSubState extends FlxSubState
|
||||
{
|
||||
// global default transitions for ALL states, used if transIn/transOut are null
|
||||
public static var defaultTransIn(get, set):TransitionData;
|
||||
|
||||
static function get_defaultTransIn():TransitionData
|
||||
{
|
||||
return FlxTransitionableState.defaultTransIn;
|
||||
}
|
||||
|
||||
static function set_defaultTransIn(value:TransitionData):TransitionData
|
||||
{
|
||||
return FlxTransitionableState.defaultTransIn = value;
|
||||
}
|
||||
|
||||
public static var defaultTransOut(get, set):TransitionData;
|
||||
|
||||
static function get_defaultTransOut():TransitionData
|
||||
{
|
||||
return FlxTransitionableState.defaultTransOut;
|
||||
}
|
||||
|
||||
static function set_defaultTransOut(value:TransitionData):TransitionData
|
||||
{
|
||||
return FlxTransitionableState.defaultTransOut = value;
|
||||
}
|
||||
|
||||
public static var skipNextTransIn(get, set):Bool;
|
||||
|
||||
static function get_skipNextTransIn():Bool
|
||||
{
|
||||
return FlxTransitionableState.skipNextTransIn;
|
||||
}
|
||||
|
||||
static function set_skipNextTransIn(value:Bool):Bool
|
||||
{
|
||||
return FlxTransitionableState.skipNextTransIn = value;
|
||||
}
|
||||
|
||||
public static var skipNextTransOut(get, set):Bool;
|
||||
|
||||
static function get_skipNextTransOut():Bool
|
||||
{
|
||||
return FlxTransitionableState.skipNextTransOut;
|
||||
}
|
||||
|
||||
static function set_skipNextTransOut(value:Bool):Bool
|
||||
{
|
||||
return FlxTransitionableState.skipNextTransOut = value;
|
||||
}
|
||||
|
||||
// beginning & ending transitions for THIS state:
|
||||
public var transIn:TransitionData;
|
||||
public var transOut:TransitionData;
|
||||
|
||||
public var hasTransIn(get, never):Bool;
|
||||
public var hasTransOut(get, never):Bool;
|
||||
|
||||
/**
|
||||
* Create a state with the ability to do visual transitions
|
||||
* @param TransIn Plays when the state begins
|
||||
* @param TransOut Plays when the state ends
|
||||
*/
|
||||
public function new(?TransIn:TransitionData, ?TransOut:TransitionData)
|
||||
{
|
||||
transIn = TransIn;
|
||||
transOut = TransOut;
|
||||
|
||||
if (transIn == null && defaultTransIn != null)
|
||||
{
|
||||
transIn = defaultTransIn;
|
||||
}
|
||||
if (transOut == null && defaultTransOut != null)
|
||||
{
|
||||
transOut = defaultTransOut;
|
||||
}
|
||||
super();
|
||||
}
|
||||
|
||||
override function destroy():Void
|
||||
{
|
||||
super.destroy();
|
||||
transIn = null;
|
||||
transOut = null;
|
||||
_onExit = null;
|
||||
}
|
||||
|
||||
override function create():Void
|
||||
{
|
||||
super.create();
|
||||
transitionIn();
|
||||
}
|
||||
|
||||
override function startOutro(onOutroComplete:() -> Void)
|
||||
{
|
||||
if (!hasTransOut) onOutroComplete();
|
||||
else if (!_exiting)
|
||||
{
|
||||
// play the exit transition, and when it's done call FlxG.switchState
|
||||
_exiting = true;
|
||||
transitionOut(onOutroComplete);
|
||||
|
||||
if (skipNextTransOut)
|
||||
{
|
||||
skipNextTransOut = false;
|
||||
finishTransOut();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the in-transition. Can be called manually at any time.
|
||||
*/
|
||||
public function transitionIn():Void
|
||||
{
|
||||
if (transIn != null && transIn.type != NONE)
|
||||
{
|
||||
if (skipNextTransIn)
|
||||
{
|
||||
skipNextTransIn = false;
|
||||
if (finishTransIn != null)
|
||||
{
|
||||
finishTransIn();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var _trans = createTransition(transIn);
|
||||
|
||||
_trans.setStatus(FULL);
|
||||
openSubState(_trans);
|
||||
|
||||
_trans.finishCallback = finishTransIn;
|
||||
_trans.start(OUT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the out-transition. Can be called manually at any time.
|
||||
*/
|
||||
public function transitionOut(?OnExit:Void->Void):Void
|
||||
{
|
||||
_onExit = OnExit;
|
||||
if (hasTransOut)
|
||||
{
|
||||
var _trans = createTransition(transOut);
|
||||
|
||||
_trans.setStatus(EMPTY);
|
||||
openSubState(_trans);
|
||||
|
||||
_trans.finishCallback = finishTransOut;
|
||||
_trans.start(IN);
|
||||
}
|
||||
else
|
||||
{
|
||||
_onExit();
|
||||
}
|
||||
}
|
||||
|
||||
var transOutFinished:Bool = false;
|
||||
|
||||
var _exiting:Bool = false;
|
||||
var _onExit:Void->Void;
|
||||
|
||||
function get_hasTransIn():Bool
|
||||
{
|
||||
return transIn != null && transIn.type != NONE;
|
||||
}
|
||||
|
||||
function get_hasTransOut():Bool
|
||||
{
|
||||
return transOut != null && transOut.type != NONE;
|
||||
}
|
||||
|
||||
function createTransition(data:TransitionData):Transition
|
||||
{
|
||||
return switch (data.type)
|
||||
{
|
||||
case TILES: new Transition(data);
|
||||
case FADE: new Transition(data);
|
||||
default: null;
|
||||
}
|
||||
}
|
||||
|
||||
function finishTransIn()
|
||||
{
|
||||
closeSubState();
|
||||
}
|
||||
|
||||
function finishTransOut()
|
||||
{
|
||||
transOutFinished = true;
|
||||
|
||||
if (!_exiting)
|
||||
{
|
||||
closeSubState();
|
||||
}
|
||||
|
||||
if (_onExit != null)
|
||||
{
|
||||
_onExit();
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
-22
@@ -3,7 +3,6 @@ package funkin;
|
||||
import funkin.util.Constants;
|
||||
import flixel.util.FlxSignal;
|
||||
import flixel.math.FlxMath;
|
||||
import funkin.play.song.Song.SongDifficulty;
|
||||
import funkin.data.song.SongData.SongTimeChange;
|
||||
import funkin.data.song.SongDataUtils;
|
||||
import funkin.save.Save;
|
||||
@@ -142,6 +141,9 @@ class Conductor
|
||||
return beatLengthMs / timeSignatureNumerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* The numerator for the current time signature (the `3` in `3/4`).
|
||||
*/
|
||||
public var timeSignatureNumerator(get, never):Int;
|
||||
|
||||
function get_timeSignatureNumerator():Int
|
||||
@@ -151,6 +153,9 @@ class Conductor
|
||||
return currentTimeChange.timeSignatureNum;
|
||||
}
|
||||
|
||||
/**
|
||||
* The denominator for the current time signature (the `4` in `3/4`).
|
||||
*/
|
||||
public var timeSignatureDenominator(get, never):Int;
|
||||
|
||||
function get_timeSignatureDenominator():Int
|
||||
@@ -284,7 +289,7 @@ class Conductor
|
||||
* WARNING: Avoid this for things like setting the BPM of the title screen music,
|
||||
* you should have a metadata file for it instead.
|
||||
*/
|
||||
public function forceBPM(?bpm:Float = null)
|
||||
public function forceBPM(?bpm:Float):Void
|
||||
{
|
||||
if (bpm != null)
|
||||
{
|
||||
@@ -292,7 +297,7 @@ class Conductor
|
||||
}
|
||||
else
|
||||
{
|
||||
// trace('[CONDUCTOR] Resetting BPM to default');
|
||||
trace('[CONDUCTOR] Resetting BPM to default');
|
||||
}
|
||||
|
||||
this.bpmOverride = bpm;
|
||||
@@ -310,15 +315,15 @@ class Conductor
|
||||
{
|
||||
if (songPos == null)
|
||||
{
|
||||
// Take into account instrumental and file format song offsets.
|
||||
songPos = (FlxG.sound.music != null) ? (FlxG.sound.music.time + instrumentalOffset + formatOffset + audioVisualOffset) : 0.0;
|
||||
songPos = (FlxG.sound.music != null) ? FlxG.sound.music.time : 0.0;
|
||||
}
|
||||
else
|
||||
songPos += applyOffsets ? instrumentalOffset + formatOffset + audioVisualOffset : 0;
|
||||
|
||||
var oldMeasure = this.currentMeasure;
|
||||
var oldBeat = this.currentBeat;
|
||||
var oldStep = this.currentStep;
|
||||
// Take into account instrumental and file format song offsets.
|
||||
songPos += applyOffsets ? (instrumentalOffset + formatOffset + audioVisualOffset) : 0;
|
||||
|
||||
var oldMeasure:Float = this.currentMeasure;
|
||||
var oldBeat:Float = this.currentBeat;
|
||||
var oldStep:Float = this.currentStep;
|
||||
|
||||
// Set the song position we are at (for purposes of calculating note positions, etc).
|
||||
this.songPosition = songPos;
|
||||
@@ -409,39 +414,43 @@ class Conductor
|
||||
return this.songPosition;
|
||||
}
|
||||
|
||||
public function mapTimeChanges(songTimeChanges:Array<SongTimeChange>)
|
||||
/**
|
||||
* Apply the `SongTimeChange` data from the song metadata to this Conductor.
|
||||
* @param songTimeChanges The SongTimeChanges.
|
||||
*/
|
||||
public function mapTimeChanges(songTimeChanges:Array<SongTimeChange>):Void
|
||||
{
|
||||
timeChanges = [];
|
||||
|
||||
// Sort in place just in case it's out of order.
|
||||
SongDataUtils.sortTimeChanges(songTimeChanges);
|
||||
|
||||
for (currentTimeChange in songTimeChanges)
|
||||
for (songTimeChange in songTimeChanges)
|
||||
{
|
||||
// TODO: Maybe handle this different?
|
||||
// Do we care about BPM at negative timestamps?
|
||||
// Without any custom handling, `currentStepTime` becomes non-zero at `songPosition = 0`.
|
||||
if (currentTimeChange.timeStamp < 0.0) currentTimeChange.timeStamp = 0.0;
|
||||
if (songTimeChange.timeStamp < 0.0) songTimeChange.timeStamp = 0.0;
|
||||
|
||||
if (currentTimeChange.timeStamp <= 0.0)
|
||||
if (songTimeChange.timeStamp <= 0.0)
|
||||
{
|
||||
currentTimeChange.beatTime = 0.0;
|
||||
songTimeChange.beatTime = 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Calculate the beat time of this timestamp.
|
||||
currentTimeChange.beatTime = 0.0;
|
||||
songTimeChange.beatTime = 0.0;
|
||||
|
||||
if (currentTimeChange.timeStamp > 0.0 && timeChanges.length > 0)
|
||||
if (songTimeChange.timeStamp > 0.0 && timeChanges.length > 0)
|
||||
{
|
||||
var prevTimeChange:SongTimeChange = timeChanges[timeChanges.length - 1];
|
||||
currentTimeChange.beatTime = FlxMath.roundDecimal(prevTimeChange.beatTime
|
||||
+ ((currentTimeChange.timeStamp - prevTimeChange.timeStamp) * prevTimeChange.bpm / Constants.SECS_PER_MIN / Constants.MS_PER_SEC),
|
||||
songTimeChange.beatTime = FlxMath.roundDecimal(prevTimeChange.beatTime
|
||||
+ ((songTimeChange.timeStamp - prevTimeChange.timeStamp) * prevTimeChange.bpm / Constants.SECS_PER_MIN / Constants.MS_PER_SEC),
|
||||
4);
|
||||
}
|
||||
}
|
||||
|
||||
timeChanges.push(currentTimeChange);
|
||||
timeChanges.push(songTimeChange);
|
||||
}
|
||||
|
||||
if (timeChanges.length > 0)
|
||||
@@ -455,6 +464,8 @@ class Conductor
|
||||
|
||||
/**
|
||||
* Given a time in milliseconds, return a time in steps.
|
||||
* @param ms The time in milliseconds.
|
||||
* @return The time in steps.
|
||||
*/
|
||||
public function getTimeInSteps(ms:Float):Float
|
||||
{
|
||||
@@ -484,7 +495,7 @@ class Conductor
|
||||
|
||||
var lastStepLengthMs:Float = ((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) / timeSignatureNumerator;
|
||||
var resultFractionalStep:Float = (ms - lastTimeChange.timeStamp) / lastStepLengthMs;
|
||||
resultStep += resultFractionalStep; // Math.floor();
|
||||
resultStep += resultFractionalStep;
|
||||
|
||||
return resultStep;
|
||||
}
|
||||
@@ -492,6 +503,8 @@ class Conductor
|
||||
|
||||
/**
|
||||
* Given a time in steps and fractional steps, return a time in milliseconds.
|
||||
* @param stepTime The time in steps.
|
||||
* @return The time in milliseconds.
|
||||
*/
|
||||
public function getStepTimeInMs(stepTime:Float):Float
|
||||
{
|
||||
@@ -528,6 +541,8 @@ class Conductor
|
||||
|
||||
/**
|
||||
* Given a time in beats and fractional beats, return a time in milliseconds.
|
||||
* @param beatTime The time in beats.
|
||||
* @return The time in milliseconds.
|
||||
*/
|
||||
public function getBeatTimeInMs(beatTime:Float):Float
|
||||
{
|
||||
@@ -563,7 +578,8 @@ class Conductor
|
||||
}
|
||||
|
||||
/**
|
||||
* @param conductorToUse defaults to Conductor.instance if null
|
||||
* Adds Conductor fields to the Flixel debugger variable display.
|
||||
* @param conductorToUse The conductor to use. Defaults to `Conductor.instance`.
|
||||
*/
|
||||
public static function watchQuick(?conductorToUse:Conductor):Void
|
||||
{
|
||||
|
||||
@@ -5,7 +5,42 @@ package funkin;
|
||||
*/
|
||||
class Highscore
|
||||
{
|
||||
/**
|
||||
* Keeps track of notes hit for the current song
|
||||
* and how accurate you were with each note (bad, missed, shit, etc.)
|
||||
*/
|
||||
public static var tallies:Tallies = new Tallies();
|
||||
|
||||
/**
|
||||
* Keeps track of notes hit for the current WEEK / level
|
||||
* for use with storymode, or likely any other "playlist" esque option
|
||||
*/
|
||||
public static var talliesLevel:Tallies = new Tallies();
|
||||
|
||||
/**
|
||||
* Produces a new Tallies object which represents the sum of two existing Tallies
|
||||
* @param newTally The first tally
|
||||
* @param baseTally The second tally
|
||||
* @return The combined tally
|
||||
*/
|
||||
public static function combineTallies(newTally:Tallies, baseTally:Tallies):Tallies
|
||||
{
|
||||
var combinedTally:Tallies = new Tallies();
|
||||
combinedTally.missed = newTally.missed + baseTally.missed;
|
||||
combinedTally.shit = newTally.shit + baseTally.shit;
|
||||
combinedTally.bad = newTally.bad + baseTally.bad;
|
||||
combinedTally.good = newTally.good + baseTally.good;
|
||||
combinedTally.sick = newTally.sick + baseTally.sick;
|
||||
combinedTally.totalNotes = newTally.totalNotes + baseTally.totalNotes;
|
||||
combinedTally.totalNotesHit = newTally.totalNotesHit + baseTally.totalNotesHit;
|
||||
|
||||
// Current combo = use most recent.
|
||||
combinedTally.combo = newTally.combo;
|
||||
// Max combo = use maximum value.
|
||||
combinedTally.maxCombo = Std.int(Math.max(newTally.maxCombo, baseTally.maxCombo));
|
||||
|
||||
return combinedTally;
|
||||
}
|
||||
}
|
||||
|
||||
@:forward
|
||||
@@ -29,6 +64,9 @@ abstract Tallies(RawTallies)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A structure object containing the data for highscore tallies.
|
||||
*/
|
||||
typedef RawTallies =
|
||||
{
|
||||
var combo:Int;
|
||||
@@ -51,7 +89,7 @@ typedef RawTallies =
|
||||
var totalNotesHit:Int;
|
||||
|
||||
/**
|
||||
* How many notes PASSED BY AND/OR HIT!!!
|
||||
* How many notes in the current chart
|
||||
*/
|
||||
var totalNotes:Int;
|
||||
}
|
||||
|
||||
+110
-84
@@ -12,7 +12,6 @@ import flixel.math.FlxRect;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.system.debug.log.LogStyle;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.ui.options.PreferencesMenu;
|
||||
import funkin.util.macro.MacroUtil;
|
||||
import funkin.util.WindowUtil;
|
||||
import funkin.play.PlayStatePlaylist;
|
||||
@@ -24,13 +23,15 @@ import funkin.data.stage.StageRegistry;
|
||||
import funkin.data.dialogue.ConversationRegistry;
|
||||
import funkin.data.dialogue.DialogueBoxRegistry;
|
||||
import funkin.data.dialogue.SpeakerRegistry;
|
||||
import funkin.data.freeplay.AlbumRegistry;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.ui.title.TitleState;
|
||||
import funkin.util.CLIUtil;
|
||||
import funkin.util.CLIUtil.CLIParams;
|
||||
import funkin.ui.transition.LoadingState;
|
||||
import funkin.util.TimerUtil;
|
||||
import funkin.util.TrackerUtil;
|
||||
#if discord_rpc
|
||||
import Discord.DiscordClient;
|
||||
#end
|
||||
@@ -66,7 +67,7 @@ class InitState extends FlxState
|
||||
/**
|
||||
* Setup a bunch of important Flixel stuff.
|
||||
*/
|
||||
function setupShit()
|
||||
function setupShit():Void
|
||||
{
|
||||
//
|
||||
// GAME SETUP
|
||||
@@ -88,70 +89,7 @@ class InitState extends FlxState
|
||||
// Set the game to a lower frame rate while it is in the background.
|
||||
FlxG.game.focusLostFramerate = 30;
|
||||
|
||||
//
|
||||
// FLIXEL DEBUG SETUP
|
||||
//
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
// Disable using ~ to open the console (we use that for the Editor menu)
|
||||
FlxG.debugger.toggleKeys = [F2];
|
||||
|
||||
// Adds an additional Close Debugger button.
|
||||
// This big obnoxious white button is for MOBILE, so that you can press it
|
||||
// easily with your finger when debug bullshit pops up during testing lol!
|
||||
FlxG.debugger.addButton(LEFT, new BitmapData(200, 200), function() {
|
||||
FlxG.debugger.visible = false;
|
||||
});
|
||||
|
||||
// Adds a red button to the debugger.
|
||||
// This pauses the game AND the music! This ensures the Conductor stops.
|
||||
FlxG.debugger.addButton(CENTER, new BitmapData(20, 20, true, 0xFFCC2233), function() {
|
||||
if (FlxG.vcr.paused)
|
||||
{
|
||||
FlxG.vcr.resume();
|
||||
|
||||
for (snd in FlxG.sound.list)
|
||||
{
|
||||
snd.resume();
|
||||
}
|
||||
|
||||
FlxG.sound.music.resume();
|
||||
}
|
||||
else
|
||||
{
|
||||
FlxG.vcr.pause();
|
||||
|
||||
for (snd in FlxG.sound.list)
|
||||
{
|
||||
snd.pause();
|
||||
}
|
||||
|
||||
FlxG.sound.music.pause();
|
||||
}
|
||||
});
|
||||
|
||||
// Adds a blue button to the debugger.
|
||||
// This skips forward in the song.
|
||||
FlxG.debugger.addButton(CENTER, new BitmapData(20, 20, true, 0xFF2222CC), function() {
|
||||
FlxG.game.debugger.vcr.onStep();
|
||||
|
||||
for (snd in FlxG.sound.list)
|
||||
{
|
||||
snd.pause();
|
||||
snd.time += FlxG.elapsed * 1000;
|
||||
}
|
||||
|
||||
FlxG.sound.music.pause();
|
||||
FlxG.sound.music.time += FlxG.elapsed * 1000;
|
||||
});
|
||||
#end
|
||||
|
||||
// Make errors and warnings less annoying.
|
||||
#if FORCE_DEBUG_VERSION
|
||||
LogStyle.ERROR.openConsole = false;
|
||||
LogStyle.ERROR.errorSound = null;
|
||||
LogStyle.WARNING.openConsole = false;
|
||||
LogStyle.WARNING.errorSound = null;
|
||||
#end
|
||||
setupFlixelDebug();
|
||||
|
||||
//
|
||||
// FLIXEL TRANSITIONS
|
||||
@@ -202,8 +140,12 @@ class InitState extends FlxState
|
||||
//
|
||||
// Plugins provide a useful interface for globally active Flixel objects,
|
||||
// that receive update events regardless of the current state.
|
||||
// TODO: Move Module behavior to a Flixel plugin.
|
||||
// TODO: Move scripted Module behavior to a Flixel plugin.
|
||||
#if debug
|
||||
funkin.util.plugins.MemoryGCPlugin.initialize();
|
||||
#end
|
||||
funkin.util.plugins.EvacuateDebugPlugin.initialize();
|
||||
funkin.util.plugins.ForceCrashPlugin.initialize();
|
||||
funkin.util.plugins.ReloadAssetsDebugPlugin.initialize();
|
||||
funkin.util.plugins.ScreenshotPlugin.initialize();
|
||||
funkin.util.plugins.VolumePlugin.initialize();
|
||||
@@ -216,7 +158,7 @@ class InitState extends FlxState
|
||||
// NOTE: Registries must be imported and not referenced with fully qualified names,
|
||||
// to ensure build macros work properly.
|
||||
trace('Parsing game data...');
|
||||
var perfStart = haxe.Timer.stamp();
|
||||
var perfStart:Float = TimerUtil.start();
|
||||
SongEventRegistry.loadEventCache(); // SongEventRegistry is structured differently so it's not a BaseRegistry.
|
||||
SongRegistry.instance.loadEntries();
|
||||
LevelRegistry.instance.loadEntries();
|
||||
@@ -224,18 +166,18 @@ class InitState extends FlxState
|
||||
ConversationRegistry.instance.loadEntries();
|
||||
DialogueBoxRegistry.instance.loadEntries();
|
||||
SpeakerRegistry.instance.loadEntries();
|
||||
AlbumRegistry.instance.loadEntries();
|
||||
StageRegistry.instance.loadEntries();
|
||||
|
||||
// TODO: CharacterDataParser doesn't use json2object, so it's way slower than the other parsers.
|
||||
CharacterDataParser.loadCharacterCache(); // TODO: Migrate characters to BaseRegistry.
|
||||
// TODO: CharacterDataParser doesn't use json2object, so it's way slower than the other parsers and more prone to syntax errors.
|
||||
// Move it to use a BaseRegistry.
|
||||
CharacterDataParser.loadCharacterCache();
|
||||
|
||||
ModuleHandler.buildModuleCallbacks();
|
||||
ModuleHandler.loadModuleCache();
|
||||
ModuleHandler.callOnCreate();
|
||||
|
||||
var perfEnd = haxe.Timer.stamp();
|
||||
|
||||
trace('Parsing game data took ${Math.floor((perfEnd - perfStart) * 1000)}ms.');
|
||||
trace('Parsing game data took: ${TimerUtil.ms(perfStart)}');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -247,25 +189,35 @@ class InitState extends FlxState
|
||||
*/
|
||||
function startGame():Void
|
||||
{
|
||||
#if SONG // -DSONG=bopeebo
|
||||
#if SONG
|
||||
// -DSONG=bopeebo
|
||||
startSong(defineSong(), defineDifficulty());
|
||||
#elseif LEVEL // -DLEVEL=week1 -DDIFFICULTY=hard
|
||||
#elseif LEVEL
|
||||
// -DLEVEL=week1 -DDIFFICULTY=hard
|
||||
startLevel(defineLevel(), defineDifficulty());
|
||||
#elseif FREEPLAY // -DFREEPLAY
|
||||
#elseif FREEPLAY
|
||||
// -DFREEPLAY
|
||||
FlxG.switchState(() -> new funkin.ui.freeplay.FreeplayState());
|
||||
#elseif DIALOGUE // -DDIALOGUE
|
||||
#elseif DIALOGUE
|
||||
// -DDIALOGUE
|
||||
FlxG.switchState(() -> new funkin.ui.debug.dialogue.ConversationDebugState());
|
||||
#elseif ANIMATE // -DANIMATE
|
||||
#elseif ANIMATE
|
||||
// -DANIMATE
|
||||
FlxG.switchState(() -> new funkin.ui.debug.anim.FlxAnimateTest());
|
||||
#elseif WAVEFORM // -DWAVEFORM
|
||||
#elseif WAVEFORM
|
||||
// -DWAVEFORM
|
||||
FlxG.switchState(() -> new funkin.ui.debug.WaveformTestState());
|
||||
#elseif CHARTING // -DCHARTING
|
||||
#elseif CHARTING
|
||||
// -DCHARTING
|
||||
FlxG.switchState(() -> new funkin.ui.debug.charting.ChartEditorState());
|
||||
#elseif STAGEBUILD // -DSTAGEBUILD
|
||||
#elseif STAGEBUILD
|
||||
// -DSTAGEBUILD
|
||||
FlxG.switchState(() -> new funkin.ui.debug.stage.StageBuilderState());
|
||||
#elseif ANIMDEBUG // -DANIMDEBUG
|
||||
#elseif ANIMDEBUG
|
||||
// -DANIMDEBUG
|
||||
FlxG.switchState(() -> new funkin.ui.debug.anim.DebugBoundingState());
|
||||
#elseif LATENCY // -DLATENCY
|
||||
#elseif LATENCY
|
||||
// -DLATENCY
|
||||
FlxG.switchState(() -> new funkin.LatencyState());
|
||||
#else
|
||||
startGameNormally();
|
||||
@@ -346,6 +298,80 @@ class InitState extends FlxState
|
||||
});
|
||||
}
|
||||
|
||||
function setupFlixelDebug():Void
|
||||
{
|
||||
//
|
||||
// FLIXEL DEBUG SETUP
|
||||
//
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
// Make errors and warnings less annoying.
|
||||
// Forcing this always since I have never been happy to have the debugger to pop up
|
||||
LogStyle.ERROR.openConsole = false;
|
||||
LogStyle.ERROR.errorSound = null;
|
||||
LogStyle.WARNING.openConsole = false;
|
||||
LogStyle.WARNING.errorSound = null;
|
||||
|
||||
// Disable using ~ to open the console (we use that for the Editor menu)
|
||||
FlxG.debugger.toggleKeys = [F2];
|
||||
TrackerUtil.initTrackers();
|
||||
// Adds an additional Close Debugger button.
|
||||
// This big obnoxious white button is for MOBILE, so that you can press it
|
||||
// easily with your finger when debug bullshit pops up during testing lol!
|
||||
FlxG.debugger.addButton(LEFT, new BitmapData(200, 200), function() {
|
||||
FlxG.debugger.visible = false;
|
||||
|
||||
// Make errors and warnings less annoying.
|
||||
// Forcing this always since I have never been happy to have the debugger to pop up
|
||||
LogStyle.ERROR.openConsole = false;
|
||||
LogStyle.ERROR.errorSound = null;
|
||||
LogStyle.WARNING.openConsole = false;
|
||||
LogStyle.WARNING.errorSound = null;
|
||||
});
|
||||
|
||||
// Adds a red button to the debugger.
|
||||
// This pauses the game AND the music! This ensures the Conductor stops.
|
||||
FlxG.debugger.addButton(CENTER, new BitmapData(20, 20, true, 0xFFCC2233), function() {
|
||||
if (FlxG.vcr.paused)
|
||||
{
|
||||
FlxG.vcr.resume();
|
||||
|
||||
for (snd in FlxG.sound.list)
|
||||
{
|
||||
snd.resume();
|
||||
}
|
||||
|
||||
FlxG.sound.music.resume();
|
||||
}
|
||||
else
|
||||
{
|
||||
FlxG.vcr.pause();
|
||||
|
||||
for (snd in FlxG.sound.list)
|
||||
{
|
||||
snd.pause();
|
||||
}
|
||||
|
||||
FlxG.sound.music.pause();
|
||||
}
|
||||
});
|
||||
|
||||
// Adds a blue button to the debugger.
|
||||
// This skips forward in the song.
|
||||
FlxG.debugger.addButton(CENTER, new BitmapData(20, 20, true, 0xFF2222CC), function() {
|
||||
FlxG.game.debugger.vcr.onStep();
|
||||
|
||||
for (snd in FlxG.sound.list)
|
||||
{
|
||||
snd.pause();
|
||||
snd.time += FlxG.elapsed * 1000;
|
||||
}
|
||||
|
||||
FlxG.sound.music.pause();
|
||||
FlxG.sound.music.time += FlxG.elapsed * 1000;
|
||||
});
|
||||
#end
|
||||
}
|
||||
|
||||
function defineSong():String
|
||||
{
|
||||
return MacroUtil.getDefine('SONG');
|
||||
|
||||
+41
-27
@@ -11,130 +11,144 @@ class Paths
|
||||
{
|
||||
static var currentLevel:String;
|
||||
|
||||
static public function setCurrentLevel(name:String)
|
||||
public static function setCurrentLevel(name:String):Void
|
||||
{
|
||||
currentLevel = name.toLowerCase();
|
||||
}
|
||||
|
||||
static function getPath(file:String, type:AssetType, library:Null<String>)
|
||||
public static function stripLibrary(path:String):String
|
||||
{
|
||||
var parts:Array<String> = path.split(':');
|
||||
if (parts.length < 2) return path;
|
||||
return parts[1];
|
||||
}
|
||||
|
||||
public static function getLibrary(path:String):String
|
||||
{
|
||||
var parts:Array<String> = path.split(':');
|
||||
if (parts.length < 2) return 'preload';
|
||||
return parts[0];
|
||||
}
|
||||
|
||||
static function getPath(file:String, type:AssetType, library:Null<String>):String
|
||||
{
|
||||
if (library != null) return getLibraryPath(file, library);
|
||||
|
||||
if (currentLevel != null)
|
||||
{
|
||||
var levelPath = getLibraryPathForce(file, currentLevel);
|
||||
var levelPath:String = getLibraryPathForce(file, currentLevel);
|
||||
if (OpenFlAssets.exists(levelPath, type)) return levelPath;
|
||||
}
|
||||
|
||||
var levelPath = getLibraryPathForce(file, "shared");
|
||||
var levelPath:String = getLibraryPathForce(file, 'shared');
|
||||
if (OpenFlAssets.exists(levelPath, type)) return levelPath;
|
||||
|
||||
return getPreloadPath(file);
|
||||
}
|
||||
|
||||
static public function getLibraryPath(file:String, library = "preload")
|
||||
public static function getLibraryPath(file:String, library = 'preload'):String
|
||||
{
|
||||
return if (library == "preload" || library == "default") getPreloadPath(file); else getLibraryPathForce(file, library);
|
||||
return if (library == 'preload' || library == 'default') getPreloadPath(file); else getLibraryPathForce(file, library);
|
||||
}
|
||||
|
||||
inline static function getLibraryPathForce(file:String, library:String)
|
||||
static inline function getLibraryPathForce(file:String, library:String):String
|
||||
{
|
||||
return '$library:assets/$library/$file';
|
||||
}
|
||||
|
||||
inline static function getPreloadPath(file:String)
|
||||
static inline function getPreloadPath(file:String):String
|
||||
{
|
||||
return 'assets/$file';
|
||||
}
|
||||
|
||||
inline static public function file(file:String, type:AssetType = TEXT, ?library:String)
|
||||
public static function file(file:String, type:AssetType = TEXT, ?library:String):String
|
||||
{
|
||||
return getPath(file, type, library);
|
||||
}
|
||||
|
||||
public static inline function animateAtlas(path:String, ?library:String)
|
||||
public static function animateAtlas(path:String, ?library:String):String
|
||||
{
|
||||
return getLibraryPath('images/$path', library);
|
||||
}
|
||||
|
||||
inline static public function txt(key:String, ?library:String)
|
||||
public static function txt(key:String, ?library:String):String
|
||||
{
|
||||
return getPath('data/$key.txt', TEXT, library);
|
||||
}
|
||||
|
||||
inline static public function frag(key:String, ?library:String)
|
||||
public static function frag(key:String, ?library:String):String
|
||||
{
|
||||
return getPath('shaders/$key.frag', TEXT, library);
|
||||
}
|
||||
|
||||
inline static public function vert(key:String, ?library:String)
|
||||
public static function vert(key:String, ?library:String):String
|
||||
{
|
||||
return getPath('shaders/$key.vert', TEXT, library);
|
||||
}
|
||||
|
||||
inline static public function xml(key:String, ?library:String)
|
||||
public static function xml(key:String, ?library:String):String
|
||||
{
|
||||
return getPath('data/$key.xml', TEXT, library);
|
||||
}
|
||||
|
||||
inline static public function json(key:String, ?library:String)
|
||||
public static function json(key:String, ?library:String):String
|
||||
{
|
||||
return getPath('data/$key.json', TEXT, library);
|
||||
}
|
||||
|
||||
static public function sound(key:String, ?library:String)
|
||||
public static function sound(key:String, ?library:String):String
|
||||
{
|
||||
return getPath('sounds/$key.${Constants.EXT_SOUND}', SOUND, library);
|
||||
}
|
||||
|
||||
inline static public function soundRandom(key:String, min:Int, max:Int, ?library:String)
|
||||
public static function soundRandom(key:String, min:Int, max:Int, ?library:String):String
|
||||
{
|
||||
return sound(key + FlxG.random.int(min, max), library);
|
||||
}
|
||||
|
||||
inline static public function music(key:String, ?library:String)
|
||||
public static function music(key:String, ?library:String):String
|
||||
{
|
||||
return getPath('music/$key.${Constants.EXT_SOUND}', MUSIC, library);
|
||||
}
|
||||
|
||||
inline static public function videos(key:String, ?library:String)
|
||||
public static function videos(key:String, ?library:String):String
|
||||
{
|
||||
return getPath('videos/$key.${Constants.EXT_VIDEO}', BINARY, library);
|
||||
}
|
||||
|
||||
inline static public function voices(song:String, ?suffix:String = '')
|
||||
public static function voices(song:String, ?suffix:String = ''):String
|
||||
{
|
||||
if (suffix == null) suffix = ""; // no suffix, for a sorta backwards compatibility with older-ish voice files
|
||||
if (suffix == null) suffix = ''; // no suffix, for a sorta backwards compatibility with older-ish voice files
|
||||
|
||||
return 'songs:assets/songs/${song.toLowerCase()}/Voices$suffix.${Constants.EXT_SOUND}';
|
||||
}
|
||||
|
||||
inline static public function inst(song:String, ?suffix:String = '')
|
||||
public static function inst(song:String, ?suffix:String = ''):String
|
||||
{
|
||||
return 'songs:assets/songs/${song.toLowerCase()}/Inst$suffix.${Constants.EXT_SOUND}';
|
||||
}
|
||||
|
||||
inline static public function image(key:String, ?library:String)
|
||||
public static function image(key:String, ?library:String):String
|
||||
{
|
||||
return getPath('images/$key.png', IMAGE, library);
|
||||
}
|
||||
|
||||
inline static public function font(key:String)
|
||||
public static function font(key:String):String
|
||||
{
|
||||
return 'assets/fonts/$key';
|
||||
}
|
||||
|
||||
inline static public function ui(key:String, ?library:String)
|
||||
public static function ui(key:String, ?library:String):String
|
||||
{
|
||||
return xml('ui/$key', library);
|
||||
}
|
||||
|
||||
static public function getSparrowAtlas(key:String, ?library:String)
|
||||
public static function getSparrowAtlas(key:String, ?library:String):FlxAtlasFrames
|
||||
{
|
||||
return FlxAtlasFrames.fromSparrow(image(key, library), file('images/$key.xml', library));
|
||||
}
|
||||
|
||||
inline static public function getPackerAtlas(key:String, ?library:String)
|
||||
public static function getPackerAtlas(key:String, ?library:String):FlxAtlasFrames
|
||||
{
|
||||
return FlxAtlasFrames.fromSpriteSheetPacker(image(key, library), file('images/$key.txt', library));
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import flixel.util.FlxSignal;
|
||||
*/
|
||||
class PlayerSettings
|
||||
{
|
||||
// TODO: Finish implementation of second player.
|
||||
public static var numPlayers(default, null) = 0;
|
||||
public static var numAvatars(default, null) = 0;
|
||||
public static var player1(default, null):PlayerSettings;
|
||||
@@ -21,12 +22,21 @@ class PlayerSettings
|
||||
public static var onAvatarAdd(default, null) = new FlxTypedSignal<PlayerSettings->Void>();
|
||||
public static var onAvatarRemove(default, null) = new FlxTypedSignal<PlayerSettings->Void>();
|
||||
|
||||
/**
|
||||
* The player number associated with this settings object.
|
||||
*/
|
||||
public var id(default, null):Int;
|
||||
|
||||
/**
|
||||
* The controls handler for this player.
|
||||
*/
|
||||
public var controls(default, null):Controls;
|
||||
|
||||
/**
|
||||
* Return the PlayerSettings for the given player number, or `null` if that player isn't active.
|
||||
*
|
||||
* @param id The player number this represents.
|
||||
* @return The PlayerSettings for the given player number, or `null` if that player isn't active.
|
||||
*/
|
||||
public static function get(id:Int):Null<PlayerSettings>
|
||||
{
|
||||
@@ -38,6 +48,9 @@ class PlayerSettings
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the PlayerSettings singletons for each player.
|
||||
*/
|
||||
public static function init():Void
|
||||
{
|
||||
if (player1 == null)
|
||||
@@ -56,22 +69,30 @@ class PlayerSettings
|
||||
}
|
||||
}
|
||||
|
||||
public static function reset()
|
||||
/**
|
||||
* Forcibly destroy the PlayerSettings singletons for each player.
|
||||
*/
|
||||
public static function reset():Void
|
||||
{
|
||||
player1 = null;
|
||||
player2 = null;
|
||||
numPlayers = 0;
|
||||
}
|
||||
|
||||
static function onGamepadAdded(gamepad:FlxGamepad)
|
||||
/**
|
||||
* Callback invoked when a gamepad is added.
|
||||
* @param gamepad The gamepad that was added.
|
||||
*/
|
||||
static function onGamepadAdded(gamepad:FlxGamepad):Void
|
||||
{
|
||||
// TODO: Make this detect and handle multiple players
|
||||
player1.addGamepad(gamepad);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param id The player number this represents. This was refactored to START AT `1`.
|
||||
*/
|
||||
private function new(id:Int)
|
||||
function new(id:Int)
|
||||
{
|
||||
trace('loading player settings for id: $id');
|
||||
|
||||
@@ -83,11 +104,11 @@ class PlayerSettings
|
||||
|
||||
function addKeyboard():Void
|
||||
{
|
||||
var useDefault = true;
|
||||
if (Save.get().hasControls(id, Keys))
|
||||
var useDefault:Bool = true;
|
||||
if (Save.instance.hasControls(id, Keys))
|
||||
{
|
||||
var keyControlData = Save.get().getControls(id, Keys);
|
||||
trace("keyControlData: " + haxe.Json.stringify(keyControlData));
|
||||
var keyControlData = Save.instance.getControls(id, Keys);
|
||||
trace('Loading keyboard control scheme from user save');
|
||||
useDefault = false;
|
||||
controls.fromSaveData(keyControlData, Keys);
|
||||
}
|
||||
@@ -98,7 +119,7 @@ class PlayerSettings
|
||||
|
||||
if (useDefault)
|
||||
{
|
||||
trace("Loading default keyboard control scheme");
|
||||
trace('Loading default keyboard control scheme');
|
||||
controls.setKeyboardScheme(Solo);
|
||||
}
|
||||
|
||||
@@ -109,13 +130,13 @@ class PlayerSettings
|
||||
* Called after an FlxGamepad has been detected.
|
||||
* @param gamepad The gamepad that was detected.
|
||||
*/
|
||||
function addGamepad(gamepad:FlxGamepad)
|
||||
function addGamepad(gamepad:FlxGamepad):Void
|
||||
{
|
||||
var useDefault = true;
|
||||
if (Save.get().hasControls(id, Gamepad(gamepad.id)))
|
||||
if (Save.instance.hasControls(id, Gamepad(gamepad.id)))
|
||||
{
|
||||
var padControlData = Save.get().getControls(id, Gamepad(gamepad.id));
|
||||
trace("padControlData: " + haxe.Json.stringify(padControlData));
|
||||
var padControlData = Save.instance.getControls(id, Gamepad(gamepad.id));
|
||||
trace('Loading gamepad control scheme from user save');
|
||||
useDefault = false;
|
||||
controls.addGamepadWithSaveData(gamepad.id, padControlData);
|
||||
}
|
||||
@@ -126,7 +147,7 @@ class PlayerSettings
|
||||
|
||||
if (useDefault)
|
||||
{
|
||||
trace("Loading gamepad control scheme");
|
||||
trace('Loading default gamepad control scheme');
|
||||
controls.addDefaultGamepad(gamepad.id);
|
||||
}
|
||||
PreciseInputManager.instance.initializeButtons(controls, gamepad);
|
||||
@@ -135,13 +156,13 @@ class PlayerSettings
|
||||
/**
|
||||
* Save this player's controls to the game's persistent save.
|
||||
*/
|
||||
public function saveControls()
|
||||
public function saveControls():Void
|
||||
{
|
||||
var keyData = controls.createSaveData(Keys);
|
||||
if (keyData != null)
|
||||
{
|
||||
trace("saving key data: " + haxe.Json.stringify(keyData));
|
||||
Save.get().setControls(id, Keys, keyData);
|
||||
trace('Saving keyboard control scheme to user save');
|
||||
Save.instance.setControls(id, Keys, keyData);
|
||||
}
|
||||
|
||||
if (controls.gamepadsAdded.length > 0)
|
||||
@@ -149,8 +170,8 @@ class PlayerSettings
|
||||
var padData = controls.createSaveData(Gamepad(controls.gamepadsAdded[0]));
|
||||
if (padData != null)
|
||||
{
|
||||
trace("saving pad data: " + haxe.Json.stringify(padData));
|
||||
Save.get().setControls(id, Gamepad(controls.gamepadsAdded[0]), padData);
|
||||
trace('Saving gamepad control scheme to user save');
|
||||
Save.instance.setControls(id, Gamepad(controls.gamepadsAdded[0]), padData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,12 +15,12 @@ class Preferences
|
||||
|
||||
static function get_naughtyness():Bool
|
||||
{
|
||||
return Save.get().options.naughtyness;
|
||||
return Save.instance.options.naughtyness;
|
||||
}
|
||||
|
||||
static function set_naughtyness(value:Bool):Bool
|
||||
{
|
||||
var save = Save.get();
|
||||
var save:Save = Save.instance;
|
||||
save.options.naughtyness = value;
|
||||
save.flush();
|
||||
return value;
|
||||
@@ -34,12 +34,12 @@ class Preferences
|
||||
|
||||
static function get_downscroll():Bool
|
||||
{
|
||||
return Save.get().options.downscroll;
|
||||
return Save.instance.options.downscroll;
|
||||
}
|
||||
|
||||
static function set_downscroll(value:Bool):Bool
|
||||
{
|
||||
var save = Save.get();
|
||||
var save:Save = Save.instance;
|
||||
save.options.downscroll = value;
|
||||
save.flush();
|
||||
return value;
|
||||
@@ -53,12 +53,12 @@ class Preferences
|
||||
|
||||
static function get_flashingLights():Bool
|
||||
{
|
||||
return Save.get().options.flashingLights;
|
||||
return Save.instance.options.flashingLights;
|
||||
}
|
||||
|
||||
static function set_flashingLights(value:Bool):Bool
|
||||
{
|
||||
var save = Save.get();
|
||||
var save:Save = Save.instance;
|
||||
save.options.flashingLights = value;
|
||||
save.flush();
|
||||
return value;
|
||||
@@ -72,12 +72,12 @@ class Preferences
|
||||
|
||||
static function get_zoomCamera():Bool
|
||||
{
|
||||
return Save.get().options.zoomCamera;
|
||||
return Save.instance.options.zoomCamera;
|
||||
}
|
||||
|
||||
static function set_zoomCamera(value:Bool):Bool
|
||||
{
|
||||
var save = Save.get();
|
||||
var save:Save = Save.instance;
|
||||
save.options.zoomCamera = value;
|
||||
save.flush();
|
||||
return value;
|
||||
@@ -91,17 +91,17 @@ class Preferences
|
||||
|
||||
static function get_debugDisplay():Bool
|
||||
{
|
||||
return Save.get().options.debugDisplay;
|
||||
return Save.instance.options.debugDisplay;
|
||||
}
|
||||
|
||||
static function set_debugDisplay(value:Bool):Bool
|
||||
{
|
||||
if (value != Save.get().options.debugDisplay)
|
||||
if (value != Save.instance.options.debugDisplay)
|
||||
{
|
||||
toggleDebugDisplay(value);
|
||||
}
|
||||
|
||||
var save = Save.get();
|
||||
var save = Save.instance;
|
||||
save.options.debugDisplay = value;
|
||||
save.flush();
|
||||
return value;
|
||||
@@ -115,22 +115,27 @@ class Preferences
|
||||
|
||||
static function get_autoPause():Bool
|
||||
{
|
||||
return Save.get().options.autoPause;
|
||||
return Save.instance.options.autoPause;
|
||||
}
|
||||
|
||||
static function set_autoPause(value:Bool):Bool
|
||||
{
|
||||
if (value != Save.get().options.autoPause) FlxG.autoPause = value;
|
||||
if (value != Save.instance.options.autoPause) FlxG.autoPause = value;
|
||||
|
||||
var save = Save.get();
|
||||
var save:Save = Save.instance;
|
||||
save.options.autoPause = value;
|
||||
save.flush();
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the user's preferences from the save data and apply them.
|
||||
*/
|
||||
public static function init():Void
|
||||
{
|
||||
// Apply the autoPause setting (enables automatic pausing on focus lost).
|
||||
FlxG.autoPause = Preferences.autoPause;
|
||||
// Apply the debugDisplay setting (enables the FPS and RAM display).
|
||||
toggleDebugDisplay(Preferences.debugDisplay);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package funkin;
|
||||
import flash.Lib;
|
||||
import flash.display.Bitmap;
|
||||
import flash.display.BitmapData;
|
||||
import flash.display.BlendMode;
|
||||
import flash.display.Sprite;
|
||||
import flixel.system.FlxBasePreloader;
|
||||
import openfl.display.Sprite;
|
||||
@@ -12,7 +11,8 @@ import openfl.text.TextField;
|
||||
import openfl.text.TextFormat;
|
||||
import flixel.system.FlxAssets;
|
||||
|
||||
@:bitmap("art/preloaderArt.png") class LogoImage extends BitmapData {}
|
||||
@:bitmap('art/preloaderArt.png')
|
||||
class LogoImage extends BitmapData {}
|
||||
|
||||
class Preloader extends FlxBasePreloader
|
||||
{
|
||||
|
||||
@@ -86,10 +86,10 @@ class NGUtil
|
||||
#end
|
||||
|
||||
var onSessionFail:Error->Void = null;
|
||||
if (sessionId == null && Save.get().ngSessionId != null)
|
||||
if (sessionId == null && Save.instance.ngSessionId != null)
|
||||
{
|
||||
trace("using stored session id");
|
||||
sessionId = Save.get().ngSessionId;
|
||||
sessionId = Save.instance.ngSessionId;
|
||||
onSessionFail = function(error) savedSessionFailed = true;
|
||||
}
|
||||
#end
|
||||
@@ -159,8 +159,8 @@ class NGUtil
|
||||
static function onNGLogin():Void
|
||||
{
|
||||
trace('logged in! user:${NG.core.user.name}');
|
||||
Save.get().ngSessionId = NG.core.sessionId;
|
||||
Save.get().flush();
|
||||
Save.instance.ngSessionId = NG.core.sessionId;
|
||||
Save.instance.flush();
|
||||
// Load medals then call onNGMedalFetch()
|
||||
NG.core.requestMedals(onNGMedalFetch);
|
||||
|
||||
@@ -174,8 +174,8 @@ class NGUtil
|
||||
{
|
||||
NG.core.logOut();
|
||||
|
||||
Save.get().ngSessionId = null;
|
||||
Save.get().flush();
|
||||
Save.instance.ngSessionId = null;
|
||||
Save.instance.flush();
|
||||
}
|
||||
|
||||
// --- MEDALS
|
||||
|
||||
@@ -86,10 +86,10 @@ class NGio
|
||||
#end
|
||||
|
||||
var onSessionFail:Error->Void = null;
|
||||
if (sessionId == null && Save.get().ngSessionId != null)
|
||||
if (sessionId == null && Save.instance.ngSessionId != null)
|
||||
{
|
||||
trace("using stored session id");
|
||||
sessionId = Save.get().ngSessionId;
|
||||
sessionId = Save.instance.ngSessionId;
|
||||
onSessionFail = function(error) savedSessionFailed = true;
|
||||
}
|
||||
#end
|
||||
@@ -159,8 +159,8 @@ class NGio
|
||||
static function onNGLogin():Void
|
||||
{
|
||||
trace('logged in! user:${NG.core.user.name}');
|
||||
Save.get().ngSessionId = NG.core.sessionId;
|
||||
Save.get().flush();
|
||||
Save.instance.ngSessionId = NG.core.sessionId;
|
||||
Save.instance.flush();
|
||||
// Load medals then call onNGMedalFetch()
|
||||
NG.core.requestMedals(onNGMedalFetch);
|
||||
|
||||
@@ -174,8 +174,8 @@ class NGio
|
||||
{
|
||||
NG.core.logOut();
|
||||
|
||||
Save.get().ngSessionId = null;
|
||||
Save.get().flush();
|
||||
Save.instance.ngSessionId = null;
|
||||
Save.instance.flush();
|
||||
}
|
||||
|
||||
// --- MEDALS
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
package funkin.audio;
|
||||
|
||||
#if flash11
|
||||
import flash.media.Sound;
|
||||
import flash.utils.ByteArray;
|
||||
#end
|
||||
import flixel.sound.FlxSound;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import flixel.util.FlxSignal.FlxTypedSignal;
|
||||
import flixel.system.FlxAssets.FlxSoundAsset;
|
||||
import funkin.util.tools.ICloneable;
|
||||
import funkin.data.song.SongData.SongMusicData;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.audio.waveform.WaveformData;
|
||||
import funkin.audio.waveform.WaveformDataParser;
|
||||
import flixel.math.FlxMath;
|
||||
@@ -23,9 +22,31 @@ import openfl.utils.AssetType;
|
||||
@:nullSafety
|
||||
class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
{
|
||||
static final MAX_VOLUME:Float = 2.0;
|
||||
static final MAX_VOLUME:Float = 1.0;
|
||||
|
||||
static var cache(default, null):FlxTypedGroup<FunkinSound> = new FlxTypedGroup<FunkinSound>();
|
||||
/**
|
||||
* An FlxSignal which is dispatched when the volume changes.
|
||||
*/
|
||||
public static var onVolumeChanged(get, never):FlxTypedSignal<Float->Void>;
|
||||
|
||||
static var _onVolumeChanged:Null<FlxTypedSignal<Float->Void>> = null;
|
||||
|
||||
static function get_onVolumeChanged():FlxTypedSignal<Float->Void>
|
||||
{
|
||||
if (_onVolumeChanged == null)
|
||||
{
|
||||
_onVolumeChanged = new FlxTypedSignal<Float->Void>();
|
||||
FlxG.sound.volumeHandler = function(volume:Float) {
|
||||
_onVolumeChanged.dispatch(volume);
|
||||
}
|
||||
}
|
||||
return _onVolumeChanged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Using `FunkinSound.load` will override a dead instance from here rather than creating a new one, if possible!
|
||||
*/
|
||||
static var pool(default, null):FlxTypedGroup<FunkinSound> = new FlxTypedGroup<FunkinSound>();
|
||||
|
||||
public var muted(default, set):Bool = false;
|
||||
|
||||
@@ -40,7 +61,6 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
override function set_volume(value:Float):Float
|
||||
{
|
||||
// Uncap the volume.
|
||||
fixMaxVolume();
|
||||
_volume = FlxMath.bound(value, 0.0, MAX_VOLUME);
|
||||
updateTransform();
|
||||
return _volume;
|
||||
@@ -88,6 +108,11 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
*/
|
||||
var _label:String = "unknown";
|
||||
|
||||
/**
|
||||
* Whether we received a focus lost event.
|
||||
*/
|
||||
var _lostFocus:Bool = false;
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
@@ -126,17 +151,6 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
return this;
|
||||
}
|
||||
|
||||
function fixMaxVolume():Void
|
||||
{
|
||||
#if lime_openal
|
||||
// This code is pretty fragile, it reaches through 5 layers of private access.
|
||||
@:privateAccess
|
||||
var handle = this?._channel?.__source?.__backend?.handle;
|
||||
if (handle == null) return;
|
||||
lime.media.openal.AL.sourcef(handle, lime.media.openal.AL.MAX_GAIN, MAX_VOLUME);
|
||||
#end
|
||||
}
|
||||
|
||||
public override function play(forceRestart:Bool = false, startTime:Float = 0, ?endTime:Float):FunkinSound
|
||||
{
|
||||
if (!exists) return this;
|
||||
@@ -176,8 +190,18 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
|
||||
public override function pause():FunkinSound
|
||||
{
|
||||
super.pause();
|
||||
this._shouldPlay = false;
|
||||
if (_shouldPlay)
|
||||
{
|
||||
// This sound will eventually play, but is still at a negative timestamp.
|
||||
// Manually set the paused flag to ensure proper focus/unfocus behavior.
|
||||
_shouldPlay = false;
|
||||
_paused = true;
|
||||
active = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
super.pause();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -186,10 +210,18 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
*/
|
||||
override function onFocus():Void
|
||||
{
|
||||
if (!_alreadyPaused && this._shouldPlay)
|
||||
// Flixel can sometimes toss spurious `onFocus` events, e.g. if the Flixel debugger is toggled
|
||||
// on and off. We only want to resume the sound if we actually lost focus, and if we weren't
|
||||
// already paused before we lost focus.
|
||||
if (_lostFocus && !_alreadyPaused)
|
||||
{
|
||||
resume();
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Not resuming audio on focus!');
|
||||
}
|
||||
_lostFocus = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -197,6 +229,8 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
*/
|
||||
override function onFocusLost():Void
|
||||
{
|
||||
trace('Focus lost, pausing audio!');
|
||||
_lostFocus = true;
|
||||
_alreadyPaused = _paused;
|
||||
pause();
|
||||
}
|
||||
@@ -205,7 +239,10 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
{
|
||||
if (this._time < 0)
|
||||
{
|
||||
this._shouldPlay = true;
|
||||
// Sound with negative timestamp, restart the timer.
|
||||
_shouldPlay = true;
|
||||
_paused = false;
|
||||
active = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -246,24 +283,58 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new `FunkinSound` object.
|
||||
* Creates a new `FunkinSound` object and loads it as the current music track.
|
||||
*
|
||||
* @param embeddedSound The embedded sound resource you want to play. To stream, use the optional URL parameter instead.
|
||||
* @param volume How loud to play it (0 to 1).
|
||||
* @param looped Whether to loop this sound.
|
||||
* @param group The group to add this sound to.
|
||||
* @param autoDestroy Whether to destroy this sound when it finishes playing.
|
||||
* @param key The key of the music you want to play. Music should be at `music/<key>/<key>.ogg`.
|
||||
* @param overrideExisting Whether to override music if it is already playing.
|
||||
* @param mapTimeChanges Whether to check for `SongMusicData` to update the Conductor with.
|
||||
* Data should be at `music/<key>/<key>-metadata.json`.
|
||||
*/
|
||||
public static function playMusic(key:String, overrideExisting:Bool = false, mapTimeChanges:Bool = true):Void
|
||||
{
|
||||
if (!overrideExisting && FlxG.sound.music?.playing) return;
|
||||
|
||||
if (mapTimeChanges)
|
||||
{
|
||||
var songMusicData:Null<SongMusicData> = SongRegistry.instance.parseMusicData(key);
|
||||
// Will fall back and return null if the metadata doesn't exist or can't be parsed.
|
||||
if (songMusicData != null)
|
||||
{
|
||||
Conductor.instance.mapTimeChanges(songMusicData.timeChanges);
|
||||
}
|
||||
else
|
||||
{
|
||||
FlxG.log.warn('Tried and failed to find music metadata for $key');
|
||||
}
|
||||
}
|
||||
|
||||
FlxG.sound.music = FunkinSound.load(Paths.music('$key/$key'));
|
||||
|
||||
// Prevent repeat update() and onFocus() calls.
|
||||
FlxG.sound.list.remove(FlxG.sound.music);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new `FunkinSound` object synchronously.
|
||||
*
|
||||
* @param embeddedSound The embedded sound resource you want to play. To stream, use the optional URL parameter instead.
|
||||
* @param volume How loud to play it (0 to 1).
|
||||
* @param looped Whether to loop this sound.
|
||||
* @param group The group to add this sound to.
|
||||
* @param autoDestroy Whether to destroy this sound when it finishes playing.
|
||||
* Leave this value set to `false` if you want to re-use this `FunkinSound` instance.
|
||||
* @param autoPlay Whether to play the sound immediately or wait for a `play()` call.
|
||||
* @param onComplete Called when the sound finished playing.
|
||||
* @param onLoad Called when the sound finished loading. Called immediately for succesfully loaded embedded sounds.
|
||||
* @return A `FunkinSound` object.
|
||||
* @param autoPlay Whether to play the sound immediately or wait for a `play()` call.
|
||||
* @param onComplete Called when the sound finished playing.
|
||||
* @param onLoad Called when the sound finished loading. Called immediately for succesfully loaded embedded sounds.
|
||||
* @return A `FunkinSound` object.
|
||||
*/
|
||||
public static function load(embeddedSound:FlxSoundAsset, volume:Float = 1.0, looped:Bool = false, autoDestroy:Bool = false, autoPlay:Bool = false,
|
||||
?onComplete:Void->Void, ?onLoad:Void->Void):FunkinSound
|
||||
{
|
||||
var sound:FunkinSound = cache.recycle(construct);
|
||||
var sound:FunkinSound = pool.recycle(construct);
|
||||
|
||||
// Load the sound.
|
||||
// Sets `exists = true` as a side effect.
|
||||
sound.loadEmbedded(embeddedSound, looped, autoDestroy, onComplete);
|
||||
|
||||
if (embeddedSound is String)
|
||||
@@ -276,9 +347,11 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
sound.persist = true;
|
||||
if (autoPlay) sound.play();
|
||||
|
||||
// Call OnlLoad() because the sound already loaded
|
||||
// Call onLoad() because the sound already loaded
|
||||
if (onLoad != null && sound._sound != null) onLoad();
|
||||
|
||||
FlxG.sound.list.remove(FlxG.sound.music);
|
||||
|
||||
return sound;
|
||||
}
|
||||
|
||||
@@ -286,7 +359,7 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||
{
|
||||
var sound:FunkinSound = new FunkinSound();
|
||||
|
||||
cache.add(sound);
|
||||
pool.add(sound);
|
||||
FlxG.sound.list.add(sound);
|
||||
|
||||
return sound;
|
||||
|
||||
@@ -80,8 +80,9 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
|
||||
|
||||
// We have to play, then pause the sound to set the time,
|
||||
// else the sound will restart immediately when played.
|
||||
result.play(true, 0.0);
|
||||
result.pause();
|
||||
// TODO: Past me experienced that issue but present me didn't? Investigate.
|
||||
// result.play(true, 0.0);
|
||||
// result.pause();
|
||||
result.time = this.time;
|
||||
|
||||
result.onComplete = function() {
|
||||
|
||||
@@ -4,13 +4,11 @@ import flixel.FlxSprite;
|
||||
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
||||
import flixel.math.FlxMath;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.math.FlxVector;
|
||||
import flixel.sound.FlxSound;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.audio.visualize.PolygonSpectogram.VISTYPE;
|
||||
import funkin.audio.visualize.VisShit.CurAudioInfo;
|
||||
import funkin.audio.visualize.dsp.FFT;
|
||||
import haxe.Timer;
|
||||
import lime.system.ThreadPool;
|
||||
import lime.utils.Int16Array;
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package funkin.audio.visualize;
|
||||
import flixel.math.FlxMath;
|
||||
import flixel.sound.FlxSound;
|
||||
import funkin.audio.visualize.dsp.FFT;
|
||||
import haxe.Timer;
|
||||
import lime.system.ThreadPool;
|
||||
import lime.utils.Int16Array;
|
||||
import funkin.util.MathUtil;
|
||||
|
||||
@@ -187,6 +187,8 @@ class WaveformData
|
||||
*/
|
||||
public function merge(that:WaveformData):WaveformData
|
||||
{
|
||||
if (that == null) return this.clone();
|
||||
|
||||
var result = this.clone([]);
|
||||
|
||||
for (channelIndex in 0...this.channels)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package funkin.audio.waveform;
|
||||
|
||||
import funkin.util.TimerUtil;
|
||||
|
||||
class WaveformDataParser
|
||||
{
|
||||
static final INT16_MAX:Int = 32767;
|
||||
@@ -71,7 +73,7 @@ class WaveformDataParser
|
||||
|
||||
var outputData:Array<Int> = [];
|
||||
|
||||
var perfStart = haxe.Timer.stamp();
|
||||
var perfStart:Float = TimerUtil.start();
|
||||
|
||||
for (pointIndex in 0...outputPointCount)
|
||||
{
|
||||
@@ -108,8 +110,7 @@ class WaveformDataParser
|
||||
var outputDataLength:Int = Std.int(outputData.length / channels / 2);
|
||||
var result = new WaveformData(null, channels, sampleRate, samplesPerPoint, bitsPerSample, outputPointCount, outputData);
|
||||
|
||||
var perfEnd = haxe.Timer.stamp();
|
||||
trace('[WAVEFORM] Interpreted audio buffer in ${perfEnd - perfStart} seconds.');
|
||||
trace('[WAVEFORM] Interpreted audio buffer in ${TimerUtil.seconds(perfStart)}.');
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package funkin.data;
|
||||
|
||||
import openfl.Assets;
|
||||
import funkin.util.assets.DataAssets;
|
||||
import funkin.util.VersionUtil;
|
||||
import haxe.Constraints.Constructible;
|
||||
@@ -19,12 +18,23 @@ typedef EntryConstructorFunction = String->Void;
|
||||
@:generic
|
||||
abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructorFunction>), J>
|
||||
{
|
||||
/**
|
||||
* The ID of the registry. Used when logging.
|
||||
*/
|
||||
public final registryId:String;
|
||||
|
||||
final dataFilePath:String;
|
||||
|
||||
/**
|
||||
* A map of entry IDs to entries.
|
||||
*/
|
||||
final entries:Map<String, T>;
|
||||
|
||||
/**
|
||||
* A map of entry IDs to scripted class names.
|
||||
*/
|
||||
final scriptedEntryIds:Map<String, String>;
|
||||
|
||||
/**
|
||||
* The version rule to use when loading entries.
|
||||
* If the entry's version does not match this rule, migration is needed.
|
||||
@@ -37,17 +47,18 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||
* @param registryId A readable ID for this registry, used when logging.
|
||||
* @param dataFilePath The path (relative to `assets/data`) to search for JSON files.
|
||||
*/
|
||||
public function new(registryId:String, dataFilePath:String, versionRule:thx.semver.VersionRule = null)
|
||||
public function new(registryId:String, dataFilePath:String, ?versionRule:thx.semver.VersionRule)
|
||||
{
|
||||
this.registryId = registryId;
|
||||
this.dataFilePath = dataFilePath;
|
||||
this.versionRule = versionRule == null ? "1.0.x" : versionRule;
|
||||
this.versionRule = versionRule == null ? '1.0.x' : versionRule;
|
||||
|
||||
this.entries = new Map<String, T>();
|
||||
this.scriptedEntryIds = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Create a `loadEntriesAsync()` function.
|
||||
* TODO: Create a `loadEntriesAsync(onProgress, onComplete)` function.
|
||||
*/
|
||||
public function loadEntries():Void
|
||||
{
|
||||
@@ -61,12 +72,22 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||
|
||||
for (entryCls in scriptedEntryClassNames)
|
||||
{
|
||||
var entry:T = createScriptedEntry(entryCls);
|
||||
var entry:Null<T> = null;
|
||||
try
|
||||
{
|
||||
entry = createScriptedEntry(entryCls);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
log('Failed to create scripted entry (${entryCls})');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry != null)
|
||||
{
|
||||
log('Successfully created scripted entry (${entryCls} = ${entry.id})');
|
||||
entries.set(entry.id, entry);
|
||||
scriptedEntryIds.set(entry.id, entryCls);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -93,7 +114,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||
entries.set(entry.id, entry);
|
||||
}
|
||||
}
|
||||
catch (e:Dynamic)
|
||||
catch (e)
|
||||
{
|
||||
// Print the error.
|
||||
trace(' Failed to load entry data: ${entryId}');
|
||||
@@ -121,6 +142,36 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||
return entries.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the entry ID is known to have an attached script.
|
||||
* @param id The ID of the entry.
|
||||
* @return `true` if the entry has an attached script, `false` otherwise.
|
||||
*/
|
||||
public function isScriptedEntry(id:String):Bool
|
||||
{
|
||||
return scriptedEntryIds.exists(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the class name of the scripted entry with the given ID, if it exists.
|
||||
* @param id The ID of the entry.
|
||||
* @return The class name, or `null` if it does not exist.
|
||||
*/
|
||||
public function getScriptedEntryClassName(id:String):String
|
||||
{
|
||||
return scriptedEntryIds.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the registry has successfully parsed an entry with the given ID.
|
||||
* @param id The ID of the entry.
|
||||
* @return `true` if the entry exists, `false` otherwise.
|
||||
*/
|
||||
public function hasEntry(id:String):Bool
|
||||
{
|
||||
return entries.exists(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch an entry by its ID.
|
||||
* @param id The ID of the entry to fetch.
|
||||
@@ -136,6 +187,11 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||
return 'Registry(' + registryId + ', ${countEntries()} entries)';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the data for an entry and parse its Semantic Version.
|
||||
* @param id The ID of the entry.
|
||||
* @return The entry's version, or `null` if it does not exist or is invalid.
|
||||
*/
|
||||
public function fetchEntryVersion(id:String):Null<thx.semver.Version>
|
||||
{
|
||||
var entryStr:String = loadEntryFile(id).contents;
|
||||
@@ -176,6 +232,8 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
||||
*
|
||||
* NOTE: Must be implemented on the implementation class.
|
||||
* @param id The ID of the entry.
|
||||
* @return The created entry.
|
||||
*/
|
||||
public abstract function parseEntryData(id:String):Null<J>;
|
||||
|
||||
@@ -185,6 +243,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||
* 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 created entry.
|
||||
*/
|
||||
public abstract function parseEntryDataRaw(contents:String, ?fileName:String):Null<J>;
|
||||
|
||||
@@ -193,9 +252,17 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||
* accounting for old versions of the data.
|
||||
*
|
||||
* NOTE: Extend this function to handle migration.
|
||||
* @param id The ID of the entry.
|
||||
* @param version The entry's version (use `fetchEntryVersion(id)`).
|
||||
* @return The created entry.
|
||||
*/
|
||||
public function parseEntryDataWithMigration(id:String, version:thx.semver.Version):Null<J>
|
||||
{
|
||||
if (version == null)
|
||||
{
|
||||
throw '[${registryId}] Entry ${id} could not be JSON-parsed or does not have a parseable version.';
|
||||
}
|
||||
|
||||
// If a version rule is not specified, do not check against it.
|
||||
if (versionRule == null || VersionUtil.validateVersion(version, versionRule))
|
||||
{
|
||||
@@ -206,12 +273,17 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||
throw '[${registryId}] Entry ${id} does not support migration to version ${versionRule}.';
|
||||
}
|
||||
|
||||
// Example:
|
||||
// if (VersionUtil.validateVersion(version, "0.1.x")) {
|
||||
// return parseEntryData_v0_1_x(id);
|
||||
// } else {
|
||||
// super.parseEntryDataWithMigration(id, version);
|
||||
// }
|
||||
/*
|
||||
* An example of what you should override this with:
|
||||
*
|
||||
* ```haxe
|
||||
* if (VersionUtil.validateVersion(version, "0.1.x")) {
|
||||
* return parseEntryData_v0_1_x(id);
|
||||
* } else {
|
||||
* super.parseEntryDataWithMigration(id, version);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -226,6 +298,7 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||
*/
|
||||
function createEntry(id:String):Null<T>
|
||||
{
|
||||
// We enforce that T is Constructible to ensure this is valid.
|
||||
return new T(id);
|
||||
}
|
||||
|
||||
@@ -240,10 +313,15 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
||||
trace('[${registryId}] Failed to parse entry data: ${id}');
|
||||
|
||||
for (error in errors)
|
||||
{
|
||||
DataError.printError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A pair of a file name and its contents.
|
||||
*/
|
||||
typedef JsonFile =
|
||||
{
|
||||
fileName:String,
|
||||
|
||||
@@ -108,8 +108,8 @@ class SongEventRegistry
|
||||
|
||||
public static function handleEvent(data:SongEventData):Void
|
||||
{
|
||||
var eventType:String = data.event;
|
||||
var eventHandler:SongEvent = eventCache.get(eventType);
|
||||
var eventKind:String = data.eventKind;
|
||||
var eventHandler:SongEvent = eventCache.get(eventKind);
|
||||
|
||||
if (eventHandler != null)
|
||||
{
|
||||
@@ -117,7 +117,7 @@ class SongEventRegistry
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('WARNING: No event handler for event with id: ${eventType}');
|
||||
trace('WARNING: No event handler for event with kind: ${eventKind}');
|
||||
}
|
||||
|
||||
data.activated = true;
|
||||
@@ -148,6 +148,29 @@ class SongEventRegistry
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The currentTime has jumped far ahead or back.
|
||||
* If we moved back in time, we need to reset all the events in that space.
|
||||
* If we moved forward in time, we need to skip all the events in that space.
|
||||
*/
|
||||
public static function handleSkippedEvents(events:Array<SongEventData>, currentTime:Float):Void
|
||||
{
|
||||
for (event in events)
|
||||
{
|
||||
// Deactivate future events.
|
||||
if (event.time > currentTime)
|
||||
{
|
||||
event.activated = false;
|
||||
}
|
||||
|
||||
// Skip past events.
|
||||
if (event.time < currentTime)
|
||||
{
|
||||
event.activated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset activation of all the provided events.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package funkin.data.freeplay;
|
||||
|
||||
/**
|
||||
* 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 AlbumData =
|
||||
{
|
||||
/**
|
||||
* Semantic version for album data.
|
||||
*/
|
||||
public var version:String;
|
||||
|
||||
/**
|
||||
* Readable name of the album.
|
||||
*/
|
||||
public var name:String;
|
||||
|
||||
/**
|
||||
* Readable name of the artist(s) of the album.
|
||||
*/
|
||||
public var artists:Array<String>;
|
||||
|
||||
/**
|
||||
* Asset key for the album art.
|
||||
* The album art will be displayed in Freeplay.
|
||||
*/
|
||||
public var albumArtAsset:String;
|
||||
|
||||
/**
|
||||
* Asset key for the album title.
|
||||
* The album title will be displayed below the album art in Freeplay.
|
||||
*/
|
||||
public var albumTitleAsset:String;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package funkin.data.freeplay;
|
||||
|
||||
import funkin.ui.freeplay.Album;
|
||||
import funkin.data.freeplay.AlbumData;
|
||||
import funkin.ui.freeplay.ScriptedAlbum;
|
||||
|
||||
class AlbumRegistry extends BaseRegistry<Album, AlbumData>
|
||||
{
|
||||
/**
|
||||
* The current version string for the album data format.
|
||||
* Handle breaking changes by incrementing this value
|
||||
* and adding migration to the `migrateAlbumData()` function.
|
||||
*/
|
||||
public static final ALBUM_DATA_VERSION:thx.semver.Version = '1.0.0';
|
||||
|
||||
public static final ALBUM_DATA_VERSION_RULE:thx.semver.VersionRule = '1.0.x';
|
||||
|
||||
public static final instance:AlbumRegistry = new AlbumRegistry();
|
||||
|
||||
public function new()
|
||||
{
|
||||
super('ALBUM', 'ui/freeplay/albums', ALBUM_DATA_VERSION_RULE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
||||
* @param id The ID of the entry to load.
|
||||
* @return The parsed data object.
|
||||
*/
|
||||
public function parseEntryData(id:String):Null<AlbumData>
|
||||
{
|
||||
// JsonParser does not take type parameters,
|
||||
// otherwise this function would be in BaseRegistry.
|
||||
var parser:json2object.JsonParser<AlbumData> = new json2object.JsonParser<AlbumData>();
|
||||
parser.ignoreUnknownVariables = false;
|
||||
|
||||
switch (loadEntryFile(id))
|
||||
{
|
||||
case {fileName: fileName, contents: contents}:
|
||||
parser.fromJson(contents, fileName);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
if (parser.errors.length > 0)
|
||||
{
|
||||
printErrors(parser.errors, id);
|
||||
return null;
|
||||
}
|
||||
return parser.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and validate the JSON data and produce the corresponding data object.
|
||||
*
|
||||
* NOTE: Must be implemented on the implementation class.
|
||||
* @param contents The JSON as a string.
|
||||
* @param fileName An optional file name for error reporting.
|
||||
* @return The parsed data object.
|
||||
*/
|
||||
public function parseEntryDataRaw(contents:String, ?fileName:String):Null<AlbumData>
|
||||
{
|
||||
var parser:json2object.JsonParser<AlbumData> = new json2object.JsonParser<AlbumData>();
|
||||
parser.ignoreUnknownVariables = false;
|
||||
parser.fromJson(contents, fileName);
|
||||
|
||||
if (parser.errors.length > 0)
|
||||
{
|
||||
printErrors(parser.errors, fileName);
|
||||
return null;
|
||||
}
|
||||
return parser.value;
|
||||
}
|
||||
|
||||
function createScriptedEntry(clsName:String):Album
|
||||
{
|
||||
return ScriptedAlbum.init(clsName, 'unknown');
|
||||
}
|
||||
|
||||
function getScriptedClassNames():Array<String>
|
||||
{
|
||||
return ScriptedAlbum.listScriptClasses();
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ typedef LevelData =
|
||||
var version:String;
|
||||
|
||||
/**
|
||||
* The title of the week, as seen in the top corner.
|
||||
* The title of the level, as seen in the top corner.
|
||||
*/
|
||||
var name:String;
|
||||
|
||||
@@ -27,21 +27,35 @@ typedef LevelData =
|
||||
@:jcustomparse(funkin.data.DataParse.stringNotEmpty)
|
||||
var titleAsset:String;
|
||||
|
||||
/**
|
||||
* The props to display over the colored background.
|
||||
* In the base game this is usually Boyfriend and the opponent.
|
||||
*/
|
||||
@:default([])
|
||||
var props:Array<LevelPropData>;
|
||||
@:default(["bopeebo"])
|
||||
|
||||
/**
|
||||
* The list of song IDs included in this level.
|
||||
*/
|
||||
@:default(['bopeebo'])
|
||||
var songs:Array<String>;
|
||||
@:default("#F9CF51")
|
||||
|
||||
/**
|
||||
* The background for the level behind the props.
|
||||
*/
|
||||
@:default('#F9CF51')
|
||||
@:optional
|
||||
var background:String;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data for a single prop for a story mode level.
|
||||
*/
|
||||
typedef LevelPropData =
|
||||
{
|
||||
/**
|
||||
* The image to use for the prop. May optionally be a sprite sheet.
|
||||
*/
|
||||
// @:jcustomparse(funkin.data.DataParse.stringNotEmpty)
|
||||
var assetPath:String;
|
||||
|
||||
/**
|
||||
|
||||
@@ -106,11 +106,14 @@ class SongMetadata implements ICloneable<SongMetadata>
|
||||
|
||||
/**
|
||||
* Serialize this SongMetadata into a JSON string.
|
||||
* @param pretty Whether the JSON should be big ol string (false),
|
||||
* or formatted with tabs (true)
|
||||
* @return The JSON string.
|
||||
*/
|
||||
public function serialize(pretty:Bool = true):String
|
||||
{
|
||||
var writer = new json2object.JsonWriter<SongMetadata>();
|
||||
var ignoreNullOptionals = true;
|
||||
var writer = new json2object.JsonWriter<SongMetadata>(ignoreNullOptionals);
|
||||
// I believe @:jignored should be iggnored by the writer?
|
||||
// var output = this.clone();
|
||||
// output.variation = null; // Not sure how to make a field optional on the reader and ignored on the writer.
|
||||
@@ -597,7 +600,8 @@ class SongChartData implements ICloneable<SongChartData>
|
||||
*/
|
||||
public function serialize(pretty:Bool = true):String
|
||||
{
|
||||
var writer = new json2object.JsonWriter<SongChartData>();
|
||||
var ignoreNullOptionals = true;
|
||||
var writer = new json2object.JsonWriter<SongChartData>(ignoreNullOptionals);
|
||||
return writer.write(this, pretty ? ' ' : null);
|
||||
}
|
||||
|
||||
@@ -648,7 +652,7 @@ class SongEventDataRaw implements ICloneable<SongEventDataRaw>
|
||||
* Custom events can be added by scripts with the `ScriptedSongEvent` class.
|
||||
*/
|
||||
@:alias("e")
|
||||
public var event:String;
|
||||
public var eventKind:String;
|
||||
|
||||
/**
|
||||
* The data for the event.
|
||||
@@ -668,10 +672,10 @@ class SongEventDataRaw implements ICloneable<SongEventDataRaw>
|
||||
@:jignored
|
||||
public var activated:Bool = false;
|
||||
|
||||
public function new(time:Float, event:String, value:Dynamic = null)
|
||||
public function new(time:Float, eventKind:String, value:Dynamic = null)
|
||||
{
|
||||
this.time = time;
|
||||
this.event = event;
|
||||
this.eventKind = eventKind;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@@ -687,22 +691,22 @@ class SongEventDataRaw implements ICloneable<SongEventDataRaw>
|
||||
|
||||
public function clone():SongEventDataRaw
|
||||
{
|
||||
return new SongEventDataRaw(this.time, this.event, this.value);
|
||||
return new SongEventDataRaw(this.time, this.eventKind, this.value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap SongEventData in an abstract so we can overload operators.
|
||||
*/
|
||||
@:forward(time, event, value, activated, getStepTime, clone)
|
||||
@:forward(time, eventKind, value, activated, getStepTime, clone)
|
||||
abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataRaw
|
||||
{
|
||||
public function new(time:Float, event:String, value:Dynamic = null)
|
||||
public function new(time:Float, eventKind:String, value:Dynamic = null)
|
||||
{
|
||||
this = new SongEventDataRaw(time, event, value);
|
||||
this = new SongEventDataRaw(time, eventKind, value);
|
||||
}
|
||||
|
||||
public inline function valueAsStruct(?defaultKey:String = "key"):Dynamic
|
||||
public function valueAsStruct(?defaultKey:String = "key"):Dynamic
|
||||
{
|
||||
if (this.value == null) return {};
|
||||
if (Std.isOfType(this.value, Array))
|
||||
@@ -726,12 +730,12 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
|
||||
|
||||
public inline function getHandler():Null<SongEvent>
|
||||
{
|
||||
return SongEventRegistry.getEvent(this.event);
|
||||
return SongEventRegistry.getEvent(this.eventKind);
|
||||
}
|
||||
|
||||
public inline function getSchema():Null<SongEventSchema>
|
||||
{
|
||||
return SongEventRegistry.getEventSchema(this.event);
|
||||
return SongEventRegistry.getEventSchema(this.eventKind);
|
||||
}
|
||||
|
||||
public inline function getDynamic(key:String):Null<Dynamic>
|
||||
@@ -784,7 +788,7 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
|
||||
var eventHandler = getHandler();
|
||||
var eventSchema = getSchema();
|
||||
|
||||
if (eventSchema == null) return 'Unknown Event: ${this.event}';
|
||||
if (eventSchema == null) return 'Unknown Event: ${this.eventKind}';
|
||||
|
||||
var result = '${eventHandler.getTitle()}';
|
||||
|
||||
@@ -809,19 +813,19 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
|
||||
|
||||
public function clone():SongEventData
|
||||
{
|
||||
return new SongEventData(this.time, this.event, this.value);
|
||||
return new SongEventData(this.time, this.eventKind, this.value);
|
||||
}
|
||||
|
||||
@:op(A == B)
|
||||
public function op_equals(other:SongEventData):Bool
|
||||
{
|
||||
return this.time == other.time && this.event == other.event && this.value == other.value;
|
||||
return this.time == other.time && this.eventKind == other.eventKind && this.value == other.value;
|
||||
}
|
||||
|
||||
@:op(A != B)
|
||||
public function op_notEquals(other:SongEventData):Bool
|
||||
{
|
||||
return this.time != other.time || this.event != other.event || this.value != other.value;
|
||||
return this.time != other.time || this.eventKind != other.eventKind || this.value != other.value;
|
||||
}
|
||||
|
||||
@:op(A > B)
|
||||
@@ -853,7 +857,7 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
|
||||
*/
|
||||
public function toString():String
|
||||
{
|
||||
return 'SongEventData(${this.time}ms, ${this.event}: ${this.value})';
|
||||
return 'SongEventData(${this.time}ms, ${this.eventKind}: ${this.value})';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1022,6 +1026,12 @@ class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
|
||||
{
|
||||
return new SongNoteDataRaw(this.time, this.data, this.length, this.kind);
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
return 'SongNoteData(${this.time}ms, ' + (this.length > 0 ? '[${this.length}ms hold]' : '') + ' ${this.data}'
|
||||
+ (this.kind != '' ? ' [kind: ${this.kind}])' : ')');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -47,7 +47,7 @@ class SongDataUtils
|
||||
public static function offsetSongEventData(events:Array<SongEventData>, offset:Float):Array<SongEventData>
|
||||
{
|
||||
return events.map(function(event:SongEventData):SongEventData {
|
||||
return new SongEventData(event.time + offset, event.event, event.value);
|
||||
return new SongEventData(event.time + offset, event.eventKind, event.value);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
||||
{
|
||||
log('Successfully created scripted entry (${entryCls} = ${entry.id})');
|
||||
entries.set(entry.id, entry);
|
||||
scriptedEntryIds.set(entry.id, entryCls);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -441,6 +442,13 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
||||
return {fileName: entryFilePath, contents: rawJson};
|
||||
}
|
||||
|
||||
function hasMusicDataFile(id:String, ?variation:String):Bool
|
||||
{
|
||||
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
||||
var entryFilePath:String = Paths.file('music/$id/$id-metadata${variation == Constants.DEFAULT_VARIATION ? '' : '-$variation'}.json');
|
||||
return openfl.Assets.exists(entryFilePath);
|
||||
}
|
||||
|
||||
function loadEntryChartFile(id:String, ?variation:String):Null<BaseRegistry.JsonFile>
|
||||
{
|
||||
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
||||
|
||||
@@ -32,18 +32,21 @@ class StageData
|
||||
bf:
|
||||
{
|
||||
zIndex: 0,
|
||||
scale: 1,
|
||||
position: [0, 0],
|
||||
cameraOffsets: [-100, -100]
|
||||
},
|
||||
dad:
|
||||
{
|
||||
zIndex: 0,
|
||||
scale: 1,
|
||||
position: [0, 0],
|
||||
cameraOffsets: [100, -100]
|
||||
},
|
||||
gf:
|
||||
{
|
||||
zIndex: 0,
|
||||
scale: 1,
|
||||
position: [0, 0],
|
||||
cameraOffsets: [0, 0]
|
||||
}
|
||||
@@ -114,6 +117,7 @@ typedef StageDataProp =
|
||||
@:jcustomparse(funkin.data.DataParse.eitherFloatOrFloats)
|
||||
@:jcustomwrite(funkin.data.DataWrite.eitherFloatOrFloats)
|
||||
@:optional
|
||||
@:default(Left(1.0))
|
||||
var scale:haxe.ds.Either<Float, Array<Float>>;
|
||||
|
||||
/**
|
||||
@@ -190,6 +194,13 @@ typedef StageDataCharacter =
|
||||
@:default([0, 0])
|
||||
var position:Array<Float>;
|
||||
|
||||
/**
|
||||
* The scale to render the character at.
|
||||
*/
|
||||
@:optional
|
||||
@:default(1)
|
||||
var scale:Float;
|
||||
|
||||
/**
|
||||
* The camera offsets to apply when focusing on the character on this stage.
|
||||
* @default [-100, -100] for BF, [100, -100] for DAD/OPPONENT, [0, 0] for GF
|
||||
|
||||
@@ -3,12 +3,30 @@ package funkin.graphics;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.graphics.FlxGraphic;
|
||||
import flixel.tweens.FlxTween;
|
||||
import openfl.display3D.textures.TextureBase;
|
||||
import funkin.graphics.framebuffer.FixedBitmapData;
|
||||
import openfl.display.BitmapData;
|
||||
|
||||
/**
|
||||
* An FlxSprite with additional functionality.
|
||||
* - A more efficient method for creating solid color sprites.
|
||||
* - TODO: Better cache handling for textures.
|
||||
*/
|
||||
class FunkinSprite extends FlxSprite
|
||||
{
|
||||
/**
|
||||
* An internal list of all the textures cached with `cacheTexture`.
|
||||
* This excludes any temporary textures like those from `FlxText` or `makeSolidColor`.
|
||||
*/
|
||||
static var currentCachedTextures:Map<String, FlxGraphic> = [];
|
||||
|
||||
/**
|
||||
* An internal list of textures that were cached in the previous state.
|
||||
* We don't know whether we want to keep them cached or not.
|
||||
*/
|
||||
static var previousCachedTextures:Map<String, FlxGraphic> = [];
|
||||
|
||||
/**
|
||||
* @param x Starting X position
|
||||
* @param y Starting Y position
|
||||
@@ -18,26 +36,225 @@ class FunkinSprite extends FlxSprite
|
||||
super(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new FunkinSprite with a static texture.
|
||||
* @param x The starting X position.
|
||||
* @param y The starting Y position.
|
||||
* @param key The key of the texture to load.
|
||||
* @return The new FunkinSprite.
|
||||
*/
|
||||
public static function create(x:Float = 0.0, y:Float = 0.0, key:String):FunkinSprite
|
||||
{
|
||||
var sprite:FunkinSprite = new FunkinSprite(x, y);
|
||||
sprite.loadTexture(key);
|
||||
return sprite;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new FunkinSprite with a Sparrow atlas animated texture.
|
||||
* @param x The starting X position.
|
||||
* @param y The starting Y position.
|
||||
* @param key The key of the texture to load.
|
||||
* @return The new FunkinSprite.
|
||||
*/
|
||||
public static function createSparrow(x:Float = 0.0, y:Float = 0.0, key:String):FunkinSprite
|
||||
{
|
||||
var sprite:FunkinSprite = new FunkinSprite(x, y);
|
||||
sprite.loadSparrow(key);
|
||||
return sprite;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new FunkinSprite with a Packer atlas animated texture.
|
||||
* @param x The starting X position.
|
||||
* @param y The starting Y position.
|
||||
* @param key The key of the texture to load.
|
||||
* @return The new FunkinSprite.
|
||||
*/
|
||||
public static function createPacker(x:Float = 0.0, y:Float = 0.0, key:String):FunkinSprite
|
||||
{
|
||||
var sprite:FunkinSprite = new FunkinSprite(x, y);
|
||||
sprite.loadPacker(key);
|
||||
return sprite;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a static image as the sprite's texture.
|
||||
* @param key The key of the texture to load.
|
||||
* @return This sprite, for chaining.
|
||||
*/
|
||||
public function loadTexture(key:String):FunkinSprite
|
||||
{
|
||||
var graphicKey:String = Paths.image(key);
|
||||
if (!isTextureCached(graphicKey)) FlxG.log.warn('Texture not cached, may experience stuttering! $graphicKey');
|
||||
|
||||
loadGraphic(graphicKey);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply an OpenFL `BitmapData` to this sprite.
|
||||
* @param input The OpenFL `BitmapData` to apply
|
||||
* @return This sprite, for chaining
|
||||
*/
|
||||
public function loadBitmapData(input:BitmapData):FunkinSprite
|
||||
{
|
||||
loadGraphic(input);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply an OpenFL `TextureBase` to this sprite.
|
||||
* @param input The OpenFL `TextureBase` to apply
|
||||
* @return This sprite, for chaining
|
||||
*/
|
||||
public function loadTextureBase(input:TextureBase):FunkinSprite
|
||||
{
|
||||
var inputBitmap:FixedBitmapData = FixedBitmapData.fromTexture(input);
|
||||
|
||||
return loadBitmapData(inputBitmap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an animated texture (Sparrow atlas spritesheet) as the sprite's texture.
|
||||
* @param key The key of the texture to load.
|
||||
* @return This sprite, for chaining.
|
||||
*/
|
||||
public function loadSparrow(key:String):FunkinSprite
|
||||
{
|
||||
var graphicKey:String = Paths.image(key);
|
||||
if (!isTextureCached(graphicKey)) FlxG.log.warn('Texture not cached, may experience stuttering! $graphicKey');
|
||||
|
||||
this.frames = Paths.getSparrowAtlas(key);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an animated texture (Packer atlas spritesheet) as the sprite's texture.
|
||||
* @param key The key of the texture to load.
|
||||
* @return This sprite, for chaining.
|
||||
*/
|
||||
public function loadPacker(key:String):FunkinSprite
|
||||
{
|
||||
var graphicKey:String = Paths.image(key);
|
||||
if (!isTextureCached(graphicKey)) FlxG.log.warn('Texture not cached, may experience stuttering! $graphicKey');
|
||||
|
||||
this.frames = Paths.getPackerAtlas(key);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the texture with the given key is cached.
|
||||
* @param key The key of the texture to check.
|
||||
* @return Whether the texture is cached.
|
||||
*/
|
||||
public static function isTextureCached(key:String):Bool
|
||||
{
|
||||
return FlxG.bitmap.get(key) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the texture with the given key is cached.
|
||||
* @param key The key of the texture to cache.
|
||||
*/
|
||||
public static function cacheTexture(key:String):Void
|
||||
{
|
||||
// We don't want to cache the same texture twice.
|
||||
if (currentCachedTextures.exists(key)) return;
|
||||
|
||||
if (previousCachedTextures.exists(key))
|
||||
{
|
||||
// Move the graphic from the previous cache to the current cache.
|
||||
var graphic = previousCachedTextures.get(key);
|
||||
previousCachedTextures.remove(key);
|
||||
currentCachedTextures.set(key, graphic);
|
||||
return;
|
||||
}
|
||||
|
||||
// Else, texture is currently uncached.
|
||||
var graphic:FlxGraphic = FlxGraphic.fromAssetKey(key, false, null, true);
|
||||
if (graphic == null)
|
||||
{
|
||||
FlxG.log.warn('Failed to cache graphic: $key');
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Successfully cached graphic: $key');
|
||||
graphic.persist = true;
|
||||
currentCachedTextures.set(key, graphic);
|
||||
}
|
||||
}
|
||||
|
||||
public static function cacheSparrow(key:String):Void
|
||||
{
|
||||
cacheTexture(Paths.image(key));
|
||||
}
|
||||
|
||||
public static function cachePacker(key:String):Void
|
||||
{
|
||||
cacheTexture(Paths.image(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this, then `cacheTexture` to keep the textures we still need, then `purgeCache` to remove the textures that we won't be using anymore.
|
||||
*/
|
||||
public static function preparePurgeCache():Void
|
||||
{
|
||||
previousCachedTextures = currentCachedTextures;
|
||||
currentCachedTextures = [];
|
||||
}
|
||||
|
||||
public static function purgeCache():Void
|
||||
{
|
||||
// Everything that is in previousCachedTextures but not in currentCachedTextures should be destroyed.
|
||||
for (graphicKey in previousCachedTextures.keys())
|
||||
{
|
||||
var graphic = previousCachedTextures.get(graphicKey);
|
||||
FlxG.bitmap.remove(graphic);
|
||||
graphic.destroy();
|
||||
previousCachedTextures.remove(graphicKey);
|
||||
}
|
||||
}
|
||||
|
||||
static function isGraphicCached(graphic:FlxGraphic):Bool
|
||||
{
|
||||
if (graphic == null) return false;
|
||||
var result = FlxG.bitmap.get(graphic.key);
|
||||
if (result == null) return false;
|
||||
if (result != graphic)
|
||||
{
|
||||
FlxG.log.warn('Cached graphic does not match original: ${graphic.key}');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Acts similarly to `makeGraphic`, but with improved memory usage,
|
||||
* at the expense of not being able to paint onto the sprite.
|
||||
* at the expense of not being able to paint onto the resulting sprite.
|
||||
*
|
||||
* @param width The target width of the sprite.
|
||||
* @param height The target height of the sprite.
|
||||
* @param color The color to fill the sprite with.
|
||||
* @return This sprite, for chaining.
|
||||
*/
|
||||
public function makeSolidColor(width:Int, height:Int, color:FlxColor = FlxColor.WHITE):FunkinSprite
|
||||
{
|
||||
// Create a tiny solid color graphic and scale it up to the desired size.
|
||||
var graphic:FlxGraphic = FlxG.bitmap.create(2, 2, color, false, 'solid#${color.toHexString(true, false)}');
|
||||
frames = graphic.imageFrame;
|
||||
scale.set(width / 2, height / 2);
|
||||
scale.set(width / 2.0, height / 2.0);
|
||||
updateHitbox();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure scale is applied when cloning a sprite.
|
||||
* Ensure scale is applied when cloning a sprite.R
|
||||
* The default `clone()` method acts kinda weird TBH.
|
||||
* @return A clone of this sprite.
|
||||
*/
|
||||
@@ -50,4 +267,13 @@ class FunkinSprite extends FlxSprite
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override function destroy():Void
|
||||
{
|
||||
frames = null;
|
||||
// Cancel all tweens so they don't continue to run on a destroyed sprite.
|
||||
// This prevents crashes.
|
||||
FlxTween.cancelTweensOf(this);
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ package funkin.graphics.adobeanimate;
|
||||
import flixel.util.FlxSignal.FlxTypedSignal;
|
||||
import flxanimate.FlxAnimate;
|
||||
import flxanimate.FlxAnimate.Settings;
|
||||
import flixel.math.FlxPoint;
|
||||
import flxanimate.frames.FlxAnimateFrames;
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
/**
|
||||
* A sprite which provides convenience functions for rendering a texture atlas with animations.
|
||||
@@ -18,7 +20,7 @@ class FlxAtlasSprite extends FlxAnimate
|
||||
// ?OnComplete:Void -> Void,
|
||||
ShowPivot: #if debug false #else false #end,
|
||||
Antialiasing: true,
|
||||
ScrollFactor: new FlxPoint(1, 1),
|
||||
ScrollFactor: null,
|
||||
// Offset: new FlxPoint(0, 0), // This is just FlxSprite.offset
|
||||
};
|
||||
|
||||
@@ -31,7 +33,7 @@ class FlxAtlasSprite extends FlxAnimate
|
||||
|
||||
var canPlayOtherAnims:Bool = true;
|
||||
|
||||
public function new(x:Float, y:Float, path:String, ?settings:Settings)
|
||||
public function new(x:Float, y:Float, ?path:String, ?settings:Settings)
|
||||
{
|
||||
if (settings == null) settings = SETTINGS;
|
||||
|
||||
@@ -55,8 +57,9 @@ class FlxAtlasSprite extends FlxAnimate
|
||||
*/
|
||||
public function listAnimations():Array<String>
|
||||
{
|
||||
// return this.anim.getFrameLabels();
|
||||
return [""];
|
||||
if (this.anim == null) return [];
|
||||
return this.anim.getFrameLabels();
|
||||
// return [""];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,9 +84,13 @@ class FlxAtlasSprite extends FlxAnimate
|
||||
* @param id A string ID of the animation to play.
|
||||
* @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 loop Whether to loop the animation
|
||||
* NOTE: `loop` and `ignoreOther` are not compatible with each other!
|
||||
*/
|
||||
public function playAnimation(id:String, restart:Bool = false, ignoreOther:Bool = false):Void
|
||||
public function playAnimation(id:String, restart:Bool = false, ignoreOther:Bool = false, ?loop:Bool = false):Void
|
||||
{
|
||||
if (loop == null) loop = false;
|
||||
|
||||
// Skip if not allowed to play animations.
|
||||
if ((!canPlayOtherAnims && !ignoreOther)) return;
|
||||
|
||||
@@ -110,15 +117,21 @@ class FlxAtlasSprite extends FlxAnimate
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop the current animation if it is playing.
|
||||
// This includes removing existing frame callbacks.
|
||||
if (this.currentAnimation != null) this.stopAnimation();
|
||||
anim.callback = function(_, frame:Int) {
|
||||
var offset = loop ? 0 : -1;
|
||||
|
||||
// Add a callback to ensure `onAnimationFinish` is dispatched.
|
||||
addFrameCallback(getNextFrameLabel(id), function() {
|
||||
trace('Animation finished: ' + id);
|
||||
onAnimationFinish.dispatch(id);
|
||||
});
|
||||
if (frame == (anim.getFrameLabel(id).duration + offset) + anim.getFrameLabel(id).index)
|
||||
{
|
||||
if (loop)
|
||||
{
|
||||
playAnimation(id, true, false, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
onAnimationFinish.dispatch(id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Prevent other animations from playing if `ignoreOther` is true.
|
||||
if (ignoreOther) canPlayOtherAnims = false;
|
||||
@@ -128,6 +141,11 @@ class FlxAtlasSprite extends FlxAnimate
|
||||
this.currentAnimation = id;
|
||||
}
|
||||
|
||||
override public function update(elapsed:Float)
|
||||
{
|
||||
super.update(elapsed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the current animation.
|
||||
*/
|
||||
@@ -146,22 +164,22 @@ class FlxAtlasSprite extends FlxAnimate
|
||||
frameLabel.add(callback);
|
||||
}
|
||||
|
||||
inline function goToFrameLabel(label:String):Void
|
||||
function goToFrameLabel(label:String):Void
|
||||
{
|
||||
this.anim.goToFrameLabel(label);
|
||||
}
|
||||
|
||||
inline function getNextFrameLabel(label:String):String
|
||||
function getNextFrameLabel(label:String):String
|
||||
{
|
||||
return listAnimations()[(getLabelIndex(label) + 1) % listAnimations().length];
|
||||
}
|
||||
|
||||
inline function getLabelIndex(label:String):Int
|
||||
function getLabelIndex(label:String):Int
|
||||
{
|
||||
return listAnimations().indexOf(label);
|
||||
}
|
||||
|
||||
inline function goToFrameIndex(index:Int):Void
|
||||
function goToFrameIndex(index:Int):Void
|
||||
{
|
||||
this.anim.curFrame = index;
|
||||
}
|
||||
@@ -169,7 +187,7 @@ class FlxAtlasSprite extends FlxAnimate
|
||||
public function cleanupAnimation(_:String):Void
|
||||
{
|
||||
canPlayOtherAnims = true;
|
||||
this.currentAnimation = null;
|
||||
this.anim.stop();
|
||||
// this.currentAnimation = null;
|
||||
this.anim.pause();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,11 +32,11 @@ class FixedBitmapData extends BitmapData
|
||||
public static function fromTexture(texture:TextureBase):FixedBitmapData
|
||||
{
|
||||
if (texture == null) return null;
|
||||
final bitmapData = new FixedBitmapData(texture.__width, texture.__height, true, 0);
|
||||
bitmapData.readable = false;
|
||||
final bitmapData:FixedBitmapData = new FixedBitmapData(texture.__width, texture.__height, true, 0);
|
||||
// bitmapData.readable = false;
|
||||
bitmapData.__texture = texture;
|
||||
bitmapData.__textureContext = texture.__textureContext;
|
||||
bitmapData.image = null;
|
||||
// bitmapData.image = null;
|
||||
return bitmapData;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +1,113 @@
|
||||
package funkin.graphics.video;
|
||||
|
||||
import flixel.FlxBasic;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxSignal.FlxTypedSignal;
|
||||
import funkin.audio.FunkinSound;
|
||||
import openfl.display3D.textures.TextureBase;
|
||||
import openfl.events.NetStatusEvent;
|
||||
import openfl.media.SoundTransform;
|
||||
import openfl.media.Video;
|
||||
import openfl.net.NetConnection;
|
||||
import openfl.net.NetStream;
|
||||
|
||||
/**
|
||||
* Plays a video via a NetStream. Only works on HTML5.
|
||||
* This does NOT replace hxCodec, nor does hxCodec replace this. hxCodec only works on desktop and does not work on HTML5!
|
||||
* This does NOT replace hxCodec, nor does hxCodec replace this.
|
||||
* hxCodec only works on desktop and does not work on HTML5!
|
||||
*/
|
||||
class FlxVideo extends FlxBasic
|
||||
class FlxVideo extends FunkinSprite
|
||||
{
|
||||
var video:Video;
|
||||
var netStream:NetStream;
|
||||
|
||||
public var finishCallback:Void->Void;
|
||||
var videoPath:String;
|
||||
|
||||
/**
|
||||
* Doesn't actually interact with Flixel shit, only just a pleasant to use class
|
||||
* A callback to execute when the video finishes.
|
||||
*/
|
||||
public var finishCallback:Void->Void;
|
||||
|
||||
public function new(videoPath:String)
|
||||
{
|
||||
super();
|
||||
|
||||
this.videoPath = videoPath;
|
||||
|
||||
makeGraphic(2, 2, FlxColor.TRANSPARENT);
|
||||
|
||||
video = new Video();
|
||||
video.x = 0;
|
||||
video.y = 0;
|
||||
video.alpha = 0;
|
||||
|
||||
FlxG.addChildBelowMouse(video);
|
||||
FlxG.game.addChild(video);
|
||||
|
||||
var netConnection = new NetConnection();
|
||||
var netConnection:NetConnection = new NetConnection();
|
||||
netConnection.connect(null);
|
||||
|
||||
netStream = new NetStream(netConnection);
|
||||
netStream.client = {onMetaData: client_onMetaData};
|
||||
netConnection.addEventListener(NetStatusEvent.NET_STATUS, netConnection_onNetStatus);
|
||||
netStream.client = {onMetaData: onClientMetaData};
|
||||
netConnection.addEventListener(NetStatusEvent.NET_STATUS, onNetConnectionNetStatus);
|
||||
netStream.play(videoPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the FlxVideo to pause playback.
|
||||
*/
|
||||
public function pauseVideo():Void
|
||||
{
|
||||
if (netStream != null)
|
||||
{
|
||||
netStream.pause();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the FlxVideo to resume if it is paused.
|
||||
*/
|
||||
public function resumeVideo():Void
|
||||
{
|
||||
// Resume playing the video.
|
||||
if (netStream != null)
|
||||
{
|
||||
netStream.resume();
|
||||
}
|
||||
}
|
||||
|
||||
var videoAvailable:Bool = false;
|
||||
var frameTimer:Float;
|
||||
|
||||
static final FRAME_RATE:Float = 60;
|
||||
|
||||
public override function update(elapsed:Float):Void
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
if (frameTimer >= (1 / FRAME_RATE))
|
||||
{
|
||||
frameTimer = 0;
|
||||
// TODO: We just draw the video buffer to the sprite 60 times a second.
|
||||
// Can we copy the video buffer instead somehow?
|
||||
pixels.draw(video);
|
||||
}
|
||||
|
||||
if (videoAvailable) frameTimer += elapsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the FlxVideo to seek to the beginning.
|
||||
*/
|
||||
public function restartVideo():Void
|
||||
{
|
||||
// Seek to the beginning of the video.
|
||||
if (netStream != null)
|
||||
{
|
||||
netStream.seek(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the FlxVideo to end.
|
||||
*/
|
||||
public function finishVideo():Void
|
||||
{
|
||||
netStream.dispose();
|
||||
@@ -48,15 +116,48 @@ class FlxVideo extends FlxBasic
|
||||
if (finishCallback != null) finishCallback();
|
||||
}
|
||||
|
||||
public function client_onMetaData(metaData:Dynamic)
|
||||
public override function destroy():Void
|
||||
{
|
||||
if (netStream != null)
|
||||
{
|
||||
netStream.dispose();
|
||||
|
||||
if (FlxG.game.contains(video)) FlxG.game.removeChild(video);
|
||||
}
|
||||
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback executed when the video stream loads.
|
||||
* @param metaData The metadata of the video
|
||||
*/
|
||||
public function onClientMetaData(metaData:Dynamic):Void
|
||||
{
|
||||
video.attachNetStream(netStream);
|
||||
|
||||
video.width = FlxG.width;
|
||||
video.height = FlxG.height;
|
||||
onVideoReady();
|
||||
}
|
||||
|
||||
function netConnection_onNetStatus(event:NetStatusEvent):Void
|
||||
function onVideoReady():Void
|
||||
{
|
||||
video.width = FlxG.width;
|
||||
video.height = FlxG.height;
|
||||
|
||||
videoAvailable = true;
|
||||
|
||||
FunkinSound.onVolumeChanged.add(onVolumeChanged);
|
||||
onVolumeChanged(FlxG.sound.muted ? 0 : FlxG.sound.volume);
|
||||
|
||||
makeGraphic(Std.int(video.width), Std.int(video.height), FlxColor.TRANSPARENT);
|
||||
}
|
||||
|
||||
function onVolumeChanged(volume:Float):Void
|
||||
{
|
||||
netStream.soundTransform = new SoundTransform(volume);
|
||||
}
|
||||
|
||||
function onNetConnectionNetStatus(event:NetStatusEvent):Void
|
||||
{
|
||||
if (event.info.code == 'NetStream.Play.Complete') finishVideo();
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ using Lambda;
|
||||
using StringTools;
|
||||
using funkin.util.tools.ArraySortTools;
|
||||
using funkin.util.tools.ArrayTools;
|
||||
using funkin.util.tools.DynamicTools;
|
||||
using funkin.util.tools.FloatTools;
|
||||
using funkin.util.tools.Int64Tools;
|
||||
using funkin.util.tools.IntTools;
|
||||
|
||||
@@ -58,12 +58,11 @@ class Controls extends FlxActionSet
|
||||
var _back = new FlxActionDigital(Action.BACK);
|
||||
var _pause = new FlxActionDigital(Action.PAUSE);
|
||||
var _reset = new FlxActionDigital(Action.RESET);
|
||||
var _screenshot = new FlxActionDigital(Action.SCREENSHOT);
|
||||
var _cutscene_advance = new FlxActionDigital(Action.CUTSCENE_ADVANCE);
|
||||
var _cutscene_skip = new FlxActionDigital(Action.CUTSCENE_SKIP);
|
||||
var _debug_menu = new FlxActionDigital(Action.DEBUG_MENU);
|
||||
var _debug_chart = new FlxActionDigital(Action.DEBUG_CHART);
|
||||
var _debug_stage = new FlxActionDigital(Action.DEBUG_STAGE);
|
||||
var _screenshot = new FlxActionDigital(Action.SCREENSHOT);
|
||||
var _volume_up = new FlxActionDigital(Action.VOLUME_UP);
|
||||
var _volume_down = new FlxActionDigital(Action.VOLUME_DOWN);
|
||||
var _volume_mute = new FlxActionDigital(Action.VOLUME_MUTE);
|
||||
@@ -208,16 +207,21 @@ class Controls extends FlxActionSet
|
||||
inline function get_PAUSE()
|
||||
return _pause.check();
|
||||
|
||||
public var RESET(get, never):Bool;
|
||||
|
||||
inline function get_RESET()
|
||||
return _reset.check();
|
||||
|
||||
public var SCREENSHOT(get, never):Bool;
|
||||
|
||||
inline function get_SCREENSHOT()
|
||||
return _screenshot.check();
|
||||
|
||||
public var CUTSCENE_ADVANCE(get, never):Bool;
|
||||
|
||||
inline function get_CUTSCENE_ADVANCE()
|
||||
return _cutscene_advance.check();
|
||||
|
||||
public var CUTSCENE_SKIP(get, never):Bool;
|
||||
|
||||
inline function get_CUTSCENE_SKIP()
|
||||
return _cutscene_skip.check();
|
||||
|
||||
public var DEBUG_MENU(get, never):Bool;
|
||||
|
||||
inline function get_DEBUG_MENU()
|
||||
@@ -233,11 +237,6 @@ class Controls extends FlxActionSet
|
||||
inline function get_DEBUG_STAGE()
|
||||
return _debug_stage.check();
|
||||
|
||||
public var SCREENSHOT(get, never):Bool;
|
||||
|
||||
inline function get_SCREENSHOT()
|
||||
return _screenshot.check();
|
||||
|
||||
public var VOLUME_UP(get, never):Bool;
|
||||
|
||||
inline function get_VOLUME_UP()
|
||||
@@ -253,11 +252,6 @@ class Controls extends FlxActionSet
|
||||
inline function get_VOLUME_MUTE()
|
||||
return _volume_mute.check();
|
||||
|
||||
public var RESET(get, never):Bool;
|
||||
|
||||
inline function get_RESET()
|
||||
return _reset.check();
|
||||
|
||||
public function new(name, scheme:KeyboardScheme = null)
|
||||
{
|
||||
super(name);
|
||||
@@ -289,16 +283,15 @@ class Controls extends FlxActionSet
|
||||
add(_accept);
|
||||
add(_back);
|
||||
add(_pause);
|
||||
add(_reset);
|
||||
add(_screenshot);
|
||||
add(_cutscene_advance);
|
||||
add(_cutscene_skip);
|
||||
add(_debug_menu);
|
||||
add(_debug_chart);
|
||||
add(_debug_stage);
|
||||
add(_screenshot);
|
||||
add(_volume_up);
|
||||
add(_volume_down);
|
||||
add(_volume_mute);
|
||||
add(_reset);
|
||||
|
||||
for (action in digitalActions)
|
||||
byName[action.name] = action;
|
||||
@@ -383,12 +376,11 @@ class Controls extends FlxActionSet
|
||||
case BACK: _back;
|
||||
case PAUSE: _pause;
|
||||
case RESET: _reset;
|
||||
case SCREENSHOT: _screenshot;
|
||||
case CUTSCENE_ADVANCE: _cutscene_advance;
|
||||
case CUTSCENE_SKIP: _cutscene_skip;
|
||||
case DEBUG_MENU: _debug_menu;
|
||||
case DEBUG_CHART: _debug_chart;
|
||||
case DEBUG_STAGE: _debug_stage;
|
||||
case SCREENSHOT: _screenshot;
|
||||
case VOLUME_UP: _volume_up;
|
||||
case VOLUME_DOWN: _volume_down;
|
||||
case VOLUME_MUTE: _volume_mute;
|
||||
@@ -449,26 +441,24 @@ class Controls extends FlxActionSet
|
||||
func(_back, JUST_PRESSED);
|
||||
case PAUSE:
|
||||
func(_pause, JUST_PRESSED);
|
||||
case RESET:
|
||||
func(_reset, JUST_PRESSED);
|
||||
case SCREENSHOT:
|
||||
func(_screenshot, JUST_PRESSED);
|
||||
case CUTSCENE_ADVANCE:
|
||||
func(_cutscene_advance, JUST_PRESSED);
|
||||
case CUTSCENE_SKIP:
|
||||
func(_cutscene_skip, PRESSED);
|
||||
case DEBUG_MENU:
|
||||
func(_debug_menu, JUST_PRESSED);
|
||||
case DEBUG_CHART:
|
||||
func(_debug_chart, JUST_PRESSED);
|
||||
case DEBUG_STAGE:
|
||||
func(_debug_stage, JUST_PRESSED);
|
||||
case SCREENSHOT:
|
||||
func(_screenshot, JUST_PRESSED);
|
||||
case VOLUME_UP:
|
||||
func(_volume_up, JUST_PRESSED);
|
||||
case VOLUME_DOWN:
|
||||
func(_volume_down, JUST_PRESSED);
|
||||
case VOLUME_MUTE:
|
||||
func(_volume_mute, JUST_PRESSED);
|
||||
case RESET:
|
||||
func(_reset, JUST_PRESSED);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -654,13 +644,12 @@ class Controls extends FlxActionSet
|
||||
bindKeys(Control.ACCEPT, getDefaultKeybinds(scheme, Control.ACCEPT));
|
||||
bindKeys(Control.BACK, getDefaultKeybinds(scheme, Control.BACK));
|
||||
bindKeys(Control.PAUSE, getDefaultKeybinds(scheme, Control.PAUSE));
|
||||
bindKeys(Control.RESET, getDefaultKeybinds(scheme, Control.RESET));
|
||||
bindKeys(Control.SCREENSHOT, getDefaultKeybinds(scheme, Control.SCREENSHOT));
|
||||
bindKeys(Control.CUTSCENE_ADVANCE, getDefaultKeybinds(scheme, Control.CUTSCENE_ADVANCE));
|
||||
bindKeys(Control.CUTSCENE_SKIP, getDefaultKeybinds(scheme, Control.CUTSCENE_SKIP));
|
||||
bindKeys(Control.DEBUG_MENU, getDefaultKeybinds(scheme, Control.DEBUG_MENU));
|
||||
bindKeys(Control.DEBUG_CHART, getDefaultKeybinds(scheme, Control.DEBUG_CHART));
|
||||
bindKeys(Control.DEBUG_STAGE, getDefaultKeybinds(scheme, Control.DEBUG_STAGE));
|
||||
bindKeys(Control.RESET, getDefaultKeybinds(scheme, Control.RESET));
|
||||
bindKeys(Control.SCREENSHOT, getDefaultKeybinds(scheme, Control.SCREENSHOT));
|
||||
bindKeys(Control.VOLUME_UP, getDefaultKeybinds(scheme, Control.VOLUME_UP));
|
||||
bindKeys(Control.VOLUME_DOWN, getDefaultKeybinds(scheme, Control.VOLUME_DOWN));
|
||||
bindKeys(Control.VOLUME_MUTE, getDefaultKeybinds(scheme, Control.VOLUME_MUTE));
|
||||
@@ -683,16 +672,15 @@ class Controls extends FlxActionSet
|
||||
case Control.ACCEPT: return [Z, SPACE, ENTER];
|
||||
case Control.BACK: return [X, BACKSPACE, ESCAPE];
|
||||
case Control.PAUSE: return [P, ENTER, ESCAPE];
|
||||
case Control.RESET: return [R];
|
||||
case Control.SCREENSHOT: return [F3]; // TODO: Change this back to PrintScreen
|
||||
case Control.CUTSCENE_ADVANCE: return [Z, ENTER];
|
||||
case Control.CUTSCENE_SKIP: return [P, ESCAPE];
|
||||
case Control.DEBUG_MENU: return [GRAVEACCENT];
|
||||
case Control.DEBUG_CHART: return [];
|
||||
case Control.DEBUG_STAGE: return [];
|
||||
case Control.SCREENSHOT: return [F3]; // TODO: Change this back to PrintScreen
|
||||
case Control.VOLUME_UP: return [PLUS, NUMPADPLUS];
|
||||
case Control.VOLUME_DOWN: return [MINUS, NUMPADMINUS];
|
||||
case Control.VOLUME_MUTE: return [ZERO, NUMPADZERO];
|
||||
case Control.RESET: return [R];
|
||||
}
|
||||
case Duo(true):
|
||||
switch (control) {
|
||||
@@ -707,16 +695,15 @@ class Controls extends FlxActionSet
|
||||
case Control.ACCEPT: return [G, Z];
|
||||
case Control.BACK: return [H, X];
|
||||
case Control.PAUSE: return [ONE];
|
||||
case Control.RESET: return [R];
|
||||
case Control.SCREENSHOT: return [PRINTSCREEN];
|
||||
case Control.CUTSCENE_ADVANCE: return [G, Z];
|
||||
case Control.CUTSCENE_SKIP: return [ONE];
|
||||
case Control.DEBUG_MENU: return [GRAVEACCENT];
|
||||
case Control.DEBUG_CHART: return [];
|
||||
case Control.DEBUG_STAGE: return [];
|
||||
case Control.SCREENSHOT: return [PRINTSCREEN];
|
||||
case Control.VOLUME_UP: return [PLUS];
|
||||
case Control.VOLUME_DOWN: return [MINUS];
|
||||
case Control.VOLUME_MUTE: return [ZERO];
|
||||
case Control.RESET: return [R];
|
||||
}
|
||||
case Duo(false):
|
||||
switch (control) {
|
||||
@@ -731,16 +718,15 @@ class Controls extends FlxActionSet
|
||||
case Control.ACCEPT: return [ENTER];
|
||||
case Control.BACK: return [ESCAPE];
|
||||
case Control.PAUSE: return [ONE];
|
||||
case Control.RESET: return [R];
|
||||
case Control.SCREENSHOT: return [PRINTSCREEN];
|
||||
case Control.CUTSCENE_ADVANCE: return [ENTER];
|
||||
case Control.CUTSCENE_SKIP: return [ONE];
|
||||
case Control.DEBUG_MENU: return [GRAVEACCENT];
|
||||
case Control.DEBUG_CHART: return [];
|
||||
case Control.DEBUG_STAGE: return [];
|
||||
case Control.SCREENSHOT: return [PRINTSCREEN];
|
||||
case Control.VOLUME_UP: return [NUMPADPLUS];
|
||||
case Control.VOLUME_DOWN: return [NUMPADMINUS];
|
||||
case Control.VOLUME_MUTE: return [NUMPADZERO];
|
||||
case Control.RESET: return [R];
|
||||
}
|
||||
default:
|
||||
// Fallthrough.
|
||||
@@ -843,15 +829,14 @@ class Controls extends FlxActionSet
|
||||
Control.NOTE_LEFT => getDefaultGamepadBinds(Control.NOTE_LEFT),
|
||||
Control.NOTE_RIGHT => getDefaultGamepadBinds(Control.NOTE_RIGHT),
|
||||
Control.PAUSE => getDefaultGamepadBinds(Control.PAUSE),
|
||||
Control.RESET => getDefaultGamepadBinds(Control.RESET),
|
||||
// Control.SCREENSHOT => [],
|
||||
// Control.VOLUME_UP => [RIGHT_SHOULDER],
|
||||
// Control.VOLUME_DOWN => [LEFT_SHOULDER],
|
||||
// Control.VOLUME_MUTE => [RIGHT_TRIGGER],
|
||||
Control.CUTSCENE_ADVANCE => getDefaultGamepadBinds(Control.CUTSCENE_ADVANCE),
|
||||
Control.CUTSCENE_SKIP => getDefaultGamepadBinds(Control.CUTSCENE_SKIP),
|
||||
// Control.DEBUG_MENU
|
||||
// Control.DEBUG_CHART
|
||||
Control.RESET => getDefaultGamepadBinds(Control.RESET)
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -868,15 +853,14 @@ class Controls extends FlxActionSet
|
||||
case Control.NOTE_LEFT: return [DPAD_LEFT, X, LEFT_STICK_DIGITAL_LEFT, RIGHT_STICK_DIGITAL_LEFT];
|
||||
case Control.NOTE_RIGHT: return [DPAD_RIGHT, B, LEFT_STICK_DIGITAL_RIGHT, RIGHT_STICK_DIGITAL_RIGHT];
|
||||
case Control.PAUSE: return [START];
|
||||
case Control.RESET: return [RIGHT_SHOULDER];
|
||||
case Control.SCREENSHOT: return [];
|
||||
case Control.VOLUME_UP: return [];
|
||||
case Control.VOLUME_DOWN: return [];
|
||||
case Control.VOLUME_MUTE: return [];
|
||||
case Control.CUTSCENE_ADVANCE: return [A];
|
||||
case Control.CUTSCENE_SKIP: return [START];
|
||||
case Control.DEBUG_MENU: return [];
|
||||
case Control.DEBUG_CHART: return [];
|
||||
case Control.RESET: return [RIGHT_SHOULDER];
|
||||
default:
|
||||
// Fallthrough.
|
||||
}
|
||||
@@ -1228,14 +1212,13 @@ enum Control
|
||||
UI_RIGHT;
|
||||
UI_DOWN;
|
||||
RESET;
|
||||
SCREENSHOT;
|
||||
ACCEPT;
|
||||
BACK;
|
||||
PAUSE;
|
||||
// CUTSCENE
|
||||
CUTSCENE_ADVANCE;
|
||||
CUTSCENE_SKIP;
|
||||
// SCREENSHOT
|
||||
SCREENSHOT;
|
||||
// VOLUME
|
||||
VOLUME_UP;
|
||||
VOLUME_DOWN;
|
||||
@@ -1279,15 +1262,14 @@ abstract Action(String) to String from String
|
||||
var BACK = "back";
|
||||
var PAUSE = "pause";
|
||||
var RESET = "reset";
|
||||
// SCREENSHOT
|
||||
var SCREENSHOT = "screenshot";
|
||||
// CUTSCENE
|
||||
var CUTSCENE_ADVANCE = "cutscene_advance";
|
||||
var CUTSCENE_SKIP = "cutscene_skip";
|
||||
// VOLUME
|
||||
var VOLUME_UP = "volume_up";
|
||||
var VOLUME_DOWN = "volume_down";
|
||||
var VOLUME_MUTE = "volume_mute";
|
||||
// SCREENSHOT
|
||||
var SCREENSHOT = "screenshot";
|
||||
// DEBUG
|
||||
var DEBUG_MENU = "debug_menu";
|
||||
var DEBUG_CHART = "debug_chart";
|
||||
|
||||
@@ -56,7 +56,20 @@ interface IStateStageProp extends IScriptedClass
|
||||
*/
|
||||
interface INoteScriptedClass extends IScriptedClass
|
||||
{
|
||||
public function onNoteHit(event:NoteScriptEvent):Void;
|
||||
/**
|
||||
* Called when a note enters the field of view and approaches the strumline.
|
||||
*/
|
||||
public function onNoteIncoming(event:NoteScriptEvent):Void;
|
||||
|
||||
/**
|
||||
* Called when EITHER player hits a note.
|
||||
* Query the note attached to the event to determine if it was hit by the player or CPU.
|
||||
*/
|
||||
public function onNoteHit(event:HitNoteScriptEvent):Void;
|
||||
|
||||
/**
|
||||
* Called when EITHER player (usually the player) misses a note.
|
||||
*/
|
||||
public function onNoteMiss(event:NoteScriptEvent):Void;
|
||||
}
|
||||
|
||||
@@ -73,7 +86,7 @@ interface INoteScriptedClass extends IScriptedClass
|
||||
/**
|
||||
* Defines a set of callbacks available to scripted classes that involve the lifecycle of the Play State.
|
||||
*/
|
||||
interface IPlayStateScriptedClass extends IScriptedClass
|
||||
interface IPlayStateScriptedClass extends INoteScriptedClass
|
||||
{
|
||||
/**
|
||||
* Called when the game is paused.
|
||||
@@ -113,17 +126,6 @@ interface IPlayStateScriptedClass extends IScriptedClass
|
||||
*/
|
||||
public function onSongRetry(event:ScriptEvent):Void;
|
||||
|
||||
/**
|
||||
* Called when EITHER player hits a note.
|
||||
* Query the note attached to the event to determine if it was hit by the player or CPU.
|
||||
*/
|
||||
public function onNoteHit(event:NoteScriptEvent):Void;
|
||||
|
||||
/**
|
||||
* Called when EITHER player (usually the player) misses a note.
|
||||
*/
|
||||
public function onNoteMiss(event:NoteScriptEvent):Void;
|
||||
|
||||
/**
|
||||
* Called when the player presses a key when no note is on the strumline.
|
||||
*/
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
package funkin.modding;
|
||||
|
||||
import funkin.util.macro.ClassMacro;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.data.song.SongData;
|
||||
import funkin.data.stage.StageData;
|
||||
import polymod.Polymod;
|
||||
import polymod.backends.PolymodAssets.PolymodAssetType;
|
||||
import polymod.format.ParseRules.TextFileFormat;
|
||||
import funkin.data.event.SongEventRegistry;
|
||||
import funkin.data.stage.StageRegistry;
|
||||
import funkin.util.FileUtil;
|
||||
import funkin.data.level.LevelRegistry;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.data.dialogue.ConversationRegistry;
|
||||
import funkin.data.dialogue.DialogueBoxRegistry;
|
||||
import funkin.data.dialogue.SpeakerRegistry;
|
||||
import funkin.data.event.SongEventRegistry;
|
||||
import funkin.data.level.LevelRegistry;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.data.stage.StageRegistry;
|
||||
import funkin.data.freeplay.AlbumRegistry;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import funkin.save.Save;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.util.FileUtil;
|
||||
import funkin.util.macro.ClassMacro;
|
||||
import polymod.backends.PolymodAssets.PolymodAssetType;
|
||||
import polymod.format.ParseRules.TextFileFormat;
|
||||
import polymod.Polymod;
|
||||
|
||||
/**
|
||||
* A class for interacting with Polymod, the atomic modding framework for Haxe.
|
||||
*/
|
||||
class PolymodHandler
|
||||
{
|
||||
/**
|
||||
@@ -27,16 +29,33 @@ class PolymodHandler
|
||||
* Bug fixes increment the patch version, new features increment the minor version.
|
||||
* Changes that break old mods increment the major version.
|
||||
*/
|
||||
static final API_VERSION:String = "0.1.0";
|
||||
static final API_VERSION:String = '0.1.0';
|
||||
|
||||
/**
|
||||
* Where relative to the executable that mods are located.
|
||||
*/
|
||||
static final MOD_FOLDER:String = #if (REDIRECT_ASSETS_FOLDER && macos) "../../../../../../../example_mods" #elseif REDIRECT_ASSETS_FOLDER "../../../../example_mods" #else "mods" #end;
|
||||
static final MOD_FOLDER:String =
|
||||
#if (REDIRECT_ASSETS_FOLDER && macos)
|
||||
'../../../../../../../example_mods'
|
||||
#elseif REDIRECT_ASSETS_FOLDER
|
||||
'../../../../example_mods'
|
||||
#else
|
||||
'mods'
|
||||
#end;
|
||||
|
||||
static final CORE_FOLDER:Null<String> = #if (REDIRECT_ASSETS_FOLDER && macos) "../../../../../../../assets" #elseif REDIRECT_ASSETS_FOLDER "../../../../assets" #else null #end;
|
||||
static final CORE_FOLDER:Null<String> =
|
||||
#if (REDIRECT_ASSETS_FOLDER && macos)
|
||||
'../../../../../../../assets'
|
||||
#elseif REDIRECT_ASSETS_FOLDER
|
||||
'../../../../assets'
|
||||
#else
|
||||
null
|
||||
#end;
|
||||
|
||||
public static function createModRoot()
|
||||
/**
|
||||
* If the mods folder doesn't exist, create it.
|
||||
*/
|
||||
public static function createModRoot():Void
|
||||
{
|
||||
FileUtil.createDirIfNotExists(MOD_FOLDER);
|
||||
}
|
||||
@@ -44,40 +63,44 @@ class PolymodHandler
|
||||
/**
|
||||
* Loads the game with ALL mods enabled with Polymod.
|
||||
*/
|
||||
public static function loadAllMods()
|
||||
public static function loadAllMods():Void
|
||||
{
|
||||
// Create the mod root if it doesn't exist.
|
||||
createModRoot();
|
||||
trace("Initializing Polymod (using all mods)...");
|
||||
trace('Initializing Polymod (using all mods)...');
|
||||
loadModsById(getAllModIds());
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the game with configured mods enabled with Polymod.
|
||||
*/
|
||||
public static function loadEnabledMods()
|
||||
public static function loadEnabledMods():Void
|
||||
{
|
||||
// Create the mod root if it doesn't exist.
|
||||
createModRoot();
|
||||
|
||||
trace("Initializing Polymod (using configured mods)...");
|
||||
loadModsById(Save.get().enabledModIds);
|
||||
trace('Initializing Polymod (using configured mods)...');
|
||||
loadModsById(Save.instance.enabledModIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the game without any mods enabled with Polymod.
|
||||
*/
|
||||
public static function loadNoMods()
|
||||
public static function loadNoMods():Void
|
||||
{
|
||||
// Create the mod root if it doesn't exist.
|
||||
createModRoot();
|
||||
|
||||
// We still need to configure the debug print calls etc.
|
||||
trace("Initializing Polymod (using no mods)...");
|
||||
trace('Initializing Polymod (using no mods)...');
|
||||
loadModsById([]);
|
||||
}
|
||||
|
||||
public static function loadModsById(ids:Array<String>)
|
||||
/**
|
||||
* Load all the mods with the given ids.
|
||||
* @param ids The ORDERED list of mod ids to load.
|
||||
*/
|
||||
public static function loadModsById(ids:Array<String>):Void
|
||||
{
|
||||
if (ids.length == 0)
|
||||
{
|
||||
@@ -90,7 +113,7 @@ class PolymodHandler
|
||||
|
||||
buildImports();
|
||||
|
||||
var loadedModList = polymod.Polymod.init(
|
||||
var loadedModList:Array<ModMetadata> = polymod.Polymod.init(
|
||||
{
|
||||
// Root directory for all mods.
|
||||
modRoot: MOD_FOLDER,
|
||||
@@ -142,30 +165,40 @@ class PolymodHandler
|
||||
}
|
||||
|
||||
#if debug
|
||||
var fileList = Polymod.listModFiles(PolymodAssetType.IMAGE);
|
||||
var fileList:Array<String> = Polymod.listModFiles(PolymodAssetType.IMAGE);
|
||||
trace('Installed mods have replaced ${fileList.length} images.');
|
||||
for (item in fileList)
|
||||
{
|
||||
trace(' * $item');
|
||||
}
|
||||
|
||||
fileList = Polymod.listModFiles(PolymodAssetType.TEXT);
|
||||
trace('Installed mods have added/replaced ${fileList.length} text files.');
|
||||
for (item in fileList)
|
||||
{
|
||||
trace(' * $item');
|
||||
}
|
||||
|
||||
fileList = Polymod.listModFiles(PolymodAssetType.AUDIO_MUSIC);
|
||||
trace('Installed mods have replaced ${fileList.length} music files.');
|
||||
for (item in fileList)
|
||||
{
|
||||
trace(' * $item');
|
||||
}
|
||||
|
||||
fileList = Polymod.listModFiles(PolymodAssetType.AUDIO_SOUND);
|
||||
trace('Installed mods have replaced ${fileList.length} sound files.');
|
||||
for (item in fileList)
|
||||
{
|
||||
trace(' * $item');
|
||||
}
|
||||
|
||||
fileList = Polymod.listModFiles(PolymodAssetType.AUDIO_GENERIC);
|
||||
trace('Installed mods have replaced ${fileList.length} generic audio files.');
|
||||
for (item in fileList)
|
||||
{
|
||||
trace(' * $item');
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
@@ -183,21 +216,21 @@ class PolymodHandler
|
||||
for (cls in ClassMacro.listClassesInPackage('polymod'))
|
||||
{
|
||||
if (cls == null) continue;
|
||||
var className = Type.getClassName(cls);
|
||||
var className:String = Type.getClassName(cls);
|
||||
Polymod.blacklistImport(className);
|
||||
}
|
||||
}
|
||||
|
||||
static function buildParseRules():polymod.format.ParseRules
|
||||
{
|
||||
var output = polymod.format.ParseRules.getDefault();
|
||||
var output:polymod.format.ParseRules = polymod.format.ParseRules.getDefault();
|
||||
// Ensure TXT files have merge support.
|
||||
output.addType("txt", TextFileFormat.LINES);
|
||||
output.addType('txt', TextFileFormat.LINES);
|
||||
// Ensure script files have merge support.
|
||||
output.addType("hscript", TextFileFormat.PLAINTEXT);
|
||||
output.addType("hxs", TextFileFormat.PLAINTEXT);
|
||||
output.addType("hxc", TextFileFormat.PLAINTEXT);
|
||||
output.addType("hx", TextFileFormat.PLAINTEXT);
|
||||
output.addType('hscript', TextFileFormat.PLAINTEXT);
|
||||
output.addType('hxs', TextFileFormat.PLAINTEXT);
|
||||
output.addType('hxc', TextFileFormat.PLAINTEXT);
|
||||
output.addType('hx', TextFileFormat.PLAINTEXT);
|
||||
|
||||
// You can specify the format of a specific file, with file extension.
|
||||
// output.addFile("data/introText.txt", TextFileFormat.LINES)
|
||||
@@ -208,17 +241,21 @@ class PolymodHandler
|
||||
{
|
||||
return {
|
||||
assetLibraryPaths: [
|
||||
"default" => "preload", "shared" => "shared", "songs" => "songs", "tutorial" => "tutorial", "week1" => "week1", "week2" => "week2",
|
||||
"week3" => "week3", "week4" => "week4", "week5" => "week5", "week6" => "week6", "week7" => "week7", "weekend1" => "weekend1",
|
||||
'default' => 'preload', 'shared' => 'shared', 'songs' => 'songs', 'tutorial' => 'tutorial', 'week1' => 'week1', 'week2' => 'week2',
|
||||
'week3' => 'week3', 'week4' => 'week4', 'week5' => 'week5', 'week6' => 'week6', 'week7' => 'week7', 'weekend1' => 'weekend1',
|
||||
],
|
||||
coreAssetRedirect: CORE_FOLDER,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a list of metadata for ALL installed mods, including disabled mods.
|
||||
* @return An array of mod metadata
|
||||
*/
|
||||
public static function getAllMods():Array<ModMetadata>
|
||||
{
|
||||
trace('Scanning the mods folder...');
|
||||
var modMetadata = Polymod.scan(
|
||||
var modMetadata:Array<ModMetadata> = Polymod.scan(
|
||||
{
|
||||
modRoot: MOD_FOLDER,
|
||||
apiVersionRule: API_VERSION,
|
||||
@@ -228,17 +265,25 @@ class PolymodHandler
|
||||
return modMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a list of ALL mod IDs, including disabled mods.
|
||||
* @return An array of mod IDs
|
||||
*/
|
||||
public static function getAllModIds():Array<String>
|
||||
{
|
||||
var modIds = [for (i in getAllMods()) i.id];
|
||||
var modIds:Array<String> = [for (i in getAllMods()) i.id];
|
||||
return modIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a list of metadata for all enabled mods.
|
||||
* @return An array of mod metadata
|
||||
*/
|
||||
public static function getEnabledMods():Array<ModMetadata>
|
||||
{
|
||||
var modIds = Save.get().enabledModIds;
|
||||
var modMetadata = getAllMods();
|
||||
var enabledMods = [];
|
||||
var modIds:Array<String> = Save.instance.enabledModIds;
|
||||
var modMetadata:Array<ModMetadata> = getAllMods();
|
||||
var enabledMods:Array<ModMetadata> = [];
|
||||
for (item in modMetadata)
|
||||
{
|
||||
if (modIds.indexOf(item.id) != -1)
|
||||
@@ -249,7 +294,11 @@ class PolymodHandler
|
||||
return enabledMods;
|
||||
}
|
||||
|
||||
public static function forceReloadAssets()
|
||||
/**
|
||||
* Clear and reload from disk all data assets.
|
||||
* Useful for "hot reloading" for fast iteration!
|
||||
*/
|
||||
public static function forceReloadAssets():Void
|
||||
{
|
||||
// Forcibly clear scripts so that scripts can be edited.
|
||||
ModuleHandler.clearModuleCache();
|
||||
@@ -276,6 +325,7 @@ class PolymodHandler
|
||||
ConversationRegistry.instance.loadEntries();
|
||||
DialogueBoxRegistry.instance.loadEntries();
|
||||
SpeakerRegistry.instance.loadEntries();
|
||||
AlbumRegistry.instance.loadEntries();
|
||||
StageRegistry.instance.loadEntries();
|
||||
CharacterDataParser.loadCharacterCache(); // TODO: Migrate characters to BaseRegistry.
|
||||
ModuleHandler.loadModuleCache();
|
||||
|
||||
@@ -106,12 +106,19 @@ class NoteScriptEvent extends ScriptEvent
|
||||
*/
|
||||
public var playSound(default, default):Bool;
|
||||
|
||||
public function new(type:ScriptEventType, note:NoteSprite, comboCount:Int = 0, cancelable:Bool = false):Void
|
||||
/**
|
||||
* The health gained or lost from this note.
|
||||
* This affects both hits and misses. Remember that max health is 2.00.
|
||||
*/
|
||||
public var healthChange:Float;
|
||||
|
||||
public function new(type:ScriptEventType, note:NoteSprite, healthChange:Float, comboCount:Int = 0, cancelable:Bool = false):Void
|
||||
{
|
||||
super(type, cancelable);
|
||||
this.note = note;
|
||||
this.comboCount = comboCount;
|
||||
this.playSound = true;
|
||||
this.healthChange = healthChange;
|
||||
}
|
||||
|
||||
public override function toString():String
|
||||
@@ -120,6 +127,31 @@ class NoteScriptEvent extends ScriptEvent
|
||||
}
|
||||
}
|
||||
|
||||
class HitNoteScriptEvent extends NoteScriptEvent
|
||||
{
|
||||
/**
|
||||
* The judgement the player received for hitting the note.
|
||||
*/
|
||||
public var judgement:String;
|
||||
|
||||
/**
|
||||
* The score the player received for hitting the note.
|
||||
*/
|
||||
public var score:Int;
|
||||
|
||||
public function new(note:NoteSprite, healthChange:Float, score:Int, judgement:String, comboCount:Int = 0):Void
|
||||
{
|
||||
super(NOTE_HIT, note, healthChange, comboCount, true);
|
||||
this.score = score;
|
||||
this.judgement = judgement;
|
||||
}
|
||||
|
||||
public override function toString():String
|
||||
{
|
||||
return 'HitNoteScriptEvent(note=' + note + ', comboCount=' + comboCount + ', judgement=' + judgement + ', score=' + score + ')';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An event that is fired when you press a key with no note present.
|
||||
*/
|
||||
@@ -182,17 +214,17 @@ class SongEventScriptEvent extends ScriptEvent
|
||||
* The note associated with this event.
|
||||
* You cannot replace it, but you can edit it.
|
||||
*/
|
||||
public var event(default, null):funkin.data.song.SongData.SongEventData;
|
||||
public var eventData(default, null):funkin.data.song.SongData.SongEventData;
|
||||
|
||||
public function new(event:funkin.data.song.SongData.SongEventData):Void
|
||||
public function new(eventData:funkin.data.song.SongData.SongEventData):Void
|
||||
{
|
||||
super(SONG_EVENT, true);
|
||||
this.event = event;
|
||||
this.eventData = eventData;
|
||||
}
|
||||
|
||||
public override function toString():String
|
||||
{
|
||||
return 'SongEventScriptEvent(event=' + event + ')';
|
||||
return 'SongEventScriptEvent(event=' + eventData + ')';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -71,17 +71,29 @@ class ScriptEventDispatcher
|
||||
}
|
||||
}
|
||||
|
||||
if (Std.isOfType(target, IPlayStateScriptedClass))
|
||||
if (Std.isOfType(target, INoteScriptedClass))
|
||||
{
|
||||
var t:IPlayStateScriptedClass = cast(target, IPlayStateScriptedClass);
|
||||
var t:INoteScriptedClass = cast(target, INoteScriptedClass);
|
||||
switch (event.type)
|
||||
{
|
||||
case NOTE_INCOMING:
|
||||
t.onNoteIncoming(cast event);
|
||||
return;
|
||||
case NOTE_HIT:
|
||||
t.onNoteHit(cast event);
|
||||
return;
|
||||
case NOTE_MISS:
|
||||
t.onNoteMiss(cast event);
|
||||
return;
|
||||
default: // Continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (Std.isOfType(target, IPlayStateScriptedClass))
|
||||
{
|
||||
var t:IPlayStateScriptedClass = cast(target, IPlayStateScriptedClass);
|
||||
switch (event.type)
|
||||
{
|
||||
case NOTE_GHOST_MISS:
|
||||
t.onNoteGhostMiss(cast event);
|
||||
return;
|
||||
|
||||
@@ -63,6 +63,13 @@ enum abstract ScriptEventType(String) from String to String
|
||||
*/
|
||||
var SONG_STEP_HIT = 'STEP_HIT';
|
||||
|
||||
/**
|
||||
* Called when a note comes on screen and starts approaching the strumline.
|
||||
*
|
||||
* This event is not cancelable.
|
||||
*/
|
||||
var NOTE_INCOMING = 'NOTE_INCOMING';
|
||||
|
||||
/**
|
||||
* Called when a character hits a note.
|
||||
* Important information such as judgement/timing, note data, player/opponent, etc. are all provided.
|
||||
|
||||
@@ -83,7 +83,9 @@ class Module implements IPlayStateScriptedClass implements IStateChangingScripte
|
||||
|
||||
public function onGameOver(event:ScriptEvent) {}
|
||||
|
||||
public function onNoteHit(event:NoteScriptEvent) {}
|
||||
public function onNoteIncoming(event:NoteScriptEvent) {}
|
||||
|
||||
public function onNoteHit(event:HitNoteScriptEvent) {}
|
||||
|
||||
public function onNoteMiss(event:NoteScriptEvent) {}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package funkin.play;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.FlxSprite;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
@@ -214,7 +215,7 @@ class Countdown
|
||||
|
||||
if (spritePath == null) return;
|
||||
|
||||
var countdownSprite:FlxSprite = new FlxSprite(0, 0).loadGraphic(Paths.image(spritePath));
|
||||
var countdownSprite:FunkinSprite = FunkinSprite.create(spritePath);
|
||||
countdownSprite.scrollFactor.set(0, 0);
|
||||
|
||||
if (isPixelStyle) countdownSprite.setGraphicSize(Std.int(countdownSprite.width * Constants.PIXEL_ART_SCALE));
|
||||
|
||||
@@ -4,16 +4,19 @@ import flixel.FlxG;
|
||||
import flixel.FlxObject;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.sound.FlxSound;
|
||||
import funkin.ui.story.StoryMenuState;
|
||||
import funkin.audio.FunkinSound;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.ui.MusicBeatSubState;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.ui.freeplay.FreeplayState;
|
||||
import funkin.play.character.BaseCharacter;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.util.MathUtil;
|
||||
import funkin.ui.freeplay.FreeplayState;
|
||||
import funkin.ui.MusicBeatSubState;
|
||||
import funkin.ui.story.StoryMenuState;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
/**
|
||||
* A substate which renders over the PlayState when the player dies.
|
||||
@@ -63,7 +66,7 @@ class GameOverSubState extends MusicBeatSubState
|
||||
/**
|
||||
* The music playing in the background of the state.
|
||||
*/
|
||||
var gameOverMusic:FlxSound = new FlxSound();
|
||||
var gameOverMusic:Null<FunkinSound> = null;
|
||||
|
||||
/**
|
||||
* Whether the player has confirmed and prepared to restart the level.
|
||||
@@ -71,10 +74,18 @@ class GameOverSubState extends MusicBeatSubState
|
||||
*/
|
||||
var isEnding:Bool = false;
|
||||
|
||||
/**
|
||||
* Whether the death music is on its first loop.
|
||||
*/
|
||||
var isStarting:Bool = true;
|
||||
|
||||
var isChartingMode:Bool = false;
|
||||
|
||||
var transparent:Bool;
|
||||
|
||||
final CAMERA_ZOOM_DURATION:Float = 0.5;
|
||||
var targetCameraZoom:Float = 1.0;
|
||||
|
||||
public function new(params:GameOverParams)
|
||||
{
|
||||
super();
|
||||
@@ -86,14 +97,14 @@ class GameOverSubState extends MusicBeatSubState
|
||||
/**
|
||||
* Reset the game over configuration to the default.
|
||||
*/
|
||||
public static function reset()
|
||||
public static function reset():Void
|
||||
{
|
||||
animationSuffix = "";
|
||||
musicSuffix = "";
|
||||
blueBallSuffix = "";
|
||||
animationSuffix = '';
|
||||
musicSuffix = '';
|
||||
blueBallSuffix = '';
|
||||
}
|
||||
|
||||
override public function create()
|
||||
override public function create():Void
|
||||
{
|
||||
if (instance != null)
|
||||
{
|
||||
@@ -108,6 +119,8 @@ class GameOverSubState extends MusicBeatSubState
|
||||
// Set up the visuals
|
||||
//
|
||||
|
||||
var playState = PlayState.instance;
|
||||
|
||||
// Add a black background to the screen.
|
||||
var bg = new FunkinSprite().makeSolidColor(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK);
|
||||
// We make this transparent so that we can see the stage underneath during debugging,
|
||||
@@ -119,55 +132,73 @@ class GameOverSubState extends MusicBeatSubState
|
||||
|
||||
// Pluck Boyfriend from the PlayState and place him (in the same position) in the GameOverSubState.
|
||||
// We can then play the character's `firstDeath` animation.
|
||||
boyfriend = PlayState.instance.currentStage.getBoyfriend(true);
|
||||
boyfriend.isDead = true;
|
||||
add(boyfriend);
|
||||
boyfriend.resetCharacter();
|
||||
if (PlayState.instance.isMinimalMode) {}
|
||||
else
|
||||
{
|
||||
boyfriend = PlayState.instance.currentStage.getBoyfriend(true);
|
||||
boyfriend.isDead = true;
|
||||
add(boyfriend);
|
||||
boyfriend.resetCharacter();
|
||||
|
||||
// Assign a camera follow point to the boyfriend's position.
|
||||
cameraFollowPoint = new FlxObject(PlayState.instance.cameraFollowPoint.x, PlayState.instance.cameraFollowPoint.y, 1, 1);
|
||||
cameraFollowPoint.x = boyfriend.getGraphicMidpoint().x;
|
||||
cameraFollowPoint.y = boyfriend.getGraphicMidpoint().y;
|
||||
var offsets:Array<Float> = boyfriend.getDeathCameraOffsets();
|
||||
cameraFollowPoint.x += offsets[0];
|
||||
cameraFollowPoint.y += offsets[1];
|
||||
add(cameraFollowPoint);
|
||||
// Assign a camera follow point to the boyfriend's position.
|
||||
cameraFollowPoint = new FlxObject(PlayState.instance.cameraFollowPoint.x, PlayState.instance.cameraFollowPoint.y, 1, 1);
|
||||
cameraFollowPoint.x = boyfriend.getGraphicMidpoint().x;
|
||||
cameraFollowPoint.y = boyfriend.getGraphicMidpoint().y;
|
||||
var offsets:Array<Float> = boyfriend.getDeathCameraOffsets();
|
||||
cameraFollowPoint.x += offsets[0];
|
||||
cameraFollowPoint.y += offsets[1];
|
||||
add(cameraFollowPoint);
|
||||
|
||||
FlxG.camera.target = null;
|
||||
FlxG.camera.follow(cameraFollowPoint, LOCKON, 0.01);
|
||||
FlxG.camera.target = null;
|
||||
FlxG.camera.follow(cameraFollowPoint, LOCKON, 0.01);
|
||||
targetCameraZoom = PlayState?.instance?.currentStage?.camZoom * boyfriend.getDeathCameraZoom();
|
||||
}
|
||||
|
||||
//
|
||||
// Set up the audio
|
||||
//
|
||||
|
||||
// Prepare the game over music.
|
||||
FlxG.sound.list.add(gameOverMusic);
|
||||
gameOverMusic.stop();
|
||||
|
||||
// The conductor now represents the BPM of the game over music.
|
||||
Conductor.instance.update(0);
|
||||
}
|
||||
|
||||
public function resetCameraZoom():Void
|
||||
{
|
||||
// Apply camera zoom level from stage data.
|
||||
FlxG.camera.zoom = PlayState?.instance?.currentStage?.camZoom ?? 1.0;
|
||||
}
|
||||
|
||||
var hasStartedAnimation:Bool = false;
|
||||
|
||||
override function update(elapsed:Float)
|
||||
override function update(elapsed:Float):Void
|
||||
{
|
||||
if (!hasStartedAnimation)
|
||||
{
|
||||
hasStartedAnimation = true;
|
||||
|
||||
if (boyfriend.hasAnimation('fakeoutDeath') && FlxG.random.bool((1 / 4096) * 100))
|
||||
if (PlayState.instance.isMinimalMode)
|
||||
{
|
||||
boyfriend.playAnimation('fakeoutDeath', true, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
boyfriend.playAnimation('firstDeath', true, false); // ignoreOther is set to FALSE since you WANT to be able to mash and confirm game over!
|
||||
// Play the "blue balled" sound. May play a variant if one has been assigned.
|
||||
playBlueBalledSFX();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (boyfriend.hasAnimation('fakeoutDeath') && FlxG.random.bool((1 / 4096) * 100))
|
||||
{
|
||||
boyfriend.playAnimation('fakeoutDeath', true, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
boyfriend.playAnimation('firstDeath', true, false); // ignoreOther is set to FALSE since you WANT to be able to mash and confirm game over!
|
||||
// Play the "blue balled" sound. May play a variant if one has been assigned.
|
||||
playBlueBalledSFX();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Smoothly lerp the camera
|
||||
FlxG.camera.zoom = MathUtil.smoothLerp(FlxG.camera.zoom, targetCameraZoom, elapsed, CAMERA_ZOOM_DURATION);
|
||||
|
||||
//
|
||||
// Handle user inputs.
|
||||
//
|
||||
@@ -216,7 +247,7 @@ class GameOverSubState extends MusicBeatSubState
|
||||
}
|
||||
}
|
||||
|
||||
if (gameOverMusic.playing)
|
||||
if (gameOverMusic != null && gameOverMusic.playing)
|
||||
{
|
||||
// Match the conductor to the music.
|
||||
// This enables the stepHit and beatHit events.
|
||||
@@ -224,27 +255,34 @@ class GameOverSubState extends MusicBeatSubState
|
||||
}
|
||||
else
|
||||
{
|
||||
// Music hasn't started yet.
|
||||
switch (PlayStatePlaylist.campaignId)
|
||||
if (PlayState.instance.isMinimalMode)
|
||||
{
|
||||
// TODO: Make the behavior for playing Jeff's voicelines generic or un-hardcoded.
|
||||
// This will simplify the class and make it easier for mods to add death quotes.
|
||||
case "week7":
|
||||
if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished() && !playingJeffQuote)
|
||||
{
|
||||
playingJeffQuote = true;
|
||||
playJeffQuote();
|
||||
// Start music at lower volume
|
||||
startDeathMusic(0.2, false);
|
||||
boyfriend.playAnimation('deathLoop' + animationSuffix);
|
||||
}
|
||||
default:
|
||||
// Start music at normal volume once the initial death animation finishes.
|
||||
if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished())
|
||||
{
|
||||
startDeathMusic(1.0, false);
|
||||
boyfriend.playAnimation('deathLoop' + animationSuffix);
|
||||
}
|
||||
// startDeathMusic(1.0, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Music hasn't started yet.
|
||||
switch (PlayStatePlaylist.campaignId)
|
||||
{
|
||||
// TODO: Make the behavior for playing Jeff's voicelines generic or un-hardcoded.
|
||||
// This will simplify the class and make it easier for mods to add death quotes.
|
||||
case 'week7':
|
||||
if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished() && !playingJeffQuote)
|
||||
{
|
||||
playingJeffQuote = true;
|
||||
playJeffQuote();
|
||||
// Start music at lower volume
|
||||
startDeathMusic(0.2, false);
|
||||
boyfriend.playAnimation('deathLoop' + animationSuffix);
|
||||
}
|
||||
default:
|
||||
// Start music at normal volume once the initial death animation finishes.
|
||||
if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished())
|
||||
{
|
||||
startDeathMusic(1.0, false);
|
||||
boyfriend.playAnimation('deathLoop' + animationSuffix);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,7 +300,11 @@ class GameOverSubState extends MusicBeatSubState
|
||||
isEnding = true;
|
||||
startDeathMusic(1.0, true); // isEnding changes this function's behavior.
|
||||
|
||||
boyfriend.playAnimation('deathConfirm' + animationSuffix, true);
|
||||
if (PlayState.instance.isMinimalMode) {}
|
||||
else
|
||||
{
|
||||
boyfriend.playAnimation('deathConfirm' + animationSuffix, true);
|
||||
}
|
||||
|
||||
// After the animation finishes...
|
||||
new FlxTimer().start(0.7, function(tmr:FlxTimer) {
|
||||
@@ -272,10 +314,17 @@ class GameOverSubState extends MusicBeatSubState
|
||||
FlxG.camera.fade(FlxColor.BLACK, 1, true, null, true);
|
||||
PlayState.instance.needsReset = true;
|
||||
|
||||
// Readd Boyfriend to the stage.
|
||||
boyfriend.isDead = false;
|
||||
remove(boyfriend);
|
||||
PlayState.instance.currentStage.addCharacter(boyfriend, BF);
|
||||
if (PlayState.instance.isMinimalMode) {}
|
||||
else
|
||||
{
|
||||
// Readd Boyfriend to the stage.
|
||||
boyfriend.isDead = false;
|
||||
remove(boyfriend);
|
||||
PlayState.instance.currentStage.addCharacter(boyfriend, BF);
|
||||
}
|
||||
|
||||
// Snap reset the camera which may have changed because of the player character data.
|
||||
resetCameraZoom();
|
||||
|
||||
// Close the substate.
|
||||
close();
|
||||
@@ -291,24 +340,71 @@ class GameOverSubState extends MusicBeatSubState
|
||||
ScriptEventDispatcher.callEvent(boyfriend, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rather than hardcoding stuff, we look for the presence of a music file
|
||||
* with the given suffix, and strip it down until we find one that's valid.
|
||||
*/
|
||||
function resolveMusicPath(suffix:String, starting:Bool = false, ending:Bool = false):Null<String>
|
||||
{
|
||||
var basePath = 'gameplay/gameover/gameOver';
|
||||
if (starting) basePath += 'Start';
|
||||
else if (ending) basePath += 'End';
|
||||
|
||||
var musicPath = Paths.music(basePath + suffix);
|
||||
while (!Assets.exists(musicPath) && suffix.length > 0)
|
||||
{
|
||||
suffix = suffix.split('-').slice(0, -1).join('-');
|
||||
musicPath = Paths.music(basePath + suffix);
|
||||
}
|
||||
if (!Assets.exists(musicPath)) return null;
|
||||
trace('Resolved music path: ' + musicPath);
|
||||
return musicPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the death music at the appropriate volume.
|
||||
* @param startingVolume
|
||||
*/
|
||||
function startDeathMusic(?startingVolume:Float = 1, force:Bool = false):Void
|
||||
public function startDeathMusic(startingVolume:Float = 1, force:Bool = false):Void
|
||||
{
|
||||
var musicPath = Paths.music('gameplay/gameover/gameOver' + musicSuffix);
|
||||
if (isEnding)
|
||||
var musicPath = resolveMusicPath(musicSuffix, isStarting, isEnding);
|
||||
var onComplete = null;
|
||||
if (isStarting)
|
||||
{
|
||||
musicPath = Paths.music('gameplay/gameover/gameOverEnd' + musicSuffix);
|
||||
if (musicPath == null)
|
||||
{
|
||||
isStarting = false;
|
||||
musicPath = resolveMusicPath(musicSuffix, isStarting, isEnding);
|
||||
}
|
||||
else
|
||||
{
|
||||
onComplete = function() {
|
||||
isStarting = false;
|
||||
// We need to force to ensure that the non-starting music plays.
|
||||
startDeathMusic(1.0, true);
|
||||
};
|
||||
}
|
||||
}
|
||||
if (!gameOverMusic.playing || force)
|
||||
|
||||
if (musicPath == null)
|
||||
{
|
||||
gameOverMusic.loadEmbedded(musicPath);
|
||||
trace('Could not find game over music!');
|
||||
return;
|
||||
}
|
||||
else if (gameOverMusic == null || !gameOverMusic.playing || force)
|
||||
{
|
||||
if (gameOverMusic != null) gameOverMusic.stop();
|
||||
gameOverMusic = FunkinSound.load(musicPath);
|
||||
gameOverMusic.volume = startingVolume;
|
||||
gameOverMusic.looped = !isEnding;
|
||||
gameOverMusic.looped = !(isEnding || isStarting);
|
||||
gameOverMusic.onComplete = onComplete;
|
||||
gameOverMusic.play();
|
||||
}
|
||||
else
|
||||
{
|
||||
@:privateAccess
|
||||
trace('Music already playing! ${gameOverMusic?._label}');
|
||||
}
|
||||
}
|
||||
|
||||
static var blueballed:Bool = false;
|
||||
@@ -320,7 +416,14 @@ class GameOverSubState extends MusicBeatSubState
|
||||
public static function playBlueBalledSFX()
|
||||
{
|
||||
blueballed = true;
|
||||
FlxG.sound.play(Paths.sound('gameplay/gameover/fnf_loss_sfx' + blueBallSuffix));
|
||||
if (Assets.exists(Paths.sound('gameplay/gameover/fnf_loss_sfx' + blueBallSuffix)))
|
||||
{
|
||||
FlxG.sound.play(Paths.sound('gameplay/gameover/fnf_loss_sfx' + blueBallSuffix));
|
||||
}
|
||||
else
|
||||
{
|
||||
FlxG.log.error('Missing blue ball sound effect: ' + Paths.sound('gameplay/gameover/fnf_loss_sfx' + blueBallSuffix));
|
||||
}
|
||||
}
|
||||
|
||||
var playingJeffQuote:Bool = false;
|
||||
@@ -344,6 +447,14 @@ class GameOverSubState extends MusicBeatSubState
|
||||
});
|
||||
}
|
||||
|
||||
public override function destroy()
|
||||
{
|
||||
super.destroy();
|
||||
if (gameOverMusic != null) gameOverMusic.stop();
|
||||
gameOverMusic = null;
|
||||
instance = null;
|
||||
}
|
||||
|
||||
public override function toString():String
|
||||
{
|
||||
return "GameOverSubState";
|
||||
|
||||
@@ -3,6 +3,7 @@ package funkin.play;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.ui.MusicBeatState;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
import funkin.ui.mainmenu.MainMenuState;
|
||||
@@ -27,25 +28,22 @@ class GitarooPause extends MusicBeatState
|
||||
{
|
||||
if (FlxG.sound.music != null) FlxG.sound.music.stop();
|
||||
|
||||
var bg:FlxSprite = new FlxSprite().loadGraphic(Paths.image('pauseAlt/pauseBG'));
|
||||
var bg:FunkinSprite = FunkinSprite.create('pauseAlt/pauseBG');
|
||||
add(bg);
|
||||
|
||||
var bf:FlxSprite = new FlxSprite(0, 30);
|
||||
bf.frames = Paths.getSparrowAtlas('pauseAlt/bfLol');
|
||||
var bf:FunkinSprite = FunkinSprite.createSparrow(0, 30, 'pauseAlt/bfLol');
|
||||
bf.animation.addByPrefix('lol', "funnyThing", 13);
|
||||
bf.animation.play('lol');
|
||||
add(bf);
|
||||
bf.screenCenter(X);
|
||||
|
||||
replayButton = new FlxSprite(FlxG.width * 0.28, FlxG.height * 0.7);
|
||||
replayButton.frames = Paths.getSparrowAtlas('pauseAlt/pauseUI');
|
||||
replayButton = FunkinSprite.createSparrow(FlxG.width * 0.28, FlxG.height * 0.7, 'pauseAlt/pauseUI');
|
||||
replayButton.animation.addByPrefix('selected', 'bluereplay', 0, false);
|
||||
replayButton.animation.appendByPrefix('selected', 'yellowreplay');
|
||||
replayButton.animation.play('selected');
|
||||
add(replayButton);
|
||||
|
||||
cancelButton = new FlxSprite(FlxG.width * 0.58, replayButton.y);
|
||||
cancelButton.frames = Paths.getSparrowAtlas('pauseAlt/pauseUI');
|
||||
cancelButton = FunkinSprite.createSparrow(FlxG.width * 0.58, replayButton.y, 'pauseAlt/pauseUI');
|
||||
cancelButton.animation.addByPrefix('selected', 'bluecancel', 0, false);
|
||||
cancelButton.animation.appendByPrefix('selected', 'cancelyellow');
|
||||
cancelButton.animation.play('selected');
|
||||
|
||||
+676
-254
@@ -1,316 +1,738 @@
|
||||
package funkin.play;
|
||||
|
||||
import funkin.play.PlayStatePlaylist;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
import flixel.FlxG;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import funkin.ui.MusicBeatSubState;
|
||||
import funkin.ui.debug.latency.LatencyState;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
import flixel.math.FlxMath;
|
||||
import flixel.sound.FlxSound;
|
||||
import flixel.text.FlxText;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.play.PlayState;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.audio.FunkinSound;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.ui.Alphabet;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.play.cutscene.VideoCutscene;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.ui.AtlasText;
|
||||
import funkin.ui.debug.latency.LatencyState;
|
||||
import funkin.ui.MusicBeatSubState;
|
||||
import funkin.ui.transition.StickerSubState;
|
||||
|
||||
/**
|
||||
* Parameters for initializing the PauseSubState.
|
||||
*/
|
||||
typedef PauseSubStateParams =
|
||||
{
|
||||
/**
|
||||
* Which mode to start in. Dictates what entries are displayed.
|
||||
*/
|
||||
?mode:PauseMode,
|
||||
};
|
||||
|
||||
/**
|
||||
* The menu displayed when the Play State is paused.
|
||||
*/
|
||||
class PauseSubState extends MusicBeatSubState
|
||||
{
|
||||
var grpMenuShit:FlxTypedGroup<Alphabet>;
|
||||
// ===============
|
||||
// Constants
|
||||
// ===============
|
||||
|
||||
// todo: maybe make these enums or somethin?
|
||||
final pauseOptionsBase:Array<String> = [
|
||||
'Resume',
|
||||
'Restart Song',
|
||||
'Change Difficulty',
|
||||
'Toggle Practice Mode',
|
||||
'Adjust Input Offsets',
|
||||
'Exit to Menu'
|
||||
/**
|
||||
* Pause menu entries for when the game is paused during a song.
|
||||
*/
|
||||
static final PAUSE_MENU_ENTRIES_STANDARD:Array<PauseMenuEntry> = [
|
||||
{text: 'Resume', callback: resume},
|
||||
{text: 'Restart Song', callback: restartPlayState},
|
||||
{text: 'Change Difficulty', callback: switchMode.bind(_, Difficulty)},
|
||||
{text: 'Enable Practice Mode', callback: enablePracticeMode, filter: () -> !(PlayState.instance?.isPracticeMode ?? false)},
|
||||
{text: 'Exit to Menu', callback: quitToMenu},
|
||||
];
|
||||
final pauseOptionsCharting:Array<String> = ['Resume', 'Restart Song', 'Adjust Input Offsets', 'Exit to Chart Editor'];
|
||||
|
||||
final pauseOptionsDifficultyBase:Array<String> = ['BACK'];
|
||||
/**
|
||||
* Pause menu entries for when the game is paused in the Chart Editor preview.
|
||||
*/
|
||||
static final PAUSE_MENU_ENTRIES_CHARTING:Array<PauseMenuEntry> = [
|
||||
{text: 'Resume', callback: resume},
|
||||
{text: 'Restart Song', callback: restartPlayState},
|
||||
{text: 'Return to Chart Editor', callback: quitToChartEditor},
|
||||
];
|
||||
|
||||
var pauseOptionsDifficulty:Array<String> = []; // AUTO-POPULATED
|
||||
/**
|
||||
* Pause menu entries for when the user selects "Change Difficulty".
|
||||
*/
|
||||
static final PAUSE_MENU_ENTRIES_DIFFICULTY:Array<PauseMenuEntry> = [
|
||||
{text: 'Back', callback: switchMode.bind(_, Standard)}
|
||||
// Other entries are added dynamically.
|
||||
];
|
||||
|
||||
var menuItems:Array<String> = [];
|
||||
var curSelected:Int = 0;
|
||||
/**
|
||||
* Pause menu entries for when the game is paused during a video cutscene.
|
||||
*/
|
||||
static final PAUSE_MENU_ENTRIES_VIDEO_CUTSCENE:Array<PauseMenuEntry> = [
|
||||
{text: 'Resume', callback: resume},
|
||||
{text: 'Restart Cutscene', callback: restartVideoCutscene},
|
||||
{text: 'Skip Cutscene', callback: skipVideoCutscene},
|
||||
{text: 'Exit to Menu', callback: quitToMenu},
|
||||
];
|
||||
|
||||
var pauseMusic:FlxSound;
|
||||
/**
|
||||
* Pause menu entries for when the game is paused during a conversation.
|
||||
*/
|
||||
static final PAUSE_MENU_ENTRIES_CONVERSATION:Array<PauseMenuEntry> = [
|
||||
{text: 'Resume', callback: resume},
|
||||
{text: 'Restart Dialogue', callback: restartConversation},
|
||||
{text: 'Skip Dialogue', callback: skipConversation},
|
||||
{text: 'Exit to Menu', callback: quitToMenu},
|
||||
];
|
||||
|
||||
var practiceText:FlxText;
|
||||
/**
|
||||
* Duration for the music to fade in when the pause menu is opened.
|
||||
*/
|
||||
static final MUSIC_FADE_IN_TIME:Float = 5;
|
||||
|
||||
public var exitingToMenu:Bool = false;
|
||||
/**
|
||||
* The final volume for the music when the pause menu is opened.
|
||||
*/
|
||||
static final MUSIC_FINAL_VOLUME:Float = 0.75;
|
||||
|
||||
var bg:FlxSprite;
|
||||
var metaDataGrp:FlxTypedGroup<FlxSprite>;
|
||||
/**
|
||||
* Defines which pause music to use.
|
||||
*/
|
||||
public static var musicSuffix:String = '';
|
||||
|
||||
var isChartingMode:Bool;
|
||||
/**
|
||||
* Reset the pause configuration to the default.
|
||||
*/
|
||||
public static function reset():Void
|
||||
{
|
||||
musicSuffix = '';
|
||||
}
|
||||
|
||||
public function new(isChartingMode:Bool = false)
|
||||
// ===============
|
||||
// Status Variables
|
||||
// ===============
|
||||
|
||||
/**
|
||||
* Disallow input until transitions are complete!
|
||||
* This prevents the pause menu from immediately closing when opened, among other things.
|
||||
*/
|
||||
public var allowInput:Bool = false;
|
||||
|
||||
/**
|
||||
* The entries currently displayed in the pause menu.
|
||||
*/
|
||||
var currentMenuEntries:Array<PauseMenuEntry>;
|
||||
|
||||
/**
|
||||
* The index of `currentMenuEntries` that is currently selected.
|
||||
*/
|
||||
var currentEntry:Int = 0;
|
||||
|
||||
/**
|
||||
* The mode that the pause menu is currently in.
|
||||
*/
|
||||
var currentMode:PauseMode;
|
||||
|
||||
// ===============
|
||||
// Graphics Variables
|
||||
// ===============
|
||||
|
||||
/**
|
||||
* The semi-transparent black background that appears when the game is paused.
|
||||
*/
|
||||
var background:FunkinSprite;
|
||||
|
||||
/**
|
||||
* The metadata displayed in the top right.
|
||||
*/
|
||||
var metadata:FlxTypedSpriteGroup<FlxText>;
|
||||
|
||||
/**
|
||||
* A text object that displays the current practice mode status.
|
||||
*/
|
||||
var metadataPractice:FlxText;
|
||||
|
||||
/**
|
||||
* A text object that displays the current death count.
|
||||
*/
|
||||
var metadataDeaths:FlxText;
|
||||
|
||||
/**
|
||||
* The actual text objects for the menu entries.
|
||||
*/
|
||||
var menuEntryText:FlxTypedSpriteGroup<AtlasText>;
|
||||
|
||||
// ===============
|
||||
// Audio Variables
|
||||
// ===============
|
||||
var pauseMusic:FunkinSound;
|
||||
|
||||
// ===============
|
||||
// Constructor
|
||||
// ===============
|
||||
|
||||
public function new(?params:PauseSubStateParams)
|
||||
{
|
||||
super();
|
||||
|
||||
this.isChartingMode = isChartingMode;
|
||||
|
||||
menuItems = this.isChartingMode ? pauseOptionsCharting : pauseOptionsBase;
|
||||
var difficultiesInVariation = PlayState.instance.currentSong.listDifficulties(PlayState.instance.currentChart.variation);
|
||||
trace('DIFFICULTIES: ${difficultiesInVariation}');
|
||||
|
||||
pauseOptionsDifficulty = difficultiesInVariation.map(function(item:String):String {
|
||||
return item.toUpperCase();
|
||||
}).concat(pauseOptionsDifficultyBase);
|
||||
|
||||
if (PlayStatePlaylist.campaignId == 'week6')
|
||||
{
|
||||
pauseMusic = new FlxSound().loadEmbedded(Paths.music('breakfast-pixel'), true, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
pauseMusic = new FlxSound().loadEmbedded(Paths.music('breakfast'), true, true);
|
||||
}
|
||||
pauseMusic.volume = 0;
|
||||
pauseMusic.play(false, FlxG.random.int(0, Std.int(pauseMusic.length / 2)));
|
||||
|
||||
FlxG.sound.list.add(pauseMusic);
|
||||
|
||||
bg = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK);
|
||||
bg.alpha = 0;
|
||||
bg.scrollFactor.set();
|
||||
add(bg);
|
||||
|
||||
metaDataGrp = new FlxTypedGroup<FlxSprite>();
|
||||
add(metaDataGrp);
|
||||
|
||||
var levelInfo:FlxText = new FlxText(20, 15, 0, '', 32);
|
||||
if (PlayState.instance.currentChart != null)
|
||||
{
|
||||
levelInfo.text += '${PlayState.instance.currentChart.songName} - ${PlayState.instance.currentChart.songArtist}';
|
||||
}
|
||||
levelInfo.scrollFactor.set();
|
||||
levelInfo.setFormat(Paths.font('vcr.ttf'), 32);
|
||||
levelInfo.updateHitbox();
|
||||
metaDataGrp.add(levelInfo);
|
||||
|
||||
var levelDifficulty:FlxText = new FlxText(20, 15 + 32, 0, '', 32);
|
||||
levelDifficulty.text += PlayState.instance.currentDifficulty.toTitleCase();
|
||||
levelDifficulty.scrollFactor.set();
|
||||
levelDifficulty.setFormat(Paths.font('vcr.ttf'), 32);
|
||||
levelDifficulty.updateHitbox();
|
||||
metaDataGrp.add(levelDifficulty);
|
||||
|
||||
var deathCounter:FlxText = new FlxText(20, 15 + 64, 0, '', 32);
|
||||
deathCounter.text = 'Blue balled: ${PlayState.instance.deathCounter}';
|
||||
FlxG.watch.addQuick('totalNotesHit', Highscore.tallies.totalNotesHit);
|
||||
FlxG.watch.addQuick('totalNotes', Highscore.tallies.totalNotes);
|
||||
deathCounter.scrollFactor.set();
|
||||
deathCounter.setFormat(Paths.font('vcr.ttf'), 32);
|
||||
deathCounter.updateHitbox();
|
||||
metaDataGrp.add(deathCounter);
|
||||
|
||||
practiceText = new FlxText(20, 15 + 64 + 32, 0, 'PRACTICE MODE', 32);
|
||||
practiceText.scrollFactor.set();
|
||||
practiceText.setFormat(Paths.font('vcr.ttf'), 32);
|
||||
practiceText.updateHitbox();
|
||||
practiceText.x = FlxG.width - (practiceText.width + 20);
|
||||
practiceText.visible = PlayState.instance.isPracticeMode;
|
||||
metaDataGrp.add(practiceText);
|
||||
|
||||
levelDifficulty.alpha = 0;
|
||||
levelInfo.alpha = 0;
|
||||
deathCounter.alpha = 0;
|
||||
|
||||
levelInfo.x = FlxG.width - (levelInfo.width + 20);
|
||||
levelDifficulty.x = FlxG.width - (levelDifficulty.width + 20);
|
||||
deathCounter.x = FlxG.width - (deathCounter.width + 20);
|
||||
|
||||
FlxTween.tween(bg, {alpha: 0.6}, 0.4, {ease: FlxEase.quartInOut});
|
||||
FlxTween.tween(levelInfo, {alpha: 1, y: 20}, 0.4, {ease: FlxEase.quartInOut, startDelay: 0.3});
|
||||
FlxTween.tween(levelDifficulty, {alpha: 1, y: levelDifficulty.y + 5}, 0.4, {ease: FlxEase.quartInOut, startDelay: 0.5});
|
||||
FlxTween.tween(deathCounter, {alpha: 1, y: deathCounter.y + 5}, 0.4, {ease: FlxEase.quartInOut, startDelay: 0.7});
|
||||
|
||||
grpMenuShit = new FlxTypedGroup<Alphabet>();
|
||||
add(grpMenuShit);
|
||||
|
||||
regenMenu();
|
||||
|
||||
// cameras = [FlxG.cameras.list[FlxG.cameras.list.length - 1]];
|
||||
this.currentMode = params?.mode ?? Standard;
|
||||
}
|
||||
|
||||
function regenMenu():Void
|
||||
// ===============
|
||||
// Lifecycle Functions
|
||||
// ===============
|
||||
|
||||
/**
|
||||
* Called when the state is first loaded.
|
||||
*/
|
||||
public override function create():Void
|
||||
{
|
||||
while (grpMenuShit.members.length > 0)
|
||||
{
|
||||
grpMenuShit.remove(grpMenuShit.members[0], true);
|
||||
}
|
||||
super.create();
|
||||
|
||||
for (i in 0...menuItems.length)
|
||||
{
|
||||
var songText:Alphabet = new Alphabet(0, (70 * i) + 30, menuItems[i], true, false);
|
||||
songText.isMenuItem = true;
|
||||
songText.targetY = i;
|
||||
grpMenuShit.add(songText);
|
||||
}
|
||||
startPauseMusic();
|
||||
|
||||
curSelected = 0;
|
||||
changeSelection();
|
||||
buildBackground();
|
||||
|
||||
buildMetadata();
|
||||
|
||||
regenerateMenu();
|
||||
|
||||
transitionIn();
|
||||
}
|
||||
|
||||
override function update(elapsed:Float):Void
|
||||
/**
|
||||
* Called every frame.
|
||||
* @param elapsed The time elapsed since the last frame, in seconds.
|
||||
*/
|
||||
public override function update(elapsed:Float):Void
|
||||
{
|
||||
if (pauseMusic.volume < 0.5) pauseMusic.volume += 0.01 * elapsed;
|
||||
|
||||
super.update(elapsed);
|
||||
|
||||
handleInputs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the state is closed.
|
||||
*/
|
||||
public override function destroy():Void
|
||||
{
|
||||
super.destroy();
|
||||
pauseMusic.stop();
|
||||
}
|
||||
|
||||
// ===============
|
||||
// Initialization Functions
|
||||
// ===============
|
||||
|
||||
/**
|
||||
* Play the pause music.
|
||||
*/
|
||||
function startPauseMusic():Void
|
||||
{
|
||||
var pauseMusicPath:String = Paths.music('breakfast$musicSuffix');
|
||||
pauseMusic = FunkinSound.load(pauseMusicPath, true, true);
|
||||
|
||||
if (pauseMusic == null)
|
||||
{
|
||||
FlxG.log.warn('Could not play pause music: ${pauseMusicPath} does not exist!');
|
||||
}
|
||||
|
||||
// Start playing at a random point in the song.
|
||||
pauseMusic.play(false, FlxG.random.int(0, Std.int(pauseMusic.length / 2)));
|
||||
pauseMusic.fadeIn(MUSIC_FADE_IN_TIME, 0, MUSIC_FINAL_VOLUME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the semi-transparent black background.
|
||||
*/
|
||||
function buildBackground():Void
|
||||
{
|
||||
// Using state.bgColor causes bugs!
|
||||
background = new FunkinSprite(0, 0);
|
||||
background.makeSolidColor(FlxG.width, FlxG.height, FlxColor.BLACK);
|
||||
background.alpha = 0.0;
|
||||
background.scrollFactor.set(0, 0);
|
||||
background.updateHitbox();
|
||||
add(background);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the metadata in the top right.
|
||||
*/
|
||||
function buildMetadata():Void
|
||||
{
|
||||
metadata = new FlxTypedSpriteGroup<FlxText>();
|
||||
metadata.scrollFactor.set(0, 0);
|
||||
add(metadata);
|
||||
|
||||
var metadataSong:FlxText = new FlxText(20, 15, FlxG.width - 40, 'Song Name - Artist');
|
||||
metadataSong.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT);
|
||||
if (PlayState.instance?.currentChart != null)
|
||||
{
|
||||
metadataSong.text = '${PlayState.instance.currentChart.songName} - ${PlayState.instance.currentChart.songArtist}';
|
||||
}
|
||||
metadataSong.scrollFactor.set(0, 0);
|
||||
metadata.add(metadataSong);
|
||||
|
||||
var metadataDifficulty:FlxText = new FlxText(20, 15 + 32, FlxG.width - 40, 'Difficulty: ');
|
||||
metadataDifficulty.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT);
|
||||
if (PlayState.instance?.currentDifficulty != null)
|
||||
{
|
||||
metadataDifficulty.text += PlayState.instance.currentDifficulty.toTitleCase();
|
||||
}
|
||||
metadataDifficulty.scrollFactor.set(0, 0);
|
||||
metadata.add(metadataDifficulty);
|
||||
|
||||
metadataDeaths = new FlxText(20, 15 + 64, FlxG.width - 40, '${PlayState.instance?.deathCounter} Blue Balls');
|
||||
metadataDeaths.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT);
|
||||
metadataDeaths.scrollFactor.set(0, 0);
|
||||
metadata.add(metadataDeaths);
|
||||
|
||||
metadataPractice = new FlxText(20, 15 + 96, FlxG.width - 40, 'PRACTICE MODE');
|
||||
metadataPractice.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT);
|
||||
metadataPractice.visible = PlayState.instance?.isPracticeMode ?? false;
|
||||
metadataPractice.scrollFactor.set(0, 0);
|
||||
metadata.add(metadataPractice);
|
||||
|
||||
updateMetadataText();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform additional animations to transition the pause menu in when it is first displayed.
|
||||
*/
|
||||
function transitionIn():Void
|
||||
{
|
||||
FlxTween.tween(background, {alpha: 0.6}, 0.8, {ease: FlxEase.quartOut});
|
||||
|
||||
// Animate each element a little bit downwards.
|
||||
var delay:Float = 0.1;
|
||||
for (child in metadata.members)
|
||||
{
|
||||
FlxTween.tween(child, {alpha: 1, y: child.y + 5}, 1.8, {ease: FlxEase.quartOut, startDelay: delay});
|
||||
delay += 0.1;
|
||||
}
|
||||
|
||||
new FlxTimer().start(0.2, (_) -> {
|
||||
allowInput = true;
|
||||
});
|
||||
}
|
||||
|
||||
// ===============
|
||||
// Input Handling
|
||||
// ===============
|
||||
|
||||
/**
|
||||
* Process user inputs every frame.
|
||||
*/
|
||||
function handleInputs():Void
|
||||
{
|
||||
var upP = controls.UI_UP_P;
|
||||
var downP = controls.UI_DOWN_P;
|
||||
var accepted = controls.ACCEPT;
|
||||
if (!allowInput) return;
|
||||
|
||||
if (controls.UI_UP_P)
|
||||
{
|
||||
changeSelection(-1);
|
||||
}
|
||||
if (controls.UI_DOWN_P)
|
||||
{
|
||||
changeSelection(1);
|
||||
}
|
||||
|
||||
if (controls.ACCEPT)
|
||||
{
|
||||
currentMenuEntries[currentEntry].callback(this);
|
||||
}
|
||||
else if (controls.PAUSE)
|
||||
{
|
||||
resume(this);
|
||||
}
|
||||
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
// to pause the game and get screenshots easy, press H on pause menu!
|
||||
if (FlxG.keys.justPressed.H)
|
||||
{
|
||||
bg.visible = !bg.visible;
|
||||
grpMenuShit.visible = !grpMenuShit.visible;
|
||||
metaDataGrp.visible = !metaDataGrp.visible;
|
||||
var visible = !metadata.visible;
|
||||
|
||||
metadata.visible = visible;
|
||||
menuEntryText.visible = visible;
|
||||
this.bgColor = visible ? 0x99000000 : 0x00000000; // 60% or fully transparent black
|
||||
}
|
||||
#end
|
||||
|
||||
if (!exitingToMenu)
|
||||
{
|
||||
if (upP)
|
||||
{
|
||||
changeSelection(-1);
|
||||
}
|
||||
if (downP)
|
||||
{
|
||||
changeSelection(1);
|
||||
}
|
||||
|
||||
var androidPause:Bool = false;
|
||||
|
||||
#if android
|
||||
androidPause = FlxG.android.justPressed.BACK;
|
||||
#end
|
||||
|
||||
if (androidPause) close();
|
||||
|
||||
if (accepted)
|
||||
{
|
||||
var daSelected:String = menuItems[curSelected];
|
||||
|
||||
switch (daSelected)
|
||||
{
|
||||
case 'Resume':
|
||||
close();
|
||||
|
||||
case 'Change Difficulty':
|
||||
menuItems = pauseOptionsDifficulty;
|
||||
regenMenu();
|
||||
|
||||
case 'Toggle Practice Mode':
|
||||
PlayState.instance.isPracticeMode = true;
|
||||
practiceText.visible = PlayState.instance.isPracticeMode;
|
||||
|
||||
case 'Restart Song':
|
||||
PlayState.instance.needsReset = true;
|
||||
close();
|
||||
|
||||
case 'Exit to Menu':
|
||||
exitingToMenu = true;
|
||||
PlayState.instance.deathCounter = 0;
|
||||
|
||||
for (item in grpMenuShit.members)
|
||||
{
|
||||
item.targetY = -3;
|
||||
item.alpha = 0.6;
|
||||
}
|
||||
|
||||
FlxTransitionableState.skipNextTransIn = true;
|
||||
FlxTransitionableState.skipNextTransOut = true;
|
||||
|
||||
if (PlayStatePlaylist.isStoryMode)
|
||||
{
|
||||
PlayStatePlaylist.reset();
|
||||
openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new funkin.ui.story.StoryMenuState(sticker)));
|
||||
}
|
||||
else
|
||||
{
|
||||
openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new funkin.ui.freeplay.FreeplayState(null, sticker)));
|
||||
}
|
||||
case 'Adjust Input Offsets':
|
||||
openSubState(new LatencyState());
|
||||
case 'Exit to Chart Editor':
|
||||
this.close();
|
||||
if (FlxG.sound.music != null) FlxG.sound.music.pause(); // Don't reset song position!
|
||||
PlayState.instance.close(); // This only works because PlayState is a substate!
|
||||
|
||||
case 'BACK':
|
||||
menuItems = this.isChartingMode ? pauseOptionsCharting : pauseOptionsBase;
|
||||
regenMenu();
|
||||
|
||||
default:
|
||||
if (pauseOptionsDifficulty.contains(daSelected))
|
||||
{
|
||||
PlayState.instance.currentSong = SongRegistry.instance.fetchEntry(PlayState.instance.currentSong.id.toLowerCase());
|
||||
|
||||
// Reset campaign score when changing difficulty
|
||||
// So if you switch difficulty on the last song of a week you get a really low overall score.
|
||||
PlayStatePlaylist.campaignScore = 0;
|
||||
PlayStatePlaylist.campaignDifficulty = daSelected.toLowerCase();
|
||||
PlayState.instance.currentDifficulty = PlayStatePlaylist.campaignDifficulty;
|
||||
|
||||
PlayState.instance.needsReset = true;
|
||||
|
||||
close();
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[WARN] Unhandled pause menu option: ${daSelected}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (FlxG.keys.justPressed.J)
|
||||
{
|
||||
// for reference later!
|
||||
// PlayerSettings.player1.controls.replaceBinding(Control.LEFT, Keys, FlxKey.J, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override function destroy():Void
|
||||
{
|
||||
pauseMusic.destroy();
|
||||
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the current selection up or down.
|
||||
* @param change The amount to change the selection by, with sign indicating direction.
|
||||
*/
|
||||
function changeSelection(change:Int = 0):Void
|
||||
{
|
||||
FlxG.sound.play(Paths.sound('scrollMenu'), 0.4);
|
||||
|
||||
curSelected += change;
|
||||
currentEntry += change;
|
||||
|
||||
if (curSelected < 0) curSelected = menuItems.length - 1;
|
||||
if (curSelected >= menuItems.length) curSelected = 0;
|
||||
if (currentEntry < 0) currentEntry = currentMenuEntries.length - 1;
|
||||
if (currentEntry >= currentMenuEntries.length) currentEntry = 0;
|
||||
|
||||
for (index => item in grpMenuShit.members)
|
||||
for (entryIndex in 0...currentMenuEntries.length)
|
||||
{
|
||||
item.targetY = index - curSelected;
|
||||
var isCurrent:Bool = entryIndex == currentEntry;
|
||||
|
||||
item.alpha = 0.6;
|
||||
var entry:PauseMenuEntry = currentMenuEntries[entryIndex];
|
||||
var text:AtlasText = entry.sprite;
|
||||
|
||||
if (item.targetY == 0)
|
||||
{
|
||||
item.alpha = 1;
|
||||
}
|
||||
// Set the transparency.
|
||||
text.alpha = isCurrent ? 1.0 : 0.6;
|
||||
|
||||
// Set the position.
|
||||
var targetX = FlxMath.remapToRange((entryIndex - currentEntry), 0, 1, 0, 1.3) * 20 + 90;
|
||||
var targetY = FlxMath.remapToRange((entryIndex - currentEntry), 0, 1, 0, 1.3) * 120 + (FlxG.height * 0.48);
|
||||
FlxTween.globalManager.cancelTweensOf(text);
|
||||
FlxTween.tween(text, {x: targetX, y: targetY}, 0.33, {ease: FlxEase.quartOut});
|
||||
}
|
||||
}
|
||||
|
||||
// ===============
|
||||
// Menu Functions
|
||||
// ===============
|
||||
|
||||
/**
|
||||
* Clear the current menu entries and regenerate them based on the current mode.
|
||||
* @param targetMode Optionally specify a mode to switch to before regenerating the menu.
|
||||
*/
|
||||
function regenerateMenu(?targetMode:PauseMode):Void
|
||||
{
|
||||
// If targetMode is null, keep the current mode.
|
||||
if (targetMode == null) targetMode = this.currentMode;
|
||||
|
||||
var previousMode:PauseMode = this.currentMode;
|
||||
this.currentMode = targetMode;
|
||||
|
||||
resetSelection();
|
||||
chooseMenuEntries();
|
||||
clearAndAddMenuEntries();
|
||||
updateMetadataText();
|
||||
changeSelection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the current selection to the first entry.
|
||||
*/
|
||||
function resetSelection():Void
|
||||
{
|
||||
this.currentEntry = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select which menu entries to display based on the current mode.
|
||||
*/
|
||||
function chooseMenuEntries():Void
|
||||
{
|
||||
// Choose the correct menu entries.
|
||||
// NOTE: We clone the arrays to prevent modifications to the arrays from affecting the original.
|
||||
switch (this.currentMode)
|
||||
{
|
||||
case PauseMode.Standard:
|
||||
currentMenuEntries = PAUSE_MENU_ENTRIES_STANDARD.clone();
|
||||
case PauseMode.Charting:
|
||||
currentMenuEntries = PAUSE_MENU_ENTRIES_CHARTING.clone();
|
||||
case PauseMode.Difficulty:
|
||||
// Prepend the difficulties.
|
||||
var entries:Array<PauseMenuEntry> = [];
|
||||
if (PlayState.instance.currentChart != null)
|
||||
{
|
||||
var difficultiesInVariation = PlayState.instance.currentSong.listDifficulties(PlayState.instance.currentChart.variation);
|
||||
trace('DIFFICULTIES: ${difficultiesInVariation}');
|
||||
for (difficulty in difficultiesInVariation)
|
||||
{
|
||||
entries.push({text: difficulty.toTitleCase(), callback: (state) -> changeDifficulty(state, difficulty)});
|
||||
}
|
||||
}
|
||||
|
||||
// Add the back button.
|
||||
currentMenuEntries = entries.concat(PAUSE_MENU_ENTRIES_DIFFICULTY.clone());
|
||||
case PauseMode.Conversation:
|
||||
currentMenuEntries = PAUSE_MENU_ENTRIES_CONVERSATION.clone();
|
||||
case PauseMode.Cutscene:
|
||||
currentMenuEntries = PAUSE_MENU_ENTRIES_VIDEO_CUTSCENE.clone();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the `menuEntryText` group and render the current menu entries to it.
|
||||
* We first create the `menuEntryText` group if it doesn't already exist.
|
||||
*/
|
||||
function clearAndAddMenuEntries():Void
|
||||
{
|
||||
if (menuEntryText == null)
|
||||
{
|
||||
menuEntryText = new FlxTypedSpriteGroup<AtlasText>();
|
||||
menuEntryText.scrollFactor.set(0, 0);
|
||||
add(menuEntryText);
|
||||
}
|
||||
menuEntryText.clear();
|
||||
|
||||
// Render out the entries depending on the mode.
|
||||
var entryIndex:Int = 0;
|
||||
var toRemove = [];
|
||||
for (entry in currentMenuEntries)
|
||||
{
|
||||
if (entry == null || (entry.filter != null && !entry.filter()))
|
||||
{
|
||||
// Remove entries that should be hidden.
|
||||
toRemove.push(entry);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Handle visible entries.
|
||||
var yPos:Float = 70 * entryIndex + 30;
|
||||
var text:AtlasText = new AtlasText(0, yPos, entry.text, AtlasFont.BOLD);
|
||||
text.scrollFactor.set(0, 0);
|
||||
text.alpha = 0;
|
||||
menuEntryText.add(text);
|
||||
|
||||
entry.sprite = text;
|
||||
|
||||
entryIndex++;
|
||||
}
|
||||
}
|
||||
for (entry in toRemove)
|
||||
{
|
||||
currentMenuEntries.remove(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// ===============
|
||||
// Metadata Functions
|
||||
// ===============
|
||||
|
||||
/**
|
||||
* Update the values for the metadata text in the top right.
|
||||
*/
|
||||
function updateMetadataText():Void
|
||||
{
|
||||
metadataPractice.visible = PlayState.instance?.isPracticeMode ?? false;
|
||||
|
||||
switch (this.currentMode)
|
||||
{
|
||||
case Standard | Difficulty:
|
||||
metadataDeaths.text = '${PlayState.instance?.deathCounter} Blue Balls';
|
||||
case Charting:
|
||||
metadataDeaths.text = 'Chart Editor Preview';
|
||||
case Conversation:
|
||||
metadataDeaths.text = 'Dialogue Paused';
|
||||
case Cutscene:
|
||||
metadataDeaths.text = 'Video Paused';
|
||||
}
|
||||
}
|
||||
|
||||
// ===============
|
||||
// Menu Callbacks
|
||||
// ===============
|
||||
|
||||
/**
|
||||
* Close the pause menu and resume the game.
|
||||
* @param state The current PauseSubState.
|
||||
*/
|
||||
static function resume(state:PauseSubState):Void
|
||||
{
|
||||
// Resume a paused video if it exists.
|
||||
VideoCutscene.resumeVideo();
|
||||
|
||||
state.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch the pause menu to the indicated mode.
|
||||
* Create a callback from this using `.bind(_, targetMode)`.
|
||||
* @param state The current PauseSubState.
|
||||
* @param targetMode The mode to switch to.
|
||||
*/
|
||||
static function switchMode(state:PauseSubState, targetMode:PauseMode):Void
|
||||
{
|
||||
state.regenerateMenu(targetMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch the game's difficulty to the indicated difficulty, then resume the game.
|
||||
* @param state The current PauseSubState.
|
||||
* @param difficulty The difficulty to switch to.
|
||||
*/
|
||||
static function changeDifficulty(state:PauseSubState, difficulty:String):Void
|
||||
{
|
||||
PlayState.instance.currentSong = SongRegistry.instance.fetchEntry(PlayState.instance.currentSong.id.toLowerCase());
|
||||
|
||||
// Reset campaign score when changing difficulty
|
||||
// So if you switch difficulty on the last song of a week you get a really low overall score.
|
||||
PlayStatePlaylist.campaignScore = 0;
|
||||
PlayStatePlaylist.campaignDifficulty = difficulty;
|
||||
PlayState.instance.currentDifficulty = PlayStatePlaylist.campaignDifficulty;
|
||||
|
||||
PlayState.instance.needsReset = true;
|
||||
|
||||
state.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restart the current level, then resume the game.
|
||||
* @param state The current PauseSubState.
|
||||
*/
|
||||
static function restartPlayState(state:PauseSubState):Void
|
||||
{
|
||||
PlayState.instance.needsReset = true;
|
||||
state.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the game into practice mode, then update the pause menu.
|
||||
* @param state The current PauseSubState.
|
||||
*/
|
||||
static function enablePracticeMode(state:PauseSubState):Void
|
||||
{
|
||||
if (PlayState.instance == null) return;
|
||||
|
||||
PlayState.instance.isPracticeMode = true;
|
||||
state.regenerateMenu();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restart the paused video cutscene, then resume the game.
|
||||
* @param state The current PauseSubState.
|
||||
*/
|
||||
static function restartVideoCutscene(state:PauseSubState):Void
|
||||
{
|
||||
VideoCutscene.restartVideo();
|
||||
state.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip the paused video cutscene, then resume the game.
|
||||
* @param state The current PauseSubState.
|
||||
*/
|
||||
static function skipVideoCutscene(state:PauseSubState):Void
|
||||
{
|
||||
VideoCutscene.finishVideo();
|
||||
state.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restart the paused conversation, then resume the game.
|
||||
* @param state The current PauseSubState.
|
||||
*/
|
||||
static function restartConversation(state:PauseSubState):Void
|
||||
{
|
||||
if (PlayState.instance?.currentConversation == null) return;
|
||||
|
||||
PlayState.instance.currentConversation.resetConversation();
|
||||
state.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip the paused conversation, then resume the game.
|
||||
* @param state The current PauseSubState.
|
||||
*/
|
||||
static function skipConversation(state:PauseSubState):Void
|
||||
{
|
||||
if (PlayState.instance?.currentConversation == null) return;
|
||||
|
||||
PlayState.instance.currentConversation.skipConversation();
|
||||
state.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Quit the game and return to the main menu.
|
||||
* @param state The current PauseSubState.
|
||||
*/
|
||||
static function quitToMenu(state:PauseSubState):Void
|
||||
{
|
||||
state.allowInput = false;
|
||||
|
||||
PlayState.instance.deathCounter = 0;
|
||||
|
||||
FlxTransitionableState.skipNextTransIn = true;
|
||||
FlxTransitionableState.skipNextTransOut = true;
|
||||
|
||||
if (PlayStatePlaylist.isStoryMode)
|
||||
{
|
||||
PlayStatePlaylist.reset();
|
||||
state.openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new funkin.ui.story.StoryMenuState(sticker)));
|
||||
}
|
||||
else
|
||||
{
|
||||
state.openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new funkin.ui.freeplay.FreeplayState(null, sticker)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Quit the game and return to the chart editor.
|
||||
* @param state The current PauseSubState.
|
||||
*/
|
||||
static function quitToChartEditor(state:PauseSubState):Void
|
||||
{
|
||||
state.close();
|
||||
if (FlxG.sound.music != null) FlxG.sound.music.pause(); // Don't reset song position!
|
||||
PlayState.instance.close(); // This only works because PlayState is a substate!
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Which set of options the pause menu should display.
|
||||
*/
|
||||
enum PauseMode
|
||||
{
|
||||
/**
|
||||
* The menu displayed when the player pauses the game during a song.
|
||||
*/
|
||||
Standard;
|
||||
|
||||
/**
|
||||
* The menu displayed when the player pauses the game during a song while in charting mode.
|
||||
*/
|
||||
Charting;
|
||||
|
||||
/**
|
||||
* The menu displayed when the player moves to change the game's difficulty.
|
||||
*/
|
||||
Difficulty;
|
||||
|
||||
/**
|
||||
* The menu displayed when the player pauses the game during a conversation.
|
||||
*/
|
||||
Conversation;
|
||||
|
||||
/**
|
||||
* The menu displayed when the player pauses the game during a video cutscene.
|
||||
*/
|
||||
Cutscene;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a single entry in the pause menu.
|
||||
*/
|
||||
typedef PauseMenuEntry =
|
||||
{
|
||||
/**
|
||||
* The text to display for this entry.
|
||||
* TODO: Implement localization.
|
||||
*/
|
||||
var text:String;
|
||||
|
||||
/**
|
||||
* The callback to execute when the user selects this entry.
|
||||
*/
|
||||
var callback:PauseSubState->Void;
|
||||
|
||||
/**
|
||||
* If this returns true, the entry will be displayed. If it returns false, the entry will be hidden.
|
||||
*/
|
||||
var ?filter:Void->Bool;
|
||||
|
||||
// Instance-specific properties
|
||||
|
||||
/**
|
||||
* The text object currently displaying this entry.
|
||||
*/
|
||||
var ?sprite:AtlasText;
|
||||
};
|
||||
|
||||
+579
-219
File diff suppressed because it is too large
Load Diff
@@ -2,26 +2,25 @@ package funkin.play;
|
||||
|
||||
import funkin.ui.story.StoryMenuState;
|
||||
import funkin.graphics.adobeanimate.FlxAtlasSprite;
|
||||
import flixel.FlxBasic;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import flixel.graphics.frames.FlxBitmapFont;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import flixel.math.FlxPoint;
|
||||
import funkin.ui.MusicBeatSubState;
|
||||
import flixel.math.FlxRect;
|
||||
import flixel.text.FlxBitmapText;
|
||||
import flixel.text.FlxText;
|
||||
import flixel.tweens.FlxEase;
|
||||
import funkin.ui.freeplay.FreeplayState;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxGradient;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.graphics.shaders.LeftMaskShader;
|
||||
import funkin.play.components.TallyCounter;
|
||||
import flxanimate.FlxAnimate.Settings;
|
||||
|
||||
/**
|
||||
* The state for the results screen after a song or week is finished.
|
||||
*/
|
||||
class ResultState extends MusicBeatSubState
|
||||
{
|
||||
final params:ResultsStateParams;
|
||||
@@ -30,8 +29,8 @@ class ResultState extends MusicBeatSubState
|
||||
var songName:FlxBitmapText;
|
||||
var difficulty:FlxSprite;
|
||||
|
||||
var maskShaderSongName = new LeftMaskShader();
|
||||
var maskShaderDifficulty = new LeftMaskShader();
|
||||
var maskShaderSongName:LeftMaskShader = new LeftMaskShader();
|
||||
var maskShaderDifficulty:LeftMaskShader = new LeftMaskShader();
|
||||
|
||||
public function new(params:ResultsStateParams)
|
||||
{
|
||||
@@ -49,22 +48,22 @@ class ResultState extends MusicBeatSubState
|
||||
else
|
||||
resultsVariation = NORMAL;
|
||||
|
||||
var loops = resultsVariation != SHIT;
|
||||
var loops:Bool = resultsVariation != SHIT;
|
||||
|
||||
FlxG.sound.playMusic(Paths.music("results" + resultsVariation), 1, loops);
|
||||
|
||||
// TEMP-ish, just used to sorta "cache" the 3000x3000 image!
|
||||
var cacheBullShit = new FlxSprite().loadGraphic(Paths.image("resultScreen/soundSystem"));
|
||||
var cacheBullShit:FlxSprite = new FlxSprite().loadGraphic(Paths.image("resultScreen/soundSystem"));
|
||||
add(cacheBullShit);
|
||||
|
||||
var dumb = new FlxSprite().loadGraphic(Paths.image("resultScreen/scorePopin"));
|
||||
var dumb:FlxSprite = new FlxSprite().loadGraphic(Paths.image("resultScreen/scorePopin"));
|
||||
add(dumb);
|
||||
|
||||
var bg:FlxSprite = FlxGradient.createGradientFlxSprite(FlxG.width, FlxG.height, [0xFFFECC5C, 0xFFFDC05C], 90);
|
||||
bg.scrollFactor.set();
|
||||
add(bg);
|
||||
|
||||
var bgFlash:FlxSprite = FlxGradient.createGradientFlxSprite(FlxG.width, FlxG.height, [0xFFffeb69, 0xFFffe66a], 90);
|
||||
var bgFlash:FlxSprite = FlxGradient.createGradientFlxSprite(FlxG.width, FlxG.height, [0xFFFFEB69, 0xFFFFE66A], 90);
|
||||
bgFlash.scrollFactor.set();
|
||||
bgFlash.visible = false;
|
||||
add(bgFlash);
|
||||
@@ -96,8 +95,7 @@ class ResultState extends MusicBeatSubState
|
||||
bfSHIT.anim.play(); // unpauses this anim, since it's on PlayOnce!
|
||||
};
|
||||
|
||||
var gf:FlxSprite = new FlxSprite(500, 300);
|
||||
gf.frames = Paths.getSparrowAtlas('resultScreen/resultGirlfriendGOOD');
|
||||
var gf:FlxSprite = FunkinSprite.createSparrow(500, 300, 'resultScreen/resultGirlfriendGOOD');
|
||||
gf.animation.addByPrefix("clap", "Girlfriend Good Anim", 24, false);
|
||||
gf.visible = false;
|
||||
gf.animation.finishCallback = _ -> {
|
||||
@@ -105,8 +103,7 @@ class ResultState extends MusicBeatSubState
|
||||
};
|
||||
add(gf);
|
||||
|
||||
var boyfriend:FlxSprite = new FlxSprite(640, -200);
|
||||
boyfriend.frames = Paths.getSparrowAtlas('resultScreen/resultBoyfriendGOOD');
|
||||
var boyfriend:FlxSprite = FunkinSprite.createSparrow(640, -200, 'resultScreen/resultBoyfriendGOOD');
|
||||
boyfriend.animation.addByPrefix("fall", "Boyfriend Good", 24, false);
|
||||
boyfriend.visible = false;
|
||||
boyfriend.animation.finishCallback = function(_) {
|
||||
@@ -115,8 +112,7 @@ class ResultState extends MusicBeatSubState
|
||||
|
||||
add(boyfriend);
|
||||
|
||||
var soundSystem:FlxSprite = new FlxSprite(-15, -180);
|
||||
soundSystem.frames = Paths.getSparrowAtlas("resultScreen/soundSystem");
|
||||
var soundSystem:FlxSprite = FunkinSprite.createSparrow(-15, -180, 'resultScreen/soundSystem');
|
||||
soundSystem.animation.addByPrefix("idle", "sound system", 24, false);
|
||||
soundSystem.visible = false;
|
||||
new FlxTimer().start(0.4, _ -> {
|
||||
@@ -162,20 +158,17 @@ class ResultState extends MusicBeatSubState
|
||||
FlxTween.tween(blackTopBar, {y: 0}, 0.4, {ease: FlxEase.quartOut, startDelay: 0.5});
|
||||
add(blackTopBar);
|
||||
|
||||
var resultsAnim:FlxSprite = new FlxSprite(-200, -10);
|
||||
resultsAnim.frames = Paths.getSparrowAtlas("resultScreen/results");
|
||||
var resultsAnim:FunkinSprite = FunkinSprite.createSparrow(-200, -10, "resultScreen/results");
|
||||
resultsAnim.animation.addByPrefix("result", "results", 24, false);
|
||||
resultsAnim.animation.play("result");
|
||||
add(resultsAnim);
|
||||
|
||||
var ratingsPopin:FlxSprite = new FlxSprite(-150, 120);
|
||||
ratingsPopin.frames = Paths.getSparrowAtlas("resultScreen/ratingsPopin");
|
||||
var ratingsPopin:FunkinSprite = FunkinSprite.createSparrow(-150, 120, "resultScreen/ratingsPopin");
|
||||
ratingsPopin.animation.addByPrefix("idle", "Categories", 24, false);
|
||||
ratingsPopin.visible = false;
|
||||
add(ratingsPopin);
|
||||
|
||||
var scorePopin:FlxSprite = new FlxSprite(-180, 520);
|
||||
scorePopin.frames = Paths.getSparrowAtlas("resultScreen/scorePopin");
|
||||
var scorePopin:FunkinSprite = FunkinSprite.createSparrow(-180, 520, "resultScreen/scorePopin");
|
||||
scorePopin.animation.addByPrefix("score", "tally score", 24, false);
|
||||
scorePopin.visible = false;
|
||||
add(scorePopin);
|
||||
@@ -193,6 +186,10 @@ class ResultState extends MusicBeatSubState
|
||||
var ratingGrp:FlxTypedGroup<TallyCounter> = new FlxTypedGroup<TallyCounter>();
|
||||
add(ratingGrp);
|
||||
|
||||
/**
|
||||
* NOTE: We display how many notes were HIT, not how many notes there were in total.
|
||||
*
|
||||
*/
|
||||
var totalHit:TallyCounter = new TallyCounter(375, hStuf * 3, params.tallies.totalNotesHit);
|
||||
ratingGrp.add(totalHit);
|
||||
|
||||
@@ -207,7 +204,7 @@ class ResultState extends MusicBeatSubState
|
||||
var tallyGood:TallyCounter = new TallyCounter(210, (hStuf * 6) + extraYOffset, params.tallies.good, 0xFF89C9E5);
|
||||
ratingGrp.add(tallyGood);
|
||||
|
||||
var tallyBad:TallyCounter = new TallyCounter(190, (hStuf * 7) + extraYOffset, params.tallies.bad, 0xffE6CF8A);
|
||||
var tallyBad:TallyCounter = new TallyCounter(190, (hStuf * 7) + extraYOffset, params.tallies.bad, 0xFFE6CF8A);
|
||||
ratingGrp.add(tallyBad);
|
||||
|
||||
var tallyShit:TallyCounter = new TallyCounter(220, (hStuf * 8) + extraYOffset, params.tallies.shit, 0xFFE68C8A);
|
||||
@@ -279,13 +276,13 @@ class ResultState extends MusicBeatSubState
|
||||
super.create();
|
||||
}
|
||||
|
||||
function timerThenSongName()
|
||||
function timerThenSongName():Void
|
||||
{
|
||||
movingSongStuff = false;
|
||||
|
||||
difficulty.x = 555;
|
||||
|
||||
var diffYTween = 122;
|
||||
var diffYTween:Float = 122;
|
||||
|
||||
difficulty.y = -difficulty.height;
|
||||
FlxTween.tween(difficulty, {y: diffYTween}, 0.5, {ease: FlxEase.quartOut, startDelay: 0.8});
|
||||
@@ -315,7 +312,7 @@ class ResultState extends MusicBeatSubState
|
||||
// maskShaderSongName.frameUV = songName.frame.uv;
|
||||
}
|
||||
|
||||
override function update(elapsed:Float)
|
||||
override function update(elapsed:Float):Void
|
||||
{
|
||||
// maskShaderSongName.swagSprX = songName.x;
|
||||
maskShaderDifficulty.swagSprX = difficulty.x;
|
||||
@@ -350,6 +347,10 @@ class ResultState extends MusicBeatSubState
|
||||
|
||||
if (controls.PAUSE)
|
||||
{
|
||||
FlxTween.tween(FlxG.sound.music, {volume: 0}, 0.8);
|
||||
FlxTween.tween(FlxG.sound.music, {pitch: 3}, 0.1, {onComplete: _ -> {
|
||||
FlxTween.tween(FlxG.sound.music, {pitch: 0.5}, 0.4);
|
||||
}});
|
||||
if (params.storyMode)
|
||||
{
|
||||
openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new StoryMenuState(sticker)));
|
||||
|
||||
@@ -46,7 +46,8 @@ class AnimateAtlasCharacter extends BaseCharacter
|
||||
var _skipTransformChildren:Bool = false;
|
||||
|
||||
var animations:Map<String, AnimateAtlasAnimation> = new Map<String, AnimateAtlasAnimation>();
|
||||
var currentAnimation:String;
|
||||
var currentAnimName:Null<String> = null;
|
||||
var animFinished:Bool = false;
|
||||
|
||||
public function new(id:String)
|
||||
{
|
||||
@@ -77,6 +78,7 @@ class AnimateAtlasCharacter extends BaseCharacter
|
||||
|
||||
var atlasSprite:FlxAtlasSprite = loadAtlasSprite();
|
||||
setSprite(atlasSprite);
|
||||
|
||||
loadAnimations();
|
||||
|
||||
super.onCreate(event);
|
||||
@@ -86,10 +88,36 @@ class AnimateAtlasCharacter extends BaseCharacter
|
||||
{
|
||||
if ((!canPlayOtherAnims && !ignoreOther)) return;
|
||||
|
||||
currentAnimation = name;
|
||||
var prefix:String = getAnimationData(name).prefix;
|
||||
if (prefix == null) prefix = name;
|
||||
this.mainSprite.playAnimation(prefix, restart, ignoreOther);
|
||||
var correctName = correctAnimationName(name);
|
||||
if (correctName == null)
|
||||
{
|
||||
trace('Could not find Atlas animation: ' + name);
|
||||
return;
|
||||
}
|
||||
|
||||
var animData = getAnimationData(correctName);
|
||||
currentAnimName = correctName;
|
||||
var prefix:String = animData.prefix;
|
||||
if (prefix == null) prefix = correctName;
|
||||
var loop:Bool = animData.looped;
|
||||
|
||||
this.mainSprite.playAnimation(prefix, restart, ignoreOther, loop);
|
||||
|
||||
animFinished = false;
|
||||
}
|
||||
|
||||
public override function hasAnimation(name:String):Bool
|
||||
{
|
||||
return getAnimationData(name) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the animation has finished playing.
|
||||
* Never true if animation is configured to loop.
|
||||
*/
|
||||
public override function isAnimationFinished():Bool
|
||||
{
|
||||
return animFinished;
|
||||
}
|
||||
|
||||
function loadAtlasSprite():FlxAtlasSprite
|
||||
@@ -114,7 +142,13 @@ class AnimateAtlasCharacter extends BaseCharacter
|
||||
}
|
||||
else
|
||||
{
|
||||
// Make the game hold on the last frame.
|
||||
this.mainSprite.cleanupAnimation(prefix);
|
||||
// currentAnimName = null;
|
||||
animFinished = true;
|
||||
|
||||
// Fallback to idle!
|
||||
// playAnimation('idle', true, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,19 +174,30 @@ class AnimateAtlasCharacter extends BaseCharacter
|
||||
|
||||
function loadAnimations():Void
|
||||
{
|
||||
trace('[ATLASCHAR] Loading ${_data.animations.length} animations for ${characterId}');
|
||||
trace('[ATLASCHAR] Attempting to load ${_data.animations.length} animations for ${characterId}');
|
||||
|
||||
var animData:Array<AnimateAtlasAnimation> = cast _data.animations;
|
||||
|
||||
for (anim in animData)
|
||||
{
|
||||
// Validate the animation before adding.
|
||||
var prefix = anim.prefix;
|
||||
if (!this.mainSprite.hasAnimation(prefix))
|
||||
{
|
||||
FlxG.log.warn('[ATLASCHAR] Animation ${prefix} not found in Animate Atlas ${_data.assetPath}');
|
||||
continue;
|
||||
}
|
||||
animations.set(anim.name, anim);
|
||||
trace('[ATLASCHAR] - Successfully loaded animation ${anim.name} to ${characterId}');
|
||||
}
|
||||
|
||||
trace('[ATLASCHAR] Loaded ${animations.size()} animations for ${characterId}');
|
||||
}
|
||||
|
||||
public override function getCurrentAnimation():String
|
||||
{
|
||||
return this.mainSprite.getCurrentAnimation();
|
||||
// return this.mainSprite.getCurrentAnimation();
|
||||
return currentAnimName;
|
||||
}
|
||||
|
||||
function getAnimationData(name:String = null):AnimateAtlasAnimation
|
||||
@@ -191,7 +236,11 @@ class AnimateAtlasCharacter extends BaseCharacter
|
||||
_skipTransformChildren = true;
|
||||
super.kill();
|
||||
_skipTransformChildren = false;
|
||||
this.mainSprite.kill();
|
||||
if (this.mainSprite != null)
|
||||
{
|
||||
this.mainSprite.kill();
|
||||
this.mainSprite = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -60,7 +60,7 @@ class BaseCharacter extends Bopper
|
||||
|
||||
@:allow(funkin.ui.debug.anim.DebugBoundingState)
|
||||
final _data:CharacterData;
|
||||
final singTimeSec:Float;
|
||||
final singTimeSteps:Float;
|
||||
|
||||
/**
|
||||
* The offset between the corner of the sprite and the origin of the sprite (at the character's feet).
|
||||
@@ -180,7 +180,7 @@ class BaseCharacter extends Bopper
|
||||
{
|
||||
this.characterName = _data.name;
|
||||
this.name = _data.name;
|
||||
this.singTimeSec = _data.singTime;
|
||||
this.singTimeSteps = _data.singTime;
|
||||
this.globalOffsets = _data.offsets;
|
||||
this.flipX = _data.flipX;
|
||||
}
|
||||
@@ -193,6 +193,21 @@ class BaseCharacter extends Bopper
|
||||
return _data.death?.cameraOffsets ?? [0.0, 0.0];
|
||||
}
|
||||
|
||||
public function getBaseScale():Float
|
||||
{
|
||||
return _data.scale;
|
||||
}
|
||||
|
||||
public function getDeathCameraZoom():Float
|
||||
{
|
||||
return _data.death?.cameraZoom ?? 1.0;
|
||||
}
|
||||
|
||||
public function getDeathPreTransitionDelay():Float
|
||||
{
|
||||
return _data.death?.preTransitionDelay ?? 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of flipX from the character data.
|
||||
* `!getFlipX()` is the direction Boyfriend should face.
|
||||
@@ -250,8 +265,8 @@ class BaseCharacter extends Bopper
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sprite scale to the appropriate value.
|
||||
* @param scale
|
||||
* Set the character's sprite scale to the appropriate value.
|
||||
* @param scale The desired scale.
|
||||
*/
|
||||
public function setScale(scale:Null<Float>):Void
|
||||
{
|
||||
@@ -367,9 +382,9 @@ class BaseCharacter extends Bopper
|
||||
// This lets you add frames to the end of the sing animation to ease back into the idle!
|
||||
|
||||
holdTimer += event.elapsed;
|
||||
var singTimeSec:Float = singTimeSec * (Conductor.instance.beatLengthMs * 0.001); // x beats, to ms.
|
||||
var singTimeSec:Float = singTimeSteps * (Conductor.instance.stepLengthMs / Constants.MS_PER_SEC); // x beats, to ms.
|
||||
|
||||
if (getCurrentAnimation().endsWith('miss')) singTimeSec *= 2; // makes it feel more awkward when you miss
|
||||
if (getCurrentAnimation().endsWith('miss')) singTimeSec *= 2; // makes it feel more awkward when you miss???
|
||||
|
||||
// Without this check here, the player character would only play the `sing` animation
|
||||
// for one beat, as opposed to holding it as long as the player is holding the button.
|
||||
@@ -378,7 +393,7 @@ class BaseCharacter extends Bopper
|
||||
FlxG.watch.addQuick('singTimeSec-${characterId}', singTimeSec);
|
||||
if (holdTimer > singTimeSec && shouldStopSinging)
|
||||
{
|
||||
// trace('holdTimer reached ${holdTimer}sec (> ${singTimeSec}), stopping sing animation');
|
||||
trace('holdTimer reached ${holdTimer}sec (> ${singTimeSec}), stopping sing animation');
|
||||
holdTimer = 0;
|
||||
dance(true);
|
||||
}
|
||||
@@ -475,7 +490,7 @@ class BaseCharacter extends Bopper
|
||||
* Every time a note is hit, check if the note is from the same strumline.
|
||||
* If it is, then play the sing animation.
|
||||
*/
|
||||
public override function onNoteHit(event:NoteScriptEvent)
|
||||
public override function onNoteHit(event:HitNoteScriptEvent)
|
||||
{
|
||||
super.onNoteHit(event);
|
||||
|
||||
|
||||
@@ -305,9 +305,15 @@ class CharacterDataParser
|
||||
icon = "darnell";
|
||||
case "senpai-angry":
|
||||
icon = "senpai";
|
||||
case "tankman" | "tankman-atlas":
|
||||
icon = "tankmen";
|
||||
}
|
||||
|
||||
return Paths.image("freeplay/icons/" + icon + "pixel");
|
||||
var path = Paths.image("freeplay/icons/" + icon + "pixel");
|
||||
if (Assets.exists(path)) return path;
|
||||
|
||||
// TODO: Hardcode some additional behavior or a fallback.
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -372,12 +378,12 @@ class CharacterDataParser
|
||||
}
|
||||
|
||||
/**
|
||||
* The default time the character should sing for, in beats.
|
||||
* The default time the character should sing for, in steps.
|
||||
* Values that are too low will cause the character to stop singing between notes.
|
||||
* Originally, this value was set to 1, but it was changed to 2 because that became
|
||||
* too low after some other code changes.
|
||||
* Values that are too high will cause the character to hold their singing pose for too long after they're done.
|
||||
* @default `8 steps`
|
||||
*/
|
||||
static final DEFAULT_SINGTIME:Float = 2.0;
|
||||
static final DEFAULT_SINGTIME:Float = 8.0;
|
||||
|
||||
static final DEFAULT_DANCEEVERY:Int = 1;
|
||||
static final DEFAULT_FLIPX:Bool = false;
|
||||
@@ -738,4 +744,17 @@ typedef DeathData =
|
||||
* @default [0, 0]
|
||||
*/
|
||||
var ?cameraOffsets:Array<Float>;
|
||||
|
||||
/**
|
||||
* The amount to zoom the camera by while focusing on this character as they die.
|
||||
* Value is a multiplier of the default camera zoom for the stage.
|
||||
* @default 1.0
|
||||
*/
|
||||
var ?cameraZoom:Float;
|
||||
|
||||
/**
|
||||
* Impose a delay between when the character reaches `0` health and when the death animation plays.
|
||||
* @default 0.0
|
||||
*/
|
||||
var ?preTransitionDelay:Float;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import flixel.math.FlxMath;
|
||||
import flixel.math.FlxPoint;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import openfl.utils.Assets;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.util.MathUtil;
|
||||
|
||||
/**
|
||||
@@ -26,7 +27,7 @@ import funkin.util.MathUtil;
|
||||
* @author MasterEric
|
||||
*/
|
||||
@:nullSafety
|
||||
class HealthIcon extends FlxSprite
|
||||
class HealthIcon extends FunkinSprite
|
||||
{
|
||||
/**
|
||||
* The character this icon is representing.
|
||||
@@ -408,7 +409,7 @@ class HealthIcon extends FlxSprite
|
||||
|
||||
if (!isLegacyStyle)
|
||||
{
|
||||
frames = Paths.getSparrowAtlas('icons/icon-$charId');
|
||||
loadSparrow('icons/icon-$charId');
|
||||
|
||||
loadAnimationNew();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@ package funkin.play.components;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.util.FlxDirection;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.util.TimerUtil;
|
||||
|
||||
class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
||||
{
|
||||
@@ -14,26 +17,20 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
||||
|
||||
public function displayRating(daRating:String)
|
||||
{
|
||||
var perfStart:Float = TimerUtil.start();
|
||||
|
||||
if (daRating == null) daRating = "good";
|
||||
|
||||
var rating:FlxSprite = new FlxSprite(0, 0);
|
||||
rating.scrollFactor.set(0.2, 0.2);
|
||||
|
||||
rating.zIndex = 1000;
|
||||
var ratingPath:String = daRating;
|
||||
|
||||
if (PlayState.instance.currentStageId.startsWith('school')) ratingPath = "weeb/pixelUI/" + ratingPath + "-pixel";
|
||||
|
||||
rating.loadGraphic(Paths.image(ratingPath));
|
||||
rating.x = FlxG.width * 0.50;
|
||||
rating.x -= FlxG.camera.scroll.x * 0.2;
|
||||
// make sure rating is visible lol!
|
||||
// if (rating.x < FlxG.camera.scroll.x)
|
||||
// rating.x = FlxG.camera.scroll.x;
|
||||
// else if (rating.x > FlxG.camera.scroll.x + FlxG.camera.width - rating.width)
|
||||
// rating.x = FlxG.camera.scroll.x + FlxG.camera.width - rating.width;
|
||||
var rating:FunkinSprite = FunkinSprite.create(0, 0, ratingPath);
|
||||
rating.scrollFactor.set(0.2, 0.2);
|
||||
|
||||
// FlxG.camera.scroll.y +
|
||||
rating.zIndex = 1000;
|
||||
rating.x = FlxG.width * 0.50;
|
||||
// rating.x -= FlxG.camera.scroll.x * 0.2;
|
||||
rating.y = FlxG.camera.height * 0.4 - 60;
|
||||
rating.acceleration.y = 550;
|
||||
rating.velocity.y -= FlxG.random.int(140, 175);
|
||||
@@ -61,10 +58,14 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
||||
},
|
||||
startDelay: Conductor.instance.beatLengthMs * 0.001
|
||||
});
|
||||
|
||||
trace('displayRating took: ${TimerUtil.seconds(perfStart)}');
|
||||
}
|
||||
|
||||
public function displayCombo(?combo:Int = 0):Int
|
||||
{
|
||||
var perfStart:Float = TimerUtil.start();
|
||||
|
||||
if (combo == null) combo = 0;
|
||||
|
||||
var pixelShitPart1:String = "";
|
||||
@@ -75,16 +76,10 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
||||
pixelShitPart1 = 'weeb/pixelUI/';
|
||||
pixelShitPart2 = '-pixel';
|
||||
}
|
||||
var comboSpr:FlxSprite = new FlxSprite().loadGraphic(Paths.image(pixelShitPart1 + 'combo' + pixelShitPart2));
|
||||
var comboSpr:FunkinSprite = FunkinSprite.create(pixelShitPart1 + 'combo' + pixelShitPart2);
|
||||
comboSpr.y = FlxG.camera.height * 0.4 + 80;
|
||||
comboSpr.x = FlxG.width * 0.50;
|
||||
comboSpr.x -= FlxG.camera.scroll.x * 0.2;
|
||||
// make sure combo is visible lol!
|
||||
// 194 fits 4 combo digits
|
||||
// if (comboSpr.x < FlxG.camera.scroll.x + 194)
|
||||
// comboSpr.x = FlxG.camera.scroll.x + 194;
|
||||
// else if (comboSpr.x > FlxG.camera.scroll.x + FlxG.camera.width - comboSpr.width)
|
||||
// comboSpr.x = FlxG.camera.scroll.x + FlxG.camera.width - comboSpr.width;
|
||||
// comboSpr.x -= FlxG.camera.scroll.x * 0.2;
|
||||
|
||||
comboSpr.acceleration.y = 600;
|
||||
comboSpr.velocity.y -= 150;
|
||||
@@ -129,8 +124,7 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
||||
var daLoop:Int = 1;
|
||||
for (i in seperatedScore)
|
||||
{
|
||||
var numScore:FlxSprite = new FlxSprite().loadGraphic(Paths.image(pixelShitPart1 + 'num' + Std.int(i) + pixelShitPart2));
|
||||
numScore.y = comboSpr.y;
|
||||
var numScore:FunkinSprite = FunkinSprite.create(0, comboSpr.y, pixelShitPart1 + 'num' + Std.int(i) + pixelShitPart2);
|
||||
|
||||
if (PlayState.instance.currentStageId.startsWith('school'))
|
||||
{
|
||||
@@ -163,6 +157,8 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
||||
daLoop++;
|
||||
}
|
||||
|
||||
trace('displayCombo took: ${TimerUtil.seconds(perfStart)}');
|
||||
|
||||
return combo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,13 +19,22 @@ import hxcodec.flixel.FlxVideoSprite;
|
||||
class VideoCutscene
|
||||
{
|
||||
static var blackScreen:FlxSprite;
|
||||
static var cutsceneType:CutsceneType;
|
||||
|
||||
#if html5
|
||||
static var vid:FlxVideo;
|
||||
#end
|
||||
#if hxCodec
|
||||
static var vid:FlxVideoSprite;
|
||||
#end
|
||||
|
||||
/**
|
||||
* Play a video cutscene.
|
||||
* TODO: Currently this is hardcoded to start the countdown after the video is done.
|
||||
* @param path The path to the video file. Use Paths.file(path) to get the correct path.
|
||||
* @param cutseneType The type of cutscene to play, determines what the game does after. Defaults to `CutsceneType.STARTING`.
|
||||
*/
|
||||
public static function play(filePath:String):Void
|
||||
public static function play(filePath:String, ?cutsceneType:CutsceneType = STARTING):Void
|
||||
{
|
||||
if (PlayState.instance == null) return;
|
||||
|
||||
@@ -36,6 +45,8 @@ class VideoCutscene
|
||||
return;
|
||||
}
|
||||
|
||||
var rawFilePath = Paths.stripLibrary(filePath);
|
||||
|
||||
// Trigger the cutscene. Don't play the song in the background.
|
||||
PlayState.instance.isInCutscene = true;
|
||||
PlayState.instance.camHUD.visible = false;
|
||||
@@ -47,12 +58,14 @@ class VideoCutscene
|
||||
blackScreen.cameras = [PlayState.instance.camCutscene];
|
||||
PlayState.instance.add(blackScreen);
|
||||
|
||||
#if html5
|
||||
playVideoHTML5(filePath);
|
||||
#end
|
||||
VideoCutscene.cutsceneType = cutsceneType;
|
||||
|
||||
#if hxCodec
|
||||
playVideoNative(filePath);
|
||||
#if html5
|
||||
playVideoHTML5(rawFilePath);
|
||||
#elseif hxCodec
|
||||
playVideoNative(rawFilePath);
|
||||
#else
|
||||
throw "No video support for this platform!";
|
||||
#end
|
||||
}
|
||||
|
||||
@@ -66,8 +79,6 @@ class VideoCutscene
|
||||
}
|
||||
|
||||
#if html5
|
||||
static var vid:FlxVideo;
|
||||
|
||||
static function playVideoHTML5(filePath:String):Void
|
||||
{
|
||||
// Video displays OVER the FlxState.
|
||||
@@ -92,8 +103,6 @@ class VideoCutscene
|
||||
#end
|
||||
|
||||
#if hxCodec
|
||||
static var vid:FlxVideoSprite;
|
||||
|
||||
static function playVideoNative(filePath:String):Void
|
||||
{
|
||||
// Video displays OVER the FlxState.
|
||||
@@ -103,6 +112,7 @@ class VideoCutscene
|
||||
{
|
||||
vid.zIndex = 0;
|
||||
vid.bitmap.onEndReached.add(finishVideo.bind(0.5));
|
||||
vid.autoPause = false;
|
||||
|
||||
vid.cameras = [PlayState.instance.camCutscene];
|
||||
|
||||
@@ -110,6 +120,15 @@ class VideoCutscene
|
||||
|
||||
PlayState.instance.refresh();
|
||||
vid.play(filePath, false);
|
||||
|
||||
// Resize videos bigger or smaller than the screen.
|
||||
vid.bitmap.onTextureSetup.add(() -> {
|
||||
vid.setGraphicSize(FlxG.width, FlxG.height);
|
||||
vid.updateHitbox();
|
||||
vid.x = 0;
|
||||
vid.y = 0;
|
||||
// vid.scale.set(0.5, 0.5);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -118,10 +137,74 @@ class VideoCutscene
|
||||
}
|
||||
#end
|
||||
|
||||
public static function restartVideo(resume:Bool = true):Void
|
||||
{
|
||||
#if html5
|
||||
if (vid != null)
|
||||
{
|
||||
vid.restartVideo();
|
||||
}
|
||||
#end
|
||||
|
||||
#if hxCodec
|
||||
if (vid != null)
|
||||
{
|
||||
// Seek to the start of the video.
|
||||
vid.bitmap.time = 0;
|
||||
if (resume)
|
||||
{
|
||||
// Resume the video if it was paused.
|
||||
vid.resume();
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
public static function pauseVideo():Void
|
||||
{
|
||||
#if html5
|
||||
if (vid != null)
|
||||
{
|
||||
vid.pauseVideo();
|
||||
}
|
||||
#end
|
||||
|
||||
#if hxCodec
|
||||
if (vid != null)
|
||||
{
|
||||
vid.pause();
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
public static function resumeVideo():Void
|
||||
{
|
||||
#if html5
|
||||
if (vid != null)
|
||||
{
|
||||
vid.resumeVideo();
|
||||
}
|
||||
#end
|
||||
|
||||
#if hxCodec
|
||||
if (vid != null)
|
||||
{
|
||||
vid.resume();
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish the active video cutscene. Done when the video is finished or when the player skips the cutscene.
|
||||
* @param transitionTime The duration of the transition to the next state. Defaults to 0.5 seconds (this time is always used when cancelling the video).
|
||||
* @param finishCutscene The callback to call when the transition is finished.
|
||||
*/
|
||||
public static function finishVideo(?transitionTime:Float = 0.5):Void
|
||||
{
|
||||
trace('ALERT: Finish video cutscene called!');
|
||||
|
||||
var cutsceneType:CutsceneType = VideoCutscene.cutsceneType;
|
||||
|
||||
#if html5
|
||||
if (vid != null)
|
||||
{
|
||||
@@ -157,8 +240,32 @@ class VideoCutscene
|
||||
{
|
||||
ease: FlxEase.quadInOut,
|
||||
onComplete: function(twn:FlxTween) {
|
||||
PlayState.instance.startCountdown();
|
||||
onCutsceneFinish(cutsceneType);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The default callback used when a cutscene is finished.
|
||||
* You can specify your own callback when calling `VideoCutscene#play()`.
|
||||
*/
|
||||
static function onCutsceneFinish(cutsceneType:CutsceneType):Void
|
||||
{
|
||||
switch (cutsceneType)
|
||||
{
|
||||
case CutsceneType.STARTING:
|
||||
PlayState.instance.startCountdown();
|
||||
case CutsceneType.ENDING:
|
||||
PlayState.instance.endSong(true); // true = right goddamn now
|
||||
case CutsceneType.MIDSONG:
|
||||
throw "Not implemented!";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum CutsceneType
|
||||
{
|
||||
STARTING; // The default cutscene type. Starts the countdown after the video is done.
|
||||
MIDSONG; // TODO: Implement this!
|
||||
ENDING; // Ends the song after the video is done.
|
||||
}
|
||||
|
||||
@@ -31,10 +31,6 @@ import funkin.data.dialogue.DialogueBoxRegistry;
|
||||
*/
|
||||
class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass implements IRegistryEntry<ConversationData>
|
||||
{
|
||||
static final CONVERSATION_SKIP_TIMER:Float = 1.5;
|
||||
|
||||
var skipHeldTimer:Float = 0.0;
|
||||
|
||||
/**
|
||||
* The ID of the conversation.
|
||||
*/
|
||||
@@ -105,8 +101,6 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
||||
|
||||
var currentDialogueBox:DialogueBox;
|
||||
|
||||
var skipTimer:FlxPieDial;
|
||||
|
||||
public function new(id:String)
|
||||
{
|
||||
super();
|
||||
@@ -124,8 +118,8 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
||||
{
|
||||
// Reset the progress in the dialogue.
|
||||
currentDialogueEntry = 0;
|
||||
currentDialogueLine = 0;
|
||||
this.state = ConversationState.Start;
|
||||
this.alpha = 1.0;
|
||||
|
||||
// Start the dialogue.
|
||||
dispatchEvent(new DialogueScriptEvent(DIALOGUE_START, this, false));
|
||||
@@ -151,8 +145,31 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
||||
music.play();
|
||||
}
|
||||
|
||||
public function pauseMusic():Void
|
||||
{
|
||||
if (music != null)
|
||||
{
|
||||
music.pause();
|
||||
}
|
||||
}
|
||||
|
||||
public function resumeMusic():Void
|
||||
{
|
||||
if (music != null)
|
||||
{
|
||||
music.resume();
|
||||
}
|
||||
}
|
||||
|
||||
function setupBackdrop():Void
|
||||
{
|
||||
if (backdrop != null)
|
||||
{
|
||||
backdrop.destroy();
|
||||
remove(backdrop);
|
||||
backdrop = null;
|
||||
}
|
||||
|
||||
backdrop = new FunkinSprite(0, 0);
|
||||
|
||||
if (_data.backdrop == null) return;
|
||||
@@ -181,12 +198,6 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
||||
refresh();
|
||||
}
|
||||
|
||||
function setupSkipTimer():Void
|
||||
{
|
||||
add(skipTimer = new FlxPieDial(16, 16, 32, FlxColor.WHITE, 36, CIRCLE, true, 24));
|
||||
skipTimer.amount = 0;
|
||||
}
|
||||
|
||||
public override function update(elapsed:Float):Void
|
||||
{
|
||||
super.update(elapsed);
|
||||
@@ -199,9 +210,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
||||
var nextSpeakerId:String = currentDialogueEntryData.speaker;
|
||||
|
||||
// Skip the next steps if the current speaker is already displayed.
|
||||
if (currentSpeaker != null && nextSpeakerId == currentSpeaker.id) return;
|
||||
|
||||
var nextSpeaker:Speaker = SpeakerRegistry.instance.fetchEntry(nextSpeakerId);
|
||||
if ((currentSpeaker != null && currentSpeaker.alive) && nextSpeakerId == currentSpeaker.id) return;
|
||||
|
||||
if (currentSpeaker != null)
|
||||
{
|
||||
@@ -210,6 +219,8 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
||||
currentSpeaker = null;
|
||||
}
|
||||
|
||||
var nextSpeaker:Speaker = SpeakerRegistry.instance.fetchEntry(nextSpeakerId);
|
||||
|
||||
if (nextSpeaker == null)
|
||||
{
|
||||
if (nextSpeakerId == null)
|
||||
@@ -222,6 +233,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!nextSpeaker.alive) nextSpeaker.revive();
|
||||
|
||||
ScriptEventDispatcher.callEvent(nextSpeaker, new ScriptEvent(CREATE, true));
|
||||
|
||||
@@ -249,8 +261,8 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
||||
{
|
||||
var nextDialogueBoxId:String = currentDialogueEntryData?.box;
|
||||
|
||||
// Skip the next steps if the current speaker is already displayed.
|
||||
if (currentDialogueBox != null && nextDialogueBoxId == currentDialogueBox.id) return;
|
||||
// Skip the next steps if the current dialogue box is already displayed.
|
||||
if ((currentDialogueBox != null && currentDialogueBox.alive) && nextDialogueBoxId == currentDialogueBox.id) return;
|
||||
|
||||
if (currentDialogueBox != null)
|
||||
{
|
||||
@@ -266,6 +278,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
||||
trace('Dialogue box could not be retrieved.');
|
||||
return;
|
||||
}
|
||||
if (!nextDialogueBox.alive) nextDialogueBox.revive();
|
||||
|
||||
ScriptEventDispatcher.callEvent(nextDialogueBox, new ScriptEvent(CREATE, true));
|
||||
|
||||
@@ -347,29 +360,28 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
||||
currentDialogueEntry = 0;
|
||||
this.state = ConversationState.Start;
|
||||
|
||||
advanceConversation();
|
||||
}
|
||||
|
||||
public function trySkipConversation(elapsed:Float):Void
|
||||
{
|
||||
if (skipTimer == null || skipTimer.animation == null) return;
|
||||
|
||||
if (elapsed < 0)
|
||||
if (outroTween != null)
|
||||
{
|
||||
skipHeldTimer = 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
skipHeldTimer += elapsed;
|
||||
outroTween.cancel();
|
||||
}
|
||||
outroTween = null;
|
||||
|
||||
skipTimer.visible = skipHeldTimer >= 0.05;
|
||||
skipTimer.amount = Math.min(skipHeldTimer / CONVERSATION_SKIP_TIMER, 1.0);
|
||||
if (this.music != null) this.music.stop();
|
||||
this.music = null;
|
||||
|
||||
if (skipHeldTimer >= CONVERSATION_SKIP_TIMER)
|
||||
{
|
||||
skipConversation();
|
||||
}
|
||||
if (currentSpeaker != null) currentSpeaker.kill();
|
||||
remove(currentSpeaker);
|
||||
currentSpeaker = null;
|
||||
|
||||
if (currentDialogueBox != null) currentDialogueBox.kill();
|
||||
remove(currentDialogueBox);
|
||||
currentDialogueBox = null;
|
||||
|
||||
if (backdrop != null) backdrop.destroy();
|
||||
remove(backdrop);
|
||||
backdrop = null;
|
||||
|
||||
startConversation();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -383,7 +395,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
||||
dispatchEvent(new DialogueScriptEvent(DIALOGUE_SKIP, this, true));
|
||||
}
|
||||
|
||||
static var outroTween:FlxTween;
|
||||
var outroTween:FlxTween;
|
||||
|
||||
public function startOutro():Void
|
||||
{
|
||||
@@ -411,7 +423,6 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
||||
|
||||
public function endOutro():Void
|
||||
{
|
||||
outroTween = null;
|
||||
ScriptEventDispatcher.callEvent(this, new ScriptEvent(DESTROY, false));
|
||||
}
|
||||
|
||||
@@ -425,7 +436,6 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
||||
// Fade in the music and backdrop.
|
||||
setupMusic();
|
||||
setupBackdrop();
|
||||
setupSkipTimer();
|
||||
|
||||
// Advance the conversation.
|
||||
state = ConversationState.Opening;
|
||||
@@ -547,19 +557,25 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
||||
{
|
||||
propagateEvent(event);
|
||||
|
||||
if (outroTween != null) outroTween.cancel(); // Canc
|
||||
if (outroTween != null)
|
||||
{
|
||||
outroTween.cancel();
|
||||
}
|
||||
outroTween = null;
|
||||
|
||||
this.alpha = 0.0;
|
||||
if (this.music != null) this.music.stop();
|
||||
this.music = null;
|
||||
|
||||
this.skipTimer = null;
|
||||
if (currentSpeaker != null) currentSpeaker.kill();
|
||||
remove(currentSpeaker);
|
||||
currentSpeaker = null;
|
||||
|
||||
if (currentDialogueBox != null) currentDialogueBox.kill();
|
||||
remove(currentDialogueBox);
|
||||
currentDialogueBox = null;
|
||||
if (backdrop != null) backdrop.kill();
|
||||
|
||||
if (backdrop != null) backdrop.destroy();
|
||||
remove(backdrop);
|
||||
backdrop = null;
|
||||
|
||||
this.clear();
|
||||
@@ -578,16 +594,27 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
||||
*/
|
||||
function propagateEvent(event:ScriptEvent):Void
|
||||
{
|
||||
if (this.currentDialogueBox != null)
|
||||
if (this.currentDialogueBox != null && this.currentDialogueBox.exists)
|
||||
{
|
||||
ScriptEventDispatcher.callEvent(this.currentDialogueBox, event);
|
||||
}
|
||||
if (this.currentSpeaker != null)
|
||||
if (this.currentSpeaker != null && this.currentSpeaker.exists)
|
||||
{
|
||||
ScriptEventDispatcher.callEvent(this.currentSpeaker, event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls `kill()` on the group's members and then on the group itself.
|
||||
* You can revive this group later via `revive()` after this.
|
||||
*/
|
||||
public override function revive():Void
|
||||
{
|
||||
super.revive();
|
||||
this.alpha = 1;
|
||||
this.visible = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls `kill()` on the group's members and then on the group itself.
|
||||
* You can revive this group later via `revive()` after this.
|
||||
@@ -599,6 +626,12 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
||||
exists = false;
|
||||
_skipTransformChildren = false;
|
||||
if (group != null) group.kill();
|
||||
|
||||
if (outroTween != null)
|
||||
{
|
||||
outroTween.cancel();
|
||||
outroTween = null;
|
||||
}
|
||||
}
|
||||
|
||||
public override function toString():String
|
||||
|
||||
@@ -4,10 +4,11 @@ import flixel.FlxSprite;
|
||||
import funkin.data.IRegistryEntry;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
import flixel.graphics.frames.FlxFramesCollection;
|
||||
import flixel.text.FlxText;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import flixel.addons.text.FlxTypeText;
|
||||
import funkin.util.assets.FlxAnimationUtil;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.audio.FunkinSound;
|
||||
import funkin.modding.IScriptedClass.IDialogueScriptedClass;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.data.dialogue.DialogueBoxData;
|
||||
@@ -111,9 +112,6 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple
|
||||
this.y = 0;
|
||||
this.alpha = 1;
|
||||
|
||||
this.boxSprite = new FlxSprite(0, 0);
|
||||
add(this.boxSprite);
|
||||
|
||||
loadSpritesheet();
|
||||
loadAnimations();
|
||||
|
||||
@@ -122,6 +120,14 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple
|
||||
|
||||
function loadSpritesheet():Void
|
||||
{
|
||||
if (this.boxSprite != null)
|
||||
{
|
||||
remove(this.boxSprite);
|
||||
this.boxSprite = null;
|
||||
}
|
||||
|
||||
this.boxSprite = new FunkinSprite(0, 0);
|
||||
|
||||
trace('[DIALOGUE BOX] Loading spritesheet ${_data.assetPath} for ${id}');
|
||||
|
||||
var tex:FlxFramesCollection = Paths.getSparrowAtlas(_data.assetPath);
|
||||
@@ -146,6 +152,8 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple
|
||||
this.flipY = _data.flipY;
|
||||
this.globalOffsets = _data.offsets;
|
||||
this.setScale(_data.scale);
|
||||
|
||||
add(this.boxSprite);
|
||||
}
|
||||
|
||||
public function setText(newText:String):Void
|
||||
@@ -190,6 +198,34 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple
|
||||
this.boxSprite.updateHitbox();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls `kill()` on the group's members and then on the group itself.
|
||||
* You can revive this group later via `revive()` after this.
|
||||
*/
|
||||
public override function kill():Void
|
||||
{
|
||||
super.kill();
|
||||
if (this.boxSprite != null)
|
||||
{
|
||||
this.boxSprite.kill();
|
||||
this.boxSprite = null;
|
||||
}
|
||||
if (this.textDisplay != null)
|
||||
{
|
||||
this.textDisplay.kill();
|
||||
this.textDisplay = null;
|
||||
}
|
||||
this.clear();
|
||||
}
|
||||
|
||||
public override function revive():Void
|
||||
{
|
||||
super.revive();
|
||||
|
||||
this.visible = true;
|
||||
this.alpha = 1.0;
|
||||
}
|
||||
|
||||
function loadAnimations():Void
|
||||
{
|
||||
trace('[DIALOGUE BOX] Loading ${_data.animations.length} animations for ${id}');
|
||||
@@ -246,7 +282,8 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple
|
||||
textDisplay.setFormat(_data.text.fontFamily, _data.text.size, FlxColor.fromString(_data.text.color), LEFT, SHADOW,
|
||||
FlxColor.fromString(_data.text.shadowColor ?? '#00000000'), false);
|
||||
textDisplay.borderSize = _data.text.shadowWidth ?? 2;
|
||||
textDisplay.sounds = [FlxG.sound.load(Paths.sound('pixelText'), 0.6)];
|
||||
// TODO: Add an option to configure this.
|
||||
textDisplay.sounds = [FunkinSound.load(Paths.sound('pixelText'), 0.6)];
|
||||
|
||||
textDisplay.completeCallback = onTypingComplete;
|
||||
|
||||
|
||||
@@ -106,6 +106,26 @@ class Speaker extends FlxSprite implements IDialogueScriptedClass implements IRe
|
||||
loadAnimations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls `kill()` on the group's members and then on the group itself.
|
||||
* You can revive this group later via `revive()` after this.
|
||||
*/
|
||||
public override function kill():Void
|
||||
{
|
||||
super.kill();
|
||||
}
|
||||
|
||||
public override function revive():Void
|
||||
{
|
||||
super.revive();
|
||||
|
||||
this.visible = true;
|
||||
this.alpha = 1.0;
|
||||
|
||||
loadSpritesheet();
|
||||
loadAnimations();
|
||||
}
|
||||
|
||||
function loadSpritesheet():Void
|
||||
{
|
||||
trace('[SPEAKER] Loading spritesheet ${_data.assetPath} for ${id}');
|
||||
@@ -198,25 +218,25 @@ class Speaker extends FlxSprite implements IDialogueScriptedClass implements IRe
|
||||
// If the animation exists, we're good.
|
||||
if (hasAnimation(name)) return name;
|
||||
|
||||
trace('[BOPPER] Animation "$name" does not exist!');
|
||||
FlxG.log.notice('Speaker tried to play animation "$name" that does not exist, stripping suffixes...');
|
||||
|
||||
// Attempt to strip a `-alt` suffix, if it exists.
|
||||
if (name.lastIndexOf('-') != -1)
|
||||
{
|
||||
var correctName = name.substring(0, name.lastIndexOf('-'));
|
||||
trace('[BOPPER] Attempting to fallback to "$correctName"');
|
||||
FlxG.log.notice('Speaker tried to play animation "$name" that does not exist, stripping suffixes...');
|
||||
return correctAnimationName(correctName);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (name != 'idle')
|
||||
{
|
||||
trace('[BOPPER] Attempting to fallback to "idle"');
|
||||
FlxG.log.warn('Speaker tried to play animation "$name" that does not exist, fallback to idle...');
|
||||
return correctAnimationName('idle');
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[BOPPER] Failing animation playback.');
|
||||
FlxG.log.error('Speaker tried to play animation "idle" that does not exist! This is bad!');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package funkin.play.event;
|
||||
|
||||
import flixel.tweens.FlxEase;
|
||||
// Data from the chart
|
||||
import funkin.data.song.SongData;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
@@ -57,6 +58,9 @@ class FocusCameraSongEvent extends SongEvent
|
||||
// Does nothing if there is no PlayState camera or stage.
|
||||
if (PlayState.instance == null || PlayState.instance.currentStage == null) return;
|
||||
|
||||
// Does nothing if we are minimal mode.
|
||||
if (PlayState.instance.isMinimalMode) return;
|
||||
|
||||
var posX:Null<Float> = data.getFloat('x');
|
||||
if (posX == null) posX = 0.0;
|
||||
var posY:Null<Float> = data.getFloat('y');
|
||||
@@ -66,6 +70,13 @@ class FocusCameraSongEvent extends SongEvent
|
||||
|
||||
if (char == null) char = cast data.value;
|
||||
|
||||
var useTween:Null<Bool> = data.getBool('useTween');
|
||||
if (useTween == null) useTween = false;
|
||||
var duration:Null<Float> = data.getFloat('duration');
|
||||
if (duration == null) duration = 4.0;
|
||||
var ease:Null<String> = data.getString('ease');
|
||||
if (ease == null) ease = 'linear';
|
||||
|
||||
switch (char)
|
||||
{
|
||||
case -1: // Position
|
||||
@@ -114,6 +125,26 @@ class FocusCameraSongEvent extends SongEvent
|
||||
default:
|
||||
trace('Unknown camera focus: ' + data);
|
||||
}
|
||||
|
||||
if (useTween)
|
||||
{
|
||||
switch (ease)
|
||||
{
|
||||
case 'INSTANT':
|
||||
PlayState.instance.tweenCameraToFollowPoint(0);
|
||||
default:
|
||||
var durSeconds = Conductor.instance.stepLengthMs * duration / 1000;
|
||||
|
||||
var easeFunction:Null<Float->Float> = Reflect.field(FlxEase, ease);
|
||||
if (easeFunction == null)
|
||||
{
|
||||
trace('Invalid ease function: $ease');
|
||||
return;
|
||||
}
|
||||
|
||||
PlayState.instance.tweenCameraToFollowPoint(durSeconds, easeFunction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override function getTitle():String
|
||||
@@ -155,6 +186,51 @@ class FocusCameraSongEvent extends SongEvent
|
||||
step: 10.0,
|
||||
type: SongEventFieldType.FLOAT,
|
||||
units: "px"
|
||||
},
|
||||
{
|
||||
name: 'useTween',
|
||||
title: 'Use Tween',
|
||||
type: SongEventFieldType.BOOL,
|
||||
defaultValue: false
|
||||
},
|
||||
{
|
||||
name: 'duration',
|
||||
title: 'Duration',
|
||||
defaultValue: 4.0,
|
||||
step: 0.5,
|
||||
type: SongEventFieldType.FLOAT,
|
||||
units: 'steps'
|
||||
},
|
||||
{
|
||||
name: 'ease',
|
||||
title: 'Easing Type',
|
||||
defaultValue: 'linear',
|
||||
type: SongEventFieldType.ENUM,
|
||||
keys: [
|
||||
'Linear' => 'linear',
|
||||
'Instant' => 'INSTANT',
|
||||
'Quad In' => 'quadIn',
|
||||
'Quad Out' => 'quadOut',
|
||||
'Quad In/Out' => 'quadInOut',
|
||||
'Cube In' => 'cubeIn',
|
||||
'Cube Out' => 'cubeOut',
|
||||
'Cube In/Out' => 'cubeInOut',
|
||||
'Quart In' => 'quartIn',
|
||||
'Quart Out' => 'quartOut',
|
||||
'Quart In/Out' => 'quartInOut',
|
||||
'Quint In' => 'quintIn',
|
||||
'Quint Out' => 'quintOut',
|
||||
'Quint In/Out' => 'quintInOut',
|
||||
'Smooth Step In' => 'smoothStepIn',
|
||||
'Smooth Step Out' => 'smoothStepOut',
|
||||
'Smooth Step In/Out' => 'smoothStepInOut',
|
||||
'Sine In' => 'sineIn',
|
||||
'Sine Out' => 'sineOut',
|
||||
'Sine In/Out' => 'sineInOut',
|
||||
'Elastic In' => 'elasticIn',
|
||||
'Elastic Out' => 'elasticOut',
|
||||
'Elastic In/Out' => 'elasticInOut',
|
||||
]
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -52,26 +52,36 @@ class ZoomCameraSongEvent extends SongEvent
|
||||
super('ZoomCamera');
|
||||
}
|
||||
|
||||
static final DEFAULT_ZOOM:Float = 1.0;
|
||||
static final DEFAULT_DURATION:Float = 4.0;
|
||||
static final DEFAULT_MODE:String = 'direct';
|
||||
static final DEFAULT_EASE:String = 'linear';
|
||||
|
||||
public override function handleEvent(data:SongEventData):Void
|
||||
{
|
||||
// Does nothing if there is no PlayState camera or stage.
|
||||
if (PlayState.instance == null) return;
|
||||
if (PlayState.instance == null || PlayState.instance.currentStage == null) return;
|
||||
|
||||
var zoom:Null<Float> = data.getFloat('zoom');
|
||||
if (zoom == null) zoom = 1.0;
|
||||
var duration:Null<Float> = data.getFloat('duration');
|
||||
if (duration == null) duration = 4.0;
|
||||
// Does nothing if we are minimal mode.
|
||||
if (PlayState.instance.isMinimalMode) return;
|
||||
|
||||
var ease:Null<String> = data.getString('ease');
|
||||
if (ease == null) ease = 'linear';
|
||||
var zoom:Float = data.getFloat('zoom') ?? DEFAULT_ZOOM;
|
||||
|
||||
var duration:Float = data.getFloat('duration') ?? DEFAULT_DURATION;
|
||||
|
||||
var mode:String = data.getString('mode') ?? DEFAULT_MODE;
|
||||
var isDirectMode:Bool = mode == 'direct';
|
||||
|
||||
var ease:String = data.getString('ease') ?? DEFAULT_EASE;
|
||||
|
||||
// If it's a string, check the value.
|
||||
switch (ease)
|
||||
{
|
||||
case 'INSTANT':
|
||||
// Set the zoom. Use defaultCameraZoom to prevent breaking camera bops.
|
||||
PlayState.instance.defaultCameraZoom = zoom * FlxCamera.defaultZoom;
|
||||
PlayState.instance.tweenCameraZoom(zoom, 0, isDirectMode);
|
||||
default:
|
||||
var durSeconds = Conductor.instance.stepLengthMs * duration / 1000;
|
||||
|
||||
var easeFunction:Null<Float->Float> = Reflect.field(FlxEase, ease);
|
||||
if (easeFunction == null)
|
||||
{
|
||||
@@ -79,8 +89,7 @@ class ZoomCameraSongEvent extends SongEvent
|
||||
return;
|
||||
}
|
||||
|
||||
FlxTween.tween(PlayState.instance, {defaultCameraZoom: zoom * FlxCamera.defaultZoom}, (Conductor.instance.stepLengthMs * duration / 1000),
|
||||
{ease: easeFunction});
|
||||
PlayState.instance.tweenCameraZoom(zoom, durSeconds, isDirectMode, easeFunction);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,8 +102,9 @@ class ZoomCameraSongEvent extends SongEvent
|
||||
* ```
|
||||
* {
|
||||
* 'zoom': FLOAT, // Target zoom level.
|
||||
* 'duration': FLOAT, // Optional duration in steps
|
||||
* 'ease': ENUM, // Optional easing function
|
||||
* 'duration': FLOAT, // Optional duration in steps.
|
||||
* 'mode': ENUM, // Whether to set additive zoom or direct zoom.
|
||||
* 'ease': ENUM, // Optional easing function.
|
||||
* }
|
||||
* @return SongEventSchema
|
||||
*/
|
||||
@@ -117,6 +127,13 @@ class ZoomCameraSongEvent extends SongEvent
|
||||
type: SongEventFieldType.FLOAT,
|
||||
units: 'steps'
|
||||
},
|
||||
{
|
||||
name: 'mode',
|
||||
title: 'Mode',
|
||||
defaultValue: 'direct',
|
||||
type: SongEventFieldType.ENUM,
|
||||
keys: ['Additive' => 'additive', 'Direct' => 'direct']
|
||||
},
|
||||
{
|
||||
name: 'ease',
|
||||
title: 'Easing Type',
|
||||
|
||||
@@ -35,6 +35,7 @@ class NoteSplash extends FlxSprite
|
||||
*/
|
||||
function setup():Void
|
||||
{
|
||||
if (frameCollection?.parent?.isDestroyed ?? false) frameCollection = null;
|
||||
if (frameCollection == null) preloadFrames();
|
||||
|
||||
this.frames = frameCollection;
|
||||
@@ -75,6 +76,8 @@ class NoteSplash extends FlxSprite
|
||||
this.playAnimation('splash${variant}Right');
|
||||
}
|
||||
|
||||
if (animation.curAnim == null) return;
|
||||
|
||||
// Vary the speed of the animation a bit.
|
||||
animation.curAnim.frameRate = FRAMERATE_DEFAULT + FlxG.random.int(-FRAMERATE_VARIANCE, FRAMERATE_VARIANCE);
|
||||
|
||||
|
||||
@@ -4,9 +4,10 @@ import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import flixel.FlxSprite;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.graphics.shaders.HSVShader;
|
||||
|
||||
class NoteSprite extends FlxSprite
|
||||
class NoteSprite extends FunkinSprite
|
||||
{
|
||||
static final DIRECTION_COLORS:Array<String> = ['purple', 'blue', 'green', 'red'];
|
||||
|
||||
@@ -15,26 +16,53 @@ class NoteSprite extends FlxSprite
|
||||
var hsvShader:HSVShader;
|
||||
|
||||
/**
|
||||
* The time at which the note should be hit, in milliseconds.
|
||||
* The strum time at which the note should be hit, in milliseconds.
|
||||
*/
|
||||
public var strumTime(default, set):Float;
|
||||
public var strumTime(get, set):Float;
|
||||
|
||||
function get_strumTime():Float
|
||||
{
|
||||
return this.noteData?.time ?? 0.0;
|
||||
}
|
||||
|
||||
function set_strumTime(value:Float):Float
|
||||
{
|
||||
this.strumTime = value;
|
||||
return this.strumTime;
|
||||
if (this.noteData == null) return value;
|
||||
return this.noteData.time = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The length for which the note should be held, in milliseconds.
|
||||
* Defaults to 0 for single notes.
|
||||
*/
|
||||
public var length(get, set):Float;
|
||||
|
||||
function get_length():Float
|
||||
{
|
||||
return this.noteData?.length ?? 0.0;
|
||||
}
|
||||
|
||||
function set_length(value:Float):Float
|
||||
{
|
||||
if (this.noteData == null) return value;
|
||||
return this.noteData.length = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* An extra attribute for the note.
|
||||
* For example, whether the note is an "alt" note, or whether it has custom behavior on hit.
|
||||
*/
|
||||
public var kind(default, set):String;
|
||||
public var kind(get, set):Null<String>;
|
||||
|
||||
function get_kind():Null<String>
|
||||
{
|
||||
return this.noteData?.kind;
|
||||
}
|
||||
|
||||
function set_kind(value:String):String
|
||||
{
|
||||
this.kind = value;
|
||||
return this.kind;
|
||||
if (this.noteData == null) return value;
|
||||
return this.noteData.kind = value;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,16 +127,13 @@ class NoteSprite extends FlxSprite
|
||||
*/
|
||||
public var handledMiss:Bool;
|
||||
|
||||
public function new(noteStyle:NoteStyle, strumTime:Float = 0, direction:Int = 0)
|
||||
public function new(noteStyle:NoteStyle, direction:Int = 0)
|
||||
{
|
||||
super(0, -9999);
|
||||
this.strumTime = strumTime;
|
||||
this.direction = direction;
|
||||
|
||||
this.hsvShader = new HSVShader();
|
||||
|
||||
if (this.strumTime < 0) this.strumTime = 0;
|
||||
|
||||
setupNoteGraphic(noteStyle);
|
||||
|
||||
// Disables the update() function for performance.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package funkin.play.notes;
|
||||
|
||||
import flixel.util.FlxSignal.FlxTypedSignal;
|
||||
import flixel.FlxG;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
@@ -39,6 +40,10 @@ class Strumline extends FlxSpriteGroup
|
||||
return FlxG.height / 0.45;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this strumline is controlled by the player's inputs.
|
||||
* False means it's controlled by the opponent or Bot Play.
|
||||
*/
|
||||
public var isPlayer:Bool;
|
||||
|
||||
/**
|
||||
@@ -56,6 +61,8 @@ class Strumline extends FlxSpriteGroup
|
||||
|
||||
public var holdNotes:FlxTypedSpriteGroup<SustainTrail>;
|
||||
|
||||
public var onNoteIncoming:FlxTypedSignal<NoteSprite->Void>;
|
||||
|
||||
var strumlineNotes:FlxTypedSpriteGroup<StrumlineNote>;
|
||||
var noteSplashes:FlxTypedSpriteGroup<NoteSplash>;
|
||||
var noteHoldCovers:FlxTypedSpriteGroup<NoteHoldCover>;
|
||||
@@ -113,6 +120,8 @@ class Strumline extends FlxSpriteGroup
|
||||
|
||||
this.refresh();
|
||||
|
||||
this.onNoteIncoming = new FlxTypedSignal<NoteSprite->Void>();
|
||||
|
||||
for (i in 0...KEY_COUNT)
|
||||
{
|
||||
var child:StrumlineNote = new StrumlineNote(noteStyle, isPlayer, DIRECTIONS[i]);
|
||||
@@ -398,6 +407,8 @@ class Strumline extends FlxSpriteGroup
|
||||
}
|
||||
|
||||
nextNoteIndex = noteIndex + 1; // Increment the nextNoteIndex rather than splicing the array, because splicing is slow.
|
||||
|
||||
onNoteIncoming.dispatch(noteSprite);
|
||||
}
|
||||
|
||||
// Update rendering of notes.
|
||||
@@ -746,7 +757,6 @@ class Strumline extends FlxSpriteGroup
|
||||
|
||||
if (noteSprite != null)
|
||||
{
|
||||
noteSprite.strumTime = note.time;
|
||||
noteSprite.direction = note.getDirection();
|
||||
noteSprite.noteData = note;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import flixel.graphics.frames.FlxFramesCollection;
|
||||
import funkin.data.animation.AnimationData;
|
||||
import funkin.data.IRegistryEntry;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.data.notestyle.NoteStyleData;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
@@ -100,6 +101,14 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
|
||||
function buildNoteFrames(force:Bool = false):FlxAtlasFrames
|
||||
{
|
||||
if (!FunkinSprite.isTextureCached(Paths.image(getNoteAssetPath())))
|
||||
{
|
||||
FlxG.log.warn('Note texture is not cached: ${getNoteAssetPath()}');
|
||||
}
|
||||
|
||||
// Purge the note frames if the cached atlas is invalid.
|
||||
if (noteFrames?.parent?.isDestroyed ?? false) noteFrames = null;
|
||||
|
||||
if (noteFrames != null && !force) return noteFrames;
|
||||
|
||||
noteFrames = Paths.getSparrowAtlas(getNoteAssetPath(), getNoteAssetLibrary());
|
||||
@@ -109,8 +118,6 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
||||
throw 'Could not load note frames for note style: $id';
|
||||
}
|
||||
|
||||
noteFrames.parent.persist = true;
|
||||
|
||||
return noteFrames;
|
||||
}
|
||||
|
||||
|
||||
+130
-16
@@ -1,6 +1,5 @@
|
||||
package funkin.play.song;
|
||||
|
||||
import flixel.sound.FlxSound;
|
||||
import funkin.audio.VoicesGroup;
|
||||
import funkin.audio.FunkinSound;
|
||||
import funkin.data.IRegistryEntry;
|
||||
@@ -13,9 +12,8 @@ import funkin.data.song.SongData.SongOffsets;
|
||||
import funkin.data.song.SongData.SongTimeChange;
|
||||
import funkin.data.song.SongData.SongTimeFormat;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.modding.IScriptedClass.IPlayStateScriptedClass;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.IScriptedClass;
|
||||
import funkin.util.SortUtil;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
@@ -31,14 +29,44 @@ import openfl.utils.Assets;
|
||||
@:nullSafety
|
||||
class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMetadata>
|
||||
{
|
||||
public static final DEFAULT_SONGNAME:String = "Unknown";
|
||||
public static final DEFAULT_ARTIST:String = "Unknown";
|
||||
/**
|
||||
* The default value for the song's name
|
||||
*/
|
||||
public static final DEFAULT_SONGNAME:String = 'Unknown';
|
||||
|
||||
/**
|
||||
* The default value for the song's artist
|
||||
*/
|
||||
public static final DEFAULT_ARTIST:String = 'Unknown';
|
||||
|
||||
/**
|
||||
* The default value for the song's time format
|
||||
*/
|
||||
public static final DEFAULT_TIMEFORMAT:SongTimeFormat = SongTimeFormat.MILLISECONDS;
|
||||
|
||||
/**
|
||||
* The default value for the song's divisions
|
||||
*/
|
||||
public static final DEFAULT_DIVISIONS:Null<Int> = null;
|
||||
|
||||
/**
|
||||
* The default value for whether the song loops.
|
||||
*/
|
||||
public static final DEFAULT_LOOPED:Bool = false;
|
||||
public static final DEFAULT_STAGE:String = "mainStage";
|
||||
|
||||
/**
|
||||
* The default value for the song's playable stage.
|
||||
*/
|
||||
public static final DEFAULT_STAGE:String = 'mainStage';
|
||||
|
||||
/**
|
||||
* The default value for the song's scroll speed.
|
||||
*/
|
||||
public static final DEFAULT_SCROLLSPEED:Float = 1.0;
|
||||
|
||||
/**
|
||||
* The internal ID of the song.
|
||||
*/
|
||||
public final id:String;
|
||||
|
||||
/**
|
||||
@@ -53,6 +81,9 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
final _metadata:Map<String, SongMetadata>;
|
||||
final difficulties:Map<String, SongDifficulty>;
|
||||
|
||||
/**
|
||||
* The list of variations a song has.
|
||||
*/
|
||||
public var variations(get, never):Array<String>;
|
||||
|
||||
function get_variations():Array<String>
|
||||
@@ -65,6 +96,9 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
*/
|
||||
public var validScore:Bool = true;
|
||||
|
||||
/**
|
||||
* The readable name of the song.
|
||||
*/
|
||||
public var songName(get, never):String;
|
||||
|
||||
function get_songName():String
|
||||
@@ -74,6 +108,9 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
return DEFAULT_SONGNAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* The artist of the song.
|
||||
*/
|
||||
public var songArtist(get, never):String;
|
||||
|
||||
function get_songArtist():String
|
||||
@@ -101,7 +138,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
{
|
||||
for (vari in _data.playData.songVariations)
|
||||
{
|
||||
var variMeta = fetchVariationMetadata(id, vari);
|
||||
var variMeta:Null<SongMetadata> = fetchVariationMetadata(id, vari);
|
||||
if (variMeta != null) _metadata.set(variMeta.variation, variMeta);
|
||||
}
|
||||
}
|
||||
@@ -115,32 +152,87 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
populateDifficulties();
|
||||
}
|
||||
|
||||
@:allow(funkin.play.song.Song)
|
||||
/**
|
||||
* Build a song from existing metadata rather than loading it from the `assets` folder.
|
||||
* Used by the Chart Editor.
|
||||
*
|
||||
* @param songId The ID of the song.
|
||||
* @param metadata The metadata of the song.
|
||||
* @param variations The list of variations this song has.
|
||||
* @param charts The chart data for each variation.
|
||||
* @param includeScript Whether to initialize the scripted class tied to the song, if it exists.
|
||||
* @param validScore Whether the song is elegible for highscores.
|
||||
* @return The constructed song object.
|
||||
*/
|
||||
public static function buildRaw(songId:String, metadata:Array<SongMetadata>, variations:Array<String>, charts:Map<String, SongChartData>,
|
||||
validScore:Bool = false):Song
|
||||
includeScript:Bool = true, validScore:Bool = false):Song
|
||||
{
|
||||
var result:Song = new Song(songId);
|
||||
@:privateAccess
|
||||
var result:Null<Song>;
|
||||
|
||||
if (includeScript && SongRegistry.instance.isScriptedEntry(songId))
|
||||
{
|
||||
var songClassName:String = SongRegistry.instance.getScriptedEntryClassName(songId);
|
||||
|
||||
@:privateAccess
|
||||
result = SongRegistry.instance.createScriptedEntry(songClassName);
|
||||
}
|
||||
else
|
||||
{
|
||||
@:privateAccess
|
||||
result = SongRegistry.instance.createEntry(songId);
|
||||
}
|
||||
|
||||
if (result == null) throw 'ERROR: Could not build Song instance ($songId), is the attached script bad?';
|
||||
|
||||
result._metadata.clear();
|
||||
for (meta in metadata)
|
||||
{
|
||||
result._metadata.set(meta.variation, meta);
|
||||
}
|
||||
|
||||
result.difficulties.clear();
|
||||
result.populateDifficulties();
|
||||
|
||||
for (variation => chartData in charts)
|
||||
{
|
||||
result.applyChartData(chartData, variation);
|
||||
}
|
||||
|
||||
result.validScore = validScore;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a list of the raw metadata for the song.
|
||||
* @return The metadata JSON objects for the song's variations.
|
||||
*/
|
||||
public function getRawMetadata():Array<SongMetadata>
|
||||
{
|
||||
return _metadata.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* List the album IDs for each variation of the song.
|
||||
* @return A map of variation IDs to album IDs.
|
||||
*/
|
||||
public function listAlbums():Map<String, String>
|
||||
{
|
||||
var result:Map<String, String> = new Map<String, String>();
|
||||
|
||||
for (difficultyId in difficulties.keys())
|
||||
{
|
||||
var meta:Null<SongDifficulty> = difficulties.get(difficultyId);
|
||||
if (meta != null && meta.album != null)
|
||||
{
|
||||
result.set(difficultyId, meta.album);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the difficulty data from the provided metadata.
|
||||
* Does not load chart data (that is triggered later when we want to play the song).
|
||||
@@ -192,6 +284,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
|
||||
/**
|
||||
* Parse and cache the chart for all difficulties of this song.
|
||||
* @param force Whether to forcibly clear the list of charts first.
|
||||
*/
|
||||
public function cacheCharts(force:Bool = false):Void
|
||||
{
|
||||
@@ -294,11 +387,14 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
|
||||
/**
|
||||
* List all the difficulties in this song.
|
||||
*
|
||||
* @param variationId Optionally filter by a single variation.
|
||||
* @param variationIds Optionally filter by multiple variations.
|
||||
* @param showHidden Include charts which are not accessible to the player.
|
||||
*
|
||||
* @return The list of difficulties.
|
||||
*/
|
||||
public function listDifficulties(?variationId:String, ?variationIds:Array<String>):Array<String>
|
||||
public function listDifficulties(?variationId:String, ?variationIds:Array<String>, showHidden:Bool = false):Array<String>
|
||||
{
|
||||
if (variationIds == null) variationIds = [];
|
||||
if (variationId != null) variationIds.push(variationId);
|
||||
@@ -314,6 +410,15 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
return difficulty.difficulty;
|
||||
}).nonNull().unique();
|
||||
|
||||
diffFiltered = diffFiltered.filter(function(diffId:String):Bool {
|
||||
if (showHidden) return true;
|
||||
for (targetVariation in variationIds)
|
||||
{
|
||||
if (isDifficultyVisible(diffId, targetVariation)) return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
diffFiltered.sort(SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_DIFFICULTY_LIST));
|
||||
|
||||
return diffFiltered;
|
||||
@@ -332,6 +437,13 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isDifficultyVisible(diffId:String, variationId:String):Bool
|
||||
{
|
||||
var variation = _metadata.get(variationId);
|
||||
if (variation == null) return false;
|
||||
return variation.playData.difficulties.contains(diffId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge the cached chart data for each difficulty of this song.
|
||||
*/
|
||||
@@ -364,7 +476,9 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||
|
||||
public function onSongRetry(event:ScriptEvent):Void {};
|
||||
|
||||
public function onNoteHit(event:NoteScriptEvent):Void {};
|
||||
public function onNoteIncoming(event:NoteScriptEvent) {}
|
||||
|
||||
public function onNoteHit(event:HitNoteScriptEvent) {}
|
||||
|
||||
public function onNoteMiss(event:NoteScriptEvent):Void {};
|
||||
|
||||
@@ -502,11 +616,11 @@ class SongDifficulty
|
||||
}
|
||||
}
|
||||
|
||||
public inline function playInst(volume:Float = 1.0, looped:Bool = false):Void
|
||||
public function playInst(volume:Float = 1.0, looped:Bool = false):Void
|
||||
{
|
||||
var suffix:String = (variation != null && variation != '' && variation != 'default') ? '-$variation' : '';
|
||||
|
||||
FlxG.sound.music = FunkinSound.load(Paths.inst(this.song.id, suffix), volume, looped);
|
||||
FlxG.sound.music = FunkinSound.load(Paths.inst(this.song.id, suffix), volume, looped, false, true);
|
||||
|
||||
// Workaround for a bug where FlxG.sound.music.update() was being called twice.
|
||||
FlxG.sound.list.remove(FlxG.sound.music);
|
||||
@@ -614,9 +728,9 @@ class SongDifficulty
|
||||
}
|
||||
|
||||
// Add player vocals.
|
||||
if (voiceList[0] != null) result.addPlayerVoice(FunkinSound.load(Assets.getSound(voiceList[0])));
|
||||
if (voiceList[0] != null) result.addPlayerVoice(FunkinSound.load(voiceList[0]));
|
||||
// Add opponent vocals.
|
||||
if (voiceList[1] != null) result.addOpponentVoice(FunkinSound.load(Assets.getSound(voiceList[1])));
|
||||
if (voiceList[1] != null) result.addOpponentVoice(FunkinSound.load(voiceList[1]));
|
||||
|
||||
// Add additional vocals.
|
||||
if (voiceList.length > 2)
|
||||
|
||||
@@ -167,10 +167,7 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
|
||||
|
||||
function update_shouldAlternate():Void
|
||||
{
|
||||
if (hasAnimation('danceLeft'))
|
||||
{
|
||||
this.shouldAlternate = true;
|
||||
}
|
||||
this.shouldAlternate = hasAnimation('danceLeft');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -228,33 +225,42 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
|
||||
|
||||
/**
|
||||
* Ensure that a given animation exists before playing it.
|
||||
* Will gracefully check for name, then name with stripped suffixes, then 'idle', then fail to play.
|
||||
* @param name
|
||||
* Will gracefully check for name, then name with stripped suffixes, then fail to play.
|
||||
* @param name The animation name to attempt to correct.
|
||||
* @param fallback Instead of failing to play, try to play this animation instead.
|
||||
*/
|
||||
function correctAnimationName(name:String):String
|
||||
function correctAnimationName(name:String, ?fallback:String):String
|
||||
{
|
||||
// If the animation exists, we're good.
|
||||
if (hasAnimation(name)) return name;
|
||||
|
||||
trace('[BOPPER] Animation "$name" does not exist!');
|
||||
FlxG.log.notice('Bopper tried to play animation "$name" that does not exist, stripping suffixes...');
|
||||
|
||||
// Attempt to strip a `-alt` suffix, if it exists.
|
||||
if (name.lastIndexOf('-') != -1)
|
||||
{
|
||||
var correctName = name.substring(0, name.lastIndexOf('-'));
|
||||
trace('[BOPPER] Attempting to fallback to "$correctName"');
|
||||
FlxG.log.notice('Bopper tried to play animation "$name" that does not exist, stripping suffixes...');
|
||||
return correctAnimationName(correctName);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (name != 'idle')
|
||||
if (fallback != null)
|
||||
{
|
||||
trace('[BOPPER] Attempting to fallback to "idle"');
|
||||
return correctAnimationName('idle');
|
||||
if (fallback == name)
|
||||
{
|
||||
FlxG.log.error('Bopper tried to play animation "$name" that does not exist! This is bad!');
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
FlxG.log.warn('Bopper tried to play animation "$name" that does not exist, fallback to idle...');
|
||||
return correctAnimationName('idle');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[BOPPER] Failing animation playback.');
|
||||
FlxG.log.error('Bopper tried to play animation "$name" that does not exist! This is bad!');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -353,7 +359,9 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
|
||||
|
||||
public function onGameOver(event:ScriptEvent) {}
|
||||
|
||||
public function onNoteHit(event:NoteScriptEvent) {}
|
||||
public function onNoteIncoming(event:NoteScriptEvent) {}
|
||||
|
||||
public function onNoteHit(event:HitNoteScriptEvent) {}
|
||||
|
||||
public function onNoteMiss(event:NoteScriptEvent) {}
|
||||
|
||||
|
||||
@@ -109,9 +109,11 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
|
||||
{
|
||||
getBoyfriend().resetCharacter(true);
|
||||
// Reapply the camera offsets.
|
||||
var charData = _data.characters.bf;
|
||||
getBoyfriend().cameraFocusPoint.x += charData.cameraOffsets[0];
|
||||
getBoyfriend().cameraFocusPoint.y += charData.cameraOffsets[1];
|
||||
var stageCharData:StageDataCharacter = _data.characters.bf;
|
||||
var finalScale:Float = getBoyfriend().getBaseScale() * stageCharData.scale;
|
||||
getBoyfriend().setScale(finalScale);
|
||||
getBoyfriend().cameraFocusPoint.x += stageCharData.cameraOffsets[0];
|
||||
getBoyfriend().cameraFocusPoint.y += stageCharData.cameraOffsets[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -121,17 +123,21 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
|
||||
{
|
||||
getGirlfriend().resetCharacter(true);
|
||||
// Reapply the camera offsets.
|
||||
var charData = _data.characters.gf;
|
||||
getGirlfriend().cameraFocusPoint.x += charData.cameraOffsets[0];
|
||||
getGirlfriend().cameraFocusPoint.y += charData.cameraOffsets[1];
|
||||
var stageCharData:StageDataCharacter = _data.characters.gf;
|
||||
var finalScale:Float = getBoyfriend().getBaseScale() * stageCharData.scale;
|
||||
getGirlfriend().setScale(finalScale);
|
||||
getGirlfriend().cameraFocusPoint.x += stageCharData.cameraOffsets[0];
|
||||
getGirlfriend().cameraFocusPoint.y += stageCharData.cameraOffsets[1];
|
||||
}
|
||||
if (getDad() != null)
|
||||
{
|
||||
getDad().resetCharacter(true);
|
||||
// Reapply the camera offsets.
|
||||
var charData = _data.characters.dad;
|
||||
getDad().cameraFocusPoint.x += charData.cameraOffsets[0];
|
||||
getDad().cameraFocusPoint.y += charData.cameraOffsets[1];
|
||||
var stageCharData:StageDataCharacter = _data.characters.dad;
|
||||
var finalScale:Float = getBoyfriend().getBaseScale() * stageCharData.scale;
|
||||
getDad().setScale(finalScale);
|
||||
getDad().cameraFocusPoint.x += stageCharData.cameraOffsets[0];
|
||||
getDad().cameraFocusPoint.y += stageCharData.cameraOffsets[1];
|
||||
}
|
||||
|
||||
// Reset positions of named props.
|
||||
@@ -185,9 +191,9 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
|
||||
switch (dataProp.animType)
|
||||
{
|
||||
case 'packer':
|
||||
propSprite.frames = Paths.getPackerAtlas(dataProp.assetPath);
|
||||
propSprite.loadPacker(dataProp.assetPath);
|
||||
default: // 'sparrow'
|
||||
propSprite.frames = Paths.getSparrowAtlas(dataProp.assetPath);
|
||||
propSprite.loadSparrow(dataProp.assetPath);
|
||||
}
|
||||
}
|
||||
else if (isSolidColor)
|
||||
@@ -209,7 +215,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
|
||||
else
|
||||
{
|
||||
// Initalize static sprite.
|
||||
propSprite.loadGraphic(Paths.image(dataProp.assetPath));
|
||||
propSprite.loadTexture(dataProp.assetPath);
|
||||
|
||||
// Disables calls to update() for a performance boost.
|
||||
propSprite.active = false;
|
||||
@@ -226,7 +232,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
|
||||
switch (dataProp.scale)
|
||||
{
|
||||
case Left(value):
|
||||
propSprite.scale.set(value);
|
||||
propSprite.scale.set(value, value);
|
||||
|
||||
case Right(values):
|
||||
propSprite.scale.set(values[0], values[1]);
|
||||
@@ -390,22 +396,25 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
|
||||
#end
|
||||
|
||||
// Apply position and z-index.
|
||||
var charData:StageDataCharacter = null;
|
||||
var stageCharData:StageDataCharacter = null;
|
||||
switch (charType)
|
||||
{
|
||||
case BF:
|
||||
this.characters.set('bf', character);
|
||||
charData = _data.characters.bf;
|
||||
stageCharData = _data.characters.bf;
|
||||
character.flipX = !character.getDataFlipX();
|
||||
character.name = 'bf';
|
||||
character.initHealthIcon(false);
|
||||
case GF:
|
||||
this.characters.set('gf', character);
|
||||
charData = _data.characters.gf;
|
||||
stageCharData = _data.characters.gf;
|
||||
character.flipX = character.getDataFlipX();
|
||||
character.name = 'gf';
|
||||
case DAD:
|
||||
this.characters.set('dad', character);
|
||||
charData = _data.characters.dad;
|
||||
stageCharData = _data.characters.dad;
|
||||
character.flipX = character.getDataFlipX();
|
||||
character.name = 'dad';
|
||||
character.initHealthIcon(true);
|
||||
default:
|
||||
this.characters.set(character.characterId, character);
|
||||
@@ -415,15 +424,15 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
|
||||
// This ensures positioning is based on the idle animation.
|
||||
character.resetCharacter(true);
|
||||
|
||||
if (charData != null)
|
||||
if (stageCharData != null)
|
||||
{
|
||||
character.zIndex = charData.zIndex;
|
||||
character.zIndex = stageCharData.zIndex;
|
||||
|
||||
// Start with the per-stage character position.
|
||||
// Subtracting the origin ensures characters are positioned relative to their feet.
|
||||
// Subtracting the global offset allows positioning on a per-character basis.
|
||||
character.x = charData.position[0] - character.characterOrigin.x + character.globalOffsets[0];
|
||||
character.y = charData.position[1] - character.characterOrigin.y + character.globalOffsets[1];
|
||||
character.x = stageCharData.position[0] - character.characterOrigin.x + character.globalOffsets[0];
|
||||
character.y = stageCharData.position[1] - character.characterOrigin.y + character.globalOffsets[1];
|
||||
|
||||
@:privateAccess(funkin.play.stage.Bopper)
|
||||
{
|
||||
@@ -432,15 +441,17 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
|
||||
character.originalPosition.y = character.y + character.animOffsets[1];
|
||||
}
|
||||
|
||||
character.cameraFocusPoint.x += charData.cameraOffsets[0];
|
||||
character.cameraFocusPoint.y += charData.cameraOffsets[1];
|
||||
var finalScale = character.getBaseScale() * stageCharData.scale;
|
||||
character.setScale(finalScale); // Don't use scale.set for characters!
|
||||
character.cameraFocusPoint.x += stageCharData.cameraOffsets[0];
|
||||
character.cameraFocusPoint.y += stageCharData.cameraOffsets[1];
|
||||
|
||||
#if debug
|
||||
// Draw the debug icon at the character's feet.
|
||||
if (charType == BF || charType == DAD)
|
||||
{
|
||||
debugIcon.x = charData.position[0];
|
||||
debugIcon.y = charData.position[1];
|
||||
debugIcon.x = stageCharData.position[0];
|
||||
debugIcon.y = stageCharData.position[1];
|
||||
debugIcon2.x = character.x;
|
||||
debugIcon2.y = character.y;
|
||||
}
|
||||
@@ -634,7 +645,30 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
|
||||
*/
|
||||
public function dispatchToCharacters(event:ScriptEvent):Void
|
||||
{
|
||||
for (characterId in characters.keys())
|
||||
var charList = this.characters.keys().array();
|
||||
|
||||
// Dad, then BF, then GF, in that order.
|
||||
|
||||
if (charList.contains('dad'))
|
||||
{
|
||||
dispatchToCharacter('dad', event);
|
||||
charList.remove('dad');
|
||||
}
|
||||
|
||||
if (charList.contains('bf'))
|
||||
{
|
||||
dispatchToCharacter('bf', event);
|
||||
charList.remove('bf');
|
||||
}
|
||||
|
||||
if (charList.contains('gf'))
|
||||
{
|
||||
dispatchToCharacter('gf', event);
|
||||
charList.remove('gf');
|
||||
}
|
||||
|
||||
// Then the rest of the characters, if any.
|
||||
for (characterId in charList)
|
||||
{
|
||||
dispatchToCharacter(characterId, event);
|
||||
}
|
||||
@@ -840,7 +874,9 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
|
||||
|
||||
public function onCountdownEnd(event:CountdownScriptEvent) {}
|
||||
|
||||
public function onNoteHit(event:NoteScriptEvent) {}
|
||||
public function onNoteIncoming(event:NoteScriptEvent) {}
|
||||
|
||||
public function onNoteHit(event:HitNoteScriptEvent) {}
|
||||
|
||||
public function onNoteMiss(event:NoteScriptEvent) {}
|
||||
|
||||
|
||||
+215
-195
@@ -11,8 +11,7 @@ import funkin.ui.debug.charting.ChartEditorState.ChartEditorTheme;
|
||||
import thx.semver.Version;
|
||||
|
||||
@:nullSafety
|
||||
@:forward(volume, mute)
|
||||
abstract Save(RawSaveData)
|
||||
class Save
|
||||
{
|
||||
// Version 2.0.2 adds attributes to `optionsChartEditor`, that should return default values if they are null.
|
||||
public static final SAVE_DATA_VERSION:thx.semver.Version = "2.0.2";
|
||||
@@ -25,6 +24,20 @@ abstract Save(RawSaveData)
|
||||
static final SAVE_PATH_LEGACY:String = 'ninjamuffin99';
|
||||
static final SAVE_NAME_LEGACY:String = 'funkin';
|
||||
|
||||
public static var instance(get, never):Save;
|
||||
static var _instance:Null<Save> = null;
|
||||
|
||||
static function get_instance():Save
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = new Save(FlxG.save.data);
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
|
||||
var data:RawSaveData;
|
||||
|
||||
public static function load():Void
|
||||
{
|
||||
trace("[SAVE] Loading save...");
|
||||
@@ -33,86 +46,87 @@ abstract Save(RawSaveData)
|
||||
loadFromSlot(1);
|
||||
}
|
||||
|
||||
public static function get():Save
|
||||
{
|
||||
return FlxG.save.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructing a new Save will load the default values.
|
||||
*/
|
||||
public function new()
|
||||
public function new(data:RawSaveData)
|
||||
{
|
||||
this =
|
||||
{
|
||||
version: Save.SAVE_DATA_VERSION,
|
||||
this.data = data;
|
||||
|
||||
volume: 1.0,
|
||||
mute: false,
|
||||
if (this.data == null) data = Save.getDefault();
|
||||
}
|
||||
|
||||
api:
|
||||
{
|
||||
newgrounds:
|
||||
{
|
||||
sessionId: null,
|
||||
}
|
||||
},
|
||||
scores:
|
||||
{
|
||||
// No saved scores.
|
||||
levels: [],
|
||||
songs: [],
|
||||
},
|
||||
options:
|
||||
{
|
||||
// Reasonable defaults.
|
||||
naughtyness: true,
|
||||
downscroll: false,
|
||||
flashingLights: true,
|
||||
zoomCamera: true,
|
||||
debugDisplay: false,
|
||||
autoPause: true,
|
||||
inputOffset: 0,
|
||||
audioVisualOffset: 0,
|
||||
public static function getDefault():RawSaveData
|
||||
{
|
||||
return {
|
||||
version: Save.SAVE_DATA_VERSION,
|
||||
|
||||
controls:
|
||||
{
|
||||
// Leave controls blank so defaults are loaded.
|
||||
p1:
|
||||
{
|
||||
keyboard: {},
|
||||
gamepad: {},
|
||||
},
|
||||
p2:
|
||||
{
|
||||
keyboard: {},
|
||||
gamepad: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
volume: 1.0,
|
||||
mute: false,
|
||||
|
||||
mods:
|
||||
{
|
||||
// No mods enabled.
|
||||
enabledMods: [],
|
||||
modOptions: [],
|
||||
},
|
||||
api:
|
||||
{
|
||||
newgrounds:
|
||||
{
|
||||
sessionId: null,
|
||||
}
|
||||
},
|
||||
scores:
|
||||
{
|
||||
// No saved scores.
|
||||
levels: [],
|
||||
songs: [],
|
||||
},
|
||||
options:
|
||||
{
|
||||
// Reasonable defaults.
|
||||
naughtyness: true,
|
||||
downscroll: false,
|
||||
flashingLights: true,
|
||||
zoomCamera: true,
|
||||
debugDisplay: false,
|
||||
autoPause: true,
|
||||
inputOffset: 0,
|
||||
audioVisualOffset: 0,
|
||||
|
||||
optionsChartEditor:
|
||||
{
|
||||
// Reasonable defaults.
|
||||
previousFiles: [],
|
||||
noteQuant: 3,
|
||||
chartEditorLiveInputStyle: ChartEditorLiveInputStyle.None,
|
||||
theme: ChartEditorTheme.Light,
|
||||
playtestStartTime: false,
|
||||
downscroll: false,
|
||||
metronomeVolume: 1.0,
|
||||
hitsoundsEnabledPlayer: true,
|
||||
hitsoundsEnabledOpponent: true,
|
||||
themeMusic: true
|
||||
},
|
||||
};
|
||||
controls:
|
||||
{
|
||||
// Leave controls blank so defaults are loaded.
|
||||
p1:
|
||||
{
|
||||
keyboard: {},
|
||||
gamepad: {},
|
||||
},
|
||||
p2:
|
||||
{
|
||||
keyboard: {},
|
||||
gamepad: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
mods:
|
||||
{
|
||||
// No mods enabled.
|
||||
enabledMods: [],
|
||||
modOptions: [],
|
||||
},
|
||||
|
||||
optionsChartEditor:
|
||||
{
|
||||
// Reasonable defaults.
|
||||
previousFiles: [],
|
||||
noteQuant: 3,
|
||||
chartEditorLiveInputStyle: ChartEditorLiveInputStyle.None,
|
||||
theme: ChartEditorTheme.Light,
|
||||
playtestStartTime: false,
|
||||
downscroll: false,
|
||||
metronomeVolume: 1.0,
|
||||
hitsoundVolumePlayer: 1.0,
|
||||
hitsoundVolumeOpponent: 1.0,
|
||||
themeMusic: true
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,7 +136,7 @@ abstract Save(RawSaveData)
|
||||
|
||||
function get_options():SaveDataOptions
|
||||
{
|
||||
return this.options;
|
||||
return data.options;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,7 +146,7 @@ abstract Save(RawSaveData)
|
||||
|
||||
function get_modOptions():Map<String, Dynamic>
|
||||
{
|
||||
return this.mods.modOptions;
|
||||
return data.mods.modOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,249 +156,232 @@ abstract Save(RawSaveData)
|
||||
|
||||
function get_ngSessionId():Null<String>
|
||||
{
|
||||
return this.api.newgrounds.sessionId;
|
||||
return data.api.newgrounds.sessionId;
|
||||
}
|
||||
|
||||
function set_ngSessionId(value:Null<String>):Null<String>
|
||||
{
|
||||
this.api.newgrounds.sessionId = value;
|
||||
data.api.newgrounds.sessionId = value;
|
||||
flush();
|
||||
return this.api.newgrounds.sessionId;
|
||||
return data.api.newgrounds.sessionId;
|
||||
}
|
||||
|
||||
public var enabledModIds(get, set):Array<String>;
|
||||
|
||||
function get_enabledModIds():Array<String>
|
||||
{
|
||||
return this.mods.enabledMods;
|
||||
return data.mods.enabledMods;
|
||||
}
|
||||
|
||||
function set_enabledModIds(value:Array<String>):Array<String>
|
||||
{
|
||||
this.mods.enabledMods = value;
|
||||
data.mods.enabledMods = value;
|
||||
flush();
|
||||
return this.mods.enabledMods;
|
||||
return data.mods.enabledMods;
|
||||
}
|
||||
|
||||
public var chartEditorPreviousFiles(get, set):Array<String>;
|
||||
|
||||
function get_chartEditorPreviousFiles():Array<String>
|
||||
{
|
||||
if (this.optionsChartEditor.previousFiles == null) this.optionsChartEditor.previousFiles = [];
|
||||
if (data.optionsChartEditor.previousFiles == null) data.optionsChartEditor.previousFiles = [];
|
||||
|
||||
return this.optionsChartEditor.previousFiles;
|
||||
return data.optionsChartEditor.previousFiles;
|
||||
}
|
||||
|
||||
function set_chartEditorPreviousFiles(value:Array<String>):Array<String>
|
||||
{
|
||||
// Set and apply.
|
||||
this.optionsChartEditor.previousFiles = value;
|
||||
data.optionsChartEditor.previousFiles = value;
|
||||
flush();
|
||||
return this.optionsChartEditor.previousFiles;
|
||||
return data.optionsChartEditor.previousFiles;
|
||||
}
|
||||
|
||||
public var chartEditorHasBackup(get, set):Bool;
|
||||
|
||||
function get_chartEditorHasBackup():Bool
|
||||
{
|
||||
if (this.optionsChartEditor.hasBackup == null) this.optionsChartEditor.hasBackup = false;
|
||||
if (data.optionsChartEditor.hasBackup == null) data.optionsChartEditor.hasBackup = false;
|
||||
|
||||
return this.optionsChartEditor.hasBackup;
|
||||
return data.optionsChartEditor.hasBackup;
|
||||
}
|
||||
|
||||
function set_chartEditorHasBackup(value:Bool):Bool
|
||||
{
|
||||
// Set and apply.
|
||||
this.optionsChartEditor.hasBackup = value;
|
||||
data.optionsChartEditor.hasBackup = value;
|
||||
flush();
|
||||
return this.optionsChartEditor.hasBackup;
|
||||
return data.optionsChartEditor.hasBackup;
|
||||
}
|
||||
|
||||
public var chartEditorNoteQuant(get, set):Int;
|
||||
|
||||
function get_chartEditorNoteQuant():Int
|
||||
{
|
||||
if (this.optionsChartEditor.noteQuant == null) this.optionsChartEditor.noteQuant = 3;
|
||||
if (data.optionsChartEditor.noteQuant == null) data.optionsChartEditor.noteQuant = 3;
|
||||
|
||||
return this.optionsChartEditor.noteQuant;
|
||||
return data.optionsChartEditor.noteQuant;
|
||||
}
|
||||
|
||||
function set_chartEditorNoteQuant(value:Int):Int
|
||||
{
|
||||
// Set and apply.
|
||||
this.optionsChartEditor.noteQuant = value;
|
||||
data.optionsChartEditor.noteQuant = value;
|
||||
flush();
|
||||
return this.optionsChartEditor.noteQuant;
|
||||
return data.optionsChartEditor.noteQuant;
|
||||
}
|
||||
|
||||
public var chartEditorLiveInputStyle(get, set):ChartEditorLiveInputStyle;
|
||||
|
||||
function get_chartEditorLiveInputStyle():ChartEditorLiveInputStyle
|
||||
{
|
||||
if (this.optionsChartEditor.chartEditorLiveInputStyle == null) this.optionsChartEditor.chartEditorLiveInputStyle = ChartEditorLiveInputStyle.None;
|
||||
if (data.optionsChartEditor.chartEditorLiveInputStyle == null) data.optionsChartEditor.chartEditorLiveInputStyle = ChartEditorLiveInputStyle.None;
|
||||
|
||||
return this.optionsChartEditor.chartEditorLiveInputStyle;
|
||||
return data.optionsChartEditor.chartEditorLiveInputStyle;
|
||||
}
|
||||
|
||||
function set_chartEditorLiveInputStyle(value:ChartEditorLiveInputStyle):ChartEditorLiveInputStyle
|
||||
{
|
||||
// Set and apply.
|
||||
this.optionsChartEditor.chartEditorLiveInputStyle = value;
|
||||
data.optionsChartEditor.chartEditorLiveInputStyle = value;
|
||||
flush();
|
||||
return this.optionsChartEditor.chartEditorLiveInputStyle;
|
||||
return data.optionsChartEditor.chartEditorLiveInputStyle;
|
||||
}
|
||||
|
||||
public var chartEditorDownscroll(get, set):Bool;
|
||||
|
||||
function get_chartEditorDownscroll():Bool
|
||||
{
|
||||
if (this.optionsChartEditor.downscroll == null) this.optionsChartEditor.downscroll = false;
|
||||
if (data.optionsChartEditor.downscroll == null) data.optionsChartEditor.downscroll = false;
|
||||
|
||||
return this.optionsChartEditor.downscroll;
|
||||
return data.optionsChartEditor.downscroll;
|
||||
}
|
||||
|
||||
function set_chartEditorDownscroll(value:Bool):Bool
|
||||
{
|
||||
// Set and apply.
|
||||
this.optionsChartEditor.downscroll = value;
|
||||
data.optionsChartEditor.downscroll = value;
|
||||
flush();
|
||||
return this.optionsChartEditor.downscroll;
|
||||
return data.optionsChartEditor.downscroll;
|
||||
}
|
||||
|
||||
public var chartEditorPlaytestStartTime(get, set):Bool;
|
||||
|
||||
function get_chartEditorPlaytestStartTime():Bool
|
||||
{
|
||||
if (this.optionsChartEditor.playtestStartTime == null) this.optionsChartEditor.playtestStartTime = false;
|
||||
if (data.optionsChartEditor.playtestStartTime == null) data.optionsChartEditor.playtestStartTime = false;
|
||||
|
||||
return this.optionsChartEditor.playtestStartTime;
|
||||
return data.optionsChartEditor.playtestStartTime;
|
||||
}
|
||||
|
||||
function set_chartEditorPlaytestStartTime(value:Bool):Bool
|
||||
{
|
||||
// Set and apply.
|
||||
this.optionsChartEditor.playtestStartTime = value;
|
||||
data.optionsChartEditor.playtestStartTime = value;
|
||||
flush();
|
||||
return this.optionsChartEditor.playtestStartTime;
|
||||
return data.optionsChartEditor.playtestStartTime;
|
||||
}
|
||||
|
||||
public var chartEditorTheme(get, set):ChartEditorTheme;
|
||||
|
||||
function get_chartEditorTheme():ChartEditorTheme
|
||||
{
|
||||
if (this.optionsChartEditor.theme == null) this.optionsChartEditor.theme = ChartEditorTheme.Light;
|
||||
if (data.optionsChartEditor.theme == null) data.optionsChartEditor.theme = ChartEditorTheme.Light;
|
||||
|
||||
return this.optionsChartEditor.theme;
|
||||
return data.optionsChartEditor.theme;
|
||||
}
|
||||
|
||||
function set_chartEditorTheme(value:ChartEditorTheme):ChartEditorTheme
|
||||
{
|
||||
// Set and apply.
|
||||
this.optionsChartEditor.theme = value;
|
||||
data.optionsChartEditor.theme = value;
|
||||
flush();
|
||||
return this.optionsChartEditor.theme;
|
||||
return data.optionsChartEditor.theme;
|
||||
}
|
||||
|
||||
public var chartEditorMetronomeVolume(get, set):Float;
|
||||
|
||||
function get_chartEditorMetronomeVolume():Float
|
||||
{
|
||||
if (this.optionsChartEditor.metronomeVolume == null) this.optionsChartEditor.metronomeVolume = 1.0;
|
||||
if (data.optionsChartEditor.metronomeVolume == null) data.optionsChartEditor.metronomeVolume = 1.0;
|
||||
|
||||
return this.optionsChartEditor.metronomeVolume;
|
||||
return data.optionsChartEditor.metronomeVolume;
|
||||
}
|
||||
|
||||
function set_chartEditorMetronomeVolume(value:Float):Float
|
||||
{
|
||||
// Set and apply.
|
||||
this.optionsChartEditor.metronomeVolume = value;
|
||||
data.optionsChartEditor.metronomeVolume = value;
|
||||
flush();
|
||||
return this.optionsChartEditor.metronomeVolume;
|
||||
return data.optionsChartEditor.metronomeVolume;
|
||||
}
|
||||
|
||||
public var chartEditorHitsoundVolume(get, set):Float;
|
||||
public var chartEditorHitsoundVolumePlayer(get, set):Float;
|
||||
|
||||
function get_chartEditorHitsoundVolume():Float
|
||||
function get_chartEditorHitsoundVolumePlayer():Float
|
||||
{
|
||||
if (this.optionsChartEditor.hitsoundVolume == null) this.optionsChartEditor.hitsoundVolume = 1.0;
|
||||
if (data.optionsChartEditor.hitsoundVolumePlayer == null) data.optionsChartEditor.hitsoundVolumePlayer = 1.0;
|
||||
|
||||
return this.optionsChartEditor.hitsoundVolume;
|
||||
return data.optionsChartEditor.hitsoundVolumePlayer;
|
||||
}
|
||||
|
||||
function set_chartEditorHitsoundVolume(value:Float):Float
|
||||
function set_chartEditorHitsoundVolumePlayer(value:Float):Float
|
||||
{
|
||||
// Set and apply.
|
||||
this.optionsChartEditor.hitsoundVolume = value;
|
||||
data.optionsChartEditor.hitsoundVolumePlayer = value;
|
||||
flush();
|
||||
return this.optionsChartEditor.hitsoundVolume;
|
||||
return data.optionsChartEditor.hitsoundVolumePlayer;
|
||||
}
|
||||
|
||||
public var chartEditorHitsoundsEnabledPlayer(get, set):Bool;
|
||||
public var chartEditorHitsoundVolumeOpponent(get, set):Float;
|
||||
|
||||
function get_chartEditorHitsoundsEnabledPlayer():Bool
|
||||
function get_chartEditorHitsoundVolumeOpponent():Float
|
||||
{
|
||||
if (this.optionsChartEditor.hitsoundsEnabledPlayer == null) this.optionsChartEditor.hitsoundsEnabledPlayer = true;
|
||||
if (data.optionsChartEditor.hitsoundVolumeOpponent == null) data.optionsChartEditor.hitsoundVolumeOpponent = 1.0;
|
||||
|
||||
return this.optionsChartEditor.hitsoundsEnabledPlayer;
|
||||
return data.optionsChartEditor.hitsoundVolumeOpponent;
|
||||
}
|
||||
|
||||
function set_chartEditorHitsoundsEnabledPlayer(value:Bool):Bool
|
||||
function set_chartEditorHitsoundVolumeOpponent(value:Float):Float
|
||||
{
|
||||
// Set and apply.
|
||||
this.optionsChartEditor.hitsoundsEnabledPlayer = value;
|
||||
data.optionsChartEditor.hitsoundVolumeOpponent = value;
|
||||
flush();
|
||||
return this.optionsChartEditor.hitsoundsEnabledPlayer;
|
||||
}
|
||||
|
||||
public var chartEditorHitsoundsEnabledOpponent(get, set):Bool;
|
||||
|
||||
function get_chartEditorHitsoundsEnabledOpponent():Bool
|
||||
{
|
||||
if (this.optionsChartEditor.hitsoundsEnabledOpponent == null) this.optionsChartEditor.hitsoundsEnabledOpponent = true;
|
||||
|
||||
return this.optionsChartEditor.hitsoundsEnabledOpponent;
|
||||
}
|
||||
|
||||
function set_chartEditorHitsoundsEnabledOpponent(value:Bool):Bool
|
||||
{
|
||||
// Set and apply.
|
||||
this.optionsChartEditor.hitsoundsEnabledOpponent = value;
|
||||
flush();
|
||||
return this.optionsChartEditor.hitsoundsEnabledOpponent;
|
||||
return data.optionsChartEditor.hitsoundVolumeOpponent;
|
||||
}
|
||||
|
||||
public var chartEditorThemeMusic(get, set):Bool;
|
||||
|
||||
function get_chartEditorThemeMusic():Bool
|
||||
{
|
||||
if (this.optionsChartEditor.themeMusic == null) this.optionsChartEditor.themeMusic = true;
|
||||
if (data.optionsChartEditor.themeMusic == null) data.optionsChartEditor.themeMusic = true;
|
||||
|
||||
return this.optionsChartEditor.themeMusic;
|
||||
return data.optionsChartEditor.themeMusic;
|
||||
}
|
||||
|
||||
function set_chartEditorThemeMusic(value:Bool):Bool
|
||||
{
|
||||
// Set and apply.
|
||||
this.optionsChartEditor.themeMusic = value;
|
||||
data.optionsChartEditor.themeMusic = value;
|
||||
flush();
|
||||
return this.optionsChartEditor.themeMusic;
|
||||
return data.optionsChartEditor.themeMusic;
|
||||
}
|
||||
|
||||
public var chartEditorPlaybackSpeed(get, set):Float;
|
||||
|
||||
function get_chartEditorPlaybackSpeed():Float
|
||||
{
|
||||
if (this.optionsChartEditor.playbackSpeed == null) this.optionsChartEditor.playbackSpeed = 1.0;
|
||||
if (data.optionsChartEditor.playbackSpeed == null) data.optionsChartEditor.playbackSpeed = 1.0;
|
||||
|
||||
return this.optionsChartEditor.playbackSpeed;
|
||||
return data.optionsChartEditor.playbackSpeed;
|
||||
}
|
||||
|
||||
function set_chartEditorPlaybackSpeed(value:Float):Float
|
||||
{
|
||||
// Set and apply.
|
||||
this.optionsChartEditor.playbackSpeed = value;
|
||||
data.optionsChartEditor.playbackSpeed = value;
|
||||
flush();
|
||||
return this.optionsChartEditor.playbackSpeed;
|
||||
return data.optionsChartEditor.playbackSpeed;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -396,11 +393,11 @@ abstract Save(RawSaveData)
|
||||
*/
|
||||
public function getLevelScore(levelId:String, difficultyId:String = 'normal'):Null<SaveScoreData>
|
||||
{
|
||||
var level = this.scores.levels.get(levelId);
|
||||
var level = data.scores.levels.get(levelId);
|
||||
if (level == null)
|
||||
{
|
||||
level = [];
|
||||
this.scores.levels.set(levelId, level);
|
||||
data.scores.levels.set(levelId, level);
|
||||
}
|
||||
|
||||
return level.get(difficultyId);
|
||||
@@ -411,11 +408,11 @@ abstract Save(RawSaveData)
|
||||
*/
|
||||
public function setLevelScore(levelId:String, difficultyId:String, score:SaveScoreData):Void
|
||||
{
|
||||
var level = this.scores.levels.get(levelId);
|
||||
var level = data.scores.levels.get(levelId);
|
||||
if (level == null)
|
||||
{
|
||||
level = [];
|
||||
this.scores.levels.set(levelId, level);
|
||||
data.scores.levels.set(levelId, level);
|
||||
}
|
||||
level.set(difficultyId, score);
|
||||
|
||||
@@ -424,11 +421,11 @@ abstract Save(RawSaveData)
|
||||
|
||||
public function isLevelHighScore(levelId:String, difficultyId:String = 'normal', score:SaveScoreData):Bool
|
||||
{
|
||||
var level = this.scores.levels.get(levelId);
|
||||
var level = data.scores.levels.get(levelId);
|
||||
if (level == null)
|
||||
{
|
||||
level = [];
|
||||
this.scores.levels.set(levelId, level);
|
||||
data.scores.levels.set(levelId, level);
|
||||
}
|
||||
|
||||
var currentScore = level.get(difficultyId);
|
||||
@@ -467,11 +464,11 @@ abstract Save(RawSaveData)
|
||||
*/
|
||||
public function getSongScore(songId:String, difficultyId:String = 'normal'):Null<SaveScoreData>
|
||||
{
|
||||
var song = this.scores.songs.get(songId);
|
||||
var song = data.scores.songs.get(songId);
|
||||
if (song == null)
|
||||
{
|
||||
song = [];
|
||||
this.scores.songs.set(songId, song);
|
||||
data.scores.songs.set(songId, song);
|
||||
}
|
||||
return song.get(difficultyId);
|
||||
}
|
||||
@@ -481,11 +478,11 @@ abstract Save(RawSaveData)
|
||||
*/
|
||||
public function setSongScore(songId:String, difficultyId:String, score:SaveScoreData):Void
|
||||
{
|
||||
var song = this.scores.songs.get(songId);
|
||||
var song = data.scores.songs.get(songId);
|
||||
if (song == null)
|
||||
{
|
||||
song = [];
|
||||
this.scores.songs.set(songId, song);
|
||||
data.scores.songs.set(songId, song);
|
||||
}
|
||||
song.set(difficultyId, score);
|
||||
|
||||
@@ -501,11 +498,11 @@ abstract Save(RawSaveData)
|
||||
*/
|
||||
public function isSongHighScore(songId:String, difficultyId:String = 'normal', score:SaveScoreData):Bool
|
||||
{
|
||||
var song = this.scores.songs.get(songId);
|
||||
var song = data.scores.songs.get(songId);
|
||||
if (song == null)
|
||||
{
|
||||
song = [];
|
||||
this.scores.songs.set(songId, song);
|
||||
data.scores.songs.set(songId, song);
|
||||
}
|
||||
|
||||
var currentScore = song.get(difficultyId);
|
||||
@@ -546,9 +543,9 @@ abstract Save(RawSaveData)
|
||||
switch (inputType)
|
||||
{
|
||||
case Keys:
|
||||
return (playerId == 0) ? this.options.controls.p1.keyboard : this.options.controls.p2.keyboard;
|
||||
return (playerId == 0) ? data.options.controls.p1.keyboard : data.options.controls.p2.keyboard;
|
||||
case Gamepad(_):
|
||||
return (playerId == 0) ? this.options.controls.p1.gamepad : this.options.controls.p2.gamepad;
|
||||
return (playerId == 0) ? data.options.controls.p1.gamepad : data.options.controls.p2.gamepad;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -566,20 +563,20 @@ abstract Save(RawSaveData)
|
||||
case Keys:
|
||||
if (playerId == 0)
|
||||
{
|
||||
this.options.controls.p1.keyboard = controls;
|
||||
data.options.controls.p1.keyboard = controls;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.options.controls.p2.keyboard = controls;
|
||||
data.options.controls.p2.keyboard = controls;
|
||||
}
|
||||
case Gamepad(_):
|
||||
if (playerId == 0)
|
||||
{
|
||||
this.options.controls.p1.gamepad = controls;
|
||||
data.options.controls.p1.gamepad = controls;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.options.controls.p2.gamepad = controls;
|
||||
data.options.controls.p2.gamepad = controls;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -600,6 +597,36 @@ abstract Save(RawSaveData)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The user's current volume setting.
|
||||
*/
|
||||
public var volume(get, set):Float;
|
||||
|
||||
function get_volume():Float
|
||||
{
|
||||
return data.volume;
|
||||
}
|
||||
|
||||
function set_volume(value:Float):Float
|
||||
{
|
||||
return data.volume = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the user's volume is currently muted.
|
||||
*/
|
||||
public var mute(get, set):Bool;
|
||||
|
||||
function get_mute():Bool
|
||||
{
|
||||
return data.mute;
|
||||
}
|
||||
|
||||
function set_mute(value:Bool):Bool
|
||||
{
|
||||
return data.mute = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this to make sure the save data is written to disk.
|
||||
*/
|
||||
@@ -625,17 +652,22 @@ abstract Save(RawSaveData)
|
||||
if (legacySaveData != null)
|
||||
{
|
||||
trace('[SAVE] Found legacy save data, converting...');
|
||||
FlxG.save.mergeData(SaveDataMigrator.migrateFromLegacy(legacySaveData));
|
||||
var gameSave = SaveDataMigrator.migrate(legacySaveData);
|
||||
@:privateAccess
|
||||
FlxG.save.mergeData(gameSave.data);
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[SAVE] No legacy save data found.');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[SAVE] Loaded save data.');
|
||||
FlxG.save.mergeData(SaveDataMigrator.migrate(FlxG.save.data));
|
||||
@:privateAccess
|
||||
var gameSave = SaveDataMigrator.migrate(FlxG.save.data);
|
||||
FlxG.save.mergeData(gameSave.data);
|
||||
}
|
||||
|
||||
trace('[SAVE] Done loading save data.');
|
||||
trace(FlxG.save.data);
|
||||
}
|
||||
|
||||
static function fetchLegacySaveData():Null<RawSaveData_v1_0_0>
|
||||
@@ -924,12 +956,6 @@ typedef SaveControlsData =
|
||||
*/
|
||||
var ?CUTSCENE_ADVANCE:Array<Int>;
|
||||
|
||||
/**
|
||||
* Keybind for skipping a cutscene.
|
||||
* @default `Escape`
|
||||
*/
|
||||
var ?CUTSCENE_SKIP:Array<Int>;
|
||||
|
||||
/**
|
||||
* Keybind for increasing volume.
|
||||
* @default `Plus`
|
||||
@@ -1004,10 +1030,16 @@ typedef SaveDataChartEditorOptions =
|
||||
var ?metronomeVolume:Float;
|
||||
|
||||
/**
|
||||
* Hitsound volume in the Chart Editor.
|
||||
* Hitsound volume (player) in the Chart Editor.
|
||||
* @default `1.0`
|
||||
*/
|
||||
var ?hitsoundVolume:Float;
|
||||
var ?hitsoundVolumePlayer:Float;
|
||||
|
||||
/**
|
||||
* Hitsound volume (opponent) in the Chart Editor.
|
||||
* @default `1.0`
|
||||
*/
|
||||
var ?hitsoundVolumeOpponent:Float;
|
||||
|
||||
/**
|
||||
* If true, playtest songs from the current position in the Chart Editor.
|
||||
@@ -1015,18 +1047,6 @@ typedef SaveDataChartEditorOptions =
|
||||
*/
|
||||
var ?playtestStartTime:Bool;
|
||||
|
||||
/**
|
||||
* Player note hit sounds in the Chart Editor.
|
||||
* @default `true`
|
||||
*/
|
||||
var ?hitsoundsEnabledPlayer:Bool;
|
||||
|
||||
/**
|
||||
* Opponent note hit sounds in the Chart Editor.
|
||||
* @default `true`
|
||||
*/
|
||||
var ?hitsoundsEnabledOpponent:Bool;
|
||||
|
||||
/**
|
||||
* Theme music in the Chart Editor.
|
||||
* @default `true`
|
||||
|
||||
@@ -35,7 +35,6 @@ typedef SaveControlsData_v1_0_0 =
|
||||
var ?ACCEPT:Array<Int>;
|
||||
var ?BACK:Array<Int>;
|
||||
var ?CUTSCENE_ADVANCE:Array<Int>;
|
||||
var ?CUTSCENE_SKIP:Array<Int>;
|
||||
var ?NOTE_DOWN:Array<Int>;
|
||||
var ?NOTE_LEFT:Array<Int>;
|
||||
var ?NOTE_RIGHT:Array<Int>;
|
||||
|
||||
@@ -19,21 +19,21 @@ class SaveDataMigrator
|
||||
{
|
||||
trace('[SAVE] No version found in save data! Returning blank data.');
|
||||
trace(inputData);
|
||||
return new Save();
|
||||
return new Save(Save.getDefault());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (VersionUtil.validateVersion(version, Save.SAVE_DATA_VERSION_RULE))
|
||||
{
|
||||
// Simply cast the structured data.
|
||||
var save:Save = inputData;
|
||||
// Simply import the structured data.
|
||||
var save:Save = new Save(inputData);
|
||||
return save;
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[SAVE] Invalid save data version! Returning blank data.');
|
||||
trace(inputData);
|
||||
return new Save();
|
||||
return new Save(Save.getDefault());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ class SaveDataMigrator
|
||||
{
|
||||
var inputSaveData:RawSaveData_v1_0_0 = cast inputData;
|
||||
|
||||
var result:Save = new Save();
|
||||
var result:Save = new Save(Save.getDefault());
|
||||
|
||||
result.volume = inputSaveData.volume;
|
||||
result.mute = inputSaveData.mute;
|
||||
@@ -272,7 +272,6 @@ class SaveDataMigrator
|
||||
ACCEPT: controlsData?.keys?.ACCEPT ?? null,
|
||||
BACK: controlsData?.keys?.BACK ?? null,
|
||||
CUTSCENE_ADVANCE: controlsData?.keys?.CUTSCENE_ADVANCE ?? null,
|
||||
CUTSCENE_SKIP: controlsData?.keys?.CUTSCENE_SKIP ?? null,
|
||||
NOTE_DOWN: controlsData?.keys?.NOTE_DOWN ?? null,
|
||||
NOTE_LEFT: controlsData?.keys?.NOTE_LEFT ?? null,
|
||||
NOTE_RIGHT: controlsData?.keys?.NOTE_RIGHT ?? null,
|
||||
@@ -293,7 +292,6 @@ class SaveDataMigrator
|
||||
ACCEPT: controlsData?.pad?.ACCEPT ?? null,
|
||||
BACK: controlsData?.pad?.BACK ?? null,
|
||||
CUTSCENE_ADVANCE: controlsData?.pad?.CUTSCENE_ADVANCE ?? null,
|
||||
CUTSCENE_SKIP: controlsData?.pad?.CUTSCENE_SKIP ?? null,
|
||||
NOTE_DOWN: controlsData?.pad?.NOTE_DOWN ?? null,
|
||||
NOTE_LEFT: controlsData?.pad?.NOTE_LEFT ?? null,
|
||||
NOTE_RIGHT: controlsData?.pad?.NOTE_RIGHT ?? null,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package funkin.ui;
|
||||
|
||||
import flixel.addons.transition.FlxTransitionableSubState;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
import flixel.FlxSubState;
|
||||
import flixel.text.FlxText;
|
||||
import funkin.ui.mainmenu.MainMenuState;
|
||||
@@ -20,7 +20,16 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler
|
||||
{
|
||||
public var leftWatermarkText:FlxText = null;
|
||||
public var rightWatermarkText:FlxText = null;
|
||||
public var conductorInUse:Conductor = Conductor.instance;
|
||||
|
||||
public var conductorInUse(get, default):Conductor;
|
||||
|
||||
var _conductorInUse:Null<Conductor>;
|
||||
|
||||
function get_conductorInUse():Conductor
|
||||
{
|
||||
if (_conductorInUse == null) return Conductor.instance;
|
||||
return _conductorInUse;
|
||||
}
|
||||
|
||||
public function new(bgColor:FlxColor = FlxColor.TRANSPARENT)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package funkin.ui.debug;
|
||||
|
||||
import funkin.util.MemoryUtil;
|
||||
import openfl.text.TextFormat;
|
||||
import openfl.system.System;
|
||||
import openfl.text.TextField;
|
||||
@@ -35,7 +36,7 @@ class MemoryCounter extends TextField
|
||||
@:noCompletion
|
||||
#if !flash override #end function __enterFrame(deltaTime:Float):Void
|
||||
{
|
||||
var mem:Float = Math.round(System.totalMemory / BYTES_PER_MEG / ROUND_TO) * ROUND_TO;
|
||||
var mem:Float = Math.round(MemoryUtil.getMemoryUsed() / BYTES_PER_MEG / ROUND_TO) * ROUND_TO;
|
||||
|
||||
if (mem > memPeak) memPeak = mem;
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ import openfl.utils.ByteArray;
|
||||
import funkin.input.Cursor;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import funkin.util.SortUtil;
|
||||
import haxe.ui.core.Screen;
|
||||
|
||||
using flixel.util.FlxSpriteUtil;
|
||||
|
||||
@@ -75,21 +76,19 @@ class DebugBoundingState extends FlxState
|
||||
|
||||
var uiStuff:Component;
|
||||
|
||||
var haxeUIFocused(get, default):Bool = false;
|
||||
|
||||
function get_haxeUIFocused():Bool
|
||||
{
|
||||
// get the screen position, according to the HUD camera, temp default to FlxG.camera juuust in case?
|
||||
var hudMousePos:FlxPoint = FlxG.mouse.getScreenPosition(hudCam ?? FlxG.camera);
|
||||
return Screen.instance.hasSolidComponentUnderPoint(hudMousePos.x, hudMousePos.y);
|
||||
}
|
||||
|
||||
override function create()
|
||||
{
|
||||
Paths.setCurrentLevel('week1');
|
||||
|
||||
var str = Paths.xml('ui/animation-editor/offset-editor-view');
|
||||
uiStuff = RuntimeComponentBuilder.fromAsset(str);
|
||||
|
||||
// uiStuff.findComponent("btnViewSpriteSheet").onClick = _ -> curView = SPRITESHEET;
|
||||
var dropdown:DropDown = cast uiStuff.findComponent("swapper");
|
||||
dropdown.onChange = function(e:UIEvent) {
|
||||
trace(e.type);
|
||||
curView = cast e.data.curView;
|
||||
trace(e.data);
|
||||
// trace(e.data);
|
||||
};
|
||||
// lv.
|
||||
// lv.onChange = function(e:UIEvent)
|
||||
// {
|
||||
@@ -106,23 +105,40 @@ class DebugBoundingState extends FlxState
|
||||
hudCam = new FlxCamera();
|
||||
hudCam.bgColor.alpha = 0;
|
||||
|
||||
FlxG.cameras.add(hudCam, false);
|
||||
|
||||
bg = FlxGridOverlay.create(10, 10);
|
||||
// bg = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.GREEN);
|
||||
|
||||
bg.scrollFactor.set();
|
||||
add(bg);
|
||||
|
||||
initSpritesheetView();
|
||||
initOffsetView();
|
||||
// we are setting this as the default draw camera only temporarily, to trick haxeui
|
||||
FlxG.cameras.add(hudCam);
|
||||
|
||||
Cursor.show();
|
||||
var str = Paths.xml('ui/animation-editor/offset-editor-view');
|
||||
uiStuff = RuntimeComponentBuilder.fromAsset(str);
|
||||
|
||||
// uiStuff.findComponent("btnViewSpriteSheet").onClick = _ -> curView = SPRITESHEET;
|
||||
var dropdown:DropDown = cast uiStuff.findComponent("swapper");
|
||||
dropdown.onChange = function(e:UIEvent) {
|
||||
trace(e.type);
|
||||
curView = cast e.data.curView;
|
||||
trace(e.data);
|
||||
// trace(e.data);
|
||||
};
|
||||
|
||||
uiStuff.cameras = [hudCam];
|
||||
|
||||
add(uiStuff);
|
||||
|
||||
// sets the default camera back to FlxG.camera, since we set it to hudCamera for haxeui stuf
|
||||
FlxG.cameras.setDefaultDrawTarget(FlxG.camera, true);
|
||||
FlxG.cameras.setDefaultDrawTarget(hudCam, false);
|
||||
|
||||
initSpritesheetView();
|
||||
initOffsetView();
|
||||
|
||||
Cursor.show();
|
||||
|
||||
super.create();
|
||||
}
|
||||
|
||||
@@ -359,7 +375,7 @@ class DebugBoundingState extends FlxState
|
||||
offsetView.visible = true;
|
||||
offsetView.active = true;
|
||||
offsetControls();
|
||||
mouseOffsetMovement();
|
||||
if (!haxeUIFocused) mouseOffsetMovement();
|
||||
}
|
||||
|
||||
if (FlxG.keys.justPressed.H) hudCam.visible = !hudCam.visible;
|
||||
@@ -367,7 +383,7 @@ class DebugBoundingState extends FlxState
|
||||
if (FlxG.keys.justPressed.F4) FlxG.switchState(() -> new MainMenuState());
|
||||
|
||||
MouseUtil.mouseCamDrag();
|
||||
MouseUtil.mouseWheelZoom();
|
||||
if (!haxeUIFocused) MouseUtil.mouseWheelZoom();
|
||||
|
||||
// bg.scale.x = FlxG.camera.zoom;
|
||||
// bg.scale.y = FlxG.camera.zoom;
|
||||
|
||||
@@ -310,10 +310,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
{
|
||||
this.songLengthInMs = value;
|
||||
|
||||
// Make sure playhead doesn't go outside the song.
|
||||
if (playheadPositionInMs > songLengthInMs) playheadPositionInMs = songLengthInMs;
|
||||
|
||||
onSongLengthChanged();
|
||||
updateGridHeight();
|
||||
|
||||
return this.songLengthInMs;
|
||||
}
|
||||
@@ -595,11 +592,22 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
*/
|
||||
var playtestPracticeMode:Bool = false;
|
||||
|
||||
/**
|
||||
* If true, playtesting a chart will make the computer do it for you!
|
||||
*/
|
||||
var playtestBotPlayMode:Bool = false;
|
||||
|
||||
/**
|
||||
* Enables or disables the "debugger" popup that appears when you run into a flixel error.
|
||||
*/
|
||||
var enabledDebuggerPopup:Bool = true;
|
||||
|
||||
/**
|
||||
* Whether song scripts should be enabled during playtesting.
|
||||
* You should probably check the box if the song has custom mechanics.
|
||||
*/
|
||||
var playtestSongScripts:Bool = true;
|
||||
|
||||
// Visuals
|
||||
|
||||
/**
|
||||
@@ -704,19 +712,14 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
var metronomeVolume:Float = 1.0;
|
||||
|
||||
/**
|
||||
* The volume to play hitsounds at.
|
||||
* The volume to play the player's hitsounds at.
|
||||
*/
|
||||
var hitsoundVolume:Float = 1.0;
|
||||
var hitsoundVolumePlayer:Float = 1.0;
|
||||
|
||||
/**
|
||||
* Whether hitsounds are enabled for the player.
|
||||
* The volume to play the opponent's hitsounds at.
|
||||
*/
|
||||
var hitsoundsEnabledPlayer:Bool = true;
|
||||
|
||||
/**
|
||||
* Whether hitsounds are enabled for the opponent.
|
||||
*/
|
||||
var hitsoundsEnabledOpponent:Bool = true;
|
||||
var hitsoundVolumeOpponent:Float = 1.0;
|
||||
|
||||
/**
|
||||
* Whether hitsounds are enabled for at least one character.
|
||||
@@ -725,7 +728,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
|
||||
function get_hitsoundsEnabled():Bool
|
||||
{
|
||||
return hitsoundsEnabledPlayer || hitsoundsEnabledOpponent;
|
||||
return hitsoundVolumePlayer + hitsoundVolumeOpponent > 0;
|
||||
}
|
||||
|
||||
// Auto-save
|
||||
@@ -875,6 +878,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
*/
|
||||
var noteDisplayDirty:Bool = true;
|
||||
|
||||
var noteTooltipsDirty:Bool = true;
|
||||
|
||||
/**
|
||||
* Whether the selected charactesr have been modified and the health icons need to be updated.
|
||||
*/
|
||||
@@ -928,12 +933,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
|
||||
function get_shouldShowBackupAvailableDialog():Bool
|
||||
{
|
||||
return Save.get().chartEditorHasBackup;
|
||||
return Save.instance.chartEditorHasBackup;
|
||||
}
|
||||
|
||||
function set_shouldShowBackupAvailableDialog(value:Bool):Bool
|
||||
{
|
||||
return Save.get().chartEditorHasBackup = value;
|
||||
return Save.instance.chartEditorHasBackup = value;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1399,7 +1404,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
|
||||
function get_currentSongId():String
|
||||
{
|
||||
return currentSongName.toLowerKebabCase().replace('.', '').replace(' ', '-');
|
||||
return currentSongName.toLowerKebabCase().replace(' ', '-').sanitize();
|
||||
}
|
||||
|
||||
var currentSongArtist(get, set):String;
|
||||
@@ -1538,6 +1543,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
// Make sure view is updated when the variation changes.
|
||||
noteDisplayDirty = true;
|
||||
notePreviewDirty = true;
|
||||
noteTooltipsDirty = true;
|
||||
notePreviewViewportBoundsDirty = true;
|
||||
|
||||
switchToCurrentInstrumental();
|
||||
@@ -1559,6 +1565,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
// Make sure view is updated when the difficulty changes.
|
||||
noteDisplayDirty = true;
|
||||
notePreviewDirty = true;
|
||||
noteTooltipsDirty = true;
|
||||
notePreviewViewportBoundsDirty = true;
|
||||
|
||||
// Make sure the difficulty we selected is in the list of difficulties.
|
||||
@@ -1757,30 +1764,30 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
*/
|
||||
var menubarItemVolumeMetronome:Slider;
|
||||
|
||||
/**
|
||||
* The `Audio -> Enable Player Hitsounds` menu checkbox.
|
||||
*/
|
||||
var menubarItemPlayerHitsounds:MenuCheckBox;
|
||||
|
||||
/**
|
||||
* The `Audio -> Enable Opponent Hitsounds` menu checkbox.
|
||||
*/
|
||||
var menubarItemOpponentHitsounds:MenuCheckBox;
|
||||
|
||||
/**
|
||||
* The `Audio -> Play Theme Music` menu checkbox.
|
||||
*/
|
||||
var menubarItemThemeMusic:MenuCheckBox;
|
||||
|
||||
/**
|
||||
* The `Audio -> Hitsound Volume` label.
|
||||
* The `Audio -> Player Hitsound Volume` label.
|
||||
*/
|
||||
var menubarLabelVolumeHitsounds:Label;
|
||||
var menubarLabelVolumeHitsoundPlayer:Label;
|
||||
|
||||
/**
|
||||
* The `Audio -> Hitsound Volume` slider.
|
||||
* The `Audio -> Enemy Hitsound Volume` label.
|
||||
*/
|
||||
var menubarItemVolumeHitsounds:Slider;
|
||||
var menubarLabelVolumeHitsoundOpponent:Label;
|
||||
|
||||
/**
|
||||
* The `Audio -> Player Hitsound Volume` slider.
|
||||
*/
|
||||
var menubarItemVolumeHitsoundPlayer:Slider;
|
||||
|
||||
/**
|
||||
* The `Audio -> Enemy Hitsound Volume` slider.
|
||||
*/
|
||||
var menubarItemVolumeHitsoundOpponent:Slider;
|
||||
|
||||
/**
|
||||
* The `Audio -> Instrumental Volume` label.
|
||||
@@ -2171,7 +2178,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
|
||||
public function loadPreferences():Void
|
||||
{
|
||||
var save:Save = Save.get();
|
||||
var save:Save = Save.instance;
|
||||
|
||||
if (previousWorkingFilePaths[0] == null)
|
||||
{
|
||||
@@ -2187,9 +2194,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
playtestStartTime = save.chartEditorPlaytestStartTime;
|
||||
currentTheme = save.chartEditorTheme;
|
||||
metronomeVolume = save.chartEditorMetronomeVolume;
|
||||
hitsoundVolume = save.chartEditorHitsoundVolume;
|
||||
hitsoundsEnabledPlayer = save.chartEditorHitsoundsEnabledPlayer;
|
||||
hitsoundsEnabledOpponent = save.chartEditorHitsoundsEnabledOpponent;
|
||||
hitsoundVolumePlayer = save.chartEditorHitsoundVolumePlayer;
|
||||
hitsoundVolumePlayer = save.chartEditorHitsoundVolumeOpponent;
|
||||
this.welcomeMusic.active = save.chartEditorThemeMusic;
|
||||
|
||||
// audioInstTrack.volume = save.chartEditorInstVolume;
|
||||
@@ -2200,7 +2206,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
|
||||
public function writePreferences(hasBackup:Bool):Void
|
||||
{
|
||||
var save:Save = Save.get();
|
||||
var save:Save = Save.instance;
|
||||
|
||||
// Can't use filter() because of null safety checking!
|
||||
var filteredWorkingFilePaths:Array<String> = [];
|
||||
@@ -2217,9 +2223,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
save.chartEditorPlaytestStartTime = playtestStartTime;
|
||||
save.chartEditorTheme = currentTheme;
|
||||
save.chartEditorMetronomeVolume = metronomeVolume;
|
||||
save.chartEditorHitsoundVolume = hitsoundVolume;
|
||||
save.chartEditorHitsoundsEnabledPlayer = hitsoundsEnabledPlayer;
|
||||
save.chartEditorHitsoundsEnabledOpponent = hitsoundsEnabledOpponent;
|
||||
save.chartEditorHitsoundVolumePlayer = hitsoundVolumePlayer;
|
||||
save.chartEditorHitsoundVolumeOpponent = hitsoundVolumeOpponent;
|
||||
save.chartEditorThemeMusic = this.welcomeMusic.active;
|
||||
|
||||
// save.chartEditorInstVolume = audioInstTrack.volume;
|
||||
@@ -2848,12 +2853,15 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
menuBarItemInputStyleNone.onClick = function(event:UIEvent) {
|
||||
currentLiveInputStyle = None;
|
||||
};
|
||||
menuBarItemInputStyleNone.selected = currentLiveInputStyle == None;
|
||||
menuBarItemInputStyleNumberKeys.onClick = function(event:UIEvent) {
|
||||
currentLiveInputStyle = NumberKeys;
|
||||
};
|
||||
menuBarItemInputStyleNumberKeys.selected = currentLiveInputStyle == NumberKeys;
|
||||
menuBarItemInputStyleWASD.onClick = function(event:UIEvent) {
|
||||
currentLiveInputStyle = WASD;
|
||||
currentLiveInputStyle = WASDKeys;
|
||||
};
|
||||
menuBarItemInputStyleWASD.selected = currentLiveInputStyle == WASDKeys;
|
||||
|
||||
menubarItemAbout.onClick = _ -> this.openAboutDialog();
|
||||
menubarItemWelcomeDialog.onClick = _ -> this.openWelcomeDialog(true);
|
||||
@@ -2912,24 +2920,25 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
};
|
||||
menubarItemVolumeMetronome.value = Std.int(metronomeVolume * 100);
|
||||
|
||||
menubarItemPlayerHitsounds.onChange = event -> hitsoundsEnabledPlayer = event.value;
|
||||
menubarItemPlayerHitsounds.selected = hitsoundsEnabledPlayer;
|
||||
|
||||
menubarItemOpponentHitsounds.onChange = event -> hitsoundsEnabledOpponent = event.value;
|
||||
menubarItemOpponentHitsounds.selected = hitsoundsEnabledOpponent;
|
||||
|
||||
menubarItemThemeMusic.onChange = event -> {
|
||||
this.welcomeMusic.active = event.value;
|
||||
fadeInWelcomeMusic(WELCOME_MUSIC_FADE_IN_DELAY, WELCOME_MUSIC_FADE_IN_DURATION);
|
||||
};
|
||||
menubarItemThemeMusic.selected = this.welcomeMusic.active;
|
||||
|
||||
menubarItemVolumeHitsound.onChange = event -> {
|
||||
menubarItemVolumeHitsoundPlayer.onChange = event -> {
|
||||
var volume:Float = event.value.toFloat() / 100.0;
|
||||
hitsoundVolume = volume;
|
||||
menubarLabelVolumeHitsound.text = 'Hitsound - ${Std.int(event.value)}%';
|
||||
hitsoundVolumePlayer = volume;
|
||||
menubarLabelVolumeHitsoundPlayer.text = 'Player - ${Std.int(event.value)}%';
|
||||
};
|
||||
menubarItemVolumeHitsound.value = Std.int(hitsoundVolume * 100);
|
||||
menubarItemVolumeHitsoundPlayer.value = Std.int(hitsoundVolumePlayer * 100);
|
||||
|
||||
menubarItemVolumeHitsoundOpponent.onChange = event -> {
|
||||
var volume:Float = event.value.toFloat() / 100.0;
|
||||
hitsoundVolumeOpponent = volume;
|
||||
menubarLabelVolumeHitsoundOpponent.text = 'Enemy - ${Std.int(event.value)}%';
|
||||
};
|
||||
menubarItemVolumeHitsoundOpponent.value = Std.int(hitsoundVolumeOpponent * 100);
|
||||
|
||||
menubarItemVolumeInstrumental.onChange = event -> {
|
||||
var volume:Float = event.value.toFloat() / 100.0;
|
||||
@@ -2951,7 +2960,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
|
||||
menubarItemPlaybackSpeed.onChange = event -> {
|
||||
var pitch:Float = (event.value.toFloat() * 2.0) / 100.0;
|
||||
pitch = Math.floor(pitch / 0.25) * 0.25; // Round to nearest 0.25.
|
||||
pitch = Math.floor(pitch / 0.05) * 0.05; // Round to nearest 5%
|
||||
pitch = Math.max(0.05, Math.min(2.0, pitch)); // Clamp to 5% to 200%
|
||||
#if FLX_PITCH
|
||||
if (audioInstTrack != null) audioInstTrack.pitch = pitch;
|
||||
audioVocalTrackGroup.pitch = pitch;
|
||||
@@ -3417,7 +3427,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
// Update the event sprite's position.
|
||||
eventSprite.updateEventPosition(renderedEvents);
|
||||
// Update the sprite's graphic. TODO: Is this inefficient?
|
||||
eventSprite.playAnimation(eventSprite.eventData.event);
|
||||
eventSprite.playAnimation(eventSprite.eventData.eventKind);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -3657,8 +3667,13 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
selectionSquare.width = eventSprite.width;
|
||||
selectionSquare.height = eventSprite.height;
|
||||
}
|
||||
|
||||
// Additional cleanup on notes.
|
||||
if (noteTooltipsDirty) eventSprite.updateTooltipText();
|
||||
}
|
||||
|
||||
noteTooltipsDirty = false;
|
||||
|
||||
// Sort the notes DESCENDING. This keeps the sustain behind the associated note.
|
||||
renderedNotes.sort(FlxSort.byY, FlxSort.DESCENDING); // TODO: .group.insertionSort()
|
||||
|
||||
@@ -4530,14 +4545,14 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
{
|
||||
// Create an event and place it in the chart.
|
||||
// TODO: Figure out configuring event data.
|
||||
var newEventData:SongEventData = new SongEventData(cursorSnappedMs, eventKindToPlace, eventDataToPlace.clone());
|
||||
var newEventData:SongEventData = new SongEventData(cursorSnappedMs, eventKindToPlace, eventDataToPlace.copy());
|
||||
|
||||
performCommand(new AddEventsCommand([newEventData], FlxG.keys.pressed.CONTROL));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create a note and place it in the chart.
|
||||
var newNoteData:SongNoteData = new SongNoteData(cursorSnappedMs, cursorColumn, 0, noteKindToPlace.clone());
|
||||
var newNoteData:SongNoteData = new SongNoteData(cursorSnappedMs, cursorColumn, 0, noteKindToPlace);
|
||||
|
||||
performCommand(new AddNotesCommand([newNoteData], FlxG.keys.pressed.CONTROL));
|
||||
|
||||
@@ -4678,9 +4693,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
|
||||
var eventData:SongEventData = gridGhostEvent.eventData != null ? gridGhostEvent.eventData : new SongEventData(cursorMs, eventKindToPlace, null);
|
||||
|
||||
if (eventKindToPlace != eventData.event)
|
||||
if (eventKindToPlace != eventData.eventKind)
|
||||
{
|
||||
eventData.event = eventKindToPlace;
|
||||
eventData.eventKind = eventKindToPlace;
|
||||
}
|
||||
eventData.time = cursorSnappedMs;
|
||||
|
||||
@@ -4942,7 +4957,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
// Place notes at the playhead.
|
||||
switch (currentLiveInputStyle)
|
||||
{
|
||||
case ChartEditorLiveInputStyle.WASD:
|
||||
case ChartEditorLiveInputStyle.WASDKeys:
|
||||
if (FlxG.keys.justPressed.A) placeNoteAtPlayhead(4);
|
||||
if (FlxG.keys.justPressed.S) placeNoteAtPlayhead(5);
|
||||
if (FlxG.keys.justPressed.W) placeNoteAtPlayhead(6);
|
||||
@@ -5245,6 +5260,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
}
|
||||
// Would bind Ctrl+A and Ctrl+D here, but they are already bound to Select All and Select None.
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Ignoring keybinds for View menu items because we are in live input mode (${currentLiveInputStyle}).');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -5267,14 +5286,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
{
|
||||
// F1 = Open Help
|
||||
if (FlxG.keys.justPressed.F1) this.openUserGuideDialog();
|
||||
|
||||
// DEBUG KEYBIND: Ctrl + Alt + Shift + L = Crash the game.
|
||||
#if debug
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.L)
|
||||
{
|
||||
throw "DEBUG: Crashing the chart editor!";
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
function handleQuickWatch():Void
|
||||
@@ -5317,10 +5328,14 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
var startTimestamp:Float = 0;
|
||||
if (playtestStartTime) startTimestamp = scrollPositionInMs + playheadPositionInMs;
|
||||
|
||||
var playbackRate:Float = ((menubarItemPlaybackSpeed.value ?? 1.0) * 2.0) / 100.0;
|
||||
playbackRate = Math.floor(playbackRate / 0.05) * 0.05; // Round to nearest 5%
|
||||
playbackRate = Math.max(0.05, Math.min(2.0, playbackRate)); // Clamp to 5% to 200%
|
||||
|
||||
var targetSong:Song;
|
||||
try
|
||||
{
|
||||
targetSong = Song.buildRaw(currentSongId, songMetadata.values(), availableVariations, songChartData, false);
|
||||
targetSong = Song.buildRaw(currentSongId, songMetadata.values(), availableVariations, songChartData, playtestSongScripts, false);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
@@ -5328,9 +5343,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
return;
|
||||
}
|
||||
|
||||
LogStyle.WARNING.openConsole = enabledDebuggerPopup;
|
||||
LogStyle.ERROR.openConsole = enabledDebuggerPopup;
|
||||
|
||||
// TODO: Rework asset system so we can remove this.
|
||||
switch (currentSongStage)
|
||||
{
|
||||
@@ -5348,7 +5360,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
Paths.setCurrentLevel('week6');
|
||||
case 'tankmanBattlefield':
|
||||
Paths.setCurrentLevel('week7');
|
||||
case 'phillyStreets' | 'phillyBlazin':
|
||||
case 'phillyStreets' | 'phillyBlazin' | 'phillyBlazin2':
|
||||
Paths.setCurrentLevel('weekend1');
|
||||
}
|
||||
|
||||
@@ -5364,8 +5376,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
targetDifficulty: selectedDifficulty,
|
||||
targetVariation: selectedVariation,
|
||||
practiceMode: playtestPracticeMode,
|
||||
botPlayMode: playtestBotPlayMode,
|
||||
minimalMode: minimal,
|
||||
startTimestamp: startTimestamp,
|
||||
playbackRate: playbackRate,
|
||||
overrideMusic: true,
|
||||
});
|
||||
|
||||
@@ -5511,8 +5525,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
this.switchToInstrumental(currentInstrumentalId, currentSongMetadata.playData.characters.player, currentSongMetadata.playData.characters.opponent);
|
||||
}
|
||||
|
||||
function onSongLengthChanged():Void
|
||||
public function updateGridHeight():Void
|
||||
{
|
||||
// Make sure playhead doesn't go outside the song after we update the grid height.
|
||||
if (playheadPositionInMs > songLengthInMs) playheadPositionInMs = songLengthInMs;
|
||||
|
||||
if (gridTiledSprite != null)
|
||||
{
|
||||
gridTiledSprite.height = songLengthInPixels;
|
||||
@@ -5929,7 +5946,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
var tempNote:NoteSprite = new NoteSprite(NoteStyleRegistry.instance.fetchDefault());
|
||||
tempNote.noteData = noteData;
|
||||
tempNote.scrollFactor.set(0, 0);
|
||||
var event:NoteScriptEvent = new NoteScriptEvent(NOTE_HIT, tempNote, 1, true);
|
||||
var event:NoteScriptEvent = new HitNoteScriptEvent(tempNote, 0.0, 0, 'perfect', 0);
|
||||
dispatchEvent(event);
|
||||
|
||||
// Calling event.cancelEvent() skips all the other logic! Neat!
|
||||
@@ -5939,9 +5956,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
switch (noteData.getStrumlineIndex())
|
||||
{
|
||||
case 0: // Player
|
||||
if (hitsoundsEnabledPlayer) this.playSound(Paths.sound('chartingSounds/hitNotePlayer'), hitsoundVolume);
|
||||
if (hitsoundVolumePlayer > 0) this.playSound(Paths.sound('chartingSounds/hitNotePlayer'), hitsoundVolumePlayer);
|
||||
case 1: // Opponent
|
||||
if (hitsoundsEnabledOpponent) this.playSound(Paths.sound('chartingSounds/hitNoteOpponent'), hitsoundVolume);
|
||||
if (hitsoundVolumeOpponent > 0) this.playSound(Paths.sound('chartingSounds/hitNoteOpponent'), hitsoundVolumeOpponent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6139,7 +6156,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
}
|
||||
|
||||
/**
|
||||
* Available input modes for the chart editor state.
|
||||
* Available input modes for the chart editor state. Numbers/arrows/WASD available for other keybinds.
|
||||
*/
|
||||
enum ChartEditorLiveInputStyle
|
||||
{
|
||||
@@ -6154,9 +6171,9 @@ enum ChartEditorLiveInputStyle
|
||||
NumberKeys;
|
||||
|
||||
/**
|
||||
* WASD to place notes on opponent's side, arrow keys to place notes on player's side.
|
||||
* WASD to place notes on opponent's side, Arrow keys to place notes on player's side.
|
||||
*/
|
||||
WASD;
|
||||
WASDKeys;
|
||||
}
|
||||
|
||||
typedef ChartEditorParams =
|
||||
|
||||
@@ -40,6 +40,8 @@ class ChangeStartingBPMCommand implements ChartEditorCommand
|
||||
state.scrollPositionInPixels = 0;
|
||||
|
||||
Conductor.instance.mapTimeChanges(state.currentSongMetadata.timeChanges);
|
||||
|
||||
state.updateGridHeight();
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
@@ -62,6 +64,8 @@ class ChangeStartingBPMCommand implements ChartEditorCommand
|
||||
state.scrollPositionInPixels = 0;
|
||||
|
||||
Conductor.instance.mapTimeChanges(state.currentSongMetadata.timeChanges);
|
||||
|
||||
state.updateGridHeight();
|
||||
}
|
||||
|
||||
public function shouldAddToHistory(state:ChartEditorState):Bool
|
||||
|
||||
@@ -34,11 +34,11 @@ class SelectItemsCommand implements ChartEditorCommand
|
||||
}
|
||||
|
||||
// If we just selected one or more events (and no notes), then we should make the event data toolbox display the event data for the selected event.
|
||||
if (this.notes.length == 0 && this.events.length >= 1)
|
||||
if (this.notes.length == 0 && this.events.length == 1)
|
||||
{
|
||||
var eventSelected = this.events[0];
|
||||
|
||||
state.eventKindToPlace = eventSelected.event;
|
||||
state.eventKindToPlace = eventSelected.eventKind;
|
||||
|
||||
// This code is here to parse event data that's not built as a struct for some reason.
|
||||
// TODO: Clean this up or get rid of it.
|
||||
@@ -46,7 +46,7 @@ class SelectItemsCommand implements ChartEditorCommand
|
||||
var defaultKey = null;
|
||||
if (eventSchema == null)
|
||||
{
|
||||
trace('[WARNING] Event schema not found for event ${eventSelected.event}.');
|
||||
trace('[WARNING] Event schema not found for event ${eventSelected.eventKind}.');
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -60,7 +60,7 @@ class SelectItemsCommand implements ChartEditorCommand
|
||||
}
|
||||
|
||||
// If we just selected one or more notes (and no events), then we should make the note data toolbox display the note data for the selected note.
|
||||
if (this.events.length == 0 && this.notes.length >= 1)
|
||||
if (this.events.length == 0 && this.notes.length == 1)
|
||||
{
|
||||
var noteSelected = this.notes[0];
|
||||
|
||||
|
||||
@@ -31,11 +31,11 @@ class SetItemSelectionCommand implements ChartEditorCommand
|
||||
state.currentEventSelection = events;
|
||||
|
||||
// If we just selected one or more events (and no notes), then we should make the event data toolbox display the event data for the selected event.
|
||||
if (this.notes.length == 0 && this.events.length >= 1)
|
||||
if (this.notes.length == 0 && this.events.length == 1)
|
||||
{
|
||||
var eventSelected = this.events[0];
|
||||
|
||||
state.eventKindToPlace = eventSelected.event;
|
||||
state.eventKindToPlace = eventSelected.eventKind;
|
||||
|
||||
// This code is here to parse event data that's not built as a struct for some reason.
|
||||
// TODO: Clean this up or get rid of it.
|
||||
@@ -43,7 +43,7 @@ class SetItemSelectionCommand implements ChartEditorCommand
|
||||
var defaultKey = null;
|
||||
if (eventSchema == null)
|
||||
{
|
||||
trace('[WARNING] Event schema not found for event ${eventSelected.event}.');
|
||||
trace('[WARNING] Event schema not found for event ${eventSelected.eventKind}.');
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -51,13 +51,18 @@ class SetItemSelectionCommand implements ChartEditorCommand
|
||||
}
|
||||
var eventData = eventSelected.valueAsStruct(defaultKey);
|
||||
|
||||
state.eventDataToPlace = eventData;
|
||||
var eventDataClone = Reflect.copy(eventData);
|
||||
|
||||
if (eventDataClone != null)
|
||||
{
|
||||
state.eventDataToPlace = eventDataClone;
|
||||
}
|
||||
|
||||
state.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT);
|
||||
}
|
||||
|
||||
// IF we just selected one or more notes (and no events), then we should make the note data toolbox display the note data for the selected note.
|
||||
if (this.events.length == 0 && this.notes.length >= 1)
|
||||
if (this.events.length == 0 && this.notes.length == 1)
|
||||
{
|
||||
var noteSelected = this.notes[0];
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ class ChartEditorEventSprite extends FlxSprite
|
||||
|
||||
public function playAnimation(?name:String):Void
|
||||
{
|
||||
if (name == null) name = eventData?.event ?? DEFAULT_EVENT;
|
||||
if (name == null) name = eventData?.eventKind ?? DEFAULT_EVENT;
|
||||
|
||||
var correctedName = correctAnimationName(name);
|
||||
this.animation.play(correctedName);
|
||||
@@ -160,12 +160,11 @@ class ChartEditorEventSprite extends FlxSprite
|
||||
else
|
||||
{
|
||||
this.visible = true;
|
||||
playAnimation(value.event);
|
||||
playAnimation(value.eventKind);
|
||||
this.eventData = value;
|
||||
// Update the position to match the note data.
|
||||
updateEventPosition();
|
||||
// Update the tooltip text.
|
||||
this.tooltip.tipData = {text: this.eventData.buildTooltip()};
|
||||
updateTooltipText();
|
||||
return this.eventData;
|
||||
}
|
||||
}
|
||||
@@ -188,6 +187,13 @@ class ChartEditorEventSprite extends FlxSprite
|
||||
this.updateTooltipPosition();
|
||||
}
|
||||
|
||||
public function updateTooltipText():Void
|
||||
{
|
||||
if (this.eventData == null) return;
|
||||
if (this.isGhost) return;
|
||||
this.tooltip.tipData = {text: this.eventData.buildTooltip()};
|
||||
}
|
||||
|
||||
public function updateTooltipPosition():Void
|
||||
{
|
||||
// No tooltip for ghost sprites.
|
||||
|
||||
@@ -58,9 +58,15 @@ class ChartEditorMeasureTicks extends FlxTypedSpriteGroup<FlxSprite>
|
||||
var measureNumberInViewport = Math.floor(viewTopPosition / ChartEditorState.GRID_SIZE / Conductor.instance.stepsPerMeasure) + 1;
|
||||
var measureNumberPosition = measureNumberInViewport * ChartEditorState.GRID_SIZE * Conductor.instance.stepsPerMeasure;
|
||||
|
||||
measureNumber.text = '${measureNumberInViewport + 1}';
|
||||
measureNumber.y = measureNumberPosition + this.y;
|
||||
|
||||
// Show the measure number only if it isn't beneath the end of the note grid.
|
||||
// Using measureNumber + 1 because the cut-off bar at the bottom is technically a bar, but it looks bad if a measure number shows up there.
|
||||
if ((measureNumberInViewport + 1) < chartEditorState.songLengthInSteps / Conductor.instance.stepsPerMeasure)
|
||||
measureNumber.text = '${measureNumberInViewport + 1}';
|
||||
else
|
||||
measureNumber.text = '';
|
||||
|
||||
// trace(measureNumber.text + ' at ' + measureNumber.y);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import funkin.audio.FunkinSound;
|
||||
import funkin.play.character.BaseCharacter.CharacterType;
|
||||
import funkin.util.FileUtil;
|
||||
import funkin.util.assets.SoundUtil;
|
||||
import funkin.util.TimerUtil;
|
||||
import funkin.audio.waveform.WaveformData;
|
||||
import funkin.audio.waveform.WaveformDataParser;
|
||||
import funkin.audio.waveform.WaveformSprite;
|
||||
@@ -128,41 +129,41 @@ class ChartEditorAudioHandler
|
||||
|
||||
public static function switchToInstrumental(state:ChartEditorState, instId:String = '', playerId:String, opponentId:String):Bool
|
||||
{
|
||||
var perfA = haxe.Timer.stamp();
|
||||
var perfA:Float = TimerUtil.start();
|
||||
|
||||
var result:Bool = playInstrumental(state, instId);
|
||||
if (!result) return false;
|
||||
|
||||
var perfB = haxe.Timer.stamp();
|
||||
var perfB:Float = TimerUtil.start();
|
||||
|
||||
stopExistingVocals(state);
|
||||
|
||||
var perfC = haxe.Timer.stamp();
|
||||
var perfC:Float = TimerUtil.start();
|
||||
|
||||
result = playVocals(state, BF, playerId, instId);
|
||||
|
||||
var perfD = haxe.Timer.stamp();
|
||||
var perfD:Float = TimerUtil.start();
|
||||
|
||||
// if (!result) return false;
|
||||
result = playVocals(state, DAD, opponentId, instId);
|
||||
// if (!result) return false;
|
||||
|
||||
var perfE = haxe.Timer.stamp();
|
||||
var perfE:Float = TimerUtil.start();
|
||||
|
||||
state.hardRefreshOffsetsToolbox();
|
||||
|
||||
var perfF = haxe.Timer.stamp();
|
||||
var perfF:Float = TimerUtil.start();
|
||||
|
||||
state.hardRefreshFreeplayToolbox();
|
||||
|
||||
var perfG = haxe.Timer.stamp();
|
||||
var perfG:Float = TimerUtil.start();
|
||||
|
||||
trace('Switched to instrumental in ${perfB - perfA} seconds.');
|
||||
trace('Stopped existing vocals in ${perfC - perfB} seconds.');
|
||||
trace('Played BF vocals in ${perfD - perfC} seconds.');
|
||||
trace('Played DAD vocals in ${perfE - perfD} seconds.');
|
||||
trace('Hard refreshed offsets toolbox in ${perfF - perfE} seconds.');
|
||||
trace('Hard refreshed freeplay toolbox in ${perfG - perfF} seconds.');
|
||||
trace('Switched to instrumental in ${TimerUtil.seconds(perfA, perfB)}.');
|
||||
trace('Stopped existing vocals in ${TimerUtil.seconds(perfB, perfC)}.');
|
||||
trace('Played BF vocals in ${TimerUtil.seconds(perfC, perfD)}.');
|
||||
trace('Played DAD vocals in ${TimerUtil.seconds(perfD, perfE)}.');
|
||||
trace('Hard refreshed offsets toolbox in ${TimerUtil.seconds(perfE, perfF)}.');
|
||||
trace('Hard refreshed freeplay toolbox in ${TimerUtil.seconds(perfF, perfG)}.');
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -174,10 +175,9 @@ class ChartEditorAudioHandler
|
||||
{
|
||||
if (instId == '') instId = 'default';
|
||||
var instTrackData:Null<Bytes> = state.audioInstTrackData.get(instId);
|
||||
var perfA = haxe.Timer.stamp();
|
||||
var perfStart:Float = TimerUtil.start();
|
||||
var instTrack:Null<FunkinSound> = SoundUtil.buildSoundFromBytes(instTrackData);
|
||||
var perfB = haxe.Timer.stamp();
|
||||
trace('Built instrumental track in ${perfB - perfA} seconds.');
|
||||
trace('Built instrumental track in ${TimerUtil.seconds(perfStart)} seconds.');
|
||||
if (instTrack == null) return false;
|
||||
|
||||
stopExistingInstrumental(state);
|
||||
@@ -205,10 +205,9 @@ class ChartEditorAudioHandler
|
||||
{
|
||||
var trackId:String = '${charId}${instId == '' ? '' : '-${instId}'}';
|
||||
var vocalTrackData:Null<Bytes> = state.audioVocalTrackData.get(trackId);
|
||||
var perfStart = haxe.Timer.stamp();
|
||||
var perfStart:Float = TimerUtil.start();
|
||||
var vocalTrack:Null<FunkinSound> = SoundUtil.buildSoundFromBytes(vocalTrackData);
|
||||
var perfEnd = haxe.Timer.stamp();
|
||||
trace('Built vocal track in ${perfEnd - perfStart} seconds.');
|
||||
trace('Built vocal track in ${TimerUtil.seconds(perfStart)}.');
|
||||
|
||||
if (state.audioVocalTrackGroup == null) state.audioVocalTrackGroup = new VoicesGroup();
|
||||
|
||||
@@ -219,10 +218,9 @@ class ChartEditorAudioHandler
|
||||
case BF:
|
||||
state.audioVocalTrackGroup.addPlayerVoice(vocalTrack);
|
||||
|
||||
var perfStart = haxe.Timer.stamp();
|
||||
var perfStart:Float = TimerUtil.start();
|
||||
var waveformData:Null<WaveformData> = vocalTrack.waveformData;
|
||||
var perfEnd = haxe.Timer.stamp();
|
||||
trace('Interpreted waveform data in ${perfEnd - perfStart} seconds.');
|
||||
trace('Interpreted waveform data in ${TimerUtil.seconds(perfStart)}.');
|
||||
|
||||
if (waveformData != null)
|
||||
{
|
||||
@@ -246,10 +244,9 @@ class ChartEditorAudioHandler
|
||||
case DAD:
|
||||
state.audioVocalTrackGroup.addOpponentVoice(vocalTrack);
|
||||
|
||||
var perfStart = haxe.Timer.stamp();
|
||||
var perfStart:Float = TimerUtil.start();
|
||||
var waveformData:Null<WaveformData> = vocalTrack.waveformData;
|
||||
var perfEnd = haxe.Timer.stamp();
|
||||
trace('Interpreted waveform data in ${perfEnd - perfStart} seconds.');
|
||||
trace('Interpreted waveform data in ${TimerUtil.seconds(perfStart)}.');
|
||||
|
||||
if (waveformData != null)
|
||||
{
|
||||
@@ -299,16 +296,14 @@ class ChartEditorAudioHandler
|
||||
*/
|
||||
public static function playSound(_state:ChartEditorState, path:String, volume:Float = 1.0):Void
|
||||
{
|
||||
var snd:FlxSound = FlxG.sound.list.recycle(FlxSound) ?? new FlxSound();
|
||||
var asset:Null<FlxSoundAsset> = FlxG.sound.cache(path);
|
||||
if (asset == null)
|
||||
{
|
||||
trace('WARN: Failed to play sound $path, asset not found.');
|
||||
return;
|
||||
}
|
||||
snd.loadEmbedded(asset);
|
||||
var snd:FunkinSound = FunkinSound.load(asset);
|
||||
snd.autoDestroy = true;
|
||||
FlxG.sound.list.add(snd);
|
||||
snd.play(true);
|
||||
snd.volume = volume;
|
||||
}
|
||||
|
||||
@@ -808,7 +808,8 @@ class ChartEditorDialogHandler
|
||||
}
|
||||
songVariationMetadataEntry.onClick = onClickMetadataVariation.bind(variation).bind(songVariationMetadataEntryLabel);
|
||||
#if FILE_DROP_SUPPORTED
|
||||
addDropHandler(songVariationMetadataEntry, onDropFileMetadataVariation.bind(variation).bind(songVariationMetadataEntryLabel));
|
||||
state.addDropHandler({component: songVariationMetadataEntry, handler: onDropFileMetadataVariation.bind(variation)
|
||||
.bind(songVariationMetadataEntryLabel)});
|
||||
#end
|
||||
chartContainerB.addComponent(songVariationMetadataEntry);
|
||||
|
||||
@@ -832,7 +833,11 @@ class ChartEditorDialogHandler
|
||||
}
|
||||
songVariationChartDataEntry.onClick = onClickChartDataVariation.bind(variation).bind(songVariationChartDataEntryLabel);
|
||||
#if FILE_DROP_SUPPORTED
|
||||
addDropHandler(songVariationChartDataEntry, onDropFileChartDataVariation.bind(variation).bind(songVariationChartDataEntryLabel));
|
||||
state.addDropHandler(
|
||||
{
|
||||
component: songVariationChartDataEntry,
|
||||
handler: onDropFileChartDataVariation.bind(variation).bind(songVariationChartDataEntryLabel)
|
||||
});
|
||||
#end
|
||||
chartContainerB.addComponent(songVariationChartDataEntry);
|
||||
}
|
||||
|
||||
@@ -299,6 +299,15 @@ class ChartEditorToolboxHandler
|
||||
state.playtestStartTime = checkboxStartTime.selected;
|
||||
};
|
||||
|
||||
var checkboxBotPlay:Null<CheckBox> = toolbox.findComponent('playtestBotPlayCheckbox', CheckBox);
|
||||
if (checkboxBotPlay == null) throw 'ChartEditorToolboxHandler.buildToolboxPlaytestPropertiesLayout() - Could not find playtestBotPlayCheckbox component.';
|
||||
|
||||
checkboxBotPlay.selected = state.playtestBotPlayMode;
|
||||
|
||||
checkboxBotPlay.onClick = _ -> {
|
||||
state.playtestBotPlayMode = checkboxBotPlay.selected;
|
||||
};
|
||||
|
||||
var checkboxDebugger:Null<CheckBox> = toolbox.findComponent('playtestDebuggerCheckbox', CheckBox);
|
||||
|
||||
if (checkboxDebugger == null) throw 'ChartEditorToolboxHandler.buildToolboxPlaytestPropertiesLayout() - Could not find playtestDebuggerCheckbox component.';
|
||||
@@ -309,6 +318,17 @@ class ChartEditorToolboxHandler
|
||||
state.enabledDebuggerPopup = checkboxDebugger.selected;
|
||||
};
|
||||
|
||||
var checkboxSongScripts:Null<CheckBox> = toolbox.findComponent('playtestSongScriptsCheckbox', CheckBox);
|
||||
|
||||
if (checkboxSongScripts == null)
|
||||
throw 'ChartEditorToolboxHandler.buildToolboxPlaytestPropertiesLayout() - Could not find playtestSongScriptsCheckbox component.';
|
||||
|
||||
state.playtestSongScripts = checkboxSongScripts.selected;
|
||||
|
||||
checkboxSongScripts.onClick = _ -> {
|
||||
state.playtestSongScripts = checkboxSongScripts.selected;
|
||||
};
|
||||
|
||||
return toolbox;
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
|
||||
// Edit the event data of any selected events.
|
||||
for (event in chartEditorState.currentEventSelection)
|
||||
{
|
||||
event.event = chartEditorState.eventKindToPlace;
|
||||
event.eventKind = chartEditorState.eventKindToPlace;
|
||||
event.value = chartEditorState.eventDataToPlace;
|
||||
}
|
||||
chartEditorState.saveDataDirty = true;
|
||||
@@ -237,6 +237,11 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
|
||||
{
|
||||
value = event.target.value.value;
|
||||
}
|
||||
else if (field.type == BOOL)
|
||||
{
|
||||
var chk:CheckBox = cast event.target;
|
||||
value = cast(chk.selected, Null<Bool>); // Need to cast to nullable bool or the compiler will get mad.
|
||||
}
|
||||
|
||||
trace('ChartEditorToolboxHandler.buildEventDataFormFromSchema() - ${event.target.id} = ${value}');
|
||||
|
||||
@@ -253,14 +258,15 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
|
||||
// Edit the event data of any existing events.
|
||||
if (!_initializing && chartEditorState.currentEventSelection.length > 0)
|
||||
{
|
||||
for (event in chartEditorState.currentEventSelection)
|
||||
for (songEvent in chartEditorState.currentEventSelection)
|
||||
{
|
||||
event.event = chartEditorState.eventKindToPlace;
|
||||
event.value = chartEditorState.eventDataToPlace;
|
||||
songEvent.eventKind = chartEditorState.eventKindToPlace;
|
||||
songEvent.value = Reflect.copy(chartEditorState.eventDataToPlace);
|
||||
}
|
||||
chartEditorState.saveDataDirty = true;
|
||||
chartEditorState.noteDisplayDirty = true;
|
||||
chartEditorState.notePreviewDirty = true;
|
||||
chartEditorState.noteTooltipsDirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
package funkin.ui.debug.charting.toolboxes;
|
||||
|
||||
import flixel.addons.display.FlxTiledSprite;
|
||||
import flixel.math.FlxMath;
|
||||
import funkin.audio.SoundGroup;
|
||||
import funkin.audio.waveform.WaveformDataParser;
|
||||
import funkin.ui.debug.charting.commands.SetFreeplayPreviewCommand;
|
||||
import funkin.ui.haxeui.components.WaveformPlayer;
|
||||
import funkin.ui.freeplay.FreeplayState;
|
||||
import funkin.util.TimerUtil;
|
||||
import haxe.ui.backend.flixel.components.SpriteWrapper;
|
||||
import haxe.ui.components.Button;
|
||||
import haxe.ui.components.HorizontalSlider;
|
||||
import haxe.ui.components.Label;
|
||||
import flixel.addons.display.FlxTiledSprite;
|
||||
import flixel.math.FlxMath;
|
||||
import haxe.ui.components.NumberStepper;
|
||||
import haxe.ui.components.Slider;
|
||||
import haxe.ui.backend.flixel.components.SpriteWrapper;
|
||||
import funkin.ui.debug.charting.commands.SetFreeplayPreviewCommand;
|
||||
import funkin.ui.haxeui.components.WaveformPlayer;
|
||||
import funkin.audio.waveform.WaveformDataParser;
|
||||
import haxe.ui.containers.VBox;
|
||||
import haxe.ui.containers.Absolute;
|
||||
import haxe.ui.containers.ScrollView;
|
||||
import funkin.ui.freeplay.FreeplayState;
|
||||
import haxe.ui.containers.Frame;
|
||||
import haxe.ui.core.Screen;
|
||||
import haxe.ui.events.DragEvent;
|
||||
@@ -288,12 +289,12 @@ class ChartEditorFreeplayToolbox extends ChartEditorBaseToolbox
|
||||
|
||||
// Build player waveform.
|
||||
// waveformMusic.waveform.forceUpdate = true;
|
||||
var perfStart = haxe.Timer.stamp();
|
||||
var waveformData1 = playerVoice.waveformData;
|
||||
var waveformData2 = opponentVoice?.waveformData ?? playerVoice.waveformData; // this null check is for songs that only have 1 vocals file!
|
||||
var perfStart:Float = TimerUtil.start();
|
||||
var waveformData1 = playerVoice?.waveformData;
|
||||
var waveformData2 = opponentVoice?.waveformData ?? playerVoice?.waveformData; // this null check is for songs that only have 1 vocals file!
|
||||
var waveformData3 = chartEditorState.audioInstTrack.waveformData;
|
||||
var waveformData = waveformData1.merge(waveformData2).merge(waveformData3);
|
||||
trace('Waveform data merging took: ${haxe.Timer.stamp() - perfStart} seconds');
|
||||
var waveformData = waveformData3.merge(waveformData1).merge(waveformData2);
|
||||
trace('Waveform data merging took: ${TimerUtil.seconds(perfStart)}');
|
||||
|
||||
waveformMusic.waveform.waveformData = waveformData;
|
||||
// Set the width and duration to render the full waveform, with the clipRect applied we only render a segment of it.
|
||||
|
||||
@@ -270,24 +270,21 @@ class ChartEditorOffsetsToolbox extends ChartEditorBaseToolbox
|
||||
|
||||
// Build player waveform.
|
||||
// waveformPlayer.waveform.forceUpdate = true;
|
||||
waveformPlayer.waveform.waveformData = playerVoice.waveformData;
|
||||
waveformPlayer.waveform.waveformData = playerVoice?.waveformData;
|
||||
// Set the width and duration to render the full waveform, with the clipRect applied we only render a segment of it.
|
||||
waveformPlayer.waveform.duration = playerVoice.length / Constants.MS_PER_SEC;
|
||||
waveformPlayer.waveform.duration = (playerVoice?.length ?? 1000.0) / Constants.MS_PER_SEC;
|
||||
|
||||
// Build opponent waveform.
|
||||
// waveformOpponent.waveform.forceUpdate = true;
|
||||
// note: if song only has one set of vocals (Vocals.ogg/mp3) then this is null and crashes charting editor
|
||||
// so we null check
|
||||
if (opponentVoice != null)
|
||||
{
|
||||
waveformOpponent.waveform.waveformData = opponentVoice.waveformData;
|
||||
waveformOpponent.waveform.duration = opponentVoice.length / Constants.MS_PER_SEC;
|
||||
}
|
||||
waveformOpponent.waveform.waveformData = opponentVoice?.waveformData;
|
||||
waveformOpponent.waveform.duration = (opponentVoice?.length ?? 1000.0) / Constants.MS_PER_SEC;
|
||||
|
||||
// Build instrumental waveform.
|
||||
// waveformInstrumental.waveform.forceUpdate = true;
|
||||
waveformInstrumental.waveform.waveformData = chartEditorState.audioInstTrack.waveformData;
|
||||
waveformInstrumental.waveform.duration = instTrack.length / Constants.MS_PER_SEC;
|
||||
waveformInstrumental.waveform.duration = (instTrack?.length ?? 1000.0) / Constants.MS_PER_SEC;
|
||||
|
||||
addOffsetsToAudioPreview();
|
||||
}
|
||||
|
||||
@@ -117,18 +117,32 @@ class ChartEditorDropdowns
|
||||
"ugh" => "Ugh (Week 7)",
|
||||
"hehPrettyGood" => "Heh, Pretty Good (Week 7)",
|
||||
// Weekend 1
|
||||
"weekend-1-lightcan" => "Light Can (2hot)",
|
||||
"weekend-1-kickcan" => "Kick Can (2hot)",
|
||||
"weekend-1-kneecan" => "Knee Can (2hot)",
|
||||
"weekend-1-cockgun" => "Cock Gun (2hot)",
|
||||
"weekend-1-firegun" => "Fire Gun (2hot)",
|
||||
"weekend-1-punchlow" => "Punch Low (Blazin)",
|
||||
"weekend-1-punchhigh" => "Punch High (Blazin)",
|
||||
"weekend-1-punchlowblocked" => "Punch Low Blocked (Blazin)",
|
||||
"weekend-1-punchhighblocked" => "Punch High Blocked (Blazin)",
|
||||
"weekend-1-dodgelow" => "Dodge Low (Blazin)",
|
||||
"weekend-1-blockhigh" => "Block High (Blazin)",
|
||||
"weekend-1-fakeout" => "Fakeout (Blazin)",
|
||||
"weekend-1-punchhigh" => "Punch High (Blazin')",
|
||||
"weekend-1-punchhighdodged" => "Punch High (Dodge) (Blazin')",
|
||||
"weekend-1-punchhighblocked" => "Punch High (Block) (Blazin')",
|
||||
"weekend-1-punchhighspin" => "Punch High (Spin) (Blazin')",
|
||||
"weekend-1-punchlow" => "Punch Low (Blazin')",
|
||||
"weekend-1-punchlowdodged" => "Punch Low (Dodge) (Blazin')",
|
||||
"weekend-1-punchlowblocked" => "Punch Low (Block) (Blazin')",
|
||||
"weekend-1-punchlowspin" => "Punch High (Spin) (Blazin')",
|
||||
"weekend-1-picouppercutprep" => "Pico Uppercut (Prep) (Blazin')",
|
||||
"weekend-1-picouppercut" => "Pico Uppercut (Blazin')",
|
||||
"weekend-1-blockhigh" => "Block High (Blazin')",
|
||||
"weekend-1-blocklow" => "Block Low (Blazin')",
|
||||
"weekend-1-blockspin" => "Block High (Spin) (Blazin')",
|
||||
"weekend-1-dodgehigh" => "Dodge High (Blazin')",
|
||||
"weekend-1-dodgelow" => "Dodge Low (Blazin')",
|
||||
"weekend-1-dodgespin" => "Dodge High (Spin) (Blazin')",
|
||||
"weekend-1-hithigh" => "Hit High (Blazin')",
|
||||
"weekend-1-hitlow" => "Hit Low (Blazin')",
|
||||
"weekend-1-hitspin" => "Hit High (Spin) (Blazin')",
|
||||
"weekend-1-darnelluppercutprep" => "Darnell Uppercut (Prep) (Blazin')",
|
||||
"weekend-1-darnelluppercut" => "Darnell Uppercut (Blazin')",
|
||||
"weekend-1-idle" => "Idle (Blazin')",
|
||||
"weekend-1-fakeout" => "Fakeout (Blazin')",
|
||||
"weekend-1-taunt" => "Taunt (If Fakeout) (Blazin')",
|
||||
"weekend-1-tauntforce" => "Taunt (Forced) (Blazin')",
|
||||
"weekend-1-reversefakeout" => "Fakeout (Reverse) (Blazin')",
|
||||
];
|
||||
|
||||
public static function populateDropdownWithNoteKinds(dropDown:DropDown, startingKindId:String):DropDownEntry
|
||||
@@ -147,6 +161,8 @@ class ChartEditorDropdowns
|
||||
dropDown.dataSource.add(value);
|
||||
}
|
||||
|
||||
dropDown.dataSource.sort('id', ASCENDING);
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import funkin.data.dialogue.DialogueBoxData;
|
||||
import funkin.data.dialogue.DialogueBoxRegistry;
|
||||
import funkin.data.dialogue.SpeakerData;
|
||||
import funkin.data.dialogue.SpeakerRegistry;
|
||||
import funkin.data.freeplay.AlbumRegistry;
|
||||
import funkin.play.cutscene.dialogue.Conversation;
|
||||
import funkin.play.cutscene.dialogue.DialogueBox;
|
||||
import funkin.play.cutscene.dialogue.Speaker;
|
||||
@@ -36,11 +37,24 @@ class ConversationDebugState extends MusicBeatState
|
||||
|
||||
public override function create():Void
|
||||
{
|
||||
conversation = ConversationRegistry.instance.fetchEntry(conversationId);
|
||||
conversation.completeCallback = onConversationComplete;
|
||||
add(conversation);
|
||||
super.create();
|
||||
startConversation();
|
||||
}
|
||||
|
||||
ScriptEventDispatcher.callEvent(conversation, new ScriptEvent(CREATE, false));
|
||||
function startConversation():Void
|
||||
{
|
||||
if (conversation != null) return;
|
||||
|
||||
conversation = ConversationRegistry.instance.fetchEntry(conversationId);
|
||||
if (conversation == null) return;
|
||||
if (!conversation.alive) conversation.revive();
|
||||
|
||||
conversation.zIndex = 1000;
|
||||
add(conversation);
|
||||
refresh();
|
||||
|
||||
var event:ScriptEvent = new ScriptEvent(CREATE, false);
|
||||
ScriptEventDispatcher.callEvent(conversation, event);
|
||||
}
|
||||
|
||||
function onConversationComplete():Void
|
||||
@@ -61,15 +75,17 @@ class ConversationDebugState extends MusicBeatState
|
||||
|
||||
if (conversation != null)
|
||||
{
|
||||
if (controls.CUTSCENE_ADVANCE) conversation.advanceConversation();
|
||||
|
||||
if (controls.CUTSCENE_SKIP)
|
||||
if (controls.CUTSCENE_ADVANCE)
|
||||
{
|
||||
conversation.trySkipConversation(elapsed);
|
||||
conversation.advanceConversation();
|
||||
}
|
||||
else
|
||||
else if (controls.PAUSE)
|
||||
{
|
||||
conversation.trySkipConversation(-1);
|
||||
conversation.kill();
|
||||
remove(conversation);
|
||||
conversation = null;
|
||||
|
||||
FlxG.switchState(() -> new ConversationDebugState());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,8 +49,11 @@ class StageOffsetSubState extends HaxeUISubState
|
||||
{
|
||||
super.create();
|
||||
|
||||
var playState = PlayState.instance;
|
||||
|
||||
FlxG.mouse.visible = true;
|
||||
PlayState.instance.pauseMusic();
|
||||
playState.pauseMusic();
|
||||
playState.cancelAllCameraTweens();
|
||||
FlxG.camera.target = null;
|
||||
|
||||
setupUIListeners();
|
||||
@@ -63,8 +66,8 @@ class StageOffsetSubState extends HaxeUISubState
|
||||
|
||||
// add(uiStuff);
|
||||
|
||||
PlayState.instance.persistentUpdate = true;
|
||||
component.cameras = [PlayState.instance.camHUD];
|
||||
playState.persistentUpdate = true;
|
||||
component.cameras = [playState.camHUD];
|
||||
// uiStuff.cameras = [PlayState.instance.camHUD];
|
||||
// btn.cameras = [PlayState.instance.camHUD];
|
||||
|
||||
@@ -72,7 +75,7 @@ class StageOffsetSubState extends HaxeUISubState
|
||||
|
||||
var layerList:ListView = findComponent("prop-layers");
|
||||
|
||||
for (thing in PlayState.instance.currentStage)
|
||||
for (thing in playState.currentStage)
|
||||
{
|
||||
var prop:StageProp = cast thing;
|
||||
if (prop != null && prop.name != null)
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
package funkin.ui.freeplay;
|
||||
|
||||
import funkin.data.freeplay.AlbumData;
|
||||
import funkin.data.freeplay.AlbumRegistry;
|
||||
import funkin.data.IRegistryEntry;
|
||||
import flixel.graphics.FlxGraphic;
|
||||
|
||||
/**
|
||||
* A class representing the data for an album as displayed in Freeplay.
|
||||
*/
|
||||
class Album implements IRegistryEntry<AlbumData>
|
||||
{
|
||||
/**
|
||||
* The internal ID for this album.
|
||||
*/
|
||||
public final id:String;
|
||||
|
||||
/**
|
||||
* The full data for an album.
|
||||
*/
|
||||
public final _data:AlbumData;
|
||||
|
||||
public function new(id:String)
|
||||
{
|
||||
this.id = id;
|
||||
this._data = _fetchData(id);
|
||||
|
||||
if (_data == null)
|
||||
{
|
||||
throw 'Could not parse album data for id: $id';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the album.
|
||||
* @
|
||||
*/
|
||||
public function getAlbumName():String
|
||||
{
|
||||
return _data.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the artists of the album.
|
||||
* @return The list of artists
|
||||
*/
|
||||
public function getAlbumArtists():Array<String>
|
||||
{
|
||||
return _data.artists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the asset key for the album art.
|
||||
* @return The asset key
|
||||
*/
|
||||
public function getAlbumArtAssetKey():String
|
||||
{
|
||||
return _data.albumArtAsset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the album art as a graphic, ready to apply to a sprite.
|
||||
* @return The built graphic
|
||||
*/
|
||||
public function getAlbumArtGraphic():FlxGraphic
|
||||
{
|
||||
return FlxG.bitmap.add(Paths.image(getAlbumArtAssetKey()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the asset key for the album title.
|
||||
*/
|
||||
public function getAlbumTitleAssetKey():String
|
||||
{
|
||||
return _data.albumTitleAsset;
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
return 'Album($id)';
|
||||
}
|
||||
|
||||
public function destroy():Void {}
|
||||
|
||||
static function _fetchData(id:String):Null<AlbumData>
|
||||
{
|
||||
return AlbumRegistry.instance.parseEntryDataWithMigration(id, AlbumRegistry.instance.fetchEntryVersion(id));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
package funkin.ui.freeplay;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
import flixel.util.FlxSort;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.util.FlxTimer;
|
||||
import flixel.tweens.FlxEase;
|
||||
import funkin.data.freeplay.AlbumRegistry;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.util.SortUtil;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
/**
|
||||
* The graphic for the album roll in the FreeplayState.
|
||||
* Simply set `albumID` to fetch the required data and update the textures.
|
||||
*/
|
||||
class AlbumRoll extends FlxSpriteGroup
|
||||
{
|
||||
/**
|
||||
* The ID of the album to display.
|
||||
* Modify this value to automatically update the album art and title.
|
||||
*/
|
||||
public var albumId(default, set):String;
|
||||
|
||||
function set_albumId(value:String):String
|
||||
{
|
||||
if (this.albumId != value)
|
||||
{
|
||||
this.albumId = value;
|
||||
updateAlbum();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
var albumArt:FunkinSprite;
|
||||
var albumTitle:FunkinSprite;
|
||||
var difficultyStars:DifficultyStars;
|
||||
|
||||
var _exitMovers:Null<FreeplayState.ExitMoverData>;
|
||||
|
||||
var albumData:Album;
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
|
||||
albumTitle = new FunkinSprite(947, 491);
|
||||
albumTitle.visible = true;
|
||||
albumTitle.zIndex = 200;
|
||||
add(albumTitle);
|
||||
|
||||
difficultyStars = new DifficultyStars(140, 39);
|
||||
|
||||
difficultyStars.stars.visible = true;
|
||||
albumTitle.visible = false;
|
||||
// albumArtist.visible = false;
|
||||
|
||||
// var albumArtist:FlxSprite = new FlxSprite(1010, 607).loadGraphic(Paths.image('freeplay/albumArtist-kawaisprite'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the album data by ID and update the textures.
|
||||
*/
|
||||
function updateAlbum():Void
|
||||
{
|
||||
albumData = AlbumRegistry.instance.fetchEntry(albumId);
|
||||
|
||||
if (albumData == null)
|
||||
{
|
||||
FlxG.log.warn('Could not find album data for album ID: ${albumId}');
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
if (albumArt != null)
|
||||
{
|
||||
FlxTween.cancelTweensOf(albumArt);
|
||||
albumArt.visible = false;
|
||||
albumArt.destroy();
|
||||
remove(albumArt);
|
||||
}
|
||||
|
||||
// Paths.animateAtlas('freeplay/albumRoll'),
|
||||
albumArt = FunkinSprite.create(1500, 360, albumData.getAlbumArtAssetKey());
|
||||
albumArt.setGraphicSize(262, 262); // Magic number for size IG
|
||||
albumArt.zIndex = 100;
|
||||
|
||||
// playIntro();
|
||||
add(albumArt);
|
||||
|
||||
applyExitMovers();
|
||||
|
||||
if (Assets.exists(Paths.image(albumData.getAlbumTitleAssetKey())))
|
||||
{
|
||||
albumTitle.loadGraphic(Paths.image(albumData.getAlbumTitleAssetKey()));
|
||||
}
|
||||
else
|
||||
{
|
||||
albumTitle.visible = false;
|
||||
}
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
public function refresh():Void
|
||||
{
|
||||
sort(SortUtil.byZIndex, FlxSort.ASCENDING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply exit movers for the album roll.
|
||||
* @param exitMovers The exit movers to apply.
|
||||
*/
|
||||
public function applyExitMovers(?exitMovers:FreeplayState.ExitMoverData):Void
|
||||
{
|
||||
if (exitMovers == null)
|
||||
{
|
||||
exitMovers = _exitMovers;
|
||||
}
|
||||
else
|
||||
{
|
||||
_exitMovers = exitMovers;
|
||||
}
|
||||
|
||||
if (exitMovers == null) return;
|
||||
|
||||
exitMovers.set([albumArt],
|
||||
{
|
||||
x: FlxG.width,
|
||||
speed: 0.4,
|
||||
wait: 0
|
||||
});
|
||||
exitMovers.set([albumTitle],
|
||||
{
|
||||
x: FlxG.width,
|
||||
speed: 0.2,
|
||||
wait: 0.1
|
||||
});
|
||||
|
||||
/*
|
||||
exitMovers.set([albumArtist],
|
||||
{
|
||||
x: FlxG.width * 1.1,
|
||||
speed: 0.2,
|
||||
wait: 0.2
|
||||
});
|
||||
*/
|
||||
exitMovers.set([difficultyStars],
|
||||
{
|
||||
x: FlxG.width * 1.2,
|
||||
speed: 0.2,
|
||||
wait: 0.3
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Play the intro animation on the album art.
|
||||
*/
|
||||
public function playIntro():Void
|
||||
{
|
||||
albumArt.visible = true;
|
||||
FlxTween.tween(albumArt, {x: 950, y: 320, angle: -340}, 0.5, {ease: FlxEase.elasticOut});
|
||||
|
||||
albumTitle.visible = false;
|
||||
new FlxTimer().start(0.75, function(_) {
|
||||
showTitle();
|
||||
});
|
||||
}
|
||||
|
||||
public function setDifficultyStars(?difficulty:Int):Void
|
||||
{
|
||||
if (difficulty == null) return;
|
||||
|
||||
difficultyStars.difficulty = difficulty;
|
||||
}
|
||||
|
||||
public function showTitle():Void
|
||||
{
|
||||
albumTitle.visible = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the album stars visible.
|
||||
*/
|
||||
public function showStars():Void
|
||||
{
|
||||
// albumArtist.visible = false;
|
||||
difficultyStars.stars.visible = false;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import flixel.FlxSprite;
|
||||
import flixel.util.FlxSignal;
|
||||
import funkin.util.assets.FlxAnimationUtil;
|
||||
import funkin.graphics.adobeanimate.FlxAtlasSprite;
|
||||
import flixel.system.FlxSound;
|
||||
import flixel.sound.FlxSound;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.audio.FlxStreamSound;
|
||||
|
||||
@@ -156,8 +156,6 @@ class DJBoyfriend extends FlxAtlasSprite
|
||||
|
||||
function setupAnimations():Void
|
||||
{
|
||||
// frames = FlxAnimationUtil.combineFramesCollections(Paths.getSparrowAtlas('freeplay/bfFreeplay'), Paths.getSparrowAtlas('freeplay/bf-freeplay-afk'));
|
||||
|
||||
// animation.addByPrefix('intro', "boyfriend dj intro", 24, false);
|
||||
addOffset('boyfriend dj intro', 8, 3);
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ class FreeplayFlames extends FlxSpriteGroup
|
||||
{
|
||||
var flame:FlxSprite = new FlxSprite(flameX + (flameSpreadX * i), flameY + (flameSpreadY * i));
|
||||
flame.frames = Paths.getSparrowAtlas("freeplay/freeplayFlame");
|
||||
flame.animation.addByPrefix("flame", "fire loop", FlxG.random.int(23, 25), false);
|
||||
flame.animation.addByPrefix("flame", "fire loop full instance 1", FlxG.random.int(23, 25), false);
|
||||
flame.animation.play("flame");
|
||||
flame.visible = false;
|
||||
flameCount = 0;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user