diff --git a/.github/actions/setup-haxe/action.yml b/.github/actions/setup-haxe/action.yml
new file mode 100644
index 000000000..5a9f7b293
--- /dev/null
+++ b/.github/actions/setup-haxe/action.yml
@@ -0,0 +1,144 @@
+name: setup-haxeshit
+description: "sets up haxe shit, using HMM!"
+
+inputs:
+ haxe:
+ description: 'Version of haxe to install'
+ required: true
+ default: '4.3.4'
+ hxcpp-cache:
+ description: 'Whether to use a shared hxcpp compile cache'
+ required: true
+ default: 'true'
+ hxcpp-cache-path:
+ description: 'Path to create hxcpp cache in'
+ required: true
+ default: ${{ runner.temp }}/hxcpp_cache
+ targets:
+ description: 'Targets we plan to compile to. Installs native dependencies needed.'
+ required: true
+ gh-token:
+ description: 'GitHub secret for private repos as dependencies'
+
+runs:
+ using: "composite"
+ steps:
+
+ - name: Setup timers
+ shell: bash
+ run: |
+ echo "TIMER_HAXE=$(date +%s)" >> "$GITHUB_ENV"
+
+ - name: Install Haxe
+ uses: funkincrew/ci-haxe@v3.1.0
+ with:
+ haxe-version: ${{ inputs.haxe }}
+
+ - name: Install native dependencies
+ if: ${{ runner.os == 'Linux' }}
+ shell: bash
+ run: |
+ ls -lah /usr/lib/x86_64-linux-gnu/
+ apt-get update
+ apt-get install -y \
+ g++ \
+ libx11-dev libxi-dev libxext-dev libxinerama-dev libxrandr-dev \
+ libgl-dev libgl1-mesa-dev \
+ libasound2-dev
+ ln -s /usr/lib/x86_64-linux-gnu/libffi.so.8 /usr/lib/x86_64-linux-gnu/libffi.so.6 || true
+ - name: Install linux-specific dependencies
+ if: ${{ runner.os == 'Linux' && contains(inputs.targets, 'linux') }}
+ shell: bash
+ run: |
+ apt-get install -y libvlc-dev libvlccore-dev
+
+ - name: Config haxelib
+ shell: bash
+ run: |
+ echo "TIMER_HAXELIB=$(date +%s)" >> "$GITHUB_ENV"
+ haxelib --debug --never install haxelib 4.1.0 --global
+ haxelib --debug --never deleterepo || true
+ haxelib --debug --never newrepo
+ echo "HAXEPATH=$(haxelib config)" >> "$GITHUB_ENV"
+ haxelib --debug --never git haxelib https://github.com/HaxeFoundation/haxelib.git master
+ haxelib --debug --global install hmm
+ echo "TIMER_DEPS=$(date +%s)" >> "$GITHUB_ENV"
+
+ - name: Restore cached dependencies
+ id: cache-hmm
+ uses: actions/cache@v4
+ with:
+ path: .haxelib
+ key: haxe-hmm-${{ runner.os }}-${{ hashFiles('**/hmm.json') }}
+
+ - if: ${{ steps.cache-hmm.outputs.cache-hit != 'true' }}
+ name: Prep git for dependency install
+ uses: gacts/run-and-post-run@v1
+ with:
+ run: git config --global 'url.https://x-access-token:${{ inputs.gh-token }}@github.com/.insteadOf' https://github.com/
+ post: git config --global --unset 'url.https://x-access-token:${{ inputs.gh-token }}@github.com/.insteadOf'
+
+ - if: ${{ steps.cache-hmm.outputs.cache-hit != 'true' }}
+ name: Install dependencies
+ shell: bash
+ run: |
+ haxelib --global run hmm install -q
+ echo "TIMER_DONE=$(date +%s)" >> "$GITHUB_ENV"
+
+ # by default use a shared hxcpp cache
+ - if: ${{ inputs.hxcpp-cache == 'true' }}
+ name: Restore hxcpp cache
+ uses: actions/cache@v4
+ with:
+ path: ${{ inputs.hxcpp-cache-path }}
+ key: haxe-hxcpp-${{ runner.os }}-${{ github.ref_name }}-${{ github.sha }}
+ restore-keys: haxe-hxcpp-${{ runner.os }}-${{ github.ref_name }}
+ # export env for it to reuse in builds
+ - if: ${{ inputs.hxcpp-cache == 'true' }}
+ name: Persist env for hxcpp cache
+ shell: bash
+ run: |
+ echo "HXCPP_COMPILE_CACHE=${{ inputs.hxcpp-cache-path }}" >> "$GITHUB_ENV"
+ echo 'HXCPP_CACHE_MB="4096"' >> "$GITHUB_ENV"
+
+ # if it's explicitly disabled, still cache export/ since that then contains the builds
+ - if: ${{ inputs.hxcpp-cache != 'true' }}
+ name: Restore export cache
+ uses: actions/cache@v4
+ with:
+ path: ${{ inputs.hxcpp-cache-path }}
+ key: haxe-export-${{ runner.os }}-${{ github.ref_name }}-${{ github.sha }}
+ restore-keys: haxe-export-${{ runner.os }}-${{ github.ref_name }}
+
+ - name: Print debug info
+ shell: bash
+ run: |
+ cat << EOF
+ runner:
+ kernel: $(uname -a)
+ haxe:
+ version: $(haxe -version)
+ which: $(which haxe)
+ haxepath: $HAXEPATH
+ took: $((TIMER_HAXELIB - TIMER_HAXE))s
+ haxelib:
+ version: $(haxelib version)
+ which: $(which haxelib)
+ 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:
+ config: $(haxelib --system config)
+ global:
+ config: $(haxelib --system config --global)
+ took: $((TIMER_DEPS - TIMER_HAXELIB))s
+ deps:
+ took: $((TIMER_DONE - TIMER_DEPS))s
+ hxcpp_cache: |
+ $(haxelib run hxcpp cache list || true)
+ EOF
diff --git a/.github/actions/setup-haxeshit/action.yml b/.github/actions/setup-haxeshit/action.yml
deleted file mode 100644
index 236d29944..000000000
--- a/.github/actions/setup-haxeshit/action.yml
+++ /dev/null
@@ -1,55 +0,0 @@
-name: setup-haxeshit
-description: "sets up haxe shit, using HMM!"
-runs:
- using: "composite"
- steps:
- - name: Install Haxe lol
- uses: funkincrew/ci-haxe@v3.1.0
- with:
- haxe-version: 4.3.3
- - name: Config haxelib
- run: |
- 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: 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: |
- haxelib --global install hmm
- shell: bash
- - name: Restore cached dependencies
- id: cache-hmm
- uses: actions/cache@v4
- with:
- path: .haxelib
- key: ${{ runner.os }}-hmm-${{ hashFiles('**/hmm.json') }}
- - if: ${{ steps.cache-hmm.outputs.cache-hit != 'true' }}
- name: hmm install
- run: |
- haxelib --global run hmm install
- shell: bash
diff --git a/.github/actions/upload-itch/action.yml b/.github/actions/upload-itch/action.yml
index 2f7d3027d..fb049efc9 100644
--- a/.github/actions/upload-itch/action.yml
+++ b/.github/actions/upload-itch/action.yml
@@ -1,44 +1,124 @@
name: upload-itch
description: "installs Butler, and uploads to itch.io!"
+
inputs:
butler-key:
description: "Butler API secret key"
required: true
+ itch-repo:
+ description: "Where to upload the game to"
+ required: true
+ default: "ninja-muffin24/funkin-secret"
build-dir:
description: "Directory of the game build"
- required: true
+ required: false
target:
- description: "Target (html5, win, linux, mac)"
+ description: "Target (html5, windows, linux, macos)"
required: true
+
runs:
using: "composite"
steps:
- - name: Install butler Windows
- if: runner.os == 'Windows'
- run: |
- curl -L -o butler.zip https://broth.itch.ovh/butler/windows-amd64/LATEST/archive/default
- 7z x butler.zip
- ./butler -v
- shell: bash
- - name: Install butler Mac
- if: runner.os == 'macOS'
- run: |
- curl -L -o butler.zip https://broth.itch.ovh/butler/darwin-amd64/LATEST/archive/default
- unzip butler.zip
- ./butler -V
- shell: bash
- - name: Install butler Linux
- if: runner.os == 'Linux'
- run: |
- curl -L -o butler.zip https://broth.itch.ovh/butler/linux-amd64/LATEST/archive/default
- unzip butler.zip
- chmod +x butler
- ./butler -V
- shell: bash
- - name: Upload game to itch.io
- env:
- BUTLER_API_KEY: ${{inputs.butler-key}}
- run: |
- ./butler login
- ./butler push ${{inputs.build-dir}} ninja-muffin24/funkin-secret:${{inputs.target}}-${GITHUB_REF_NAME}
- shell: bash
+
+ # RUNNER_OS = Windows | macOS | Linux
+ # TARGET_BUILD = windows | macos | linux
+ # TARGET_ITCH = win | macos | linux
+ # TARGET_BUTLER_DOWNLOAD = windows-amd64 | darwin-amd64 | linux-amd64
+ - name: Setup variables
+ shell: bash
+ run: |
+ TARGET_OS=${{ inputs.target }}
+ RUNNER=${RUNNER_OS@L}
+ TARGET=${TARGET_OS@L}
+ case "$TARGET" in
+ "windows")
+ TARGET_ITCH=win
+ ;;
+ *)
+ TARGET_ITCH=$TARGET
+ ;;
+ esac
+ case "$RUNNER" in
+ "macos")
+ OS_NODE=darwin
+ ;;
+ *)
+ OS_NODE=$RUNNER
+ ;;
+ esac
+ BUTLER_PATH=$RUNNER_TEMP/butler
+
+ echo BUILD_DIR="export/release/$TARGET/bin" >> "$GITHUB_ENV"
+ echo ITCH_TAG=${{ inputs.itch-repo }}:$TARGET_ITCH-$GITHUB_REF_NAME >> "$GITHUB_ENV"
+ echo OS_AND_ARCH=$OS_NODE-amd64 >> "$GITHUB_ENV"
+ echo BUTLER_API_KEY=${{ inputs.butler-key }} >> "$GITHUB_ENV"
+ echo BUTLER_INSTALL_PATH=$BUTLER_PATH >> "$GITHUB_ENV"
+ echo TIMER_BUTLER=$(date +%s) >> "$GITHUB_ENV"
+ echo TARGET_ITCH=$TARGET_ITCH >> "$GITHUB_ENV"
+
+ echo "$BUTLER_PATH" >> "$GITHUB_PATH"
+
+ - name: Get latest butler version
+ shell: bash
+ run: |
+ LATEST=$(curl -sfL https://broth.itch.ovh/butler/$OS_AND_ARCH/LATEST)
+ echo BUTLER_LATEST=$LATEST >> "$GITHUB_ENV"
+
+ command -v butler \
+ && echo BUTLER_CURRENT=$(butler -V 2>&1 | cut -d ',' -f 1) >> "$GITHUB_ENV" \
+ || echo BUTLER_CURRENT=none >> "$GITHUB_ENV"
+
+ - name: Try to get butler from cache
+ id: cache-butler
+ uses: actions/cache@v4
+ with:
+ path: ${{ env.BUTLER_INSTALL_PATH }}
+ key: butler-${{ runner.os }}-${{ env.BUTLER_LATEST }}
+
+ - if: steps.cache-butler.outputs.cache-hit == 'true'
+ name: Make sure butler is executable
+ shell: bash
+ run: |
+ chmod +x $BUTLER_INSTALL_PATH/butler
+
+ - if: steps.cache-butler.outputs.cache-hit != 'true'
+ name: Install butler
+ shell: bash
+ run: |
+ mkdir -p $BUTLER_INSTALL_PATH
+ cd $BUTLER_INSTALL_PATH
+
+ curl -L -o butler.zip https://broth.itch.ovh/butler/$OS_AND_ARCH/LATEST/archive/default
+ unzip butler.zip
+ chmod +x butler
+
+ - name: Upload game to itch.io
+ shell: bash
+ run: |
+ echo "TIMER_UPLOAD=$(date +%s)" >> "$GITHUB_ENV"
+ butler login
+ butler push $BUILD_DIR $ITCH_TAG
+ echo "TIMER_DONE=$(date +%s)" >> "$GITHUB_ENV"
+
+ - name: Print debug info
+ shell: bash
+ run: |
+ cat << EOF
+ butler:
+ version: $(
+ if [[ "$BUTLER_CURRENT" == "$BUTLER_LATEST" ]]
+ then
+ echo $BUTLER_CURRENT
+ else
+ echo $BUTLER_CURRENT -> $BUTLER_LATEST
+ fi
+ )
+ install:
+ took: $(($TIMER_UPLOAD-$TIMER_BUTLER))s
+ upload:
+ tag: $TARGET_ITCH/$GITHUB_REF_NAME
+ took: $(($TIMER_DONE-$TIMER_UPLOAD))s
+ EOF
+ cat << EOF >> "$GITHUB_STEP_SUMMARY"
+ ### open in launcher: [$TARGET_ITCH/$GITHUB_REF_NAME](https://run.funkin.me/$TARGET_ITCH/$GITHUB_REF_NAME)
+ EOF
diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml
new file mode 100644
index 000000000..6fbc9677e
--- /dev/null
+++ b/.github/workflows/build-docker-image.yml
@@ -0,0 +1,53 @@
+name: Create and publish Docker image
+
+on:
+ workflow_dispatch:
+ push:
+ paths:
+ - '**/Dockerfile'
+ - '.github/workflows/build-docker-image.yml'
+
+jobs:
+ build-and-push-image:
+ runs-on: build-set
+ permissions:
+ contents: read
+ packages: write
+
+ steps:
+ - 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
+ uses: funkincrew/ci-checkout@v6
+ with:
+ submodules: false
+ token: ${{ steps.app_token.outputs.token }}
+
+ - name: Log into GitHub Container Registry
+ uses: docker/login-action@v3.1.0
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@v5.3.0
+ with:
+ context: ./build
+ push: true
+ tags: |
+ ghcr.io/funkincrew/build-dependencies:latest
+ ghcr.io/funkincrew/build-dependencies:${{ github.sha }}
+ labels: |
+ org.opencontainers.image.description=precooked haxe build-dependencies
+ org.opencontainers.image.revision=${{ github.sha }}
+ org.opencontainers.image.source=https://github.com/${{ github.repository }}
+ org.opencontainers.image.title=${{ github.repository_owner }}/build-dependencies
+ org.opencontainers.image.url=https://github.com/${{ github.repository }}
+ org.opencontainers.image.version=${{ github.sha }}
diff --git a/.github/workflows/build-game.yml b/.github/workflows/build-game.yml
new file mode 100644
index 000000000..07802557c
--- /dev/null
+++ b/.github/workflows/build-game.yml
@@ -0,0 +1,129 @@
+name: Build and Upload nightly game builds
+
+on:
+ workflow_dispatch:
+ push:
+ paths-ignore:
+ - '**/Dockerfile'
+ - '.github/workflows/build-docker-image.yml'
+
+jobs:
+
+ build-game-on-host:
+ strategy:
+ matrix:
+ include:
+ - target: windows
+ - target: macos
+ runs-on:
+ - ${{ matrix.target }}
+ defaults:
+ run:
+ shell: bash
+
+ steps:
+ - name: Make git happy
+ run: |
+ git config --global --replace-all 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
+ uses: funkincrew/ci-checkout@v6
+ with:
+ submodules: 'recursive'
+ token: ${{ steps.app_token.outputs.token }}
+ persist-credentials: false
+
+ - name: Setup build environment
+ uses: ./.github/actions/setup-haxe
+ with:
+ gh-token: ${{ steps.app_token.outputs.token }}
+
+ - name: Build game
+ if: ${{ matrix.target == 'windows' }}
+ run: |
+ haxelib run lime build windows -v -release -DGITHUB_BUILD
+ timeout-minutes: 120
+ - name: Build game
+ if: ${{ matrix.target != 'windows' }}
+ run: |
+ haxelib run lime build ${{ matrix.target }} -v -release --times -DGITHUB_BUILD
+ timeout-minutes: 120
+
+ - name: Upload build artifacts
+ uses: ./.github/actions/upload-itch
+ with:
+ butler-key: ${{ secrets.BUTLER_API_KEY}}
+ target: ${{ matrix.target }}
+
+ build-game-in-container:
+ runs-on: build-set
+ container: ghcr.io/funkincrew/build-dependencies:latest
+ strategy:
+ matrix:
+ include:
+ - target: linux
+ - target: html5
+ defaults:
+ run:
+ shell: bash
+
+ steps:
+ - 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
+ uses: funkincrew/ci-checkout@v6
+ with:
+ submodules: 'recursive'
+ token: ${{ steps.app_token.outputs.token }}
+ persist-credentials: false
+
+ - name: Config haxelib
+ run: |
+ haxelib --never newrepo
+ echo "HAXEPATH=$(haxelib config)" >> "$GITHUB_ENV"
+
+ - name: Restore cached dependencies
+ id: cache-hmm
+ uses: actions/cache@v4
+ with:
+ path: .haxelib
+ key: haxe-hmm-${{ runner.os }}-${{ hashFiles('**/hmm.json') }}
+
+ - if: ${{ steps.cache-hmm.outputs.cache-hit != 'true' }}
+ name: Install dependencies
+ run: |
+ git config --global 'url.https://x-access-token:${{ steps.app_token.outputs.token }}@github.com/.insteadOf' https://github.com/
+ haxelib --global run hmm install -q
+
+ - if: ${{ matrix.target != 'html5' }}
+ name: Restore hxcpp cache
+ uses: actions/cache@v4
+ with:
+ path: /usr/share/hxcpp
+ key: haxe-hxcpp-${{ runner.os }}-${{ github.ref_name }}-${{ github.sha }}
+ restore-keys: haxe-hxcpp-${{ runner.os }}-${{ github.ref_name }}
+
+ - name: Build game
+ run: |
+ haxelib run lime build ${{ matrix.target }} -v -release --times -DGITHUB_BUILD
+ timeout-minutes: 120
+
+ - name: Upload build artifacts
+ uses: ./.github/actions/upload-itch
+ with:
+ butler-key: ${{ secrets.BUTLER_API_KEY}}
+ target: ${{ matrix.target }}
diff --git a/.github/workflows/build-shit.yml b/.github/workflows/build-shit.yml
deleted file mode 100644
index 4d674f025..000000000
--- a/.github/workflows/build-shit.yml
+++ /dev/null
@@ -1,136 +0,0 @@
-name: build-upload
-on:
- workflow_dispatch:
- push:
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: true
-
-jobs:
- create-nightly-html5:
- runs-on: [self-hosted, linux]
- container: ubuntu:23.10
- steps:
- - name: Install tools missing in container
- run: |
- apt update
- apt install -y sudo git curl unzip
- - name: Fix git config on posix runner
- # this can't be {{ github.workspace }} because that's not docker-aware
- 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
- uses: funkincrew/ci-checkout@v6
- with:
- submodules: 'recursive'
- token: ${{ steps.app_token.outputs.token }}
- - name: Install Haxe, dependencies
- uses: ./.github/actions/setup-haxeshit
- - name: Install native dependencies
- run: |
- 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
- - 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: [self-hosted, windows]
- defaults:
- run:
- shell: bash
- steps:
- - 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
- uses: funkincrew/ci-checkout@v6
- with:
- submodules: 'recursive'
- token: ${{ steps.app_token.outputs.token }}
- - name: Install Haxe, dependencies
- uses: ./.github/actions/setup-haxeshit
- - name: Setup build cache
- run: |
- mkdir -p ${{ runner.temp }}/hxcpp_cache
- - name: Restore build cache
- id: cache-build-win
- uses: actions/cache@v4
- with:
- path: |
- export
- ${{ runner.temp }}/hxcpp_cache
- key: ${{ runner.os }}-build-win-${{ github.ref_name }}
- - name: Build game
- run: |
- haxelib run lime build windows -v -release -DGITHUB_BUILD
- env:
- HXCPP_COMPILE_CACHE: "${{ runner.temp }}\\hxcpp_cache"
- - name: Upload build artifacts
- uses: ./.github/actions/upload-itch
- with:
- butler-key: ${{ secrets.BUTLER_API_KEY }}
- build-dir: export/release/windows/bin
- target: win
- create-nightly-mac:
- runs-on: [self-hosted, macos]
- steps:
- - name: Fix git config on posix runner
- # this can't be {{ github.workspace }} because that's not docker-aware
- 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
- uses: funkincrew/ci-checkout@v6
- with:
- submodules: 'recursive'
- token: ${{ steps.app_token.outputs.token }}
- - name: Install Haxe, dependencies
- uses: ./.github/actions/setup-haxeshit
- - name: Setup build cache
- run: |
- mkdir -p ${{ runner.temp }}/hxcpp_cache
- - name: Restore build cache
- id: cache-build-win
- uses: actions/cache@v4
- with:
- path: |
- export
- ${{ runner.temp }}/hxcpp_cache
- key: ${{ runner.os }}-build-mac-${{ github.ref_name }}
- - name: Build game
- run: |
- haxelib run lime build macos -release --times -DGITHUB_BUILD
- env:
- HXCPP_COMPILE_CACHE: "${{ runner.temp }}/hxcpp_cache"
- - name: Upload build artifacts
- uses: ./.github/actions/upload-itch
- with:
- butler-key: ${{ secrets.BUTLER_API_KEY}}
- build-dir: export/release/macos/bin
- target: macos
diff --git a/.github/workflows/cancel-merged-branches.yml b/.github/workflows/cancel-merged-branches.yml
index 84e3bedc9..254b21a24 100644
--- a/.github/workflows/cancel-merged-branches.yml
+++ b/.github/workflows/cancel-merged-branches.yml
@@ -1,35 +1,38 @@
-name: cancel-merged-branches
+name: Cancel queued workflows on PR merge
+
on:
pull_request:
types:
- - closed
+ - closed
jobs:
+
cancel_stuff:
if: github.event.pull_request.merged == true
- runs-on: ubuntu-latest
+ runs-on: build-set
permissions:
actions: write
+
steps:
- - uses: actions/github-script@v7
- id: cancel-runs
- with:
- result-encoding: string
- retries: 3
- script: |
- let branch_workflows = await github.rest.actions.listWorkflowRuns({
+ - name: Cancel queued workflows for ${{ github.event.pull_request.head.ref }}
+ uses: actions/github-script@v7
+ with:
+ result-encoding: string
+ retries: 3
+ script: |
+ let branch_workflows = await github.rest.actions.listWorkflowRuns({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ workflow_id: "build-shit.yml",
+ status: "queued",
+ branch: "${{ github.event.pull_request.head.ref }}"
+ });
+ let runs = branch_workflows.data.workflow_runs;
+ runs.forEach((run) => {
+ github.rest.actions.cancelWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
- workflow_id: "build-shit.yml",
- status: "queued",
- branch: "${{ github.event.pull_request.head.ref }}"
+ run_id: run.id
});
- let runs = branch_workflows.data.workflow_runs;
- runs.forEach((run) => {
- github.rest.actions.cancelWorkflowRun({
- owner: context.repo.owner,
- repo: context.repo.repo,
- run_id: run.id
- });
- });
- console.log(runs);
+ });
+ console.log(runs);
diff --git a/.gitignore b/.gitignore
index b6a415fe9..84585eee0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,8 @@ RECOVER_*.fla
shitAudio/
.build_time
.swp
+
+# Exclude JS stuff
+node_modules/
+package.json
+package-lock.json
diff --git a/.prettierignore b/.prettierignore
index 2e132c06f..ccf886f1a 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -9,3 +9,5 @@ assets/weekend1/images
# Don't ignore data files
# TODO: These don't work.
!assets/preload/data/
+
+assets/exclude/data/credits.json
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 13a1862d2..a8a67245b 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -96,6 +96,11 @@
"target": "windows",
"args": ["-debug", "-DFORCE_DEBUG_VERSION"]
},
+ {
+ "label": "Linux / Debug",
+ "target": "linux",
+ "args": ["-debug", "-DFORCE_DEBUG_VERSION"]
+ },
{
"label": "HashLink / Debug",
"target": "hl",
@@ -130,6 +135,11 @@
"-DFORCE_DEBUG_VERSION"
]
},
+ {
+ "label": "Windows / Debug (Straight to Play - 2hot)",
+ "target": "windows",
+ "args": ["-debug", "-DSONG=2hot", "-DFORCE_DEBUG_VERSION"]
+ },
{
"label": "HashLink / Debug (Straight to Play - Bopeebo Normal)",
"target": "hl",
@@ -160,6 +170,11 @@
"target": "windows",
"args": ["-debug", "-DANIMDEBUG", "-DFORCE_DEBUG_VERSION"]
},
+ {
+ "label": "Windows / Debug (Debug hxCodec)",
+ "target": "windows",
+ "args": ["-debug", "-DHXC_LIBVLC_LOGGING", "-DFORCE_DEBUG_VERSION"]
+ },
{
"label": "HashLink / Debug (Straight to Animation Editor)",
"target": "hl",
diff --git a/Project.xml b/Project.xml
index 4b81fd07b..0a44a268c 100644
--- a/Project.xml
+++ b/Project.xml
@@ -45,6 +45,7 @@
+
+
+
@@ -119,7 +123,9 @@
-
+
+
+
@@ -233,6 +239,10 @@
+
+
+
+
diff --git a/assets b/assets
index ad6ed62bd..e6c707ede 160000
--- a/assets
+++ b/assets
@@ -1 +1 @@
-Subproject commit ad6ed62bd254456b9584b02ed0a5402118b033a8
+Subproject commit e6c707ededfa1a41c08756a6fc4ec0e2848c3734
diff --git a/build/Dockerfile b/build/Dockerfile
new file mode 100644
index 000000000..c545d1364
--- /dev/null
+++ b/build/Dockerfile
@@ -0,0 +1,186 @@
+FROM ubuntu:mantic
+
+ARG haxe_version=4.3.4
+ARG haxelib_version=4.1.0
+ARG neko_version=2.3.0
+
+# prepare runner
+ENV GITHUB_HOME="/github/home"
+
+RUN <> /etc/apt/apt.conf.d/10apt-autoremove
+APT::Get::AutomaticRemove "0";
+APT::Get::HideAutoRemove "1";
+EOC
+
+echo <> /etc/apt/apt.conf.d/80retries
+"APT::Acquire::Retries \"10\";"
+EOC
+
+echo <> /etc/apt/apt.conf.d/90assumeyes
+"APT::Get::Assume-Yes \"true\";"
+EOC
+EOF
+
+# Prepare apt-fast
+RUN <> /etc/gitconfig
+[safe]
+ directory = *
+EOC
+
+ssh-keyscan -t rsa,ecdsa,ed25519 github.com >> /etc/ssh/ssh_known_hosts
+ssh-keyscan -t rsa,ecdsa,ed25519 ravy.dev >> /etc/ssh/ssh_known_hosts
+EOF
+
+# Haxe native dependencies
+RUN < = null;
- static function get_instance():Conductor
- {
- if (_instance == null) _instance = new Conductor();
- return _instance;
- }
-
/**
- * Signal fired when the current Conductor instance advances to a new measure.
+ * Signal fired when the current static Conductor instance advances to a new measure.
*/
public static var measureHit(default, null):FlxSignal = new FlxSignal();
+ /**
+ * Signal fired when THIS Conductor instance advances to a new measure.
+ * TODO: This naming sucks but we can't make a static and instance field with the same name!
+ */
+ public var onMeasureHit(default, null):FlxSignal = new FlxSignal();
+
/**
* Signal fired when the current Conductor instance advances to a new beat.
*/
public static var beatHit(default, null):FlxSignal = new FlxSignal();
+ /**
+ * Signal fired when THIS Conductor instance advances to a new beat.
+ * TODO: This naming sucks but we can't make a static and instance field with the same name!
+ */
+ public var onBeatHit(default, null):FlxSignal = new FlxSignal();
+
/**
* Signal fired when the current Conductor instance advances to a new step.
*/
public static var stepHit(default, null):FlxSignal = new FlxSignal();
+ /**
+ * Signal fired when THIS Conductor instance advances to a new step.
+ * TODO: This naming sucks but we can't make a static and instance field with the same name!
+ */
+ public var onStepHit(default, null):FlxSignal = new FlxSignal();
+
/**
* The list of time changes in the song.
* There should be at least one time change (at the beginning of the song) to define the BPM.
@@ -286,6 +298,69 @@ class Conductor
return Std.int(timeSignatureNumerator / timeSignatureDenominator * Constants.STEPS_PER_BEAT * Constants.STEPS_PER_BEAT);
}
+ /**
+ * Reset the Conductor, replacing the current instance with a fresh one.
+ */
+ public static function reset():Void
+ {
+ set_instance(new Conductor());
+ }
+
+ static function dispatchMeasureHit():Void
+ {
+ Conductor.measureHit.dispatch();
+ }
+
+ static function dispatchBeatHit():Void
+ {
+ Conductor.beatHit.dispatch();
+ }
+
+ static function dispatchStepHit():Void
+ {
+ Conductor.stepHit.dispatch();
+ }
+
+ static function setupSingleton(input:Conductor):Void
+ {
+ input.onMeasureHit.add(dispatchMeasureHit);
+
+ input.onBeatHit.add(dispatchBeatHit);
+
+ input.onStepHit.add(dispatchStepHit);
+ }
+
+ static function clearSingleton(input:Conductor):Void
+ {
+ input.onMeasureHit.remove(dispatchMeasureHit);
+
+ input.onBeatHit.remove(dispatchBeatHit);
+
+ input.onStepHit.remove(dispatchStepHit);
+ }
+
+ static function get_instance():Conductor
+ {
+ if (Conductor._instance == null) set_instance(new Conductor());
+ if (Conductor._instance == null) throw "Could not initialize singleton Conductor!";
+ return Conductor._instance;
+ }
+
+ static function set_instance(instance:Conductor):Conductor
+ {
+ // Use _instance in here to avoid recursion
+ if (Conductor._instance != null) clearSingleton(Conductor._instance);
+
+ Conductor._instance = instance;
+
+ if (Conductor._instance != null) setupSingleton(Conductor._instance);
+
+ return Conductor._instance;
+ }
+
+ /**
+ * The constructor.
+ */
public function new() {}
/**
@@ -296,6 +371,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.
+ * We should probably deprecate this in the future.
*/
public function forceBPM(?bpm:Float):Void
{
@@ -372,24 +448,20 @@ class Conductor
this.currentMeasure = Math.floor(currentMeasureTime);
}
- // Only fire the signal if we are THE Conductor.
- if (this == Conductor.instance || forceDispatch)
+ // FlxSignals are really cool.
+ if (currentStep != oldStep)
{
- // FlxSignals are really cool.
- if (currentStep != oldStep)
- {
- Conductor.stepHit.dispatch();
- }
+ this.onStepHit.dispatch();
+ }
- if (currentBeat != oldBeat)
- {
- Conductor.beatHit.dispatch();
- }
+ if (currentBeat != oldBeat)
+ {
+ this.onBeatHit.dispatch();
+ }
- if (currentMeasure != oldMeasure)
- {
- Conductor.measureHit.dispatch();
- }
+ if (currentMeasure != oldMeasure)
+ {
+ this.onMeasureHit.dispatch();
}
// only update the timestamp if songPosition actually changed
@@ -589,22 +661,14 @@ class Conductor
* 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
+ public static function watchQuick(?target:Conductor):Void
{
- if (conductorToUse == null) conductorToUse = Conductor.instance;
+ if (target == null) target = Conductor.instance;
- FlxG.watch.addQuick("songPosition", conductorToUse.songPosition);
- FlxG.watch.addQuick("bpm", conductorToUse.bpm);
- FlxG.watch.addQuick("currentMeasureTime", conductorToUse.currentMeasureTime);
- FlxG.watch.addQuick("currentBeatTime", conductorToUse.currentBeatTime);
- FlxG.watch.addQuick("currentStepTime", conductorToUse.currentStepTime);
- }
-
- /**
- * Reset the Conductor, replacing the current instance with a fresh one.
- */
- public static function reset():Void
- {
- _instance = new Conductor();
+ FlxG.watch.addQuick('songPosition', target.songPosition);
+ FlxG.watch.addQuick('bpm', target.bpm);
+ FlxG.watch.addQuick('currentMeasureTime', target.currentMeasureTime);
+ FlxG.watch.addQuick('currentBeatTime', target.currentBeatTime);
+ FlxG.watch.addQuick('currentStepTime', target.currentStepTime);
}
}
diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx
index 59c19d6ce..2377343c3 100644
--- a/source/funkin/InitState.hx
+++ b/source/funkin/InitState.hx
@@ -261,6 +261,35 @@ class InitState extends FlxState
return;
}
+ // TODO: Rework loading behavior so we don't have to do this.
+ switch (songId)
+ {
+ case 'tutorial' | 'bopeebo' | 'fresh' | 'dadbattle':
+ Paths.setCurrentLevel('week1');
+ PlayStatePlaylist.campaignId = 'week1';
+ case 'spookeez' | 'south' | 'monster':
+ Paths.setCurrentLevel('week2');
+ PlayStatePlaylist.campaignId = 'week2';
+ case 'pico' | 'philly-nice' | 'blammed':
+ Paths.setCurrentLevel('week3');
+ PlayStatePlaylist.campaignId = 'week3';
+ case 'high' | 'satin-panties' | 'milf':
+ Paths.setCurrentLevel('week4');
+ PlayStatePlaylist.campaignId = 'week4';
+ case 'cocoa' | 'eggnog' | 'winter-horrorland':
+ Paths.setCurrentLevel('week5');
+ PlayStatePlaylist.campaignId = 'week5';
+ case 'senpai' | 'roses' | 'thorns':
+ Paths.setCurrentLevel('week6');
+ PlayStatePlaylist.campaignId = 'week6';
+ case 'ugh' | 'guns' | 'stress':
+ Paths.setCurrentLevel('week7');
+ PlayStatePlaylist.campaignId = 'week7';
+ case 'darnell' | 'lit-up' | '2hot' | 'blazin':
+ Paths.setCurrentLevel('weekend1');
+ PlayStatePlaylist.campaignId = 'weekend1';
+ }
+
LoadingState.loadPlayState(
{
targetSong: songData,
@@ -283,6 +312,10 @@ class InitState extends FlxState
return;
}
+ // TODO: Rework loading behavior so we don't have to do this.
+ Paths.setCurrentLevel(levelId);
+ PlayStatePlaylist.campaignId = levelId;
+
PlayStatePlaylist.playlistSongIds = currentLevel.getSongs();
PlayStatePlaylist.isStoryMode = true;
PlayStatePlaylist.campaignScore = 0;
diff --git a/source/funkin/Paths.hx b/source/funkin/Paths.hx
index fd4ef76fa..54a4b7acf 100644
--- a/source/funkin/Paths.hx
+++ b/source/funkin/Paths.hx
@@ -113,7 +113,7 @@ class Paths
public static function videos(key:String, ?library:String):String
{
- return getPath('videos/$key.${Constants.EXT_VIDEO}', BINARY, library);
+ return getPath('videos/$key.${Constants.EXT_VIDEO}', BINARY, library ?? 'videos');
}
public static function voices(song:String, ?suffix:String = ''):String
diff --git a/source/funkin/audio/FunkinSound.hx b/source/funkin/audio/FunkinSound.hx
index 8c1bf3b41..df05cc3ef 100644
--- a/source/funkin/audio/FunkinSound.hx
+++ b/source/funkin/audio/FunkinSound.hx
@@ -223,11 +223,12 @@ class FunkinSound extends FlxSound implements ICloneable
// already paused before we lost focus.
if (_lostFocus && !_alreadyPaused)
{
+ trace('Resuming audio (${this._label}) on focus!');
resume();
}
else
{
- trace('Not resuming audio on focus!');
+ trace('Not resuming audio (${this._label}) on focus!');
}
_lostFocus = false;
}
@@ -402,6 +403,12 @@ class FunkinSound extends FlxSound implements ICloneable
sound.group = FlxG.sound.defaultSoundGroup;
sound.persist = true;
+ // Make sure to add the sound to the list.
+ // If it's already in, it won't get re-added.
+ // If it's not in the list (it gets removed by FunkinSound.playMusic()),
+ // it will get re-added (then if this was called by playMusic(), removed again)
+ FlxG.sound.list.add(sound);
+
// Call onLoad() because the sound already loaded
if (onLoad != null && sound._sound != null) onLoad();
diff --git a/source/funkin/audio/visualize/ABotVis.hx b/source/funkin/audio/visualize/ABotVis.hx
index 89b004df4..5874b8921 100644
--- a/source/funkin/audio/visualize/ABotVis.hx
+++ b/source/funkin/audio/visualize/ABotVis.hx
@@ -8,153 +8,180 @@ import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
import flixel.math.FlxMath;
import flixel.sound.FlxSound;
import funkin.util.MathUtil;
+import funkVis.dsp.SpectralAnalyzer;
+import funkVis.audioclip.frontends.LimeAudioClip;
using Lambda;
class ABotVis extends FlxTypedSpriteGroup
{
- public var vis:VisShit;
+ // public var vis:VisShit;
+ var analyzer:SpectralAnalyzer;
var volumes:Array = [];
+ public var snd:FlxSound;
+
public function new(snd:FlxSound)
{
super();
- vis = new VisShit(snd);
+ this.snd = snd;
+
+ // vis = new VisShit(snd);
// vis.snd = snd;
var visFrms:FlxAtlasFrames = Paths.getSparrowAtlas('aBotViz');
+ // these are the differences in X position, from left to right
+ var positionX:Array = [0, 59, 56, 66, 54, 52, 51];
+ var positionY:Array = [0, -8, -3.5, -0.4, 0.5, 4.7, 7];
+
for (lol in 1...8)
{
// pushes initial value
volumes.push(0.0);
+ var sum = function(num:Float, total:Float) return total += num;
+ var posX:Float = positionX.slice(0, lol).fold(sum, 0);
+ var posY:Float = positionY.slice(0, lol).fold(sum, 0);
- var viz:FlxSprite = new FlxSprite(50 * lol, 0);
+ var viz:FlxSprite = new FlxSprite(posX, posY);
viz.frames = visFrms;
add(viz);
- var visStr = 'VIZ';
- if (lol == 5) visStr = 'viz'; // lol makes it lowercase, accomodates for art that I dont wanna rename!
-
+ var visStr = 'viz';
viz.animation.addByPrefix('VIZ', visStr + lol, 0);
- viz.animation.play('VIZ', false, false, -1);
+ viz.animation.play('VIZ', false, false, 2);
}
}
+ public function initAnalyzer()
+ {
+ @:privateAccess
+ analyzer = new SpectralAnalyzer(7, new LimeAudioClip(cast snd._channel.__source), 0.01, 30);
+ analyzer.maxDb = -35;
+ // analyzer.fftN = 2048;
+ }
+
+ var visTimer:Float = -1;
+ var visTimeMax:Float = 1 / 30;
+
override function update(elapsed:Float)
{
// updateViz();
- updateFFT(elapsed);
+ // updateFFT(elapsed);
+ //
super.update(elapsed);
}
- function updateFFT(elapsed:Float)
+ static inline function min(x:Int, y:Int):Int
{
- if (vis.snd != null)
+ return x > y ? y : x;
+ }
+
+ override function draw()
+ {
+ #if web
+ if (analyzer != null) drawFFT();
+ #end
+ super.draw();
+ }
+
+ /**
+ * TJW funkVis based visualizer! updateFFT() is the old nasty shit that dont worky!
+ */
+ function drawFFT():Void
+ {
+ var levels = analyzer.getLevels(false);
+
+ for (i in 0...min(group.members.length, levels.length))
{
- vis.checkAndSetBuffer();
+ var animFrame:Int = Math.round(levels[i].value * 5);
- if (vis.setBuffer)
- {
- var remappedShit:Int = 0;
+ animFrame = Math.floor(Math.min(5, animFrame));
+ animFrame = Math.floor(Math.max(0, animFrame));
- if (vis.snd.playing) remappedShit = Std.int(FlxMath.remapToRange(vis.snd.time, 0, vis.snd.length, 0, vis.numSamples));
- else
- remappedShit = Std.int(FlxMath.remapToRange(Conductor.instance.songPosition, 0, vis.snd.length, 0, vis.numSamples));
+ animFrame = Std.int(Math.abs(animFrame - 5)); // shitty dumbass flip, cuz dave got da shit backwards lol!
- var fftSamples:Array = [];
-
- var swagBucks = remappedShit;
-
- for (i in remappedShit...remappedShit + (Std.int((44100 * (1 / 144)))))
- {
- var left = vis.audioData[swagBucks] / 32767;
- var right = vis.audioData[swagBucks + 1] / 32767;
-
- var balanced = (left + right) / 2;
-
- swagBucks += 2;
-
- fftSamples.push(balanced);
- }
-
- var freqShit = vis.funnyFFT(fftSamples);
-
- for (i in 0...group.members.length)
- {
- var getSliceShit = function(s:Int) {
- var powShit = FlxMath.remapToRange(s, 0, group.members.length, 0, MathUtil.logBase(10, freqShit[0].length));
- return Math.round(Math.pow(10, powShit));
- };
-
- // var powShit:Float = getSliceShit(i);
- var hzSliced:Int = getSliceShit(i);
-
- var sliceLength:Int = Std.int(freqShit[0].length / group.members.length);
-
- var volSlice = freqShit[0].slice(hzSliced, getSliceShit(i + 1));
-
- var avgVel:Float = 0;
-
- for (slice in volSlice)
- {
- avgVel += slice;
- }
-
- avgVel /= volSlice.length;
-
- avgVel *= 10000000;
-
- volumes[i] += avgVel - (elapsed * (volumes[i] * 50));
-
- var animFrame:Int = Std.int(volumes[i]);
-
- animFrame = Math.floor(Math.min(5, animFrame));
- animFrame = Math.floor(Math.max(0, animFrame));
-
- animFrame = Std.int(Math.abs(animFrame - 5)); // shitty dumbass flip, cuz dave got da shit backwards lol!
-
- group.members[i].animation.curAnim.curFrame = animFrame;
- if (FlxG.keys.justPressed.U)
- {
- trace(avgVel);
- trace(group.members[i].animation.curAnim.curFrame);
- }
- }
-
- // group.members[0].animation.curAnim.curFrame =
- }
+ group.members[i].animation.curAnim.curFrame = animFrame;
}
}
- public function updateViz()
- {
- if (vis.snd != null)
- {
- var remappedShit:Int = 0;
- vis.checkAndSetBuffer();
-
- if (vis.setBuffer)
- {
- // var startingSample:Int = Std.int(FlxMath.remapToRange)
-
- if (vis.snd.playing) remappedShit = Std.int(FlxMath.remapToRange(vis.snd.time, 0, vis.snd.length, 0, vis.numSamples));
-
- for (i in 0...group.members.length)
- {
- var sampleApprox:Int = Std.int(FlxMath.remapToRange(i, 0, group.members.length, remappedShit, remappedShit + 500));
-
- var left = vis.audioData[sampleApprox] / 32767;
-
- var animFrame:Int = Std.int(FlxMath.remapToRange(left, -1, 1, 0, 6));
-
- group.members[i].animation.curAnim.curFrame = animFrame;
- }
- }
- }
- }
+ // function updateFFT(elapsed:Float)
+ // {
+ // if (vis.snd != null)
+ // {
+ // vis.checkAndSetBuffer();
+ // if (vis.setBuffer)
+ // {
+ // var remappedShit:Int = 0;
+ // if (vis.snd.playing) remappedShit = Std.int(FlxMath.remapToRange(vis.snd.time, 0, vis.snd.length, 0, vis.numSamples));
+ // else
+ // remappedShit = Std.int(FlxMath.remapToRange(Conductor.instance.songPosition, 0, vis.snd.length, 0, vis.numSamples));
+ // var fftSamples:Array = [];
+ // var swagBucks = remappedShit;
+ // for (i in remappedShit...remappedShit + (Std.int((44100 * (1 / 144)))))
+ // {
+ // var left = vis.audioData[swagBucks] / 32767;
+ // var right = vis.audioData[swagBucks + 1] / 32767;
+ // var balanced = (left + right) / 2;
+ // swagBucks += 2;
+ // fftSamples.push(balanced);
+ // }
+ // var freqShit = vis.funnyFFT(fftSamples);
+ // for (i in 0...group.members.length)
+ // {
+ // var getSliceShit = function(s:Int) {
+ // var powShit = FlxMath.remapToRange(s, 0, group.members.length, 0, MathUtil.logBase(10, freqShit[0].length));
+ // return Math.round(Math.pow(10, powShit));
+ // };
+ // // var powShit:Float = getSliceShit(i);
+ // var hzSliced:Int = getSliceShit(i);
+ // var sliceLength:Int = Std.int(freqShit[0].length / group.members.length);
+ // var volSlice = freqShit[0].slice(hzSliced, getSliceShit(i + 1));
+ // var avgVel:Float = 0;
+ // for (slice in volSlice)
+ // {
+ // avgVel += slice;
+ // }
+ // avgVel /= volSlice.length;
+ // avgVel *= 10000000;
+ // volumes[i] += avgVel - (elapsed * (volumes[i] * 50));
+ // var animFrame:Int = Std.int(volumes[i]);
+ // animFrame = Math.floor(Math.min(5, animFrame));
+ // animFrame = Math.floor(Math.max(0, animFrame));
+ // animFrame = Std.int(Math.abs(animFrame - 5)); // shitty dumbass flip, cuz dave got da shit backwards lol!
+ // group.members[i].animation.curAnim.curFrame = animFrame;
+ // if (FlxG.keys.justPressed.U)
+ // {
+ // trace(avgVel);
+ // trace(group.members[i].animation.curAnim.curFrame);
+ // }
+ // }
+ // // group.members[0].animation.curAnim.curFrame =
+ // }
+ // }
+ // }
+ // public function updateViz()
+ // {
+ // if (vis.snd != null)
+ // {
+ // var remappedShit:Int = 0;
+ // vis.checkAndSetBuffer();
+ // if (vis.setBuffer)
+ // {
+ // // var startingSample:Int = Std.int(FlxMath.remapToRange)
+ // if (vis.snd.playing) remappedShit = Std.int(FlxMath.remapToRange(vis.snd.time, 0, vis.snd.length, 0, vis.numSamples));
+ // for (i in 0...group.members.length)
+ // {
+ // var sampleApprox:Int = Std.int(FlxMath.remapToRange(i, 0, group.members.length, remappedShit, remappedShit + 500));
+ // var left = vis.audioData[sampleApprox] / 32767;
+ // var animFrame:Int = Std.int(FlxMath.remapToRange(left, -1, 1, 0, 6));
+ // group.members[i].animation.curAnim.curFrame = animFrame;
+ // }
+ // }
+ // }
+ // }
}
diff --git a/source/funkin/graphics/FunkinCamera.hx b/source/funkin/graphics/FunkinCamera.hx
index 90861c263..8c8a1c9a7 100644
--- a/source/funkin/graphics/FunkinCamera.hx
+++ b/source/funkin/graphics/FunkinCamera.hx
@@ -47,9 +47,13 @@ class FunkinCamera extends FlxCamera
public var shouldDraw:Bool = true;
- public function new(x:Int = 0, y:Int = 0, width:Int = 0, height:Int = 0, zoom:Float = 0)
+ // Used to identify the camera during debugging.
+ final id:String = 'unknown';
+
+ public function new(id:String = 'unknown', x:Int = 0, y:Int = 0, width:Int = 0, height:Int = 0, zoom:Float = 0)
{
super(x, y, width, height, zoom);
+ this.id = id;
bgTexture = pickTexture(width, height);
bgBitmap = FixedBitmapData.fromTexture(bgTexture);
bgFrame = new FlxFrame(new FlxGraphic('', null));
diff --git a/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx b/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx
index 5394bce1a..8a77c1c85 100644
--- a/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx
+++ b/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx
@@ -37,6 +37,11 @@ class FlxAtlasSprite extends FlxAnimate
{
if (settings == null) settings = SETTINGS;
+ if (path == null)
+ {
+ throw 'Null path specified for FlxAtlasSprite!';
+ }
+
super(x, y, path, settings);
if (this.anim.curInstance == null)
@@ -137,7 +142,8 @@ class FlxAtlasSprite extends FlxAnimate
anim.callback = function(_, frame:Int) {
var offset = loop ? 0 : -1;
- if (frame == (anim.getFrameLabel(id).duration + offset) + anim.getFrameLabel(id).index)
+ var frameLabel = anim.getFrameLabel(id);
+ if (frame == (frameLabel.duration + offset) + frameLabel.index)
{
if (loop)
{
diff --git a/source/funkin/graphics/shaders/GaussianBlurShader.hx b/source/funkin/graphics/shaders/GaussianBlurShader.hx
index 81167655b..cecfdab80 100644
--- a/source/funkin/graphics/shaders/GaussianBlurShader.hx
+++ b/source/funkin/graphics/shaders/GaussianBlurShader.hx
@@ -20,6 +20,6 @@ class GaussianBlurShader extends FlxRuntimeShader
public function setAmount(value:Float):Void
{
this.amount = value;
- this.setFloat("amount", amount);
+ this.setFloat("_amount", amount);
}
}
diff --git a/source/funkin/graphics/shaders/Grayscale.hx b/source/funkin/graphics/shaders/Grayscale.hx
index 6673ace24..fbd0970e5 100644
--- a/source/funkin/graphics/shaders/Grayscale.hx
+++ b/source/funkin/graphics/shaders/Grayscale.hx
@@ -17,6 +17,6 @@ class Grayscale extends FlxRuntimeShader
public function setAmount(value:Float):Void
{
amount = value;
- this.setFloat("amount", amount);
+ this.setFloat("_amount", amount);
}
}
diff --git a/source/funkin/graphics/shaders/HSVShader.hx b/source/funkin/graphics/shaders/HSVShader.hx
index 9b6ae4cdd..587008ce2 100644
--- a/source/funkin/graphics/shaders/HSVShader.hx
+++ b/source/funkin/graphics/shaders/HSVShader.hx
@@ -13,6 +13,7 @@ class HSVShader extends FlxRuntimeShader
public function new()
{
super(Assets.getText(Paths.frag('hsv')));
+ FlxG.debugger.addTrackerProfile(new TrackerProfile(HSVShader, ['hue', 'saturation', 'value']));
hue = 1;
saturation = 1;
value = 1;
diff --git a/source/funkin/input/Controls.hx b/source/funkin/input/Controls.hx
index 0857678d0..14b226d88 100644
--- a/source/funkin/input/Controls.hx
+++ b/source/funkin/input/Controls.hx
@@ -30,44 +30,44 @@ class Controls extends FlxActionSet
* A list of actions that a player would invoke via some input device.
* Uses FlxActions to funnel various inputs to a single action.
*/
- var _ui_up = new FlxActionDigital(Action.UI_UP);
- var _ui_left = new FlxActionDigital(Action.UI_LEFT);
- var _ui_right = new FlxActionDigital(Action.UI_RIGHT);
- var _ui_down = new FlxActionDigital(Action.UI_DOWN);
- var _ui_upP = new FlxActionDigital(Action.UI_UP_P);
- var _ui_leftP = new FlxActionDigital(Action.UI_LEFT_P);
- var _ui_rightP = new FlxActionDigital(Action.UI_RIGHT_P);
- var _ui_downP = new FlxActionDigital(Action.UI_DOWN_P);
- var _ui_upR = new FlxActionDigital(Action.UI_UP_R);
- var _ui_leftR = new FlxActionDigital(Action.UI_LEFT_R);
- var _ui_rightR = new FlxActionDigital(Action.UI_RIGHT_R);
- var _ui_downR = new FlxActionDigital(Action.UI_DOWN_R);
- var _note_up = new FlxActionDigital(Action.NOTE_UP);
- var _note_left = new FlxActionDigital(Action.NOTE_LEFT);
- var _note_right = new FlxActionDigital(Action.NOTE_RIGHT);
- var _note_down = new FlxActionDigital(Action.NOTE_DOWN);
- var _note_upP = new FlxActionDigital(Action.NOTE_UP_P);
- var _note_leftP = new FlxActionDigital(Action.NOTE_LEFT_P);
- var _note_rightP = new FlxActionDigital(Action.NOTE_RIGHT_P);
- var _note_downP = new FlxActionDigital(Action.NOTE_DOWN_P);
- var _note_upR = new FlxActionDigital(Action.NOTE_UP_R);
- var _note_leftR = new FlxActionDigital(Action.NOTE_LEFT_R);
- var _note_rightR = new FlxActionDigital(Action.NOTE_RIGHT_R);
- var _note_downR = new FlxActionDigital(Action.NOTE_DOWN_R);
- var _accept = new FlxActionDigital(Action.ACCEPT);
- 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 _debug_menu = new FlxActionDigital(Action.DEBUG_MENU);
- var _debug_chart = new FlxActionDigital(Action.DEBUG_CHART);
- var _debug_stage = new FlxActionDigital(Action.DEBUG_STAGE);
- var _volume_up = new FlxActionDigital(Action.VOLUME_UP);
- var _volume_down = new FlxActionDigital(Action.VOLUME_DOWN);
- var _volume_mute = new FlxActionDigital(Action.VOLUME_MUTE);
+ var _ui_up = new FunkinAction(Action.UI_UP);
+ var _ui_left = new FunkinAction(Action.UI_LEFT);
+ var _ui_right = new FunkinAction(Action.UI_RIGHT);
+ var _ui_down = new FunkinAction(Action.UI_DOWN);
+ var _ui_upP = new FunkinAction(Action.UI_UP_P);
+ var _ui_leftP = new FunkinAction(Action.UI_LEFT_P);
+ var _ui_rightP = new FunkinAction(Action.UI_RIGHT_P);
+ var _ui_downP = new FunkinAction(Action.UI_DOWN_P);
+ var _ui_upR = new FunkinAction(Action.UI_UP_R);
+ var _ui_leftR = new FunkinAction(Action.UI_LEFT_R);
+ var _ui_rightR = new FunkinAction(Action.UI_RIGHT_R);
+ var _ui_downR = new FunkinAction(Action.UI_DOWN_R);
+ var _note_up = new FunkinAction(Action.NOTE_UP);
+ var _note_left = new FunkinAction(Action.NOTE_LEFT);
+ var _note_right = new FunkinAction(Action.NOTE_RIGHT);
+ var _note_down = new FunkinAction(Action.NOTE_DOWN);
+ var _note_upP = new FunkinAction(Action.NOTE_UP_P);
+ var _note_leftP = new FunkinAction(Action.NOTE_LEFT_P);
+ var _note_rightP = new FunkinAction(Action.NOTE_RIGHT_P);
+ var _note_downP = new FunkinAction(Action.NOTE_DOWN_P);
+ var _note_upR = new FunkinAction(Action.NOTE_UP_R);
+ var _note_leftR = new FunkinAction(Action.NOTE_LEFT_R);
+ var _note_rightR = new FunkinAction(Action.NOTE_RIGHT_R);
+ var _note_downR = new FunkinAction(Action.NOTE_DOWN_R);
+ var _accept = new FunkinAction(Action.ACCEPT);
+ var _back = new FunkinAction(Action.BACK);
+ var _pause = new FunkinAction(Action.PAUSE);
+ var _reset = new FunkinAction(Action.RESET);
+ var _screenshot = new FunkinAction(Action.SCREENSHOT);
+ var _cutscene_advance = new FunkinAction(Action.CUTSCENE_ADVANCE);
+ var _debug_menu = new FunkinAction(Action.DEBUG_MENU);
+ var _debug_chart = new FunkinAction(Action.DEBUG_CHART);
+ var _debug_stage = new FunkinAction(Action.DEBUG_STAGE);
+ var _volume_up = new FunkinAction(Action.VOLUME_UP);
+ var _volume_down = new FunkinAction(Action.VOLUME_DOWN);
+ var _volume_mute = new FunkinAction(Action.VOLUME_MUTE);
- var byName:Map = new Map();
+ var byName:Map = new Map();
public var gamepadsAdded:Array = [];
public var keyboardScheme = KeyboardScheme.None;
@@ -75,122 +75,142 @@ class Controls extends FlxActionSet
public var UI_UP(get, never):Bool;
inline function get_UI_UP()
- return _ui_up.check();
+ return _ui_up.checkPressed();
public var UI_LEFT(get, never):Bool;
inline function get_UI_LEFT()
- return _ui_left.check();
+ return _ui_left.checkPressed();
public var UI_RIGHT(get, never):Bool;
inline function get_UI_RIGHT()
- return _ui_right.check();
+ return _ui_right.checkPressed();
public var UI_DOWN(get, never):Bool;
inline function get_UI_DOWN()
- return _ui_down.check();
+ return _ui_down.checkPressed();
public var UI_UP_P(get, never):Bool;
inline function get_UI_UP_P()
- return _ui_upP.check();
+ return _ui_up.checkJustPressed();
public var UI_LEFT_P(get, never):Bool;
inline function get_UI_LEFT_P()
- return _ui_leftP.check();
+ return _ui_left.checkJustPressed();
public var UI_RIGHT_P(get, never):Bool;
inline function get_UI_RIGHT_P()
- return _ui_rightP.check();
+ return _ui_right.checkJustPressed();
public var UI_DOWN_P(get, never):Bool;
inline function get_UI_DOWN_P()
- return _ui_downP.check();
+ return _ui_down.checkJustPressed();
public var UI_UP_R(get, never):Bool;
inline function get_UI_UP_R()
- return _ui_upR.check();
+ return _ui_up.checkJustReleased();
public var UI_LEFT_R(get, never):Bool;
inline function get_UI_LEFT_R()
- return _ui_leftR.check();
+ return _ui_left.checkJustReleased();
public var UI_RIGHT_R(get, never):Bool;
inline function get_UI_RIGHT_R()
- return _ui_rightR.check();
+ return _ui_right.checkJustReleased();
public var UI_DOWN_R(get, never):Bool;
inline function get_UI_DOWN_R()
- return _ui_downR.check();
+ return _ui_down.checkJustReleased();
+
+ public var UI_UP_GAMEPAD(get, never):Bool;
+
+ inline function get_UI_UP_GAMEPAD()
+ return _ui_up.checkPressedGamepad();
+
+ public var UI_LEFT_GAMEPAD(get, never):Bool;
+
+ inline function get_UI_LEFT_GAMEPAD()
+ return _ui_left.checkPressedGamepad();
+
+ public var UI_RIGHT_GAMEPAD(get, never):Bool;
+
+ inline function get_UI_RIGHT_GAMEPAD()
+ return _ui_right.checkPressedGamepad();
+
+ public var UI_DOWN_GAMEPAD(get, never):Bool;
+
+ inline function get_UI_DOWN_GAMEPAD()
+ return _ui_down.checkPressedGamepad();
public var NOTE_UP(get, never):Bool;
inline function get_NOTE_UP()
- return _note_up.check();
+ return _note_up.checkPressed();
public var NOTE_LEFT(get, never):Bool;
inline function get_NOTE_LEFT()
- return _note_left.check();
+ return _note_left.checkPressed();
public var NOTE_RIGHT(get, never):Bool;
inline function get_NOTE_RIGHT()
- return _note_right.check();
+ return _note_right.checkPressed();
public var NOTE_DOWN(get, never):Bool;
inline function get_NOTE_DOWN()
- return _note_down.check();
+ return _note_down.checkPressed();
public var NOTE_UP_P(get, never):Bool;
inline function get_NOTE_UP_P()
- return _note_upP.check();
+ return _note_up.checkJustPressed();
public var NOTE_LEFT_P(get, never):Bool;
inline function get_NOTE_LEFT_P()
- return _note_leftP.check();
+ return _note_left.checkJustPressed();
public var NOTE_RIGHT_P(get, never):Bool;
inline function get_NOTE_RIGHT_P()
- return _note_rightP.check();
+ return _note_right.checkJustPressed();
public var NOTE_DOWN_P(get, never):Bool;
inline function get_NOTE_DOWN_P()
- return _note_downP.check();
+ return _note_down.checkJustPressed();
public var NOTE_UP_R(get, never):Bool;
inline function get_NOTE_UP_R()
- return _note_upR.check();
+ return _note_up.checkJustReleased();
public var NOTE_LEFT_R(get, never):Bool;
inline function get_NOTE_LEFT_R()
- return _note_leftR.check();
+ return _note_left.checkJustReleased();
public var NOTE_RIGHT_R(get, never):Bool;
inline function get_NOTE_RIGHT_R()
- return _note_rightR.check();
+ return _note_right.checkJustReleased();
public var NOTE_DOWN_R(get, never):Bool;
inline function get_NOTE_DOWN_R()
- return _note_downR.check();
+ return _note_down.checkJustReleased();
public var ACCEPT(get, never):Bool;
@@ -260,26 +280,10 @@ class Controls extends FlxActionSet
add(_ui_left);
add(_ui_right);
add(_ui_down);
- add(_ui_upP);
- add(_ui_leftP);
- add(_ui_rightP);
- add(_ui_downP);
- add(_ui_upR);
- add(_ui_leftR);
- add(_ui_rightR);
- add(_ui_downR);
add(_note_up);
add(_note_left);
add(_note_right);
add(_note_down);
- add(_note_upP);
- add(_note_leftP);
- add(_note_rightP);
- add(_note_downP);
- add(_note_upR);
- add(_note_leftR);
- add(_note_rightR);
- add(_note_downR);
add(_accept);
add(_back);
add(_pause);
@@ -293,8 +297,16 @@ class Controls extends FlxActionSet
add(_volume_down);
add(_volume_mute);
- for (action in digitalActions)
- byName[action.name] = action;
+ for (action in digitalActions) {
+ if (Std.isOfType(action, FunkinAction)) {
+ var funkinAction:FunkinAction = cast action;
+ byName[funkinAction.name] = funkinAction;
+ if (funkinAction.namePressed != null)
+ byName[funkinAction.namePressed] = funkinAction;
+ if (funkinAction.nameReleased != null)
+ byName[funkinAction.nameReleased] = funkinAction;
+ }
+ }
if (scheme == null)
scheme = None;
@@ -307,14 +319,17 @@ class Controls extends FlxActionSet
super.update();
}
- // inline
- public function checkByName(name:Action):Bool
+ public function check(name:Action, trigger:FlxInputState = JUST_PRESSED, gamepadOnly:Bool = false):Bool
{
#if debug
if (!byName.exists(name))
throw 'Invalid name: $name';
#end
- return byName[name].check();
+ var action = byName[name];
+ if (gamepadOnly)
+ return action.checkFiltered(trigger, GAMEPAD);
+ else
+ return action.checkFiltered(trigger);
}
public function getKeysForAction(name:Action):Array {
@@ -405,36 +420,36 @@ class Controls extends FlxActionSet
{
case UI_UP:
func(_ui_up, PRESSED);
- func(_ui_upP, JUST_PRESSED);
- func(_ui_upR, JUST_RELEASED);
+ func(_ui_up, JUST_PRESSED);
+ func(_ui_up, JUST_RELEASED);
case UI_LEFT:
func(_ui_left, PRESSED);
- func(_ui_leftP, JUST_PRESSED);
- func(_ui_leftR, JUST_RELEASED);
+ func(_ui_left, JUST_PRESSED);
+ func(_ui_left, JUST_RELEASED);
case UI_RIGHT:
func(_ui_right, PRESSED);
- func(_ui_rightP, JUST_PRESSED);
- func(_ui_rightR, JUST_RELEASED);
+ func(_ui_right, JUST_PRESSED);
+ func(_ui_right, JUST_RELEASED);
case UI_DOWN:
func(_ui_down, PRESSED);
- func(_ui_downP, JUST_PRESSED);
- func(_ui_downR, JUST_RELEASED);
+ func(_ui_down, JUST_PRESSED);
+ func(_ui_down, JUST_RELEASED);
case NOTE_UP:
func(_note_up, PRESSED);
- func(_note_upP, JUST_PRESSED);
- func(_note_upR, JUST_RELEASED);
+ func(_note_up, JUST_PRESSED);
+ func(_note_up, JUST_RELEASED);
case NOTE_LEFT:
func(_note_left, PRESSED);
- func(_note_leftP, JUST_PRESSED);
- func(_note_leftR, JUST_RELEASED);
+ func(_note_left, JUST_PRESSED);
+ func(_note_left, JUST_RELEASED);
case NOTE_RIGHT:
func(_note_right, PRESSED);
- func(_note_rightP, JUST_PRESSED);
- func(_note_rightR, JUST_RELEASED);
+ func(_note_right, JUST_PRESSED);
+ func(_note_right, JUST_RELEASED);
case NOTE_DOWN:
func(_note_down, PRESSED);
- func(_note_downP, JUST_PRESSED);
- func(_note_downR, JUST_RELEASED);
+ func(_note_down, JUST_PRESSED);
+ func(_note_down, JUST_RELEASED);
case ACCEPT:
func(_accept, JUST_PRESSED);
case BACK:
@@ -1042,6 +1057,173 @@ typedef Swipes =
?curTouchPos:FlxPoint
};
+/**
+ * An FlxActionDigital with additional functionality, including:
+ * - Combining `pressed` and `released` inputs into one action.
+ * - Filtering by input method (`KEYBOARD`, `MOUSE`, `GAMEPAD`, etc).
+ */
+class FunkinAction extends FlxActionDigital {
+ public var namePressed(default, null):Null;
+ public var nameReleased(default, null):Null;
+
+ var cache:Map = [];
+
+ public function new(?name:String = "", ?namePressed:String, ?nameReleased:String)
+ {
+ super(name);
+
+ this.namePressed = namePressed;
+ this.nameReleased = nameReleased;
+ }
+
+ /**
+ * Input checks default to whether the input was just pressed, on any input device.
+ */
+ public override function check():Bool {
+ return checkFiltered(JUST_PRESSED);
+ }
+
+ /**
+ * Check whether the input is currently being held.
+ */
+ public function checkPressed():Bool {
+ return checkFiltered(PRESSED);
+ }
+
+ /**
+ * Check whether the input is currently being held, and was not held last frame.
+ */
+ public function checkJustPressed():Bool {
+ return checkFiltered(JUST_PRESSED);
+ }
+
+ /**
+ * Check whether the input is not currently being held.
+ */
+ public function checkReleased():Bool {
+ return checkFiltered(RELEASED);
+ }
+
+ /**
+ * Check whether the input is not currently being held, and was held last frame.
+ */
+ public function checkJustReleased():Bool {
+ return checkFiltered(JUST_RELEASED);
+ }
+
+ /**
+ * Check whether the input is currently being held by a gamepad device.
+ */
+ public function checkPressedGamepad():Bool {
+ return checkFiltered(PRESSED, GAMEPAD);
+ }
+
+ /**
+ * Check whether the input is currently being held by a gamepad device, and was not held last frame.
+ */
+ public function checkJustPressedGamepad():Bool {
+ return checkFiltered(JUST_PRESSED, GAMEPAD);
+ }
+
+ /**
+ * Check whether the input is not currently being held by a gamepad device.
+ */
+ public function checkReleasedGamepad():Bool {
+ return checkFiltered(RELEASED, GAMEPAD);
+ }
+
+ /**
+ * Check whether the input is not currently being held by a gamepad device, and was held last frame.
+ */
+ public function checkJustReleasedGamepad():Bool {
+ return checkFiltered(JUST_RELEASED, GAMEPAD);
+ }
+
+ public function checkMultiFiltered(?filterTriggers:Array, ?filterDevices:Array):Bool {
+ if (filterTriggers == null) {
+ filterTriggers = [PRESSED, JUST_PRESSED];
+ }
+ if (filterDevices == null) {
+ filterDevices = [];
+ }
+
+ // Perform checkFiltered for each combination.
+ for (i in filterTriggers) {
+ if (filterDevices.length == 0) {
+ if (checkFiltered(i)) {
+ return true;
+ }
+ } else {
+ for (j in filterDevices) {
+ if (checkFiltered(i, j)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Performs the functionality of `FlxActionDigital.check()`, but with optional filters.
+ * @param action The action to check for.
+ * @param filterTrigger Optionally filter by trigger condition (`JUST_PRESSED`, `PRESSED`, `JUST_RELEASED`, `RELEASED`).
+ * @param filterDevice Optionally filter by device (`KEYBOARD`, `MOUSE`, `GAMEPAD`, `OTHER`).
+ */
+ public function checkFiltered(?filterTrigger:FlxInputState, ?filterDevice:FlxInputDevice):Bool {
+ // The normal
+
+ // Make sure we only update the inputs once per frame.
+ var key = '${filterTrigger}:${filterDevice}';
+ var cacheEntry = cache.get(key);
+
+ if (cacheEntry != null && cacheEntry.timestamp == FlxG.game.ticks) {
+ return cacheEntry.value;
+ }
+ // Use a for loop instead so we can remove inputs while iterating.
+
+ // We don't return early because we need to call check() on ALL inputs.
+ var result = false;
+ var len = inputs != null ? inputs.length : 0;
+ for (i in 0...len)
+ {
+ var j = len - i - 1;
+ var input = inputs[j];
+
+ // Filter out dead inputs.
+ if (input.destroyed)
+ {
+ inputs.splice(j, 1);
+ continue;
+ }
+
+ // Update the input.
+ input.update();
+
+ // Check whether the input is the right trigger.
+ if (filterTrigger != null && input.trigger != filterTrigger) {
+ continue;
+ }
+
+ // Check whether the input is the right device.
+ if (filterDevice != null && input.device != filterDevice) {
+ continue;
+ }
+
+ // Check whether the input has triggered.
+ if (input.check(this))
+ {
+ result = true;
+ }
+ }
+
+ // We need to cache this result.
+ cache.set(key, {timestamp: FlxG.game.ticks, value: result});
+
+ return result;
+ }
+}
+
class FlxActionInputDigitalMobileSwipeGameplay extends FlxActionInputDigital
{
var touchMap:Map = new Map();
@@ -1229,8 +1411,7 @@ enum Control
DEBUG_STAGE;
}
-enum
-abstract Action(String) to String from String
+enum abstract Action(String) to String from String
{
// NOTE
var NOTE_UP = "note_up";
diff --git a/source/funkin/input/TurboActionHandler.hx b/source/funkin/input/TurboActionHandler.hx
new file mode 100644
index 000000000..9425db8cd
--- /dev/null
+++ b/source/funkin/input/TurboActionHandler.hx
@@ -0,0 +1,111 @@
+package funkin.input;
+
+import flixel.input.keyboard.FlxKey;
+import flixel.FlxBasic;
+import funkin.input.Controls;
+import funkin.input.Controls.Action;
+
+/**
+ * Handles repeating behavior when holding down a control action.
+ *
+ * When the `action` is pressed, `activated` will be true for the first frame,
+ * then wait `delay` seconds before becoming true for one frame every `interval` seconds.
+ *
+ * Example: Pressing Ctrl+Z will undo, while holding Ctrl+Z will start to undo repeatedly.
+ */
+class TurboActionHandler extends FlxBasic
+{
+ /**
+ * Default delay before repeating.
+ */
+ static inline final DEFAULT_DELAY:Float = 0.4;
+
+ /**
+ * Default interval between repeats.
+ */
+ static inline final DEFAULT_INTERVAL:Float = 0.1;
+
+ /**
+ * Whether the action for this handler is pressed.
+ */
+ public var pressed(get, never):Bool;
+
+ /**
+ * Whether the action for this handler is pressed,
+ * and the handler is ready to repeat.
+ */
+ public var activated(default, null):Bool = false;
+
+ /**
+ * The Funkin Controls handler.
+ */
+ var controls(get, never):Controls;
+
+ function get_controls():Controls
+ {
+ return PlayerSettings.player1.controls;
+ }
+
+ var action:Action;
+
+ var delay:Float;
+ var interval:Float;
+ var gamepadOnly:Bool;
+
+ var pressedTime:Float = 0;
+
+ function new(action:Action, delay:Float = DEFAULT_DELAY, interval:Float = DEFAULT_INTERVAL, gamepadOnly:Bool = false)
+ {
+ super();
+ this.action = action;
+ this.delay = delay;
+ this.interval = interval;
+ this.gamepadOnly = gamepadOnly;
+ }
+
+ function get_pressed():Bool
+ {
+ return controls.check(action, PRESSED, gamepadOnly);
+ }
+
+ public override function update(elapsed:Float):Void
+ {
+ super.update(elapsed);
+
+ if (pressed)
+ {
+ if (pressedTime == 0)
+ {
+ activated = true;
+ }
+ else if (pressedTime >= (delay + interval))
+ {
+ activated = true;
+ pressedTime -= interval;
+ }
+ else
+ {
+ activated = false;
+ }
+ pressedTime += elapsed;
+ }
+ else
+ {
+ pressedTime = 0;
+ activated = false;
+ }
+ }
+
+ /**
+ * Builds a TurboActionHandler that monitors from a single key.
+ * @param inputKey The key to monitor.
+ * @param delay How long to wait before repeating.
+ * @param repeatDelay How long to wait between repeats.
+ * @return A TurboActionHandler
+ */
+ public static overload inline extern function build(action:Action, ?delay:Float = DEFAULT_DELAY, ?interval:Float = DEFAULT_INTERVAL,
+ ?gamepadOnly:Bool = false):TurboActionHandler
+ {
+ return new TurboActionHandler(action, delay, interval);
+ }
+}
diff --git a/source/funkin/input/TurboButtonHandler.hx b/source/funkin/input/TurboButtonHandler.hx
new file mode 100644
index 000000000..63c2a294b
--- /dev/null
+++ b/source/funkin/input/TurboButtonHandler.hx
@@ -0,0 +1,127 @@
+package funkin.input;
+
+import flixel.input.gamepad.FlxGamepadInputID;
+import flixel.input.gamepad.FlxGamepad;
+import flixel.FlxBasic;
+
+/**
+ * Handles repeating behavior when holding down a gamepad button or button combination.
+ *
+ * When the `inputs` are pressed, `activated` will be true for the first frame,
+ * then wait `delay` seconds before becoming true for one frame every `interval` seconds.
+ *
+ * Example: Pressing Ctrl+Z will undo, while holding Ctrl+Z will start to undo repeatedly.
+ */
+class TurboButtonHandler extends FlxBasic
+{
+ /**
+ * Default delay before repeating.
+ */
+ static inline final DEFAULT_DELAY:Float = 0.4;
+
+ /**
+ * Default interval between repeats.
+ */
+ static inline final DEFAULT_INTERVAL:Float = 0.1;
+
+ /**
+ * Whether all of the keys for this handler are pressed.
+ */
+ public var allPressed(get, never):Bool;
+
+ /**
+ * Whether all of the keys for this handler are activated,
+ * and the handler is ready to repeat.
+ */
+ public var activated(default, null):Bool = false;
+
+ var inputs:Array;
+ var delay:Float;
+ var interval:Float;
+ var targetGamepad:FlxGamepad;
+
+ var allPressedTime:Float = 0;
+
+ function new(inputs:Array, delay:Float = DEFAULT_DELAY, interval:Float = DEFAULT_INTERVAL, ?targetGamepad:FlxGamepad)
+ {
+ super();
+ this.inputs = inputs;
+ this.delay = delay;
+ this.interval = interval;
+ this.targetGamepad = targetGamepad ?? FlxG.gamepads.firstActive;
+ }
+
+ function get_allPressed():Bool
+ {
+ if (targetGamepad == null) return false;
+ if (inputs == null || inputs.length == 0) return false;
+ if (inputs.length == 1) return targetGamepad.anyPressed(inputs);
+
+ // Check if ANY keys are unpressed
+ for (input in inputs)
+ {
+ if (!targetGamepad.anyPressed([input])) return false;
+ }
+ return true;
+ }
+
+ public override function update(elapsed:Float):Void
+ {
+ super.update(elapsed);
+
+ // Try to find a gamepad if we don't have one
+ if (targetGamepad == null)
+ {
+ targetGamepad = FlxG.gamepads.firstActive;
+ }
+
+ if (allPressed)
+ {
+ if (allPressedTime == 0)
+ {
+ activated = true;
+ }
+ else if (allPressedTime >= (delay + interval))
+ {
+ activated = true;
+ allPressedTime -= interval;
+ }
+ else
+ {
+ activated = false;
+ }
+ allPressedTime += elapsed;
+ }
+ else
+ {
+ allPressedTime = 0;
+ activated = false;
+ }
+ }
+
+ /**
+ * Builds a TurboButtonHandler that monitors from a single input.
+ * @param input The input to monitor.
+ * @param delay How long to wait before repeating.
+ * @param repeatDelay How long to wait between repeats.
+ * @return A TurboKeyHandler
+ */
+ public static overload inline extern function build(input:FlxGamepadInputID, ?delay:Float = DEFAULT_DELAY,
+ ?interval:Float = DEFAULT_INTERVAL):TurboButtonHandler
+ {
+ return new TurboButtonHandler([input], delay, interval);
+ }
+
+ /**
+ * Builds a TurboKeyHandler that monitors a key combination.
+ * @param inputs The combination of inputs to monitor.
+ * @param delay How long to wait before repeating.
+ * @param repeatDelay How long to wait between repeats.
+ * @return A TurboKeyHandler
+ */
+ public static overload inline extern function build(inputs:Array, ?delay:Float = DEFAULT_DELAY,
+ ?interval:Float = DEFAULT_INTERVAL):TurboButtonHandler
+ {
+ return new TurboButtonHandler(inputs, delay, interval);
+ }
+}
diff --git a/source/funkin/modding/PolymodHandler.hx b/source/funkin/modding/PolymodHandler.hx
index 9a54e4053..32be3c8e0 100644
--- a/source/funkin/modding/PolymodHandler.hx
+++ b/source/funkin/modding/PolymodHandler.hx
@@ -240,8 +240,8 @@ 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', 'videos' => 'videos', 'tutorial' => 'tutorial', 'week1' => 'week1',
+ 'week2' => 'week2', 'week3' => 'week3', 'week4' => 'week4', 'week5' => 'week5', 'week6' => 'week6', 'week7' => 'week7', 'weekend1' => 'weekend1',
],
coreAssetRedirect: CORE_FOLDER,
}
diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx
index a715f0a93..787349952 100644
--- a/source/funkin/play/PlayState.hx
+++ b/source/funkin/play/PlayState.hx
@@ -245,20 +245,26 @@ class PlayState extends MusicBeatSubState
/**
* The current camera zoom level without any modifiers applied.
*/
- public var currentCameraZoom:Float = FlxCamera.defaultZoom * 1.05;
+ public var currentCameraZoom:Float = FlxCamera.defaultZoom;
/**
- * currentCameraZoom is increased every beat, and lerped back to this value every frame, creating a smooth 'zoom-in' effect.
- * Defaults to 1.05, but may be larger or smaller depending on the current stage.
- * Tweened via the `ZoomCamera` song event in direct mode.
+ * Multiplier for currentCameraZoom for camera bops.
+ * Lerped back to 1.0x every frame.
*/
- public var defaultCameraZoom:Float = FlxCamera.defaultZoom * 1.05;
+ public var cameraBopMultiplier:Float = 1.0;
/**
- * Camera zoom applied on top of currentCameraZoom.
- * Tweened via the `ZoomCamera` song event in additive mode.
+ * Default camera zoom for the current stage.
+ * If we aren't in a stage, just use the default zoom (1.05x).
*/
- public var additiveCameraZoom:Float = 0;
+ public var stageZoom(get, never):Float;
+
+ function get_stageZoom():Float
+ {
+ if (currentStage != null) return currentStage.camZoom;
+ else
+ return FlxCamera.defaultZoom * 1.05;
+ }
/**
* The current HUD camera zoom level.
@@ -268,16 +274,18 @@ class PlayState extends MusicBeatSubState
public var defaultHUDCameraZoom:Float = FlxCamera.defaultZoom * 1.0;
/**
- * Intensity of the gameplay camera zoom.
- * @default `1.5%`
+ * Camera bop intensity multiplier.
+ * Applied to cameraBopMultiplier on camera bops (usually every beat).
+ * @default `101.5%`
*/
- public var cameraZoomIntensity:Float = Constants.DEFAULT_ZOOM_INTENSITY;
+ public var cameraBopIntensity:Float = Constants.DEFAULT_BOP_INTENSITY;
/**
* Intensity of the HUD camera zoom.
+ * Need to make this a multiplier later. Just shoving in 0.015 for now so it doesn't break.
* @default `3.0%`
*/
- public var hudCameraZoomIntensity:Float = Constants.DEFAULT_ZOOM_INTENSITY * 2.0;
+ public var hudCameraZoomIntensity:Float = 0.015 * 2.0;
/**
* How many beats (quarter notes) between camera zooms.
@@ -490,7 +498,7 @@ class PlayState extends MusicBeatSubState
/**
* The combo popups. Includes the real-time combo counter and the rating.
*/
- var comboPopUps:PopUpStuff;
+ public var comboPopUps:PopUpStuff;
/**
* PROPERTIES
@@ -805,6 +813,7 @@ class PlayState extends MusicBeatSubState
super.update(elapsed);
+ var list = FlxG.sound.list;
updateHealthBar();
updateScoreText();
@@ -826,9 +835,12 @@ class PlayState extends MusicBeatSubState
inputSpitter = [];
// Reset music properly.
- FlxG.sound.music.time = startTimestamp - Conductor.instance.instrumentalOffset;
- FlxG.sound.music.pitch = playbackRate;
- FlxG.sound.music.pause();
+ if (FlxG.sound.music != null)
+ {
+ FlxG.sound.music.time = startTimestamp - Conductor.instance.instrumentalOffset;
+ FlxG.sound.music.pitch = playbackRate;
+ FlxG.sound.music.pause();
+ }
if (!overrideMusic)
{
@@ -844,7 +856,7 @@ class PlayState extends MusicBeatSubState
vocals.pause();
vocals.time = 0;
- FlxG.sound.music.volume = 1;
+ if (FlxG.sound.music != null) FlxG.sound.music.volume = 1;
vocals.volume = 1;
vocals.playerVolume = 1;
vocals.opponentVolume = 1;
@@ -861,8 +873,8 @@ class PlayState extends MusicBeatSubState
regenNoteData();
// Reset camera zooming
- cameraZoomIntensity = Constants.DEFAULT_ZOOM_INTENSITY;
- hudCameraZoomIntensity = Constants.DEFAULT_ZOOM_INTENSITY * 2.0;
+ cameraBopIntensity = Constants.DEFAULT_BOP_INTENSITY;
+ hudCameraZoomIntensity = (cameraBopIntensity - 1.0) * 2.0;
cameraZoomRate = Constants.DEFAULT_ZOOM_RATE;
health = Constants.HEALTH_STARTING;
@@ -957,11 +969,12 @@ class PlayState extends MusicBeatSubState
if (health > Constants.HEALTH_MAX) health = Constants.HEALTH_MAX;
if (health < Constants.HEALTH_MIN) health = Constants.HEALTH_MIN;
- // Lerp the camera zoom towards the target level.
- if (subState == null)
+ // Apply camera zoom + multipliers.
+ if (subState == null && cameraZoomRate > 0.0) // && !isInCutscene)
{
- currentCameraZoom = FlxMath.lerp(defaultCameraZoom, currentCameraZoom, 0.95);
- FlxG.camera.zoom = currentCameraZoom + additiveCameraZoom;
+ cameraBopMultiplier = FlxMath.lerp(1.0, cameraBopMultiplier, 0.95); // Lerp bop multiplier back to 1.0x
+ var zoomPlusBop = currentCameraZoom * cameraBopMultiplier; // Apply camera bop multiplier.
+ FlxG.camera.zoom = zoomPlusBop; // Actually apply the zoom to the camera.
camHUD.zoom = FlxMath.lerp(defaultHUDCameraZoom, camHUD.zoom, 0.95);
}
@@ -971,6 +984,7 @@ class PlayState extends MusicBeatSubState
FlxG.watch.addQuick('bfAnim', currentStage.getBoyfriend().getCurrentAnimation());
}
FlxG.watch.addQuick('health', health);
+ FlxG.watch.addQuick('cameraBopIntensity', cameraBopIntensity);
// TODO: Add a song event for Handle GF dance speed.
@@ -1367,15 +1381,15 @@ class PlayState extends MusicBeatSubState
// activeNotes.sort(SortUtil.byStrumtime, FlxSort.DESCENDING);
}
- // Only zoom camera if we are zoomed by less than 35%.
+ // Only bop camera if zoom level is below 135%
if (Preferences.zoomCamera
- && FlxG.camera.zoom < (1.35 * defaultCameraZoom)
+ && FlxG.camera.zoom < (1.35 * FlxCamera.defaultZoom)
&& cameraZoomRate > 0
&& Conductor.instance.currentBeat % cameraZoomRate == 0)
{
- // Zoom camera in (1.5%)
- currentCameraZoom += cameraZoomIntensity * defaultCameraZoom;
- // Hud zooms double (3%)
+ // Set zoom multiplier for camera bop.
+ cameraBopMultiplier = cameraBopIntensity;
+ // HUD camera zoom still uses old system. To change. (+3%)
camHUD.zoom += hudCameraZoomIntensity * defaultHUDCameraZoom;
}
// trace('Not bopping camera: ${FlxG.camera.zoom} < ${(1.35 * defaultCameraZoom)} && ${cameraZoomRate} > 0 && ${Conductor.instance.currentBeat} % ${cameraZoomRate} == ${Conductor.instance.currentBeat % cameraZoomRate}}');
@@ -1457,7 +1471,7 @@ class PlayState extends MusicBeatSubState
*/
function initCameras():Void
{
- camGame = new FunkinCamera();
+ camGame = new FunkinCamera('playStateCamGame');
camGame.bgColor = BACKGROUND_COLOR; // Show a pink background behind the stage.
camHUD = new FlxCamera();
camHUD.bgColor.alpha = 0; // Show the game scene behind the camera.
@@ -1538,10 +1552,11 @@ class PlayState extends MusicBeatSubState
function loadStage(id:String):Void
{
currentStage = StageRegistry.instance.fetchEntry(id);
- currentStage.revive(); // Stages are killed and props destroyed when the PlayState is destroyed to save memory.
if (currentStage != null)
{
+ currentStage.revive(); // Stages are killed and props destroyed when the PlayState is destroyed to save memory.
+
// Actually create and position the sprites.
var event:ScriptEvent = new ScriptEvent(CREATE, false);
ScriptEventDispatcher.callEvent(currentStage, event);
@@ -1566,12 +1581,11 @@ class PlayState extends MusicBeatSubState
{
if (PlayState.instance.isMinimalMode) return;
// Apply camera zoom level from stage data.
- defaultCameraZoom = currentStage?.camZoom ?? 1.0;
- currentCameraZoom = defaultCameraZoom;
+ currentCameraZoom = stageZoom;
FlxG.camera.zoom = currentCameraZoom;
- // Reset additive zoom.
- additiveCameraZoom = 0;
+ // Reset bop multiplier.
+ cameraBopMultiplier = 1.0;
}
/**
@@ -1856,6 +1870,8 @@ class PlayState extends MusicBeatSubState
isInCutscene = false;
camCutscene.visible = false;
+
+ // TODO: Maybe tween in the camera after any cutscenes.
camHUD.visible = true;
}
@@ -1906,8 +1922,6 @@ class PlayState extends MusicBeatSubState
*/
function startSong():Void
{
- dispatchEvent(new ScriptEvent(SONG_START));
-
startingSong = false;
if (!overrideMusic && !isGamePaused && currentChart != null)
@@ -1929,7 +1943,7 @@ class PlayState extends MusicBeatSubState
// Prevent the volume from being wrong.
FlxG.sound.music.volume = 1.0;
- FlxG.sound.music.fadeTween?.cancel();
+ if (FlxG.sound.music.fadeTween != null) FlxG.sound.music.fadeTween.cancel();
trace('Playing vocals...');
add(vocals);
@@ -1948,6 +1962,8 @@ class PlayState extends MusicBeatSubState
// FlxG.sound.music.time = startTimestamp - Conductor.instance.instrumentalOffset;
handleSkippedNotes();
}
+
+ dispatchEvent(new ScriptEvent(SONG_START));
}
/**
@@ -2379,13 +2395,6 @@ class PlayState extends MusicBeatSubState
// Display the combo meter and add the calculation to the score.
popUpScore(note, event.score, event.judgement, event.healthChange);
-
- if (note.isHoldNote && note.holdNoteSprite != null)
- {
- playerStrumline.playNoteHoldCover(note.holdNoteSprite);
- }
-
- vocals.playerVolume = 1;
}
/**
@@ -2520,6 +2529,7 @@ class PlayState extends MusicBeatSubState
if (FlxG.keys.justPressed.H) camHUD.visible = !camHUD.visible;
#end
+ #if CHART_EDITOR_SUPPORTED
// Open the stage editor overlaying the current state.
if (controls.DEBUG_STAGE)
{
@@ -2539,6 +2549,7 @@ class PlayState extends MusicBeatSubState
targetSongId: currentSong.id,
}));
}
+ #end
#if (debug || FORCE_DEBUG_VERSION)
// 1: End the song immediately.
@@ -2667,6 +2678,13 @@ class PlayState extends MusicBeatSubState
}
comboPopUps.displayRating(daRating);
if (Highscore.tallies.combo >= 10 || Highscore.tallies.combo == 0) comboPopUps.displayCombo(Highscore.tallies.combo);
+
+ if (daNote.isHoldNote && daNote.holdNoteSprite != null)
+ {
+ playerStrumline.playNoteHoldCover(daNote.holdNoteSprite);
+ }
+
+ vocals.playerVolume = 1;
}
/**
@@ -2777,7 +2795,7 @@ class PlayState extends MusicBeatSubState
// adds current song data into the tallies for the level (story levels)
Highscore.talliesLevel = Highscore.combineTallies(Highscore.tallies, Highscore.talliesLevel);
- if (Save.instance.isSongHighScore(currentSong.id, currentDifficulty, data))
+ if (!isPracticeMode && !isBotPlayMode && Save.instance.isSongHighScore(currentSong.id, currentDifficulty, data))
{
Save.instance.setSongScore(currentSong.id, currentDifficulty, data);
#if newgrounds
@@ -3063,18 +3081,18 @@ class PlayState extends MusicBeatSubState
title: PlayStatePlaylist.isStoryMode ? ('${PlayStatePlaylist.campaignTitle}') : ('${currentChart.songName} by ${currentChart.songArtist}'),
scoreData:
{
- score: songScore,
+ score: PlayStatePlaylist.isStoryMode ? PlayStatePlaylist.campaignScore : songScore,
tallies:
{
- sick: Highscore.tallies.sick,
- good: Highscore.tallies.good,
- bad: Highscore.tallies.bad,
- shit: Highscore.tallies.shit,
- missed: Highscore.tallies.missed,
- combo: Highscore.tallies.combo,
- maxCombo: Highscore.tallies.maxCombo,
- totalNotesHit: Highscore.tallies.totalNotesHit,
- totalNotes: Highscore.tallies.totalNotes,
+ sick: talliesToUse.sick,
+ good: talliesToUse.good,
+ bad: talliesToUse.bad,
+ shit: talliesToUse.shit,
+ missed: talliesToUse.missed,
+ combo: talliesToUse.combo,
+ maxCombo: talliesToUse.maxCombo,
+ totalNotesHit: talliesToUse.totalNotesHit,
+ totalNotes: talliesToUse.totalNotes,
},
accuracy: Highscore.tallies.totalNotesHit / Highscore.tallies.totalNotes,
},
@@ -3116,6 +3134,15 @@ class PlayState extends MusicBeatSubState
FlxG.camera.focusOn(cameraFollowPoint.getPosition());
}
+ /**
+ * Sets the camera follow point's position and tweens the camera there.
+ */
+ public function tweenCameraToPosition(?x:Float, ?y:Float, ?duration:Float, ?ease:NullFloat>):Void
+ {
+ cameraFollowPoint.setPosition(x, y);
+ tweenCameraToFollowPoint(duration, ease);
+ }
+
/**
* Disables camera following and tweens the camera to the follow point manually.
*/
@@ -3157,38 +3184,24 @@ class PlayState extends MusicBeatSubState
/**
* Tweens the camera zoom to the desired amount.
*/
- public function tweenCameraZoom(?zoom:Float, ?duration:Float, ?directMode:Bool, ?ease:NullFloat>):Void
+ public function tweenCameraZoom(?zoom:Float, ?duration:Float, ?direct:Bool, ?ease:NullFloat>):Void
{
// Cancel the current tween if it's active.
cancelCameraZoomTween();
- var targetZoom = zoom * FlxCamera.defaultZoom;
+ // Direct mode: Set zoom directly.
+ // Stage mode: Set zoom as a multiplier of the current stage's default zoom.
+ var targetZoom = zoom * (direct ? FlxCamera.defaultZoom : stageZoom);
- if (directMode) // Direct mode: Tween defaultCameraZoom for basic "smooth" zooms.
+ if (duration == 0)
{
- if (duration == 0)
- {
- // Instant zoom. No tween needed.
- defaultCameraZoom = targetZoom;
- }
- else
- {
- // Zoom tween! Caching it so we can cancel/pause it later if needed.
- cameraZoomTween = FlxTween.tween(this, {defaultCameraZoom: targetZoom}, duration, {ease: ease});
- }
+ // Instant zoom. No tween needed.
+ currentCameraZoom = targetZoom;
}
- else // Additive mode: Tween additiveCameraZoom for ease-based zooms.
+ else
{
- if (duration == 0)
- {
- // Instant zoom. No tween needed.
- additiveCameraZoom = targetZoom;
- }
- else
- {
- // Zoom tween! Caching it so we can cancel/pause it later if needed.
- cameraZoomTween = FlxTween.tween(this, {additiveCameraZoom: targetZoom}, duration, {ease: ease});
- }
+ // Zoom tween! Caching it so we can cancel/pause it later if needed.
+ cameraZoomTween = FlxTween.tween(this, {currentCameraZoom: targetZoom}, duration, {ease: ease});
}
}
diff --git a/source/funkin/play/ResultState.hx b/source/funkin/play/ResultState.hx
index c05257338..acce8645a 100644
--- a/source/funkin/play/ResultState.hx
+++ b/source/funkin/play/ResultState.hx
@@ -64,6 +64,9 @@ class ResultState extends MusicBeatSubState
loop: resultsVariation != SHIT
});
+ // Reset the camera zoom on the results screen.
+ FlxG.camera.zoom = 1.0;
+
// TEMP-ish, just used to sorta "cache" the 3000x3000 image!
var cacheBullShit:FlxSprite = new FlxSprite().loadGraphic(Paths.image("resultScreen/soundSystem"));
add(cacheBullShit);
@@ -80,34 +83,34 @@ class ResultState extends MusicBeatSubState
bgFlash.visible = false;
add(bgFlash);
- var bfGfExcellent:FlxAtlasSprite = new FlxAtlasSprite(380, -170, Paths.animateAtlas("resultScreen/resultsBoyfriendExcellent", "shared"));
- bfGfExcellent.visible = false;
- add(bfGfExcellent);
+ // var bfGfExcellent:FlxAtlasSprite = new FlxAtlasSprite(380, -170, Paths.animateAtlas("resultScreen/resultsBoyfriendExcellent", "shared"));
+ // bfGfExcellent.visible = false;
+ // add(bfGfExcellent);
+ //
+ // var bfPerfect:FlxAtlasSprite = new FlxAtlasSprite(370, -180, Paths.animateAtlas("resultScreen/resultsBoyfriendPerfect", "shared"));
+ // bfPerfect.visible = false;
+ // add(bfPerfect);
+ //
+ // var bfSHIT:FlxAtlasSprite = new FlxAtlasSprite(0, 20, Paths.animateAtlas("resultScreen/resultsBoyfriendSHIT", "shared"));
+ // bfSHIT.visible = false;
+ // add(bfSHIT);
+ //
+ // bfGfExcellent.anim.onComplete = () -> {
+ // bfGfExcellent.anim.curFrame = 28;
+ // bfGfExcellent.anim.play(); // unpauses this anim, since it's on PlayOnce!
+ // };
+ //
+ // bfPerfect.anim.onComplete = () -> {
+ // bfPerfect.anim.curFrame = 136;
+ // bfPerfect.anim.play(); // unpauses this anim, since it's on PlayOnce!
+ // };
+ //
+ // bfSHIT.anim.onComplete = () -> {
+ // bfSHIT.anim.curFrame = 150;
+ // bfSHIT.anim.play(); // unpauses this anim, since it's on PlayOnce!
+ // };
- var bfPerfect:FlxAtlasSprite = new FlxAtlasSprite(370, -180, Paths.animateAtlas("resultScreen/resultsBoyfriendPerfect", "shared"));
- bfPerfect.visible = false;
- add(bfPerfect);
-
- var bfSHIT:FlxAtlasSprite = new FlxAtlasSprite(0, 20, Paths.animateAtlas("resultScreen/resultsBoyfriendSHIT", "shared"));
- bfSHIT.visible = false;
- add(bfSHIT);
-
- bfGfExcellent.anim.onComplete = () -> {
- bfGfExcellent.anim.curFrame = 28;
- bfGfExcellent.anim.play(); // unpauses this anim, since it's on PlayOnce!
- };
-
- bfPerfect.anim.onComplete = () -> {
- bfPerfect.anim.curFrame = 136;
- bfPerfect.anim.play(); // unpauses this anim, since it's on PlayOnce!
- };
-
- bfSHIT.anim.onComplete = () -> {
- bfSHIT.anim.curFrame = 150;
- bfSHIT.anim.play(); // unpauses this anim, since it's on PlayOnce!
- };
-
- var gf:FlxSprite = FunkinSprite.createSparrow(500, 300, 'resultScreen/resultGirlfriendGOOD');
+ var gf:FlxSprite = FunkinSprite.createSparrow(625, 325, 'resultScreen/resultGirlfriendGOOD');
gf.animation.addByPrefix("clap", "Girlfriend Good Anim", 24, false);
gf.visible = false;
gf.animation.finishCallback = _ -> {
@@ -268,9 +271,9 @@ class ResultState extends MusicBeatSubState
switch (resultsVariation)
{
- case SHIT:
- bfSHIT.visible = true;
- bfSHIT.playAnimation("");
+ // case SHIT:
+ // bfSHIT.visible = true;
+ // bfSHIT.playAnimation("");
case NORMAL:
boyfriend.animation.play('fall');
@@ -292,9 +295,9 @@ class ResultState extends MusicBeatSubState
gf.animation.play('clap', true);
gf.visible = true;
});
- case PERFECT:
- bfPerfect.visible = true;
- bfPerfect.playAnimation("");
+ // case PERFECT:
+ // bfPerfect.visible = true;
+ // bfPerfect.playAnimation("");
// bfGfExcellent.visible = true;
// bfGfExcellent.playAnimation("");
diff --git a/source/funkin/play/character/AnimateAtlasCharacter.hx b/source/funkin/play/character/AnimateAtlasCharacter.hx
index f1dadf3e2..ed58b92b5 100644
--- a/source/funkin/play/character/AnimateAtlasCharacter.hx
+++ b/source/funkin/play/character/AnimateAtlasCharacter.hx
@@ -192,6 +192,7 @@ class AnimateAtlasCharacter extends BaseCharacter
if (!this.mainSprite.hasAnimation(prefix))
{
FlxG.log.warn('[ATLASCHAR] Animation ${prefix} not found in Animate Atlas ${_data.assetPath}');
+ trace('[ATLASCHAR] Animation ${prefix} not found in Animate Atlas ${_data.assetPath}');
continue;
}
animations.set(anim.name, anim);
diff --git a/source/funkin/play/components/PopUpStuff.hx b/source/funkin/play/components/PopUpStuff.hx
index 724bf0cb9..b062b22cf 100644
--- a/source/funkin/play/components/PopUpStuff.hx
+++ b/source/funkin/play/components/PopUpStuff.hx
@@ -10,6 +10,8 @@ import funkin.util.TimerUtil;
class PopUpStuff extends FlxTypedGroup
{
+ public var offsets:Array = [0, 0];
+
override public function new()
{
super();
@@ -29,9 +31,9 @@ class PopUpStuff extends FlxTypedGroup
rating.scrollFactor.set(0.2, 0.2);
rating.zIndex = 1000;
- rating.x = FlxG.width * 0.50;
+ rating.x = (FlxG.width * 0.474) + offsets[0];
// rating.x -= FlxG.camera.scroll.x * 0.2;
- rating.y = FlxG.camera.height * 0.4 - 60;
+ rating.y = (FlxG.camera.height * 0.45 - 60) + offsets[1];
rating.acceleration.y = 550;
rating.velocity.y -= FlxG.random.int(140, 175);
rating.velocity.x -= FlxG.random.int(0, 10);
@@ -40,16 +42,19 @@ class PopUpStuff extends FlxTypedGroup
if (PlayState.instance.currentStageId.startsWith('school'))
{
- rating.setGraphicSize(Std.int(rating.width * Constants.PIXEL_ART_SCALE * 0.7));
+ rating.setGraphicSize(Std.int(rating.width * Constants.PIXEL_ART_SCALE * 0.65));
rating.antialiasing = false;
}
else
{
- rating.setGraphicSize(Std.int(rating.width * 0.7));
+ rating.setGraphicSize(Std.int(rating.width * 0.65));
rating.antialiasing = true;
}
rating.updateHitbox();
+ rating.x -= rating.width / 2;
+ rating.y -= rating.height / 2;
+
FlxTween.tween(rating, {alpha: 0}, 0.2,
{
onComplete: function(tween:FlxTween) {
@@ -77,8 +82,8 @@ class PopUpStuff extends FlxTypedGroup
pixelShitPart2 = '-pixel';
}
var comboSpr:FunkinSprite = FunkinSprite.create(pixelShitPart1 + 'combo' + pixelShitPart2);
- comboSpr.y = FlxG.camera.height * 0.4 + 80;
- comboSpr.x = FlxG.width * 0.50;
+ comboSpr.y = (FlxG.camera.height * 0.44) + offsets[1];
+ comboSpr.x = (FlxG.width * 0.507) + offsets[0];
// comboSpr.x -= FlxG.camera.scroll.x * 0.2;
comboSpr.acceleration.y = 600;
@@ -133,14 +138,14 @@ class PopUpStuff extends FlxTypedGroup
}
else
{
- numScore.setGraphicSize(Std.int(numScore.width * 0.5));
+ numScore.setGraphicSize(Std.int(numScore.width * 0.45));
numScore.antialiasing = true;
}
numScore.updateHitbox();
- numScore.x = comboSpr.x - (43 * daLoop); //- 90;
- numScore.acceleration.y = FlxG.random.int(200, 300);
- numScore.velocity.y -= FlxG.random.int(140, 160);
+ numScore.x = comboSpr.x - (36 * daLoop) - 65; //- 90;
+ numScore.acceleration.y = FlxG.random.int(250, 300);
+ numScore.velocity.y -= FlxG.random.int(130, 150);
numScore.velocity.x = FlxG.random.float(-5, 5);
add(numScore);
diff --git a/source/funkin/play/cutscene/VideoCutscene.hx b/source/funkin/play/cutscene/VideoCutscene.hx
index 3da51185f..0939dae38 100644
--- a/source/funkin/play/cutscene/VideoCutscene.hx
+++ b/source/funkin/play/cutscene/VideoCutscene.hx
@@ -67,8 +67,13 @@ class VideoCutscene
if (!openfl.Assets.exists(filePath))
{
// Display a popup.
- lime.app.Application.current.window.alert('Video file does not exist: ${filePath}', 'Error playing video');
- return;
+ // lime.app.Application.current.window.alert('Video file does not exist: ${filePath}', 'Error playing video');
+ // return;
+
+ // TODO: After moving videos to their own library,
+ // this function ALWAYS FAILS on web, but the video still plays.
+ // I think that's due to a weird quirk with how OpenFL libraries work.
+ trace('Video file does not exist: ${filePath}');
}
var rawFilePath = Paths.stripLibrary(filePath);
@@ -311,7 +316,7 @@ class VideoCutscene
blackScreen = null;
}
});
- FlxTween.tween(FlxG.camera, {zoom: PlayState.instance.defaultCameraZoom}, transitionTime,
+ FlxTween.tween(FlxG.camera, {zoom: PlayState.instance.stageZoom}, transitionTime,
{
ease: FlxEase.quadInOut,
onComplete: function(twn:FlxTween) {
diff --git a/source/funkin/play/cutscene/dialogue/Conversation.hx b/source/funkin/play/cutscene/dialogue/Conversation.hx
index 1e399e2c8..9c217948d 100644
--- a/source/funkin/play/cutscene/dialogue/Conversation.hx
+++ b/source/funkin/play/cutscene/dialogue/Conversation.hx
@@ -23,6 +23,7 @@ import funkin.modding.IScriptedClass.IDialogueScriptedClass;
import funkin.modding.IScriptedClass.IEventHandler;
import funkin.play.cutscene.dialogue.DialogueBox;
import funkin.util.SortUtil;
+import funkin.util.EaseUtil;
/**
* A high-level handler for dialogue.
@@ -179,7 +180,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
if (backdropData.fadeTime > 0.0)
{
backdrop.alpha = 0.0;
- FlxTween.tween(backdrop, {alpha: 1.0}, backdropData.fadeTime, {ease: FlxEase.linear});
+ FlxTween.tween(backdrop, {alpha: 1.0}, backdropData.fadeTime, {ease: EaseUtil.stepped(10)});
}
else
{
@@ -403,6 +404,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
type: ONESHOT, // holy shit like the game no way
startDelay: 0,
onComplete: (_) -> endOutro(),
+ ease: EaseUtil.stepped(8)
});
FlxTween.tween(this.music, {volume: 0.0}, outroData.fadeTime);
diff --git a/source/funkin/play/event/FocusCameraSongEvent.hx b/source/funkin/play/event/FocusCameraSongEvent.hx
index d4ab4f24f..569dcb87a 100644
--- a/source/funkin/play/event/FocusCameraSongEvent.hx
+++ b/source/funkin/play/event/FocusCameraSongEvent.hx
@@ -70,80 +70,76 @@ class FocusCameraSongEvent extends SongEvent
if (char == null) char = cast data.value;
- var useTween:Null = data.getBool('useTween');
- if (useTween == null) useTween = false;
var duration:Null = data.getFloat('duration');
if (duration == null) duration = 4.0;
var ease:Null = data.getString('ease');
- if (ease == null) ease = 'linear';
+ if (ease == null) ease = 'CLASSIC';
+
+ var currentStage = PlayState.instance.currentStage;
+
+ // Get target position based on char.
+ var targetX:Float = posX;
+ var targetY:Float = posY;
switch (char)
{
- case -1: // Position
+ case -1: // Position ("focus" on origin)
trace('Focusing camera on static position.');
- var xTarget:Float = posX;
- var yTarget:Float = posY;
- PlayState.instance.cameraFollowPoint.setPosition(xTarget, yTarget);
- case 0: // Boyfriend
- // Focus the camera on the player.
- if (PlayState.instance.currentStage.getBoyfriend() == null)
+ case 0: // Boyfriend (focus on player)
+ if (currentStage.getBoyfriend() == null)
{
trace('No BF to focus on.');
return;
}
trace('Focusing camera on player.');
- var xTarget:Float = PlayState.instance.currentStage.getBoyfriend().cameraFocusPoint.x + posX;
- var yTarget:Float = PlayState.instance.currentStage.getBoyfriend().cameraFocusPoint.y + posY;
+ var bfPoint = currentStage.getBoyfriend().cameraFocusPoint;
+ targetX += bfPoint.x;
+ targetY += bfPoint.y;
- PlayState.instance.cameraFollowPoint.setPosition(xTarget, yTarget);
- case 1: // Dad
- // Focus the camera on the dad.
- if (PlayState.instance.currentStage.getDad() == null)
+ case 1: // Dad (focus on opponent)
+ if (currentStage.getDad() == null)
{
trace('No dad to focus on.');
return;
}
- trace('Focusing camera on dad.');
- trace(PlayState.instance.currentStage.getDad());
- var xTarget:Float = PlayState.instance.currentStage.getDad().cameraFocusPoint.x + posX;
- var yTarget:Float = PlayState.instance.currentStage.getDad().cameraFocusPoint.y + posY;
+ trace('Focusing camera on opponent.');
+ var dadPoint = currentStage.getDad().cameraFocusPoint;
+ targetX += dadPoint.x;
+ targetY += dadPoint.y;
- PlayState.instance.cameraFollowPoint.setPosition(xTarget, yTarget);
- case 2: // Girlfriend
- // Focus the camera on the girlfriend.
- if (PlayState.instance.currentStage.getGirlfriend() == null)
+ case 2: // Girlfriend (focus on girlfriend)
+ if (currentStage.getGirlfriend() == null)
{
trace('No GF to focus on.');
return;
}
trace('Focusing camera on girlfriend.');
- var xTarget:Float = PlayState.instance.currentStage.getGirlfriend().cameraFocusPoint.x + posX;
- var yTarget:Float = PlayState.instance.currentStage.getGirlfriend().cameraFocusPoint.y + posY;
+ var gfPoint = currentStage.getGirlfriend().cameraFocusPoint;
+ targetX += gfPoint.x;
+ targetY += gfPoint.y;
- PlayState.instance.cameraFollowPoint.setPosition(xTarget, yTarget);
default:
trace('Unknown camera focus: ' + data);
}
- if (useTween)
+ // Apply tween based on ease.
+ switch (ease)
{
- switch (ease)
- {
- case 'INSTANT':
- PlayState.instance.tweenCameraToFollowPoint(0);
- default:
- var durSeconds = Conductor.instance.stepLengthMs * duration / 1000;
-
- var easeFunction:NullFloat> = Reflect.field(FlxEase, ease);
- if (easeFunction == null)
- {
- trace('Invalid ease function: $ease');
- return;
- }
-
- PlayState.instance.tweenCameraToFollowPoint(durSeconds, easeFunction);
- }
+ case 'CLASSIC': // Old-school. No ease. Just set follow point.
+ PlayState.instance.resetCamera();
+ PlayState.instance.cameraFollowPoint.setPosition(targetX, targetY);
+ case 'INSTANT': // Instant ease. Duration is automatically 0.
+ PlayState.instance.tweenCameraToPosition(targetX, targetY, 0);
+ default:
+ var durSeconds = Conductor.instance.stepLengthMs * duration / 1000;
+ var easeFunction:NullFloat> = Reflect.field(FlxEase, ease);
+ if (easeFunction == null)
+ {
+ trace('Invalid ease function: $ease');
+ return;
+ }
+ PlayState.instance.tweenCameraToPosition(targetX, targetY, durSeconds, easeFunction);
}
}
@@ -187,12 +183,6 @@ class FocusCameraSongEvent extends SongEvent
type: SongEventFieldType.FLOAT,
units: "px"
},
- {
- name: 'useTween',
- title: 'Use Tween',
- type: SongEventFieldType.BOOL,
- defaultValue: false
- },
{
name: 'duration',
title: 'Duration',
@@ -208,7 +198,9 @@ class FocusCameraSongEvent extends SongEvent
type: SongEventFieldType.ENUM,
keys: [
'Linear' => 'linear',
- 'Instant' => 'INSTANT',
+ 'Sine In' => 'sineIn',
+ 'Sine Out' => 'sineOut',
+ 'Sine In/Out' => 'sineInOut',
'Quad In' => 'quadIn',
'Quad Out' => 'quadOut',
'Quad In/Out' => 'quadInOut',
@@ -221,15 +213,17 @@ class FocusCameraSongEvent extends SongEvent
'Quint In' => 'quintIn',
'Quint Out' => 'quintOut',
'Quint In/Out' => 'quintInOut',
+ 'Expo In' => 'expoIn',
+ 'Expo Out' => 'expoOut',
+ 'Expo In/Out' => 'expoInOut',
'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',
+ 'Instant (Ignores duration)' => 'INSTANT',
+ 'Classic (Ignores duration)' => 'CLASSIC'
]
}
]);
diff --git a/source/funkin/play/event/SetCameraBopSongEvent.hx b/source/funkin/play/event/SetCameraBopSongEvent.hx
index a82577a5f..f3efc04e3 100644
--- a/source/funkin/play/event/SetCameraBopSongEvent.hx
+++ b/source/funkin/play/event/SetCameraBopSongEvent.hx
@@ -50,8 +50,8 @@ class SetCameraBopSongEvent extends SongEvent
var intensity:Null = data.getFloat('intensity');
if (intensity == null) intensity = 1.0;
- PlayState.instance.cameraZoomIntensity = Constants.DEFAULT_ZOOM_INTENSITY * intensity;
- PlayState.instance.hudCameraZoomIntensity = Constants.DEFAULT_ZOOM_INTENSITY * intensity * 2.0;
+ PlayState.instance.cameraBopIntensity = (Constants.DEFAULT_BOP_INTENSITY - 1.0) * intensity + 1.0;
+ PlayState.instance.hudCameraZoomIntensity = (Constants.DEFAULT_BOP_INTENSITY - 1.0) * intensity * 2.0;
PlayState.instance.cameraZoomRate = rate;
trace('Set camera zoom rate to ${PlayState.instance.cameraZoomRate}');
}
diff --git a/source/funkin/play/event/ZoomCameraSongEvent.hx b/source/funkin/play/event/ZoomCameraSongEvent.hx
index b913aebe7..748abda19 100644
--- a/source/funkin/play/event/ZoomCameraSongEvent.hx
+++ b/source/funkin/play/event/ZoomCameraSongEvent.hx
@@ -81,7 +81,6 @@ class ZoomCameraSongEvent extends SongEvent
PlayState.instance.tweenCameraZoom(zoom, 0, isDirectMode);
default:
var durSeconds = Conductor.instance.stepLengthMs * duration / 1000;
-
var easeFunction:NullFloat> = Reflect.field(FlxEase, ease);
if (easeFunction == null)
{
@@ -102,9 +101,9 @@ class ZoomCameraSongEvent extends SongEvent
* ```
* {
* 'zoom': FLOAT, // Target zoom level.
- * 'duration': FLOAT, // Optional duration in steps.
- * 'mode': ENUM, // Whether to set additive zoom or direct zoom.
- * 'ease': ENUM, // Optional easing function.
+ * 'duration': FLOAT, // Duration in steps.
+ * 'mode': ENUM, // Whether zoom is relative to the stage or absolute zoom.
+ * 'ease': ENUM, // Easing function.
* }
* @return SongEventSchema
*/
@@ -130,9 +129,9 @@ class ZoomCameraSongEvent extends SongEvent
{
name: 'mode',
title: 'Mode',
- defaultValue: 'direct',
+ defaultValue: 'stage',
type: SongEventFieldType.ENUM,
- keys: ['Additive' => 'additive', 'Direct' => 'direct']
+ keys: ['Stage zoom' => 'stage', 'Absolute zoom' => 'direct']
},
{
name: 'ease',
@@ -142,6 +141,9 @@ class ZoomCameraSongEvent extends SongEvent
keys: [
'Linear' => 'linear',
'Instant' => 'INSTANT',
+ 'Sine In' => 'sineIn',
+ 'Sine Out' => 'sineOut',
+ 'Sine In/Out' => 'sineInOut',
'Quad In' => 'quadIn',
'Quad Out' => 'quadOut',
'Quad In/Out' => 'quadInOut',
@@ -154,15 +156,15 @@ class ZoomCameraSongEvent extends SongEvent
'Quint In' => 'quintIn',
'Quint Out' => 'quintOut',
'Quint In/Out' => 'quintInOut',
+ 'Expo In' => 'expoIn',
+ 'Expo Out' => 'expoOut',
+ 'Expo In/Out' => 'expoInOut',
'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',
+ 'Elastic In/Out' => 'elasticInOut'
]
}
]);
diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx
index d219dc2f6..e71ae3213 100644
--- a/source/funkin/play/song/Song.hx
+++ b/source/funkin/play/song/Song.hx
@@ -609,28 +609,33 @@ class SongDifficulty
return cast events;
}
- public function cacheInst(instrumental = ''):Void
+ public function getInstPath(instrumental = ''):String
{
if (characters != null)
{
if (instrumental != '' && characters.altInstrumentals.contains(instrumental))
{
var instId = '-$instrumental';
- FlxG.sound.cache(Paths.inst(this.song.id, instId));
+ return Paths.inst(this.song.id, instId);
}
else
{
// Fallback to default instrumental.
var instId = (characters.instrumental ?? '') != '' ? '-${characters.instrumental}' : '';
- FlxG.sound.cache(Paths.inst(this.song.id, instId));
+ return Paths.inst(this.song.id, instId);
}
}
else
{
- FlxG.sound.cache(Paths.inst(this.song.id));
+ return Paths.inst(this.song.id);
}
}
+ public function cacheInst(instrumental = ''):Void
+ {
+ FlxG.sound.cache(getInstPath(instrumental));
+ }
+
public function playInst(volume:Float = 1.0, looped:Bool = false):Void
{
var suffix:String = (variation != null && variation != '' && variation != 'default') ? '-$variation' : '';
diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx
index 89dfe3041..fbec836bb 100644
--- a/source/funkin/save/Save.hx
+++ b/source/funkin/save/Save.hx
@@ -695,7 +695,7 @@ class Save
trace("[SAVE] Checking for legacy save data...");
var legacySave:FlxSave = new FlxSave();
legacySave.bind(SAVE_NAME_LEGACY, SAVE_PATH_LEGACY);
- if (legacySave?.data == null)
+ if (legacySave.isEmpty())
{
trace("[SAVE] No legacy save data found.");
return null;
diff --git a/source/funkin/ui/credits/CreditsDataHandler.hx b/source/funkin/ui/credits/CreditsDataHandler.hx
index 86afdafd1..2240ec50e 100644
--- a/source/funkin/ui/credits/CreditsDataHandler.hx
+++ b/source/funkin/ui/credits/CreditsDataHandler.hx
@@ -57,10 +57,6 @@ class CreditsDataHandler
{line: 'KawaiSprite'},
{line: 'evilsk8r'},
]
- },
- {
- header: 'Kickstarter Backers',
- appendBackers: true
}
]
};
@@ -68,11 +64,11 @@ class CreditsDataHandler
public static function fetchBackerEntries():Array
{
- // TODO: Replace this with a web request.
+ // TODO: Implement a web request.
// We can't just grab the current Kickstarter data and include it in builds,
// because we don't want to deadname people who haven't logged into the portal yet.
// It can be async and paginated for performance!
- return ['See the list of backers at $BACKER_PUBLIC_URL.'];
+ return [];
}
#if HARDCODED_CREDITS
@@ -99,12 +95,19 @@ class CreditsDataHandler
static function fetchCreditsData():funkin.data.JsonFile
{
+ #if !macro
var rawJson:String = openfl.Assets.getText(CREDITS_DATA_PATH).trim();
return {
fileName: CREDITS_DATA_PATH,
contents: rawJson
};
+ #else
+ return {
+ fileName: CREDITS_DATA_PATH,
+ contents: null
+ };
+ #end
}
static function parseCreditsData(file:JsonFile):Null
diff --git a/source/funkin/ui/credits/CreditsState.hx b/source/funkin/ui/credits/CreditsState.hx
index d43e25114..6a15f1263 100644
--- a/source/funkin/ui/credits/CreditsState.hx
+++ b/source/funkin/ui/credits/CreditsState.hx
@@ -185,7 +185,7 @@ class CreditsState extends MusicBeatState
}
else if (controls.PAUSE)
{
- scrollPaused = !scrollPaused;
+ // scrollPaused = !scrollPaused;
}
}
@@ -196,7 +196,7 @@ class CreditsState extends MusicBeatState
function exit():Void
{
- FlxG.switchState(new funkin.ui.mainmenu.MainMenuState());
+ FlxG.switchState(funkin.ui.mainmenu.MainMenuState.new);
}
public override function destroy():Void
diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx
index dba1a7e55..93907bdda 100644
--- a/source/funkin/ui/debug/charting/ChartEditorState.hx
+++ b/source/funkin/ui/debug/charting/ChartEditorState.hx
@@ -8,6 +8,7 @@ import flixel.FlxSprite;
import flixel.FlxSubState;
import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.group.FlxSpriteGroup;
+import flixel.input.gamepad.FlxGamepadInputID;
import flixel.input.keyboard.FlxKey;
import flixel.input.mouse.FlxMouseEvent;
import flixel.math.FlxMath;
@@ -40,6 +41,8 @@ import funkin.data.stage.StageData;
import funkin.graphics.FunkinCamera;
import funkin.graphics.FunkinSprite;
import funkin.input.Cursor;
+import funkin.input.TurboActionHandler;
+import funkin.input.TurboButtonHandler;
import funkin.input.TurboKeyHandler;
import funkin.modding.events.ScriptEvent;
import funkin.play.character.BaseCharacter.CharacterType;
@@ -74,6 +77,7 @@ import funkin.ui.debug.charting.commands.SetItemSelectionCommand;
import funkin.ui.debug.charting.components.ChartEditorEventSprite;
import funkin.ui.debug.charting.components.ChartEditorHoldNoteSprite;
import funkin.ui.debug.charting.components.ChartEditorMeasureTicks;
+import funkin.ui.debug.charting.components.ChartEditorMeasureTicks;
import funkin.ui.debug.charting.components.ChartEditorNotePreview;
import funkin.ui.debug.charting.components.ChartEditorNoteSprite;
import funkin.ui.debug.charting.components.ChartEditorPlaybarHead;
@@ -401,8 +405,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
renderedSelectionSquares.setPosition(gridTiledSprite?.x ?? 0.0, gridTiledSprite?.y ?? 0.0);
// Offset the selection box start position, if we are dragging.
if (selectionBoxStartPos != null) selectionBoxStartPos.y -= diff;
- // Update the note preview viewport box.
+
+ // Update the note preview.
setNotePreviewViewportBounds(calculateNotePreviewViewportBounds());
+ refreshNotePreviewPlayheadPosition();
+
// Update the measure tick display.
if (measureTicks != null) measureTicks.y = gridTiledSprite?.y ?? 0.0;
return this.scrollPositionInPixels;
@@ -463,6 +470,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// Move the playhead sprite to the correct position.
gridPlayhead.y = this.playheadPositionInPixels + GRID_INITIAL_Y_POS;
+ updatePlayheadGhostHoldNotes();
+ refreshNotePreviewPlayheadPosition();
+
return this.playheadPositionInPixels;
}
@@ -769,6 +779,13 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
return currentPlaceNoteData = value;
}
+ /**
+ * The SongNoteData which is currently being placed, for each column.
+ * `null` if the user isn't currently placing a note.
+ * As the user moves down, we will update this note's sustain length, and finalize the note when they release.
+ */
+ var currentLiveInputPlaceNoteData:Array = [];
+
// Note Movement
/**
@@ -799,6 +816,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
*/
var dragLengthCurrent:Float = 0;
+ /**
+ * The current length of the hold note we are placing with the playhead, in steps.
+ * Play a sound when this value changes.
+ */
+ var playheadDragLengthCurrent:Array = [];
+
/**
* Flip-flop to alternate between two stretching sounds.
*/
@@ -1071,6 +1094,66 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
*/
var pageDownKeyHandler:TurboKeyHandler = TurboKeyHandler.build(FlxKey.PAGEDOWN);
+ /**
+ * Variable used to track how long the user has been holding up on the dpad.
+ */
+ var dpadUpGamepadHandler:TurboButtonHandler = TurboButtonHandler.build(FlxGamepadInputID.DPAD_UP);
+
+ /**
+ * Variable used to track how long the user has been holding down on the dpad.
+ */
+ var dpadDownGamepadHandler:TurboButtonHandler = TurboButtonHandler.build(FlxGamepadInputID.DPAD_DOWN);
+
+ /**
+ * Variable used to track how long the user has been holding left on the dpad.
+ */
+ var dpadLeftGamepadHandler:TurboButtonHandler = TurboButtonHandler.build(FlxGamepadInputID.DPAD_LEFT);
+
+ /**
+ * Variable used to track how long the user has been holding right on the dpad.
+ */
+ var dpadRightGamepadHandler:TurboButtonHandler = TurboButtonHandler.build(FlxGamepadInputID.DPAD_RIGHT);
+
+ /**
+ * Variable used to track how long the user has been holding up on the left stick.
+ */
+ var leftStickUpGamepadHandler:TurboButtonHandler = TurboButtonHandler.build(FlxGamepadInputID.LEFT_STICK_DIGITAL_UP);
+
+ /**
+ * Variable used to track how long the user has been holding down on the left stick.
+ */
+ var leftStickDownGamepadHandler:TurboButtonHandler = TurboButtonHandler.build(FlxGamepadInputID.LEFT_STICK_DIGITAL_DOWN);
+
+ /**
+ * Variable used to track how long the user has been holding left on the left stick.
+ */
+ var leftStickLeftGamepadHandler:TurboButtonHandler = TurboButtonHandler.build(FlxGamepadInputID.LEFT_STICK_DIGITAL_LEFT);
+
+ /**
+ * Variable used to track how long the user has been holding right on the left stick.
+ */
+ var leftStickRightGamepadHandler:TurboButtonHandler = TurboButtonHandler.build(FlxGamepadInputID.LEFT_STICK_DIGITAL_RIGHT);
+
+ /**
+ * Variable used to track how long the user has been holding up on the right stick.
+ */
+ var rightStickUpGamepadHandler:TurboButtonHandler = TurboButtonHandler.build(FlxGamepadInputID.RIGHT_STICK_DIGITAL_UP);
+
+ /**
+ * Variable used to track how long the user has been holding down on the right stick.
+ */
+ var rightStickDownGamepadHandler:TurboButtonHandler = TurboButtonHandler.build(FlxGamepadInputID.RIGHT_STICK_DIGITAL_DOWN);
+
+ /**
+ * Variable used to track how long the user has been holding left on the right stick.
+ */
+ var rightStickLeftGamepadHandler:TurboButtonHandler = TurboButtonHandler.build(FlxGamepadInputID.RIGHT_STICK_DIGITAL_LEFT);
+
+ /**
+ * Variable used to track how long the user has been holding right on the right stick.
+ */
+ var rightStickRightGamepadHandler:TurboButtonHandler = TurboButtonHandler.build(FlxGamepadInputID.RIGHT_STICK_DIGITAL_RIGHT);
+
/**
* AUDIO AND SOUND DATA
*/
@@ -1949,10 +2032,15 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
var gridGhostNote:Null = null;
/**
- * A sprite used to indicate the note that will be placed on click.
+ * A sprite used to indicate the hold note that will be placed on click.
*/
var gridGhostHoldNote:Null = null;
+ /**
+ * A sprite used to indicate the hold note that will be placed on button release.
+ */
+ var gridPlayheadGhostHoldNotes:Array = [];
+
/**
* A sprite used to indicate the event that will be placed on click.
*/
@@ -1970,6 +2058,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
*/
var notePreviewViewport:Null = null;
+ /**
+ * The thin sprite used for representing the playhead on the note preview.
+ * We move this up and down to represent the current position.
+ */
+ var notePreviewPlayhead:Null = null;
+
/**
* The rectangular sprite used for rendering the selection box.
* Uses a 9-slice to stretch the selection box to the correct size without warping.
@@ -2091,7 +2185,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
loadPreferences();
- uiCamera = new FunkinCamera();
+ uiCamera = new FunkinCamera('chartEditorUI');
FlxG.cameras.reset(uiCamera);
buildDefaultSongData();
@@ -2349,7 +2443,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
gridGhostHoldNote = new ChartEditorHoldNoteSprite(this);
gridGhostHoldNote.alpha = 0.6;
- gridGhostHoldNote.noteData = new SongNoteData(0, 0, 0, "");
+ gridGhostHoldNote.noteData = null;
gridGhostHoldNote.visible = false;
add(gridGhostHoldNote);
gridGhostHoldNote.zIndex = 11;
@@ -2423,6 +2517,15 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
add(notePreviewViewport);
notePreviewViewport.zIndex = 30;
+ notePreviewPlayhead = new FlxSprite().makeGraphic(2, 2, 0xFFFF0000);
+ notePreviewPlayhead.scrollFactor.set(0, 0);
+ notePreviewPlayhead.scale.set(notePreview.width / 2, 0.5); // Setting width does nothing.
+ notePreviewPlayhead.updateHitbox();
+ notePreviewPlayhead.x = notePreview.x;
+ notePreviewPlayhead.y = notePreview.y;
+ add(notePreviewPlayhead);
+ notePreviewPlayhead.zIndex = 31;
+
setNotePreviewViewportBounds(calculateNotePreviewViewportBounds());
}
@@ -2519,6 +2622,13 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
}
}
+ function refreshNotePreviewPlayheadPosition():Void
+ {
+ if (notePreviewPlayhead == null) return;
+
+ notePreviewPlayhead.y = notePreview.y + (notePreview.height * ((scrollPositionInPixels + playheadPositionInPixels) / songLengthInPixels));
+ }
+
/**
* Builds the group that will hold all the notes.
*/
@@ -3015,6 +3125,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
*/
function setupTurboKeyHandlers():Void
{
+ // Keyboard shortcuts
add(undoKeyHandler);
add(redoKeyHandler);
add(upKeyHandler);
@@ -3023,6 +3134,20 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
add(sKeyHandler);
add(pageUpKeyHandler);
add(pageDownKeyHandler);
+
+ // Gamepad inputs
+ add(dpadUpGamepadHandler);
+ add(dpadDownGamepadHandler);
+ add(dpadLeftGamepadHandler);
+ add(dpadRightGamepadHandler);
+ add(leftStickUpGamepadHandler);
+ add(leftStickDownGamepadHandler);
+ add(leftStickLeftGamepadHandler);
+ add(leftStickRightGamepadHandler);
+ add(rightStickUpGamepadHandler);
+ add(rightStickDownGamepadHandler);
+ add(rightStickLeftGamepadHandler);
+ add(rightStickRightGamepadHandler);
}
/**
@@ -3709,32 +3834,56 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// Up Arrow = Scroll Up
if (upKeyHandler.activated && currentLiveInputStyle == None)
{
- scrollAmount = -GRID_SIZE * 0.25 * 25.0;
+ scrollAmount = -GRID_SIZE * 4;
shouldPause = true;
}
// Down Arrow = Scroll Down
if (downKeyHandler.activated && currentLiveInputStyle == None)
{
- scrollAmount = GRID_SIZE * 0.25 * 25.0;
+ scrollAmount = GRID_SIZE * 4;
shouldPause = true;
}
// W = Scroll Up (doesn't work with Ctrl+Scroll)
if (wKeyHandler.activated && currentLiveInputStyle == None && !FlxG.keys.pressed.CONTROL)
{
- scrollAmount = -GRID_SIZE * 0.25 * 25.0;
+ scrollAmount = -GRID_SIZE * 4;
shouldPause = true;
}
// S = Scroll Down (doesn't work with Ctrl+Scroll)
if (sKeyHandler.activated && currentLiveInputStyle == None && !FlxG.keys.pressed.CONTROL)
{
- scrollAmount = GRID_SIZE * 0.25 * 25.0;
+ scrollAmount = GRID_SIZE * 4;
shouldPause = true;
}
- // PAGE UP = Jump up to nearest measure
- if (pageUpKeyHandler.activated)
+ // GAMEPAD LEFT STICK UP = Scroll Up by 1 note snap
+ if (leftStickUpGamepadHandler.activated)
{
+ scrollAmount = -GRID_SIZE * noteSnapRatio;
+ shouldPause = true;
+ }
+ // GAMEPAD LEFT STICK DOWN = Scroll Down by 1 note snap
+ if (leftStickDownGamepadHandler.activated)
+ {
+ scrollAmount = GRID_SIZE * noteSnapRatio;
+ shouldPause = true;
+ }
+
+ // GAMEPAD RIGHT STICK UP = Scroll Up by 1 note snap (playhead only)
+ if (rightStickUpGamepadHandler.activated)
+ {
+ playheadAmount = -GRID_SIZE * noteSnapRatio;
+ shouldPause = true;
+ }
+ // GAMEPAD RIGHT STICK DOWN = Scroll Down by 1 note snap (playhead only)
+ if (rightStickDownGamepadHandler.activated)
+ {
+ playheadAmount = GRID_SIZE * noteSnapRatio;
+ shouldPause = true;
+ }
+
+ var funcJumpUp = (playheadOnly:Bool) -> {
var measureHeight:Float = GRID_SIZE * 4 * Conductor.instance.beatsPerMeasure;
var playheadPos:Float = scrollPositionInPixels + playheadPositionInPixels;
var targetScrollPosition:Float = Math.floor(playheadPos / measureHeight) * measureHeight;
@@ -3744,20 +3893,37 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
{
targetScrollPosition -= GRID_SIZE * Constants.STEPS_PER_BEAT * Conductor.instance.beatsPerMeasure;
}
- scrollAmount = targetScrollPosition - playheadPos;
+ if (playheadOnly)
+ {
+ playheadAmount = targetScrollPosition - playheadPos;
+ }
+ else
+ {
+ scrollAmount = targetScrollPosition - playheadPos;
+ }
+ }
+
+ // PAGE UP = Jump up to nearest measure
+ // GAMEPAD LEFT STICK LEFT = Jump up to nearest measure
+ if (pageUpKeyHandler.activated || leftStickLeftGamepadHandler.activated)
+ {
+ funcJumpUp(false);
+ shouldPause = true;
+ }
+ if (rightStickLeftGamepadHandler.activated)
+ {
+ funcJumpUp(true);
shouldPause = true;
}
if (playbarButtonPressed == 'playbarBack')
{
playbarButtonPressed = '';
- scrollAmount = -GRID_SIZE * 4 * Conductor.instance.beatsPerMeasure;
+ funcJumpUp(false);
shouldPause = true;
}
- // PAGE DOWN = Jump down to nearest measure
- if (pageDownKeyHandler.activated)
- {
+ var funcJumpDown = (playheadOnly:Bool) -> {
var measureHeight:Float = GRID_SIZE * 4 * Conductor.instance.beatsPerMeasure;
var playheadPos:Float = scrollPositionInPixels + playheadPositionInPixels;
var targetScrollPosition:Float = Math.ceil(playheadPos / measureHeight) * measureHeight;
@@ -3767,26 +3933,46 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
{
targetScrollPosition += GRID_SIZE * Constants.STEPS_PER_BEAT * Conductor.instance.beatsPerMeasure;
}
- scrollAmount = targetScrollPosition - playheadPos;
+ if (playheadOnly)
+ {
+ playheadAmount = targetScrollPosition - playheadPos;
+ }
+ else
+ {
+ scrollAmount = targetScrollPosition - playheadPos;
+ }
+ }
+
+ // PAGE DOWN = Jump down to nearest measure
+ // GAMEPAD LEFT STICK RIGHT = Jump down to nearest measure
+ if (pageDownKeyHandler.activated || leftStickRightGamepadHandler.activated)
+ {
+ funcJumpDown(false);
+ shouldPause = true;
+ }
+ if (rightStickRightGamepadHandler.activated)
+ {
+ funcJumpDown(true);
shouldPause = true;
}
if (playbarButtonPressed == 'playbarForward')
{
playbarButtonPressed = '';
- scrollAmount = GRID_SIZE * 4 * Conductor.instance.beatsPerMeasure;
+ funcJumpDown(false);
shouldPause = true;
}
// SHIFT + Scroll = Scroll Fast
- if (FlxG.keys.pressed.SHIFT)
+ // GAMEPAD LEFT STICK CLICK + Scroll = Scroll Fast
+ if (FlxG.keys.pressed.SHIFT || (FlxG.gamepads.firstActive?.pressed?.LEFT_STICK_CLICK ?? false))
{
scrollAmount *= 2;
}
// CONTROL + Scroll = Scroll Precise
if (FlxG.keys.pressed.CONTROL)
{
- scrollAmount /= 10;
+ scrollAmount /= 4;
}
// Alt + Drag = Scroll but move the playhead the same amount.
@@ -4380,9 +4566,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
}
gridGhostHoldNote.visible = true;
- gridGhostHoldNote.noteData = currentPlaceNoteData;
- gridGhostHoldNote.noteDirection = currentPlaceNoteData.getDirection();
-
+ gridGhostHoldNote.noteData = gridGhostNote.noteData;
+ gridGhostHoldNote.noteDirection = gridGhostNote.noteData.getDirection();
gridGhostHoldNote.setHeightDirectly(dragLengthPixels, true);
gridGhostHoldNote.updateHoldNotePosition(renderedHoldNotes);
@@ -4943,37 +5128,57 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
function handlePlayhead():Void
{
- // Place notes at the playhead.
+ // Place notes at the playhead with the keyboard.
switch (currentLiveInputStyle)
{
case ChartEditorLiveInputStyle.WASDKeys:
if (FlxG.keys.justPressed.A) placeNoteAtPlayhead(4);
+ if (FlxG.keys.justReleased.A) finishPlaceNoteAtPlayhead(4);
if (FlxG.keys.justPressed.S) placeNoteAtPlayhead(5);
+ if (FlxG.keys.justReleased.S) finishPlaceNoteAtPlayhead(5);
if (FlxG.keys.justPressed.W) placeNoteAtPlayhead(6);
+ if (FlxG.keys.justReleased.W) finishPlaceNoteAtPlayhead(6);
if (FlxG.keys.justPressed.D) placeNoteAtPlayhead(7);
+ if (FlxG.keys.justReleased.D) finishPlaceNoteAtPlayhead(7);
if (FlxG.keys.justPressed.LEFT) placeNoteAtPlayhead(0);
+ if (FlxG.keys.justReleased.LEFT) finishPlaceNoteAtPlayhead(0);
if (FlxG.keys.justPressed.DOWN) placeNoteAtPlayhead(1);
+ if (FlxG.keys.justReleased.DOWN) finishPlaceNoteAtPlayhead(1);
if (FlxG.keys.justPressed.UP) placeNoteAtPlayhead(2);
+ if (FlxG.keys.justReleased.UP) finishPlaceNoteAtPlayhead(2);
if (FlxG.keys.justPressed.RIGHT) placeNoteAtPlayhead(3);
+ if (FlxG.keys.justReleased.RIGHT) finishPlaceNoteAtPlayhead(3);
case ChartEditorLiveInputStyle.NumberKeys:
// Flipped because Dad is on the left but represents data 0-3.
if (FlxG.keys.justPressed.ONE) placeNoteAtPlayhead(4);
+ if (FlxG.keys.justReleased.ONE) finishPlaceNoteAtPlayhead(4);
if (FlxG.keys.justPressed.TWO) placeNoteAtPlayhead(5);
+ if (FlxG.keys.justReleased.TWO) finishPlaceNoteAtPlayhead(5);
if (FlxG.keys.justPressed.THREE) placeNoteAtPlayhead(6);
+ if (FlxG.keys.justReleased.THREE) finishPlaceNoteAtPlayhead(6);
if (FlxG.keys.justPressed.FOUR) placeNoteAtPlayhead(7);
+ if (FlxG.keys.justReleased.FOUR) finishPlaceNoteAtPlayhead(7);
if (FlxG.keys.justPressed.FIVE) placeNoteAtPlayhead(0);
+ if (FlxG.keys.justReleased.FIVE) finishPlaceNoteAtPlayhead(0);
if (FlxG.keys.justPressed.SIX) placeNoteAtPlayhead(1);
if (FlxG.keys.justPressed.SEVEN) placeNoteAtPlayhead(2);
+ if (FlxG.keys.justReleased.SEVEN) finishPlaceNoteAtPlayhead(2);
if (FlxG.keys.justPressed.EIGHT) placeNoteAtPlayhead(3);
+ if (FlxG.keys.justReleased.EIGHT) finishPlaceNoteAtPlayhead(3);
case ChartEditorLiveInputStyle.None:
// Do nothing.
}
+
+ updatePlayheadGhostHoldNotes();
}
function placeNoteAtPlayhead(column:Int):Void
{
+ // SHIFT + press or LEFT_SHOULDER + press to remove notes instead of placing them.
+ var removeNoteInstead:Bool = FlxG.keys.pressed.SHIFT || (FlxG.gamepads.firstActive?.pressed?.LEFT_SHOULDER ?? false);
+
var playheadPos:Float = scrollPositionInPixels + playheadPositionInPixels;
var playheadPosFractionalStep:Float = playheadPos / GRID_SIZE / noteSnapRatio;
var playheadPosStep:Int = Std.int(Math.floor(playheadPosFractionalStep));
@@ -4984,14 +5189,136 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
playheadPosSnappedMs + Conductor.instance.stepLengthMs * noteSnapRatio);
notesAtPos = SongDataUtils.getNotesWithData(notesAtPos, [column]);
- if (notesAtPos.length == 0)
+ if (notesAtPos.length == 0 && !removeNoteInstead)
{
+ trace('Placing note. ${column}');
var newNoteData:SongNoteData = new SongNoteData(playheadPosSnappedMs, column, 0, noteKindToPlace);
performCommand(new AddNotesCommand([newNoteData], FlxG.keys.pressed.CONTROL));
+ currentLiveInputPlaceNoteData[column] = newNoteData;
+ }
+ else if (removeNoteInstead)
+ {
+ trace('Removing existing note at position. ${column}');
+ performCommand(new RemoveNotesCommand(notesAtPos));
}
else
{
- trace('Already a note there.');
+ trace('Already a note there. ${column}');
+ }
+ }
+
+ function updatePlayheadGhostHoldNotes():Void
+ {
+ // Ensure all the ghost hold notes exist.
+ while (gridPlayheadGhostHoldNotes.length < (STRUMLINE_SIZE * 2))
+ {
+ var ghost = new ChartEditorHoldNoteSprite(this);
+ ghost.alpha = 0.6;
+ ghost.noteData = null;
+ ghost.visible = false;
+ ghost.zIndex = 11;
+ add(ghost); // Don't add to `renderedHoldNotes` because then it will get killed every frame.
+
+ gridPlayheadGhostHoldNotes.push(ghost);
+ refresh();
+ }
+
+ // Update playhead ghost hold notes.
+ for (column in 0...gridPlayheadGhostHoldNotes.length)
+ {
+ var targetNoteData = currentLiveInputPlaceNoteData[column];
+ var ghostHold = gridPlayheadGhostHoldNotes[column];
+
+ if (targetNoteData == null && ghostHold.noteData != null)
+ {
+ // Remove the ghost hold note.
+ ghostHold.noteData = null;
+ }
+
+ if (targetNoteData != null && ghostHold.noteData == null)
+ {
+ // Readd the new ghost hold note.
+ ghostHold.noteData = targetNoteData.clone();
+ ghostHold.noteDirection = ghostHold.noteData.getDirection();
+ ghostHold.visible = true;
+ ghostHold.alpha = 0.6;
+ ghostHold.setHeightDirectly(0);
+ ghostHold.updateHoldNotePosition(renderedHoldNotes);
+ }
+
+ if (ghostHold.noteData == null)
+ {
+ ghostHold.visible = false;
+ ghostHold.setHeightDirectly(0);
+ playheadDragLengthCurrent[column] = 0;
+ continue;
+ }
+
+ var playheadPos:Float = scrollPositionInPixels + playheadPositionInPixels;
+ var playheadPosFractionalStep:Float = playheadPos / GRID_SIZE / noteSnapRatio;
+ var playheadPosStep:Int = Std.int(Math.floor(playheadPosFractionalStep));
+ var playheadPosSnappedMs:Float = playheadPosStep * Conductor.instance.stepLengthMs * noteSnapRatio;
+
+ var newNoteLength:Float = playheadPosSnappedMs - ghostHold.noteData.time;
+ trace('newNoteLength: ${newNoteLength}');
+
+ if (newNoteLength > 0)
+ {
+ ghostHold.noteData.length = newNoteLength;
+ var targetNoteLengthSteps:Float = ghostHold.noteData.getStepLength(true);
+ var targetNoteLengthStepsInt:Int = Std.int(Math.floor(targetNoteLengthSteps));
+ var targetNoteLengthPixels:Float = targetNoteLengthSteps * GRID_SIZE;
+
+ if (playheadDragLengthCurrent[column] != targetNoteLengthStepsInt)
+ {
+ stretchySounds = !stretchySounds;
+ this.playSound(Paths.sound('chartingSounds/stretch' + (stretchySounds ? '1' : '2') + '_UI'));
+ playheadDragLengthCurrent[column] = targetNoteLengthStepsInt;
+ }
+ ghostHold.visible = true;
+ ghostHold.alpha = 0.6;
+ ghostHold.setHeightDirectly(targetNoteLengthPixels, true);
+ ghostHold.updateHoldNotePosition(renderedHoldNotes);
+ trace('lerpLength: ${ghostHold.fullSustainLength}');
+ trace('position: ${ghostHold.x}, ${ghostHold.y}');
+ }
+ else
+ {
+ ghostHold.visible = false;
+ ghostHold.setHeightDirectly(0);
+ playheadDragLengthCurrent[column] = 0;
+ continue;
+ }
+ }
+ }
+
+ function finishPlaceNoteAtPlayhead(column:Int):Void
+ {
+ if (currentLiveInputPlaceNoteData[column] == null) return;
+
+ var playheadPos:Float = scrollPositionInPixels + playheadPositionInPixels;
+ var playheadPosFractionalStep:Float = playheadPos / GRID_SIZE / noteSnapRatio;
+ var playheadPosStep:Int = Std.int(Math.floor(playheadPosFractionalStep));
+ var playheadPosSnappedMs:Float = playheadPosStep * Conductor.instance.stepLengthMs * noteSnapRatio;
+
+ var newNoteLength:Float = playheadPosSnappedMs - currentLiveInputPlaceNoteData[column].time;
+ trace('finishPlace newNoteLength: ${newNoteLength}');
+
+ if (newNoteLength < Conductor.instance.stepLengthMs)
+ {
+ // Don't extend the note if it's too short.
+ trace('Not extending note. ${column}');
+ currentLiveInputPlaceNoteData[column] = null;
+ gridPlayheadGhostHoldNotes[column].noteData = null;
+ }
+ else
+ {
+ // Extend the note to the playhead position.
+ trace('Extending note. ${column}');
+ this.playSound(Paths.sound('chartingSounds/stretchSNAP_UI'));
+ performCommand(new ExtendNoteLengthCommand(currentLiveInputPlaceNoteData[column], newNoteLength));
+ currentLiveInputPlaceNoteData[column] = null;
+ gridPlayheadGhostHoldNotes[column].noteData = null;
}
}
@@ -5382,7 +5709,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// Kill and replace the UI camera so it doesn't get destroyed during the state transition.
uiCamera.kill();
FlxG.cameras.remove(uiCamera, false);
- FlxG.cameras.reset(new FunkinCamera());
+ FlxG.cameras.reset(new FunkinCamera('chartEditorUI2'));
this.persistentUpdate = false;
this.persistentDraw = false;
diff --git a/source/funkin/ui/debug/charting/commands/RemoveEventsCommand.hx b/source/funkin/ui/debug/charting/commands/RemoveEventsCommand.hx
index b4d913607..30f4280d2 100644
--- a/source/funkin/ui/debug/charting/commands/RemoveEventsCommand.hx
+++ b/source/funkin/ui/debug/charting/commands/RemoveEventsCommand.hx
@@ -20,6 +20,8 @@ class RemoveEventsCommand implements ChartEditorCommand
public function execute(state:ChartEditorState):Void
{
+ if (events.length == 0) return;
+
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events);
state.currentEventSelection = [];
@@ -34,6 +36,8 @@ class RemoveEventsCommand implements ChartEditorCommand
public function undo(state:ChartEditorState):Void
{
+ if (events.length == 0) return;
+
for (event in events)
{
state.currentSongChartEventData.push(event);
diff --git a/source/funkin/ui/debug/charting/commands/RemoveItemsCommand.hx b/source/funkin/ui/debug/charting/commands/RemoveItemsCommand.hx
index 69317aff4..1cc61f233 100644
--- a/source/funkin/ui/debug/charting/commands/RemoveItemsCommand.hx
+++ b/source/funkin/ui/debug/charting/commands/RemoveItemsCommand.hx
@@ -23,6 +23,8 @@ class RemoveItemsCommand implements ChartEditorCommand
public function execute(state:ChartEditorState):Void
{
+ if ((notes.length + events.length) == 0) return;
+
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events);
@@ -40,6 +42,8 @@ class RemoveItemsCommand implements ChartEditorCommand
public function undo(state:ChartEditorState):Void
{
+ if ((notes.length + events.length) == 0) return;
+
for (note in notes)
{
state.currentSongChartNoteData.push(note);
diff --git a/source/funkin/ui/debug/charting/commands/RemoveNotesCommand.hx b/source/funkin/ui/debug/charting/commands/RemoveNotesCommand.hx
index 4811f831d..18ad6e04d 100644
--- a/source/funkin/ui/debug/charting/commands/RemoveNotesCommand.hx
+++ b/source/funkin/ui/debug/charting/commands/RemoveNotesCommand.hx
@@ -20,6 +20,8 @@ class RemoveNotesCommand implements ChartEditorCommand
public function execute(state:ChartEditorState):Void
{
+ if (notes.length == 0) return;
+
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
state.currentNoteSelection = [];
state.currentEventSelection = [];
@@ -35,6 +37,8 @@ class RemoveNotesCommand implements ChartEditorCommand
public function undo(state:ChartEditorState):Void
{
+ if (notes.length == 0) return;
+
for (note in notes)
{
state.currentSongChartNoteData.push(note);
diff --git a/source/funkin/ui/debug/charting/components/ChartEditorHoldNoteSprite.hx b/source/funkin/ui/debug/charting/components/ChartEditorHoldNoteSprite.hx
index c7f7747c0..aeb6dd0e4 100644
--- a/source/funkin/ui/debug/charting/components/ChartEditorHoldNoteSprite.hx
+++ b/source/funkin/ui/debug/charting/components/ChartEditorHoldNoteSprite.hx
@@ -54,11 +54,16 @@ class ChartEditorHoldNoteSprite extends SustainTrail
* Set the height directly, to a value in pixels.
* @param h The desired height in pixels.
*/
- public function setHeightDirectly(h:Float, ?lerp:Bool = false)
+ public function setHeightDirectly(h:Float, lerp:Bool = false)
{
- if (lerp != null && lerp) sustainLength = FlxMath.lerp(sustainLength, h / (getScrollSpeed() * Constants.PIXELS_PER_MS), 0.25);
+ if (lerp)
+ {
+ sustainLength = FlxMath.lerp(sustainLength, h / (getScrollSpeed() * Constants.PIXELS_PER_MS), 0.25);
+ }
else
+ {
sustainLength = h / (getScrollSpeed() * Constants.PIXELS_PER_MS);
+ }
fullSustainLength = sustainLength;
}
diff --git a/source/funkin/ui/debug/charting/components/ChartEditorNoteSprite.hx b/source/funkin/ui/debug/charting/components/ChartEditorNoteSprite.hx
index cd403c6f8..98f5a47aa 100644
--- a/source/funkin/ui/debug/charting/components/ChartEditorNoteSprite.hx
+++ b/source/funkin/ui/debug/charting/components/ChartEditorNoteSprite.hx
@@ -117,12 +117,6 @@ class ChartEditorNoteSprite extends FlxSprite
{
noteFrameCollection.pushFrame(frame);
}
- var frameCollectionNormal2:FlxAtlasFrames = Paths.getSparrowAtlas('NoteHoldNormal');
-
- for (frame in frameCollectionNormal2.frames)
- {
- noteFrameCollection.pushFrame(frame);
- }
// Pixel notes
var graphicPixel = FlxG.bitmap.add(Paths.image('weeb/pixelUI/arrows-pixels', 'week6'), false, null);
diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorGamepadHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorGamepadHandler.hx
new file mode 100644
index 000000000..70383d3fd
--- /dev/null
+++ b/source/funkin/ui/debug/charting/handlers/ChartEditorGamepadHandler.hx
@@ -0,0 +1,193 @@
+package funkin.ui.debug.charting.handlers;
+
+import haxe.ui.focus.FocusManager;
+import flixel.input.gamepad.FlxGamepad;
+import haxe.ui.actions.ActionManager;
+import haxe.ui.actions.IActionInputSource;
+import haxe.ui.actions.ActionType;
+
+/**
+ * Yes, we're that crazy. Gamepad support for the chart editor.
+ */
+// @:nullSafety
+
+@:access(funkin.ui.debug.charting.ChartEditorState)
+class ChartEditorGamepadHandler
+{
+ public static function handleGamepadControls(chartEditorState:ChartEditorState)
+ {
+ if (FlxG.gamepads.firstActive != null) handleGamepad(chartEditorState, FlxG.gamepads.firstActive);
+ }
+
+ /**
+ * Handle context-generic binds for the gamepad.
+ * @param chartEditorState The chart editor state.
+ * @param gamepad The gamepad to handle.
+ */
+ static function handleGamepad(chartEditorState:ChartEditorState, gamepad:FlxGamepad):Void
+ {
+ if (chartEditorState.isHaxeUIFocused)
+ {
+ ChartEditorGamepadActionInputSource.instance.handleGamepad(gamepad);
+ }
+ else
+ {
+ handleGamepadLiveInputs(chartEditorState, gamepad);
+
+ if (gamepad.justPressed.RIGHT_SHOULDER)
+ {
+ trace('Gamepad: Right shoulder pressed, toggling audio playback.');
+ chartEditorState.toggleAudioPlayback();
+ }
+
+ if (gamepad.justPressed.START)
+ {
+ var minimal = gamepad.pressed.LEFT_SHOULDER;
+ chartEditorState.hideAllToolboxes();
+ trace('Gamepad: Start pressed, opening playtest (minimal: ${minimal})');
+ chartEditorState.testSongInPlayState(minimal);
+ }
+
+ if (gamepad.justPressed.BACK && !gamepad.pressed.LEFT_SHOULDER)
+ {
+ trace('Gamepad: Back pressed, focusing on HaxeUI menu.');
+ // FocusManager.instance.focus = chartEditorState.menubarMenuFile;
+ }
+ else if (gamepad.justPressed.BACK && gamepad.pressed.LEFT_SHOULDER)
+ {
+ trace('Gamepad: Back pressed, unfocusing on HaxeUI menu.');
+ FocusManager.instance.focus = null;
+ }
+ }
+
+ if (gamepad.justPressed.GUIDE)
+ {
+ trace('Gamepad: Guide pressed, quitting chart editor.');
+ chartEditorState.quitChartEditor();
+ }
+ }
+
+ static function handleGamepadLiveInputs(chartEditorState:ChartEditorState, gamepad:FlxGamepad):Void
+ {
+ // Place notes at the playhead with the gamepad.
+ // Disable when we are interacting with HaxeUI.
+ if (!(chartEditorState.isHaxeUIFocused || chartEditorState.isHaxeUIDialogOpen))
+ {
+ if (gamepad.justPressed.DPAD_LEFT) chartEditorState.placeNoteAtPlayhead(4);
+ if (gamepad.justReleased.DPAD_LEFT) chartEditorState.finishPlaceNoteAtPlayhead(4);
+ if (gamepad.justPressed.DPAD_DOWN) chartEditorState.placeNoteAtPlayhead(5);
+ if (gamepad.justReleased.DPAD_DOWN) chartEditorState.finishPlaceNoteAtPlayhead(5);
+ if (gamepad.justPressed.DPAD_UP) chartEditorState.placeNoteAtPlayhead(6);
+ if (gamepad.justReleased.DPAD_UP) chartEditorState.finishPlaceNoteAtPlayhead(6);
+ if (gamepad.justPressed.DPAD_RIGHT) chartEditorState.placeNoteAtPlayhead(7);
+ if (gamepad.justReleased.DPAD_RIGHT) chartEditorState.finishPlaceNoteAtPlayhead(7);
+
+ if (gamepad.justPressed.X) chartEditorState.placeNoteAtPlayhead(0);
+ if (gamepad.justReleased.X) chartEditorState.finishPlaceNoteAtPlayhead(0);
+ if (gamepad.justPressed.A) chartEditorState.placeNoteAtPlayhead(1);
+ if (gamepad.justReleased.A) chartEditorState.finishPlaceNoteAtPlayhead(1);
+ if (gamepad.justPressed.Y) chartEditorState.placeNoteAtPlayhead(2);
+ if (gamepad.justReleased.Y) chartEditorState.finishPlaceNoteAtPlayhead(2);
+ if (gamepad.justPressed.B) chartEditorState.placeNoteAtPlayhead(3);
+ if (gamepad.justReleased.B) chartEditorState.finishPlaceNoteAtPlayhead(3);
+ }
+ }
+}
+
+class ChartEditorGamepadActionInputSource implements IActionInputSource
+{
+ public static var instance:ChartEditorGamepadActionInputSource = new ChartEditorGamepadActionInputSource();
+
+ public function new() {}
+
+ public function start():Void {}
+
+ /**
+ * Handle HaxeUI-specific binds for the gamepad.
+ * Only called when the HaxeUI menu is focused.
+ * @param chartEditorState The chart editor state.
+ * @param gamepad The gamepad to handle.
+ */
+ public function handleGamepad(gamepad:FlxGamepad):Void
+ {
+ if (gamepad.justPressed.DPAD_LEFT)
+ {
+ trace('Gamepad: DPAD_LEFT pressed, moving left.');
+ ActionManager.instance.actionStart(ActionType.LEFT, this);
+ }
+ else if (gamepad.justReleased.DPAD_LEFT)
+ {
+ ActionManager.instance.actionEnd(ActionType.LEFT, this);
+ }
+
+ if (gamepad.justPressed.DPAD_RIGHT)
+ {
+ trace('Gamepad: DPAD_RIGHT pressed, moving right.');
+ ActionManager.instance.actionStart(ActionType.RIGHT, this);
+ }
+ else if (gamepad.justReleased.DPAD_RIGHT)
+ {
+ ActionManager.instance.actionEnd(ActionType.RIGHT, this);
+ }
+
+ if (gamepad.justPressed.DPAD_UP)
+ {
+ trace('Gamepad: DPAD_UP pressed, moving up.');
+ ActionManager.instance.actionStart(ActionType.UP, this);
+ }
+ else if (gamepad.justReleased.DPAD_UP)
+ {
+ ActionManager.instance.actionEnd(ActionType.UP, this);
+ }
+
+ if (gamepad.justPressed.DPAD_DOWN)
+ {
+ trace('Gamepad: DPAD_DOWN pressed, moving down.');
+ ActionManager.instance.actionStart(ActionType.DOWN, this);
+ }
+ else if (gamepad.justReleased.DPAD_DOWN)
+ {
+ ActionManager.instance.actionEnd(ActionType.DOWN, this);
+ }
+
+ if (gamepad.justPressed.A)
+ {
+ trace('Gamepad: A pressed, confirmingg.');
+ ActionManager.instance.actionStart(ActionType.CONFIRM, this);
+ }
+ else if (gamepad.justReleased.A)
+ {
+ ActionManager.instance.actionEnd(ActionType.CONFIRM, this);
+ }
+
+ if (gamepad.justPressed.B)
+ {
+ trace('Gamepad: B pressed, cancelling.');
+ ActionManager.instance.actionStart(ActionType.CANCEL, this);
+ }
+ else if (gamepad.justReleased.B)
+ {
+ ActionManager.instance.actionEnd(ActionType.CANCEL, this);
+ }
+
+ if (gamepad.justPressed.LEFT_TRIGGER)
+ {
+ trace('Gamepad: LEFT_TRIGGER pressed, moving to previous item.');
+ ActionManager.instance.actionStart(ActionType.PREVIOUS, this);
+ }
+ else if (gamepad.justReleased.LEFT_TRIGGER)
+ {
+ ActionManager.instance.actionEnd(ActionType.PREVIOUS, this);
+ }
+
+ if (gamepad.justPressed.RIGHT_TRIGGER)
+ {
+ trace('Gamepad: RIGHT_TRIGGER pressed, moving to next item.');
+ ActionManager.instance.actionStart(ActionType.NEXT, this);
+ }
+ else if (gamepad.justReleased.RIGHT_TRIGGER)
+ {
+ ActionManager.instance.actionEnd(ActionType.NEXT, this);
+ }
+ }
+}
diff --git a/source/funkin/ui/debug/charting/import.hx b/source/funkin/ui/debug/charting/import.hx
index b0569e3bb..2c3d59ef7 100644
--- a/source/funkin/ui/debug/charting/import.hx
+++ b/source/funkin/ui/debug/charting/import.hx
@@ -5,6 +5,7 @@ package funkin.ui.debug.charting;
using funkin.ui.debug.charting.handlers.ChartEditorAudioHandler;
using funkin.ui.debug.charting.handlers.ChartEditorContextMenuHandler;
using funkin.ui.debug.charting.handlers.ChartEditorDialogHandler;
+using funkin.ui.debug.charting.handlers.ChartEditorGamepadHandler;
using funkin.ui.debug.charting.handlers.ChartEditorImportExportHandler;
using funkin.ui.debug.charting.handlers.ChartEditorNotificationHandler;
using funkin.ui.debug.charting.handlers.ChartEditorShortcutHandler;
diff --git a/source/funkin/ui/freeplay/AlbumRoll.hx b/source/funkin/ui/freeplay/AlbumRoll.hx
index 0b04fa4af..35facf131 100644
--- a/source/funkin/ui/freeplay/AlbumRoll.hx
+++ b/source/funkin/ui/freeplay/AlbumRoll.hx
@@ -1,5 +1,6 @@
package funkin.ui.freeplay;
+import funkin.graphics.adobeanimate.FlxAtlasSprite;
import flixel.FlxSprite;
import flixel.group.FlxSpriteGroup;
import flixel.util.FlxSort;
@@ -35,30 +36,47 @@ class AlbumRoll extends FlxSpriteGroup
return value;
}
- var albumArt:FunkinSprite;
- var albumTitle:FunkinSprite;
- var difficultyStars:DifficultyStars;
+ var newAlbumArt:FlxAtlasSprite;
+ // var difficultyStars:DifficultyStars;
var _exitMovers:Null;
var albumData:Album;
+ final animNames:Map = [
+ "volume1-active" => "ENTRANCE",
+ "volume2-active" => "ENTRANCE VOL2",
+ "volume3-active" => "ENTRANCE VOL3",
+ "volume1-trans" => "VOL1 TRANS",
+ "volume2-trans" => "VOL2 TRANS",
+ "volume3-trans" => "VOL3 TRANS",
+ "volume1-idle" => "VOL1 STILL",
+ "volume2-idle" => "VOL2 STILL",
+ "volume3-idle" => "VOL3 STILL",
+ ];
+
public function new()
{
super();
- albumTitle = new FunkinSprite(947, 491);
- albumTitle.visible = true;
- albumTitle.zIndex = 200;
- add(albumTitle);
+ newAlbumArt = new FlxAtlasSprite(0, 0, Paths.animateAtlas("freeplay/albumRoll/freeplayAlbum"));
+ newAlbumArt.visible = false;
+ newAlbumArt.onAnimationFinish.add(onAlbumFinish);
- difficultyStars = new DifficultyStars(140, 39);
+ add(newAlbumArt);
- difficultyStars.stars.visible = true;
- albumTitle.visible = false;
- // albumArtist.visible = false;
+ // difficultyStars = new DifficultyStars(140, 39);
+ // difficultyStars.stars.visible = false;
+ // add(difficultyStars);
+ }
- // var albumArtist:FlxSprite = new FlxSprite(1010, 607).loadGraphic(Paths.image('freeplay/albumArtist-kawaisprite'));
+ function onAlbumFinish(animName:String):Void
+ {
+ // Play the idle animation for the current album.
+ newAlbumArt.playAnimation(animNames.get('$albumId-idle'), false, false, true);
+
+ // End on the last frame and don't continue until playAnimation is called again.
+ // newAlbumArt.anim.pause();
}
/**
@@ -68,13 +86,8 @@ class AlbumRoll extends FlxSpriteGroup
{
if (albumId == null)
{
- albumArt.visible = false;
- albumTitle.visible = false;
- if (titleTimer != null)
- {
- titleTimer.cancel();
- titleTimer = null;
- }
+ // difficultyStars.stars.visible = false;
+ return;
}
albumData = AlbumRegistry.instance.fetchEntry(albumId);
@@ -86,41 +99,8 @@ class AlbumRoll extends FlxSpriteGroup
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())))
- {
- if (albumData.hasAlbumTitleAnimations())
- {
- albumTitle.loadSparrow(albumData.getAlbumTitleAssetKey());
- FlxAnimationUtil.addAtlasAnimations(albumTitle, albumData.getAlbumTitleAnimations());
- }
- else
- {
- albumTitle.loadGraphic(Paths.image(albumData.getAlbumTitleAssetKey()));
- }
- }
- else
- {
- albumTitle.visible = false;
- }
-
refresh();
}
@@ -146,33 +126,12 @@ class AlbumRoll extends FlxSpriteGroup
if (exitMovers == null) return;
- exitMovers.set([albumArt],
+ exitMovers.set([newAlbumArt],
{
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
- });
}
var titleTimer:Null = null;
@@ -182,42 +141,31 @@ class AlbumRoll extends FlxSpriteGroup
*/
public function playIntro():Void
{
- albumArt.visible = true;
- FlxTween.tween(albumArt, {x: 950, y: 320, angle: -340}, 0.5, {ease: FlxEase.elasticOut});
+ newAlbumArt.visible = true;
+ newAlbumArt.playAnimation(animNames.get('$albumId-active'), false, false, false);
- albumTitle.visible = false;
-
- if (titleTimer != null)
- {
- titleTimer.cancel();
- titleTimer = null;
- }
-
- titleTimer = new FlxTimer().start(0.75, function(_) {
- showTitle();
+ // difficultyStars.stars.visible = false;
+ new FlxTimer().start(0.75, function(_) {
+ // showTitle();
+ // showStars();
});
}
- public function setDifficultyStars(?difficulty:Int):Void
+ public function skipIntro():Void
{
- if (difficulty == null) return;
-
- difficultyStars.difficulty = difficulty;
+ newAlbumArt.playAnimation(animNames.get('$albumId-trans'), false, false, false);
}
- public function showTitle():Void
- {
- albumTitle.visible = true;
- albumTitle.animation.play('active');
- albumTitle.animation.finishCallback = (_) -> albumTitle.animation.play('idle');
- }
-
- /**
- * Make the album stars visible.
- */
- public function showStars():Void
- {
- // albumArtist.visible = false;
- difficultyStars.stars.visible = false;
- }
+ // public function setDifficultyStars(?difficulty:Int):Void
+ // {
+ // if (difficulty == null) return;
+ // difficultyStars.difficulty = difficulty;
+ // }
+ // /**
+ // * Make the album stars visible.
+ // */
+ // public function showStars():Void
+ // {
+ // difficultyStars.stars.visible = false; // true;
+ // }
}
diff --git a/source/funkin/ui/freeplay/DifficultyStars.hx b/source/funkin/ui/freeplay/DifficultyStars.hx
deleted file mode 100644
index 51526bcbe..000000000
--- a/source/funkin/ui/freeplay/DifficultyStars.hx
+++ /dev/null
@@ -1,106 +0,0 @@
-package funkin.ui.freeplay;
-
-import flixel.group.FlxSpriteGroup;
-import funkin.graphics.adobeanimate.FlxAtlasSprite;
-import funkin.graphics.shaders.HSVShader;
-
-class DifficultyStars extends FlxSpriteGroup
-{
- /**
- * Internal handler var for difficulty... ranges from 0... to 15
- * 0 is 1 star... 15 is 0 stars!
- */
- var curDifficulty(default, set):Int = 0;
-
- /**
- * Range between 0 and 15
- */
- public var difficulty(default, set):Int = 1;
-
- public var stars:FlxAtlasSprite;
-
- var flames:FreeplayFlames;
-
- var hsvShader:HSVShader;
-
- public function new(x:Float, y:Float)
- {
- super(x, y);
-
- hsvShader = new HSVShader();
-
- flames = new FreeplayFlames(0, 0);
- add(flames);
-
- stars = new FlxAtlasSprite(0, 0, Paths.animateAtlas("freeplay/freeplayStars"));
- stars.anim.play("diff stars");
- add(stars);
-
- stars.shader = hsvShader;
-
- for (memb in flames.members)
- memb.shader = hsvShader;
- }
-
- override function update(elapsed:Float):Void
- {
- super.update(elapsed);
-
- // "loops" the current animation
- // for clarity, the animation file looks like
- // frame : stars
- // 0-99: 1 star
- // 100-199: 2 stars
- // ......
- // 1300-1499: 15 stars
- // 1500 : 0 stars
- if (curDifficulty < 15 && stars.anim.curFrame >= (curDifficulty + 1) * 100)
- {
- stars.anim.play("diff stars", true, false, curDifficulty * 100);
- }
- }
-
- function set_difficulty(value:Int):Int
- {
- difficulty = value;
-
- if (difficulty <= 0)
- {
- difficulty = 0;
- curDifficulty = 15;
- }
- else if (difficulty <= 15)
- {
- difficulty = value;
- curDifficulty = difficulty - 1;
- }
- else
- {
- difficulty = 15;
- curDifficulty = difficulty - 1;
- }
-
- if (difficulty > 10) flames.flameCount = difficulty - 10;
- else
- flames.flameCount = 0;
-
- return difficulty;
- }
-
- function set_curDifficulty(value:Int):Int
- {
- curDifficulty = value;
- if (curDifficulty == 15)
- {
- stars.anim.play("diff stars", true, false, 1500);
- stars.anim.pause();
- }
- else
- {
- stars.anim.curFrame = Std.int(curDifficulty * 100);
- stars.anim.play("diff stars", true, false, curDifficulty * 100);
- }
-
- return curDifficulty;
- }
-}
diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx
index ea89596db..efd3473e4 100644
--- a/source/funkin/ui/freeplay/FreeplayState.hx
+++ b/source/funkin/ui/freeplay/FreeplayState.hx
@@ -470,11 +470,7 @@ class FreeplayState extends MusicBeatSubState
albumRoll.playIntro();
new FlxTimer().start(0.75, function(_) {
- albumRoll.showTitle();
- });
-
- new FlxTimer().start(35 / 24, function(_) {
- albumRoll.showStars();
+ // albumRoll.showTitle();
});
FlxTween.tween(grpDifficulties, {x: 90}, 0.6, {ease: FlxEase.quartOut});
@@ -521,7 +517,7 @@ class FreeplayState extends MusicBeatSubState
// var swag:Alphabet = new Alphabet(1, 0, 'swag');
- var funnyCam:FunkinCamera = new FunkinCamera(0, 0, FlxG.width, FlxG.height);
+ var funnyCam:FunkinCamera = new FunkinCamera('freeplayFunny', 0, 0, FlxG.width, FlxG.height);
funnyCam.bgColor = FlxColor.TRANSPARENT;
FlxG.cameras.add(funnyCam);
@@ -543,41 +539,14 @@ class FreeplayState extends MusicBeatSubState
* Given the current filter, rebuild the current song list.
*
* @param filterStuff A filter to apply to the song list (regex, startswith, all, favorite)
- * @param force
+ * @param force Whether the capsules should "jump" back in or not using their animation
* @param onlyIfChanged Only apply the filter if the song list has changed
*/
public function generateSongList(filterStuff:Null, force:Bool = false, onlyIfChanged:Bool = true):Void
{
var tempSongs:Array = songs;
- if (filterStuff != null)
- {
- switch (filterStuff.filterType)
- {
- case REGEXP:
- // filterStuff.filterData has a string with the first letter of the sorting range, and the second one
- // this creates a filter to return all the songs that start with a letter between those two
- var filterRegexp:EReg = new EReg('^[' + filterStuff.filterData + '].*', 'i');
- tempSongs = tempSongs.filter(str -> {
- if (str == null) return true; // Random
- return filterRegexp.match(str.songName);
- });
- case STARTSWITH:
- tempSongs = tempSongs.filter(str -> {
- if (str == null) return true; // Random
- return str.songName.toLowerCase().startsWith(filterStuff.filterData);
- });
- case ALL:
- // no filter!
- case FAVORITE:
- tempSongs = tempSongs.filter(str -> {
- if (str == null) return true; // Random
- return str.isFav;
- });
- default:
- // return all on default
- }
- }
+ if (filterStuff != null) tempSongs = sortSongs(tempSongs, filterStuff);
// Filter further by current selected difficulty.
if (currentDifficulty != null)
@@ -642,14 +611,7 @@ class FreeplayState extends MusicBeatSubState
funnyMenu.favIcon.visible = tempSongs[i].isFav;
funnyMenu.hsvShader = hsvShader;
- if (i < 8)
- {
- funnyMenu.initJumpIn(Math.min(i, 4), force);
- }
- else
- {
- funnyMenu.forcePosition();
- }
+ funnyMenu.forcePosition();
grpCapsules.add(funnyMenu);
}
@@ -662,6 +624,48 @@ class FreeplayState extends MusicBeatSubState
changeDiff();
}
+ /**
+ * Filters an array of songs based on a filter
+ * @param songsToFilter What data to use when filtering
+ * @param songFilter The filter to apply
+ * @return Array
+ */
+ public function sortSongs(songsToFilter:Array, songFilter:SongFilter):Array
+ {
+ switch (songFilter.filterType)
+ {
+ case REGEXP:
+ // filterStuff.filterData has a string with the first letter of the sorting range, and the second one
+ // this creates a filter to return all the songs that start with a letter between those two
+
+ // if filterData looks like "A-C", the regex should look something like this: ^[A-C].*
+ // to get every song that starts between A and C
+ var filterRegexp:EReg = new EReg('^[' + songFilter.filterData + '].*', 'i');
+ songsToFilter = songsToFilter.filter(str -> {
+ if (str == null) return true; // Random
+ return filterRegexp.match(str.songName);
+ });
+
+ case STARTSWITH:
+ // extra note: this is essentially a "search"
+
+ songsToFilter = songsToFilter.filter(str -> {
+ if (str == null) return true; // Random
+ return str.songName.toLowerCase().startsWith(songFilter.filterData);
+ });
+ case ALL:
+ // no filter!
+ case FAVORITE:
+ songsToFilter = songsToFilter.filter(str -> {
+ if (str == null) return true; // Random
+ return str.isFav;
+ });
+ default:
+ // return all on default
+ }
+ return songsToFilter;
+ }
+
var touchY:Float = 0;
var touchX:Float = 0;
var dxTouch:Float = 0;
@@ -1047,15 +1051,12 @@ class FreeplayState extends MusicBeatSubState
}
}
- // Set the difficulty star count on the right.
- albumRoll.setDifficultyStars(daSong?.songRating);
-
// Set the album graphic and play the animation if relevant.
var newAlbumId:String = daSong?.albumId;
if (albumRoll.albumId != newAlbumId)
{
albumRoll.albumId = newAlbumId;
- albumRoll.playIntro();
+ albumRoll.skipIntro();
}
}
@@ -1169,10 +1170,6 @@ class FreeplayState extends MusicBeatSubState
{
currentDifficulty = rememberedDifficulty;
}
-
- // Set the difficulty star count on the right.
- var daSong:Null = grpCapsules.members[curSelected]?.songData;
- albumRoll.setDifficultyStars(daSong?.songRating ?? 0);
}
function changeSelection(change:Int = 0):Void
diff --git a/source/funkin/ui/freeplay/LetterSort.hx b/source/funkin/ui/freeplay/LetterSort.hx
index 7e71b6aa1..e813c9198 100644
--- a/source/funkin/ui/freeplay/LetterSort.hx
+++ b/source/funkin/ui/freeplay/LetterSort.hx
@@ -39,7 +39,6 @@ class LetterSort extends FlxTypedSpriteGroup
var letter:FreeplayLetter = new FreeplayLetter(i * 80, 0, i);
letter.x += 50;
letter.y += 50;
- letter.ogY = y;
// letter.visible = false;
add(letter);
@@ -54,7 +53,7 @@ class LetterSort extends FlxTypedSpriteGroup
// don't put the last seperator
if (i == 4) continue;
- var sep:FlxSprite = new FlxSprite((i * 80) + 55, 20).loadGraphic(Paths.image("freeplay/seperator"));
+ var sep:FlxSprite = new FlxSprite((i * 80) + 60, 20).loadGraphic(Paths.image("freeplay/seperator"));
// sep.animation.play("seperator");
sep.color = letter.color.getDarkened(darkness);
add(sep);
@@ -70,7 +69,7 @@ class LetterSort extends FlxTypedSpriteGroup
changeSelection(0);
}
- override function update(elapsed:Float)
+ override function update(elapsed:Float):Void
{
super.update(elapsed);
@@ -81,7 +80,27 @@ class LetterSort extends FlxTypedSpriteGroup
}
}
- public function changeSelection(diff:Int = 0)
+ public function changeSelection(diff:Int = 0):Void
+ {
+ doLetterChangeAnims(diff);
+
+ var multiPosOrNeg:Float = diff > 0 ? 1 : -1;
+
+ // if we're moving left (diff < 0), we want control of the right arrow, and vice versa
+ var arrowToMove:FlxSprite = diff < 0 ? leftArrow : rightArrow;
+ arrowToMove.offset.x = 3 * multiPosOrNeg;
+
+ new FlxTimer().start(2 / 24, function(_) {
+ arrowToMove.offset.x = 0;
+ });
+ }
+
+ /**
+ * Buncho timers and stuff to move the letters and seperators
+ * Seperated out so we can call it again on letters with songs within them
+ * @param diff
+ */
+ function doLetterChangeAnims(diff:Int):Void
{
var ezTimer:Int->FlxSprite->Float->Void = function(frameNum:Int, spr:FlxSprite, offsetNum:Float) {
new FlxTimer().start(frameNum / 24, function(_) {
@@ -91,148 +110,119 @@ class LetterSort extends FlxTypedSpriteGroup
var positions:Array = [-10, -22, 2, 0];
- if (diff < 0)
+ // if we're moving left, we want to move the positions the same amount, but negative direciton
+ var multiPosOrNeg:Float = diff > 0 ? 1 : -1;
+
+ for (sep in grpSeperators)
{
- for (sep in grpSeperators)
- {
- ezTimer(0, sep, positions[0]);
- ezTimer(1, sep, positions[1]);
- ezTimer(2, sep, positions[2]);
- ezTimer(3, sep, positions[3]);
- }
-
- for (index => letter in letters)
- {
- letter.offset.x = positions[0];
-
- new FlxTimer().start(1 / 24, function(_) {
- letter.offset.x = positions[1];
- if (index == 0) letter.visible = false;
- });
-
- new FlxTimer().start(2 / 24, function(_) {
- letter.offset.x = positions[2];
- if (index == 0.) letter.visible = true;
- });
-
- if (index == 2)
- {
- ezTimer(3, letter, 0);
- // letter.offset.x = 0;
- continue;
- }
-
- ezTimer(3, letter, positions[3]);
- }
-
- leftArrow.offset.x = 3;
- new FlxTimer().start(2 / 24, function(_) {
- leftArrow.offset.x = 0;
- });
+ ezTimer(0, sep, positions[0] * multiPosOrNeg);
+ ezTimer(1, sep, positions[1] * multiPosOrNeg);
+ ezTimer(2, sep, positions[2] * multiPosOrNeg);
+ ezTimer(3, sep, positions[3] * multiPosOrNeg);
}
- else if (diff > 0)
+
+ for (index => letter in letters)
{
- for (sep in grpSeperators)
- {
- ezTimer(0, sep, -positions[0]);
- ezTimer(1, sep, -positions[1]);
- ezTimer(2, sep, -positions[2]);
- ezTimer(3, sep, -positions[3]);
- }
- // same timing and functions and shit as the left one... except to the right!!
+ letter.offset.x = positions[0] * multiPosOrNeg;
- for (index => letter in letters)
- {
- letter.offset.x = -positions[0];
-
- new FlxTimer().start(1 / 24, function(_) {
- letter.offset.x = -positions[1];
- if (index == 0) letter.visible = false;
- });
-
- new FlxTimer().start(2 / 24, function(_) {
- letter.offset.x = -positions[2];
- if (index == 0) letter.visible = true;
- });
-
- if (index == 2)
- {
- ezTimer(3, letter, 0);
- // letter.offset.x = 0;
- continue;
- }
-
- ezTimer(3, letter, -positions[3]);
- }
-
- rightArrow.offset.x = -3;
- new FlxTimer().start(2 / 24, function(_) {
- rightArrow.offset.x = 0;
+ new FlxTimer().start(1 / 24, function(_) {
+ letter.offset.x = positions[1] * multiPosOrNeg;
+ if (index == 0) letter.visible = false;
});
+
+ new FlxTimer().start(2 / 24, function(_) {
+ letter.offset.x = positions[2] * multiPosOrNeg;
+ if (index == 0.) letter.visible = true;
+ });
+
+ if (index == 2)
+ {
+ ezTimer(3, letter, 0);
+ // letter.offset.x = 0;
+ continue;
+ }
+
+ ezTimer(3, letter, positions[3] * multiPosOrNeg);
}
curSelection += diff;
- if (curSelection < 0) curSelection = letters[0].arr.length - 1;
- if (curSelection >= letters[0].arr.length) curSelection = 0;
+ if (curSelection < 0) curSelection = letters[0].regexLetters.length - 1;
+ if (curSelection >= letters[0].regexLetters.length) curSelection = 0;
for (letter in letters)
letter.changeLetter(diff, curSelection);
- if (changeSelectionCallback != null) changeSelectionCallback(letters[2].arr[letters[2].curLetter]); // bullshit and long lol!
+ if (changeSelectionCallback != null) changeSelectionCallback(letters[2].regexLetters[letters[2].curLetter]); // bullshit and long lol!
}
}
+/**
+ * The actual FlxAtlasSprite for the letters, with their animation code stuff and regex stuff
+ */
class FreeplayLetter extends FlxAtlasSprite
{
- public var arr:Array = [];
+ /**
+ * A preformatted array of letter strings, for use when doing regex
+ * ex: ['A-B', 'C-D', 'E-H', 'I-L' ...]
+ */
+ public var regexLetters:Array = [];
+ /**
+ * A preformatted array of the letters, for use when accessing symbol animation info
+ * ex: ['AB', 'CD', 'EH', 'IL' ...]
+ */
+ public var animLetters:Array = [];
+
+ /**
+ * The current letter in the regexLetters array this FreeplayLetter is on
+ */
public var curLetter:Int = 0;
- public var ogY:Float = 0;
-
public function new(x:Float, y:Float, ?letterInd:Int)
{
super(x, y, Paths.animateAtlas("freeplay/sortedLetters"));
- // frames = Paths.getSparrowAtlas("freeplay/letterStuff");
- // this.anim.play("AB");
- // trace(this.anim.symbolDictionary);
- var alphabet:String = "AB-CD-EH-I L-MN-OR-s-t-UZ";
- arr = alphabet.split("-");
- arr.insert(0, "ALL");
- arr.insert(0, "fav");
- arr.insert(0, "#");
+ // this is used for the regex
+ // /^[OR].*/gi doesn't work for showing the song Pico, so now it's
+ // /^[O-R].*/gi ant it works for displaying Pico
+ // https://regex101.com/r/bWFPfS/1
+ // we split by underscores, simply for nice lil convinience
+ var alphabet:String = 'A-B_C-D_E-H_I-L_M-N_O-R_S_T_U-Z';
+ regexLetters = alphabet.split('_');
+ regexLetters.insert(0, 'ALL');
+ regexLetters.insert(0, 'fav');
+ regexLetters.insert(0, '#');
- // trace(arr);
-
- // for (str in arr)
- // {
- // animation.addByPrefix(str, str + " "); // string followed by a space! intentional!
- // }
-
- // animation.addByPrefix("arrow", "mini arrow");
- // animation.addByPrefix("seperator", "seperator");
+ // the symbols from flash don't have dashes, so we clean this up for use with animations
+ // (we don't need to re-export, rule of thumb is to accomodate files named in flash from dave
+ // until we get him programming classes (and since i cant find the .fla file....))
+ animLetters = regexLetters.map(animLetter -> animLetter.replace('-', ''));
if (letterInd != null)
{
- this.anim.play(arr[letterInd] + " move");
+ this.anim.play(animLetters[letterInd] + " move");
this.anim.pause();
curLetter = letterInd;
}
}
- public function changeLetter(diff:Int = 0, ?curSelection:Int)
+ /**
+ * Changes the letter graphic/anim, used in the LetterSort class above
+ * @param diff -1 or 1, to go left or right in the animation array
+ * @param curSelection what the current letter selection is, to play the bouncing anim if it matches the current letter
+ */
+ public function changeLetter(diff:Int = 0, ?curSelection:Int):Void
{
curLetter += diff;
- if (curLetter < 0) curLetter = arr.length - 1;
- if (curLetter >= arr.length) curLetter = 0;
+ if (curLetter < 0) curLetter = regexLetters.length - 1;
+ if (curLetter >= regexLetters.length) curLetter = 0;
- var animName:String = arr[curLetter] + " move";
+ var animName:String = animLetters[curLetter] + ' move';
- switch (arr[curLetter])
+ switch (animLetters[curLetter])
{
- case "I L":
+ case "IL":
animName = "IL move";
case "s":
animName = "S move";
diff --git a/source/funkin/ui/freeplay/SongMenuItem.hx b/source/funkin/ui/freeplay/SongMenuItem.hx
index bfb40c9ac..f6d85e56e 100644
--- a/source/funkin/ui/freeplay/SongMenuItem.hx
+++ b/source/funkin/ui/freeplay/SongMenuItem.hx
@@ -46,7 +46,7 @@ class SongMenuItem extends FlxSpriteGroup
public var hsvShader(default, set):HSVShader;
- var diffRatingSprite:FlxSprite;
+ // var diffRatingSprite:FlxSprite;
public function new(x:Float, y:Float)
{
@@ -65,13 +65,13 @@ class SongMenuItem extends FlxSpriteGroup
var rank:String = FlxG.random.getObject(ranks);
ranking = new FlxSprite(capsule.width * 0.84, 30);
- ranking.loadGraphic(Paths.image('freeplay/ranks/' + rank));
- ranking.scale.x = ranking.scale.y = realScaled;
+ // ranking.loadGraphic(Paths.image('freeplay/ranks/' + rank));
+ // ranking.scale.x = ranking.scale.y = realScaled;
// ranking.alpha = 0.75;
- ranking.visible = false;
- ranking.origin.set(capsule.origin.x - ranking.x, capsule.origin.y - ranking.y);
- add(ranking);
- grpHide.add(ranking);
+ // ranking.visible = false;
+ // ranking.origin.set(capsule.origin.x - ranking.x, capsule.origin.y - ranking.y);
+ // add(ranking);
+ // grpHide.add(ranking);
switch (rank)
{
@@ -81,9 +81,9 @@ class SongMenuItem extends FlxSpriteGroup
grayscaleShader = new Grayscale(1);
- diffRatingSprite = new FlxSprite(145, 90).loadGraphic(Paths.image('freeplay/diffRatings/diff00'));
+ // diffRatingSprite = new FlxSprite(145, 90).loadGraphic(Paths.image('freeplay/diffRatings/diff00'));
// diffRatingSprite.shader = grayscaleShader;
- diffRatingSprite.origin.set(capsule.origin.x - diffRatingSprite.x, capsule.origin.y - diffRatingSprite.y);
+ // diffRatingSprite.origin.set(capsule.origin.x - diffRatingSprite.x, capsule.origin.y - diffRatingSprite.y);
// TODO: Readd once ratings are fully implemented
// add(diffRatingSprite);
// grpHide.add(diffRatingSprite);
@@ -118,8 +118,8 @@ class SongMenuItem extends FlxSpriteGroup
function updateDifficultyRating(newRating:Int):Void
{
var ratingPadded:String = newRating < 10 ? '0$newRating' : '$newRating';
- diffRatingSprite.loadGraphic(Paths.image('freeplay/diffRatings/diff${ratingPadded}'));
- diffRatingSprite.visible = false;
+ // diffRatingSprite.loadGraphic(Paths.image('freeplay/diffRatings/diff${ratingPadded}'));
+ // diffRatingSprite.visible = false;
}
function set_hsvShader(value:HSVShader):HSVShader
diff --git a/source/funkin/ui/haxeui/FlxGamepadActionInputSource.hx b/source/funkin/ui/haxeui/FlxGamepadActionInputSource.hx
new file mode 100644
index 000000000..9c2901d16
--- /dev/null
+++ b/source/funkin/ui/haxeui/FlxGamepadActionInputSource.hx
@@ -0,0 +1,53 @@
+package funkin.ui.haxeui;
+
+import flixel.FlxBasic;
+import flixel.input.gamepad.FlxGamepad;
+import haxe.ui.actions.IActionInputSource;
+
+/**
+ * Receives button presses from the Flixel gamepad and emits HaxeUI events.
+ */
+class FlxGamepadActionInputSource extends FlxBasic
+{
+ public static var instance(get, null):FlxGamepadActionInputSource;
+
+ static function get_instance():FlxGamepadActionInputSource
+ {
+ if (instance == null) instance = new FlxGamepadActionInputSource();
+ return instance;
+ }
+
+ public function new()
+ {
+ super();
+ }
+
+ public function start():Void
+ {
+ FlxG.plugins.addPlugin(this);
+ }
+
+ public override function update(elapsed:Float):Void
+ {
+ super.update(elapsed);
+
+ if (FlxG.gamepads.firstActive != null)
+ {
+ updateGamepad(elapsed, FlxG.gamepads.firstActive);
+ }
+ }
+
+ function updateGamepad(elapsed:Float, gamepad:FlxGamepad):Void
+ {
+ if (gamepad.justPressed.BACK)
+ {
+ //
+ }
+ }
+
+ public override function destroy():Void
+ {
+ super.destroy();
+ FlxG.plugins.remove(this);
+ }
+}
diff --git a/source/funkin/ui/mainmenu/MainMenuState.hx b/source/funkin/ui/mainmenu/MainMenuState.hx
index f38db1ccd..90d79ccc1 100644
--- a/source/funkin/ui/mainmenu/MainMenuState.hx
+++ b/source/funkin/ui/mainmenu/MainMenuState.hx
@@ -1,5 +1,6 @@
package funkin.ui.mainmenu;
+import funkin.graphics.FunkinSprite;
import flixel.addons.transition.FlxTransitionableState;
import funkin.ui.debug.DebugMenuSubState;
import flixel.FlxObject;
@@ -67,7 +68,7 @@ class MainMenuState extends MusicBeatState
camFollow = new FlxObject(0, 0, 1, 1);
add(camFollow);
- magenta = new FlxSprite(Paths.image('menuDesat'));
+ magenta = new FlxSprite(Paths.image('menuBGMagenta'));
magenta.scrollFactor.x = bg.scrollFactor.x;
magenta.scrollFactor.y = bg.scrollFactor.y;
magenta.setGraphicSize(Std.int(bg.width));
@@ -75,7 +76,6 @@ class MainMenuState extends MusicBeatState
magenta.x = bg.x;
magenta.y = bg.y;
magenta.visible = false;
- magenta.color = 0xFFfd719b;
// TODO: Why doesn't this line compile I'm going fucking feral
@@ -172,8 +172,9 @@ class MainMenuState extends MusicBeatState
function resetCamStuff()
{
- FlxG.cameras.reset(new FunkinCamera());
+ FlxG.cameras.reset(new FunkinCamera('mainMenu'));
FlxG.camera.follow(camFollow, null, 0.06);
+ FlxG.camera.snapToTarget();
}
function createMenuItem(name:String, atlas:String, callback:Void->Void, fireInstantly:Bool = false):Void
@@ -322,10 +323,37 @@ class MainMenuState extends MusicBeatState
}
// Open the debug menu, defaults to ` / ~
+ #if CHART_EDITOR_SUPPORTED
if (controls.DEBUG_MENU)
{
FlxG.state.openSubState(new DebugMenuSubState());
}
+ #end
+
+ #if (debug || FORCE_DEBUG_VERSION)
+ if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.W)
+ {
+ // Give the user a score of 1 point on Weekend 1 story mode.
+ // This makes the level count as cleared and displays the songs in Freeplay.
+ funkin.save.Save.instance.setLevelScore('weekend1', 'easy',
+ {
+ score: 1,
+ tallies:
+ {
+ sick: 0,
+ good: 0,
+ bad: 0,
+ shit: 0,
+ missed: 0,
+ combo: 0,
+ maxCombo: 0,
+ totalNotesHit: 0,
+ totalNotes: 0,
+ },
+ accuracy: 0,
+ });
+ }
+ #end
if (FlxG.sound.music.volume < 0.8)
{
diff --git a/source/funkin/ui/options/ControlsMenu.hx b/source/funkin/ui/options/ControlsMenu.hx
index 62ae4b1a9..dd7d5ff38 100644
--- a/source/funkin/ui/options/ControlsMenu.hx
+++ b/source/funkin/ui/options/ControlsMenu.hx
@@ -48,7 +48,7 @@ class ControlsMenu extends funkin.ui.options.OptionsState.Page
{
super();
- menuCamera = new FunkinCamera();
+ menuCamera = new FunkinCamera('controlsMenu');
FlxG.cameras.add(menuCamera, false);
menuCamera.bgColor = 0x0;
camera = menuCamera;
diff --git a/source/funkin/ui/options/OptionsState.hx b/source/funkin/ui/options/OptionsState.hx
index f3d6d6fe4..81331b266 100644
--- a/source/funkin/ui/options/OptionsState.hx
+++ b/source/funkin/ui/options/OptionsState.hx
@@ -9,6 +9,7 @@ import flixel.util.FlxSignal;
import funkin.audio.FunkinSound;
import funkin.ui.mainmenu.MainMenuState;
import funkin.ui.MusicBeatState;
+import funkin.graphics.shaders.HSVShader;
import funkin.util.WindowUtil;
import funkin.audio.FunkinSound;
import funkin.input.Controls;
@@ -19,13 +20,18 @@ class OptionsState extends MusicBeatState
var currentName:PageName = Options;
var currentPage(get, never):Page;
- inline function get_currentPage()
+ inline function get_currentPage():Page
return pages[currentName];
- override function create()
+ override function create():Void
{
- var menuBG = new FlxSprite().loadGraphic(Paths.image('menuDesat'));
- menuBG.color = 0xFFea71fd;
+ var menuBG = new FlxSprite().loadGraphic(Paths.image('menuBG'));
+ var hsv = new HSVShader();
+ hsv.hue = -0.6;
+ hsv.saturation = 0.9;
+ hsv.value = 3.6;
+ menuBG.shader = hsv;
+ FlxG.debugger.track(hsv);
menuBG.setGraphicSize(Std.int(menuBG.width * 1.1));
menuBG.updateHitbox();
menuBG.screenCenter();
diff --git a/source/funkin/ui/options/PreferencesMenu.hx b/source/funkin/ui/options/PreferencesMenu.hx
index c23c3f165..783aef0ba 100644
--- a/source/funkin/ui/options/PreferencesMenu.hx
+++ b/source/funkin/ui/options/PreferencesMenu.hx
@@ -21,7 +21,7 @@ class PreferencesMenu extends Page
{
super();
- menuCamera = new FunkinCamera();
+ menuCamera = new FunkinCamera('prefMenu');
FlxG.cameras.add(menuCamera, false);
menuCamera.bgColor = 0x0;
camera = menuCamera;
diff --git a/source/funkin/ui/story/StoryMenuState.hx b/source/funkin/ui/story/StoryMenuState.hx
index 5be31fa71..d8bfd59fb 100644
--- a/source/funkin/ui/story/StoryMenuState.hx
+++ b/source/funkin/ui/story/StoryMenuState.hx
@@ -344,6 +344,17 @@ class StoryMenuState extends MusicBeatState
changeDifficulty(0);
}
+ // TODO: Querying UI_RIGHT_P (justPressed) after UI_RIGHT always returns false. Fix it!
+ if (controls.UI_RIGHT_P)
+ {
+ changeDifficulty(1);
+ }
+
+ if (controls.UI_LEFT_P)
+ {
+ changeDifficulty(-1);
+ }
+
if (controls.UI_RIGHT)
{
rightDifficultyArrow.animation.play('press');
@@ -362,16 +373,6 @@ class StoryMenuState extends MusicBeatState
leftDifficultyArrow.animation.play('idle');
}
- if (controls.UI_RIGHT_P)
- {
- changeDifficulty(1);
- }
-
- if (controls.UI_LEFT_P)
- {
- changeDifficulty(-1);
- }
-
if (FlxG.keys.justPressed.TAB && modeText.visible)
{
switchMode(!displayingModdedLevels);
diff --git a/source/funkin/ui/title/TitleState.hx b/source/funkin/ui/title/TitleState.hx
index e76e66003..d122bf7c1 100644
--- a/source/funkin/ui/title/TitleState.hx
+++ b/source/funkin/ui/title/TitleState.hx
@@ -188,6 +188,7 @@ class TitleState extends MusicBeatState
ngSpr.animation.add('idle', [0, 1], 4);
ngSpr.animation.play('idle');
ngSpr.setGraphicSize(Std.int(ngSpr.width * 0.55));
+ ngSpr.y += 25;
}
else
{
diff --git a/source/funkin/ui/transition/LoadingState.hx b/source/funkin/ui/transition/LoadingState.hx
index af8798ae2..3b53e1b4a 100644
--- a/source/funkin/ui/transition/LoadingState.hx
+++ b/source/funkin/ui/transition/LoadingState.hx
@@ -77,7 +77,7 @@ class LoadingState extends MusicBeatSubState
var difficulty:String = playParams.targetDifficulty ?? Constants.DEFAULT_DIFFICULTY;
var variation:String = playParams.targetVariation ?? Constants.DEFAULT_VARIATION;
var targetChart:SongDifficulty = playParams.targetSong?.getDifficulty(difficulty, variation);
- var instPath:String = Paths.inst(targetChart.song.id);
+ var instPath:String = targetChart.getInstPath(playParams.targetInstrumental);
var voicesPaths:Array = targetChart.buildVoiceList();
checkLoadSong(instPath);
@@ -281,7 +281,6 @@ class LoadingState extends MusicBeatSubState
{
// TODO: This section is a hack! Redo this later when we have a proper asset caching system.
FunkinSprite.preparePurgeCache();
- FunkinSprite.cacheTexture(Paths.image('combo'));
FunkinSprite.cacheTexture(Paths.image('healthBar'));
FunkinSprite.cacheTexture(Paths.image('menuDesat'));
FunkinSprite.cacheTexture(Paths.image('combo'));
@@ -333,6 +332,7 @@ class LoadingState extends MusicBeatSubState
// Since FlxGraphic tells OpenFL to not cache it, we have to do it manually.
if (path.endsWith('spritemap1.png'))
{
+ trace('Preloading FlxAnimate asset: ${path}');
openfl.Assets.getBitmapData(path, true);
}
}
diff --git a/source/funkin/ui/transition/StickerSubState.hx b/source/funkin/ui/transition/StickerSubState.hx
index 0b5e16f97..e5abef872 100644
--- a/source/funkin/ui/transition/StickerSubState.hx
+++ b/source/funkin/ui/transition/StickerSubState.hx
@@ -247,10 +247,6 @@ class StickerSubState extends MusicBeatSubState
FlxTransitionableState.skipNextTransIn = true;
FlxTransitionableState.skipNextTransOut = true;
- // TODO: Rework this asset caching stuff
- FunkinSprite.preparePurgeCache();
- FunkinSprite.purgeCache();
-
// I think this grabs the screen and puts it under the stickers?
// Leaving this commented out rather than stripping it out because it's cool...
/*
@@ -265,7 +261,15 @@ class StickerSubState extends MusicBeatSubState
// FlxG.addChildBelowMouse(dipshit);
*/
- FlxG.switchState(() -> targetState(this));
+ FlxG.switchState(() -> {
+ // TODO: Rework this asset caching stuff
+ // NOTE: This has to come AFTER the state switch,
+ // otherwise the game tries to render destroyed sprites!
+ FunkinSprite.preparePurgeCache();
+ FunkinSprite.purgeCache();
+
+ return targetState(this);
+ });
}
});
});
diff --git a/source/funkin/util/Constants.hx b/source/funkin/util/Constants.hx
index ebc58889f..568f9ec5a 100644
--- a/source/funkin/util/Constants.hx
+++ b/source/funkin/util/Constants.hx
@@ -218,14 +218,15 @@ class Constants
public static final DEFAULT_VARIATION:String = 'default';
/**
- * Standard variations used by the game.
+ * Standardized variations for charts
*/
public static final DEFAULT_VARIATION_LIST:Array = ['default', 'erect', 'pico'];
/**
- * The default intensity for camera zooms.
+ * The default intensity multiplier for camera bops.
+ * Prolly needs to be tuned bc it's a multiplier now.
*/
- public static final DEFAULT_ZOOM_INTENSITY:Float = 0.015;
+ public static final DEFAULT_BOP_INTENSITY:Float = 1.015;
/**
* The default rate for camera zooms (in beats per zoom).
diff --git a/source/funkin/util/EaseUtil.hx b/source/funkin/util/EaseUtil.hx
new file mode 100644
index 000000000..200e74d07
--- /dev/null
+++ b/source/funkin/util/EaseUtil.hx
@@ -0,0 +1,17 @@
+package funkin.util;
+
+class EaseUtil
+{
+ /**
+ * Returns an ease function that eases via steps.
+ * Useful for "retro" style fades (week 6!)
+ * @param steps how many steps to ease over
+ * @return Float->Float
+ */
+ public static inline function stepped(steps:Int):Float->Float
+ {
+ return function(t:Float):Float {
+ return Math.floor(t * steps) / steps;
+ }
+ }
+}
diff --git a/source/funkin/util/FlxTweenUtil.hx b/source/funkin/util/FlxTweenUtil.hx
new file mode 100644
index 000000000..57c8f0cfe
--- /dev/null
+++ b/source/funkin/util/FlxTweenUtil.hx
@@ -0,0 +1,36 @@
+package funkin.util;
+
+import flixel.addons.plugin.taskManager.FlxTask;
+import flixel.tweens.FlxTween;
+import flixel.tweens.FlxEase;
+
+class FlxTweenUtil
+{
+ public static function pauseTween(tween:FlxTween):Void
+ {
+ if (tween != null)
+ {
+ tween.active = false;
+ }
+ }
+
+ public static function resumeTween(tween:FlxTween):Void
+ {
+ if (tween != null)
+ {
+ tween.active = true;
+ }
+ }
+
+ public static function pauseTweensOf(Object:Dynamic, ?FieldPaths:Array):Void
+ {
+ @:privateAccess
+ FlxTween.globalManager.forEachTweensOf(Object, FieldPaths, pauseTween);
+ }
+
+ public static function resumeTweensOf(Object:Dynamic, ?FieldPaths:Array):Void
+ {
+ @:privateAccess
+ FlxTween.globalManager.forEachTweensOf(Object, FieldPaths, resumeTween);
+ }
+}