mirror of
https://github.com/FunkinCrew/Funkin.git
synced 2024-11-27 01:55:52 -05:00
Merge remote-tracking branch 'origin/rewrite/master' into feature/2hot-cutscenes
This commit is contained in:
commit
4fcec49943
92 changed files with 3874 additions and 1124 deletions
135
.github/actions/setup-haxe/action.yml
vendored
Normal file
135
.github/actions/setup-haxe/action.yml
vendored
Normal file
|
@ -0,0 +1,135 @@
|
|||
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
|
||||
|
||||
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: Install dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
haxelib --debug --global run hmm install
|
||||
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
|
55
.github/actions/setup-haxeshit/action.yml
vendored
55
.github/actions/setup-haxeshit/action.yml
vendored
|
@ -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
|
142
.github/actions/upload-itch/action.yml
vendored
142
.github/actions/upload-itch/action.yml
vendored
|
@ -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
|
||||
|
|
53
.github/workflows/build-docker-image.yml
vendored
Normal file
53
.github/workflows/build-docker-image.yml
vendored
Normal file
|
@ -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 }}
|
125
.github/workflows/build-game.yml
vendored
Normal file
125
.github/workflows/build-game.yml
vendored
Normal file
|
@ -0,0 +1,125 @@
|
|||
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
|
||||
if: ${{ matrix.target == 'macos' }}
|
||||
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: Setup build environment
|
||||
uses: ./.github/actions/setup-haxe
|
||||
|
||||
- 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 }}
|
||||
|
||||
- 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: |
|
||||
haxelib --global run hmm install
|
||||
|
||||
- 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 }}
|
132
.github/workflows/build-shit.yml
vendored
132
.github/workflows/build-shit.yml
vendored
|
@ -1,132 +0,0 @@
|
|||
name: build-upload
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
|
||||
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
|
38
.github/workflows/cancel-merged-branches.yml
vendored
Normal file
38
.github/workflows/cancel-merged-branches.yml
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
name: Cancel queued workflows on PR merge
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- closed
|
||||
|
||||
jobs:
|
||||
|
||||
cancel_stuff:
|
||||
if: github.event.pull_request.merged == true
|
||||
runs-on: build-set
|
||||
permissions:
|
||||
actions: write
|
||||
|
||||
steps:
|
||||
- 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,
|
||||
run_id: run.id
|
||||
});
|
||||
});
|
||||
console.log(runs);
|
15
.vscode/settings.json
vendored
15
.vscode/settings.json
vendored
|
@ -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",
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
<library name="week6" preload="true" />
|
||||
<library name="week7" preload="true" />
|
||||
<library name="weekend1" preload="true" />
|
||||
<library name="videos" preload="true" />
|
||||
</section>
|
||||
<section if="NO_PRELOAD_ALL">
|
||||
<library name="songs" preload="false" />
|
||||
|
@ -58,10 +59,13 @@
|
|||
<library name="week6" preload="false" />
|
||||
<library name="week7" preload="false" />
|
||||
<library name="weekend1" preload="false" />
|
||||
<library name="videos" preload="false" />
|
||||
</section>
|
||||
<library name="art" preload="false" />
|
||||
<assets path="assets/songs" library="songs" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||
<assets path="assets/songs" library="songs" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||
<!-- Videos go in their own library because web never needs to preload them, they can just be streamed. -->
|
||||
<assets path="assets/videos" library="videos" />
|
||||
<assets path="assets/shared" library="shared" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||
<assets path="assets/shared" library="shared" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||
<assets path="assets/tutorial" library="tutorial" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||
|
@ -119,7 +123,9 @@
|
|||
<haxelib name="flixel-text-input" /> <!-- Improved text field rendering for HaxeUI -->
|
||||
<haxelib name="polymod" /> <!-- Modding framework -->
|
||||
<haxelib name="flxanimate" /> <!-- Texture atlas rendering -->
|
||||
<haxelib name="hxCodec" if="desktop" /> <!-- Video playback -->
|
||||
<haxelib name="hxCodec" if="desktop" unless="hl" /> <!-- Video playback -->
|
||||
<haxelib name="funkVis"/>
|
||||
|
||||
|
||||
<haxelib name="json2object" /> <!-- JSON parsing -->
|
||||
<haxelib name="thx.semver" /> <!-- Version string handling -->
|
||||
|
@ -183,6 +189,7 @@
|
|||
<haxedef name="haxeui_focus_out_on_click" />
|
||||
<!-- Required to use haxe.ui.backend.flixel.UIState with build macros. -->
|
||||
<haxedef name="haxeui_dont_impose_base_class" />
|
||||
<haxedef name="HARDCODED_CREDITS" />
|
||||
|
||||
<!-- Skip the Intro -->
|
||||
<section if="debug">
|
||||
|
|
2
art
2
art
|
@ -1 +1 @@
|
|||
Subproject commit 03e7c2a2353b184e45955c96d763b7cdf1acbc34
|
||||
Subproject commit 00463685fa570f0c853d08e250b46ef80f30bc48
|
2
assets
2
assets
|
@ -1 +1 @@
|
|||
Subproject commit 9c35ee01c5305ba04cde618bc224535e56c051fb
|
||||
Subproject commit c25a4119d6c37fa5ff49533111bd797e6fe2d7b1
|
185
build/Dockerfile
Normal file
185
build/Dockerfile
Normal file
|
@ -0,0 +1,185 @@
|
|||
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 <<EOF
|
||||
mkdir -p "$GITHUB_HOME"
|
||||
mkdir -p /opt
|
||||
mkdir -p /usr/share/hxcpp
|
||||
mkdir -p /usr/local/bin
|
||||
chmod -R 777 /opt
|
||||
chmod -R 777 /usr/share
|
||||
chmod -R 777 /usr/local/bin
|
||||
EOF
|
||||
|
||||
|
||||
# Prepare Ubuntu
|
||||
# https://github.com/actions/runner-images/blob/main/images/ubuntu/scripts/build/configure-environment.sh
|
||||
# https://github.com/actions/runner-images/blob/main/images/ubuntu/scripts/build/configure-system.sh
|
||||
RUN <<EOF
|
||||
echo 'vm.max_map_count=262144' | tee -a /etc/sysctl.conf
|
||||
echo 'fs.inotify.max_user_watches=655360' | tee -a /etc/sysctl.conf
|
||||
echo 'fs.inotify.max_user_instances=1280' | tee -a /etc/sysctl.conf
|
||||
EOF
|
||||
|
||||
ENV DEBIAN_FRONTEND="noninteractive"
|
||||
|
||||
# Prepare APT
|
||||
RUN <<EOF
|
||||
cat <<EOC >> /etc/apt/apt.conf.d/10apt-autoremove
|
||||
APT::Get::AutomaticRemove "0";
|
||||
APT::Get::HideAutoRemove "1";
|
||||
EOC
|
||||
|
||||
echo <<EOC >> /etc/apt/apt.conf.d/80retries
|
||||
"APT::Acquire::Retries \"10\";"
|
||||
EOC
|
||||
|
||||
echo <<EOC >> /etc/apt/apt.conf.d/90assumeyes
|
||||
"APT::Get::Assume-Yes \"true\";"
|
||||
EOC
|
||||
EOF
|
||||
|
||||
# Prepare apt-fast
|
||||
RUN <<EOF
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends software-properties-common
|
||||
add-apt-repository -y ppa:apt-fast/stable
|
||||
apt-get -y install apt-fast
|
||||
echo debconf apt-fast/maxdownloads string 8 | debconf-set-selections
|
||||
echo debconf apt-fast/dlflag boolean true | debconf-set-selections
|
||||
echo debconf apt-fast/aptmanager string apt-get | debconf-set-selections
|
||||
EOF
|
||||
|
||||
# Base packages
|
||||
# https://github.com/actions/runner-images/blob/main/images/ubuntu/toolsets/toolset-2204.json#L114
|
||||
RUN <<EOF
|
||||
apt-fast install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
bzip2 curl g++ gcc make jq tar unzip wget \
|
||||
sudo git openssh-client
|
||||
EOF
|
||||
|
||||
# Prepare git
|
||||
RUN <<EOF
|
||||
cat <<EOC >> /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 <<EOF
|
||||
apt-fast install -y --no-install-recommends \
|
||||
libc6-dev libffi-dev \
|
||||
libx11-dev libxi-dev libxext-dev libxinerama-dev libxrandr-dev \
|
||||
libgl-dev libgl1-mesa-dev \
|
||||
libasound2-dev \
|
||||
libvlc-dev libvlccore-dev
|
||||
EOF
|
||||
|
||||
# Janky libffi.6 fix
|
||||
RUN <<EOF
|
||||
ln -s \
|
||||
/usr/lib/x86_64-linux-gnu/libffi.so.8 \
|
||||
/usr/lib/x86_64-linux-gnu/libffi.so.6 \
|
||||
|| true
|
||||
EOF
|
||||
|
||||
# neko
|
||||
# https://github.com/HaxeFoundation/neko/releases/download/v2-3-0/neko-2.3.0-linux64.tar.gz
|
||||
RUN <<EOF
|
||||
neko_url=$(curl https://api.github.com/repos/HaxeFoundation/neko/releases -sfL \
|
||||
| jq '.[] | select(.name == "'"$neko_version"'")' \
|
||||
| jq '.assets[] | select(.name | endswith("linux64.tar.gz"))' \
|
||||
| jq -r '.browser_download_url')
|
||||
curl -sfL "$neko_url" | tar -xz -C /usr/local
|
||||
EOF
|
||||
|
||||
RUN <<EOF
|
||||
neko_path="$(find /usr/local -maxdepth 1 -type d -name 'neko*')"
|
||||
ln -s "$neko_path" /usr/local/neko
|
||||
EOF
|
||||
|
||||
ENV NEKOPATH="/usr/local/neko"
|
||||
ENV LD_LIBRARY_PATH="$NEKOPATH:$LD_LIBRARY_PATH"
|
||||
ENV PATH="$NEKOPATH:$PATH"
|
||||
|
||||
# haxe
|
||||
# https://github.com/HaxeFoundation/haxe/releases/download/4.0.5/haxe-4.0.5-linux64.tar.gz
|
||||
RUN <<EOF
|
||||
haxe_url=$(curl https://api.github.com/repos/HaxeFoundation/haxe/releases -sfL \
|
||||
| jq '.[] | select(.name == "'"$haxe_version"'")' \
|
||||
| jq '.assets[] | select(.name | endswith("linux64.tar.gz"))' \
|
||||
| jq -r '.browser_download_url')
|
||||
curl -sfL "$haxe_url" | tar -xz -C /usr/local
|
||||
EOF
|
||||
|
||||
RUN <<EOF
|
||||
haxe_path="$(find /usr/local -maxdepth 1 -type d -name 'haxe*')"
|
||||
ln -s "$haxe_path" /usr/local/haxe
|
||||
EOF
|
||||
|
||||
ENV HAXEPATH="/usr/local/haxe"
|
||||
ENV HAXE_STD_PATH="$HAXEPATH/std"
|
||||
ENV PATH="$HAXEPATH:$PATH"
|
||||
|
||||
# haxelib
|
||||
RUN <<EOF
|
||||
HOME=/etc haxelib setup "$HAXEPATH/lib"
|
||||
haxelib --global --never install haxelib $haxelib_version
|
||||
haxelib --global --never git haxelib https://github.com/HaxeFoundation/haxelib.git master
|
||||
haxelib --global --never install hmm
|
||||
EOF
|
||||
|
||||
# hxcpp
|
||||
ENV HXCPP_COMPILE_CACHE="/usr/share/hxcpp"
|
||||
ENV HXCPP_CACHE_MB="4096"
|
||||
|
||||
# Clean up
|
||||
# https://github.com/actions/runner-images/blob/main/images/ubuntu/scripts/build/cleanup.sh
|
||||
RUN <<EOF
|
||||
rm -r /var/cache/apt/apt-fast
|
||||
apt-get clean
|
||||
if [ -d /var/lib/apt/lists ]; then
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
fi
|
||||
|
||||
if [ -d /tmp ]; then
|
||||
rm -rf /tmp/*
|
||||
fi
|
||||
if [ -d /root/.cache ]; then
|
||||
rm -rf /root/.cache
|
||||
fi
|
||||
|
||||
if command -v journalctl; then
|
||||
journalctl --rotate
|
||||
journalctl --vacuum-time=1s
|
||||
fi
|
||||
if [ -d /var/log ]; then
|
||||
find /var/log -type f -regex ".*\.gz$" -delete
|
||||
find /var/log -type f -regex ".*\.[0-9]$" -delete
|
||||
find /var/log/ -type f -exec cp /dev/null {} \;
|
||||
fi
|
||||
if [ -f /usr/local/bin/invoke_tests ]; then
|
||||
rm -rf /usr/local/bin/invoke_tests
|
||||
fi
|
||||
EOF
|
||||
|
||||
# Print debug info
|
||||
RUN <<EOF
|
||||
echo "/root"
|
||||
ls -la /root
|
||||
cat /root/.haxelib && echo
|
||||
|
||||
id
|
||||
env
|
||||
EOF
|
37
hmm.json
37
hmm.json
|
@ -4,7 +4,7 @@
|
|||
"name": "discord_rpc",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "2d83fa863ef0c1eace5f1cf67c3ac315d1a3a8a5",
|
||||
"ref": "2d83fa8",
|
||||
"url": "https://github.com/Aidan63/linc_discord-rpc"
|
||||
},
|
||||
{
|
||||
|
@ -18,7 +18,7 @@
|
|||
"name": "flixel-addons",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "a523c3b56622f0640933944171efed46929e360e",
|
||||
"ref": "a523c3b",
|
||||
"url": "https://github.com/FunkinCrew/flixel-addons"
|
||||
},
|
||||
{
|
||||
|
@ -30,14 +30,14 @@
|
|||
"name": "flixel-ui",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "719b4f10d94186ed55f6fef1b6618d32abec8c15",
|
||||
"ref": "719b4f1",
|
||||
"url": "https://github.com/HaxeFlixel/flixel-ui"
|
||||
},
|
||||
{
|
||||
"name": "flxanimate",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "9bacdd6ea39f5e3a33b0f5dfb7bc583fe76060d4",
|
||||
"ref": "9bacdd6",
|
||||
"url": "https://github.com/FunkinCrew/flxanimate"
|
||||
},
|
||||
{
|
||||
|
@ -45,6 +45,13 @@
|
|||
"type": "haxelib",
|
||||
"version": "3.5.0"
|
||||
},
|
||||
{
|
||||
"name": "funkVis",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "backend-rework",
|
||||
"url": "https://github.com/FunkinCrew/funkVis"
|
||||
},
|
||||
{
|
||||
"name": "hamcrest",
|
||||
"type": "haxelib",
|
||||
|
@ -54,14 +61,14 @@
|
|||
"name": "haxeui-core",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "0212d8fdfcafeb5f0d5a41e1ddba8ff21d0e183b",
|
||||
"ref": "0212d8fd",
|
||||
"url": "https://github.com/haxeui/haxeui-core"
|
||||
},
|
||||
{
|
||||
"name": "haxeui-flixel",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "63a906a6148958dbfde8c7b48d90b0693767fd95",
|
||||
"ref": "63a906a",
|
||||
"url": "https://github.com/haxeui/haxeui-flixel"
|
||||
},
|
||||
{
|
||||
|
@ -73,7 +80,7 @@
|
|||
"name": "hxCodec",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "387e1665d6feb5762358134f168e6ebfe46acec8",
|
||||
"ref": "387e166",
|
||||
"url": "https://github.com/FunkinCrew/hxCodec"
|
||||
},
|
||||
{
|
||||
|
@ -85,7 +92,7 @@
|
|||
"name": "hxcpp-debug-server",
|
||||
"type": "git",
|
||||
"dir": "hxcpp-debug-server",
|
||||
"ref": "147294123f983e35f50a966741474438069a7a8f",
|
||||
"ref": "1472941",
|
||||
"url": "https://github.com/FunkinCrew/hxcpp-debugger"
|
||||
},
|
||||
{
|
||||
|
@ -97,49 +104,49 @@
|
|||
"name": "json2object",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "a8c26f18463c98da32f744c214fe02273e1823fa",
|
||||
"ref": "a8c26f1",
|
||||
"url": "https://github.com/FunkinCrew/json2object"
|
||||
},
|
||||
{
|
||||
"name": "lime",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "1359fe6ad52e91175dc636a516d460bd54ea22ed",
|
||||
"ref": "43ebebdd8119936b99f23407057025c7849c5f5b",
|
||||
"url": "https://github.com/FunkinCrew/lime"
|
||||
},
|
||||
{
|
||||
"name": "mconsole",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "master",
|
||||
"ref": "06c0499",
|
||||
"url": "https://github.com/massive-oss/mconsole"
|
||||
},
|
||||
{
|
||||
"name": "mcover",
|
||||
"type": "git",
|
||||
"dir": "src",
|
||||
"ref": "master",
|
||||
"ref": "c3c47cd",
|
||||
"url": "https://github.com/massive-oss/mcover"
|
||||
},
|
||||
{
|
||||
"name": "mockatoo",
|
||||
"type": "git",
|
||||
"dir": "src",
|
||||
"ref": "master",
|
||||
"ref": "13d77a0",
|
||||
"url": "https://github.com/FunkinCrew/mockatoo"
|
||||
},
|
||||
{
|
||||
"name": "munit",
|
||||
"type": "git",
|
||||
"dir": "src",
|
||||
"ref": "master",
|
||||
"ref": "f61be7f",
|
||||
"url": "https://github.com/FunkinCrew/MassiveUnit"
|
||||
},
|
||||
{
|
||||
"name": "openfl",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "f229d76361c7e31025a048fe7909847f75bb5d5e",
|
||||
"ref": "228c1b5063911e2ad75cef6e3168ef0a4b9f9134",
|
||||
"url": "https://github.com/FunkinCrew/openfl"
|
||||
},
|
||||
{
|
||||
|
|
|
@ -113,6 +113,8 @@ class Main extends Sprite
|
|||
|
||||
addChild(game);
|
||||
|
||||
addChild(fpsCounter);
|
||||
|
||||
#if hxcpp_debug_server
|
||||
trace('hxcpp_debug_server is enabled! You can now connect to the game with a debugger.');
|
||||
#else
|
||||
|
@ -128,6 +130,8 @@ class Main extends Sprite
|
|||
Toolkit.init();
|
||||
Toolkit.theme = 'dark'; // don't be cringe
|
||||
Toolkit.autoScale = false;
|
||||
// Don't focus on UI elements when they first appear.
|
||||
haxe.ui.focus.FocusManager.instance.autoFocus = false;
|
||||
funkin.input.Cursor.registerHaxeUICursors();
|
||||
haxe.ui.tooltips.ToolTipManager.defaultDelay = 200;
|
||||
}
|
||||
|
|
|
@ -59,6 +59,7 @@ abstract Tallies(RawTallies)
|
|||
totalNotes: 0,
|
||||
totalNotesHit: 0,
|
||||
maxCombo: 0,
|
||||
score: 0,
|
||||
isNewHighscore: false
|
||||
}
|
||||
}
|
||||
|
@ -81,6 +82,9 @@ typedef RawTallies =
|
|||
var good:Int;
|
||||
var sick:Int;
|
||||
var maxCombo:Int;
|
||||
|
||||
var score:Int;
|
||||
|
||||
var isNewHighscore:Bool;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -9,7 +9,7 @@ import openfl.utils.Assets as OpenFlAssets;
|
|||
*/
|
||||
class Paths
|
||||
{
|
||||
static var currentLevel:String;
|
||||
static var currentLevel:Null<String> = null;
|
||||
|
||||
public static function setCurrentLevel(name:String):Void
|
||||
{
|
||||
|
@ -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
|
||||
|
|
|
@ -223,11 +223,12 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
|||
// 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;
|
||||
}
|
||||
|
@ -265,10 +266,16 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
|||
@:allow(flixel.sound.FlxSoundGroup)
|
||||
override function updateTransform():Void
|
||||
{
|
||||
_transform.volume = #if FLX_SOUND_SYSTEM ((FlxG.sound.muted || this.muted) ? 0 : 1) * FlxG.sound.volume * #end
|
||||
(group != null ? group.volume : 1) * _volume * _volumeAdjust;
|
||||
if (_transform != null)
|
||||
{
|
||||
_transform.volume = #if FLX_SOUND_SYSTEM ((FlxG.sound.muted || this.muted) ? 0 : 1) * FlxG.sound.volume * #end
|
||||
(group != null ? group.volume : 1) * _volume * _volumeAdjust;
|
||||
}
|
||||
|
||||
if (_channel != null) _channel.soundTransform = _transform;
|
||||
if (_channel != null)
|
||||
{
|
||||
_channel.soundTransform = _transform;
|
||||
}
|
||||
}
|
||||
|
||||
public function clone():FunkinSound
|
||||
|
@ -315,6 +322,13 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
|||
}
|
||||
}
|
||||
|
||||
if (FlxG.sound.music != null)
|
||||
{
|
||||
FlxG.sound.music.fadeTween?.cancel();
|
||||
FlxG.sound.music.stop();
|
||||
FlxG.sound.music.kill();
|
||||
}
|
||||
|
||||
if (params?.mapTimeChanges ?? true)
|
||||
{
|
||||
var songMusicData:Null<SongMusicData> = SongRegistry.instance.parseMusicData(key);
|
||||
|
@ -329,13 +343,6 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
|||
}
|
||||
}
|
||||
|
||||
if (FlxG.sound.music != null)
|
||||
{
|
||||
FlxG.sound.music.fadeTween?.cancel();
|
||||
FlxG.sound.music.stop();
|
||||
FlxG.sound.music.kill();
|
||||
}
|
||||
|
||||
var music = FunkinSound.load(Paths.music('$key/$key'), params?.startingVolume ?? 1.0, params.loop ?? true, false, true);
|
||||
if (music != null)
|
||||
{
|
||||
|
@ -391,10 +398,16 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
|||
sound._label = 'unknown';
|
||||
}
|
||||
|
||||
if (autoPlay) sound.play();
|
||||
sound.volume = volume;
|
||||
sound.group = FlxG.sound.defaultSoundGroup;
|
||||
sound.persist = true;
|
||||
if (autoPlay) sound.play();
|
||||
|
||||
// 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();
|
||||
|
|
|
@ -150,7 +150,7 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
|
|||
/**
|
||||
* Stop all the sounds in the group.
|
||||
*/
|
||||
public function stop()
|
||||
public function stop():Void
|
||||
{
|
||||
if (members != null)
|
||||
{
|
||||
|
@ -160,7 +160,7 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
|
|||
}
|
||||
}
|
||||
|
||||
public override function destroy()
|
||||
public override function destroy():Void
|
||||
{
|
||||
stop();
|
||||
super.destroy();
|
||||
|
@ -178,9 +178,14 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
|
|||
|
||||
function get_time():Float
|
||||
{
|
||||
if (getFirstAlive() != null) return getFirstAlive().time;
|
||||
if (getFirstAlive() != null)
|
||||
{
|
||||
return getFirstAlive().time;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function set_time(time:Float):Float
|
||||
|
@ -195,16 +200,26 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
|
|||
|
||||
function get_playing():Bool
|
||||
{
|
||||
if (getFirstAlive() != null) return getFirstAlive().playing;
|
||||
if (getFirstAlive() != null)
|
||||
{
|
||||
return getFirstAlive().playing;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function get_volume():Float
|
||||
{
|
||||
if (getFirstAlive() != null) return getFirstAlive().volume;
|
||||
if (getFirstAlive() != null)
|
||||
{
|
||||
return getFirstAlive().volume;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// in PlayState, adjust the code so that it only mutes the player1 vocal tracks?
|
||||
|
|
|
@ -159,10 +159,18 @@ class VoicesGroup extends SoundGroup
|
|||
|
||||
public override function destroy():Void
|
||||
{
|
||||
playerVoices.destroy();
|
||||
playerVoices = null;
|
||||
opponentVoices.destroy();
|
||||
opponentVoices = null;
|
||||
if (playerVoices != null)
|
||||
{
|
||||
playerVoices.destroy();
|
||||
playerVoices = null;
|
||||
}
|
||||
|
||||
if (opponentVoices != null)
|
||||
{
|
||||
opponentVoices.destroy();
|
||||
opponentVoices = null;
|
||||
}
|
||||
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<FlxSprite>
|
||||
{
|
||||
public var vis:VisShit;
|
||||
// public var vis:VisShit;
|
||||
var analyzer:SpectralAnalyzer;
|
||||
|
||||
var volumes:Array<Float> = [];
|
||||
|
||||
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<Float> = [0, 59, 56, 66, 54, 52, 51];
|
||||
var positionY:Array<Float> = [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<Float> = [];
|
||||
|
||||
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<Float> = [];
|
||||
// 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;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -325,12 +325,3 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A pair of a file name and its contents.
|
||||
*/
|
||||
typedef JsonFile =
|
||||
{
|
||||
fileName:String,
|
||||
contents:String
|
||||
};
|
||||
|
|
10
source/funkin/data/JsonFile.hx
Normal file
10
source/funkin/data/JsonFile.hx
Normal file
|
@ -0,0 +1,10 @@
|
|||
package funkin.data;
|
||||
|
||||
/**
|
||||
* A pair of a file name and its contents.
|
||||
*/
|
||||
typedef JsonFile =
|
||||
{
|
||||
fileName:String,
|
||||
contents:String
|
||||
};
|
|
@ -1,5 +1,7 @@
|
|||
package funkin.data.freeplay;
|
||||
|
||||
import funkin.data.animation.AnimationData;
|
||||
|
||||
/**
|
||||
* A type definition for the data for an album of songs.
|
||||
* It includes things like what graphics to display in Freeplay.
|
||||
|
@ -33,4 +35,11 @@ typedef AlbumData =
|
|||
* The album title will be displayed below the album art in Freeplay.
|
||||
*/
|
||||
public var albumTitleAsset:String;
|
||||
|
||||
/**
|
||||
* An optional array of animations for the album title.
|
||||
*/
|
||||
@:optional
|
||||
@:default([])
|
||||
public var albumTitleAnimations:Array<AnimationData>;
|
||||
}
|
||||
|
|
|
@ -427,7 +427,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||
return ScriptedSong.listScriptClasses();
|
||||
}
|
||||
|
||||
function loadEntryMetadataFile(id:String, ?variation:String):Null<BaseRegistry.JsonFile>
|
||||
function loadEntryMetadataFile(id:String, ?variation:String):Null<JsonFile>
|
||||
{
|
||||
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
||||
var entryFilePath:String = Paths.json('$dataFilePath/$id/$id-metadata${variation == Constants.DEFAULT_VARIATION ? '' : '-$variation'}');
|
||||
|
@ -442,7 +442,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||
return {fileName: entryFilePath, contents: rawJson};
|
||||
}
|
||||
|
||||
function loadMusicDataFile(id:String, ?variation:String):Null<BaseRegistry.JsonFile>
|
||||
function loadMusicDataFile(id:String, ?variation:String):Null<JsonFile>
|
||||
{
|
||||
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
||||
var entryFilePath:String = Paths.file('music/$id/$id-metadata${variation == Constants.DEFAULT_VARIATION ? '' : '-$variation'}.json');
|
||||
|
@ -460,7 +460,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||
return openfl.Assets.exists(entryFilePath);
|
||||
}
|
||||
|
||||
function loadEntryChartFile(id:String, ?variation:String):Null<BaseRegistry.JsonFile>
|
||||
function loadEntryChartFile(id:String, ?variation:String):Null<JsonFile>
|
||||
{
|
||||
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
||||
var entryFilePath:String = Paths.json('$dataFilePath/$id/$id-chart${variation == Constants.DEFAULT_VARIATION ? '' : '-$variation'}');
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -137,7 +137,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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,6 @@ class Grayscale extends FlxRuntimeShader
|
|||
public function setAmount(value:Float):Void
|
||||
{
|
||||
amount = value;
|
||||
this.setFloat("amount", amount);
|
||||
this.setFloat("_amount", amount);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ class HSVShader extends FlxRuntimeShader
|
|||
|
||||
function set_hue(value:Float):Float
|
||||
{
|
||||
this.setFloat('hue', value);
|
||||
this.setFloat('_hue', value);
|
||||
this.hue = value;
|
||||
|
||||
return this.hue;
|
||||
|
|
|
@ -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<String, FlxActionDigital> = new Map<String, FlxActionDigital>();
|
||||
var byName:Map<String, FunkinAction> = new Map<String, FunkinAction>();
|
||||
|
||||
public var gamepadsAdded:Array<Int> = [];
|
||||
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<FlxKey> {
|
||||
|
@ -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<String>;
|
||||
public var nameReleased(default, null):Null<String>;
|
||||
|
||||
var cache:Map<String, {timestamp:Int, value:Bool}> = [];
|
||||
|
||||
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<FlxInputState>, ?filterDevices:Array<FlxInputDevice>):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<Int, Swipes> = 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";
|
||||
|
|
111
source/funkin/input/TurboActionHandler.hx
Normal file
111
source/funkin/input/TurboActionHandler.hx
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
127
source/funkin/input/TurboButtonHandler.hx
Normal file
127
source/funkin/input/TurboButtonHandler.hx
Normal file
|
@ -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<FlxGamepadInputID>;
|
||||
var delay:Float;
|
||||
var interval:Float;
|
||||
var targetGamepad:FlxGamepad;
|
||||
|
||||
var allPressedTime:Float = 0;
|
||||
|
||||
function new(inputs:Array<FlxGamepadInputID>, 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<FlxGamepadInputID>, ?delay:Float = DEFAULT_DELAY,
|
||||
?interval:Float = DEFAULT_INTERVAL):TurboButtonHandler
|
||||
{
|
||||
return new TurboButtonHandler(inputs, delay, interval);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -8,7 +8,12 @@ import funkin.modding.IScriptedClass;
|
|||
*/
|
||||
class ScriptEventDispatcher
|
||||
{
|
||||
public static function callEvent(target:IScriptedClass, event:ScriptEvent):Void
|
||||
/**
|
||||
* Invoke the given event hook on the given scripted class.
|
||||
* @param target The target class to call script hooks on.
|
||||
* @param event The event, which determines the script hook to call and provides parameters for it.
|
||||
*/
|
||||
public static function callEvent(target:Null<IScriptedClass>, event:ScriptEvent):Void
|
||||
{
|
||||
if (target == null || event == null) return;
|
||||
|
||||
|
|
|
@ -3,18 +3,18 @@ package funkin.play;
|
|||
import flixel.FlxG;
|
||||
import flixel.FlxObject;
|
||||
import flixel.FlxSprite;
|
||||
import funkin.audio.FunkinSound;
|
||||
import flixel.input.touch.FlxTouch;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.audio.FunkinSound;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.play.character.BaseCharacter;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.util.MathUtil;
|
||||
import funkin.ui.freeplay.FreeplayState;
|
||||
import funkin.ui.MusicBeatSubState;
|
||||
import funkin.ui.story.StoryMenuState;
|
||||
import funkin.util.MathUtil;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
/**
|
||||
|
@ -23,13 +23,14 @@ import openfl.utils.Assets;
|
|||
*
|
||||
* The newest implementation uses a substate, which prevents having to reload the song and stage each reset.
|
||||
*/
|
||||
@:nullSafety
|
||||
class GameOverSubState extends MusicBeatSubState
|
||||
{
|
||||
/**
|
||||
* The currently active GameOverSubState.
|
||||
* There should be only one GameOverSubState in existance at a time, we can use a singleton.
|
||||
*/
|
||||
public static var instance:GameOverSubState = null;
|
||||
public static var instance:Null<GameOverSubState> = null;
|
||||
|
||||
/**
|
||||
* Which alternate animation on the character to use.
|
||||
|
@ -37,7 +38,7 @@ class GameOverSubState extends MusicBeatSubState
|
|||
* For example, playing a different animation when BF dies in Week 4
|
||||
* or Pico dies in Weekend 1.
|
||||
*/
|
||||
public static var animationSuffix:String = "";
|
||||
public static var animationSuffix:String = '';
|
||||
|
||||
/**
|
||||
* Which alternate game over music to use.
|
||||
|
@ -45,17 +46,19 @@ class GameOverSubState extends MusicBeatSubState
|
|||
* For example, the bf-pixel script sets this to `-pixel`
|
||||
* and the pico-playable script sets this to `Pico`.
|
||||
*/
|
||||
public static var musicSuffix:String = "";
|
||||
public static var musicSuffix:String = '';
|
||||
|
||||
/**
|
||||
* Which alternate "blue ball" sound effect to use.
|
||||
*/
|
||||
public static var blueBallSuffix:String = "";
|
||||
public static var blueBallSuffix:String = '';
|
||||
|
||||
static var blueballed:Bool = false;
|
||||
|
||||
/**
|
||||
* The boyfriend character.
|
||||
*/
|
||||
var boyfriend:BaseCharacter;
|
||||
var boyfriend:Null<BaseCharacter> = null;
|
||||
|
||||
/**
|
||||
* The invisible object in the scene which the camera focuses on.
|
||||
|
@ -82,7 +85,8 @@ class GameOverSubState extends MusicBeatSubState
|
|||
|
||||
var transparent:Bool;
|
||||
|
||||
final CAMERA_ZOOM_DURATION:Float = 0.5;
|
||||
static final CAMERA_ZOOM_DURATION:Float = 0.5;
|
||||
|
||||
var targetCameraZoom:Float = 1.0;
|
||||
|
||||
public function new(params:GameOverParams)
|
||||
|
@ -91,6 +95,8 @@ class GameOverSubState extends MusicBeatSubState
|
|||
|
||||
this.isChartingMode = params?.isChartingMode ?? false;
|
||||
transparent = params.transparent;
|
||||
|
||||
cameraFollowPoint = new FlxObject(PlayState.instance.cameraFollowPoint.x, PlayState.instance.cameraFollowPoint.y, 1, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -101,14 +107,15 @@ class GameOverSubState extends MusicBeatSubState
|
|||
animationSuffix = '';
|
||||
musicSuffix = '';
|
||||
blueBallSuffix = '';
|
||||
blueballed = false;
|
||||
}
|
||||
|
||||
override public function create():Void
|
||||
public override function create():Void
|
||||
{
|
||||
if (instance != null)
|
||||
{
|
||||
// TODO: Do something in this case? IDK.
|
||||
trace('WARNING: GameOverSubState instance already exists. This should not happen.');
|
||||
FlxG.log.warn('WARNING: GameOverSubState instance already exists. This should not happen.');
|
||||
}
|
||||
instance = this;
|
||||
|
||||
|
@ -121,7 +128,7 @@ class GameOverSubState extends MusicBeatSubState
|
|||
var playState = PlayState.instance;
|
||||
|
||||
// Add a black background to the screen.
|
||||
var bg = new FunkinSprite().makeSolidColor(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK);
|
||||
var bg:FunkinSprite = new FunkinSprite().makeSolidColor(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK);
|
||||
// We make this transparent so that we can see the stage underneath during debugging,
|
||||
// but it's normally opaque.
|
||||
bg.alpha = transparent ? 0.25 : 1.0;
|
||||
|
@ -138,21 +145,10 @@ class GameOverSubState extends MusicBeatSubState
|
|||
boyfriend.isDead = true;
|
||||
add(boyfriend);
|
||||
boyfriend.resetCharacter();
|
||||
|
||||
// Assign a camera follow point to the boyfriend's position.
|
||||
cameraFollowPoint = new FlxObject(PlayState.instance.cameraFollowPoint.x, PlayState.instance.cameraFollowPoint.y, 1, 1);
|
||||
cameraFollowPoint.x = boyfriend.getGraphicMidpoint().x;
|
||||
cameraFollowPoint.y = boyfriend.getGraphicMidpoint().y;
|
||||
var offsets:Array<Float> = boyfriend.getDeathCameraOffsets();
|
||||
cameraFollowPoint.x += offsets[0];
|
||||
cameraFollowPoint.y += offsets[1];
|
||||
add(cameraFollowPoint);
|
||||
|
||||
FlxG.camera.target = null;
|
||||
FlxG.camera.follow(cameraFollowPoint, LOCKON, 0.01);
|
||||
targetCameraZoom = PlayState?.instance?.currentStage?.camZoom * boyfriend.getDeathCameraZoom();
|
||||
}
|
||||
|
||||
setCameraTarget();
|
||||
|
||||
//
|
||||
// Set up the audio
|
||||
//
|
||||
|
@ -161,6 +157,27 @@ class GameOverSubState extends MusicBeatSubState
|
|||
Conductor.instance.update(0);
|
||||
}
|
||||
|
||||
@:nullSafety(Off)
|
||||
function setCameraTarget():Void
|
||||
{
|
||||
// Assign a camera follow point to the boyfriend's position.
|
||||
cameraFollowPoint = new FlxObject(PlayState.instance.cameraFollowPoint.x, PlayState.instance.cameraFollowPoint.y, 1, 1);
|
||||
cameraFollowPoint.x = boyfriend.getGraphicMidpoint().x;
|
||||
cameraFollowPoint.y = boyfriend.getGraphicMidpoint().y;
|
||||
var offsets:Array<Float> = boyfriend.getDeathCameraOffsets();
|
||||
cameraFollowPoint.x += offsets[0];
|
||||
cameraFollowPoint.y += offsets[1];
|
||||
add(cameraFollowPoint);
|
||||
|
||||
FlxG.camera.target = null;
|
||||
FlxG.camera.follow(cameraFollowPoint, LOCKON, Constants.DEFAULT_CAMERA_FOLLOW_RATE / 2);
|
||||
targetCameraZoom = (PlayState?.instance?.currentStage?.camZoom ?? 1.0) * boyfriend.getDeathCameraZoom();
|
||||
}
|
||||
|
||||
/**
|
||||
* Forcibly reset the camera zoom level to that of the current stage.
|
||||
* This prevents camera zoom events from adversely affecting the game over state.
|
||||
*/
|
||||
public function resetCameraZoom():Void
|
||||
{
|
||||
// Apply camera zoom level from stage data.
|
||||
|
@ -175,7 +192,7 @@ class GameOverSubState extends MusicBeatSubState
|
|||
{
|
||||
hasStartedAnimation = true;
|
||||
|
||||
if (PlayState.instance.isMinimalMode)
|
||||
if (boyfriend == null || PlayState.instance.isMinimalMode)
|
||||
{
|
||||
// Play the "blue balled" sound. May play a variant if one has been assigned.
|
||||
playBlueBalledSFX();
|
||||
|
@ -205,10 +222,10 @@ class GameOverSubState extends MusicBeatSubState
|
|||
// MOBILE ONLY: Restart the level when tapping Boyfriend.
|
||||
if (FlxG.onMobile)
|
||||
{
|
||||
var touch = FlxG.touches.getFirst();
|
||||
var touch:FlxTouch = FlxG.touches.getFirst();
|
||||
if (touch != null)
|
||||
{
|
||||
if (touch.overlaps(boyfriend))
|
||||
if (boyfriend == null || touch.overlaps(boyfriend))
|
||||
{
|
||||
confirmDeath();
|
||||
}
|
||||
|
@ -228,7 +245,7 @@ class GameOverSubState extends MusicBeatSubState
|
|||
blueballed = false;
|
||||
PlayState.instance.deathCounter = 0;
|
||||
// PlayState.seenCutscene = false; // old thing...
|
||||
gameOverMusic.stop();
|
||||
if (gameOverMusic != null) gameOverMusic.stop();
|
||||
|
||||
if (isChartingMode)
|
||||
{
|
||||
|
@ -238,11 +255,11 @@ class GameOverSubState extends MusicBeatSubState
|
|||
}
|
||||
else if (PlayStatePlaylist.isStoryMode)
|
||||
{
|
||||
FlxG.switchState(() -> new StoryMenuState());
|
||||
openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new StoryMenuState(sticker)));
|
||||
}
|
||||
else
|
||||
{
|
||||
FlxG.switchState(() -> new FreeplayState());
|
||||
openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> FreeplayState.build(sticker)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -252,7 +269,7 @@ class GameOverSubState extends MusicBeatSubState
|
|||
// This enables the stepHit and beatHit events.
|
||||
Conductor.instance.update(gameOverMusic.time);
|
||||
}
|
||||
else
|
||||
else if (boyfriend != null)
|
||||
{
|
||||
if (PlayState.instance.isMinimalMode)
|
||||
{
|
||||
|
@ -299,7 +316,7 @@ class GameOverSubState extends MusicBeatSubState
|
|||
isEnding = true;
|
||||
startDeathMusic(1.0, true); // isEnding changes this function's behavior.
|
||||
|
||||
if (PlayState.instance.isMinimalMode) {}
|
||||
if (PlayState.instance.isMinimalMode || boyfriend == null) {}
|
||||
else
|
||||
{
|
||||
boyfriend.playAnimation('deathConfirm' + animationSuffix, true);
|
||||
|
@ -313,7 +330,7 @@ class GameOverSubState extends MusicBeatSubState
|
|||
FlxG.camera.fade(FlxColor.BLACK, 1, true, null, true);
|
||||
PlayState.instance.needsReset = true;
|
||||
|
||||
if (PlayState.instance.isMinimalMode) {}
|
||||
if (PlayState.instance.isMinimalMode || boyfriend == null) {}
|
||||
else
|
||||
{
|
||||
// Readd Boyfriend to the stage.
|
||||
|
@ -332,7 +349,7 @@ class GameOverSubState extends MusicBeatSubState
|
|||
}
|
||||
}
|
||||
|
||||
public override function dispatchEvent(event:ScriptEvent)
|
||||
public override function dispatchEvent(event:ScriptEvent):Void
|
||||
{
|
||||
super.dispatchEvent(event);
|
||||
|
||||
|
@ -345,11 +362,11 @@ class GameOverSubState extends MusicBeatSubState
|
|||
*/
|
||||
function resolveMusicPath(suffix:String, starting:Bool = false, ending:Bool = false):Null<String>
|
||||
{
|
||||
var basePath = 'gameplay/gameover/gameOver';
|
||||
if (starting) basePath += 'Start';
|
||||
else if (ending) basePath += 'End';
|
||||
var basePath:String = 'gameplay/gameover/gameOver';
|
||||
if (ending) basePath += 'End';
|
||||
else if (starting) basePath += 'Start';
|
||||
|
||||
var musicPath = Paths.music(basePath + suffix);
|
||||
var musicPath:String = Paths.music(basePath + suffix);
|
||||
while (!Assets.exists(musicPath) && suffix.length > 0)
|
||||
{
|
||||
suffix = suffix.split('-').slice(0, -1).join('-');
|
||||
|
@ -362,23 +379,26 @@ class GameOverSubState extends MusicBeatSubState
|
|||
|
||||
/**
|
||||
* Starts the death music at the appropriate volume.
|
||||
* @param startingVolume
|
||||
* @param startingVolume The initial volume for the music.
|
||||
* @param force Whether or not to force the music to restart.
|
||||
*/
|
||||
public function startDeathMusic(startingVolume:Float = 1, force:Bool = false):Void
|
||||
{
|
||||
var musicPath = resolveMusicPath(musicSuffix, isStarting, isEnding);
|
||||
var onComplete = null;
|
||||
var musicPath:Null<String> = resolveMusicPath(musicSuffix, isStarting, isEnding);
|
||||
var onComplete:() -> Void = () -> {};
|
||||
|
||||
if (isStarting)
|
||||
{
|
||||
if (musicPath == null)
|
||||
{
|
||||
// Looked for starting music and didn't find it. Use middle music instead.
|
||||
isStarting = false;
|
||||
musicPath = resolveMusicPath(musicSuffix, isStarting, isEnding);
|
||||
}
|
||||
else
|
||||
{
|
||||
onComplete = function() {
|
||||
isStarting = false;
|
||||
isStarting = true;
|
||||
// We need to force to ensure that the non-starting music plays.
|
||||
startDeathMusic(1.0, true);
|
||||
};
|
||||
|
@ -387,13 +407,16 @@ class GameOverSubState extends MusicBeatSubState
|
|||
|
||||
if (musicPath == null)
|
||||
{
|
||||
trace('Could not find game over music!');
|
||||
FlxG.log.warn('[GAMEOVER] Could not find game over music at path ($musicPath)!');
|
||||
return;
|
||||
}
|
||||
else if (gameOverMusic == null || !gameOverMusic.playing || force)
|
||||
{
|
||||
if (gameOverMusic != null) gameOverMusic.stop();
|
||||
|
||||
gameOverMusic = FunkinSound.load(musicPath);
|
||||
if (gameOverMusic == null) return;
|
||||
|
||||
gameOverMusic.volume = startingVolume;
|
||||
gameOverMusic.looped = !(isEnding || isStarting);
|
||||
gameOverMusic.onComplete = onComplete;
|
||||
|
@ -406,13 +429,11 @@ class GameOverSubState extends MusicBeatSubState
|
|||
}
|
||||
}
|
||||
|
||||
static var blueballed:Bool = false;
|
||||
|
||||
/**
|
||||
* Play the sound effect that occurs when
|
||||
* boyfriend's testicles get utterly annihilated.
|
||||
*/
|
||||
public static function playBlueBalledSFX()
|
||||
public static function playBlueBalledSFX():Void
|
||||
{
|
||||
blueballed = true;
|
||||
if (Assets.exists(Paths.sound('gameplay/gameover/fnf_loss_sfx' + blueBallSuffix)))
|
||||
|
@ -431,7 +452,7 @@ class GameOverSubState extends MusicBeatSubState
|
|||
* Week 7-specific hardcoded behavior, to play a custom death quote.
|
||||
* TODO: Make this a module somehow.
|
||||
*/
|
||||
function playJeffQuote()
|
||||
function playJeffQuote():Void
|
||||
{
|
||||
var randomCensor:Array<Int> = [];
|
||||
|
||||
|
@ -446,20 +467,27 @@ class GameOverSubState extends MusicBeatSubState
|
|||
});
|
||||
}
|
||||
|
||||
public override function destroy()
|
||||
public override function destroy():Void
|
||||
{
|
||||
super.destroy();
|
||||
if (gameOverMusic != null) gameOverMusic.stop();
|
||||
gameOverMusic = null;
|
||||
if (gameOverMusic != null)
|
||||
{
|
||||
gameOverMusic.stop();
|
||||
gameOverMusic = null;
|
||||
}
|
||||
blueballed = false;
|
||||
instance = null;
|
||||
}
|
||||
|
||||
public override function toString():String
|
||||
{
|
||||
return "GameOverSubState";
|
||||
return 'GameOverSubState';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters used to instantiate a GameOverSubState.
|
||||
*/
|
||||
typedef GameOverParams =
|
||||
{
|
||||
var isChartingMode:Bool;
|
||||
|
|
|
@ -12,6 +12,7 @@ import flixel.tweens.FlxTween;
|
|||
import flixel.util.FlxColor;
|
||||
import funkin.audio.FunkinSound;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.ui.freeplay.FreeplayState;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.play.cutscene.VideoCutscene;
|
||||
import funkin.play.PlayState;
|
||||
|
@ -72,8 +73,8 @@ class PauseSubState extends MusicBeatSubState
|
|||
*/
|
||||
static final PAUSE_MENU_ENTRIES_VIDEO_CUTSCENE:Array<PauseMenuEntry> = [
|
||||
{text: 'Resume', callback: resume},
|
||||
{text: 'Restart Cutscene', callback: restartVideoCutscene},
|
||||
{text: 'Skip Cutscene', callback: skipVideoCutscene},
|
||||
{text: 'Restart Cutscene', callback: restartVideoCutscene},
|
||||
{text: 'Exit to Menu', callback: quitToMenu},
|
||||
];
|
||||
|
||||
|
@ -440,7 +441,7 @@ class PauseSubState extends MusicBeatSubState
|
|||
var entries:Array<PauseMenuEntry> = [];
|
||||
if (PlayState.instance.currentChart != null)
|
||||
{
|
||||
var difficultiesInVariation = PlayState.instance.currentSong.listDifficulties(PlayState.instance.currentChart.variation);
|
||||
var difficultiesInVariation = PlayState.instance.currentSong.listDifficulties(PlayState.instance.currentChart.variation, true);
|
||||
trace('DIFFICULTIES: ${difficultiesInVariation}');
|
||||
for (difficulty in difficultiesInVariation)
|
||||
{
|
||||
|
@ -567,6 +568,8 @@ class PauseSubState extends MusicBeatSubState
|
|||
PlayStatePlaylist.campaignDifficulty = difficulty;
|
||||
PlayState.instance.currentDifficulty = PlayStatePlaylist.campaignDifficulty;
|
||||
|
||||
FreeplayState.rememberedDifficulty = difficulty;
|
||||
|
||||
PlayState.instance.needsReset = true;
|
||||
|
||||
state.close();
|
||||
|
@ -658,7 +661,7 @@ class PauseSubState extends MusicBeatSubState
|
|||
}
|
||||
else
|
||||
{
|
||||
state.openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new funkin.ui.freeplay.FreeplayState(null, sticker)));
|
||||
state.openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> FreeplayState.build(null, sticker)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -498,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
|
||||
|
@ -736,6 +736,10 @@ class PlayState extends MusicBeatSubState
|
|||
#end
|
||||
|
||||
initialized = true;
|
||||
|
||||
// This step ensures z-indexes are applied properly,
|
||||
// and it's important to call it last so all elements get affected.
|
||||
refresh();
|
||||
}
|
||||
|
||||
public override function draw():Void
|
||||
|
@ -830,9 +834,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)
|
||||
{
|
||||
|
@ -848,7 +855,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;
|
||||
|
@ -866,7 +873,7 @@ class PlayState extends MusicBeatSubState
|
|||
|
||||
// Reset camera zooming
|
||||
cameraBopIntensity = Constants.DEFAULT_BOP_INTENSITY;
|
||||
hudCameraZoomIntensity = 0.015 * 2.0;
|
||||
hudCameraZoomIntensity = (cameraBopIntensity - 1.0) * 2.0;
|
||||
cameraZoomRate = Constants.DEFAULT_ZOOM_RATE;
|
||||
|
||||
health = Constants.HEALTH_STARTING;
|
||||
|
@ -962,7 +969,7 @@ class PlayState extends MusicBeatSubState
|
|||
if (health < Constants.HEALTH_MIN) health = Constants.HEALTH_MIN;
|
||||
|
||||
// Apply camera zoom + multipliers.
|
||||
if (subState == null)
|
||||
if (subState == null && cameraZoomRate > 0.0 && !isInCutscene)
|
||||
{
|
||||
cameraBopMultiplier = FlxMath.lerp(1.0, cameraBopMultiplier, 0.95); // Lerp bop multiplier back to 1.0x
|
||||
var zoomPlusBop = currentCameraZoom * cameraBopMultiplier; // Apply camera bop multiplier.
|
||||
|
@ -976,6 +983,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.
|
||||
|
||||
|
@ -1462,7 +1470,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.
|
||||
|
@ -1543,10 +1551,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);
|
||||
|
@ -1728,8 +1737,6 @@ class PlayState extends MusicBeatSubState
|
|||
playerStrumline.fadeInArrows();
|
||||
opponentStrumline.fadeInArrows();
|
||||
}
|
||||
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1912,8 +1919,6 @@ class PlayState extends MusicBeatSubState
|
|||
*/
|
||||
function startSong():Void
|
||||
{
|
||||
dispatchEvent(new ScriptEvent(SONG_START));
|
||||
|
||||
startingSong = false;
|
||||
|
||||
if (!overrideMusic && !isGamePaused && currentChart != null)
|
||||
|
@ -1935,7 +1940,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);
|
||||
|
@ -1954,6 +1959,8 @@ class PlayState extends MusicBeatSubState
|
|||
// FlxG.sound.music.time = startTimestamp - Conductor.instance.instrumentalOffset;
|
||||
handleSkippedNotes();
|
||||
}
|
||||
|
||||
dispatchEvent(new ScriptEvent(SONG_START));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2230,8 +2237,8 @@ class PlayState extends MusicBeatSubState
|
|||
holdNote.handledMiss = true;
|
||||
|
||||
// Mute vocals and play miss animation, but don't penalize.
|
||||
vocals.playerVolume = 0;
|
||||
if (currentStage != null && currentStage.getBoyfriend() != null) currentStage.getBoyfriend().playSingAnimation(holdNote.noteData.getDirection(), true);
|
||||
// vocals.playerVolume = 0;
|
||||
// if (currentStage != null && currentStage.getBoyfriend() != null) currentStage.getBoyfriend().playSingAnimation(holdNote.noteData.getDirection(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2385,13 +2392,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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2449,7 +2449,8 @@ class PlayState extends MusicBeatSubState
|
|||
if (Highscore.tallies.combo != 0)
|
||||
{
|
||||
// Break the combo.
|
||||
Highscore.tallies.combo = comboPopUps.displayCombo(0);
|
||||
if (Highscore.tallies.combo >= 10) comboPopUps.displayCombo(0);
|
||||
Highscore.tallies.combo = 0;
|
||||
}
|
||||
|
||||
if (playSound)
|
||||
|
@ -2576,32 +2577,38 @@ class PlayState extends MusicBeatSubState
|
|||
*/
|
||||
function popUpScore(daNote:NoteSprite, score:Int, daRating:String, healthChange:Float):Void
|
||||
{
|
||||
vocals.playerVolume = 1;
|
||||
|
||||
if (daRating == 'miss')
|
||||
{
|
||||
// If daRating is 'miss', that means we made a mistake and should not continue.
|
||||
trace('[WARNING] popUpScore judged a note as a miss!');
|
||||
FlxG.log.warn('popUpScore judged a note as a miss!');
|
||||
// TODO: Remove this.
|
||||
comboPopUps.displayRating('miss');
|
||||
// comboPopUps.displayRating('miss');
|
||||
return;
|
||||
}
|
||||
|
||||
vocals.playerVolume = 1;
|
||||
|
||||
var isComboBreak = false;
|
||||
switch (daRating)
|
||||
{
|
||||
case 'sick':
|
||||
Highscore.tallies.sick += 1;
|
||||
Highscore.tallies.totalNotesHit++;
|
||||
isComboBreak = Constants.JUDGEMENT_SICK_COMBO_BREAK;
|
||||
case 'good':
|
||||
Highscore.tallies.good += 1;
|
||||
Highscore.tallies.totalNotesHit++;
|
||||
isComboBreak = Constants.JUDGEMENT_GOOD_COMBO_BREAK;
|
||||
case 'bad':
|
||||
Highscore.tallies.bad += 1;
|
||||
Highscore.tallies.totalNotesHit++;
|
||||
isComboBreak = Constants.JUDGEMENT_BAD_COMBO_BREAK;
|
||||
case 'shit':
|
||||
Highscore.tallies.shit += 1;
|
||||
Highscore.tallies.totalNotesHit++;
|
||||
isComboBreak = Constants.JUDGEMENT_SHIT_COMBO_BREAK;
|
||||
default:
|
||||
FlxG.log.error('Wuh? Buh? Guh? Note hit judgement was $daRating!');
|
||||
}
|
||||
|
||||
health += healthChange;
|
||||
|
@ -2609,18 +2616,18 @@ class PlayState extends MusicBeatSubState
|
|||
if (isComboBreak)
|
||||
{
|
||||
// Break the combo, but don't increment tallies.misses.
|
||||
Highscore.tallies.combo = comboPopUps.displayCombo(0);
|
||||
if (Highscore.tallies.combo >= 10) comboPopUps.displayCombo(0);
|
||||
Highscore.tallies.combo = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
Highscore.tallies.combo++;
|
||||
Highscore.tallies.totalNotesHit++;
|
||||
if (Highscore.tallies.combo > Highscore.tallies.maxCombo) Highscore.tallies.maxCombo = Highscore.tallies.combo;
|
||||
}
|
||||
|
||||
playerStrumline.hitNote(daNote, !isComboBreak);
|
||||
|
||||
if (daRating == "sick")
|
||||
if (daRating == 'sick')
|
||||
{
|
||||
playerStrumline.playNoteSplash(daNote.noteData.getDirection());
|
||||
}
|
||||
|
@ -2666,6 +2673,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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2732,7 +2746,7 @@ class PlayState extends MusicBeatSubState
|
|||
*/
|
||||
public function endSong(rightGoddamnNow:Bool = false):Void
|
||||
{
|
||||
FlxG.sound.music.volume = 0;
|
||||
if (FlxG.sound.music != null) FlxG.sound.music.volume = 0;
|
||||
vocals.volume = 0;
|
||||
mayPauseGame = false;
|
||||
|
||||
|
@ -2750,6 +2764,8 @@ class PlayState extends MusicBeatSubState
|
|||
|
||||
deathCounter = 0;
|
||||
|
||||
var isNewHighscore = false;
|
||||
|
||||
if (currentSong != null && currentSong.validScore)
|
||||
{
|
||||
// crackhead double thingie, sets whether was new highscore, AND saves the song!
|
||||
|
@ -2774,17 +2790,20 @@ 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
|
||||
NGio.postScore(score, currentSong.id);
|
||||
#end
|
||||
isNewHighscore = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (PlayStatePlaylist.isStoryMode)
|
||||
{
|
||||
isNewHighscore = false;
|
||||
|
||||
PlayStatePlaylist.campaignScore += songScore;
|
||||
|
||||
// Pop the next song ID from the list.
|
||||
|
@ -2793,18 +2812,6 @@ class PlayState extends MusicBeatSubState
|
|||
|
||||
if (targetSongId == null)
|
||||
{
|
||||
FunkinSound.playMusic('freakyMenu',
|
||||
{
|
||||
overrideExisting: true,
|
||||
restartTrack: false
|
||||
});
|
||||
|
||||
// transIn = FlxTransitionableState.defaultTransIn;
|
||||
// transOut = FlxTransitionableState.defaultTransOut;
|
||||
|
||||
// TODO: Rework week unlock logic.
|
||||
// StoryMenuState.weekUnlocked[Std.int(Math.min(storyWeek + 1, StoryMenuState.weekUnlocked.length - 1))] = true;
|
||||
|
||||
if (currentSong.validScore)
|
||||
{
|
||||
NGio.unlockMedal(60961);
|
||||
|
@ -2834,6 +2841,7 @@ class PlayState extends MusicBeatSubState
|
|||
#if newgrounds
|
||||
NGio.postScore(score, 'Level ${PlayStatePlaylist.campaignId}');
|
||||
#end
|
||||
isNewHighscore = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2845,11 +2853,11 @@ class PlayState extends MusicBeatSubState
|
|||
{
|
||||
if (rightGoddamnNow)
|
||||
{
|
||||
moveToResultsScreen();
|
||||
moveToResultsScreen(isNewHighscore);
|
||||
}
|
||||
else
|
||||
{
|
||||
zoomIntoResultsScreen();
|
||||
zoomIntoResultsScreen(isNewHighscore);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2862,7 +2870,7 @@ class PlayState extends MusicBeatSubState
|
|||
FlxTransitionableState.skipNextTransIn = true;
|
||||
FlxTransitionableState.skipNextTransOut = true;
|
||||
|
||||
FlxG.sound.music.stop();
|
||||
if (FlxG.sound.music != null) FlxG.sound.music.stop();
|
||||
vocals.stop();
|
||||
|
||||
// TODO: Softcode this cutscene.
|
||||
|
@ -2910,11 +2918,11 @@ class PlayState extends MusicBeatSubState
|
|||
{
|
||||
if (rightGoddamnNow)
|
||||
{
|
||||
moveToResultsScreen();
|
||||
moveToResultsScreen(isNewHighscore);
|
||||
}
|
||||
else
|
||||
{
|
||||
zoomIntoResultsScreen();
|
||||
zoomIntoResultsScreen(isNewHighscore);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2988,7 +2996,7 @@ class PlayState extends MusicBeatSubState
|
|||
/**
|
||||
* Play the camera zoom animation and then move to the results screen once it's done.
|
||||
*/
|
||||
function zoomIntoResultsScreen():Void
|
||||
function zoomIntoResultsScreen(isNewHighscore:Bool):Void
|
||||
{
|
||||
trace('WENT TO RESULTS SCREEN!');
|
||||
|
||||
|
@ -3045,7 +3053,7 @@ class PlayState extends MusicBeatSubState
|
|||
{
|
||||
ease: FlxEase.expoIn,
|
||||
onComplete: function(_) {
|
||||
moveToResultsScreen();
|
||||
moveToResultsScreen(isNewHighscore);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -3054,7 +3062,7 @@ class PlayState extends MusicBeatSubState
|
|||
/**
|
||||
* Move to the results screen right goddamn now.
|
||||
*/
|
||||
function moveToResultsScreen():Void
|
||||
function moveToResultsScreen(isNewHighscore:Bool):Void
|
||||
{
|
||||
persistentUpdate = false;
|
||||
vocals.stop();
|
||||
|
@ -3066,7 +3074,24 @@ class PlayState extends MusicBeatSubState
|
|||
{
|
||||
storyMode: PlayStatePlaylist.isStoryMode,
|
||||
title: PlayStatePlaylist.isStoryMode ? ('${PlayStatePlaylist.campaignTitle}') : ('${currentChart.songName} by ${currentChart.songArtist}'),
|
||||
tallies: talliesToUse,
|
||||
scoreData:
|
||||
{
|
||||
score: PlayStatePlaylist.isStoryMode ? PlayStatePlaylist.campaignScore : songScore,
|
||||
tallies:
|
||||
{
|
||||
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,
|
||||
},
|
||||
isNewHighscore: isNewHighscore
|
||||
});
|
||||
res.camera = camHUD;
|
||||
openSubState(res);
|
||||
|
@ -3208,7 +3233,10 @@ class PlayState extends MusicBeatSubState
|
|||
// Don't go back in time to before the song started.
|
||||
targetTimeMs = Math.max(0, targetTimeMs);
|
||||
|
||||
FlxG.sound.music.time = targetTimeMs;
|
||||
if (FlxG.sound.music != null)
|
||||
{
|
||||
FlxG.sound.music.time = targetTimeMs;
|
||||
}
|
||||
|
||||
handleSkippedNotes();
|
||||
SongEventRegistry.handleSkippedEvents(songEvents, Conductor.instance.songPosition);
|
||||
|
|
|
@ -5,12 +5,13 @@ package funkin.play;
|
|||
*
|
||||
* TODO: Add getters/setters for all these properties to validate them.
|
||||
*/
|
||||
@:nullSafety
|
||||
class PlayStatePlaylist
|
||||
{
|
||||
/**
|
||||
* Whether the game is currently in Story Mode. If false, we are in Free Play Mode.
|
||||
*/
|
||||
public static var isStoryMode(default, default):Bool = false;
|
||||
public static var isStoryMode:Bool = false;
|
||||
|
||||
/**
|
||||
* The loist of upcoming songs to be played.
|
||||
|
@ -31,8 +32,9 @@ class PlayStatePlaylist
|
|||
|
||||
/**
|
||||
* The internal ID of the current playlist, for example `week4` or `weekend-1`.
|
||||
* @default `null`, used when no playlist is loaded
|
||||
*/
|
||||
public static var campaignId:String = 'unknown';
|
||||
public static var campaignId:Null<String> = null;
|
||||
|
||||
public static var campaignDifficulty:String = Constants.DEFAULT_DIFFICULTY;
|
||||
|
||||
|
@ -45,7 +47,7 @@ class PlayStatePlaylist
|
|||
playlistSongIds = [];
|
||||
campaignScore = 0;
|
||||
campaignTitle = 'UNKNOWN';
|
||||
campaignId = 'unknown';
|
||||
campaignId = null;
|
||||
campaignDifficulty = Constants.DEFAULT_DIFFICULTY;
|
||||
}
|
||||
}
|
||||
|
|
140
source/funkin/play/ResultScore.hx
Normal file
140
source/funkin/play/ResultScore.hx
Normal file
|
@ -0,0 +1,140 @@
|
|||
package funkin.play;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
||||
|
||||
class ResultScore extends FlxTypedSpriteGroup<ScoreNum>
|
||||
{
|
||||
public var scoreShit(default, set):Int = 0;
|
||||
|
||||
function set_scoreShit(val):Int
|
||||
{
|
||||
if (group == null || group.members == null) return val;
|
||||
var loopNum:Int = group.members.length - 1;
|
||||
var dumbNumb = Std.parseInt(Std.string(val));
|
||||
var prevNum:ScoreNum;
|
||||
|
||||
while (dumbNumb > 0)
|
||||
{
|
||||
group.members[loopNum].digit = dumbNumb % 10;
|
||||
|
||||
// var funnyNum = group.members[loopNum];
|
||||
// prevNum = group.members[loopNum + 1];
|
||||
|
||||
// if (prevNum != null)
|
||||
// {
|
||||
// funnyNum.x = prevNum.x - (funnyNum.width * 0.7);
|
||||
// }
|
||||
|
||||
// funnyNum.y = (funnyNum.baseY - (funnyNum.height / 2)) + 73;
|
||||
// funnyNum.x = (funnyNum.baseX - (funnyNum.width / 2)) + 450; // this plus value is hand picked lol!
|
||||
|
||||
dumbNumb = Math.floor(dumbNumb / 10);
|
||||
loopNum--;
|
||||
}
|
||||
|
||||
while (loopNum > 0)
|
||||
{
|
||||
group.members[loopNum].digit = 10;
|
||||
loopNum--;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
public function animateNumbers():Void
|
||||
{
|
||||
for (i in group.members)
|
||||
{
|
||||
i.playAnim();
|
||||
}
|
||||
}
|
||||
|
||||
public function new(x:Float, y:Float, digitCount:Int, scoreShit:Int = 100)
|
||||
{
|
||||
super(x, y);
|
||||
|
||||
for (i in 0...digitCount)
|
||||
{
|
||||
add(new ScoreNum(x + (65 * i), y));
|
||||
}
|
||||
|
||||
this.scoreShit = scoreShit;
|
||||
}
|
||||
|
||||
public function updateScore(scoreNew:Int)
|
||||
{
|
||||
scoreShit = scoreNew;
|
||||
}
|
||||
}
|
||||
|
||||
class ScoreNum extends FlxSprite
|
||||
{
|
||||
public var digit(default, set):Int = 10;
|
||||
|
||||
function set_digit(val):Int
|
||||
{
|
||||
if (val >= 0 && animation.curAnim != null && animation.curAnim.name != numToString[val])
|
||||
{
|
||||
animation.play(numToString[val], true, false, 0);
|
||||
updateHitbox();
|
||||
|
||||
switch (val)
|
||||
{
|
||||
case 1:
|
||||
// offset.x -= 15;
|
||||
case 5:
|
||||
// set offsets
|
||||
// offset.x += 0;
|
||||
// offset.y += 10;
|
||||
|
||||
case 7:
|
||||
// offset.y += 6;
|
||||
case 4:
|
||||
// offset.y += 5;
|
||||
case 9:
|
||||
// offset.y += 5;
|
||||
default:
|
||||
centerOffsets(false);
|
||||
}
|
||||
}
|
||||
|
||||
return digit = val;
|
||||
}
|
||||
|
||||
public function playAnim():Void
|
||||
{
|
||||
animation.play(numToString[digit], true, false, 0);
|
||||
}
|
||||
|
||||
public var baseY:Float = 0;
|
||||
public var baseX:Float = 0;
|
||||
|
||||
var numToString:Array<String> = [
|
||||
"ZERO", "ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN", "EIGHT", "NINE", "DISABLED"
|
||||
];
|
||||
|
||||
public function new(x:Float, y:Float)
|
||||
{
|
||||
super(x, y);
|
||||
|
||||
baseY = y;
|
||||
baseX = x;
|
||||
|
||||
frames = Paths.getSparrowAtlas('resultScreen/score-digital-numbers');
|
||||
|
||||
for (i in 0...10)
|
||||
{
|
||||
var stringNum:String = numToString[i];
|
||||
animation.addByPrefix(stringNum, '$stringNum DIGITAL', 24, false);
|
||||
}
|
||||
|
||||
animation.addByPrefix('DISABLED', 'DISABLED', 24, false);
|
||||
|
||||
this.digit = 10;
|
||||
|
||||
animation.play(numToString[digit], true);
|
||||
|
||||
updateHitbox();
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package funkin.play;
|
||||
|
||||
import funkin.util.MathUtil;
|
||||
import funkin.ui.story.StoryMenuState;
|
||||
import funkin.graphics.adobeanimate.FlxAtlasSprite;
|
||||
import flixel.FlxSprite;
|
||||
|
@ -10,12 +11,15 @@ import flixel.math.FlxPoint;
|
|||
import funkin.ui.MusicBeatSubState;
|
||||
import flixel.math.FlxRect;
|
||||
import flixel.text.FlxBitmapText;
|
||||
import funkin.ui.freeplay.FreeplayScore;
|
||||
import flixel.tweens.FlxEase;
|
||||
import funkin.ui.freeplay.FreeplayState;
|
||||
import flixel.tweens.FlxTween;
|
||||
import funkin.audio.FunkinSound;
|
||||
import flixel.util.FlxGradient;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.save.Save;
|
||||
import funkin.save.Save.SaveScoreData;
|
||||
import funkin.graphics.shaders.LeftMaskShader;
|
||||
import funkin.play.components.TallyCounter;
|
||||
|
||||
|
@ -42,12 +46,15 @@ class ResultState extends MusicBeatSubState
|
|||
|
||||
override function create():Void
|
||||
{
|
||||
if (params.tallies.sick == params.tallies.totalNotesHit
|
||||
&& params.tallies.maxCombo == params.tallies.totalNotesHit) resultsVariation = PERFECT;
|
||||
else if (params.tallies.missed + params.tallies.bad + params.tallies.shit >= params.tallies.totalNotes * 0.50)
|
||||
resultsVariation = SHIT; // if more than half of your song was missed, bad, or shit notes, you get shit ending!
|
||||
else
|
||||
resultsVariation = NORMAL;
|
||||
/*
|
||||
if (params.scoreData.sick == params.scoreData.totalNotesHit
|
||||
&& params.scoreData.maxCombo == params.scoreData.totalNotesHit) resultsVariation = PERFECT;
|
||||
else if (params.scoreData.missed + params.scoreData.bad + params.scoreData.shit >= params.scoreData.totalNotes * 0.50)
|
||||
resultsVariation = SHIT; // if more than half of your song was missed, bad, or shit notes, you get shit ending!
|
||||
else
|
||||
resultsVariation = NORMAL;
|
||||
*/
|
||||
resultsVariation = NORMAL;
|
||||
|
||||
FunkinSound.playMusic('results$resultsVariation',
|
||||
{
|
||||
|
@ -73,34 +80,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 = _ -> {
|
||||
|
@ -130,12 +137,16 @@ class ResultState extends MusicBeatSubState
|
|||
|
||||
var diffSpr:String = switch (PlayState.instance.currentDifficulty)
|
||||
{
|
||||
case 'EASY':
|
||||
case 'easy':
|
||||
'difEasy';
|
||||
case 'NORMAL':
|
||||
case 'normal':
|
||||
'difNormal';
|
||||
case 'HARD':
|
||||
case 'hard':
|
||||
'difHard';
|
||||
case 'erect':
|
||||
'difErect';
|
||||
case 'nightmare':
|
||||
'difNightmare';
|
||||
case _:
|
||||
'difNormal';
|
||||
}
|
||||
|
@ -178,7 +189,7 @@ class ResultState extends MusicBeatSubState
|
|||
scorePopin.visible = false;
|
||||
add(scorePopin);
|
||||
|
||||
var highscoreNew:FlxSprite = new FlxSprite(280, 580);
|
||||
var highscoreNew:FlxSprite = new FlxSprite(310, 570);
|
||||
highscoreNew.frames = Paths.getSparrowAtlas("resultScreen/highscoreNew");
|
||||
highscoreNew.animation.addByPrefix("new", "NEW HIGHSCORE", 24);
|
||||
highscoreNew.visible = false;
|
||||
|
@ -195,29 +206,33 @@ class ResultState extends MusicBeatSubState
|
|||
* NOTE: We display how many notes were HIT, not how many notes there were in total.
|
||||
*
|
||||
*/
|
||||
var totalHit:TallyCounter = new TallyCounter(375, hStuf * 3, params.tallies.totalNotesHit);
|
||||
var totalHit:TallyCounter = new TallyCounter(375, hStuf * 3, params.scoreData.tallies.totalNotesHit);
|
||||
ratingGrp.add(totalHit);
|
||||
|
||||
var maxCombo:TallyCounter = new TallyCounter(375, hStuf * 4, params.tallies.maxCombo);
|
||||
var maxCombo:TallyCounter = new TallyCounter(375, hStuf * 4, params.scoreData.tallies.maxCombo);
|
||||
ratingGrp.add(maxCombo);
|
||||
|
||||
hStuf += 2;
|
||||
var extraYOffset:Float = 5;
|
||||
var tallySick:TallyCounter = new TallyCounter(230, (hStuf * 5) + extraYOffset, params.tallies.sick, 0xFF89E59E);
|
||||
var tallySick:TallyCounter = new TallyCounter(230, (hStuf * 5) + extraYOffset, params.scoreData.tallies.sick, 0xFF89E59E);
|
||||
ratingGrp.add(tallySick);
|
||||
|
||||
var tallyGood:TallyCounter = new TallyCounter(210, (hStuf * 6) + extraYOffset, params.tallies.good, 0xFF89C9E5);
|
||||
var tallyGood:TallyCounter = new TallyCounter(210, (hStuf * 6) + extraYOffset, params.scoreData.tallies.good, 0xFF89C9E5);
|
||||
ratingGrp.add(tallyGood);
|
||||
|
||||
var tallyBad:TallyCounter = new TallyCounter(190, (hStuf * 7) + extraYOffset, params.tallies.bad, 0xFFE6CF8A);
|
||||
var tallyBad:TallyCounter = new TallyCounter(190, (hStuf * 7) + extraYOffset, params.scoreData.tallies.bad, 0xFFE6CF8A);
|
||||
ratingGrp.add(tallyBad);
|
||||
|
||||
var tallyShit:TallyCounter = new TallyCounter(220, (hStuf * 8) + extraYOffset, params.tallies.shit, 0xFFE68C8A);
|
||||
var tallyShit:TallyCounter = new TallyCounter(220, (hStuf * 8) + extraYOffset, params.scoreData.tallies.shit, 0xFFE68C8A);
|
||||
ratingGrp.add(tallyShit);
|
||||
|
||||
var tallyMissed:TallyCounter = new TallyCounter(260, (hStuf * 9) + extraYOffset, params.tallies.missed, 0xFFC68AE6);
|
||||
var tallyMissed:TallyCounter = new TallyCounter(260, (hStuf * 9) + extraYOffset, params.scoreData.tallies.missed, 0xFFC68AE6);
|
||||
ratingGrp.add(tallyMissed);
|
||||
|
||||
var score:ResultScore = new ResultScore(35, 305, 10, params.scoreData.score);
|
||||
score.visible = false;
|
||||
add(score);
|
||||
|
||||
for (ind => rating in ratingGrp.members)
|
||||
{
|
||||
rating.visible = false;
|
||||
|
@ -233,18 +248,29 @@ class ResultState extends MusicBeatSubState
|
|||
|
||||
ratingsPopin.animation.finishCallback = anim -> {
|
||||
scorePopin.animation.play("score");
|
||||
scorePopin.animation.finishCallback = anim -> {
|
||||
score.visible = true;
|
||||
score.animateNumbers();
|
||||
};
|
||||
scorePopin.visible = true;
|
||||
|
||||
highscoreNew.visible = true;
|
||||
highscoreNew.animation.play("new");
|
||||
FlxTween.tween(highscoreNew, {y: highscoreNew.y + 10}, 0.8, {ease: FlxEase.quartOut});
|
||||
if (params.isNewHighscore)
|
||||
{
|
||||
highscoreNew.visible = true;
|
||||
highscoreNew.animation.play("new");
|
||||
FlxTween.tween(highscoreNew, {y: highscoreNew.y + 10}, 0.8, {ease: FlxEase.quartOut});
|
||||
}
|
||||
else
|
||||
{
|
||||
highscoreNew.visible = false;
|
||||
}
|
||||
};
|
||||
|
||||
switch (resultsVariation)
|
||||
{
|
||||
case SHIT:
|
||||
bfSHIT.visible = true;
|
||||
bfSHIT.playAnimation("");
|
||||
// case SHIT:
|
||||
// bfSHIT.visible = true;
|
||||
// bfSHIT.playAnimation("");
|
||||
|
||||
case NORMAL:
|
||||
boyfriend.animation.play('fall');
|
||||
|
@ -266,9 +292,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("");
|
||||
|
@ -276,8 +302,6 @@ class ResultState extends MusicBeatSubState
|
|||
}
|
||||
});
|
||||
|
||||
if (params.tallies.isNewHighscore) trace("ITS A NEW HIGHSCORE!!!");
|
||||
|
||||
super.create();
|
||||
}
|
||||
|
||||
|
@ -365,7 +389,7 @@ class ResultState extends MusicBeatSubState
|
|||
}
|
||||
else
|
||||
{
|
||||
openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new FreeplayState(null, sticker)));
|
||||
openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> FreeplayState.build(null, sticker)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -393,8 +417,13 @@ typedef ResultsStateParams =
|
|||
*/
|
||||
var title:String;
|
||||
|
||||
/**
|
||||
* Whether the displayed score is a new highscore
|
||||
*/
|
||||
var isNewHighscore:Bool;
|
||||
|
||||
/**
|
||||
* The score, accuracy, and judgements.
|
||||
*/
|
||||
var tallies:Highscore.Tallies;
|
||||
var scoreData:SaveScoreData;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -10,6 +10,8 @@ import funkin.util.TimerUtil;
|
|||
|
||||
class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
||||
{
|
||||
public var offsets:Array<Int> = [0, 0];
|
||||
|
||||
override public function new()
|
||||
{
|
||||
super();
|
||||
|
@ -29,9 +31,9 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
|||
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<FlxSprite>
|
|||
|
||||
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,15 +82,15 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
|||
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;
|
||||
comboSpr.velocity.y -= 150;
|
||||
comboSpr.velocity.x += FlxG.random.int(1, 10);
|
||||
|
||||
add(comboSpr);
|
||||
// add(comboSpr);
|
||||
|
||||
if (PlayState.instance.currentStageId.startsWith('school'))
|
||||
{
|
||||
|
@ -133,14 +138,14 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
|
|||
}
|
||||
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);
|
||||
|
|
|
@ -6,6 +6,8 @@ import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
|||
import flixel.math.FlxMath;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.text.FlxText.FlxTextAlign;
|
||||
import funkin.util.MathUtil;
|
||||
|
||||
/**
|
||||
* Numerical counters used next to each judgement in the Results screen.
|
||||
|
@ -13,18 +15,23 @@ import flixel.tweens.FlxTween;
|
|||
class TallyCounter extends FlxTypedSpriteGroup<FlxSprite>
|
||||
{
|
||||
public var curNumber:Float = 0;
|
||||
|
||||
public var neededNumber:Int = 0;
|
||||
|
||||
public var flavour:Int = 0xFFFFFFFF;
|
||||
|
||||
public function new(x:Float, y:Float, neededNumber:Int = 0, ?flavour:Int = 0xFFFFFFFF)
|
||||
public var align:FlxTextAlign = FlxTextAlign.LEFT;
|
||||
|
||||
public function new(x:Float, y:Float, neededNumber:Int = 0, ?flavour:Int = 0xFFFFFFFF, align:FlxTextAlign = FlxTextAlign.LEFT)
|
||||
{
|
||||
super(x, y);
|
||||
|
||||
this.align = align;
|
||||
|
||||
this.flavour = flavour;
|
||||
|
||||
this.neededNumber = neededNumber;
|
||||
drawNumbers();
|
||||
|
||||
if (curNumber == neededNumber) drawNumbers();
|
||||
}
|
||||
|
||||
var tmr:Float = 0;
|
||||
|
@ -41,6 +48,8 @@ class TallyCounter extends FlxTypedSpriteGroup<FlxSprite>
|
|||
var seperatedScore:Array<Int> = [];
|
||||
var tempCombo:Int = Math.round(curNumber);
|
||||
|
||||
var fullNumberDigits:Int = Std.int(Math.max(1, Math.ceil(MathUtil.logBase(10, neededNumber))));
|
||||
|
||||
while (tempCombo != 0)
|
||||
{
|
||||
seperatedScore.push(tempCombo % 10);
|
||||
|
@ -55,7 +64,13 @@ class TallyCounter extends FlxTypedSpriteGroup<FlxSprite>
|
|||
{
|
||||
if (ind >= members.length)
|
||||
{
|
||||
var numb:TallyNumber = new TallyNumber(ind * 43, 0, num);
|
||||
var xPos = ind * (43 * this.scale.x);
|
||||
if (this.align == FlxTextAlign.RIGHT)
|
||||
{
|
||||
xPos -= (fullNumberDigits * (43 * this.scale.x));
|
||||
}
|
||||
var numb:TallyNumber = new TallyNumber(xPos, 0, num);
|
||||
numb.scale.set(this.scale.x, this.scale.y);
|
||||
add(numb);
|
||||
numb.color = flavour;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -50,8 +50,8 @@ class SetCameraBopSongEvent extends SongEvent
|
|||
var intensity:Null<Float> = data.getFloat('intensity');
|
||||
if (intensity == null) intensity = 1.0;
|
||||
|
||||
PlayState.instance.cameraBopIntensity = Constants.DEFAULT_BOP_INTENSITY * intensity;
|
||||
PlayState.instance.hudCameraZoomIntensity = 1.015 * 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}');
|
||||
}
|
||||
|
|
|
@ -77,13 +77,6 @@ class NoteHoldCover extends FlxTypedSpriteGroup<FlxSprite>
|
|||
public override function update(elapsed):Void
|
||||
{
|
||||
super.update(elapsed);
|
||||
if ((!holdNote.alive || holdNote.missedNote) && !glow.animation.curAnim.name.startsWith('holdCoverEnd'))
|
||||
{
|
||||
// If alive is false, the hold note was held to completion.
|
||||
// If missedNote is true, the hold note was "dropped".
|
||||
|
||||
playEnd();
|
||||
}
|
||||
}
|
||||
|
||||
public function playStart():Void
|
||||
|
|
|
@ -295,6 +295,11 @@ class Strumline extends FlxSpriteGroup
|
|||
{
|
||||
if (noteData.length == 0) return;
|
||||
|
||||
// Ensure note data gets reset if the song happens to loop.
|
||||
// NOTE: I had to remove this line because it was causing notes visible during the countdown to be placed multiple times.
|
||||
// I don't remember what bug I was trying to fix by adding this.
|
||||
// if (conductorInUse.currentStep == 0) nextNoteIndex = 0;
|
||||
|
||||
var songStart:Float = PlayState.instance?.startTimestamp ?? 0.0;
|
||||
var hitWindowStart:Float = Conductor.instance.songPosition - Constants.HIT_WINDOW_MS;
|
||||
var renderWindowStart:Float = Conductor.instance.songPosition + RENDER_DISTANCE_MS;
|
||||
|
@ -365,8 +370,6 @@ class Strumline extends FlxSpriteGroup
|
|||
// Hold note is offscreen, kill it.
|
||||
holdNote.visible = false;
|
||||
holdNote.kill(); // Do not destroy! Recycling is faster.
|
||||
|
||||
// The cover will see this and clean itself up.
|
||||
}
|
||||
else if (holdNote.hitNote && holdNote.sustainLength <= 0)
|
||||
{
|
||||
|
@ -380,10 +383,16 @@ class Strumline extends FlxSpriteGroup
|
|||
playStatic(holdNote.noteDirection);
|
||||
}
|
||||
|
||||
if (holdNote.cover != null)
|
||||
if (holdNote.cover != null && isPlayer)
|
||||
{
|
||||
holdNote.cover.playEnd();
|
||||
}
|
||||
else if (holdNote.cover != null)
|
||||
{
|
||||
// *lightning* *zap* *crackle*
|
||||
holdNote.cover.visible = false;
|
||||
holdNote.cover.kill();
|
||||
}
|
||||
|
||||
holdNote.visible = false;
|
||||
holdNote.kill();
|
||||
|
@ -405,6 +414,13 @@ class Strumline extends FlxSpriteGroup
|
|||
{
|
||||
holdNote.y = this.y - INITIAL_OFFSET + calculateNoteYPos(holdNote.strumTime, vwoosh) + yOffset + STRUMLINE_SIZE / 2;
|
||||
}
|
||||
|
||||
// Clean up the cover.
|
||||
if (holdNote.cover != null)
|
||||
{
|
||||
holdNote.cover.visible = false;
|
||||
holdNote.cover.kill();
|
||||
}
|
||||
}
|
||||
else if (Conductor.instance.songPosition > holdNote.strumTime && holdNote.hitNote)
|
||||
{
|
||||
|
@ -822,7 +838,7 @@ class Strumline extends FlxSpriteGroup
|
|||
{
|
||||
// The note sprite pool is full and all note splashes are active.
|
||||
// We have to create a new note.
|
||||
result = new SustainTrail(0, 100, noteStyle);
|
||||
result = new SustainTrail(0, 0, noteStyle);
|
||||
this.holdNotes.add(result);
|
||||
}
|
||||
|
||||
|
|
|
@ -404,11 +404,12 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
|||
*
|
||||
* @param variationId Optionally filter by a single variation.
|
||||
* @param variationIds Optionally filter by multiple variations.
|
||||
* @param showLocked Include charts which are not unlocked
|
||||
* @param showHidden Include charts which are not accessible to the player.
|
||||
*
|
||||
* @return The list of difficulties.
|
||||
*/
|
||||
public function listDifficulties(?variationId:String, ?variationIds:Array<String>, showHidden:Bool = false):Array<String>
|
||||
public function listDifficulties(?variationId:String, ?variationIds:Array<String>, showLocked:Bool = false, showHidden:Bool = false):Array<String>
|
||||
{
|
||||
if (variationIds == null) variationIds = [];
|
||||
if (variationId != null) variationIds.push(variationId);
|
||||
|
|
|
@ -223,7 +223,8 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
|
|||
|
||||
if (propSprite.frames == null || propSprite.frames.numFrames == 0)
|
||||
{
|
||||
trace(' ERROR: Could not build texture for prop.');
|
||||
@:privateAccess
|
||||
trace(' ERROR: Could not build texture for prop. Check the asset path (${Paths.currentLevel ?? 'default'}, ${dataProp.assetPath}).');
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,12 +9,13 @@ import funkin.save.migrator.SaveDataMigrator;
|
|||
import funkin.ui.debug.charting.ChartEditorState.ChartEditorLiveInputStyle;
|
||||
import funkin.ui.debug.charting.ChartEditorState.ChartEditorTheme;
|
||||
import thx.semver.Version;
|
||||
import funkin.util.SerializerUtil;
|
||||
|
||||
@:nullSafety
|
||||
class Save
|
||||
{
|
||||
// Version 2.0.2 adds attributes to `optionsChartEditor`, that should return default values if they are null.
|
||||
public static final SAVE_DATA_VERSION:thx.semver.Version = "2.0.2";
|
||||
public static final SAVE_DATA_VERSION:thx.semver.Version = "2.0.3";
|
||||
public static final SAVE_DATA_VERSION_RULE:thx.semver.VersionRule = "2.0.x";
|
||||
|
||||
// We load this version's saves from a new save path, to maintain SOME level of backwards compatibility.
|
||||
|
@ -391,6 +392,22 @@ class Save
|
|||
*/
|
||||
public function getLevelScore(levelId:String, difficultyId:String = 'normal'):Null<SaveScoreData>
|
||||
{
|
||||
if (data.scores?.levels == null)
|
||||
{
|
||||
if (data.scores == null)
|
||||
{
|
||||
data.scores =
|
||||
{
|
||||
songs: [],
|
||||
levels: []
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
data.scores.levels = [];
|
||||
}
|
||||
}
|
||||
|
||||
var level = data.scores.levels.get(levelId);
|
||||
if (level == null)
|
||||
{
|
||||
|
@ -641,6 +658,9 @@ class Save
|
|||
{
|
||||
trace("[SAVE] Loading save from slot " + slot + "...");
|
||||
|
||||
// Prevent crashes if the save data is corrupted.
|
||||
SerializerUtil.initSerializer();
|
||||
|
||||
FlxG.save.bind('$SAVE_NAME${slot}', SAVE_PATH);
|
||||
|
||||
if (FlxG.save.isEmpty())
|
||||
|
@ -650,9 +670,9 @@ class Save
|
|||
if (legacySaveData != null)
|
||||
{
|
||||
trace('[SAVE] Found legacy save data, converting...');
|
||||
var gameSave = SaveDataMigrator.migrate(legacySaveData);
|
||||
var gameSave = SaveDataMigrator.migrateFromLegacy(legacySaveData);
|
||||
@:privateAccess
|
||||
FlxG.save.mergeData(gameSave.data);
|
||||
FlxG.save.mergeData(gameSave.data, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -664,7 +684,7 @@ class Save
|
|||
trace('[SAVE] Loaded save data.');
|
||||
@:privateAccess
|
||||
var gameSave = SaveDataMigrator.migrate(FlxG.save.data);
|
||||
FlxG.save.mergeData(gameSave.data);
|
||||
FlxG.save.mergeData(gameSave.data, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -673,7 +693,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;
|
||||
|
|
|
@ -3,6 +3,7 @@ package funkin.save.migrator;
|
|||
import funkin.save.Save;
|
||||
import funkin.save.migrator.RawSaveData_v1_0_0;
|
||||
import thx.semver.Version;
|
||||
import funkin.util.StructureUtil;
|
||||
import funkin.util.VersionUtil;
|
||||
|
||||
@:nullSafety
|
||||
|
@ -26,7 +27,7 @@ class SaveDataMigrator
|
|||
if (VersionUtil.validateVersion(version, Save.SAVE_DATA_VERSION_RULE))
|
||||
{
|
||||
// Simply import the structured data.
|
||||
var save:Save = new Save(inputData);
|
||||
var save:Save = new Save(StructureUtil.deepMerge(Save.getDefault(), inputData));
|
||||
return save;
|
||||
}
|
||||
else
|
||||
|
|
34
source/funkin/ui/credits/CreditsData.hx
Normal file
34
source/funkin/ui/credits/CreditsData.hx
Normal file
|
@ -0,0 +1,34 @@
|
|||
package funkin.ui.credits;
|
||||
|
||||
/**
|
||||
* The members of the Funkin' Crew, organized by their roles.
|
||||
*/
|
||||
typedef CreditsData =
|
||||
{
|
||||
var entries:Array<CreditsDataRole>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The members of a specific role on the Funkin' Crew.
|
||||
*/
|
||||
typedef CreditsDataRole =
|
||||
{
|
||||
@:optional
|
||||
var header:String;
|
||||
|
||||
@:optional
|
||||
@:default([])
|
||||
var body:Array<CreditsDataMember>;
|
||||
|
||||
@:optional
|
||||
@:default(false)
|
||||
var appendBackers:Bool;
|
||||
}
|
||||
|
||||
/**
|
||||
* A member of a specific person on the Funkin' Crew.
|
||||
*/
|
||||
typedef CreditsDataMember =
|
||||
{
|
||||
var line:String;
|
||||
}
|
142
source/funkin/ui/credits/CreditsDataHandler.hx
Normal file
142
source/funkin/ui/credits/CreditsDataHandler.hx
Normal file
|
@ -0,0 +1,142 @@
|
|||
package funkin.ui.credits;
|
||||
|
||||
import funkin.data.JsonFile;
|
||||
|
||||
using StringTools;
|
||||
|
||||
@:nullSafety
|
||||
class CreditsDataHandler
|
||||
{
|
||||
public static final BACKER_PUBLIC_URL:String = 'https://funkin.me/backers';
|
||||
|
||||
#if HARDCODED_CREDITS
|
||||
static final CREDITS_DATA_PATH:String = "assets/exclude/data/credits.json";
|
||||
#else
|
||||
static final CREDITS_DATA_PATH:String = "assets/data/credits.json";
|
||||
#end
|
||||
|
||||
public static function debugPrint(data:Null<CreditsData>):Void
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
trace('CreditsData(NULL)');
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.entries == null || data.entries.length == 0)
|
||||
{
|
||||
trace('CreditsData(EMPTY)');
|
||||
return;
|
||||
}
|
||||
|
||||
var entryCount = data.entries.length;
|
||||
var lineCount = 0;
|
||||
for (entry in data.entries)
|
||||
{
|
||||
lineCount += entry?.body?.length ?? 0;
|
||||
}
|
||||
|
||||
trace('CreditsData($entryCount entries containing $lineCount lines)');
|
||||
}
|
||||
|
||||
/**
|
||||
* If for some reason the full credits won't load,
|
||||
* use this hardcoded data for the original Funkin' Crew.
|
||||
*
|
||||
* @return `CreditsData`
|
||||
*/
|
||||
public static inline function getFallback():CreditsData
|
||||
{
|
||||
return {
|
||||
entries: [
|
||||
{
|
||||
header: 'Founders',
|
||||
body: [
|
||||
{line: 'ninjamuffin99'},
|
||||
{line: 'PhantomArcade'},
|
||||
{line: 'KawaiSprite'},
|
||||
{line: 'evilsk8r'},
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
public static function fetchBackerEntries():Array<String>
|
||||
{
|
||||
// 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 [];
|
||||
}
|
||||
|
||||
#if HARDCODED_CREDITS
|
||||
/**
|
||||
* The data for the credits.
|
||||
* Hardcoded into game via a macro at compile time.
|
||||
*/
|
||||
public static final CREDITS_DATA:Null<CreditsData> = #if macro null #else CreditsDataMacro.loadCreditsData() #end;
|
||||
#else
|
||||
|
||||
/**
|
||||
* The data for the credits.
|
||||
* Loaded dynamically from the game folder when needed.
|
||||
* Nullable because data may fail to parse.
|
||||
*/
|
||||
public static var CREDITS_DATA(get, default):Null<CreditsData> = null;
|
||||
|
||||
static function get_CREDITS_DATA():Null<CreditsData>
|
||||
{
|
||||
if (CREDITS_DATA == null) CREDITS_DATA = parseCreditsData(fetchCreditsData());
|
||||
|
||||
return CREDITS_DATA;
|
||||
}
|
||||
|
||||
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<CreditsData>
|
||||
{
|
||||
#if !macro
|
||||
if (file.contents == null) return null;
|
||||
|
||||
var parser = new json2object.JsonParser<CreditsData>();
|
||||
parser.ignoreUnknownVariables = false;
|
||||
trace('[CREDITS] Parsing credits data from ${CREDITS_DATA_PATH}');
|
||||
parser.fromJson(file.contents, file.fileName);
|
||||
|
||||
if (parser.errors.length > 0)
|
||||
{
|
||||
printErrors(parser.errors, file.fileName);
|
||||
return null;
|
||||
}
|
||||
return parser.value;
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
}
|
||||
|
||||
static function printErrors(errors:Array<json2object.Error>, id:String = ''):Void
|
||||
{
|
||||
trace('[CREDITS] Failed to parse credits data: ${id}');
|
||||
|
||||
for (error in errors)
|
||||
funkin.data.DataError.printError(error);
|
||||
}
|
||||
#end
|
||||
}
|
67
source/funkin/ui/credits/CreditsDataMacro.hx
Normal file
67
source/funkin/ui/credits/CreditsDataMacro.hx
Normal file
|
@ -0,0 +1,67 @@
|
|||
package funkin.ui.credits;
|
||||
|
||||
#if macro
|
||||
import haxe.macro.Context;
|
||||
#end
|
||||
|
||||
@:access(funkin.ui.credits.CreditsDataHandler)
|
||||
class CreditsDataMacro
|
||||
{
|
||||
public static macro function loadCreditsData():haxe.macro.Expr.ExprOf<CreditsData>
|
||||
{
|
||||
#if !display
|
||||
trace('Hardcoding credits data...');
|
||||
var json = CreditsDataMacro.fetchJSON();
|
||||
|
||||
if (json == null)
|
||||
{
|
||||
Context.info('[WARN] Could not fetch JSON data for credits.', Context.currentPos());
|
||||
return macro $v{CreditsDataHandler.getFallback()};
|
||||
}
|
||||
|
||||
var creditsData = CreditsDataMacro.parseJSON(json);
|
||||
|
||||
if (creditsData == null)
|
||||
{
|
||||
Context.info('[WARN] Could not parse JSON data for credits.', Context.currentPos());
|
||||
return macro $v{CreditsDataHandler.getFallback()};
|
||||
}
|
||||
|
||||
CreditsDataHandler.debugPrint(creditsData);
|
||||
return macro $v{creditsData};
|
||||
// return macro $v{null};
|
||||
#else
|
||||
// `#if display` is used for code completion. In this case we return
|
||||
// a minimal value to keep code completion fast.
|
||||
return macro $v{CreditsDataHandler.getFallback()};
|
||||
#end
|
||||
}
|
||||
|
||||
#if macro
|
||||
static function fetchJSON():Null<String>
|
||||
{
|
||||
return sys.io.File.getContent(CreditsDataHandler.CREDITS_DATA_PATH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the JSON data for the credits.
|
||||
*
|
||||
* @param json The string data to parse.
|
||||
* @return The parsed data.
|
||||
*/
|
||||
static function parseJSON(json:String):Null<CreditsData>
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: Use something with better validation but that still works at macro time.
|
||||
return haxe.Json.parse(json);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
trace('[ERROR] Failed to parse JSON data for credits.');
|
||||
trace(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
213
source/funkin/ui/credits/CreditsState.hx
Normal file
213
source/funkin/ui/credits/CreditsState.hx
Normal file
|
@ -0,0 +1,213 @@
|
|||
package funkin.ui.credits;
|
||||
|
||||
import flixel.text.FlxText;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.audio.FunkinSound;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
|
||||
/**
|
||||
* The state used to display the credits scroll.
|
||||
* AAA studios often fail to credit properly, and we're better than them!
|
||||
*/
|
||||
class CreditsState extends MusicBeatState
|
||||
{
|
||||
/**
|
||||
* The height the credits should start at.
|
||||
* Make this an instanced variable so it gets set by the constructor.
|
||||
*/
|
||||
final STARTING_HEIGHT = FlxG.height;
|
||||
|
||||
/**
|
||||
* The padding on each side of the screen.
|
||||
*/
|
||||
static final SCREEN_PAD = 24;
|
||||
|
||||
/**
|
||||
* The width of the screen the credits should maximally fill up.
|
||||
* Make this an instanced variable so it gets set by the constructor.
|
||||
*/
|
||||
final FULL_WIDTH = FlxG.width - (SCREEN_PAD * 2);
|
||||
|
||||
/**
|
||||
* The font to use to display the text.
|
||||
* To use a font from the `assets` folder, use `Paths.font(...)`.
|
||||
* Choose something that will render Unicode properly.
|
||||
*/
|
||||
static final CREDITS_FONT = 'Arial';
|
||||
|
||||
/**
|
||||
* The size of the font.
|
||||
*/
|
||||
static final CREDITS_FONT_SIZE = 48;
|
||||
|
||||
static final CREDITS_HEADER_FONT_SIZE = 72;
|
||||
|
||||
/**
|
||||
* The color of the text itself.
|
||||
*/
|
||||
static final CREDITS_FONT_COLOR = FlxColor.WHITE;
|
||||
|
||||
/**
|
||||
* The color of the text's outline.
|
||||
*/
|
||||
static final CREDITS_FONT_STROKE_COLOR = FlxColor.BLACK;
|
||||
|
||||
/**
|
||||
* The speed the credits scroll at, in pixels per second.
|
||||
*/
|
||||
static final CREDITS_SCROLL_BASE_SPEED = 25.0;
|
||||
|
||||
/**
|
||||
* The speed the credits scroll at while the button is held, in pixels per second.
|
||||
*/
|
||||
static final CREDITS_SCROLL_FAST_SPEED = CREDITS_SCROLL_BASE_SPEED * 4.0;
|
||||
|
||||
/**
|
||||
* The actual sprites and text used to display the credits.
|
||||
*/
|
||||
var creditsGroup:FlxSpriteGroup;
|
||||
|
||||
var scrollPaused:Bool = false;
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
public override function create():Void
|
||||
{
|
||||
super.create();
|
||||
|
||||
// Background
|
||||
var bg = new FlxSprite(Paths.image('menuDesat'));
|
||||
bg.scrollFactor.x = 0;
|
||||
bg.scrollFactor.y = 0;
|
||||
bg.setGraphicSize(Std.int(FlxG.width));
|
||||
bg.updateHitbox();
|
||||
bg.x = 0;
|
||||
bg.y = 0;
|
||||
bg.visible = true;
|
||||
bg.color = 0xFFB57EDC; // Lavender
|
||||
add(bg);
|
||||
|
||||
// TODO: Once we need to display Kickstarter backers,
|
||||
// make this use a recycled pool so we don't kill peformance.
|
||||
creditsGroup = new FlxSpriteGroup();
|
||||
creditsGroup.x = SCREEN_PAD;
|
||||
creditsGroup.y = STARTING_HEIGHT;
|
||||
|
||||
buildCreditsGroup();
|
||||
|
||||
add(creditsGroup);
|
||||
|
||||
// Music
|
||||
FunkinSound.playMusic('freeplayRandom',
|
||||
{
|
||||
startingVolume: 0.0,
|
||||
overrideExisting: true,
|
||||
restartTrack: true,
|
||||
loop: true
|
||||
});
|
||||
FlxG.sound.music.fadeIn(2, 0, 0.8);
|
||||
}
|
||||
|
||||
function buildCreditsGroup():Void
|
||||
{
|
||||
var y = 0;
|
||||
|
||||
for (entry in CreditsDataHandler.CREDITS_DATA.entries)
|
||||
{
|
||||
if (entry.header != null)
|
||||
{
|
||||
creditsGroup.add(buildCreditsLine(entry.header, y, true, CreditsSide.Center));
|
||||
y += CREDITS_HEADER_FONT_SIZE;
|
||||
}
|
||||
|
||||
for (line in entry?.body ?? [])
|
||||
{
|
||||
creditsGroup.add(buildCreditsLine(line.line, y, false, CreditsSide.Center));
|
||||
y += CREDITS_FONT_SIZE;
|
||||
}
|
||||
|
||||
if (entry.appendBackers)
|
||||
{
|
||||
var backers = CreditsDataHandler.fetchBackerEntries();
|
||||
for (backer in backers)
|
||||
{
|
||||
creditsGroup.add(buildCreditsLine(backer, y, false, CreditsSide.Center));
|
||||
y += CREDITS_FONT_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
// Padding between each role.
|
||||
y += CREDITS_FONT_SIZE * 2;
|
||||
}
|
||||
}
|
||||
|
||||
function buildCreditsLine(text:String, yPos:Float, header:Bool, side:CreditsSide = CreditsSide.Center):FlxText
|
||||
{
|
||||
// CreditsSide.Center: Full screen width
|
||||
// CreditsSide.Left: Left half of screen
|
||||
// CreditsSide.Right: Right half of screen
|
||||
var xPos = (side == CreditsSide.Right) ? (FULL_WIDTH / 2) : 0;
|
||||
var width = (side == CreditsSide.Center) ? FULL_WIDTH : (FULL_WIDTH / 2);
|
||||
var size = header ? CREDITS_HEADER_FONT_SIZE : CREDITS_FONT_SIZE;
|
||||
|
||||
var creditsLine:FlxText = new FlxText(xPos, yPos, width, text);
|
||||
creditsLine.setFormat(CREDITS_FONT, size, CREDITS_FONT_COLOR, FlxTextAlign.CENTER, FlxTextBorderStyle.OUTLINE, CREDITS_FONT_STROKE_COLOR, true);
|
||||
|
||||
return creditsLine;
|
||||
}
|
||||
|
||||
public override function update(elapsed:Float):Void
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
if (!scrollPaused)
|
||||
{
|
||||
// TODO: Replace with whatever the special note button is.
|
||||
if (controls.ACCEPT || FlxG.keys.pressed.SPACE)
|
||||
{
|
||||
// Move the whole group.
|
||||
creditsGroup.y -= CREDITS_SCROLL_FAST_SPEED * elapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Move the whole group.
|
||||
creditsGroup.y -= CREDITS_SCROLL_BASE_SPEED * elapsed;
|
||||
}
|
||||
}
|
||||
|
||||
if (controls.BACK || hasEnded())
|
||||
{
|
||||
exit();
|
||||
}
|
||||
else if (controls.PAUSE)
|
||||
{
|
||||
scrollPaused = !scrollPaused;
|
||||
}
|
||||
}
|
||||
|
||||
function hasEnded():Bool
|
||||
{
|
||||
return creditsGroup.y < -creditsGroup.height;
|
||||
}
|
||||
|
||||
function exit():Void
|
||||
{
|
||||
FlxG.switchState(new funkin.ui.mainmenu.MainMenuState());
|
||||
}
|
||||
|
||||
public override function destroy():Void
|
||||
{
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
enum CreditsSide
|
||||
{
|
||||
Left;
|
||||
Center;
|
||||
Right;
|
||||
}
|
|
@ -6,15 +6,15 @@ import flixel.addons.transition.FlxTransitionableState;
|
|||
import flixel.FlxCamera;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.FlxSubState;
|
||||
import flixel.graphics.FlxGraphic;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import funkin.graphics.FunkinCamera;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
import flixel.input.gamepad.FlxGamepadInputID;
|
||||
import flixel.input.keyboard.FlxKey;
|
||||
import flixel.input.mouse.FlxMouseEvent;
|
||||
import flixel.math.FlxMath;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.math.FlxRect;
|
||||
import flixel.sound.FlxSound;
|
||||
import flixel.system.debug.log.LogStyle;
|
||||
import flixel.system.FlxAssets.FlxSoundAsset;
|
||||
import flixel.text.FlxText;
|
||||
|
@ -26,28 +26,23 @@ import flixel.util.FlxSort;
|
|||
import flixel.util.FlxTimer;
|
||||
import funkin.audio.FunkinSound;
|
||||
import funkin.audio.visualize.PolygonSpectogram;
|
||||
import funkin.audio.visualize.PolygonSpectogram;
|
||||
import funkin.audio.VoicesGroup;
|
||||
import funkin.audio.waveform.WaveformSprite;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.data.song.SongData.SongCharacterData;
|
||||
import funkin.data.song.SongData.SongCharacterData;
|
||||
import funkin.data.song.SongData.SongChartData;
|
||||
import funkin.data.song.SongData.SongChartData;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.data.song.SongData.SongMetadata;
|
||||
import funkin.data.song.SongData.SongMetadata;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongData.SongOffsets;
|
||||
import funkin.data.song.SongDataUtils;
|
||||
import funkin.data.song.SongDataUtils;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.data.song.SongRegistry;
|
||||
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;
|
||||
|
@ -56,13 +51,12 @@ import funkin.play.character.CharacterData.CharacterDataParser;
|
|||
import funkin.play.components.HealthIcon;
|
||||
import funkin.play.notes.NoteSprite;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.play.PlayStatePlaylist;
|
||||
import funkin.play.song.Song;
|
||||
import funkin.save.Save;
|
||||
import funkin.ui.debug.charting.commands.AddEventsCommand;
|
||||
import funkin.ui.debug.charting.commands.AddNotesCommand;
|
||||
import funkin.ui.debug.charting.commands.ChartEditorCommand;
|
||||
import funkin.ui.debug.charting.commands.ChartEditorCommand;
|
||||
import funkin.ui.debug.charting.commands.ChartEditorCommand;
|
||||
import funkin.ui.debug.charting.commands.CopyItemsCommand;
|
||||
import funkin.ui.debug.charting.commands.CutItemsCommand;
|
||||
import funkin.ui.debug.charting.commands.DeselectAllItemsCommand;
|
||||
|
@ -83,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;
|
||||
|
@ -95,6 +90,7 @@ import funkin.ui.debug.charting.toolboxes.ChartEditorOffsetsToolbox;
|
|||
import funkin.ui.haxeui.components.CharacterPlayer;
|
||||
import funkin.ui.haxeui.HaxeUIState;
|
||||
import funkin.ui.mainmenu.MainMenuState;
|
||||
import funkin.ui.transition.LoadingState;
|
||||
import funkin.util.Constants;
|
||||
import funkin.util.FileUtil;
|
||||
import funkin.util.logging.CrashHandler;
|
||||
|
@ -119,7 +115,6 @@ import haxe.ui.containers.Grid;
|
|||
import haxe.ui.containers.HBox;
|
||||
import haxe.ui.containers.menus.Menu;
|
||||
import haxe.ui.containers.menus.MenuBar;
|
||||
import haxe.ui.containers.menus.MenuBar;
|
||||
import haxe.ui.containers.menus.MenuCheckBox;
|
||||
import haxe.ui.containers.menus.MenuItem;
|
||||
import haxe.ui.containers.ScrollView;
|
||||
|
@ -130,7 +125,6 @@ import haxe.ui.core.Screen;
|
|||
import haxe.ui.events.DragEvent;
|
||||
import haxe.ui.events.MouseEvent;
|
||||
import haxe.ui.events.UIEvent;
|
||||
import haxe.ui.events.UIEvent;
|
||||
import haxe.ui.focus.FocusManager;
|
||||
import haxe.ui.Toolkit;
|
||||
import openfl.display.BitmapData;
|
||||
|
@ -411,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;
|
||||
|
@ -473,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;
|
||||
}
|
||||
|
||||
|
@ -779,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<SongNoteData> = [];
|
||||
|
||||
// Note Movement
|
||||
|
||||
/**
|
||||
|
@ -809,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<Float> = [];
|
||||
|
||||
/**
|
||||
* Flip-flop to alternate between two stretching sounds.
|
||||
*/
|
||||
|
@ -1081,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
|
||||
*/
|
||||
|
@ -1959,10 +2032,15 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
var gridGhostNote:Null<ChartEditorNoteSprite> = 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<ChartEditorHoldNoteSprite> = null;
|
||||
|
||||
/**
|
||||
* A sprite used to indicate the hold note that will be placed on button release.
|
||||
*/
|
||||
var gridPlayheadGhostHoldNotes:Array<ChartEditorHoldNoteSprite> = [];
|
||||
|
||||
/**
|
||||
* A sprite used to indicate the event that will be placed on click.
|
||||
*/
|
||||
|
@ -1980,6 +2058,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
*/
|
||||
var notePreviewViewport:Null<FlxSliceSprite> = 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<FlxSprite> = 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.
|
||||
|
@ -2101,7 +2185,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
|
||||
loadPreferences();
|
||||
|
||||
uiCamera = new FunkinCamera();
|
||||
uiCamera = new FunkinCamera('chartEditorUI');
|
||||
FlxG.cameras.reset(uiCamera);
|
||||
|
||||
buildDefaultSongData();
|
||||
|
@ -2359,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;
|
||||
|
@ -2433,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());
|
||||
}
|
||||
|
||||
|
@ -2529,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.
|
||||
*/
|
||||
|
@ -3025,6 +3125,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
*/
|
||||
function setupTurboKeyHandlers():Void
|
||||
{
|
||||
// Keyboard shortcuts
|
||||
add(undoKeyHandler);
|
||||
add(redoKeyHandler);
|
||||
add(upKeyHandler);
|
||||
|
@ -3033,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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3719,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;
|
||||
|
@ -3754,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;
|
||||
|
@ -3777,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.
|
||||
|
@ -4390,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);
|
||||
|
@ -4953,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));
|
||||
|
@ -4994,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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5338,30 +5655,31 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
}
|
||||
catch (e)
|
||||
{
|
||||
this.error("Could Not Playtest", 'Got an error trying to playtest the song.\n${e}');
|
||||
this.error('Could Not Playtest', 'Got an error trying to playtest the song.\n${e}');
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Rework asset system so we can remove this.
|
||||
// TODO: Rework asset system so we can remove this jank.
|
||||
switch (currentSongStage)
|
||||
{
|
||||
case 'mainStage':
|
||||
Paths.setCurrentLevel('week1');
|
||||
PlayStatePlaylist.campaignId = 'week1';
|
||||
case 'spookyMansion':
|
||||
Paths.setCurrentLevel('week2');
|
||||
PlayStatePlaylist.campaignId = 'week2';
|
||||
case 'phillyTrain':
|
||||
Paths.setCurrentLevel('week3');
|
||||
PlayStatePlaylist.campaignId = 'week3';
|
||||
case 'limoRide':
|
||||
Paths.setCurrentLevel('week4');
|
||||
PlayStatePlaylist.campaignId = 'week4';
|
||||
case 'mallXmas' | 'mallEvil':
|
||||
Paths.setCurrentLevel('week5');
|
||||
PlayStatePlaylist.campaignId = 'week5';
|
||||
case 'school' | 'schoolEvil':
|
||||
Paths.setCurrentLevel('week6');
|
||||
PlayStatePlaylist.campaignId = 'week6';
|
||||
case 'tankmanBattlefield':
|
||||
Paths.setCurrentLevel('week7');
|
||||
PlayStatePlaylist.campaignId = 'week7';
|
||||
case 'phillyStreets' | 'phillyBlazin' | 'phillyBlazin2':
|
||||
Paths.setCurrentLevel('weekend1');
|
||||
PlayStatePlaylist.campaignId = 'weekend1';
|
||||
}
|
||||
Paths.setCurrentLevel(PlayStatePlaylist.campaignId);
|
||||
|
||||
subStateClosed.add(reviveUICamera);
|
||||
subStateClosed.add(resetConductorAfterTest);
|
||||
|
@ -5369,7 +5687,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
FlxTransitionableState.skipNextTransIn = false;
|
||||
FlxTransitionableState.skipNextTransOut = false;
|
||||
|
||||
var targetState = new PlayState(
|
||||
var targetStateParams =
|
||||
{
|
||||
targetSong: targetSong,
|
||||
targetDifficulty: selectedDifficulty,
|
||||
|
@ -5380,24 +5698,26 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
startTimestamp: startTimestamp,
|
||||
playbackRate: playbackRate,
|
||||
overrideMusic: true,
|
||||
});
|
||||
};
|
||||
|
||||
// Override music.
|
||||
if (audioInstTrack != null)
|
||||
{
|
||||
FlxG.sound.music = audioInstTrack;
|
||||
}
|
||||
targetState.vocals = audioVocalTrackGroup;
|
||||
|
||||
// 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;
|
||||
stopWelcomeMusic();
|
||||
openSubState(targetState);
|
||||
|
||||
LoadingState.loadPlayState(targetStateParams, false, true, function(targetState) {
|
||||
targetState.vocals = audioVocalTrackGroup;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -73,7 +73,7 @@ class ChartEditorImportExportHandler
|
|||
state.loadInstFromAsset(Paths.inst(songId, '-$variation'), variation);
|
||||
}
|
||||
|
||||
for (difficultyId in song.listDifficulties(variation))
|
||||
for (difficultyId in song.listDifficulties(variation, true, true))
|
||||
{
|
||||
var diff:Null<SongDifficulty> = song.getDifficulty(difficultyId, variation);
|
||||
if (diff == null) continue;
|
||||
|
|
|
@ -308,16 +308,6 @@ class ChartEditorToolboxHandler
|
|||
state.playtestBotPlayMode = checkboxBotPlay.selected;
|
||||
};
|
||||
|
||||
var checkboxDebugger:Null<CheckBox> = toolbox.findComponent('playtestDebuggerCheckbox', CheckBox);
|
||||
|
||||
if (checkboxDebugger == null) throw 'ChartEditorToolboxHandler.buildToolboxPlaytestPropertiesLayout() - Could not find playtestDebuggerCheckbox component.';
|
||||
|
||||
state.enabledDebuggerPopup = checkboxDebugger.selected;
|
||||
|
||||
checkboxDebugger.onClick = _ -> {
|
||||
state.enabledDebuggerPopup = checkboxDebugger.selected;
|
||||
};
|
||||
|
||||
var checkboxSongScripts:Null<CheckBox> = toolbox.findComponent('playtestSongScriptsCheckbox', CheckBox);
|
||||
|
||||
if (checkboxSongScripts == null)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -171,10 +171,7 @@ class LatencyState extends MusicBeatSubState
|
|||
trace(FlxG.sound.music._channel.position);
|
||||
*/
|
||||
|
||||
#if FLX_DEBUG
|
||||
funnyStatsGraph.update(FlxG.sound.music.time % 500);
|
||||
realStats.update(swagSong.getTimeWithDiff() % 500);
|
||||
#end
|
||||
// localConductor.update(swagSong.time, false);
|
||||
|
||||
if (FlxG.keys.justPressed.S)
|
||||
{
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package funkin.ui.freeplay;
|
||||
|
||||
import funkin.data.freeplay.AlbumData;
|
||||
import funkin.data.animation.AnimationData;
|
||||
import funkin.data.freeplay.AlbumRegistry;
|
||||
import funkin.data.IRegistryEntry;
|
||||
import flixel.graphics.FlxGraphic;
|
||||
|
@ -75,6 +76,16 @@ class Album implements IRegistryEntry<AlbumData>
|
|||
return _data.albumTitleAsset;
|
||||
}
|
||||
|
||||
public function hasAlbumTitleAnimations()
|
||||
{
|
||||
return _data.albumTitleAnimations.length > 0;
|
||||
}
|
||||
|
||||
public function getAlbumTitleAnimations():Array<AnimationData>
|
||||
{
|
||||
return _data.albumTitleAnimations;
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
return 'Album($id)';
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package funkin.ui.freeplay;
|
||||
|
||||
import funkin.graphics.adobeanimate.FlxAtlasSprite;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
import flixel.util.FlxSort;
|
||||
|
@ -7,6 +8,7 @@ import flixel.tweens.FlxTween;
|
|||
import flixel.util.FlxTimer;
|
||||
import flixel.tweens.FlxEase;
|
||||
import funkin.data.freeplay.AlbumRegistry;
|
||||
import funkin.util.assets.FlxAnimationUtil;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.util.SortUtil;
|
||||
import openfl.utils.Assets;
|
||||
|
@ -21,9 +23,9 @@ class AlbumRoll extends FlxSpriteGroup
|
|||
* The ID of the album to display.
|
||||
* Modify this value to automatically update the album art and title.
|
||||
*/
|
||||
public var albumId(default, set):String;
|
||||
public var albumId(default, set):Null<String>;
|
||||
|
||||
function set_albumId(value:String):String
|
||||
function set_albumId(value:Null<String>):Null<String>
|
||||
{
|
||||
if (this.albumId != value)
|
||||
{
|
||||
|
@ -34,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<FreeplayState.ExitMoverData>;
|
||||
|
||||
var albumData:Album;
|
||||
|
||||
final animNames:Map<String, String> = [
|
||||
"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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -65,6 +84,12 @@ class AlbumRoll extends FlxSpriteGroup
|
|||
*/
|
||||
function updateAlbum():Void
|
||||
{
|
||||
if (albumId == null)
|
||||
{
|
||||
// difficultyStars.stars.visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
albumData = AlbumRegistry.instance.fetchEntry(albumId);
|
||||
|
||||
if (albumData == null)
|
||||
|
@ -74,33 +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())))
|
||||
{
|
||||
albumTitle.loadGraphic(Paths.image(albumData.getAlbumTitleAssetKey()));
|
||||
}
|
||||
else
|
||||
{
|
||||
albumTitle.visible = false;
|
||||
}
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
|
@ -126,67 +126,46 @@ 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<FlxTimer> = null;
|
||||
|
||||
/**
|
||||
* Play the intro animation on the album art.
|
||||
*/
|
||||
public function playIntro():Void
|
||||
{
|
||||
albumArt.visible = true;
|
||||
FlxTween.tween(albumArt, {x: 950, y: 320, angle: -340}, 0.5, {ease: FlxEase.elasticOut});
|
||||
newAlbumArt.visible = true;
|
||||
newAlbumArt.playAnimation(animNames.get('$albumId-active'), false, false, false);
|
||||
|
||||
albumTitle.visible = false;
|
||||
// difficultyStars.stars.visible = false;
|
||||
new FlxTimer().start(0.75, function(_) {
|
||||
showTitle();
|
||||
// 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -27,8 +27,8 @@ class DJBoyfriend extends FlxAtlasSprite
|
|||
|
||||
var gotSpooked:Bool = false;
|
||||
|
||||
static final SPOOK_PERIOD:Float = 10.0;
|
||||
static final TV_PERIOD:Float = 10.0;
|
||||
static final SPOOK_PERIOD:Float = 120.0;
|
||||
static final TV_PERIOD:Float = 180.0;
|
||||
|
||||
// Time since dad last SPOOKED you.
|
||||
var timeSinceSpook:Float = 0;
|
||||
|
@ -43,7 +43,14 @@ class DJBoyfriend extends FlxAtlasSprite
|
|||
switch (name)
|
||||
{
|
||||
case "Boyfriend DJ watchin tv OG":
|
||||
if (number == 85) runTvLogic();
|
||||
if (number == 80)
|
||||
{
|
||||
FunkinSound.playOnce(Paths.sound('remote_click'));
|
||||
}
|
||||
if (number == 85)
|
||||
{
|
||||
runTvLogic();
|
||||
}
|
||||
default:
|
||||
}
|
||||
};
|
||||
|
@ -219,19 +226,17 @@ class DJBoyfriend extends FlxAtlasSprite
|
|||
if (cartoonSnd == null)
|
||||
{
|
||||
// tv is OFF, but getting turned on
|
||||
// Eric got FUCKING TROLLED there is no `tv_on` or `channel_switch` sound!
|
||||
// FunkinSound.playOnce(Paths.sound('tv_on'), 1.0, function() {
|
||||
// });
|
||||
loadCartoon();
|
||||
FunkinSound.playOnce(Paths.sound('tv_on'), 1.0, function() {
|
||||
loadCartoon();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// plays it smidge after the click
|
||||
// new FlxTimer().start(0.1, function(_) {
|
||||
// // FunkinSound.playOnce(Paths.sound('channel_switch'));
|
||||
// });
|
||||
cartoonSnd.destroy();
|
||||
loadCartoon();
|
||||
FunkinSound.playOnce(Paths.sound('channel_switch'), 1.0, function() {
|
||||
cartoonSnd.destroy();
|
||||
loadCartoon();
|
||||
});
|
||||
}
|
||||
|
||||
// loadCartoon();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -42,11 +42,11 @@ class FreeplayScore extends FlxTypedSpriteGroup<ScoreNum>
|
|||
return val;
|
||||
}
|
||||
|
||||
public function new(x:Float, y:Float, scoreShit:Int = 100)
|
||||
public function new(x:Float, y:Float, digitCount:Int, scoreShit:Int = 100)
|
||||
{
|
||||
super(x, y);
|
||||
|
||||
for (i in 0...7)
|
||||
for (i in 0...digitCount)
|
||||
{
|
||||
add(new ScoreNum(x + (45 * i), y, 0));
|
||||
}
|
||||
|
|
|
@ -133,8 +133,8 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
var stickerSubState:StickerSubState;
|
||||
|
||||
static var rememberedDifficulty:Null<String> = Constants.DEFAULT_DIFFICULTY;
|
||||
static var rememberedSongId:Null<String> = null;
|
||||
public static var rememberedDifficulty:Null<String> = Constants.DEFAULT_DIFFICULTY;
|
||||
public static var rememberedSongId:Null<String> = 'tutorial';
|
||||
|
||||
public function new(?params:FreeplayStateParams, ?stickers:StickerSubState)
|
||||
{
|
||||
|
@ -145,7 +145,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
stickerSubState = stickers;
|
||||
}
|
||||
|
||||
super();
|
||||
super(FlxColor.TRANSPARENT);
|
||||
}
|
||||
|
||||
override function create():Void
|
||||
|
@ -195,7 +195,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
var song:Song = SongRegistry.instance.fetchEntry(songId);
|
||||
|
||||
// Only display songs which actually have available charts for the current character.
|
||||
var availableDifficultiesForSong:Array<String> = song.listDifficulties(displayedVariations);
|
||||
var availableDifficultiesForSong:Array<String> = song.listDifficulties(displayedVariations, false);
|
||||
if (availableDifficultiesForSong.length == 0) continue;
|
||||
|
||||
songs.push(new FreeplaySongData(levelId, songId, song, displayedVariations));
|
||||
|
@ -380,7 +380,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
albumRoll = new AlbumRoll();
|
||||
albumRoll.albumId = 'volume1';
|
||||
albumRoll.albumId = null;
|
||||
add(albumRoll);
|
||||
|
||||
albumRoll.applyExitMovers(exitMovers);
|
||||
|
@ -425,7 +425,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
tmr.time = FlxG.random.float(20, 60);
|
||||
}, 0);
|
||||
|
||||
fp = new FreeplayScore(460, 60, 100);
|
||||
fp = new FreeplayScore(460, 60, 7, 100);
|
||||
fp.visible = false;
|
||||
add(fp);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -536,21 +532,18 @@ class FreeplayState extends MusicBeatSubState
|
|||
});
|
||||
}
|
||||
|
||||
var currentFilter:SongFilter = null;
|
||||
var currentFilteredSongs:Array<FreeplaySongData> = [];
|
||||
|
||||
/**
|
||||
* 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 onlyIfChanged Only apply the filter if the song list has changed
|
||||
*/
|
||||
public function generateSongList(?filterStuff:SongFilter, force:Bool = false):Void
|
||||
public function generateSongList(filterStuff:Null<SongFilter>, force:Bool = false, onlyIfChanged:Bool = true):Void
|
||||
{
|
||||
curSelected = 1;
|
||||
|
||||
for (cap in grpCapsules.members)
|
||||
{
|
||||
cap.kill();
|
||||
}
|
||||
|
||||
var tempSongs:Array<FreeplaySongData> = songs;
|
||||
|
||||
if (filterStuff != null)
|
||||
|
@ -582,6 +575,35 @@ class FreeplayState extends MusicBeatSubState
|
|||
}
|
||||
}
|
||||
|
||||
// Filter further by current selected difficulty.
|
||||
if (currentDifficulty != null)
|
||||
{
|
||||
tempSongs = tempSongs.filter(song -> {
|
||||
if (song == null) return true; // Random
|
||||
return song.songDifficulties.contains(currentDifficulty);
|
||||
});
|
||||
}
|
||||
|
||||
if (onlyIfChanged)
|
||||
{
|
||||
// == performs equality by reference
|
||||
if (tempSongs.isEqualUnordered(currentFilteredSongs)) return;
|
||||
}
|
||||
|
||||
// Only now do we know that the filter is actually changing.
|
||||
|
||||
rememberedSongId = grpCapsules.members[curSelected]?.songData?.songId ?? rememberedSongId;
|
||||
|
||||
for (cap in grpCapsules.members)
|
||||
{
|
||||
cap.kill();
|
||||
}
|
||||
|
||||
currentFilter = filterStuff;
|
||||
|
||||
currentFilteredSongs = tempSongs;
|
||||
curSelected = 0;
|
||||
|
||||
var hsvShader:HSVShader = new HSVShader();
|
||||
|
||||
var randomCapsule:SongMenuItem = grpCapsules.recycle(SongMenuItem);
|
||||
|
@ -616,14 +638,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);
|
||||
}
|
||||
|
@ -658,11 +673,12 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
if (FlxG.keys.justPressed.F)
|
||||
{
|
||||
if (songs[curSelected] != null)
|
||||
var targetSong = grpCapsules.members[curSelected]?.songData;
|
||||
if (targetSong != null)
|
||||
{
|
||||
var realShit:Int = curSelected;
|
||||
songs[curSelected].isFav = !songs[curSelected].isFav;
|
||||
if (songs[curSelected].isFav)
|
||||
targetSong.isFav = !targetSong.isFav;
|
||||
if (targetSong.isFav)
|
||||
{
|
||||
FlxTween.tween(grpCapsules.members[realShit], {angle: 360}, 0.4,
|
||||
{
|
||||
|
@ -854,11 +870,13 @@ class FreeplayState extends MusicBeatSubState
|
|||
{
|
||||
dj.resetAFKTimer();
|
||||
changeDiff(-1);
|
||||
generateSongList(currentFilter, true);
|
||||
}
|
||||
if (controls.UI_RIGHT_P && !FlxG.keys.pressed.CONTROL)
|
||||
{
|
||||
dj.resetAFKTimer();
|
||||
changeDiff(1);
|
||||
generateSongList(currentFilter, true);
|
||||
}
|
||||
|
||||
if (controls.BACK && !typing.hasFocus)
|
||||
|
@ -877,6 +895,8 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
for (spr in grpSpr)
|
||||
{
|
||||
if (spr == null) continue;
|
||||
|
||||
var funnyMoveShit:MoveData = moveData;
|
||||
|
||||
if (moveData.x == null) funnyMoveShit.x = spr.x;
|
||||
|
@ -899,7 +919,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
if (Type.getClass(FlxG.state) == MainMenuState)
|
||||
{
|
||||
FlxG.state.persistentUpdate = true;
|
||||
FlxG.state.persistentUpdate = false;
|
||||
FlxG.state.persistentDraw = true;
|
||||
}
|
||||
|
||||
|
@ -908,6 +928,11 @@ class FreeplayState extends MusicBeatSubState
|
|||
FlxTransitionableState.skipNextTransOut = true;
|
||||
if (Type.getClass(FlxG.state) == MainMenuState)
|
||||
{
|
||||
FunkinSound.playMusic('freakyMenu',
|
||||
{
|
||||
overrideExisting: true,
|
||||
restartTrack: false
|
||||
});
|
||||
close();
|
||||
}
|
||||
else
|
||||
|
@ -926,7 +951,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
public override function destroy():Void
|
||||
{
|
||||
super.destroy();
|
||||
var daSong:Null<FreeplaySongData> = songs[curSelected];
|
||||
var daSong:Null<FreeplaySongData> = currentFilteredSongs[curSelected];
|
||||
if (daSong != null)
|
||||
{
|
||||
clearDaCache(daSong.songName);
|
||||
|
@ -948,10 +973,10 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
currentDifficulty = diffIdsCurrent[currentDifficultyIndex];
|
||||
|
||||
var daSong:Null<FreeplaySongData> = songs[curSelected];
|
||||
var daSong:Null<FreeplaySongData> = grpCapsules.members[curSelected].songData;
|
||||
if (daSong != null)
|
||||
{
|
||||
var songScore:SaveScoreData = Save.instance.getSongScore(songs[curSelected].songId, currentDifficulty);
|
||||
var songScore:SaveScoreData = Save.instance.getSongScore(grpCapsules.members[curSelected].songData.songId, currentDifficulty);
|
||||
intendedScore = songScore?.score ?? 0;
|
||||
intendedCompletion = songScore?.accuracy ?? 0.0;
|
||||
rememberedDifficulty = currentDifficulty;
|
||||
|
@ -1011,15 +1036,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 ?? Constants.DEFAULT_ALBUM_ID;
|
||||
var newAlbumId:String = daSong?.albumId;
|
||||
if (albumRoll.albumId != newAlbumId)
|
||||
{
|
||||
albumRoll.albumId = newAlbumId;
|
||||
albumRoll.playIntro();
|
||||
albumRoll.skipIntro();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1103,6 +1125,12 @@ class FreeplayState extends MusicBeatSubState
|
|||
targetVariation: targetVariation,
|
||||
practiceMode: false,
|
||||
minimalMode: false,
|
||||
|
||||
#if (debug || FORCE_DEBUG_VERSION)
|
||||
botPlayMode: FlxG.keys.pressed.SHIFT,
|
||||
#else
|
||||
botPlayMode: false,
|
||||
#end
|
||||
// TODO: Make these an option! It's currently only accessible via chart editor.
|
||||
// startTimestamp: 0.0,
|
||||
// playbackRate: 0.5,
|
||||
|
@ -1115,20 +1143,18 @@ class FreeplayState extends MusicBeatSubState
|
|||
{
|
||||
if (rememberedSongId != null)
|
||||
{
|
||||
curSelected = songs.findIndex(function(song) {
|
||||
curSelected = currentFilteredSongs.findIndex(function(song) {
|
||||
if (song == null) return false;
|
||||
return song.songId == rememberedSongId;
|
||||
});
|
||||
|
||||
if (curSelected == -1) curSelected = 0;
|
||||
}
|
||||
|
||||
if (rememberedDifficulty != null)
|
||||
{
|
||||
currentDifficulty = rememberedDifficulty;
|
||||
}
|
||||
|
||||
// Set the difficulty star count on the right.
|
||||
var daSong:Null<FreeplaySongData> = songs[curSelected];
|
||||
albumRoll.setDifficultyStars(daSong?.songRating ?? 0);
|
||||
}
|
||||
|
||||
function changeSelection(change:Int = 0):Void
|
||||
|
@ -1156,8 +1182,10 @@ class FreeplayState extends MusicBeatSubState
|
|||
{
|
||||
intendedScore = 0;
|
||||
intendedCompletion = 0.0;
|
||||
diffIdsCurrent = diffIdsTotal;
|
||||
rememberedSongId = null;
|
||||
rememberedDifficulty = null;
|
||||
albumRoll.albumId = null;
|
||||
}
|
||||
|
||||
for (index => capsule in grpCapsules.members)
|
||||
|
@ -1195,12 +1223,33 @@ class FreeplayState extends MusicBeatSubState
|
|||
});
|
||||
if (didReplace)
|
||||
{
|
||||
FunkinSound.playMusic('freakyMenu',
|
||||
{
|
||||
startingVolume: 0.0,
|
||||
overrideExisting: true,
|
||||
restartTrack: false
|
||||
});
|
||||
FlxG.sound.music.fadeIn(2, 0, 0.8);
|
||||
}
|
||||
}
|
||||
grpCapsules.members[curSelected].selected = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an instance of `FreeplayState` that is above the `MainMenuState`.
|
||||
* @return The MainMenuState with the FreeplayState as a substate.
|
||||
*/
|
||||
public static function build(?params:FreeplayStateParams, ?stickers:StickerSubState):MusicBeatState
|
||||
{
|
||||
var result = new MainMenuState();
|
||||
result.persistentUpdate = false;
|
||||
result.persistentDraw = true;
|
||||
|
||||
result.openSubState(new FreeplayState(params, stickers));
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1307,7 +1356,7 @@ class FreeplaySongData
|
|||
public var songName(default, null):String = '';
|
||||
public var songCharacter(default, null):String = '';
|
||||
public var songRating(default, null):Int = 0;
|
||||
public var albumId(default, null):String = '';
|
||||
public var albumId(default, null):Null<String> = null;
|
||||
|
||||
public var currentDifficulty(default, set):String = Constants.DEFAULT_DIFFICULTY;
|
||||
public var displayedVariations(default, null):Array<String> = [Constants.DEFAULT_VARIATION];
|
||||
|
@ -1333,7 +1382,7 @@ class FreeplaySongData
|
|||
|
||||
function updateValues(variations:Array<String>):Void
|
||||
{
|
||||
this.songDifficulties = song.listDifficulties(variations);
|
||||
this.songDifficulties = song.listDifficulties(variations, false, false);
|
||||
if (!this.songDifficulties.contains(currentDifficulty)) currentDifficulty = Constants.DEFAULT_DIFFICULTY;
|
||||
|
||||
var songDifficulty:SongDifficulty = song.getDifficulty(currentDifficulty, variations);
|
||||
|
@ -1341,7 +1390,15 @@ class FreeplaySongData
|
|||
this.songName = songDifficulty.songName;
|
||||
this.songCharacter = songDifficulty.characters.opponent;
|
||||
this.songRating = songDifficulty.difficultyRating;
|
||||
this.albumId = songDifficulty.album;
|
||||
if (songDifficulty.album == null)
|
||||
{
|
||||
FlxG.log.warn('No album for: ${songDifficulty.songName}');
|
||||
this.albumId = Constants.DEFAULT_ALBUM_ID;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.albumId = songDifficulty.album;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,12 +81,12 @@ class SongMenuItem extends FlxSpriteGroup
|
|||
|
||||
grayscaleShader = new Grayscale(1);
|
||||
|
||||
diffRatingSprite = new FlxSprite(145, 90).loadGraphic(Paths.image('freeplay/diffRatings/diff00'));
|
||||
diffRatingSprite.shader = grayscaleShader;
|
||||
// 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);
|
||||
// TODO: Readd once ratings are fully implemented
|
||||
// add(diffRatingSprite);
|
||||
diffRatingSprite.origin.set(capsule.origin.x - diffRatingSprite.x, capsule.origin.y - diffRatingSprite.y);
|
||||
grpHide.add(diffRatingSprite);
|
||||
// grpHide.add(diffRatingSprite);
|
||||
|
||||
songText = new CapsuleText(capsule.width * 0.26, 45, 'Random', Std.int(40 * realScaled));
|
||||
add(songText);
|
||||
|
@ -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
|
||||
|
|
53
source/funkin/ui/haxeui/FlxGamepadActionInputSource.hx
Normal file
53
source/funkin/ui/haxeui/FlxGamepadActionInputSource.hx
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
@ -51,12 +52,10 @@ class MainMenuState extends MusicBeatState
|
|||
transIn = FlxTransitionableState.defaultTransIn;
|
||||
transOut = FlxTransitionableState.defaultTransOut;
|
||||
|
||||
if (!(FlxG?.sound?.music?.playing ?? false))
|
||||
{
|
||||
playMenuMusic();
|
||||
}
|
||||
playMenuMusic();
|
||||
|
||||
persistentUpdate = persistentDraw = true;
|
||||
persistentUpdate = false;
|
||||
persistentDraw = true;
|
||||
|
||||
var bg:FlxSprite = new FlxSprite(Paths.image('menuBG'));
|
||||
bg.scrollFactor.x = 0;
|
||||
|
@ -69,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));
|
||||
|
@ -77,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
|
||||
|
||||
|
@ -109,14 +107,21 @@ class MainMenuState extends MusicBeatState
|
|||
});
|
||||
|
||||
#if CAN_OPEN_LINKS
|
||||
// In order to prevent popup blockers from triggering,
|
||||
// we need to open the link as an immediate result of a keypress event,
|
||||
// so we can't wait for the flicker animation to complete.
|
||||
var hasPopupBlocker = #if web true #else false #end;
|
||||
createMenuItem('donate', 'mainmenu/donate', selectDonate, hasPopupBlocker);
|
||||
createMenuItem('merch', 'mainmenu/merch', selectMerch, hasPopupBlocker);
|
||||
#end
|
||||
|
||||
createMenuItem('options', 'mainmenu/options', function() {
|
||||
startExitState(() -> new funkin.ui.options.OptionsState());
|
||||
});
|
||||
|
||||
createMenuItem('credits', 'mainmenu/credits', function() {
|
||||
startExitState(() -> new funkin.ui.credits.CreditsState());
|
||||
});
|
||||
|
||||
// Reset position of menu items.
|
||||
var spacing = 160;
|
||||
var top = (FlxG.height - (spacing * (menuItems.length - 1))) / 2;
|
||||
|
@ -125,6 +130,9 @@ class MainMenuState extends MusicBeatState
|
|||
var menuItem = menuItems.members[i];
|
||||
menuItem.x = FlxG.width / 2;
|
||||
menuItem.y = top + spacing * i;
|
||||
menuItem.scrollFactor.x = 0.0;
|
||||
// This one affects how much the menu items move when you scroll between them.
|
||||
menuItem.scrollFactor.y = 0.4;
|
||||
}
|
||||
|
||||
resetCamStuff();
|
||||
|
@ -164,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
|
||||
|
@ -212,6 +221,11 @@ class MainMenuState extends MusicBeatState
|
|||
{
|
||||
WindowUtil.openURL(Constants.URL_ITCH);
|
||||
}
|
||||
|
||||
function selectMerch()
|
||||
{
|
||||
WindowUtil.openURL(Constants.URL_MERCH);
|
||||
}
|
||||
#end
|
||||
|
||||
#if newgrounds
|
||||
|
@ -311,8 +325,6 @@ class MainMenuState extends MusicBeatState
|
|||
// Open the debug menu, defaults to ` / ~
|
||||
if (controls.DEBUG_MENU)
|
||||
{
|
||||
this.persistentUpdate = false;
|
||||
this.persistentDraw = false;
|
||||
FlxG.state.openSubState(new DebugMenuSubState());
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -23,8 +23,7 @@ class OptionsState extends MusicBeatState
|
|||
|
||||
override function create()
|
||||
{
|
||||
var menuBG = new FlxSprite().loadGraphic(Paths.image('menuDesat'));
|
||||
menuBG.color = 0xFFea71fd;
|
||||
var menuBG = new FlxSprite().loadGraphic(Paths.image('menuBGBlue'));
|
||||
menuBG.setGraphicSize(Std.int(menuBG.width * 1.1));
|
||||
menuBG.updateHitbox();
|
||||
menuBG.screenCenter();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -169,7 +169,7 @@ class Level implements IRegistryEntry<LevelData>
|
|||
if (firstSong != null)
|
||||
{
|
||||
// Don't display alternate characters in Story Mode. Only show `default` and `erect` variations.
|
||||
for (difficulty in firstSong.listDifficulties([Constants.DEFAULT_VARIATION, 'erect']))
|
||||
for (difficulty in firstSong.listDifficulties([Constants.DEFAULT_VARIATION, 'erect'], false, false))
|
||||
{
|
||||
difficulties.push(difficulty);
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ import funkin.ui.MusicBeatState;
|
|||
*/
|
||||
class AttractState extends MusicBeatState
|
||||
{
|
||||
static final ATTRACT_VIDEO_PATH:String = Paths.videos('kickstarterTrailer');
|
||||
static final ATTRACT_VIDEO_PATH:String = Paths.stripLibrary(Paths.videos('kickstarterTrailer', 'shared'));
|
||||
|
||||
public override function create():Void
|
||||
{
|
||||
|
@ -29,10 +29,12 @@ class AttractState extends MusicBeatState
|
|||
}
|
||||
|
||||
#if html5
|
||||
trace('Playing web video ${ATTRACT_VIDEO_PATH}');
|
||||
playVideoHTML5(ATTRACT_VIDEO_PATH);
|
||||
#end
|
||||
|
||||
#if hxCodec
|
||||
trace('Playing native video ${ATTRACT_VIDEO_PATH}');
|
||||
playVideoNative(ATTRACT_VIDEO_PATH);
|
||||
#end
|
||||
}
|
||||
|
|
|
@ -220,7 +220,7 @@ class TitleState extends MusicBeatState
|
|||
|
||||
function playMenuMusic():Void
|
||||
{
|
||||
var shouldFadeIn = (FlxG.sound.music == null);
|
||||
var shouldFadeIn:Bool = (FlxG.sound.music == null);
|
||||
// Load music. Includes logic to handle BPM changes.
|
||||
FunkinSound.playMusic('freakyMenu',
|
||||
{
|
||||
|
@ -229,7 +229,7 @@ class TitleState extends MusicBeatState
|
|||
restartTrack: true
|
||||
});
|
||||
// Fade from 0.0 to 0.7 over 4 seconds
|
||||
if (shouldFadeIn) FlxG.sound.music.fadeIn(4.0, 0.0, 0.7);
|
||||
if (shouldFadeIn) FlxG.sound.music.fadeIn(4.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
function getIntroTextShit():Array<Array<String>>
|
||||
|
@ -290,18 +290,6 @@ class TitleState extends MusicBeatState
|
|||
// do controls.PAUSE | controls.ACCEPT instead?
|
||||
var pressedEnter:Bool = FlxG.keys.justPressed.ENTER;
|
||||
|
||||
if (FlxG.onMobile)
|
||||
{
|
||||
for (touch in FlxG.touches.list)
|
||||
{
|
||||
if (touch.justPressed)
|
||||
{
|
||||
FlxG.switchState(() -> new FreeplayState());
|
||||
pressedEnter = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var gamepad:FlxGamepad = FlxG.gamepads.lastActive;
|
||||
|
||||
if (gamepad != null)
|
||||
|
|
|
@ -22,10 +22,12 @@ import openfl.filters.ShaderFilter;
|
|||
import openfl.utils.Assets;
|
||||
import flixel.util.typeLimit.NextState;
|
||||
|
||||
class LoadingState extends MusicBeatState
|
||||
class LoadingState extends MusicBeatSubState
|
||||
{
|
||||
inline static var MIN_TIME = 1.0;
|
||||
|
||||
var asSubState:Bool = false;
|
||||
|
||||
var target:NextState;
|
||||
var playParams:Null<PlayStateParams>;
|
||||
var stopMusic:Bool = false;
|
||||
|
@ -178,7 +180,16 @@ class LoadingState extends MusicBeatState
|
|||
FlxG.sound.music = null;
|
||||
}
|
||||
|
||||
FlxG.switchState(target);
|
||||
if (asSubState)
|
||||
{
|
||||
this.close();
|
||||
// We will assume the target is a valid substate.
|
||||
FlxG.state.openSubState(cast target);
|
||||
}
|
||||
else
|
||||
{
|
||||
FlxG.switchState(target);
|
||||
}
|
||||
}
|
||||
|
||||
static function getSongPath():String
|
||||
|
@ -190,17 +201,41 @@ class LoadingState extends MusicBeatState
|
|||
* Starts the transition to a new `PlayState` to start a new song.
|
||||
* First switches to the `LoadingState` if assets need to be loaded.
|
||||
* @param params The parameters for the next `PlayState`.
|
||||
* @param asSubState Whether to open as a substate rather than switching to the `PlayState`.
|
||||
* @param shouldStopMusic Whether to stop the current music while loading.
|
||||
*/
|
||||
public static function loadPlayState(params:PlayStateParams, shouldStopMusic = false):Void
|
||||
public static function loadPlayState(params:PlayStateParams, shouldStopMusic = false, asSubState = false, ?onConstruct:PlayState->Void):Void
|
||||
{
|
||||
Paths.setCurrentLevel(PlayStatePlaylist.campaignId);
|
||||
var playStateCtor:NextState = () -> new PlayState(params);
|
||||
var playStateCtor:() -> PlayState = function() {
|
||||
return new PlayState(params);
|
||||
};
|
||||
|
||||
if (onConstruct != null)
|
||||
{
|
||||
playStateCtor = function() {
|
||||
var result = new PlayState(params);
|
||||
onConstruct(result);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
#if NO_PRELOAD_ALL
|
||||
// Switch to loading state while we load assets (default on HTML5 target).
|
||||
var loadStateCtor:NextState = () -> new LoadingState(playStateCtor, shouldStopMusic, params);
|
||||
FlxG.switchState(loadStateCtor);
|
||||
var loadStateCtor = function() {
|
||||
var result = new LoadingState(playStateCtor, shouldStopMusic, params);
|
||||
@:privateAccess
|
||||
result.asSubState = asSubState;
|
||||
return result;
|
||||
}
|
||||
if (asSubState)
|
||||
{
|
||||
FlxG.state.openSubState(cast loadStateCtor());
|
||||
}
|
||||
else
|
||||
{
|
||||
FlxG.switchState(loadStateCtor);
|
||||
}
|
||||
#else
|
||||
// All assets preloaded, switch directly to play state (defualt on other targets).
|
||||
if (shouldStopMusic && FlxG.sound.music != null)
|
||||
|
@ -210,14 +245,42 @@ class LoadingState extends MusicBeatState
|
|||
}
|
||||
|
||||
// Load and cache the song's charts.
|
||||
if (params?.targetSong != null)
|
||||
// Don't do this if we already provided the music and charts.
|
||||
if (params?.targetSong != null && !params.overrideMusic)
|
||||
{
|
||||
params.targetSong.cacheCharts(true);
|
||||
}
|
||||
|
||||
var shouldPreloadLevelAssets:Bool = !(params?.minimalMode ?? false);
|
||||
|
||||
if (shouldPreloadLevelAssets) preloadLevelAssets();
|
||||
|
||||
if (asSubState)
|
||||
{
|
||||
FlxG.state.openSubState(cast playStateCtor());
|
||||
}
|
||||
else
|
||||
{
|
||||
FlxG.switchState(playStateCtor);
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
#if NO_PRELOAD_ALL
|
||||
static function isSoundLoaded(path:String):Bool
|
||||
{
|
||||
return Assets.cache.hasSound(path);
|
||||
}
|
||||
|
||||
static function isLibraryLoaded(library:String):Bool
|
||||
{
|
||||
return Assets.getLibrary(library) != null;
|
||||
}
|
||||
#else
|
||||
static function preloadLevelAssets():Void
|
||||
{
|
||||
// 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'));
|
||||
|
@ -247,7 +310,10 @@ class LoadingState extends MusicBeatState
|
|||
// List all image assets in the level's library.
|
||||
// This is crude and I want to remove it when we have a proper asset caching system.
|
||||
// TODO: Get rid of this junk!
|
||||
var library = openfl.utils.Assets.getLibrary(PlayStatePlaylist.campaignId);
|
||||
var library = PlayStatePlaylist.campaignId != null ? openfl.utils.Assets.getLibrary(PlayStatePlaylist.campaignId) : null;
|
||||
|
||||
if (library == null) return; // We don't need to do anymore precaching.
|
||||
|
||||
var assets = library.list(lime.utils.AssetType.IMAGE);
|
||||
trace('Got ${assets.length} assets: ${assets}');
|
||||
|
||||
|
@ -278,20 +344,6 @@ class LoadingState extends MusicBeatState
|
|||
// FunkinSprite.cacheAllSongTextures(stage)
|
||||
|
||||
FunkinSprite.purgeCache();
|
||||
|
||||
FlxG.switchState(playStateCtor);
|
||||
#end
|
||||
}
|
||||
|
||||
#if NO_PRELOAD_ALL
|
||||
static function isSoundLoaded(path:String):Bool
|
||||
{
|
||||
return Assets.cache.hasSound(path);
|
||||
}
|
||||
|
||||
static function isLibraryLoaded(library:String):Bool
|
||||
{
|
||||
return Assets.getLibrary(library) != null;
|
||||
}
|
||||
#end
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
package funkin.ui.transition.preload;
|
||||
|
||||
import openfl.filters.GlowFilter;
|
||||
import openfl.display.SpreadMethod;
|
||||
import openfl.display.GradientType;
|
||||
import openfl.geom.Matrix;
|
||||
import openfl.filters.BlurFilter;
|
||||
import openfl.events.MouseEvent;
|
||||
import flash.display.Bitmap;
|
||||
import flash.display.BitmapData;
|
||||
|
@ -46,7 +51,7 @@ class FunkinPreloader extends FlxBasePreloader
|
|||
*/
|
||||
static final BAR_PADDING:Float = 20;
|
||||
|
||||
static final BAR_HEIGHT:Int = 20;
|
||||
static final BAR_HEIGHT:Int = 12;
|
||||
|
||||
/**
|
||||
* Logo takes this long (in seconds) to fade in.
|
||||
|
@ -108,13 +113,22 @@ class FunkinPreloader extends FlxBasePreloader
|
|||
#if TOUCH_HERE_TO_PLAY
|
||||
var touchHereToPlay:Bitmap;
|
||||
#end
|
||||
var progressBarPieces:Array<Sprite>;
|
||||
var progressBar:Bitmap;
|
||||
var progressLeftText:TextField;
|
||||
var progressRightText:TextField;
|
||||
|
||||
var dspText:TextField;
|
||||
var enhancedText:TextField;
|
||||
var stereoText:TextField;
|
||||
|
||||
var vfdShader:VFDOverlay;
|
||||
var box:Sprite;
|
||||
var progressLines:Sprite;
|
||||
|
||||
public function new()
|
||||
{
|
||||
super(Constants.PRELOADER_MIN_STAGE_TIME, Constants.SITE_LOCK);
|
||||
super(Constants.PRELOADER_MIN_STAGE_TIME);
|
||||
|
||||
// We can't even call trace() yet, until Flixel loads.
|
||||
trace('Initializing custom preloader...');
|
||||
|
@ -146,7 +160,7 @@ class FunkinPreloader extends FlxBasePreloader
|
|||
bmp.x = (this._width - bmp.width) / 2;
|
||||
bmp.y = (this._height - bmp.height) / 2;
|
||||
});
|
||||
addChild(logo);
|
||||
// addChild(logo);
|
||||
|
||||
#if TOUCH_HERE_TO_PLAY
|
||||
touchHereToPlay = createBitmap(TouchHereToPlayImage, function(bmp:Bitmap) {
|
||||
|
@ -160,16 +174,48 @@ class FunkinPreloader extends FlxBasePreloader
|
|||
addChild(touchHereToPlay);
|
||||
#end
|
||||
|
||||
var amountOfPieces:Int = 16;
|
||||
progressBarPieces = [];
|
||||
var maxBarWidth = this._width - BAR_PADDING * 2;
|
||||
var pieceWidth = maxBarWidth / amountOfPieces;
|
||||
var pieceGap:Int = 8;
|
||||
|
||||
progressLines = new openfl.display.Sprite();
|
||||
progressLines.graphics.lineStyle(2, Constants.COLOR_PRELOADER_BAR);
|
||||
progressLines.graphics.drawRect(-2, 480, this._width + 4, 30);
|
||||
addChild(progressLines);
|
||||
|
||||
var progressBarPiece = new Sprite();
|
||||
progressBarPiece.graphics.beginFill(Constants.COLOR_PRELOADER_BAR);
|
||||
progressBarPiece.graphics.drawRoundRect(0, 0, pieceWidth - pieceGap, BAR_HEIGHT, 4, 4);
|
||||
progressBarPiece.graphics.endFill();
|
||||
|
||||
for (i in 0...amountOfPieces)
|
||||
{
|
||||
var piece = new Sprite();
|
||||
piece.graphics.beginFill(Constants.COLOR_PRELOADER_BAR);
|
||||
piece.graphics.drawRoundRect(0, 0, pieceWidth - pieceGap, BAR_HEIGHT, 4, 4);
|
||||
piece.graphics.endFill();
|
||||
|
||||
piece.x = i * (piece.width + pieceGap);
|
||||
piece.y = this._height - BAR_PADDING - BAR_HEIGHT - 200;
|
||||
addChild(piece);
|
||||
progressBarPieces.push(piece);
|
||||
}
|
||||
|
||||
// Create the progress bar.
|
||||
progressBar = new Bitmap(new BitmapData(1, BAR_HEIGHT, true, Constants.COLOR_PRELOADER_BAR));
|
||||
progressBar.x = BAR_PADDING;
|
||||
progressBar.y = this._height - BAR_PADDING - BAR_HEIGHT;
|
||||
addChild(progressBar);
|
||||
// progressBar = new Bitmap(new BitmapData(1, BAR_HEIGHT, true, Constants.COLOR_PRELOADER_BAR));
|
||||
// progressBar.x = BAR_PADDING;
|
||||
// progressBar.y = this._height - BAR_PADDING - BAR_HEIGHT;
|
||||
// addChild(progressBar);
|
||||
|
||||
// Create the progress message.
|
||||
progressLeftText = new TextField();
|
||||
dspText = new TextField();
|
||||
enhancedText = new TextField();
|
||||
stereoText = new TextField();
|
||||
|
||||
var progressLeftTextFormat = new TextFormat("VCR OSD Mono", 16, Constants.COLOR_PRELOADER_BAR, true);
|
||||
var progressLeftTextFormat = new TextFormat("DS-Digital", 32, Constants.COLOR_PRELOADER_BAR, true);
|
||||
progressLeftTextFormat.align = TextFormatAlign.LEFT;
|
||||
progressLeftText.defaultTextFormat = progressLeftTextFormat;
|
||||
|
||||
|
@ -177,13 +223,14 @@ class FunkinPreloader extends FlxBasePreloader
|
|||
progressLeftText.width = this._width - BAR_PADDING * 2;
|
||||
progressLeftText.text = 'Downloading assets...';
|
||||
progressLeftText.x = BAR_PADDING;
|
||||
progressLeftText.y = this._height - BAR_PADDING - BAR_HEIGHT - 16 - 4;
|
||||
progressLeftText.y = this._height - BAR_PADDING - BAR_HEIGHT - 290;
|
||||
// progressLeftText.shader = new VFDOverlay();
|
||||
addChild(progressLeftText);
|
||||
|
||||
// Create the progress %.
|
||||
progressRightText = new TextField();
|
||||
|
||||
var progressRightTextFormat = new TextFormat("VCR OSD Mono", 16, Constants.COLOR_PRELOADER_BAR, true);
|
||||
var progressRightTextFormat = new TextFormat("DS-Digital", 16, Constants.COLOR_PRELOADER_BAR, true);
|
||||
progressRightTextFormat.align = TextFormatAlign.RIGHT;
|
||||
progressRightText.defaultTextFormat = progressRightTextFormat;
|
||||
|
||||
|
@ -193,6 +240,60 @@ class FunkinPreloader extends FlxBasePreloader
|
|||
progressRightText.x = BAR_PADDING;
|
||||
progressRightText.y = this._height - BAR_PADDING - BAR_HEIGHT - 16 - 4;
|
||||
addChild(progressRightText);
|
||||
|
||||
box = new Sprite();
|
||||
box.graphics.beginFill(Constants.COLOR_PRELOADER_BAR, 1);
|
||||
box.graphics.drawRoundRect(0, 0, 64, 20, 5, 5);
|
||||
box.graphics.drawRoundRect(70, 0, 58, 20, 5, 5);
|
||||
box.graphics.endFill();
|
||||
box.graphics.beginFill(Constants.COLOR_PRELOADER_BAR, 0.1);
|
||||
box.graphics.drawRoundRect(0, 0, 128, 20, 5, 5);
|
||||
box.graphics.endFill();
|
||||
box.x = 880;
|
||||
box.y = 440;
|
||||
addChild(box);
|
||||
|
||||
dspText.selectable = false;
|
||||
dspText.textColor = 0x000000;
|
||||
dspText.width = this._width;
|
||||
dspText.height = 20;
|
||||
dspText.text = 'DSP';
|
||||
dspText.x = 10;
|
||||
dspText.y = -5;
|
||||
box.addChild(dspText);
|
||||
|
||||
enhancedText.selectable = false;
|
||||
enhancedText.textColor = Constants.COLOR_PRELOADER_BAR;
|
||||
enhancedText.width = this._width;
|
||||
enhancedText.height = 100;
|
||||
enhancedText.text = 'ENHANCED';
|
||||
enhancedText.x = -100;
|
||||
enhancedText.y = 0;
|
||||
box.addChild(enhancedText);
|
||||
|
||||
stereoText.selectable = false;
|
||||
stereoText.textColor = Constants.COLOR_PRELOADER_BAR;
|
||||
stereoText.width = this._width;
|
||||
stereoText.height = 100;
|
||||
stereoText.text = 'STEREO';
|
||||
stereoText.x = 0;
|
||||
stereoText.y = -40;
|
||||
box.addChild(stereoText);
|
||||
|
||||
// var dummyMatrix:openfl.geom.Matrix = new Matrix();
|
||||
// dummyMatrix.createGradientBox(this._width, this._height * 0.1, 90 * Math.PI / 180);
|
||||
|
||||
// var gradient:Sprite = new Sprite();
|
||||
// gradient.graphics.beginGradientFill(GradientType.LINEAR, [0xFFFFFF, 0x000000], [1, 1], [0, 255], dummyMatrix, SpreadMethod.REFLECT);
|
||||
// gradient.graphics.drawRect(0, 0, this._width, this._height);
|
||||
// gradient.graphics.endFill();
|
||||
// addChild(gradient);
|
||||
|
||||
var vfdBitmap:Bitmap = new Bitmap(new BitmapData(this._width, this._height, true, 0xFFFFFFFF));
|
||||
addChild(vfdBitmap);
|
||||
|
||||
vfdShader = new VFDOverlay();
|
||||
vfdBitmap.shader = vfdShader;
|
||||
}
|
||||
|
||||
var lastElapsed:Float = 0.0;
|
||||
|
@ -200,6 +301,8 @@ class FunkinPreloader extends FlxBasePreloader
|
|||
override function update(percent:Float):Void
|
||||
{
|
||||
var elapsed:Float = (Date.now().getTime() - this._startTime) / 1000.0;
|
||||
|
||||
vfdShader.update(elapsed * 100);
|
||||
// trace('Time since last frame: ' + (lastElapsed - elapsed));
|
||||
|
||||
downloadingAssetsPercent = percent;
|
||||
|
@ -748,12 +851,19 @@ class FunkinPreloader extends FlxBasePreloader
|
|||
else
|
||||
{
|
||||
renderLogoFadeIn(elapsed);
|
||||
|
||||
// Render progress bar
|
||||
var maxWidth = this._width - BAR_PADDING * 2;
|
||||
var barWidth = maxWidth * percent;
|
||||
var piecesToRender:Int = Std.int(percent * progressBarPieces.length);
|
||||
|
||||
for (i => piece in progressBarPieces)
|
||||
{
|
||||
piece.alpha = i <= piecesToRender ? 0.9 : 0.1;
|
||||
}
|
||||
}
|
||||
|
||||
// Render progress bar
|
||||
var maxWidth = this._width - BAR_PADDING * 2;
|
||||
var barWidth = maxWidth * percent;
|
||||
progressBar.width = barWidth;
|
||||
// progressBar.width = barWidth;
|
||||
|
||||
// Cycle ellipsis count to show loading
|
||||
var ellipsisCount:Int = Std.int(elapsed / ELLIPSIS_TIME) % 3 + 1;
|
||||
|
@ -766,29 +876,29 @@ class FunkinPreloader extends FlxBasePreloader
|
|||
{
|
||||
// case FunkinPreloaderState.NotStarted:
|
||||
default:
|
||||
updateProgressLeftText('Loading (0/$TOTAL_STEPS)$ellipsis');
|
||||
updateProgressLeftText('Loading \n0/$TOTAL_STEPS $ellipsis');
|
||||
case FunkinPreloaderState.DownloadingAssets:
|
||||
updateProgressLeftText('Downloading assets (1/$TOTAL_STEPS)$ellipsis');
|
||||
updateProgressLeftText('Downloading assets \n1/$TOTAL_STEPS $ellipsis');
|
||||
case FunkinPreloaderState.PreloadingPlayAssets:
|
||||
updateProgressLeftText('Preloading assets (2/$TOTAL_STEPS)$ellipsis');
|
||||
updateProgressLeftText('Preloading assets \n2/$TOTAL_STEPS $ellipsis');
|
||||
case FunkinPreloaderState.InitializingScripts:
|
||||
updateProgressLeftText('Initializing scripts (3/$TOTAL_STEPS)$ellipsis');
|
||||
updateProgressLeftText('Initializing scripts \n3/$TOTAL_STEPS $ellipsis');
|
||||
case FunkinPreloaderState.CachingGraphics:
|
||||
updateProgressLeftText('Caching graphics (4/$TOTAL_STEPS)$ellipsis');
|
||||
updateProgressLeftText('Caching graphics \n4/$TOTAL_STEPS $ellipsis');
|
||||
case FunkinPreloaderState.CachingAudio:
|
||||
updateProgressLeftText('Caching audio (5/$TOTAL_STEPS)$ellipsis');
|
||||
updateProgressLeftText('Caching audio \n5/$TOTAL_STEPS $ellipsis');
|
||||
case FunkinPreloaderState.CachingData:
|
||||
updateProgressLeftText('Caching data (6/$TOTAL_STEPS)$ellipsis');
|
||||
updateProgressLeftText('Caching data \n6/$TOTAL_STEPS $ellipsis');
|
||||
case FunkinPreloaderState.ParsingSpritesheets:
|
||||
updateProgressLeftText('Parsing spritesheets (7/$TOTAL_STEPS)$ellipsis');
|
||||
updateProgressLeftText('Parsing spritesheets \n7/$TOTAL_STEPS $ellipsis');
|
||||
case FunkinPreloaderState.ParsingStages:
|
||||
updateProgressLeftText('Parsing stages (8/$TOTAL_STEPS)$ellipsis');
|
||||
updateProgressLeftText('Parsing stages \n8/$TOTAL_STEPS $ellipsis');
|
||||
case FunkinPreloaderState.ParsingCharacters:
|
||||
updateProgressLeftText('Parsing characters (9/$TOTAL_STEPS)$ellipsis');
|
||||
updateProgressLeftText('Parsing characters \n9/$TOTAL_STEPS $ellipsis');
|
||||
case FunkinPreloaderState.ParsingSongs:
|
||||
updateProgressLeftText('Parsing songs (10/$TOTAL_STEPS)$ellipsis');
|
||||
updateProgressLeftText('Parsing songs \n10/$TOTAL_STEPS $ellipsis');
|
||||
case FunkinPreloaderState.Complete:
|
||||
updateProgressLeftText('Finishing up ($TOTAL_STEPS/$TOTAL_STEPS)$ellipsis');
|
||||
updateProgressLeftText('Finishing up \n$TOTAL_STEPS/$TOTAL_STEPS $ellipsis');
|
||||
#if TOUCH_HERE_TO_PLAY
|
||||
case FunkinPreloaderState.TouchHereToPlay:
|
||||
updateProgressLeftText(null);
|
||||
|
@ -815,10 +925,21 @@ class FunkinPreloader extends FlxBasePreloader
|
|||
else if (progressLeftText.text != text)
|
||||
{
|
||||
// We have to keep updating the text format, because the font can take a frame or two to load.
|
||||
var progressLeftTextFormat = new TextFormat("VCR OSD Mono", 16, Constants.COLOR_PRELOADER_BAR, true);
|
||||
var progressLeftTextFormat = new TextFormat("DS-Digital", 32, Constants.COLOR_PRELOADER_BAR, true);
|
||||
progressLeftTextFormat.align = TextFormatAlign.LEFT;
|
||||
progressLeftText.defaultTextFormat = progressLeftTextFormat;
|
||||
progressLeftText.text = text;
|
||||
|
||||
dspText.defaultTextFormat = new TextFormat("Quantico", 20, 0x000000, false);
|
||||
dspText.text = 'DSP\t\t\t\t\tFNF'; // fukin dum....
|
||||
dspText.textColor = 0x000000;
|
||||
|
||||
enhancedText.defaultTextFormat = new TextFormat("Inconsolata Black", 16, Constants.COLOR_PRELOADER_BAR, false);
|
||||
enhancedText.text = 'ENHANCED';
|
||||
enhancedText.textColor = Constants.COLOR_PRELOADER_BAR;
|
||||
|
||||
stereoText.defaultTextFormat = new TextFormat("Inconsolata Bold", 36, Constants.COLOR_PRELOADER_BAR, false);
|
||||
stereoText.text = 'NATURAL STEREO';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -845,9 +966,17 @@ class FunkinPreloader extends FlxBasePreloader
|
|||
logo.y = (this._height - logo.height) / 2;
|
||||
|
||||
// Fade out progress bar too.
|
||||
progressBar.alpha = logo.alpha;
|
||||
// progressBar.alpha = logo.alpha;
|
||||
progressLeftText.alpha = logo.alpha;
|
||||
progressRightText.alpha = logo.alpha;
|
||||
box.alpha = logo.alpha;
|
||||
dspText.alpha = logo.alpha;
|
||||
enhancedText.alpha = logo.alpha;
|
||||
stereoText.alpha = logo.alpha;
|
||||
progressLines.alpha = logo.alpha;
|
||||
|
||||
for (piece in progressBarPieces)
|
||||
piece.alpha = logo.alpha;
|
||||
|
||||
return elapsedFinished;
|
||||
}
|
||||
|
@ -901,8 +1030,8 @@ class FunkinPreloader extends FlxBasePreloader
|
|||
{
|
||||
// Ensure the graphics are properly destroyed and GC'd.
|
||||
removeChild(logo);
|
||||
removeChild(progressBar);
|
||||
logo = progressBar = null;
|
||||
// removeChild(progressBar);
|
||||
logo = null;
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
|
|
70
source/funkin/ui/transition/preload/VFDOverlay.hx
Normal file
70
source/funkin/ui/transition/preload/VFDOverlay.hx
Normal file
|
@ -0,0 +1,70 @@
|
|||
package funkin.ui.transition.preload;
|
||||
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import openfl.display.GraphicsShader;
|
||||
|
||||
class VFDOverlay extends GraphicsShader
|
||||
{
|
||||
public var elapsedTime(default, set):Float = 0;
|
||||
|
||||
function set_elapsedTime(value:Float):Float
|
||||
{
|
||||
u_time.value = [value];
|
||||
return value;
|
||||
}
|
||||
|
||||
@:glFragmentSource('#pragma header
|
||||
const vec2 s = vec2(1, 1.7320508);
|
||||
|
||||
uniform float u_time;
|
||||
|
||||
float rand(float co) { return fract(sin(co*(91.3458)) * 47453.5453); }
|
||||
float rand(vec2 co){ return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); }
|
||||
|
||||
void main(void) {
|
||||
vec4 col = texture2D (bitmap, openfl_TextureCoordv);
|
||||
vec2 game_res = vec2(1280.0, 720.0);
|
||||
const float tileAmount = 10.;
|
||||
|
||||
vec2 uv = (2. * openfl_TextureCoordv.xy * -1.);
|
||||
uv *= 50.;
|
||||
|
||||
vec4 hexCenter = floor(vec4(uv, uv - vec2(0.5, 1.0)) / s.xyxy) + 0.5;
|
||||
vec4 offset = vec4(uv - hexCenter.xy * s, uv - (hexCenter.zw + 0.5) * s) + 0.0;
|
||||
vec4 hexInfo = dot(offset.xy, offset.xy) < dot(offset.zw, offset.zw) ? vec4(offset.xy, hexCenter.xy) : vec4(offset.zw, hexCenter.zw);
|
||||
|
||||
// Distance to the nearest edge of a hexagon
|
||||
vec2 p = abs(hexInfo.xy) ;
|
||||
float edgeDist = max(dot(p, normalize(vec2(1.0, sqrt(3.0)))), p.x);
|
||||
float edgeWidth = 0.05 * tileAmount; // Adjust edge width based on tile amount
|
||||
float edgeSharpness = 0.011 * tileAmount;
|
||||
|
||||
float outline = smoothstep(edgeWidth - edgeSharpness, edgeWidth, edgeDist);
|
||||
float color_mix = mix(0.0, 0.3, outline); // Mix black outline with white fill
|
||||
|
||||
float flicker = (sin(u_time) * 0.05) + 1.0;
|
||||
float sinshit = smoothstep(-3.0, 1.0, sin(uv.y * 3.));
|
||||
|
||||
col = vec4(vec3(0.0), color_mix);
|
||||
col = mix(col, vec4(0., 0., 0., sinshit), 0.5 * flicker);
|
||||
|
||||
float specs = rand(uv.xy);
|
||||
vec4 noise = vec4(0., 0., 0., specs);
|
||||
col = mix(col, noise, 0.1);
|
||||
|
||||
gl_FragColor = col;
|
||||
}
|
||||
')
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
|
||||
this.elapsedTime = 0;
|
||||
}
|
||||
|
||||
public function update(elapsed:Float):Void
|
||||
{
|
||||
this.elapsedTime += elapsed;
|
||||
}
|
||||
}
|
|
@ -60,6 +60,11 @@ class Constants
|
|||
*/
|
||||
// ==============================
|
||||
|
||||
/**
|
||||
* Link to buy merch for the game.
|
||||
*/
|
||||
public static final URL_MERCH:String = 'https://needlejuicerecords.com/pages/friday-night-funkin';
|
||||
|
||||
/**
|
||||
* Preloader sitelock.
|
||||
* Matching is done by `FlxStringUtil.getDomain`, so any URL on the domain will work.
|
||||
|
@ -135,7 +140,7 @@ class Constants
|
|||
/**
|
||||
* Color for the preloader progress bar
|
||||
*/
|
||||
public static final COLOR_PRELOADER_BAR:FlxColor = 0xFF00FF00;
|
||||
public static final COLOR_PRELOADER_BAR:FlxColor = 0xFFA4FF11;
|
||||
|
||||
/**
|
||||
* Color for the preloader site lock background
|
||||
|
@ -181,6 +186,12 @@ class Constants
|
|||
*/
|
||||
public static final DEFAULT_DIFFICULTY_LIST:Array<String> = ['easy', 'normal', 'hard'];
|
||||
|
||||
/**
|
||||
* List of all difficulties used by the base game.
|
||||
* Includes Erect and Nightmare.
|
||||
*/
|
||||
public static final DEFAULT_DIFFICULTY_LIST_FULL:Array<String> = ['easy', 'normal', 'hard', 'erect', 'nightmare'];
|
||||
|
||||
/**
|
||||
* Default player character for charts.
|
||||
*/
|
||||
|
@ -347,7 +358,7 @@ class Constants
|
|||
* The progress bare is automatically rescaled to match.
|
||||
*/
|
||||
#if debug
|
||||
public static final PRELOADER_MIN_STAGE_TIME:Float = 1.0;
|
||||
public static final PRELOADER_MIN_STAGE_TIME:Float = 0.0;
|
||||
#else
|
||||
public static final PRELOADER_MIN_STAGE_TIME:Float = 0.1;
|
||||
#end
|
||||
|
@ -515,4 +526,10 @@ class Constants
|
|||
* The vertical offset of the strumline from the top edge of the screen.
|
||||
*/
|
||||
public static final STRUMLINE_Y_OFFSET:Float = 24;
|
||||
|
||||
/**
|
||||
* The rate at which the camera lerps to its target.
|
||||
* 0.04 = 4% of distance per frame.
|
||||
*/
|
||||
public static final DEFAULT_CAMERA_FOLLOW_RATE:Float = 0.04;
|
||||
}
|
||||
|
|
17
source/funkin/util/EaseUtil.hx
Normal file
17
source/funkin/util/EaseUtil.hx
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -63,6 +63,31 @@ class SerializerUtil
|
|||
}
|
||||
}
|
||||
|
||||
public static function initSerializer():Void
|
||||
{
|
||||
haxe.Unserializer.DEFAULT_RESOLVER = new FunkinTypeResolver();
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a Haxe object using the built-in Serializer.
|
||||
* @param input The object to serialize
|
||||
* @return The serialized object as a string
|
||||
*/
|
||||
public static function fromHaxeObject(input:Dynamic):String
|
||||
{
|
||||
return haxe.Serializer.run(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a serialized Haxe object back into a Haxe object.
|
||||
* @param input The serialized object as a string
|
||||
* @return The deserialized object
|
||||
*/
|
||||
public static function toHaxeObject(input:String):Dynamic
|
||||
{
|
||||
return haxe.Unserializer.run(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Customize how certain types are serialized when converting to JSON.
|
||||
*/
|
||||
|
@ -90,3 +115,26 @@ class SerializerUtil
|
|||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class FunkinTypeResolver
|
||||
{
|
||||
public function new()
|
||||
{
|
||||
// Blank constructor.
|
||||
}
|
||||
|
||||
public function resolveClass(name:String):Class<Dynamic>
|
||||
{
|
||||
if (name == 'Dynamic')
|
||||
{
|
||||
FlxG.log.warn('Found invalid class type in save data, indicates partial save corruption.');
|
||||
return null;
|
||||
}
|
||||
return Type.resolveClass(name);
|
||||
};
|
||||
|
||||
public function resolveEnum(name:String):Enum<Dynamic>
|
||||
{
|
||||
return Type.resolveEnum(name);
|
||||
};
|
||||
}
|
||||
|
|
136
source/funkin/util/StructureUtil.hx
Normal file
136
source/funkin/util/StructureUtil.hx
Normal file
|
@ -0,0 +1,136 @@
|
|||
package funkin.util;
|
||||
|
||||
import funkin.util.tools.MapTools;
|
||||
import haxe.DynamicAccess;
|
||||
|
||||
/**
|
||||
* Utilities for working with anonymous structures.
|
||||
*/
|
||||
class StructureUtil
|
||||
{
|
||||
/**
|
||||
* Merge two structures, with the second overwriting the first.
|
||||
* Performs a SHALLOW clone, where child structures are not merged.
|
||||
* @param a The base structure.
|
||||
* @param b The new structure.
|
||||
* @return The merged structure.
|
||||
*/
|
||||
public static function merge(a:Dynamic, b:Dynamic):Dynamic
|
||||
{
|
||||
var result:DynamicAccess<Dynamic> = Reflect.copy(a);
|
||||
|
||||
for (field in Reflect.fields(b))
|
||||
{
|
||||
result.set(field, Reflect.field(b, field));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static function toMap(a:Dynamic):haxe.ds.Map<String, Dynamic>
|
||||
{
|
||||
var result:haxe.ds.Map<String, Dynamic> = [];
|
||||
|
||||
for (field in Reflect.fields(a))
|
||||
{
|
||||
result.set(field, Reflect.field(a, field));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static function isMap(a:Dynamic):Bool
|
||||
{
|
||||
return Std.isOfType(a, haxe.Constraints.IMap);
|
||||
}
|
||||
|
||||
public static function isObject(a:Dynamic):Bool
|
||||
{
|
||||
switch (Type.typeof(a))
|
||||
{
|
||||
case TObject:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function isPrimitive(a:Dynamic):Bool
|
||||
{
|
||||
switch (Type.typeof(a))
|
||||
{
|
||||
case TInt | TFloat | TBool:
|
||||
return true;
|
||||
case TClass(c):
|
||||
return false;
|
||||
case TEnum(e):
|
||||
return false;
|
||||
case TObject:
|
||||
return false;
|
||||
case TFunction:
|
||||
return false;
|
||||
case TNull:
|
||||
return true;
|
||||
case TUnknown:
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge two structures, with the second overwriting the first.
|
||||
* Performs a DEEP clone, where child structures are also merged recursively.
|
||||
* @param a The base structure.
|
||||
* @param b The new structure.
|
||||
* @return The merged structure.
|
||||
*/
|
||||
public static function deepMerge(a:Dynamic, b:Dynamic):Dynamic
|
||||
{
|
||||
if (a == null) return b;
|
||||
if (b == null) return null;
|
||||
if (isPrimitive(a) && isPrimitive(b)) return b;
|
||||
if (isMap(b))
|
||||
{
|
||||
if (isMap(a))
|
||||
{
|
||||
return MapTools.merge(a, b);
|
||||
}
|
||||
else
|
||||
{
|
||||
return StructureUtil.toMap(a).merge(b);
|
||||
}
|
||||
}
|
||||
if (!Reflect.isObject(a) || !Reflect.isObject(b)) return b;
|
||||
if (Std.isOfType(b, haxe.ds.StringMap))
|
||||
{
|
||||
if (Std.isOfType(a, haxe.ds.StringMap))
|
||||
{
|
||||
return MapTools.merge(a, b);
|
||||
}
|
||||
else
|
||||
{
|
||||
return StructureUtil.toMap(a).merge(b);
|
||||
}
|
||||
}
|
||||
|
||||
var result:DynamicAccess<Dynamic> = Reflect.copy(a);
|
||||
|
||||
for (field in Reflect.fields(b))
|
||||
{
|
||||
if (Reflect.isObject(b))
|
||||
{
|
||||
// Note that isObject also returns true for class instances,
|
||||
// but we just assume that's not a problem here.
|
||||
result.set(field, deepMerge(Reflect.field(result, field), Reflect.field(b, field)));
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we're here, b[field] is a primitive.
|
||||
result.set(field, Reflect.field(b, field));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -33,6 +33,24 @@ class MapTools
|
|||
return map.copy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new map which is a combination of the two given maps.
|
||||
* @param a The base map.
|
||||
* @param b The other map. The values from this take precedence.
|
||||
* @return The combined map.
|
||||
*/
|
||||
public static function merge<K, T>(a:Map<K, T>, b:Map<K, T>):Map<K, T>
|
||||
{
|
||||
var result = a.copy();
|
||||
|
||||
for (pair in b.keyValueIterator())
|
||||
{
|
||||
result.set(pair.key, pair.value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new array with clones of all elements of the given array, to prevent modifying the original.
|
||||
*/
|
||||
|
|
Loading…
Reference in a new issue