mirror of
https://github.com/FunkinCrew/Funkin.git
synced 2024-11-27 01:55:52 -05:00
Merge branch 'rewrite/master' into bugfix/draw-crash
This commit is contained in:
commit
035db7f00a
41 changed files with 1900 additions and 564 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
|
|
126
.github/actions/upload-itch/action.yml
vendored
126
.github/actions/upload-itch/action.yml
vendored
|
@ -1,44 +1,124 @@
|
||||||
name: upload-itch
|
name: upload-itch
|
||||||
description: "installs Butler, and uploads to itch.io!"
|
description: "installs Butler, and uploads to itch.io!"
|
||||||
|
|
||||||
inputs:
|
inputs:
|
||||||
butler-key:
|
butler-key:
|
||||||
description: "Butler API secret key"
|
description: "Butler API secret key"
|
||||||
required: true
|
required: true
|
||||||
|
itch-repo:
|
||||||
|
description: "Where to upload the game to"
|
||||||
|
required: true
|
||||||
|
default: "ninja-muffin24/funkin-secret"
|
||||||
build-dir:
|
build-dir:
|
||||||
description: "Directory of the game build"
|
description: "Directory of the game build"
|
||||||
required: true
|
required: false
|
||||||
target:
|
target:
|
||||||
description: "Target (html5, win, linux, mac)"
|
description: "Target (html5, windows, linux, macos)"
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: "composite"
|
using: "composite"
|
||||||
steps:
|
steps:
|
||||||
- name: Install butler Windows
|
|
||||||
if: runner.os == 'Windows'
|
# RUNNER_OS = Windows | macOS | Linux
|
||||||
run: |
|
# TARGET_BUILD = windows | macos | linux
|
||||||
curl -L -o butler.zip https://broth.itch.ovh/butler/windows-amd64/LATEST/archive/default
|
# TARGET_ITCH = win | macos | linux
|
||||||
7z x butler.zip
|
# TARGET_BUTLER_DOWNLOAD = windows-amd64 | darwin-amd64 | linux-amd64
|
||||||
./butler -v
|
- name: Setup variables
|
||||||
shell: bash
|
shell: bash
|
||||||
- name: Install butler Mac
|
|
||||||
if: runner.os == 'macOS'
|
|
||||||
run: |
|
run: |
|
||||||
curl -L -o butler.zip https://broth.itch.ovh/butler/darwin-amd64/LATEST/archive/default
|
TARGET_OS=${{ inputs.target }}
|
||||||
unzip butler.zip
|
RUNNER=${RUNNER_OS@L}
|
||||||
./butler -V
|
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
|
shell: bash
|
||||||
- name: Install butler Linux
|
|
||||||
if: runner.os == 'Linux'
|
|
||||||
run: |
|
run: |
|
||||||
curl -L -o butler.zip https://broth.itch.ovh/butler/linux-amd64/LATEST/archive/default
|
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
|
unzip butler.zip
|
||||||
chmod +x butler
|
chmod +x butler
|
||||||
./butler -V
|
|
||||||
shell: bash
|
|
||||||
- name: Upload game to itch.io
|
- 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
|
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 }}
|
136
.github/workflows/build-shit.yml
vendored
136
.github/workflows/build-shit.yml
vendored
|
@ -1,136 +0,0 @@
|
||||||
name: build-upload
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
create-nightly-html5:
|
|
||||||
runs-on: [self-hosted, linux]
|
|
||||||
container: ubuntu:23.10
|
|
||||||
steps:
|
|
||||||
- name: Install tools missing in container
|
|
||||||
run: |
|
|
||||||
apt update
|
|
||||||
apt install -y sudo git curl unzip
|
|
||||||
- name: Fix git config on posix runner
|
|
||||||
# this can't be {{ github.workspace }} because that's not docker-aware
|
|
||||||
run: |
|
|
||||||
git config --global --add safe.directory $GITHUB_WORKSPACE
|
|
||||||
- name: Get checkout token
|
|
||||||
uses: actions/create-github-app-token@v1
|
|
||||||
id: app_token
|
|
||||||
with:
|
|
||||||
app-id: ${{ vars.APP_ID }}
|
|
||||||
private-key: ${{ secrets.APP_PEM }}
|
|
||||||
owner: ${{ github.repository_owner }}
|
|
||||||
- name: Checkout repo
|
|
||||||
uses: funkincrew/ci-checkout@v6
|
|
||||||
with:
|
|
||||||
submodules: 'recursive'
|
|
||||||
token: ${{ steps.app_token.outputs.token }}
|
|
||||||
- name: Install Haxe, dependencies
|
|
||||||
uses: ./.github/actions/setup-haxeshit
|
|
||||||
- name: Install native dependencies
|
|
||||||
run: |
|
|
||||||
apt install -y \
|
|
||||||
libx11-dev libxi-dev libxext-dev libxinerama-dev libxrandr-dev \
|
|
||||||
libgl-dev libgl1-mesa-dev \
|
|
||||||
libasound2-dev
|
|
||||||
- name: Build game
|
|
||||||
run: |
|
|
||||||
haxelib run lime build html5 -release --times -DGITHUB_BUILD
|
|
||||||
- name: Upload build artifacts
|
|
||||||
uses: ./.github/actions/upload-itch
|
|
||||||
with:
|
|
||||||
butler-key: ${{ secrets.BUTLER_API_KEY}}
|
|
||||||
build-dir: export/release/html5/bin
|
|
||||||
target: html5
|
|
||||||
create-nightly-win:
|
|
||||||
runs-on: [self-hosted, windows]
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: bash
|
|
||||||
steps:
|
|
||||||
- name: Get checkout token
|
|
||||||
uses: actions/create-github-app-token@v1
|
|
||||||
id: app_token
|
|
||||||
with:
|
|
||||||
app-id: ${{ vars.APP_ID }}
|
|
||||||
private-key: ${{ secrets.APP_PEM }}
|
|
||||||
owner: ${{ github.repository_owner }}
|
|
||||||
- name: Checkout repo
|
|
||||||
uses: funkincrew/ci-checkout@v6
|
|
||||||
with:
|
|
||||||
submodules: 'recursive'
|
|
||||||
token: ${{ steps.app_token.outputs.token }}
|
|
||||||
- name: Install Haxe, dependencies
|
|
||||||
uses: ./.github/actions/setup-haxeshit
|
|
||||||
- name: Setup build cache
|
|
||||||
run: |
|
|
||||||
mkdir -p ${{ runner.temp }}/hxcpp_cache
|
|
||||||
- name: Restore build cache
|
|
||||||
id: cache-build-win
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
export
|
|
||||||
${{ runner.temp }}/hxcpp_cache
|
|
||||||
key: ${{ runner.os }}-build-win-${{ github.ref_name }}
|
|
||||||
- name: Build game
|
|
||||||
run: |
|
|
||||||
haxelib run lime build windows -v -release -DGITHUB_BUILD
|
|
||||||
env:
|
|
||||||
HXCPP_COMPILE_CACHE: "${{ runner.temp }}\\hxcpp_cache"
|
|
||||||
- name: Upload build artifacts
|
|
||||||
uses: ./.github/actions/upload-itch
|
|
||||||
with:
|
|
||||||
butler-key: ${{ secrets.BUTLER_API_KEY }}
|
|
||||||
build-dir: export/release/windows/bin
|
|
||||||
target: win
|
|
||||||
create-nightly-mac:
|
|
||||||
runs-on: [self-hosted, macos]
|
|
||||||
steps:
|
|
||||||
- name: Fix git config on posix runner
|
|
||||||
# this can't be {{ github.workspace }} because that's not docker-aware
|
|
||||||
run: |
|
|
||||||
git config --global --add safe.directory $GITHUB_WORKSPACE
|
|
||||||
- name: Get checkout token
|
|
||||||
uses: actions/create-github-app-token@v1
|
|
||||||
id: app_token
|
|
||||||
with:
|
|
||||||
app-id: ${{ vars.APP_ID }}
|
|
||||||
private-key: ${{ secrets.APP_PEM }}
|
|
||||||
owner: ${{ github.repository_owner }}
|
|
||||||
- name: Checkout repo
|
|
||||||
uses: funkincrew/ci-checkout@v6
|
|
||||||
with:
|
|
||||||
submodules: 'recursive'
|
|
||||||
token: ${{ steps.app_token.outputs.token }}
|
|
||||||
- name: Install Haxe, dependencies
|
|
||||||
uses: ./.github/actions/setup-haxeshit
|
|
||||||
- name: Setup build cache
|
|
||||||
run: |
|
|
||||||
mkdir -p ${{ runner.temp }}/hxcpp_cache
|
|
||||||
- name: Restore build cache
|
|
||||||
id: cache-build-win
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
export
|
|
||||||
${{ runner.temp }}/hxcpp_cache
|
|
||||||
key: ${{ runner.os }}-build-mac-${{ github.ref_name }}
|
|
||||||
- name: Build game
|
|
||||||
run: |
|
|
||||||
haxelib run lime build macos -release --times -DGITHUB_BUILD
|
|
||||||
env:
|
|
||||||
HXCPP_COMPILE_CACHE: "${{ runner.temp }}/hxcpp_cache"
|
|
||||||
- name: Upload build artifacts
|
|
||||||
uses: ./.github/actions/upload-itch
|
|
||||||
with:
|
|
||||||
butler-key: ${{ secrets.BUTLER_API_KEY}}
|
|
||||||
build-dir: export/release/macos/bin
|
|
||||||
target: macos
|
|
11
.github/workflows/cancel-merged-branches.yml
vendored
11
.github/workflows/cancel-merged-branches.yml
vendored
|
@ -1,18 +1,21 @@
|
||||||
name: cancel-merged-branches
|
name: Cancel queued workflows on PR merge
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
types:
|
types:
|
||||||
- closed
|
- closed
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
cancel_stuff:
|
cancel_stuff:
|
||||||
if: github.event.pull_request.merged == true
|
if: github.event.pull_request.merged == true
|
||||||
runs-on: ubuntu-latest
|
runs-on: build-set
|
||||||
permissions:
|
permissions:
|
||||||
actions: write
|
actions: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/github-script@v7
|
- name: Cancel queued workflows for ${{ github.event.pull_request.head.ref }}
|
||||||
id: cancel-runs
|
uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
result-encoding: string
|
result-encoding: string
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|
10
.vscode/settings.json
vendored
10
.vscode/settings.json
vendored
|
@ -96,6 +96,11 @@
|
||||||
"target": "windows",
|
"target": "windows",
|
||||||
"args": ["-debug", "-DFORCE_DEBUG_VERSION"]
|
"args": ["-debug", "-DFORCE_DEBUG_VERSION"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"label": "Linux / Debug",
|
||||||
|
"target": "linux",
|
||||||
|
"args": ["-debug", "-DFORCE_DEBUG_VERSION"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "HashLink / Debug",
|
"label": "HashLink / Debug",
|
||||||
"target": "hl",
|
"target": "hl",
|
||||||
|
@ -130,6 +135,11 @@
|
||||||
"-DFORCE_DEBUG_VERSION"
|
"-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)",
|
"label": "HashLink / Debug (Straight to Play - Bopeebo Normal)",
|
||||||
"target": "hl",
|
"target": "hl",
|
||||||
|
|
|
@ -45,6 +45,7 @@
|
||||||
<library name="week6" preload="true" />
|
<library name="week6" preload="true" />
|
||||||
<library name="week7" preload="true" />
|
<library name="week7" preload="true" />
|
||||||
<library name="weekend1" preload="true" />
|
<library name="weekend1" preload="true" />
|
||||||
|
<library name="videos" preload="true" />
|
||||||
</section>
|
</section>
|
||||||
<section if="NO_PRELOAD_ALL">
|
<section if="NO_PRELOAD_ALL">
|
||||||
<library name="songs" preload="false" />
|
<library name="songs" preload="false" />
|
||||||
|
@ -58,10 +59,13 @@
|
||||||
<library name="week6" preload="false" />
|
<library name="week6" preload="false" />
|
||||||
<library name="week7" preload="false" />
|
<library name="week7" preload="false" />
|
||||||
<library name="weekend1" preload="false" />
|
<library name="weekend1" preload="false" />
|
||||||
|
<library name="videos" preload="false" />
|
||||||
</section>
|
</section>
|
||||||
<library name="art" preload="false" />
|
<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|*.ogg|*.wav" if="web" />
|
||||||
<assets path="assets/songs" library="songs" exclude="*.fla|*.mp3|*.wav" unless="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|*.ogg|*.wav" if="web" />
|
||||||
<assets path="assets/shared" library="shared" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
<assets path="assets/shared" library="shared" exclude="*.fla|*.mp3|*.wav" unless="web" />
|
||||||
<assets path="assets/tutorial" library="tutorial" exclude="*.fla|*.ogg|*.wav" if="web" />
|
<assets path="assets/tutorial" library="tutorial" exclude="*.fla|*.ogg|*.wav" if="web" />
|
||||||
|
|
2
assets
2
assets
|
@ -1 +1 @@
|
||||||
Subproject commit 1a7a0b6cc60dc8131f1651caa7abef0c1944a10c
|
Subproject commit 069c9bf45f197ebe0b38483d11bb30c15bbb5eca
|
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
|
2
hmm.json
2
hmm.json
|
@ -104,7 +104,7 @@
|
||||||
"name": "lime",
|
"name": "lime",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "1359fe6ad52e91175dc636a516d460bd54ea22ed",
|
"ref": "43ebebdd8119936b99f23407057025c7849c5f5b",
|
||||||
"url": "https://github.com/FunkinCrew/lime"
|
"url": "https://github.com/FunkinCrew/lime"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -128,6 +128,8 @@ class Main extends Sprite
|
||||||
Toolkit.init();
|
Toolkit.init();
|
||||||
Toolkit.theme = 'dark'; // don't be cringe
|
Toolkit.theme = 'dark'; // don't be cringe
|
||||||
Toolkit.autoScale = false;
|
Toolkit.autoScale = false;
|
||||||
|
// Don't focus on UI elements when they first appear.
|
||||||
|
haxe.ui.focus.FocusManager.instance.autoFocus = false;
|
||||||
funkin.input.Cursor.registerHaxeUICursors();
|
funkin.input.Cursor.registerHaxeUICursors();
|
||||||
haxe.ui.tooltips.ToolTipManager.defaultDelay = 200;
|
haxe.ui.tooltips.ToolTipManager.defaultDelay = 200;
|
||||||
}
|
}
|
||||||
|
|
|
@ -261,6 +261,35 @@ class InitState extends FlxState
|
||||||
return;
|
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(
|
LoadingState.loadPlayState(
|
||||||
{
|
{
|
||||||
targetSong: songData,
|
targetSong: songData,
|
||||||
|
@ -283,6 +312,10 @@ class InitState extends FlxState
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Rework loading behavior so we don't have to do this.
|
||||||
|
Paths.setCurrentLevel(levelId);
|
||||||
|
PlayStatePlaylist.campaignId = levelId;
|
||||||
|
|
||||||
PlayStatePlaylist.playlistSongIds = currentLevel.getSongs();
|
PlayStatePlaylist.playlistSongIds = currentLevel.getSongs();
|
||||||
PlayStatePlaylist.isStoryMode = true;
|
PlayStatePlaylist.isStoryMode = true;
|
||||||
PlayStatePlaylist.campaignScore = 0;
|
PlayStatePlaylist.campaignScore = 0;
|
||||||
|
|
|
@ -113,7 +113,7 @@ class Paths
|
||||||
|
|
||||||
public static function videos(key:String, ?library:String):String
|
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
|
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.
|
// already paused before we lost focus.
|
||||||
if (_lostFocus && !_alreadyPaused)
|
if (_lostFocus && !_alreadyPaused)
|
||||||
{
|
{
|
||||||
|
trace('Resuming audio (${this._label}) on focus!');
|
||||||
resume();
|
resume();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
trace('Not resuming audio on focus!');
|
trace('Not resuming audio (${this._label}) on focus!');
|
||||||
}
|
}
|
||||||
_lostFocus = false;
|
_lostFocus = false;
|
||||||
}
|
}
|
||||||
|
@ -402,6 +403,12 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||||
sound.group = FlxG.sound.defaultSoundGroup;
|
sound.group = FlxG.sound.defaultSoundGroup;
|
||||||
sound.persist = true;
|
sound.persist = true;
|
||||||
|
|
||||||
|
// Make sure to add the sound to the list.
|
||||||
|
// If it's already in, it won't get re-added.
|
||||||
|
// If it's not in the list (it gets removed by FunkinSound.playMusic()),
|
||||||
|
// it will get re-added (then if this was called by playMusic(), removed again)
|
||||||
|
FlxG.sound.list.add(sound);
|
||||||
|
|
||||||
// Call onLoad() because the sound already loaded
|
// Call onLoad() because the sound already loaded
|
||||||
if (onLoad != null && sound._sound != null) onLoad();
|
if (onLoad != null && sound._sound != null) onLoad();
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,7 @@ class Controls extends FlxActionSet
|
||||||
var _volume_down = new FlxActionDigital(Action.VOLUME_DOWN);
|
var _volume_down = new FlxActionDigital(Action.VOLUME_DOWN);
|
||||||
var _volume_mute = new FlxActionDigital(Action.VOLUME_MUTE);
|
var _volume_mute = new FlxActionDigital(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 gamepadsAdded:Array<Int> = [];
|
||||||
public var keyboardScheme = KeyboardScheme.None;
|
public var keyboardScheme = KeyboardScheme.None;
|
||||||
|
@ -75,122 +75,142 @@ class Controls extends FlxActionSet
|
||||||
public var UI_UP(get, never):Bool;
|
public var UI_UP(get, never):Bool;
|
||||||
|
|
||||||
inline function get_UI_UP()
|
inline function get_UI_UP()
|
||||||
return _ui_up.check();
|
return _ui_up.checkPressed();
|
||||||
|
|
||||||
public var UI_LEFT(get, never):Bool;
|
public var UI_LEFT(get, never):Bool;
|
||||||
|
|
||||||
inline function get_UI_LEFT()
|
inline function get_UI_LEFT()
|
||||||
return _ui_left.check();
|
return _ui_left.checkPressed();
|
||||||
|
|
||||||
public var UI_RIGHT(get, never):Bool;
|
public var UI_RIGHT(get, never):Bool;
|
||||||
|
|
||||||
inline function get_UI_RIGHT()
|
inline function get_UI_RIGHT()
|
||||||
return _ui_right.check();
|
return _ui_right.checkPressed();
|
||||||
|
|
||||||
public var UI_DOWN(get, never):Bool;
|
public var UI_DOWN(get, never):Bool;
|
||||||
|
|
||||||
inline function get_UI_DOWN()
|
inline function get_UI_DOWN()
|
||||||
return _ui_down.check();
|
return _ui_down.checkPressed();
|
||||||
|
|
||||||
public var UI_UP_P(get, never):Bool;
|
public var UI_UP_P(get, never):Bool;
|
||||||
|
|
||||||
inline function get_UI_UP_P()
|
inline function get_UI_UP_P()
|
||||||
return _ui_upP.check();
|
return _ui_up.checkJustPressed();
|
||||||
|
|
||||||
public var UI_LEFT_P(get, never):Bool;
|
public var UI_LEFT_P(get, never):Bool;
|
||||||
|
|
||||||
inline function get_UI_LEFT_P()
|
inline function get_UI_LEFT_P()
|
||||||
return _ui_leftP.check();
|
return _ui_left.checkJustPressed();
|
||||||
|
|
||||||
public var UI_RIGHT_P(get, never):Bool;
|
public var UI_RIGHT_P(get, never):Bool;
|
||||||
|
|
||||||
inline function get_UI_RIGHT_P()
|
inline function get_UI_RIGHT_P()
|
||||||
return _ui_rightP.check();
|
return _ui_right.checkJustPressed();
|
||||||
|
|
||||||
public var UI_DOWN_P(get, never):Bool;
|
public var UI_DOWN_P(get, never):Bool;
|
||||||
|
|
||||||
inline function get_UI_DOWN_P()
|
inline function get_UI_DOWN_P()
|
||||||
return _ui_downP.check();
|
return _ui_down.checkJustPressed();
|
||||||
|
|
||||||
public var UI_UP_R(get, never):Bool;
|
public var UI_UP_R(get, never):Bool;
|
||||||
|
|
||||||
inline function get_UI_UP_R()
|
inline function get_UI_UP_R()
|
||||||
return _ui_upR.check();
|
return _ui_up.checkJustReleased();
|
||||||
|
|
||||||
public var UI_LEFT_R(get, never):Bool;
|
public var UI_LEFT_R(get, never):Bool;
|
||||||
|
|
||||||
inline function get_UI_LEFT_R()
|
inline function get_UI_LEFT_R()
|
||||||
return _ui_leftR.check();
|
return _ui_left.checkJustReleased();
|
||||||
|
|
||||||
public var UI_RIGHT_R(get, never):Bool;
|
public var UI_RIGHT_R(get, never):Bool;
|
||||||
|
|
||||||
inline function get_UI_RIGHT_R()
|
inline function get_UI_RIGHT_R()
|
||||||
return _ui_rightR.check();
|
return _ui_right.checkJustReleased();
|
||||||
|
|
||||||
public var UI_DOWN_R(get, never):Bool;
|
public var UI_DOWN_R(get, never):Bool;
|
||||||
|
|
||||||
inline function get_UI_DOWN_R()
|
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;
|
public var NOTE_UP(get, never):Bool;
|
||||||
|
|
||||||
inline function get_NOTE_UP()
|
inline function get_NOTE_UP()
|
||||||
return _note_up.check();
|
return _note_up.checkPressed();
|
||||||
|
|
||||||
public var NOTE_LEFT(get, never):Bool;
|
public var NOTE_LEFT(get, never):Bool;
|
||||||
|
|
||||||
inline function get_NOTE_LEFT()
|
inline function get_NOTE_LEFT()
|
||||||
return _note_left.check();
|
return _note_left.checkPressed();
|
||||||
|
|
||||||
public var NOTE_RIGHT(get, never):Bool;
|
public var NOTE_RIGHT(get, never):Bool;
|
||||||
|
|
||||||
inline function get_NOTE_RIGHT()
|
inline function get_NOTE_RIGHT()
|
||||||
return _note_right.check();
|
return _note_right.checkPressed();
|
||||||
|
|
||||||
public var NOTE_DOWN(get, never):Bool;
|
public var NOTE_DOWN(get, never):Bool;
|
||||||
|
|
||||||
inline function get_NOTE_DOWN()
|
inline function get_NOTE_DOWN()
|
||||||
return _note_down.check();
|
return _note_down.checkPressed();
|
||||||
|
|
||||||
public var NOTE_UP_P(get, never):Bool;
|
public var NOTE_UP_P(get, never):Bool;
|
||||||
|
|
||||||
inline function get_NOTE_UP_P()
|
inline function get_NOTE_UP_P()
|
||||||
return _note_upP.check();
|
return _note_up.checkJustPressed();
|
||||||
|
|
||||||
public var NOTE_LEFT_P(get, never):Bool;
|
public var NOTE_LEFT_P(get, never):Bool;
|
||||||
|
|
||||||
inline function get_NOTE_LEFT_P()
|
inline function get_NOTE_LEFT_P()
|
||||||
return _note_leftP.check();
|
return _note_left.checkJustPressed();
|
||||||
|
|
||||||
public var NOTE_RIGHT_P(get, never):Bool;
|
public var NOTE_RIGHT_P(get, never):Bool;
|
||||||
|
|
||||||
inline function get_NOTE_RIGHT_P()
|
inline function get_NOTE_RIGHT_P()
|
||||||
return _note_rightP.check();
|
return _note_right.checkJustPressed();
|
||||||
|
|
||||||
public var NOTE_DOWN_P(get, never):Bool;
|
public var NOTE_DOWN_P(get, never):Bool;
|
||||||
|
|
||||||
inline function get_NOTE_DOWN_P()
|
inline function get_NOTE_DOWN_P()
|
||||||
return _note_downP.check();
|
return _note_down.checkJustPressed();
|
||||||
|
|
||||||
public var NOTE_UP_R(get, never):Bool;
|
public var NOTE_UP_R(get, never):Bool;
|
||||||
|
|
||||||
inline function get_NOTE_UP_R()
|
inline function get_NOTE_UP_R()
|
||||||
return _note_upR.check();
|
return _note_up.checkJustReleased();
|
||||||
|
|
||||||
public var NOTE_LEFT_R(get, never):Bool;
|
public var NOTE_LEFT_R(get, never):Bool;
|
||||||
|
|
||||||
inline function get_NOTE_LEFT_R()
|
inline function get_NOTE_LEFT_R()
|
||||||
return _note_leftR.check();
|
return _note_left.checkJustReleased();
|
||||||
|
|
||||||
public var NOTE_RIGHT_R(get, never):Bool;
|
public var NOTE_RIGHT_R(get, never):Bool;
|
||||||
|
|
||||||
inline function get_NOTE_RIGHT_R()
|
inline function get_NOTE_RIGHT_R()
|
||||||
return _note_rightR.check();
|
return _note_right.checkJustReleased();
|
||||||
|
|
||||||
public var NOTE_DOWN_R(get, never):Bool;
|
public var NOTE_DOWN_R(get, never):Bool;
|
||||||
|
|
||||||
inline function get_NOTE_DOWN_R()
|
inline function get_NOTE_DOWN_R()
|
||||||
return _note_downR.check();
|
return _note_down.checkJustReleased();
|
||||||
|
|
||||||
public var ACCEPT(get, never):Bool;
|
public var ACCEPT(get, never):Bool;
|
||||||
|
|
||||||
|
@ -260,26 +280,10 @@ class Controls extends FlxActionSet
|
||||||
add(_ui_left);
|
add(_ui_left);
|
||||||
add(_ui_right);
|
add(_ui_right);
|
||||||
add(_ui_down);
|
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_up);
|
||||||
add(_note_left);
|
add(_note_left);
|
||||||
add(_note_right);
|
add(_note_right);
|
||||||
add(_note_down);
|
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(_accept);
|
||||||
add(_back);
|
add(_back);
|
||||||
add(_pause);
|
add(_pause);
|
||||||
|
@ -293,8 +297,16 @@ class Controls extends FlxActionSet
|
||||||
add(_volume_down);
|
add(_volume_down);
|
||||||
add(_volume_mute);
|
add(_volume_mute);
|
||||||
|
|
||||||
for (action in digitalActions)
|
for (action in digitalActions) {
|
||||||
byName[action.name] = action;
|
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)
|
if (scheme == null)
|
||||||
scheme = None;
|
scheme = None;
|
||||||
|
@ -307,14 +319,17 @@ class Controls extends FlxActionSet
|
||||||
super.update();
|
super.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
// inline
|
public function check(name:Action, trigger:FlxInputState = JUST_PRESSED, gamepadOnly:Bool = false):Bool
|
||||||
public function checkByName(name:Action):Bool
|
|
||||||
{
|
{
|
||||||
#if debug
|
#if debug
|
||||||
if (!byName.exists(name))
|
if (!byName.exists(name))
|
||||||
throw 'Invalid name: $name';
|
throw 'Invalid name: $name';
|
||||||
#end
|
#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> {
|
public function getKeysForAction(name:Action):Array<FlxKey> {
|
||||||
|
@ -405,36 +420,36 @@ class Controls extends FlxActionSet
|
||||||
{
|
{
|
||||||
case UI_UP:
|
case UI_UP:
|
||||||
func(_ui_up, PRESSED);
|
func(_ui_up, PRESSED);
|
||||||
func(_ui_upP, JUST_PRESSED);
|
func(_ui_up, JUST_PRESSED);
|
||||||
func(_ui_upR, JUST_RELEASED);
|
func(_ui_up, JUST_RELEASED);
|
||||||
case UI_LEFT:
|
case UI_LEFT:
|
||||||
func(_ui_left, PRESSED);
|
func(_ui_left, PRESSED);
|
||||||
func(_ui_leftP, JUST_PRESSED);
|
func(_ui_left, JUST_PRESSED);
|
||||||
func(_ui_leftR, JUST_RELEASED);
|
func(_ui_left, JUST_RELEASED);
|
||||||
case UI_RIGHT:
|
case UI_RIGHT:
|
||||||
func(_ui_right, PRESSED);
|
func(_ui_right, PRESSED);
|
||||||
func(_ui_rightP, JUST_PRESSED);
|
func(_ui_right, JUST_PRESSED);
|
||||||
func(_ui_rightR, JUST_RELEASED);
|
func(_ui_right, JUST_RELEASED);
|
||||||
case UI_DOWN:
|
case UI_DOWN:
|
||||||
func(_ui_down, PRESSED);
|
func(_ui_down, PRESSED);
|
||||||
func(_ui_downP, JUST_PRESSED);
|
func(_ui_down, JUST_PRESSED);
|
||||||
func(_ui_downR, JUST_RELEASED);
|
func(_ui_down, JUST_RELEASED);
|
||||||
case NOTE_UP:
|
case NOTE_UP:
|
||||||
func(_note_up, PRESSED);
|
func(_note_up, PRESSED);
|
||||||
func(_note_upP, JUST_PRESSED);
|
func(_note_up, JUST_PRESSED);
|
||||||
func(_note_upR, JUST_RELEASED);
|
func(_note_up, JUST_RELEASED);
|
||||||
case NOTE_LEFT:
|
case NOTE_LEFT:
|
||||||
func(_note_left, PRESSED);
|
func(_note_left, PRESSED);
|
||||||
func(_note_leftP, JUST_PRESSED);
|
func(_note_left, JUST_PRESSED);
|
||||||
func(_note_leftR, JUST_RELEASED);
|
func(_note_left, JUST_RELEASED);
|
||||||
case NOTE_RIGHT:
|
case NOTE_RIGHT:
|
||||||
func(_note_right, PRESSED);
|
func(_note_right, PRESSED);
|
||||||
func(_note_rightP, JUST_PRESSED);
|
func(_note_right, JUST_PRESSED);
|
||||||
func(_note_rightR, JUST_RELEASED);
|
func(_note_right, JUST_RELEASED);
|
||||||
case NOTE_DOWN:
|
case NOTE_DOWN:
|
||||||
func(_note_down, PRESSED);
|
func(_note_down, PRESSED);
|
||||||
func(_note_downP, JUST_PRESSED);
|
func(_note_down, JUST_PRESSED);
|
||||||
func(_note_downR, JUST_RELEASED);
|
func(_note_down, JUST_RELEASED);
|
||||||
case ACCEPT:
|
case ACCEPT:
|
||||||
func(_accept, JUST_PRESSED);
|
func(_accept, JUST_PRESSED);
|
||||||
case BACK:
|
case BACK:
|
||||||
|
@ -1042,6 +1057,173 @@ typedef Swipes =
|
||||||
?curTouchPos:FlxPoint
|
?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
|
class FlxActionInputDigitalMobileSwipeGameplay extends FlxActionInputDigital
|
||||||
{
|
{
|
||||||
var touchMap:Map<Int, Swipes> = new Map();
|
var touchMap:Map<Int, Swipes> = new Map();
|
||||||
|
@ -1229,8 +1411,7 @@ enum Control
|
||||||
DEBUG_STAGE;
|
DEBUG_STAGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum
|
enum abstract Action(String) to String from String
|
||||||
abstract Action(String) to String from String
|
|
||||||
{
|
{
|
||||||
// NOTE
|
// NOTE
|
||||||
var NOTE_UP = "note_up";
|
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 {
|
return {
|
||||||
assetLibraryPaths: [
|
assetLibraryPaths: [
|
||||||
'default' => 'preload', 'shared' => 'shared', 'songs' => 'songs', 'tutorial' => 'tutorial', 'week1' => 'week1', 'week2' => 'week2',
|
'default' => 'preload', 'shared' => 'shared', 'songs' => 'songs', 'videos' => 'videos', 'tutorial' => 'tutorial', 'week1' => 'week1',
|
||||||
'week3' => 'week3', 'week4' => 'week4', 'week5' => 'week5', 'week6' => 'week6', 'week7' => 'week7', 'weekend1' => 'weekend1',
|
'week2' => 'week2', 'week3' => 'week3', 'week4' => 'week4', 'week5' => 'week5', 'week6' => 'week6', 'week7' => 'week7', 'weekend1' => 'weekend1',
|
||||||
],
|
],
|
||||||
coreAssetRedirect: CORE_FOLDER,
|
coreAssetRedirect: CORE_FOLDER,
|
||||||
}
|
}
|
||||||
|
|
|
@ -834,9 +834,12 @@ class PlayState extends MusicBeatSubState
|
||||||
inputSpitter = [];
|
inputSpitter = [];
|
||||||
|
|
||||||
// Reset music properly.
|
// Reset music properly.
|
||||||
|
if (FlxG.sound.music != null)
|
||||||
|
{
|
||||||
FlxG.sound.music.time = startTimestamp - Conductor.instance.instrumentalOffset;
|
FlxG.sound.music.time = startTimestamp - Conductor.instance.instrumentalOffset;
|
||||||
FlxG.sound.music.pitch = playbackRate;
|
FlxG.sound.music.pitch = playbackRate;
|
||||||
FlxG.sound.music.pause();
|
FlxG.sound.music.pause();
|
||||||
|
}
|
||||||
|
|
||||||
if (!overrideMusic)
|
if (!overrideMusic)
|
||||||
{
|
{
|
||||||
|
@ -852,7 +855,7 @@ class PlayState extends MusicBeatSubState
|
||||||
vocals.pause();
|
vocals.pause();
|
||||||
vocals.time = 0;
|
vocals.time = 0;
|
||||||
|
|
||||||
FlxG.sound.music.volume = 1;
|
if (FlxG.sound.music != null) FlxG.sound.music.volume = 1;
|
||||||
vocals.volume = 1;
|
vocals.volume = 1;
|
||||||
vocals.playerVolume = 1;
|
vocals.playerVolume = 1;
|
||||||
vocals.opponentVolume = 1;
|
vocals.opponentVolume = 1;
|
||||||
|
@ -1548,10 +1551,11 @@ class PlayState extends MusicBeatSubState
|
||||||
function loadStage(id:String):Void
|
function loadStage(id:String):Void
|
||||||
{
|
{
|
||||||
currentStage = StageRegistry.instance.fetchEntry(id);
|
currentStage = StageRegistry.instance.fetchEntry(id);
|
||||||
currentStage.revive(); // Stages are killed and props destroyed when the PlayState is destroyed to save memory.
|
|
||||||
|
|
||||||
if (currentStage != null)
|
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.
|
// Actually create and position the sprites.
|
||||||
var event:ScriptEvent = new ScriptEvent(CREATE, false);
|
var event:ScriptEvent = new ScriptEvent(CREATE, false);
|
||||||
ScriptEventDispatcher.callEvent(currentStage, event);
|
ScriptEventDispatcher.callEvent(currentStage, event);
|
||||||
|
@ -2388,13 +2392,6 @@ class PlayState extends MusicBeatSubState
|
||||||
|
|
||||||
// Display the combo meter and add the calculation to the score.
|
// Display the combo meter and add the calculation to the score.
|
||||||
popUpScore(note, event.score, event.judgement, event.healthChange);
|
popUpScore(note, event.score, event.judgement, event.healthChange);
|
||||||
|
|
||||||
if (note.isHoldNote && note.holdNoteSprite != null)
|
|
||||||
{
|
|
||||||
playerStrumline.playNoteHoldCover(note.holdNoteSprite);
|
|
||||||
}
|
|
||||||
|
|
||||||
vocals.playerVolume = 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2676,6 +2673,13 @@ class PlayState extends MusicBeatSubState
|
||||||
}
|
}
|
||||||
comboPopUps.displayRating(daRating);
|
comboPopUps.displayRating(daRating);
|
||||||
if (Highscore.tallies.combo >= 10 || Highscore.tallies.combo == 0) comboPopUps.displayCombo(Highscore.tallies.combo);
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2786,7 +2790,7 @@ class PlayState extends MusicBeatSubState
|
||||||
// adds current song data into the tallies for the level (story levels)
|
// adds current song data into the tallies for the level (story levels)
|
||||||
Highscore.talliesLevel = Highscore.combineTallies(Highscore.tallies, Highscore.talliesLevel);
|
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);
|
Save.instance.setSongScore(currentSong.id, currentDifficulty, data);
|
||||||
#if newgrounds
|
#if newgrounds
|
||||||
|
@ -3072,18 +3076,18 @@ class PlayState extends MusicBeatSubState
|
||||||
title: PlayStatePlaylist.isStoryMode ? ('${PlayStatePlaylist.campaignTitle}') : ('${currentChart.songName} by ${currentChart.songArtist}'),
|
title: PlayStatePlaylist.isStoryMode ? ('${PlayStatePlaylist.campaignTitle}') : ('${currentChart.songName} by ${currentChart.songArtist}'),
|
||||||
scoreData:
|
scoreData:
|
||||||
{
|
{
|
||||||
score: songScore,
|
score: PlayStatePlaylist.isStoryMode ? PlayStatePlaylist.campaignScore : songScore,
|
||||||
tallies:
|
tallies:
|
||||||
{
|
{
|
||||||
sick: Highscore.tallies.sick,
|
sick: talliesToUse.sick,
|
||||||
good: Highscore.tallies.good,
|
good: talliesToUse.good,
|
||||||
bad: Highscore.tallies.bad,
|
bad: talliesToUse.bad,
|
||||||
shit: Highscore.tallies.shit,
|
shit: talliesToUse.shit,
|
||||||
missed: Highscore.tallies.missed,
|
missed: talliesToUse.missed,
|
||||||
combo: Highscore.tallies.combo,
|
combo: talliesToUse.combo,
|
||||||
maxCombo: Highscore.tallies.maxCombo,
|
maxCombo: talliesToUse.maxCombo,
|
||||||
totalNotesHit: Highscore.tallies.totalNotesHit,
|
totalNotesHit: talliesToUse.totalNotesHit,
|
||||||
totalNotes: Highscore.tallies.totalNotes,
|
totalNotes: talliesToUse.totalNotes,
|
||||||
},
|
},
|
||||||
accuracy: Highscore.tallies.totalNotesHit / Highscore.tallies.totalNotes,
|
accuracy: Highscore.tallies.totalNotesHit / Highscore.tallies.totalNotes,
|
||||||
},
|
},
|
||||||
|
|
|
@ -80,34 +80,34 @@ class ResultState extends MusicBeatSubState
|
||||||
bgFlash.visible = false;
|
bgFlash.visible = false;
|
||||||
add(bgFlash);
|
add(bgFlash);
|
||||||
|
|
||||||
var bfGfExcellent:FlxAtlasSprite = new FlxAtlasSprite(380, -170, Paths.animateAtlas("resultScreen/resultsBoyfriendExcellent", "shared"));
|
// var bfGfExcellent:FlxAtlasSprite = new FlxAtlasSprite(380, -170, Paths.animateAtlas("resultScreen/resultsBoyfriendExcellent", "shared"));
|
||||||
bfGfExcellent.visible = false;
|
// bfGfExcellent.visible = false;
|
||||||
add(bfGfExcellent);
|
// 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"));
|
var gf:FlxSprite = FunkinSprite.createSparrow(625, 325, 'resultScreen/resultGirlfriendGOOD');
|
||||||
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');
|
|
||||||
gf.animation.addByPrefix("clap", "Girlfriend Good Anim", 24, false);
|
gf.animation.addByPrefix("clap", "Girlfriend Good Anim", 24, false);
|
||||||
gf.visible = false;
|
gf.visible = false;
|
||||||
gf.animation.finishCallback = _ -> {
|
gf.animation.finishCallback = _ -> {
|
||||||
|
@ -268,9 +268,9 @@ class ResultState extends MusicBeatSubState
|
||||||
|
|
||||||
switch (resultsVariation)
|
switch (resultsVariation)
|
||||||
{
|
{
|
||||||
case SHIT:
|
// case SHIT:
|
||||||
bfSHIT.visible = true;
|
// bfSHIT.visible = true;
|
||||||
bfSHIT.playAnimation("");
|
// bfSHIT.playAnimation("");
|
||||||
|
|
||||||
case NORMAL:
|
case NORMAL:
|
||||||
boyfriend.animation.play('fall');
|
boyfriend.animation.play('fall');
|
||||||
|
@ -292,9 +292,9 @@ class ResultState extends MusicBeatSubState
|
||||||
gf.animation.play('clap', true);
|
gf.animation.play('clap', true);
|
||||||
gf.visible = true;
|
gf.visible = true;
|
||||||
});
|
});
|
||||||
case PERFECT:
|
// case PERFECT:
|
||||||
bfPerfect.visible = true;
|
// bfPerfect.visible = true;
|
||||||
bfPerfect.playAnimation("");
|
// bfPerfect.playAnimation("");
|
||||||
|
|
||||||
// bfGfExcellent.visible = true;
|
// bfGfExcellent.visible = true;
|
||||||
// bfGfExcellent.playAnimation("");
|
// bfGfExcellent.playAnimation("");
|
||||||
|
|
|
@ -192,6 +192,7 @@ class AnimateAtlasCharacter extends BaseCharacter
|
||||||
if (!this.mainSprite.hasAnimation(prefix))
|
if (!this.mainSprite.hasAnimation(prefix))
|
||||||
{
|
{
|
||||||
FlxG.log.warn('[ATLASCHAR] Animation ${prefix} not found in Animate Atlas ${_data.assetPath}');
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
animations.set(anim.name, anim);
|
animations.set(anim.name, anim);
|
||||||
|
|
|
@ -67,8 +67,13 @@ class VideoCutscene
|
||||||
if (!openfl.Assets.exists(filePath))
|
if (!openfl.Assets.exists(filePath))
|
||||||
{
|
{
|
||||||
// Display a popup.
|
// Display a popup.
|
||||||
lime.app.Application.current.window.alert('Video file does not exist: ${filePath}', 'Error playing video');
|
// lime.app.Application.current.window.alert('Video file does not exist: ${filePath}', 'Error playing video');
|
||||||
return;
|
// 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);
|
var rawFilePath = Paths.stripLibrary(filePath);
|
||||||
|
|
|
@ -693,7 +693,7 @@ class Save
|
||||||
trace("[SAVE] Checking for legacy save data...");
|
trace("[SAVE] Checking for legacy save data...");
|
||||||
var legacySave:FlxSave = new FlxSave();
|
var legacySave:FlxSave = new FlxSave();
|
||||||
legacySave.bind(SAVE_NAME_LEGACY, SAVE_PATH_LEGACY);
|
legacySave.bind(SAVE_NAME_LEGACY, SAVE_PATH_LEGACY);
|
||||||
if (legacySave?.data == null)
|
if (legacySave.isEmpty())
|
||||||
{
|
{
|
||||||
trace("[SAVE] No legacy save data found.");
|
trace("[SAVE] No legacy save data found.");
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -57,10 +57,6 @@ class CreditsDataHandler
|
||||||
{line: 'KawaiSprite'},
|
{line: 'KawaiSprite'},
|
||||||
{line: 'evilsk8r'},
|
{line: 'evilsk8r'},
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
header: 'Kickstarter Backers',
|
|
||||||
appendBackers: true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
@ -68,11 +64,11 @@ class CreditsDataHandler
|
||||||
|
|
||||||
public static function fetchBackerEntries():Array<String>
|
public static function fetchBackerEntries():Array<String>
|
||||||
{
|
{
|
||||||
// TODO: Replace this with a web request.
|
// TODO: Implement a web request.
|
||||||
// We can't just grab the current Kickstarter data and include it in builds,
|
// 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.
|
// because we don't want to deadname people who haven't logged into the portal yet.
|
||||||
// It can be async and paginated for performance!
|
// It can be async and paginated for performance!
|
||||||
return ['See the list of backers at $BACKER_PUBLIC_URL.'];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
#if HARDCODED_CREDITS
|
#if HARDCODED_CREDITS
|
||||||
|
@ -99,12 +95,19 @@ class CreditsDataHandler
|
||||||
|
|
||||||
static function fetchCreditsData():funkin.data.JsonFile
|
static function fetchCreditsData():funkin.data.JsonFile
|
||||||
{
|
{
|
||||||
|
#if !macro
|
||||||
var rawJson:String = openfl.Assets.getText(CREDITS_DATA_PATH).trim();
|
var rawJson:String = openfl.Assets.getText(CREDITS_DATA_PATH).trim();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fileName: CREDITS_DATA_PATH,
|
fileName: CREDITS_DATA_PATH,
|
||||||
contents: rawJson
|
contents: rawJson
|
||||||
};
|
};
|
||||||
|
#else
|
||||||
|
return {
|
||||||
|
fileName: CREDITS_DATA_PATH,
|
||||||
|
contents: null
|
||||||
|
};
|
||||||
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
static function parseCreditsData(file:JsonFile):Null<CreditsData>
|
static function parseCreditsData(file:JsonFile):Null<CreditsData>
|
||||||
|
|
|
@ -8,6 +8,7 @@ import flixel.FlxSprite;
|
||||||
import flixel.FlxSubState;
|
import flixel.FlxSubState;
|
||||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||||
import flixel.group.FlxSpriteGroup;
|
import flixel.group.FlxSpriteGroup;
|
||||||
|
import flixel.input.gamepad.FlxGamepadInputID;
|
||||||
import flixel.input.keyboard.FlxKey;
|
import flixel.input.keyboard.FlxKey;
|
||||||
import flixel.input.mouse.FlxMouseEvent;
|
import flixel.input.mouse.FlxMouseEvent;
|
||||||
import flixel.math.FlxMath;
|
import flixel.math.FlxMath;
|
||||||
|
@ -40,6 +41,8 @@ import funkin.data.stage.StageData;
|
||||||
import funkin.graphics.FunkinCamera;
|
import funkin.graphics.FunkinCamera;
|
||||||
import funkin.graphics.FunkinSprite;
|
import funkin.graphics.FunkinSprite;
|
||||||
import funkin.input.Cursor;
|
import funkin.input.Cursor;
|
||||||
|
import funkin.input.TurboActionHandler;
|
||||||
|
import funkin.input.TurboButtonHandler;
|
||||||
import funkin.input.TurboKeyHandler;
|
import funkin.input.TurboKeyHandler;
|
||||||
import funkin.modding.events.ScriptEvent;
|
import funkin.modding.events.ScriptEvent;
|
||||||
import funkin.play.character.BaseCharacter.CharacterType;
|
import funkin.play.character.BaseCharacter.CharacterType;
|
||||||
|
@ -74,6 +77,7 @@ import funkin.ui.debug.charting.commands.SetItemSelectionCommand;
|
||||||
import funkin.ui.debug.charting.components.ChartEditorEventSprite;
|
import funkin.ui.debug.charting.components.ChartEditorEventSprite;
|
||||||
import funkin.ui.debug.charting.components.ChartEditorHoldNoteSprite;
|
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.ChartEditorMeasureTicks;
|
||||||
import funkin.ui.debug.charting.components.ChartEditorNotePreview;
|
import funkin.ui.debug.charting.components.ChartEditorNotePreview;
|
||||||
import funkin.ui.debug.charting.components.ChartEditorNoteSprite;
|
import funkin.ui.debug.charting.components.ChartEditorNoteSprite;
|
||||||
import funkin.ui.debug.charting.components.ChartEditorPlaybarHead;
|
import funkin.ui.debug.charting.components.ChartEditorPlaybarHead;
|
||||||
|
@ -401,8 +405,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
renderedSelectionSquares.setPosition(gridTiledSprite?.x ?? 0.0, gridTiledSprite?.y ?? 0.0);
|
renderedSelectionSquares.setPosition(gridTiledSprite?.x ?? 0.0, gridTiledSprite?.y ?? 0.0);
|
||||||
// Offset the selection box start position, if we are dragging.
|
// Offset the selection box start position, if we are dragging.
|
||||||
if (selectionBoxStartPos != null) selectionBoxStartPos.y -= diff;
|
if (selectionBoxStartPos != null) selectionBoxStartPos.y -= diff;
|
||||||
// Update the note preview viewport box.
|
|
||||||
|
// Update the note preview.
|
||||||
setNotePreviewViewportBounds(calculateNotePreviewViewportBounds());
|
setNotePreviewViewportBounds(calculateNotePreviewViewportBounds());
|
||||||
|
refreshNotePreviewPlayheadPosition();
|
||||||
|
|
||||||
// Update the measure tick display.
|
// Update the measure tick display.
|
||||||
if (measureTicks != null) measureTicks.y = gridTiledSprite?.y ?? 0.0;
|
if (measureTicks != null) measureTicks.y = gridTiledSprite?.y ?? 0.0;
|
||||||
return this.scrollPositionInPixels;
|
return this.scrollPositionInPixels;
|
||||||
|
@ -463,6 +470,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
// Move the playhead sprite to the correct position.
|
// Move the playhead sprite to the correct position.
|
||||||
gridPlayhead.y = this.playheadPositionInPixels + GRID_INITIAL_Y_POS;
|
gridPlayhead.y = this.playheadPositionInPixels + GRID_INITIAL_Y_POS;
|
||||||
|
|
||||||
|
updatePlayheadGhostHoldNotes();
|
||||||
|
refreshNotePreviewPlayheadPosition();
|
||||||
|
|
||||||
return this.playheadPositionInPixels;
|
return this.playheadPositionInPixels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -769,6 +779,13 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
return currentPlaceNoteData = value;
|
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
|
// Note Movement
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -799,6 +816,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
*/
|
*/
|
||||||
var dragLengthCurrent:Float = 0;
|
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.
|
* Flip-flop to alternate between two stretching sounds.
|
||||||
*/
|
*/
|
||||||
|
@ -1071,6 +1094,66 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
*/
|
*/
|
||||||
var pageDownKeyHandler:TurboKeyHandler = TurboKeyHandler.build(FlxKey.PAGEDOWN);
|
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
|
* AUDIO AND SOUND DATA
|
||||||
*/
|
*/
|
||||||
|
@ -1949,10 +2032,15 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
var gridGhostNote:Null<ChartEditorNoteSprite> = null;
|
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;
|
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.
|
* A sprite used to indicate the event that will be placed on click.
|
||||||
*/
|
*/
|
||||||
|
@ -1970,6 +2058,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
*/
|
*/
|
||||||
var notePreviewViewport:Null<FlxSliceSprite> = null;
|
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.
|
* The rectangular sprite used for rendering the selection box.
|
||||||
* Uses a 9-slice to stretch the selection box to the correct size without warping.
|
* Uses a 9-slice to stretch the selection box to the correct size without warping.
|
||||||
|
@ -2349,7 +2443,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
|
|
||||||
gridGhostHoldNote = new ChartEditorHoldNoteSprite(this);
|
gridGhostHoldNote = new ChartEditorHoldNoteSprite(this);
|
||||||
gridGhostHoldNote.alpha = 0.6;
|
gridGhostHoldNote.alpha = 0.6;
|
||||||
gridGhostHoldNote.noteData = new SongNoteData(0, 0, 0, "");
|
gridGhostHoldNote.noteData = null;
|
||||||
gridGhostHoldNote.visible = false;
|
gridGhostHoldNote.visible = false;
|
||||||
add(gridGhostHoldNote);
|
add(gridGhostHoldNote);
|
||||||
gridGhostHoldNote.zIndex = 11;
|
gridGhostHoldNote.zIndex = 11;
|
||||||
|
@ -2423,6 +2517,15 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
add(notePreviewViewport);
|
add(notePreviewViewport);
|
||||||
notePreviewViewport.zIndex = 30;
|
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());
|
setNotePreviewViewportBounds(calculateNotePreviewViewportBounds());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2519,6 +2622,13 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function refreshNotePreviewPlayheadPosition():Void
|
||||||
|
{
|
||||||
|
if (notePreviewPlayhead == null) return;
|
||||||
|
|
||||||
|
notePreviewPlayhead.y = notePreview.y + (notePreview.height * ((scrollPositionInPixels + playheadPositionInPixels) / songLengthInPixels));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the group that will hold all the notes.
|
* Builds the group that will hold all the notes.
|
||||||
*/
|
*/
|
||||||
|
@ -3015,6 +3125,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
*/
|
*/
|
||||||
function setupTurboKeyHandlers():Void
|
function setupTurboKeyHandlers():Void
|
||||||
{
|
{
|
||||||
|
// Keyboard shortcuts
|
||||||
add(undoKeyHandler);
|
add(undoKeyHandler);
|
||||||
add(redoKeyHandler);
|
add(redoKeyHandler);
|
||||||
add(upKeyHandler);
|
add(upKeyHandler);
|
||||||
|
@ -3023,6 +3134,20 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
add(sKeyHandler);
|
add(sKeyHandler);
|
||||||
add(pageUpKeyHandler);
|
add(pageUpKeyHandler);
|
||||||
add(pageDownKeyHandler);
|
add(pageDownKeyHandler);
|
||||||
|
|
||||||
|
// Gamepad inputs
|
||||||
|
add(dpadUpGamepadHandler);
|
||||||
|
add(dpadDownGamepadHandler);
|
||||||
|
add(dpadLeftGamepadHandler);
|
||||||
|
add(dpadRightGamepadHandler);
|
||||||
|
add(leftStickUpGamepadHandler);
|
||||||
|
add(leftStickDownGamepadHandler);
|
||||||
|
add(leftStickLeftGamepadHandler);
|
||||||
|
add(leftStickRightGamepadHandler);
|
||||||
|
add(rightStickUpGamepadHandler);
|
||||||
|
add(rightStickDownGamepadHandler);
|
||||||
|
add(rightStickLeftGamepadHandler);
|
||||||
|
add(rightStickRightGamepadHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3709,32 +3834,56 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
// Up Arrow = Scroll Up
|
// Up Arrow = Scroll Up
|
||||||
if (upKeyHandler.activated && currentLiveInputStyle == None)
|
if (upKeyHandler.activated && currentLiveInputStyle == None)
|
||||||
{
|
{
|
||||||
scrollAmount = -GRID_SIZE * 0.25 * 25.0;
|
scrollAmount = -GRID_SIZE * 4;
|
||||||
shouldPause = true;
|
shouldPause = true;
|
||||||
}
|
}
|
||||||
// Down Arrow = Scroll Down
|
// Down Arrow = Scroll Down
|
||||||
if (downKeyHandler.activated && currentLiveInputStyle == None)
|
if (downKeyHandler.activated && currentLiveInputStyle == None)
|
||||||
{
|
{
|
||||||
scrollAmount = GRID_SIZE * 0.25 * 25.0;
|
scrollAmount = GRID_SIZE * 4;
|
||||||
shouldPause = true;
|
shouldPause = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// W = Scroll Up (doesn't work with Ctrl+Scroll)
|
// W = Scroll Up (doesn't work with Ctrl+Scroll)
|
||||||
if (wKeyHandler.activated && currentLiveInputStyle == None && !FlxG.keys.pressed.CONTROL)
|
if (wKeyHandler.activated && currentLiveInputStyle == None && !FlxG.keys.pressed.CONTROL)
|
||||||
{
|
{
|
||||||
scrollAmount = -GRID_SIZE * 0.25 * 25.0;
|
scrollAmount = -GRID_SIZE * 4;
|
||||||
shouldPause = true;
|
shouldPause = true;
|
||||||
}
|
}
|
||||||
// S = Scroll Down (doesn't work with Ctrl+Scroll)
|
// S = Scroll Down (doesn't work with Ctrl+Scroll)
|
||||||
if (sKeyHandler.activated && currentLiveInputStyle == None && !FlxG.keys.pressed.CONTROL)
|
if (sKeyHandler.activated && currentLiveInputStyle == None && !FlxG.keys.pressed.CONTROL)
|
||||||
{
|
{
|
||||||
scrollAmount = GRID_SIZE * 0.25 * 25.0;
|
scrollAmount = GRID_SIZE * 4;
|
||||||
shouldPause = true;
|
shouldPause = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// PAGE UP = Jump up to nearest measure
|
// GAMEPAD LEFT STICK UP = Scroll Up by 1 note snap
|
||||||
if (pageUpKeyHandler.activated)
|
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 measureHeight:Float = GRID_SIZE * 4 * Conductor.instance.beatsPerMeasure;
|
||||||
var playheadPos:Float = scrollPositionInPixels + playheadPositionInPixels;
|
var playheadPos:Float = scrollPositionInPixels + playheadPositionInPixels;
|
||||||
var targetScrollPosition:Float = Math.floor(playheadPos / measureHeight) * measureHeight;
|
var targetScrollPosition:Float = Math.floor(playheadPos / measureHeight) * measureHeight;
|
||||||
|
@ -3744,20 +3893,37 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
{
|
{
|
||||||
targetScrollPosition -= GRID_SIZE * Constants.STEPS_PER_BEAT * Conductor.instance.beatsPerMeasure;
|
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;
|
shouldPause = true;
|
||||||
}
|
}
|
||||||
if (playbarButtonPressed == 'playbarBack')
|
if (playbarButtonPressed == 'playbarBack')
|
||||||
{
|
{
|
||||||
playbarButtonPressed = '';
|
playbarButtonPressed = '';
|
||||||
scrollAmount = -GRID_SIZE * 4 * Conductor.instance.beatsPerMeasure;
|
funcJumpUp(false);
|
||||||
shouldPause = true;
|
shouldPause = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// PAGE DOWN = Jump down to nearest measure
|
var funcJumpDown = (playheadOnly:Bool) -> {
|
||||||
if (pageDownKeyHandler.activated)
|
|
||||||
{
|
|
||||||
var measureHeight:Float = GRID_SIZE * 4 * Conductor.instance.beatsPerMeasure;
|
var measureHeight:Float = GRID_SIZE * 4 * Conductor.instance.beatsPerMeasure;
|
||||||
var playheadPos:Float = scrollPositionInPixels + playheadPositionInPixels;
|
var playheadPos:Float = scrollPositionInPixels + playheadPositionInPixels;
|
||||||
var targetScrollPosition:Float = Math.ceil(playheadPos / measureHeight) * measureHeight;
|
var targetScrollPosition:Float = Math.ceil(playheadPos / measureHeight) * measureHeight;
|
||||||
|
@ -3767,26 +3933,46 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
{
|
{
|
||||||
targetScrollPosition += GRID_SIZE * Constants.STEPS_PER_BEAT * Conductor.instance.beatsPerMeasure;
|
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;
|
shouldPause = true;
|
||||||
}
|
}
|
||||||
if (playbarButtonPressed == 'playbarForward')
|
if (playbarButtonPressed == 'playbarForward')
|
||||||
{
|
{
|
||||||
playbarButtonPressed = '';
|
playbarButtonPressed = '';
|
||||||
scrollAmount = GRID_SIZE * 4 * Conductor.instance.beatsPerMeasure;
|
funcJumpDown(false);
|
||||||
shouldPause = true;
|
shouldPause = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// SHIFT + Scroll = Scroll Fast
|
// 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;
|
scrollAmount *= 2;
|
||||||
}
|
}
|
||||||
// CONTROL + Scroll = Scroll Precise
|
// CONTROL + Scroll = Scroll Precise
|
||||||
if (FlxG.keys.pressed.CONTROL)
|
if (FlxG.keys.pressed.CONTROL)
|
||||||
{
|
{
|
||||||
scrollAmount /= 10;
|
scrollAmount /= 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alt + Drag = Scroll but move the playhead the same amount.
|
// Alt + Drag = Scroll but move the playhead the same amount.
|
||||||
|
@ -4380,9 +4566,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
}
|
}
|
||||||
|
|
||||||
gridGhostHoldNote.visible = true;
|
gridGhostHoldNote.visible = true;
|
||||||
gridGhostHoldNote.noteData = currentPlaceNoteData;
|
gridGhostHoldNote.noteData = gridGhostNote.noteData;
|
||||||
gridGhostHoldNote.noteDirection = currentPlaceNoteData.getDirection();
|
gridGhostHoldNote.noteDirection = gridGhostNote.noteData.getDirection();
|
||||||
|
|
||||||
gridGhostHoldNote.setHeightDirectly(dragLengthPixels, true);
|
gridGhostHoldNote.setHeightDirectly(dragLengthPixels, true);
|
||||||
|
|
||||||
gridGhostHoldNote.updateHoldNotePosition(renderedHoldNotes);
|
gridGhostHoldNote.updateHoldNotePosition(renderedHoldNotes);
|
||||||
|
@ -4943,37 +5128,57 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
|
|
||||||
function handlePlayhead():Void
|
function handlePlayhead():Void
|
||||||
{
|
{
|
||||||
// Place notes at the playhead.
|
// Place notes at the playhead with the keyboard.
|
||||||
switch (currentLiveInputStyle)
|
switch (currentLiveInputStyle)
|
||||||
{
|
{
|
||||||
case ChartEditorLiveInputStyle.WASDKeys:
|
case ChartEditorLiveInputStyle.WASDKeys:
|
||||||
if (FlxG.keys.justPressed.A) placeNoteAtPlayhead(4);
|
if (FlxG.keys.justPressed.A) placeNoteAtPlayhead(4);
|
||||||
|
if (FlxG.keys.justReleased.A) finishPlaceNoteAtPlayhead(4);
|
||||||
if (FlxG.keys.justPressed.S) placeNoteAtPlayhead(5);
|
if (FlxG.keys.justPressed.S) placeNoteAtPlayhead(5);
|
||||||
|
if (FlxG.keys.justReleased.S) finishPlaceNoteAtPlayhead(5);
|
||||||
if (FlxG.keys.justPressed.W) placeNoteAtPlayhead(6);
|
if (FlxG.keys.justPressed.W) placeNoteAtPlayhead(6);
|
||||||
|
if (FlxG.keys.justReleased.W) finishPlaceNoteAtPlayhead(6);
|
||||||
if (FlxG.keys.justPressed.D) placeNoteAtPlayhead(7);
|
if (FlxG.keys.justPressed.D) placeNoteAtPlayhead(7);
|
||||||
|
if (FlxG.keys.justReleased.D) finishPlaceNoteAtPlayhead(7);
|
||||||
|
|
||||||
if (FlxG.keys.justPressed.LEFT) placeNoteAtPlayhead(0);
|
if (FlxG.keys.justPressed.LEFT) placeNoteAtPlayhead(0);
|
||||||
|
if (FlxG.keys.justReleased.LEFT) finishPlaceNoteAtPlayhead(0);
|
||||||
if (FlxG.keys.justPressed.DOWN) placeNoteAtPlayhead(1);
|
if (FlxG.keys.justPressed.DOWN) placeNoteAtPlayhead(1);
|
||||||
|
if (FlxG.keys.justReleased.DOWN) finishPlaceNoteAtPlayhead(1);
|
||||||
if (FlxG.keys.justPressed.UP) placeNoteAtPlayhead(2);
|
if (FlxG.keys.justPressed.UP) placeNoteAtPlayhead(2);
|
||||||
|
if (FlxG.keys.justReleased.UP) finishPlaceNoteAtPlayhead(2);
|
||||||
if (FlxG.keys.justPressed.RIGHT) placeNoteAtPlayhead(3);
|
if (FlxG.keys.justPressed.RIGHT) placeNoteAtPlayhead(3);
|
||||||
|
if (FlxG.keys.justReleased.RIGHT) finishPlaceNoteAtPlayhead(3);
|
||||||
case ChartEditorLiveInputStyle.NumberKeys:
|
case ChartEditorLiveInputStyle.NumberKeys:
|
||||||
// Flipped because Dad is on the left but represents data 0-3.
|
// Flipped because Dad is on the left but represents data 0-3.
|
||||||
if (FlxG.keys.justPressed.ONE) placeNoteAtPlayhead(4);
|
if (FlxG.keys.justPressed.ONE) placeNoteAtPlayhead(4);
|
||||||
|
if (FlxG.keys.justReleased.ONE) finishPlaceNoteAtPlayhead(4);
|
||||||
if (FlxG.keys.justPressed.TWO) placeNoteAtPlayhead(5);
|
if (FlxG.keys.justPressed.TWO) placeNoteAtPlayhead(5);
|
||||||
|
if (FlxG.keys.justReleased.TWO) finishPlaceNoteAtPlayhead(5);
|
||||||
if (FlxG.keys.justPressed.THREE) placeNoteAtPlayhead(6);
|
if (FlxG.keys.justPressed.THREE) placeNoteAtPlayhead(6);
|
||||||
|
if (FlxG.keys.justReleased.THREE) finishPlaceNoteAtPlayhead(6);
|
||||||
if (FlxG.keys.justPressed.FOUR) placeNoteAtPlayhead(7);
|
if (FlxG.keys.justPressed.FOUR) placeNoteAtPlayhead(7);
|
||||||
|
if (FlxG.keys.justReleased.FOUR) finishPlaceNoteAtPlayhead(7);
|
||||||
|
|
||||||
if (FlxG.keys.justPressed.FIVE) placeNoteAtPlayhead(0);
|
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.SIX) placeNoteAtPlayhead(1);
|
||||||
if (FlxG.keys.justPressed.SEVEN) placeNoteAtPlayhead(2);
|
if (FlxG.keys.justPressed.SEVEN) placeNoteAtPlayhead(2);
|
||||||
|
if (FlxG.keys.justReleased.SEVEN) finishPlaceNoteAtPlayhead(2);
|
||||||
if (FlxG.keys.justPressed.EIGHT) placeNoteAtPlayhead(3);
|
if (FlxG.keys.justPressed.EIGHT) placeNoteAtPlayhead(3);
|
||||||
|
if (FlxG.keys.justReleased.EIGHT) finishPlaceNoteAtPlayhead(3);
|
||||||
case ChartEditorLiveInputStyle.None:
|
case ChartEditorLiveInputStyle.None:
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatePlayheadGhostHoldNotes();
|
||||||
}
|
}
|
||||||
|
|
||||||
function placeNoteAtPlayhead(column:Int):Void
|
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 playheadPos:Float = scrollPositionInPixels + playheadPositionInPixels;
|
||||||
var playheadPosFractionalStep:Float = playheadPos / GRID_SIZE / noteSnapRatio;
|
var playheadPosFractionalStep:Float = playheadPos / GRID_SIZE / noteSnapRatio;
|
||||||
var playheadPosStep:Int = Std.int(Math.floor(playheadPosFractionalStep));
|
var playheadPosStep:Int = Std.int(Math.floor(playheadPosFractionalStep));
|
||||||
|
@ -4984,14 +5189,136 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
playheadPosSnappedMs + Conductor.instance.stepLengthMs * noteSnapRatio);
|
playheadPosSnappedMs + Conductor.instance.stepLengthMs * noteSnapRatio);
|
||||||
notesAtPos = SongDataUtils.getNotesWithData(notesAtPos, [column]);
|
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);
|
var newNoteData:SongNoteData = new SongNoteData(playheadPosSnappedMs, column, 0, noteKindToPlace);
|
||||||
performCommand(new AddNotesCommand([newNoteData], FlxG.keys.pressed.CONTROL));
|
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
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,8 @@ class RemoveEventsCommand implements ChartEditorCommand
|
||||||
|
|
||||||
public function execute(state:ChartEditorState):Void
|
public function execute(state:ChartEditorState):Void
|
||||||
{
|
{
|
||||||
|
if (events.length == 0) return;
|
||||||
|
|
||||||
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events);
|
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events);
|
||||||
state.currentEventSelection = [];
|
state.currentEventSelection = [];
|
||||||
|
|
||||||
|
@ -34,6 +36,8 @@ class RemoveEventsCommand implements ChartEditorCommand
|
||||||
|
|
||||||
public function undo(state:ChartEditorState):Void
|
public function undo(state:ChartEditorState):Void
|
||||||
{
|
{
|
||||||
|
if (events.length == 0) return;
|
||||||
|
|
||||||
for (event in events)
|
for (event in events)
|
||||||
{
|
{
|
||||||
state.currentSongChartEventData.push(event);
|
state.currentSongChartEventData.push(event);
|
||||||
|
|
|
@ -23,6 +23,8 @@ class RemoveItemsCommand implements ChartEditorCommand
|
||||||
|
|
||||||
public function execute(state:ChartEditorState):Void
|
public function execute(state:ChartEditorState):Void
|
||||||
{
|
{
|
||||||
|
if ((notes.length + events.length) == 0) return;
|
||||||
|
|
||||||
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
|
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
|
||||||
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events);
|
state.currentSongChartEventData = SongDataUtils.subtractEvents(state.currentSongChartEventData, events);
|
||||||
|
|
||||||
|
@ -40,6 +42,8 @@ class RemoveItemsCommand implements ChartEditorCommand
|
||||||
|
|
||||||
public function undo(state:ChartEditorState):Void
|
public function undo(state:ChartEditorState):Void
|
||||||
{
|
{
|
||||||
|
if ((notes.length + events.length) == 0) return;
|
||||||
|
|
||||||
for (note in notes)
|
for (note in notes)
|
||||||
{
|
{
|
||||||
state.currentSongChartNoteData.push(note);
|
state.currentSongChartNoteData.push(note);
|
||||||
|
|
|
@ -20,6 +20,8 @@ class RemoveNotesCommand implements ChartEditorCommand
|
||||||
|
|
||||||
public function execute(state:ChartEditorState):Void
|
public function execute(state:ChartEditorState):Void
|
||||||
{
|
{
|
||||||
|
if (notes.length == 0) return;
|
||||||
|
|
||||||
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
|
state.currentSongChartNoteData = SongDataUtils.subtractNotes(state.currentSongChartNoteData, notes);
|
||||||
state.currentNoteSelection = [];
|
state.currentNoteSelection = [];
|
||||||
state.currentEventSelection = [];
|
state.currentEventSelection = [];
|
||||||
|
@ -35,6 +37,8 @@ class RemoveNotesCommand implements ChartEditorCommand
|
||||||
|
|
||||||
public function undo(state:ChartEditorState):Void
|
public function undo(state:ChartEditorState):Void
|
||||||
{
|
{
|
||||||
|
if (notes.length == 0) return;
|
||||||
|
|
||||||
for (note in notes)
|
for (note in notes)
|
||||||
{
|
{
|
||||||
state.currentSongChartNoteData.push(note);
|
state.currentSongChartNoteData.push(note);
|
||||||
|
|
|
@ -54,11 +54,16 @@ class ChartEditorHoldNoteSprite extends SustainTrail
|
||||||
* Set the height directly, to a value in pixels.
|
* Set the height directly, to a value in pixels.
|
||||||
* @param h The desired height 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
|
else
|
||||||
|
{
|
||||||
sustainLength = h / (getScrollSpeed() * Constants.PIXELS_PER_MS);
|
sustainLength = h / (getScrollSpeed() * Constants.PIXELS_PER_MS);
|
||||||
|
}
|
||||||
|
|
||||||
fullSustainLength = sustainLength;
|
fullSustainLength = sustainLength;
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,12 +117,6 @@ class ChartEditorNoteSprite extends FlxSprite
|
||||||
{
|
{
|
||||||
noteFrameCollection.pushFrame(frame);
|
noteFrameCollection.pushFrame(frame);
|
||||||
}
|
}
|
||||||
var frameCollectionNormal2:FlxAtlasFrames = Paths.getSparrowAtlas('NoteHoldNormal');
|
|
||||||
|
|
||||||
for (frame in frameCollectionNormal2.frames)
|
|
||||||
{
|
|
||||||
noteFrameCollection.pushFrame(frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pixel notes
|
// Pixel notes
|
||||||
var graphicPixel = FlxG.bitmap.add(Paths.image('weeb/pixelUI/arrows-pixels', 'week6'), false, null);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ package funkin.ui.debug.charting;
|
||||||
using funkin.ui.debug.charting.handlers.ChartEditorAudioHandler;
|
using funkin.ui.debug.charting.handlers.ChartEditorAudioHandler;
|
||||||
using funkin.ui.debug.charting.handlers.ChartEditorContextMenuHandler;
|
using funkin.ui.debug.charting.handlers.ChartEditorContextMenuHandler;
|
||||||
using funkin.ui.debug.charting.handlers.ChartEditorDialogHandler;
|
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.ChartEditorImportExportHandler;
|
||||||
using funkin.ui.debug.charting.handlers.ChartEditorNotificationHandler;
|
using funkin.ui.debug.charting.handlers.ChartEditorNotificationHandler;
|
||||||
using funkin.ui.debug.charting.handlers.ChartEditorShortcutHandler;
|
using funkin.ui.debug.charting.handlers.ChartEditorShortcutHandler;
|
||||||
|
|
|
@ -37,8 +37,8 @@ class AlbumRoll extends FlxSpriteGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
var newAlbumArt:FlxAtlasSprite;
|
var newAlbumArt:FlxAtlasSprite;
|
||||||
var difficultyStars:DifficultyStars;
|
|
||||||
|
|
||||||
|
// var difficultyStars:DifficultyStars;
|
||||||
var _exitMovers:Null<FreeplayState.ExitMoverData>;
|
var _exitMovers:Null<FreeplayState.ExitMoverData>;
|
||||||
|
|
||||||
var albumData:Album;
|
var albumData:Album;
|
||||||
|
@ -65,9 +65,9 @@ class AlbumRoll extends FlxSpriteGroup
|
||||||
|
|
||||||
add(newAlbumArt);
|
add(newAlbumArt);
|
||||||
|
|
||||||
difficultyStars = new DifficultyStars(140, 39);
|
// difficultyStars = new DifficultyStars(140, 39);
|
||||||
difficultyStars.stars.visible = false;
|
// difficultyStars.stars.visible = false;
|
||||||
add(difficultyStars);
|
// add(difficultyStars);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onAlbumFinish(animName:String):Void
|
function onAlbumFinish(animName:String):Void
|
||||||
|
@ -86,7 +86,7 @@ class AlbumRoll extends FlxSpriteGroup
|
||||||
{
|
{
|
||||||
if (albumId == null)
|
if (albumId == null)
|
||||||
{
|
{
|
||||||
difficultyStars.stars.visible = false;
|
// difficultyStars.stars.visible = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,13 +132,6 @@ class AlbumRoll extends FlxSpriteGroup
|
||||||
speed: 0.4,
|
speed: 0.4,
|
||||||
wait: 0
|
wait: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
exitMovers.set([difficultyStars],
|
|
||||||
{
|
|
||||||
x: FlxG.width * 1.2,
|
|
||||||
speed: 0.2,
|
|
||||||
wait: 0.3
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var titleTimer:Null<FlxTimer> = null;
|
var titleTimer:Null<FlxTimer> = null;
|
||||||
|
@ -151,10 +144,10 @@ class AlbumRoll extends FlxSpriteGroup
|
||||||
newAlbumArt.visible = true;
|
newAlbumArt.visible = true;
|
||||||
newAlbumArt.playAnimation(animNames.get('$albumId-active'), false, false, false);
|
newAlbumArt.playAnimation(animNames.get('$albumId-active'), false, false, false);
|
||||||
|
|
||||||
difficultyStars.stars.visible = false;
|
// difficultyStars.stars.visible = false;
|
||||||
new FlxTimer().start(0.75, function(_) {
|
new FlxTimer().start(0.75, function(_) {
|
||||||
// showTitle();
|
// showTitle();
|
||||||
showStars();
|
// showStars();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,18 +156,16 @@ class AlbumRoll extends FlxSpriteGroup
|
||||||
newAlbumArt.playAnimation(animNames.get('$albumId-trans'), false, false, false);
|
newAlbumArt.playAnimation(animNames.get('$albumId-trans'), false, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setDifficultyStars(?difficulty:Int):Void
|
// public function setDifficultyStars(?difficulty:Int):Void
|
||||||
{
|
// {
|
||||||
if (difficulty == null) return;
|
// if (difficulty == null) return;
|
||||||
|
// difficultyStars.difficulty = difficulty;
|
||||||
difficultyStars.difficulty = difficulty;
|
// }
|
||||||
}
|
// /**
|
||||||
|
// * Make the album stars visible.
|
||||||
/**
|
// */
|
||||||
* Make the album stars visible.
|
// public function showStars():Void
|
||||||
*/
|
// {
|
||||||
public function showStars():Void
|
// difficultyStars.stars.visible = false; // true;
|
||||||
{
|
// }
|
||||||
difficultyStars.stars.visible = false; // true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -469,6 +469,10 @@ class FreeplayState extends MusicBeatSubState
|
||||||
|
|
||||||
albumRoll.playIntro();
|
albumRoll.playIntro();
|
||||||
|
|
||||||
|
new FlxTimer().start(0.75, function(_) {
|
||||||
|
// albumRoll.showTitle();
|
||||||
|
});
|
||||||
|
|
||||||
FlxTween.tween(grpDifficulties, {x: 90}, 0.6, {ease: FlxEase.quartOut});
|
FlxTween.tween(grpDifficulties, {x: 90}, 0.6, {ease: FlxEase.quartOut});
|
||||||
|
|
||||||
var diffSelLeft:DifficultySelector = new DifficultySelector(20, grpDifficulties.y - 10, false, controls);
|
var diffSelLeft:DifficultySelector = new DifficultySelector(20, grpDifficulties.y - 10, false, controls);
|
||||||
|
@ -634,14 +638,7 @@ class FreeplayState extends MusicBeatSubState
|
||||||
funnyMenu.favIcon.visible = tempSongs[i].isFav;
|
funnyMenu.favIcon.visible = tempSongs[i].isFav;
|
||||||
funnyMenu.hsvShader = hsvShader;
|
funnyMenu.hsvShader = hsvShader;
|
||||||
|
|
||||||
if (i < 8)
|
|
||||||
{
|
|
||||||
funnyMenu.initJumpIn(Math.min(i, 4), force);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
funnyMenu.forcePosition();
|
funnyMenu.forcePosition();
|
||||||
}
|
|
||||||
|
|
||||||
grpCapsules.add(funnyMenu);
|
grpCapsules.add(funnyMenu);
|
||||||
}
|
}
|
||||||
|
@ -1039,9 +1036,6 @@ 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.
|
// Set the album graphic and play the animation if relevant.
|
||||||
var newAlbumId:String = daSong?.albumId;
|
var newAlbumId:String = daSong?.albumId;
|
||||||
if (albumRoll.albumId != newAlbumId)
|
if (albumRoll.albumId != newAlbumId)
|
||||||
|
@ -1161,10 +1155,6 @@ class FreeplayState extends MusicBeatSubState
|
||||||
{
|
{
|
||||||
currentDifficulty = rememberedDifficulty;
|
currentDifficulty = rememberedDifficulty;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the difficulty star count on the right.
|
|
||||||
var daSong:Null<FreeplaySongData> = grpCapsules.members[curSelected]?.songData;
|
|
||||||
albumRoll.setDifficultyStars(daSong?.songRating ?? 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeSelection(change:Int = 0):Void
|
function changeSelection(change:Int = 0):Void
|
||||||
|
|
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;
|
package funkin.ui.mainmenu;
|
||||||
|
|
||||||
|
import funkin.graphics.FunkinSprite;
|
||||||
import flixel.addons.transition.FlxTransitionableState;
|
import flixel.addons.transition.FlxTransitionableState;
|
||||||
import funkin.ui.debug.DebugMenuSubState;
|
import funkin.ui.debug.DebugMenuSubState;
|
||||||
import flixel.FlxObject;
|
import flixel.FlxObject;
|
||||||
|
@ -67,7 +68,7 @@ class MainMenuState extends MusicBeatState
|
||||||
camFollow = new FlxObject(0, 0, 1, 1);
|
camFollow = new FlxObject(0, 0, 1, 1);
|
||||||
add(camFollow);
|
add(camFollow);
|
||||||
|
|
||||||
magenta = new FlxSprite(Paths.image('menuDesat'));
|
magenta = new FlxSprite(Paths.image('menuBGMagenta'));
|
||||||
magenta.scrollFactor.x = bg.scrollFactor.x;
|
magenta.scrollFactor.x = bg.scrollFactor.x;
|
||||||
magenta.scrollFactor.y = bg.scrollFactor.y;
|
magenta.scrollFactor.y = bg.scrollFactor.y;
|
||||||
magenta.setGraphicSize(Std.int(bg.width));
|
magenta.setGraphicSize(Std.int(bg.width));
|
||||||
|
@ -75,7 +76,6 @@ class MainMenuState extends MusicBeatState
|
||||||
magenta.x = bg.x;
|
magenta.x = bg.x;
|
||||||
magenta.y = bg.y;
|
magenta.y = bg.y;
|
||||||
magenta.visible = false;
|
magenta.visible = false;
|
||||||
magenta.color = 0xFFfd719b;
|
|
||||||
|
|
||||||
// TODO: Why doesn't this line compile I'm going fucking feral
|
// TODO: Why doesn't this line compile I'm going fucking feral
|
||||||
|
|
||||||
|
|
|
@ -23,8 +23,7 @@ class OptionsState extends MusicBeatState
|
||||||
|
|
||||||
override function create()
|
override function create()
|
||||||
{
|
{
|
||||||
var menuBG = new FlxSprite().loadGraphic(Paths.image('menuDesat'));
|
var menuBG = new FlxSprite().loadGraphic(Paths.image('menuBGBlue'));
|
||||||
menuBG.color = 0xFFea71fd;
|
|
||||||
menuBG.setGraphicSize(Std.int(menuBG.width * 1.1));
|
menuBG.setGraphicSize(Std.int(menuBG.width * 1.1));
|
||||||
menuBG.updateHitbox();
|
menuBG.updateHitbox();
|
||||||
menuBG.screenCenter();
|
menuBG.screenCenter();
|
||||||
|
|
|
@ -281,7 +281,6 @@ class LoadingState extends MusicBeatSubState
|
||||||
{
|
{
|
||||||
// TODO: This section is a hack! Redo this later when we have a proper asset caching system.
|
// TODO: This section is a hack! Redo this later when we have a proper asset caching system.
|
||||||
FunkinSprite.preparePurgeCache();
|
FunkinSprite.preparePurgeCache();
|
||||||
FunkinSprite.cacheTexture(Paths.image('combo'));
|
|
||||||
FunkinSprite.cacheTexture(Paths.image('healthBar'));
|
FunkinSprite.cacheTexture(Paths.image('healthBar'));
|
||||||
FunkinSprite.cacheTexture(Paths.image('menuDesat'));
|
FunkinSprite.cacheTexture(Paths.image('menuDesat'));
|
||||||
FunkinSprite.cacheTexture(Paths.image('combo'));
|
FunkinSprite.cacheTexture(Paths.image('combo'));
|
||||||
|
|
Loading…
Reference in a new issue