mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2025-12-08 21:18:53 +00:00
Move part of the code from the old repository
Co-Authored-By: sector-a <82838084+sector-a@users.noreply.github.com> Co-Authored-By: mcagabe19 <egzozu.be.bas@gmail.com> Co-Authored-By: Mihai Alexandru <77043862+majigsaw77@users.noreply.github.com> Co-Authored-By: MoonDroid <81515012+moondroidcoder@users.noreply.github.com> Co-Authored-By: luckydog7 <59097731+luckydog7@users.noreply.github.com>
This commit is contained in:
parent
f001562270
commit
944141292d
3
.github/ISSUE_TEMPLATE/bug.yml
vendored
3
.github/ISSUE_TEMPLATE/bug.yml
vendored
|
|
@ -26,6 +26,9 @@ body:
|
|||
- Itch.io (Downloadable Build) - Windows
|
||||
- Itch.io (Downloadable Build) - MacOS
|
||||
- Itch.io (Downloadable Build) - Linux
|
||||
- Unofficial - Android
|
||||
- Unofficial - iOS
|
||||
- Unofficial - Web
|
||||
- Compiled from GitHub Source Code
|
||||
validations:
|
||||
required: true
|
||||
|
|
|
|||
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 }}
|
||||
49
.github/workflows/build-game-html5.yml
vendored
Normal file
49
.github/workflows/build-game-html5.yml
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
name: Build Game for HTML5
|
||||
on: workflow_dispatch
|
||||
jobs:
|
||||
HTML5:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@main
|
||||
with:
|
||||
submodules: true
|
||||
persist-credentials: false
|
||||
# - name: Configure git
|
||||
# run: |
|
||||
# git config --global url."https://${TOKEN}:x-oauth-basic@github.com/".insteadOf "https://github.com/"
|
||||
# git config --global user.name "mcagabe19"
|
||||
# git config --global user.email "egzozu.be.bas@gmail.com"
|
||||
# env:
|
||||
# TOKEN: ${{ secrets.GITHUB_TOKEN }} # ${{ secrets.LILY_GH_TOKEN }}
|
||||
- name: Setup Haxe
|
||||
uses: krdlab/setup-haxe@master
|
||||
with:
|
||||
haxe-version: 4.3.4
|
||||
- name: Install Libraries
|
||||
run: |
|
||||
haxelib install hmm --quiet
|
||||
haxelib run hmm install --quiet
|
||||
- name: Compile
|
||||
run: haxelib run lime build html5
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@main
|
||||
with:
|
||||
name: html5Build
|
||||
path: export/release/html5/bin/*
|
||||
if-no-files-found: error
|
||||
- name: Upload Artifact (Only JavaScript Files)
|
||||
uses: actions/upload-artifact@main
|
||||
with:
|
||||
name: html5Build-onlyJS
|
||||
path: export/release/html5/bin/*.js
|
||||
# - name: Upload To Website
|
||||
# run: |
|
||||
# cd ..
|
||||
# git clone https://github.com/FunkinDroidTeam/funkin-0.3-html5
|
||||
# cd funkin-0.3-html5
|
||||
# rm -rf *
|
||||
# mv ${{ github.workspace }}/export/release/html5/bin/* .
|
||||
# git add .
|
||||
# git commit -m "Uploaded via actions."
|
||||
# git push -u origin main
|
||||
36
.github/workflows/build-game-linux.yml
vendored
Normal file
36
.github/workflows/build-game-linux.yml
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
name: Build Game for Linux
|
||||
on: workflow_dispatch
|
||||
jobs:
|
||||
Linux:
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@main
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup Haxe
|
||||
uses: krdlab/setup-haxe@master
|
||||
with:
|
||||
haxe-version: 4.3.4
|
||||
|
||||
- name: Installing Dependencies
|
||||
run: sudo apt-get install libvlc-dev libvlccore-dev vlc-bin vlc
|
||||
|
||||
- name: Install Libraries
|
||||
run: |
|
||||
haxelib install hmm --quiet
|
||||
haxelib run hmm install --quiet
|
||||
haxelib run lime rebuild hxcpp
|
||||
|
||||
- name: Compile
|
||||
run: haxelib run lime build linux
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@main
|
||||
with:
|
||||
name: linuxBuild
|
||||
path: export/release/linux/bin
|
||||
if-no-files-found: error
|
||||
30
.github/workflows/build-game-macos.yml
vendored
Normal file
30
.github/workflows/build-game-macos.yml
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
name: Build Game for macOS
|
||||
on: workflow_dispatch
|
||||
jobs:
|
||||
macOS:
|
||||
runs-on: macos-${{matrix.version}}
|
||||
strategy:
|
||||
matrix:
|
||||
version: [11, 12, 13]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@main
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup Haxe
|
||||
uses: krdlab/setup-haxe@master
|
||||
with:
|
||||
haxe-version: 4.3.4
|
||||
- name: Install Libraries
|
||||
run: |
|
||||
haxelib install hmm --quiet
|
||||
haxelib run hmm install --quiet
|
||||
haxelib run lime rebuild hxcpp
|
||||
- name: Compile
|
||||
run: haxelib run lime build mac
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@main
|
||||
with:
|
||||
name: macOSBuild-${{matrix.version}}
|
||||
path: export/release/macos/bin/*
|
||||
if-no-files-found: error
|
||||
84
.github/workflows/build-game-mobile.yml
vendored
Normal file
84
.github/workflows/build-game-mobile.yml
vendored
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
name: Build Game for Mobile
|
||||
on: workflow_dispatch
|
||||
jobs:
|
||||
Android:
|
||||
runs-on: macos-15
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@main
|
||||
with:
|
||||
submodules: true
|
||||
token: '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'
|
||||
|
||||
- name: Setup Haxe
|
||||
uses: krdlab/setup-haxe@master
|
||||
with:
|
||||
haxe-version: 4.3.6
|
||||
|
||||
# https://github.com/actions/runner-images/issues/10814
|
||||
- name: Workaround build-tools issue
|
||||
run: |
|
||||
curl https://dl.google.com/android/repository/build-tools_r35_macosx.zip > $ANDROID_HOME/build-tools_r35_macosx.zip
|
||||
cd $ANDROID_HOME
|
||||
mkdir build-tools
|
||||
unzip build-tools_r35_macosx.zip
|
||||
mv android-15 build-tools/35.0.0
|
||||
|
||||
- name: Install Libraries
|
||||
run: |
|
||||
haxelib git hmm https://github.com/FunkinCrew/hmm.git skip-deps --quiet --skip-dependencies
|
||||
haxelib run hmm install --quiet
|
||||
haxelib run lime rebuild hxcpp
|
||||
|
||||
- name: Configure Android
|
||||
run: |
|
||||
haxelib run lime config ANDROID_SDK $ANDROID_HOME
|
||||
haxelib run lime config ANDROID_NDK_ROOT $ANDROID_NDK_LATEST_HOME
|
||||
haxelib run lime config JAVA_HOME $JAVA_HOME_17_arm64
|
||||
haxelib run lime config ANDROID_SETUP true
|
||||
|
||||
- name: Compile
|
||||
run: haxelib run lime build android
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@main
|
||||
with:
|
||||
name: androidBuild
|
||||
path: export/release/android/bin/app/build/outputs/apk/debug/*.apk
|
||||
if-no-files-found: error
|
||||
iOS:
|
||||
runs-on: macos-15
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@main
|
||||
with:
|
||||
submodules: true
|
||||
token: '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'
|
||||
|
||||
- name: Setup Haxe
|
||||
uses: krdlab/setup-haxe@master
|
||||
with:
|
||||
haxe-version: 4.3.6
|
||||
|
||||
- name: Install Libraries
|
||||
run: |
|
||||
haxelib git hmm https://github.com/FunkinCrew/hmm.git skip-deps --quiet --skip-dependencies
|
||||
haxelib run hmm install --quiet
|
||||
haxelib run lime rebuild hxcpp
|
||||
|
||||
- name: Compile
|
||||
run: haxelib run lime build ios -nosign
|
||||
|
||||
- name: Make .ipa
|
||||
run: |
|
||||
cd export/*/ios/build/*-iphoneos
|
||||
mkdir Payload
|
||||
mv *.app Payload
|
||||
zip -r Funkin.ipa Payload
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@main
|
||||
with:
|
||||
name: iOSBuild
|
||||
path: export/release/ios/build/Release-iphoneos/*.ipa
|
||||
if-no-files-found: error
|
||||
33
.github/workflows/build-game-windows.yml
vendored
Normal file
33
.github/workflows/build-game-windows.yml
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
name: Build Game for Windows
|
||||
on: workflow_dispatch
|
||||
jobs:
|
||||
Windows:
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@main
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup Haxe
|
||||
uses: krdlab/setup-haxe@master
|
||||
with:
|
||||
haxe-version: 4.3.4
|
||||
|
||||
- name: Install Libraries
|
||||
run: |
|
||||
haxelib install hmm --quiet
|
||||
haxelib run hmm install --quiet
|
||||
haxelib run lime rebuild hxcpp
|
||||
|
||||
- name: Compile
|
||||
run: haxelib run lime build windows
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@main
|
||||
with:
|
||||
name: windowsBuild
|
||||
path: export/release/windows/bin
|
||||
if-no-files-found: error
|
||||
7
.vscode/settings.json
vendored
7
.vscode/settings.json
vendored
|
|
@ -90,6 +90,8 @@
|
|||
"haxecheckstyle.configurationFile": "checkstyle.json",
|
||||
"haxecheckstyle.codeSimilarityBufferSize": 100,
|
||||
|
||||
"lime.projectFile": "project.hxp",
|
||||
|
||||
"lime.targetConfigurations": [
|
||||
{
|
||||
"label": "Windows / Debug (Discord)",
|
||||
|
|
@ -158,7 +160,7 @@
|
|||
{
|
||||
"label": "Windows / Debug (Debug hxvlc)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DHXC_LIBVLC_LOGGING", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
"args": ["-debug", "-DHXVLC_LOGGING", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug (Straight to Animation Editor)",
|
||||
|
|
@ -225,5 +227,6 @@
|
|||
"coverage.xml",
|
||||
"jacoco.xml",
|
||||
"coverage.cobertura.xml"
|
||||
]
|
||||
],
|
||||
"vscord.app.privacyMode.enable": true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -586,7 +586,7 @@ The Weekend 1 update!
|
|||
- Improvements to video cutscenes and dialogue, allowing them to be easily skipped or restarted.
|
||||
- Updated Polymod by several major versions, allowing for fully dynamic asset replacement and support for scripted classes.
|
||||
- Completely refactored almost every part of the game's code for performance, stability, and extensibility.
|
||||
- This is not the Ludum Dare game held together with sticks and glue you played three years ago.
|
||||
- This is not the Ludem Dare game held together with sticks and glue you played three years ago.
|
||||
- Characters, stages, songs, story levels, and dialogue are now built from JSON data registries rather than being hardcoded.
|
||||
- All of these also support attaching scripts for custom behavior, more documentation on this soon.
|
||||
- You can forcibly reload the game's JSON data and scripts by pressing F5.
|
||||
|
|
|
|||
52
README.md
52
README.md
|
|
@ -1,15 +1,21 @@
|
|||
# Friday Night Funkin'
|
||||
|
||||
Friday Night Funkin' is a rhythm game. Built using HaxeFlixel for Ludum Dare 47.
|
||||
|
||||
# Friday Night Funkin'
|
||||
|
||||
Friday Night Funkin' is a rhythm game. Built using HaxeFlixel for Ludum Dare 47.
|
||||
|
||||
This game was made with love to Newgrounds and its community. Extra love to Tom Fulp.
|
||||
|
||||
- [Playable web demo on Newgrounds!](https://www.newgrounds.com/portal/view/770371)
|
||||
- [Demo download builds for Windows, Mac, and Linux from Itch.io!](https://ninja-muffin24.itch.io/funkin)
|
||||
|
||||
# Getting Started
|
||||
|
||||
**PLEASE USE THE LINKS ABOVE IF YOU JUST WANT TO PLAY THE GAME**
|
||||
- [Playable web demo on Newgrounds!](https://www.newgrounds.com/portal/view/770371)
|
||||
- [Demo download builds for Windows, Mac, and Linux from Itch.io!](https://ninja-muffin24.itch.io/funkin)
|
||||
|
||||
## Holy shit, FNF on mobile?
|
||||
|
||||
With this fan port we try and make it so FNF runs on mobile as if it was made FOR mobile gameplay. Coming toe-to-toe with what would be an official Friday Night Funkin’ mobile build! Little to no new features will be added unless it really really REALLY enhances the mobile experience.
|
||||
|
||||
Currently speaking, this mobile port can: Load videos, mods, visualize sounds, basically everything else the base game does!
|
||||
|
||||
# Getting Started
|
||||
|
||||
**PLEASE USE THE LINKS ABOVE IF YOU JUST WANT TO PLAY THE GAME**
|
||||
|
||||
To learn how to install the necessary dependencies and compile the game from source, please follow our [Compiling Guide](/docs/COMPILING.md).
|
||||
|
||||
|
|
@ -27,12 +33,18 @@ Full credits can be found in-game, or in the `credits.json` file which is locate
|
|||
|
||||
## Programming
|
||||
- [ninjamuffin99](https://twitter.com/ninja_muffin99) - Lead Programmer
|
||||
- [EliteMasterEric](https://twitter.com/EliteMasterEric) - Programmer
|
||||
- [MtH](https://twitter.com/emmnyaa) - Charting and Additional Programming
|
||||
- [GeoKureli](https://twitter.com/Geokureli/) - Additional Programming
|
||||
- Our contributors on GitHub
|
||||
|
||||
## Art / Animation / UI
|
||||
- [EliteMasterEric](https://twitter.com/EliteMasterEric) - Programmer
|
||||
- [MtH](https://twitter.com/emmnyaa) - Charting and Additional Programming
|
||||
- [GeoKureli](https://twitter.com/Geokureli/) - Additional Programming
|
||||
- [ZackDroid](https://x.com/ZackDroidCoder) - Mobile Programming (Android)
|
||||
- [MAJigsaw77](https://github.com/MAJigsaw77) - Mobile Programming (Android)
|
||||
- [Karim-Akra](https://x.com/KarimAkra_0) - Mobile Programming (Android)
|
||||
- [Sector_5](https://github.com/sector-a) - Mobile Programming (Android)
|
||||
- [Luckydog7](https://github.com/luckydog7) - Mobile Programming (Android)
|
||||
- [Lily](https://github.com/mcagabe19) - Mobile Programming (iOS)
|
||||
- Our contributors on GitHub
|
||||
|
||||
## Art / Animation / UI
|
||||
- [PhantomArcade3K](https://twitter.com/phantomarcade3k) - Artist and Animator
|
||||
- [Evilsk8r](https://twitter.com/evilsk8r) - Art
|
||||
- [Moawling](https://twitter.com/moawko) - Week 6 Pixel Art
|
||||
|
|
@ -43,6 +55,8 @@ Full credits can be found in-game, or in the `credits.json` file which is locate
|
|||
- [BassetFilms](https://twitter.com/Bassetfilms) - Music for "Monster", Additional Character Design
|
||||
|
||||
## Special Thanks
|
||||
- [Tom Fulp](https://twitter.com/tomfulp) - For being a great guy and for Newgrounds
|
||||
- [JohnnyUtah](https://twitter.com/JohnnyUtahNG/) - Voice of Tankman
|
||||
- [L0Litsmonica](https://twitter.com/L0Litsmonica) - Voice of Mommy Mearest
|
||||
- [Tom Fulp](https://twitter.com/tomfulp) - For being a great guy and for Newgrounds
|
||||
- [JohnnyUtah](https://twitter.com/JohnnyUtahNG/) - Voice of Tankman
|
||||
- [L0Litsmonica](https://twitter.com/L0Litsmonica) - Voice of Mommy Mearest
|
||||
- [Shyllis](https://x.com/1shyll) - iOS Testing
|
||||
- [Toffee](https://x.com/toffee_caramel_) - Help with da guides
|
||||
|
|
|
|||
|
|
@ -87,4 +87,3 @@ filter_commits = false
|
|||
topo_order = false
|
||||
# sort the commits inside sections by oldest/newest order
|
||||
sort_commits = "newest"
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,17 @@
|
|||
- Note: Funkin's fork currently doesn't come with the necessary binaries so you'll have to rebuild Lime. See [Troubleshooting](TROUBLESHOOTING.md#lime-related-issues).
|
||||
- One of Funkin's dependencies uses libVLC, which requires you to install some packages to be able to compile: `sudo apt install libvlc-dev libvlccore-dev libvlccore9`
|
||||
- HTML5: Compiles without any extra setup
|
||||
- Android:
|
||||
- Run `setup-android-[yourOS].bat` in the docs folder by clicking it to install the required development kits on your machine.
|
||||
- If for some reason the downloads don’t work (most likely JDK) [Download it directly.](https://adoptium.net/temurin/releases/?version=17)
|
||||
- (ONLY DO THIS STEP IF THE DOWNLOAD FAILED) After installing the JDK, make sure you know where it installed! If you installed using a `.msi` file, it should be somewhere around `C:\Program Files\`. Go and look for an`Eclipse Adoptium` folder and open it.
|
||||
- (ONLY DO THIS STEP IF THE DOWNLOAD FAILED) look for a folder named something like `jdk-17`. Right click and click on `Copy as path`.
|
||||
- (ONLY DO THIS STEP IF THE DOWNLOAD FAILED) Go to your command prompt and type `haxelib run lime config JAVA_HOME [JdkPathYouCopied]`
|
||||
- after that is done delete the `temp` folder that just got made.
|
||||
- iOS:
|
||||
- Get Xcode from the app store on your MacOS Machine.
|
||||
- Download the iPhone SDK (First thing that pops up in Xcode)
|
||||
- Open up a terminal tab and do `sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer`
|
||||
9. If you are targeting for native, you may need to run `lime rebuild <PLATFORM>` and `lime rebuild <PLATFORM> -debug`
|
||||
10. `lime test <PLATFORM>` to build and launch the game for your platform (for example, `lime test windows`)
|
||||
|
||||
|
|
@ -39,8 +50,8 @@ There are several useful build flags you can add to a build to affect how it wor
|
|||
- This feature causes the game to load exported assets from the project's assets folder rather than the exported one. Great for fast iteration, but the game will break if you try to zip it up and send it to someone, so it's disabled for release builds.
|
||||
- `-DFEATURE_DISCORD_RPC` or `-DNO_FEATURE_DISCORD_RPC` to forcibly enable or disable support for Discord Rich Presence.
|
||||
- `-DFEATURE_VIDEO_PLAYBACK` or `-DNO_FEATURE_VIDEO_PLAYBACK` to forcibly enable or disable video cutscene support.
|
||||
- `-DFEATURE_CHART_EDITOR` or `-DNO_FEATURE_CHART_EDITOR` to forcibly enable or disable the chart editor in the Debug menu.
|
||||
- `-DFEATURE_SCREENSHOTS` or `-DNO_FEATURE_SCREENSHOTS` to forcibly enable or disable the screenshots feature.
|
||||
- `-DFEATURE_CHART_EDITOR` or `-DNO_FEATURE_CHART_EDITOR` to forcibly enable or disable the chart editor in the Debug menu.
|
||||
- `-DFEATURE_STAGE_EDITOR` to forcibly enable the experimental stage editor.
|
||||
- `-DFEATURE_GHOST_TAPPING` to forcibly enable an experimental gameplay change to the anti-mash system.
|
||||
|
||||
|
|
|
|||
66
docs/setup-android-win64.bat
Normal file
66
docs/setup-android-win64.bat
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
@echo off
|
||||
|
||||
set ZIP_FILE="./temp/_temp_jdk.zip"
|
||||
set OUTPUT_DIR="./temp/"
|
||||
set SIX_LINK="https://drive.usercontent.google.com/download?id=1GqFpIk_bkxFb0tNN3x9LxnN-Zh_oDUX5&export=download&authuser=0&confirm=t&uuid=43108c0a-bd53-4465-86f3-80aaceaa7a38&at=APZUnTVNS_BV9cNyC_iicDInosmz%3A1718921284514"
|
||||
set EIGHT_LINK="https://drive.usercontent.google.com/download?id=1X8jjtYYos8aDfZKwehGS9B3zFQa-sCb-&export=download&authuser=0&confirm=t&uuid=07b24a6c-5352-4ba5-9fb8-cff151a6d91e&at=APZUnTUfw26NBAl0nCMn6HBKgHwK%3A1718922303598"
|
||||
|
||||
echo MAKING TEMP
|
||||
mkdir %OUTPUT_DIR%
|
||||
echo MADE TEMP
|
||||
|
||||
|
||||
|
||||
echo INSTALLING ANDROID BUILD TOOLS
|
||||
call .\asclt\bin\sdkmanager "build-tools;32.0.0" --sdk_root="%LOCALAPPDATA%/Android/Sdk/"
|
||||
call .\asclt\bin\sdkmanager "build-tools;32.1.0-rc1" --sdk_root="%LOCALAPPDATA%/Android/Sdk/"
|
||||
echo INSTALLED ANDROID BUILD TOOLS
|
||||
|
||||
|
||||
|
||||
echo INSTALLING ANDROID SDK
|
||||
REM First install the sdks
|
||||
call .\asclt\bin\sdkmanager "platforms;android-29" --sdk_root="%LOCALAPPDATA%/Android/Sdk/"
|
||||
echo ANDROID SDK INSTALLED
|
||||
|
||||
|
||||
|
||||
echo INSTALLING ANDROID NDK
|
||||
REM then the ndks
|
||||
call ./asclt/bin/sdkmanager "ndk;21.4.7075529" --sdk_root="%LOCALAPPDATA%/Android/Sdk/"
|
||||
echo ANDROID NDK INSTALLED
|
||||
|
||||
|
||||
|
||||
echo DOWNLOADING JDK
|
||||
call curl -o %ZIP_FILE% %SIX_LINK%
|
||||
echo DOWNLOADED JDK
|
||||
|
||||
|
||||
|
||||
echo UNZIPPING JDK
|
||||
call powershell -Command "Expand-Archive -Path '%ZIP_FILE%' -DestinationPath '%OUTPUT_DIR%' -Force"
|
||||
echo UNZIPPED JDK
|
||||
|
||||
|
||||
|
||||
echo MAKING JDK PATH
|
||||
mkdir "%LOCALAPPDATA%/Android/jdk"
|
||||
echo MADE JDK PATH
|
||||
|
||||
|
||||
|
||||
echo MOVING JDK TO PROPER PATH
|
||||
call move "%OUTPUT_DIR%/jdk-17.0.11+9" "%LOCALAPPDATA%/Android/jdk/"
|
||||
echo MOVED JDK
|
||||
|
||||
|
||||
|
||||
echo LIME SETTING UP
|
||||
haxelib run lime config ANDROID_SDK %LOCALAPPDATA%\Android\Sdk
|
||||
haxelib run lime config ANDROID_NDK_ROOT %LOCALAPPDATA%\Android\Sdk\ndk\21.4.7075529
|
||||
haxelib run lime config JAVA_HOME %LOCALAPPDATA%\Android\Sdk\jdk\jdk-17.0.11+9
|
||||
haxelib run lime config ANDROID_SETUP true
|
||||
echo DONE
|
||||
|
||||
pause
|
||||
66
docs/setup-android-win86.bat
Normal file
66
docs/setup-android-win86.bat
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
@echo off
|
||||
|
||||
set ZIP_FILE="./temp/_temp_jdk.zip"
|
||||
set OUTPUT_DIR="./temp/"
|
||||
set SIX_LINK="https://drive.usercontent.google.com/download?id=1GqFpIk_bkxFb0tNN3x9LxnN-Zh_oDUX5&export=download&authuser=0&confirm=t&uuid=43108c0a-bd53-4465-86f3-80aaceaa7a38&at=APZUnTVNS_BV9cNyC_iicDInosmz%3A1718921284514"
|
||||
set EIGHT_LINK="https://drive.usercontent.google.com/download?id=1X8jjtYYos8aDfZKwehGS9B3zFQa-sCb-&export=download&authuser=0&confirm=t&uuid=07b24a6c-5352-4ba5-9fb8-cff151a6d91e&at=APZUnTUfw26NBAl0nCMn6HBKgHwK%3A1718922303598"
|
||||
|
||||
echo MAKING TEMP
|
||||
mkdir %OUTPUT_DIR%
|
||||
echo MADE TEMP
|
||||
|
||||
|
||||
|
||||
echo INSTALLING ANDROID BUILD TOOLS
|
||||
call .\asclt\bin\sdkmanager "build-tools;32.0.0" --sdk_root="%LOCALAPPDATA%/Android/Sdk/"
|
||||
call .\asclt\bin\sdkmanager "build-tools;32.1.0-rc1" --sdk_root="%LOCALAPPDATA%/Android/Sdk/"
|
||||
echo INSTALLED ANDROID BUILD TOOLS
|
||||
|
||||
|
||||
|
||||
echo INSTALLING ANDROID SDK
|
||||
REM First install the sdks
|
||||
call .\asclt\bin\sdkmanager "platforms;android-29" --sdk_root="%LOCALAPPDATA%/Android/Sdk/"
|
||||
echo ANDROID SDK INSTALLED
|
||||
|
||||
|
||||
|
||||
echo INSTALLING ANDROID NDK
|
||||
REM then the ndks
|
||||
call ./asclt/bin/sdkmanager "ndk;21.4.7075529" --sdk_root="%LOCALAPPDATA%/Android/Sdk/"
|
||||
echo ANDROID NDK INSTALLED
|
||||
|
||||
|
||||
|
||||
echo DOWNLOADING JDK
|
||||
call curl -o %ZIP_FILE% %EIGHT_LINK%
|
||||
echo DOWNLOADED JDK
|
||||
|
||||
|
||||
|
||||
echo UNZIPPING JDK
|
||||
call powershell -Command "Expand-Archive -Path '%ZIP_FILE%' -DestinationPath '%OUTPUT_DIR%' -Force"
|
||||
echo UNZIPPED JDK
|
||||
|
||||
|
||||
|
||||
echo MAKING JDK PATH
|
||||
mkdir "%LOCALAPPDATA%/Android/jdk"
|
||||
echo MADE JDK PATH
|
||||
|
||||
|
||||
|
||||
echo MOVING JDK TO PROPER PATH
|
||||
call move "%OUTPUT_DIR%/jdk-17.0.11+9" "%LOCALAPPDATA%/Android/jdk/"
|
||||
echo MOVED JDK
|
||||
|
||||
|
||||
|
||||
echo LIME SETTING UP
|
||||
haxelib run lime config ANDROID_SDK %LOCALAPPDATA%\Android\Sdk
|
||||
haxelib run lime config ANDROID_NDK_ROOT %LOCALAPPDATA%\Android\Sdk\ndk\21.4.7075529
|
||||
haxelib run lime config JAVA_HOME %LOCALAPPDATA%\Android\Sdk\jdk\jdk-17.0.11+9
|
||||
haxelib run lime config ANDROID_SETUP true
|
||||
echo DONE
|
||||
|
||||
pause
|
||||
41
project.hxp
41
project.hxp
|
|
@ -94,6 +94,7 @@ class Project extends HXProject {
|
|||
// FEATURE FLAGS
|
||||
// Inverse feature flags are automatically populated.
|
||||
//
|
||||
static final MOUSE_TO_TOUCH:FeatureFlag = "MOUSE_TO_TOUCH";
|
||||
|
||||
/**
|
||||
* `-DGITHUB_BUILD`
|
||||
|
|
@ -279,6 +280,12 @@ class Project extends HXProject {
|
|||
*/
|
||||
static final FEATURE_GHOST_TAPPING:FeatureFlag = "FEATURE_GHOST_TAPPING";
|
||||
|
||||
/**
|
||||
* `-DEXCLUDE_ARMV7`
|
||||
* If this flag is enabled, armv7 won't be compiled when building android.
|
||||
*/
|
||||
static final EXCLUDE_ARMV7:FeatureFlag = "EXCLUDE_ARMV7";
|
||||
|
||||
//
|
||||
// CONFIGURATION FUNCTIONS
|
||||
//
|
||||
|
|
@ -379,11 +386,15 @@ class Project extends HXProject {
|
|||
|
||||
if (isMobile()) {
|
||||
this.window.orientation = Orientation.LANDSCAPE;
|
||||
this.window.fullscreen = false;
|
||||
this.window.fullscreen = true;
|
||||
this.window.resizable = false;
|
||||
this.window.width = 0;
|
||||
this.window.height = 0;
|
||||
}
|
||||
|
||||
if (isAndroid() && EXCLUDE_ARMV7.isEnabled(this)) {
|
||||
this.excludeArchitectures.push(Architecture.ARMV7);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -496,6 +507,9 @@ class Project extends HXProject {
|
|||
// Should be false unless explicitly requested.
|
||||
GITHUB_BUILD.apply(this, false);
|
||||
FEATURE_GHOST_TAPPING.apply(this, false);
|
||||
// TODO: Thanks to new changes to FlxTouch and such, this became useless. Find a way to make it work again -Zack.
|
||||
MOUSE_TO_TOUCH.apply(this, false);
|
||||
EXCLUDE_ARMV7.apply(this, false);
|
||||
|
||||
// Should be true unless explicitly requested.
|
||||
HARDCODED_CREDITS.apply(this, true);
|
||||
|
|
@ -529,20 +543,20 @@ class Project extends HXProject {
|
|||
|
||||
// Should be true only on web builds.
|
||||
// Audio context issues only exist there.
|
||||
TOUCH_HERE_TO_PLAY.apply(this, isWeb());
|
||||
TOUCH_HERE_TO_PLAY.apply(this, isWeb() || isMobile());
|
||||
|
||||
// Should be true only on web builds.
|
||||
// Enabling embedding and preloading is required to preload assets properly.
|
||||
EMBED_ASSETS.apply(this, isWeb());
|
||||
PRELOAD_ALL.apply(this, !isWeb());
|
||||
|
||||
// Should be true except on MacOS.
|
||||
// Should be true except on MacOS and Mobile.
|
||||
// File drop doesn't work there.
|
||||
FEATURE_FILE_DROP.apply(this, !isMac());
|
||||
FEATURE_FILE_DROP.apply(this, !(isMac() || isMobile()));
|
||||
|
||||
// Should be true except on web builds.
|
||||
// Should be true except on web and mobile builds.
|
||||
// Chart editor doesn't work there.
|
||||
FEATURE_CHART_EDITOR.apply(this, !isWeb());
|
||||
FEATURE_CHART_EDITOR.apply(this, !(isWeb() || isMobile()));
|
||||
|
||||
// Should be true except on web builds.
|
||||
// Screenshots doesn't work there.
|
||||
|
|
@ -607,7 +621,11 @@ class Project extends HXProject {
|
|||
|
||||
// Forcibly include all Funkin' classes in builds.
|
||||
// This prevents classes that are unused by the base game from being unavailable to HScript.
|
||||
addHaxeMacro("include('funkin')");
|
||||
addHaxeMacro("include('funkin', true, ['funkin.mobile.*'])");
|
||||
|
||||
if (isMobile()) {
|
||||
addHaxeMacro("include('funkin.mobile')");
|
||||
}
|
||||
|
||||
// Ensure all HaxeUI components are available at runtime.
|
||||
addHaxeMacro("include('haxe.ui.backend.flixel.components')");
|
||||
|
|
@ -673,8 +691,8 @@ class Project extends HXProject {
|
|||
|
||||
function configureHaxelibs() {
|
||||
// Don't enforce
|
||||
addHaxelib('lime'); // Game engine backend
|
||||
addHaxelib('openfl'); // Game engine backend
|
||||
// addHaxelib('lime'); // Game engine backend
|
||||
// addHaxelib('openfl'); // Game engine backend
|
||||
|
||||
addHaxelib('flixel'); // Game engine
|
||||
|
||||
|
|
@ -698,12 +716,15 @@ class Project extends HXProject {
|
|||
addHaxelib('hxcpp-debug-server'); // VSCode debug support
|
||||
}
|
||||
|
||||
if (isDesktop() && !isHashLink() && FEATURE_VIDEO_PLAYBACK.isEnabled(this)) {
|
||||
if (isDesktop() || isMobile() && !isHashLink() && FEATURE_VIDEO_PLAYBACK.isEnabled(this)) {
|
||||
// hxvlc doesn't function on HashLink or non-desktop platforms
|
||||
// It's also unnecessary if video playback is disabled
|
||||
addHaxelib('hxvlc'); // Video playback
|
||||
}
|
||||
|
||||
if (isAndroid())
|
||||
addHaxelib('extension-androidtools'); // Android Functions
|
||||
|
||||
if (FEATURE_DISCORD_RPC.isEnabled(this)) {
|
||||
addHaxelib('hxdiscord_rpc'); // Discord API
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package;
|
||||
|
||||
import flixel.FlxG;
|
||||
import flixel.FlxGame;
|
||||
import flixel.FlxState;
|
||||
import funkin.Preferences;
|
||||
|
|
@ -30,6 +31,16 @@ class Main extends Sprite
|
|||
|
||||
public static function main():Void
|
||||
{
|
||||
// Set the current working directory for Android and iOS devices
|
||||
#if android
|
||||
// For Android we determine the appropriate directory based on Android version
|
||||
Sys.setCwd(haxe.io.Path.addTrailingSlash(android.os.Build.VERSION.SDK_INT > 30 ? android.content.Context.getObbDir() : // Use Obb directory for Android SDK version > 30
|
||||
android.content.Context.getExternalFilesDir() // Use External Files directory for Android SDK version < 30
|
||||
));
|
||||
#elseif ios
|
||||
Sys.setCwd(haxe.io.Path.addTrailingSlash(lime.system.System.documentsDirectory)); // For iOS we use documents directory and this is only way we can do.
|
||||
#end
|
||||
|
||||
// We need to make the crash handler LITERALLY FIRST so nothing EVER gets past it.
|
||||
CrashHandler.initialize();
|
||||
CrashHandler.queryStatus();
|
||||
|
|
@ -104,6 +115,11 @@ class Main extends Sprite
|
|||
|
||||
// George recommends binding the save before FlxGame is created.
|
||||
Save.load();
|
||||
|
||||
#if mobile
|
||||
FlxG.signals.gameResized.add(resizeGame);
|
||||
#end
|
||||
|
||||
var game:FlxGame = new FlxGame(gameWidth, gameHeight, initialState, Preferences.framerate, Preferences.framerate, skipSplash, startFullscreen);
|
||||
|
||||
// FlxG.game._customSoundTray wants just the class, it calls new from
|
||||
|
|
@ -118,6 +134,12 @@ class Main extends Sprite
|
|||
game.debugger.interaction.addTool(new funkin.util.TrackerToolButtonUtil());
|
||||
#end
|
||||
|
||||
#if mobile
|
||||
flixel.FlxG.game.addChild(fpsCounter);
|
||||
#else
|
||||
addChild(fpsCounter);
|
||||
#end
|
||||
|
||||
#if hxcpp_debug_server
|
||||
trace('hxcpp_debug_server is enabled! You can now connect to the game with a debugger.');
|
||||
#else
|
||||
|
|
@ -139,4 +161,19 @@ class Main extends Sprite
|
|||
funkin.input.Cursor.registerHaxeUICursors();
|
||||
haxe.ui.tooltips.ToolTipManager.defaultDelay = 200;
|
||||
}
|
||||
|
||||
function resizeGame(width:Int, height:Int):Void
|
||||
{
|
||||
// Calling this so it gets scaled based on the resolution of the game and device's resolution.
|
||||
final scale:Float = Math.min(flixel.FlxG.stage.stageWidth / flixel.FlxG.width, flixel.FlxG.stage.stageHeight / flixel.FlxG.height);
|
||||
|
||||
if (fpsCounter != null) fpsCounter.scaleX = fpsCounter.scaleY = #if android (scale > 1 ? scale : 1) #else (scale < 1 ? scale : 1) #end;
|
||||
|
||||
if (memoryCounter != null)
|
||||
{
|
||||
memoryCounter.scaleX = memoryCounter.scaleY = #if android (scale > 1 ? scale : 1) #else (scale < 1 ? scale : 1) #end;
|
||||
|
||||
memoryCounter.y = 13 * #if android (scale > 1 ? scale : 1) #else (scale < 1 ? scale : 1) #end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
package funkin;
|
||||
|
||||
#if mobile
|
||||
import funkin.mobile.ui.FunkinHitbox;
|
||||
#end
|
||||
import funkin.save.Save;
|
||||
|
||||
/**
|
||||
|
|
@ -17,6 +20,12 @@ class Preferences
|
|||
{
|
||||
#if web
|
||||
return 60;
|
||||
#elseif mobile
|
||||
var refreshRate:Int = FlxG.stage.window.displayMode.refreshRate;
|
||||
|
||||
if (refreshRate < 60) refreshRate = 60;
|
||||
|
||||
return Save?.instance?.options?.framerate ?? refreshRate;
|
||||
#else
|
||||
return Save?.instance?.options?.framerate ?? 60;
|
||||
#end
|
||||
|
|
@ -345,8 +354,16 @@ class Preferences
|
|||
#if web
|
||||
toggleFramerateCap(Preferences.unlockedFramerate);
|
||||
#end
|
||||
|
||||
#if desktop
|
||||
// Apply the autoFullscreen setting (launches the game in fullscreen automatically)
|
||||
FlxG.fullscreen = Preferences.autoFullscreen;
|
||||
#end
|
||||
|
||||
#if mobile
|
||||
// Apply the allowScreenTimeout setting.
|
||||
lime.system.System.allowScreenTimeout = Preferences.screenTimeout;
|
||||
#end
|
||||
}
|
||||
|
||||
static function toggleFramerateCap(unlocked:Bool):Void
|
||||
|
|
@ -362,18 +379,97 @@ class Preferences
|
|||
if (show)
|
||||
{
|
||||
// Enable the debug display.
|
||||
#if mobile
|
||||
FlxG.game.addChild(Main.fpsCounter);
|
||||
#else
|
||||
FlxG.stage.addChild(Main.fpsCounter);
|
||||
#end
|
||||
|
||||
#if !html5
|
||||
#if mobile
|
||||
FlxG.game.addChild(Main.memoryCounter);
|
||||
#else
|
||||
FlxG.stage.addChild(Main.memoryCounter);
|
||||
#end
|
||||
#end
|
||||
}
|
||||
else
|
||||
{
|
||||
// Disable the debug display.
|
||||
#if mobile
|
||||
FlxG.game.removeChild(Main.fpsCounter);
|
||||
#else
|
||||
FlxG.stage.removeChild(Main.fpsCounter);
|
||||
#end
|
||||
|
||||
#if !html5
|
||||
#if mobile
|
||||
FlxG.game.removeChild(Main.memoryCounter);
|
||||
#else
|
||||
FlxG.stage.removeChild(Main.memoryCounter);
|
||||
#end
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
#if mobile
|
||||
/**
|
||||
* If enabled, device will be able to sleep on its own.
|
||||
* @default `false`
|
||||
*/
|
||||
public static var screenTimeout(get, set):Bool;
|
||||
|
||||
static function get_screenTimeout():Bool
|
||||
{
|
||||
return Save?.instance?.mobileOptions?.screenTimeout ?? false;
|
||||
}
|
||||
|
||||
static function set_screenTimeout(value:Bool):Bool
|
||||
{
|
||||
if (value != Save.instance.mobileOptions.screenTimeout) lime.system.System.allowScreenTimeout = value;
|
||||
|
||||
var save:Save = Save.instance;
|
||||
save.mobileOptions.screenTimeout = value;
|
||||
save.flush();
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* If enabled, vibration will be enabled.
|
||||
* @default `true`
|
||||
*/
|
||||
public static var vibration(get, set):Bool;
|
||||
|
||||
static function get_vibration():Bool
|
||||
{
|
||||
return Save?.instance?.mobileOptions?.vibration ?? true;
|
||||
}
|
||||
|
||||
static function set_vibration(value:Bool):Bool
|
||||
{
|
||||
var save:Save = Save.instance;
|
||||
save.mobileOptions.vibration = value;
|
||||
save.flush();
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Controls Scheme for the hitbox.
|
||||
* @default `4 Lanes`
|
||||
*/
|
||||
public static var controlsScheme(get, set):String;
|
||||
|
||||
static function get_controlsScheme():String
|
||||
{
|
||||
return Save?.instance?.mobileOptions?.controlsScheme ?? FunkinHitboxControlSchemes.FourLanes;
|
||||
}
|
||||
|
||||
static function set_controlsScheme(value:String):String
|
||||
{
|
||||
var save:Save = Save.instance;
|
||||
save.mobileOptions.controlsScheme = value;
|
||||
save.flush();
|
||||
return value;
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,9 +16,6 @@ import funkin.Paths.PathsFunction;
|
|||
import lime.app.Promise;
|
||||
import openfl.media.SoundMixer;
|
||||
|
||||
#if (openfl >= "8.0.0")
|
||||
#end
|
||||
|
||||
/**
|
||||
* A FlxSound which adds additional functionality:
|
||||
* - Delayed playback via negative song position.
|
||||
|
|
|
|||
|
|
@ -72,8 +72,8 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
|||
// we use a very low minFreq since some songs use low low subbass like a boss
|
||||
analyzer.minFreq = 10;
|
||||
|
||||
#if desktop
|
||||
// On desktop it uses FFT stuff that isn't as optimized as the direct browser stuff we use on HTML5
|
||||
#if !web
|
||||
// On non web it uses FFT stuff that isn't as optimized as the direct browser stuff we use on HTML5
|
||||
// So we want to manually change it!
|
||||
analyzer.fftN = 256;
|
||||
#end
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ class StageRegistry extends BaseRegistry<Stage, StageData>
|
|||
* Handle breaking changes by incrementing this value
|
||||
* and adding migration to the `migrateStageData()` function.
|
||||
*/
|
||||
public static final STAGE_DATA_VERSION:thx.semver.Version = "1.0.3";
|
||||
public static final STAGE_DATA_VERSION:thx.semver.Version = "1.0.2";
|
||||
|
||||
public static final STAGE_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <1.1.0";
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import flixel.math.FlxRect;
|
|||
import flixel.math.FlxPoint;
|
||||
import flixel.graphics.frames.FlxFrame.FlxFrameAngle;
|
||||
import flixel.FlxCamera;
|
||||
import openfl.system.System;
|
||||
|
||||
/**
|
||||
* An FlxSprite with additional functionality.
|
||||
|
|
@ -267,6 +268,8 @@ class FunkinSprite extends FlxSprite
|
|||
graphic.destroy();
|
||||
previousCachedTextures.remove(graphicKey);
|
||||
}
|
||||
|
||||
System.gc();
|
||||
}
|
||||
|
||||
static function isGraphicCached(graphic:FlxGraphic):Bool
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import flixel.input.gamepad.FlxGamepadInputID;
|
|||
import flixel.input.keyboard.FlxKey;
|
||||
import flixel.math.FlxAngle;
|
||||
import flixel.math.FlxPoint;
|
||||
import lime.ui.Haptic;
|
||||
|
||||
/**
|
||||
* A core class which handles receiving player input and interpreting it into game actions.
|
||||
|
|
@ -726,11 +725,6 @@ class Controls extends FlxActionSet
|
|||
forEachBound(control, function(action, state) addKeys(action, keys, state));
|
||||
}
|
||||
|
||||
public function bindSwipe(control:Control, swipeDir:FlxDirectionFlags = FlxDirectionFlags.UP, ?swpLength:Float = 90)
|
||||
{
|
||||
forEachBound(control, function(action, press) action.add(new FlxActionInputDigitalMobileSwipeGameplay(swipeDir, press, swpLength)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets all actions that pertain to the binder to trigger when the supplied keys are used.
|
||||
* If binder is a literal you can inline this
|
||||
|
|
@ -798,8 +792,6 @@ class Controls extends FlxActionSet
|
|||
bindKeys(Control.VOLUME_UP, getDefaultKeybinds(scheme, Control.VOLUME_UP));
|
||||
bindKeys(Control.VOLUME_DOWN, getDefaultKeybinds(scheme, Control.VOLUME_DOWN));
|
||||
bindKeys(Control.VOLUME_MUTE, getDefaultKeybinds(scheme, Control.VOLUME_MUTE));
|
||||
|
||||
bindMobileLol();
|
||||
}
|
||||
|
||||
function getDefaultKeybinds(scheme:KeyboardScheme, control:Control):Array<FlxKey>
|
||||
|
|
@ -906,30 +898,6 @@ class Controls extends FlxActionSet
|
|||
return [];
|
||||
}
|
||||
|
||||
function bindMobileLol()
|
||||
{
|
||||
#if FLX_TOUCH
|
||||
// MAKE BETTER TOUCH BIND CODE
|
||||
|
||||
bindSwipe(Control.NOTE_UP, FlxDirectionFlags.UP, 40);
|
||||
bindSwipe(Control.NOTE_DOWN, FlxDirectionFlags.DOWN, 40);
|
||||
bindSwipe(Control.NOTE_LEFT, FlxDirectionFlags.LEFT, 40);
|
||||
bindSwipe(Control.NOTE_RIGHT, FlxDirectionFlags.RIGHT, 40);
|
||||
|
||||
// feels more like drag when up/down are inversed
|
||||
bindSwipe(Control.UI_UP, FlxDirectionFlags.DOWN);
|
||||
bindSwipe(Control.UI_DOWN, FlxDirectionFlags.UP);
|
||||
bindSwipe(Control.UI_LEFT, FlxDirectionFlags.LEFT);
|
||||
bindSwipe(Control.UI_RIGHT, FlxDirectionFlags.RIGHT);
|
||||
#end
|
||||
|
||||
#if android
|
||||
forEachBound(Control.BACK, function(action, pres) {
|
||||
action.add(new FlxActionInputDigitalAndroid(FlxAndroidKey.BACK, JUST_PRESSED));
|
||||
});
|
||||
#end
|
||||
}
|
||||
|
||||
function removeKeyboard()
|
||||
{
|
||||
for (action in this.digitalActions)
|
||||
|
|
@ -1101,13 +1069,6 @@ class Controls extends FlxActionSet
|
|||
forEachBound(control, function(action, state) addButtons(action, buttons, state, id));
|
||||
}
|
||||
|
||||
public function touchShit(control:Control, id)
|
||||
{
|
||||
forEachBound(control, function(action, state) {
|
||||
// action
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets all actions that pertain to the binder to trigger when the supplied keys are used.
|
||||
* If binder is a literal you can inline this
|
||||
|
|
@ -1469,163 +1430,6 @@ class FunkinAction extends FlxActionDigital
|
|||
}
|
||||
}
|
||||
|
||||
class FlxActionInputDigitalMobileSwipeGameplay extends FlxActionInputDigital
|
||||
{
|
||||
var touchMap:Map<Int, Swipes> = new Map();
|
||||
|
||||
var vibrationSteps:Int = 5;
|
||||
var curStep:Int = 5;
|
||||
var activateLength:Float = 90;
|
||||
var hapticPressure:Int = 100;
|
||||
|
||||
public function new(swipeDir:FlxDirectionFlags = FlxDirectionFlags.ANY, Trigger:FlxInputState, ?swipeLength:Float = 90)
|
||||
{
|
||||
super(OTHER, swipeDir.toInt(), Trigger);
|
||||
|
||||
activateLength = swipeLength;
|
||||
}
|
||||
|
||||
// fix right swipe
|
||||
// make so cant double swipe during gameplay
|
||||
// hold notes?
|
||||
|
||||
override function update():Void
|
||||
{
|
||||
super.update();
|
||||
|
||||
#if FLX_TOUCH
|
||||
for (touch in FlxG.touches.list)
|
||||
{
|
||||
if (touch.justPressed)
|
||||
{
|
||||
var pos:FlxPoint = new FlxPoint(touch.screenX, touch.screenY);
|
||||
var pos2:FlxPoint = new FlxPoint(touch.screenX, touch.screenY);
|
||||
|
||||
var swp:Swipes =
|
||||
{
|
||||
initTouchPos: pos,
|
||||
curTouchPos: pos2,
|
||||
touchAngle: 0,
|
||||
touchLength: 0
|
||||
};
|
||||
touchMap[touch.touchPointID] = swp;
|
||||
|
||||
curStep = 1;
|
||||
Haptic.vibrate(40, 70);
|
||||
}
|
||||
if (touch.pressed)
|
||||
{
|
||||
var daSwipe = touchMap[touch.touchPointID];
|
||||
|
||||
daSwipe.curTouchPos.set(touch.screenX, touch.screenY);
|
||||
|
||||
var dx = daSwipe.initTouchPos.x - touch.screenX;
|
||||
var dy = daSwipe.initTouchPos.y - touch.screenY;
|
||||
|
||||
daSwipe.touchAngle = Math.atan2(dy, dx);
|
||||
daSwipe.touchLength = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
FlxG.watch.addQuick("LENGTH", daSwipe.touchLength);
|
||||
FlxG.watch.addQuick("ANGLE", FlxAngle.asDegrees(daSwipe.touchAngle));
|
||||
|
||||
if (daSwipe.touchLength >= (activateLength / vibrationSteps) * curStep)
|
||||
{
|
||||
curStep += 1;
|
||||
// Haptic.vibrate(Std.int(hapticPressure / (curStep * 1.5)), 50);
|
||||
}
|
||||
}
|
||||
|
||||
if (touch.justReleased)
|
||||
{
|
||||
touchMap.remove(touch.touchPointID);
|
||||
}
|
||||
|
||||
/* switch (inputID)
|
||||
{
|
||||
case FlxDirectionFlags.UP:
|
||||
return
|
||||
case FlxDirectionFlags.DOWN:
|
||||
}
|
||||
*/
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
override public function check(Action:FlxAction):Bool
|
||||
{
|
||||
for (swp in touchMap)
|
||||
{
|
||||
var degAngle = FlxAngle.asDegrees(swp.touchAngle);
|
||||
|
||||
switch (trigger)
|
||||
{
|
||||
case JUST_PRESSED:
|
||||
if (swp.touchLength >= activateLength)
|
||||
{
|
||||
if (inputID == FlxDirectionFlags.UP.toInt())
|
||||
{
|
||||
if (degAngle >= 45 && degAngle <= 90 + 45) return properTouch(swp);
|
||||
}
|
||||
else if (inputID == FlxDirectionFlags.DOWN.toInt())
|
||||
{
|
||||
if (-degAngle >= 45 && -degAngle <= 90 + 45) return properTouch(swp);
|
||||
}
|
||||
else if (inputID == FlxDirectionFlags.LEFT.toInt())
|
||||
{
|
||||
if (degAngle <= 45 && -degAngle <= 45) return properTouch(swp);
|
||||
}
|
||||
else if (inputID == FlxDirectionFlags.RIGHT.toInt())
|
||||
{
|
||||
if (degAngle >= 90 + 45 && degAngle <= -90 + -45) return properTouch(swp);
|
||||
}
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function properTouch(swipe:Swipes):Bool
|
||||
{
|
||||
curStep = 1;
|
||||
Haptic.vibrate(100, 30);
|
||||
swipe.initTouchPos.set(swipe.curTouchPos.x, swipe.curTouchPos.y);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Maybe this can be committed to main HaxeFlixel repo?
|
||||
#if android
|
||||
class FlxActionInputDigitalAndroid extends FlxActionInputDigital
|
||||
{
|
||||
/**
|
||||
* Android buttons action input
|
||||
* @param androidKeyID Key identifier (FlxAndroidKey.BACK, FlxAndroidKey.MENU... those are the only 2 android specific ones)
|
||||
* @param Trigger What state triggers this action (PRESSED, JUST_PRESSED, RELEASED, JUST_RELEASED)
|
||||
*/
|
||||
public function new(androidKeyID:FlxAndroidKey, Trigger:FlxInputState)
|
||||
{
|
||||
super(FlxInputDevice.OTHER, androidKeyID, Trigger);
|
||||
}
|
||||
|
||||
override public function check(Action:FlxAction):Bool
|
||||
{
|
||||
return switch (trigger)
|
||||
{
|
||||
#if android
|
||||
case PRESSED: FlxG.android.checkStatus(inputID, PRESSED) || FlxG.android.checkStatus(inputID, PRESSED);
|
||||
case RELEASED: FlxG.android.checkStatus(inputID, RELEASED) || FlxG.android.checkStatus(inputID, JUST_RELEASED);
|
||||
case JUST_PRESSED: FlxG.android.checkStatus(inputID, JUST_PRESSED);
|
||||
case JUST_RELEASED: FlxG.android.checkStatus(inputID, JUST_RELEASED);
|
||||
#end
|
||||
|
||||
default: false;
|
||||
}
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
/**
|
||||
* Since, in many cases multiple actions should use similar keys, we don't want the
|
||||
* rebinding UI to list every action. ActionBinders are what the user percieves as
|
||||
|
|
|
|||
98
source/funkin/mobile/input/ControlsHandler.hx
Normal file
98
source/funkin/mobile/input/ControlsHandler.hx
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
package funkin.mobile.input;
|
||||
|
||||
import funkin.input.Controls;
|
||||
import flixel.input.FlxInput;
|
||||
import flixel.input.actions.FlxAction;
|
||||
import flixel.input.actions.FlxActionInput;
|
||||
import flixel.input.actions.FlxActionInputDigital;
|
||||
import funkin.mobile.ui.FunkinButton;
|
||||
import funkin.mobile.ui.FunkinHitbox;
|
||||
import funkin.play.notes.NoteDirection;
|
||||
|
||||
/**
|
||||
* Handles setting up and managing input controls for the game.
|
||||
*/
|
||||
class ControlsHandler
|
||||
{
|
||||
/**
|
||||
* Adds a button input to a given FlxActionDigital and caches it.
|
||||
*
|
||||
* @param action The FlxActionDigital to add the button input to.
|
||||
* @param button The FunkinButton associated with the action.
|
||||
* @param state The input state to associate with the action.
|
||||
* @param cachedInput The array of FlxActionInput objects to cache the input.
|
||||
*/
|
||||
public static function addButton(action:FlxActionDigital, button:FunkinButton, state:FlxInputState, cachedInput:Array<FlxActionInput>):Void
|
||||
{
|
||||
if (action == null || button == null || cachedInput == null) return;
|
||||
|
||||
final input:FlxActionInputDigitalIFlxInput = new FlxActionInputDigitalIFlxInput(button, state);
|
||||
cachedInput.push(input);
|
||||
action.add(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up hitbox controls based on game controls and hitbox hints.
|
||||
*
|
||||
* @param controls The controls instance defining game controls.
|
||||
* @param hitbox The hitbox to associate with the controls.
|
||||
* @param cachedInput The array of action input objects to cache the input.
|
||||
*/
|
||||
@:access(funkin.input.Controls)
|
||||
public static function setupHitbox(controls:Controls, hitbox:FunkinHitbox, cachedInput:Array<FlxActionInput>):Void
|
||||
{
|
||||
if (controls == null || hitbox == null) return;
|
||||
|
||||
for (hint in hitbox.members)
|
||||
{
|
||||
@:privateAccess
|
||||
switch (hint.noteDirection)
|
||||
{
|
||||
case NoteDirection.LEFT:
|
||||
controls.forEachBound(Control.NOTE_LEFT, function(action:FlxActionDigital, state:FlxInputState):Void {
|
||||
addButton(action, hint, state, cachedInput);
|
||||
});
|
||||
case NoteDirection.DOWN:
|
||||
controls.forEachBound(Control.NOTE_DOWN, function(action:FlxActionDigital, state:FlxInputState):Void {
|
||||
addButton(action, hint, state, cachedInput);
|
||||
});
|
||||
case NoteDirection.UP:
|
||||
controls.forEachBound(Control.NOTE_UP, function(action:FlxActionDigital, state:FlxInputState):Void {
|
||||
addButton(action, hint, state, cachedInput);
|
||||
});
|
||||
case NoteDirection.RIGHT:
|
||||
controls.forEachBound(Control.NOTE_RIGHT, function(action:FlxActionDigital, state:FlxInputState):Void {
|
||||
addButton(action, hint, state, cachedInput);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes cached input associated with game controls.
|
||||
*
|
||||
* @param controls The Controls instance defining game controls.
|
||||
* @param cachedInput The array of action input objects to clear cached input from.
|
||||
*/
|
||||
public static function removeCachedInput(controls:Controls, cachedInput:Array<FlxActionInput>):Void
|
||||
{
|
||||
for (action in controls.digitalActions)
|
||||
{
|
||||
var i:Int = action.inputs.length;
|
||||
|
||||
while (i-- > 0)
|
||||
{
|
||||
var j:Int = cachedInput.length;
|
||||
|
||||
while (j-- > 0)
|
||||
{
|
||||
if (cachedInput[j] == action.inputs[i])
|
||||
{
|
||||
action.remove(action.inputs[i]);
|
||||
cachedInput.remove(cachedInput[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
59
source/funkin/mobile/input/PreciseInputHandler.hx
Normal file
59
source/funkin/mobile/input/PreciseInputHandler.hx
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
package funkin.mobile.input;
|
||||
|
||||
import flixel.input.FlxInput;
|
||||
import funkin.input.PreciseInputManager;
|
||||
import funkin.mobile.ui.FunkinHitbox;
|
||||
import funkin.play.notes.NoteDirection;
|
||||
import haxe.Int64;
|
||||
|
||||
/**
|
||||
* Handles setting up and managing precise input controls for the game.
|
||||
*/
|
||||
@:access(funkin.input.PreciseInputManager)
|
||||
class PreciseInputHandler
|
||||
{
|
||||
/**
|
||||
* Initializes the hitbox with the relevant hints and event handlers.
|
||||
*
|
||||
* @param hitbox The hitbox to initialize.
|
||||
*/
|
||||
public static function initializeHitbox(hitbox:FunkinHitbox):Void
|
||||
{
|
||||
hitbox.onHintDown.add(handleHintDown);
|
||||
hitbox.onHintUp.add(handleHintUp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the event when a hint is pressed.
|
||||
*
|
||||
* @param hint The hint that was pressed.
|
||||
*/
|
||||
static function handleHintDown(hint:FunkinHint):Void
|
||||
{
|
||||
final timestamp:Int64 = PreciseInputManager.getCurrentTimestamp();
|
||||
|
||||
@:privateAccess
|
||||
if (hint.input?.justPressed ?? false)
|
||||
{
|
||||
PreciseInputManager.instance.onInputPressed.dispatch({noteDirection: hint.noteDirection, timestamp: timestamp});
|
||||
PreciseInputManager.instance._dirPressTimestamps.set(hint.noteDirection, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the event when a hint is released.
|
||||
*
|
||||
* @param hint The hint that was released.
|
||||
*/
|
||||
static function handleHintUp(hint:FunkinHint):Void
|
||||
{
|
||||
final timestamp:Int64 = PreciseInputManager.getCurrentTimestamp();
|
||||
|
||||
@:privateAccess
|
||||
if (hint.input?.justReleased ?? false)
|
||||
{
|
||||
PreciseInputManager.instance.onInputReleased.dispatch({noteDirection: hint.noteDirection, timestamp: timestamp});
|
||||
PreciseInputManager.instance._dirPressTimestamps.set(hint.noteDirection, timestamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
20
source/funkin/mobile/ui/FunkinBackspace.hx
Normal file
20
source/funkin/mobile/ui/FunkinBackspace.hx
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
package funkin.mobile.ui;
|
||||
|
||||
import flixel.FlxG;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.mobile.ui.FunkinButton;
|
||||
|
||||
class FunkinBackspace extends FunkinButton
|
||||
{
|
||||
public function new(?xPos:Float = 0, ?yPos:Float = 0, ?theColor:FlxColor = FlxColor.WHITE, ?onClick:Void->Void = null):Void
|
||||
{
|
||||
super(xPos, yPos);
|
||||
|
||||
frames = Paths.getSparrowAtlas("backspace");
|
||||
animation.addByPrefix("idle", "backspace to exit white0");
|
||||
animation.play("idle");
|
||||
color = theColor;
|
||||
|
||||
if (onClick != null) onDown.add(onClick);
|
||||
}
|
||||
}
|
||||
238
source/funkin/mobile/ui/FunkinButton.hx
Normal file
238
source/funkin/mobile/ui/FunkinButton.hx
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
package funkin.mobile.ui;
|
||||
|
||||
import flixel.FlxCamera;
|
||||
import flixel.FlxG;
|
||||
import flixel.FlxSprite;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import flixel.input.FlxInput;
|
||||
import flixel.input.FlxPointer;
|
||||
import flixel.input.IFlxInput;
|
||||
import flixel.input.touch.FlxTouch;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.util.FlxDestroyUtil;
|
||||
import flixel.util.FlxSignal;
|
||||
import funkin.mobile.util.SwipeUtil;
|
||||
|
||||
/**
|
||||
* Enum representing the status of the button.
|
||||
*/
|
||||
enum abstract FunkinButtonStatus(Int) from Int to Int
|
||||
{
|
||||
var NORMAL = 0;
|
||||
var PRESSED = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple button class that calls a function when touched.
|
||||
*/
|
||||
#if !display
|
||||
@:generic
|
||||
#end
|
||||
class FunkinButton extends FunkinSprite implements IFlxInput
|
||||
{
|
||||
/**
|
||||
* The current state of the button, either `FunkinButtonStatus.NORMAL` or `FunkinButtonStatus.PRESSED`.
|
||||
*/
|
||||
public var status:FunkinButtonStatus;
|
||||
|
||||
/**
|
||||
* The callback function to call when the button is released.
|
||||
*/
|
||||
public var onUp(default, null):FlxSignal = new FlxSignal();
|
||||
|
||||
/**
|
||||
* The callback function to call when the button is pressed down.
|
||||
*/
|
||||
public var onDown(default, null):FlxSignal = new FlxSignal();
|
||||
|
||||
/**
|
||||
* The callback function to call when the button is no longer hovered over.
|
||||
*/
|
||||
public var onOut(default, null):FlxSignal = new FlxSignal();
|
||||
|
||||
/**
|
||||
* Whether the button was just released.
|
||||
*/
|
||||
public var justReleased(get, never):Bool;
|
||||
|
||||
/**
|
||||
* Whether the button is currently released.
|
||||
*/
|
||||
public var released(get, never):Bool;
|
||||
|
||||
/**
|
||||
* Whether the button is currently pressed.
|
||||
*/
|
||||
public var pressed(get, never):Bool;
|
||||
|
||||
/**
|
||||
* Whether the button was just pressed.
|
||||
*/
|
||||
public var justPressed(get, never):Bool;
|
||||
|
||||
/**
|
||||
* An array of objects that blocks your input.
|
||||
*/
|
||||
public var deadZones:Array<FunkinSprite> = [];
|
||||
|
||||
/**
|
||||
* The input associated with the button, using `Int` as the type.
|
||||
*/
|
||||
var input:FlxInput<Int>;
|
||||
|
||||
/**
|
||||
* The input currently pressing this button, if none, it's `null`.
|
||||
* Needed to check for its release.
|
||||
*/
|
||||
var currentInput:IFlxInput;
|
||||
|
||||
/**
|
||||
* Creates a new `FunkinButton` object.
|
||||
*
|
||||
* @param X The x position of the button.
|
||||
* @param Y The y position of the button.
|
||||
*/
|
||||
public function new(X:Float = 0, Y:Float = 0):Void
|
||||
{
|
||||
super(X, Y);
|
||||
|
||||
status = FunkinButtonStatus.NORMAL;
|
||||
solid = false;
|
||||
immovable = true;
|
||||
#if FLX_DEBUG
|
||||
ignoreDrawDebug = true;
|
||||
#end
|
||||
scrollFactor.set();
|
||||
input = new FlxInput(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the game state when the state is changed (if this object belongs to the state).
|
||||
*/
|
||||
public override function destroy():Void
|
||||
{
|
||||
deadZones = FlxDestroyUtil.destroyArray(deadZones);
|
||||
currentInput = null;
|
||||
input = null;
|
||||
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the game loop automatically, handles touch over and click detection.
|
||||
*/
|
||||
public override function update(elapsed:Float):Void
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
#if FLX_POINTER_INPUT
|
||||
// Update the button, but only if touches are enabled
|
||||
if (visible)
|
||||
{
|
||||
final overlapFound:Bool = checkTouchOverlap();
|
||||
|
||||
if (currentInput != null && currentInput.justReleased && overlapFound) onUpHandler();
|
||||
|
||||
if (status != FunkinButtonStatus.NORMAL
|
||||
&& (!overlapFound || (currentInput != null && currentInput.justReleased))
|
||||
&& !SwipeUtil.swipeAny) onOutHandler();
|
||||
}
|
||||
#end
|
||||
|
||||
input.update();
|
||||
}
|
||||
|
||||
private function checkTouchOverlap():Bool
|
||||
{
|
||||
for (camera in cameras)
|
||||
{
|
||||
for (touch in FlxG.touches.list)
|
||||
{
|
||||
final worldPos:FlxPoint = touch.getWorldPosition(camera, _point);
|
||||
|
||||
for (zone in deadZones)
|
||||
{
|
||||
if (zone != null)
|
||||
{
|
||||
if (zone.overlapsPoint(worldPos, true, camera)) return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (overlapsPoint(worldPos, true, camera))
|
||||
{
|
||||
updateStatus(touch);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function updateStatus(input:IFlxInput):Void
|
||||
{
|
||||
if (input.justPressed)
|
||||
{
|
||||
currentInput = input;
|
||||
|
||||
onDownHandler();
|
||||
}
|
||||
else if (status == FunkinButtonStatus.NORMAL)
|
||||
{
|
||||
if (input.pressed)
|
||||
{
|
||||
onDownHandler();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function onUpHandler():Void
|
||||
{
|
||||
status = FunkinButtonStatus.NORMAL;
|
||||
|
||||
input.release();
|
||||
|
||||
currentInput = null;
|
||||
|
||||
onUp.dispatch();
|
||||
}
|
||||
|
||||
private function onDownHandler():Void
|
||||
{
|
||||
status = FunkinButtonStatus.PRESSED;
|
||||
|
||||
input.press();
|
||||
|
||||
onDown.dispatch();
|
||||
}
|
||||
|
||||
private function onOutHandler():Void
|
||||
{
|
||||
status = FunkinButtonStatus.NORMAL;
|
||||
|
||||
input.release();
|
||||
|
||||
onOut.dispatch();
|
||||
}
|
||||
|
||||
private inline function get_justReleased():Bool
|
||||
{
|
||||
return input.justReleased;
|
||||
}
|
||||
|
||||
private inline function get_released():Bool
|
||||
{
|
||||
return input.released;
|
||||
}
|
||||
|
||||
private inline function get_pressed():Bool
|
||||
{
|
||||
return input.pressed;
|
||||
}
|
||||
|
||||
private inline function get_justPressed():Bool
|
||||
{
|
||||
return input.justPressed;
|
||||
}
|
||||
}
|
||||
590
source/funkin/mobile/ui/FunkinHitbox.hx
Normal file
590
source/funkin/mobile/ui/FunkinHitbox.hx
Normal file
|
|
@ -0,0 +1,590 @@
|
|||
package funkin.mobile.ui;
|
||||
|
||||
import flixel.FlxG;
|
||||
import flixel.graphics.FlxGraphic;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
import flixel.input.actions.FlxActionInput;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxDestroyUtil;
|
||||
import flixel.util.FlxSignal;
|
||||
import funkin.graphics.shaders.HSVShader;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.mobile.input.ControlsHandler;
|
||||
import funkin.mobile.ui.FunkinButton;
|
||||
import funkin.play.notes.NoteDirection;
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.display.Shape;
|
||||
import openfl.geom.Matrix;
|
||||
import openfl.Vector;
|
||||
import flixel.math.FlxMath;
|
||||
|
||||
enum FunkinHintAlphaStyle
|
||||
{
|
||||
INVISIBLE_TILL_PRESS;
|
||||
VISIBLE_TILL_PRESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* The `FunkinHint` class represents a button with HSV color properties, allowing hue and saturation adjustments.
|
||||
*/
|
||||
@:nullSafety
|
||||
class FunkinHint extends FunkinPolygonButton
|
||||
{
|
||||
/**
|
||||
* A map defining different alpha styles for hint visibility during press and release states.
|
||||
*
|
||||
* Each style is represented as a key with an associated array of two alpha values:
|
||||
* - The first value corresponds to the alpha when the hint is pressed.
|
||||
* - The second value corresponds to the alpha when the hint is not pressed.
|
||||
*
|
||||
* Available styles:
|
||||
* - `'invisible_till_press'`: The hint is invisible until pressed, then becomes visible.
|
||||
* Alpha values: [0.3, 0.00001]
|
||||
* - `'visible_till_press'`: The hint is visible until pressed, and then becomes less visible.
|
||||
* Alpha values: [0.3, 0.15]
|
||||
*/
|
||||
@:noCompletion
|
||||
static final HINT_ALPHA_STYLE:Map<FunkinHintAlphaStyle, Array<Float>> = [INVISIBLE_TILL_PRESS => [0.3, 0.00001], VISIBLE_TILL_PRESS => [0.5, 0.35]];
|
||||
|
||||
/**
|
||||
* The direction of the note associated with the button.
|
||||
*/
|
||||
@:noCompletion
|
||||
var noteDirection:NoteDirection;
|
||||
|
||||
/**
|
||||
* The label associated with the button, represented as a `FunkinSprite`.
|
||||
* It is displayed on top of the button.
|
||||
*/
|
||||
@:noCompletion
|
||||
var label:Null<FunkinSprite>;
|
||||
|
||||
/**
|
||||
* The HSV shader used to adjust the hue and saturation of the button.
|
||||
*/
|
||||
@:noCompletion
|
||||
var hsvShader:HSVShader;
|
||||
|
||||
/**
|
||||
* The tween used to animate the alpha changes of the button.
|
||||
*/
|
||||
@:noCompletion
|
||||
var alphaTween:Null<FlxTween>;
|
||||
|
||||
/**
|
||||
* Creates a new `FunkinHint` object.
|
||||
*
|
||||
* @param x The x position of the button.
|
||||
* @param y The y position of the button.
|
||||
* @param noteDirection The direction of the note the button represents (e.g. left, right).
|
||||
* @param label An graphic to display as the label on the button.
|
||||
*/
|
||||
public function new(x:Float, y:Float, noteDirection:NoteDirection, label:Null<FlxGraphic>):Void
|
||||
{
|
||||
super(x, y);
|
||||
|
||||
this.noteDirection = noteDirection;
|
||||
|
||||
if (label != null)
|
||||
{
|
||||
this.label = new FunkinSprite(x, y);
|
||||
this.label.loadGraphic(label);
|
||||
|
||||
final hintAlpha:Null<Array<Float>> = HINT_ALPHA_STYLE.get(INVISIBLE_TILL_PRESS);
|
||||
|
||||
if (hintAlpha != null) this.label.alpha = hintAlpha[0];
|
||||
}
|
||||
|
||||
hsvShader = new HSVShader();
|
||||
hsvShader.hue = 1.0;
|
||||
hsvShader.saturation = 1.0;
|
||||
hsvShader.value = 1.0;
|
||||
|
||||
shader = hsvShader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes alpha tween animations for the button.
|
||||
*
|
||||
* Sets up handlers for `onDown`, `onUp`, and `onOut` events to modify the button's
|
||||
* `alpha` property using tweens. Cancels any existing tween when an event occurs
|
||||
* and creates a new tween to transition the `alpha` property accordingly:
|
||||
* - `onDown`: Transitions `alpha` to `HINT_ALPHA_DOWN`.
|
||||
* - `onUp` and `onOut`: Transitions `alpha` to `HINT_ALPHA_UP`.
|
||||
*
|
||||
* Uses `FlxEase.circInOut` easing for smooth transitions.
|
||||
*/
|
||||
public function initTween(style:FunkinHintAlphaStyle):Void
|
||||
{
|
||||
final hintAlpha:Null<Array<Float>> = HINT_ALPHA_STYLE.get(style);
|
||||
|
||||
if (hintAlpha == null) return;
|
||||
|
||||
switch (style)
|
||||
{
|
||||
case INVISIBLE_TILL_PRESS:
|
||||
onDown.add(function():Void {
|
||||
if (alphaTween != null) alphaTween.cancel();
|
||||
|
||||
alphaTween = FlxTween.tween(this, {alpha: hintAlpha[0]}, hintAlpha[1],
|
||||
{
|
||||
ease: FlxEase.circInOut,
|
||||
onUpdate: function(twn:FlxTween):Void {
|
||||
if (label != null) label.alpha = hintAlpha[0] - (hintAlpha[0] * twn.percent);
|
||||
}
|
||||
});
|
||||
});
|
||||
onUp.add(function():Void {
|
||||
if (alphaTween != null) alphaTween.cancel();
|
||||
|
||||
alphaTween = FlxTween.tween(this, {alpha: hintAlpha[1]}, hintAlpha[0],
|
||||
{
|
||||
ease: FlxEase.circInOut,
|
||||
onUpdate: function(twn:FlxTween):Void {
|
||||
if (label != null) label.alpha = hintAlpha[0] * twn.percent;
|
||||
}
|
||||
});
|
||||
});
|
||||
onOut.add(function():Void {
|
||||
if (alphaTween != null) alphaTween.cancel();
|
||||
|
||||
alphaTween = FlxTween.tween(this, {alpha: hintAlpha[1]}, hintAlpha[0],
|
||||
{
|
||||
ease: FlxEase.circInOut,
|
||||
onUpdate: function(twn:FlxTween):Void {
|
||||
if (label != null) label.alpha = hintAlpha[0] * twn.percent;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
alpha = hintAlpha[1];
|
||||
case VISIBLE_TILL_PRESS:
|
||||
onDown.add(function():Void {
|
||||
if (alphaTween != null) alphaTween.cancel();
|
||||
|
||||
alphaTween = FlxTween.tween(this, {alpha: hintAlpha[0]}, hintAlpha[1],
|
||||
{
|
||||
ease: FlxEase.circInOut,
|
||||
onUpdate: function(twn:FlxTween):Void {
|
||||
if (label != null) label.alpha = hintAlpha[0] - (hintAlpha[0] * twn.percent);
|
||||
}
|
||||
});
|
||||
});
|
||||
onUp.add(function():Void {
|
||||
if (alphaTween != null) alphaTween.cancel();
|
||||
|
||||
alphaTween = FlxTween.tween(this, {alpha: hintAlpha[1]}, hintAlpha[0],
|
||||
{
|
||||
ease: FlxEase.circInOut,
|
||||
onUpdate: function(twn:FlxTween):Void {
|
||||
if (label != null) label.alpha = hintAlpha[0] * twn.percent;
|
||||
}
|
||||
});
|
||||
});
|
||||
onOut.add(function():Void {
|
||||
if (alphaTween != null) alphaTween.cancel();
|
||||
|
||||
alphaTween = FlxTween.tween(this, {alpha: hintAlpha[1]}, hintAlpha[0],
|
||||
{
|
||||
ease: FlxEase.circInOut,
|
||||
onUpdate: function(twn:FlxTween):Void {
|
||||
if (label != null) label.alpha = hintAlpha[0] * twn.percent;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
alpha = hintAlpha[1];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Desaturates the button, setting its saturation to 0.2.
|
||||
*/
|
||||
public function desaturate():Void
|
||||
{
|
||||
hsvShader.saturation = 0.2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the hue of the button.
|
||||
*
|
||||
* @param hue The new hue value.
|
||||
*/
|
||||
public function setHue(hue:Float):Void
|
||||
{
|
||||
hsvShader.hue = hue;
|
||||
}
|
||||
|
||||
public override function draw():Void
|
||||
{
|
||||
super.draw();
|
||||
|
||||
if (label != null && label.visible)
|
||||
{
|
||||
label.cameras = _cameras;
|
||||
label.draw();
|
||||
}
|
||||
}
|
||||
|
||||
#if FLX_DEBUG
|
||||
public override function drawDebug():Void
|
||||
{
|
||||
super.drawDebug();
|
||||
|
||||
if (label != null) label.drawDebug();
|
||||
}
|
||||
#end
|
||||
|
||||
/**
|
||||
* Cleans up memory used by the `FunkinHint`.
|
||||
*/
|
||||
public override function destroy():Void
|
||||
{
|
||||
if (label != null) label = FlxDestroyUtil.destroy(label);
|
||||
|
||||
if (alphaTween != null) alphaTween = FlxDestroyUtil.destroy(alphaTween);
|
||||
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
@:noCompletion
|
||||
private override function set_x(Value:Float):Float
|
||||
{
|
||||
super.set_x(Value);
|
||||
|
||||
if (label != null) label.x = x;
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
@:noCompletion
|
||||
private override function set_y(Value:Float):Float
|
||||
{
|
||||
super.set_y(Value);
|
||||
|
||||
if (label != null) label.y = y;
|
||||
|
||||
return y;
|
||||
}
|
||||
}
|
||||
|
||||
enum abstract FunkinHitboxControlSchemes(String) from String to String
|
||||
{
|
||||
final FourLanes = 'fourLanes';
|
||||
final DoubleThumbTriangle = 'doubleThumbTriangle';
|
||||
final DoubleThumbSquare = 'doubleThumbSquare';
|
||||
final DoubleThumbDPad = 'doubleThumbDPad';
|
||||
}
|
||||
|
||||
/**
|
||||
* The `FunkinHitbox` class represents a zone with four buttons, designed to be easily customizable in layout.
|
||||
* It extends `FlxTypedSpriteGroup` with `FunkinHint` as the type parameter.
|
||||
*/
|
||||
@:nullSafety
|
||||
class FunkinHitbox extends FlxTypedSpriteGroup<FunkinHint>
|
||||
{
|
||||
/**
|
||||
* A `FlxTypedSignal` that triggers every time a button is pressed.
|
||||
*/
|
||||
public var onHintDown:FlxTypedSignal<FunkinHint->Void> = new FlxTypedSignal<FunkinHint->Void>();
|
||||
|
||||
/**
|
||||
* A `FlxTypedSignal` that triggers every time a button is released.
|
||||
*/
|
||||
public var onHintUp:FlxTypedSignal<FunkinHint->Void> = new FlxTypedSignal<FunkinHint->Void>();
|
||||
|
||||
/**
|
||||
* The list of tracked inputs for the hitbox.
|
||||
*/
|
||||
@:noCompletion
|
||||
var trackedInputs:Array<FlxActionInput> = [];
|
||||
|
||||
/**
|
||||
* Creates a new `FunkinHitbox` object.
|
||||
*/
|
||||
public function new(?schemeOverride:String = null):Void
|
||||
{
|
||||
super();
|
||||
|
||||
final hintsColors:Array<FlxColor> = [0xFFC34B9A, 0xFF00FFFF, 0xFF12FB06, 0xFFF9393F];
|
||||
final hintsNoteDirections:Array<NoteDirection> = [NoteDirection.LEFT, NoteDirection.DOWN, NoteDirection.UP, NoteDirection.RIGHT];
|
||||
final controlsScheme:String = (schemeOverride == null || schemeOverride.length == 0) ? Preferences.controlsScheme : schemeOverride;
|
||||
|
||||
switch (controlsScheme)
|
||||
{
|
||||
case FunkinHitboxControlSchemes.FourLanes:
|
||||
final hintWidth:Int = Math.floor(FlxG.width / hintsNoteDirections.length);
|
||||
final hintHeight:Int = FlxG.height;
|
||||
|
||||
for (i in 0...hintsNoteDirections.length)
|
||||
add(createHintLane(i * hintWidth, 0, hintsNoteDirections[i % hintsNoteDirections.length], hintWidth, hintHeight,
|
||||
hintsColors[i % hintsColors.length]));
|
||||
case FunkinHitboxControlSchemes.DoubleThumbTriangle:
|
||||
final screenHalf:Int = Math.floor(FlxG.width / 2);
|
||||
|
||||
for (i in 0...2)
|
||||
{
|
||||
final xOffset:Int = (i == 1) ? screenHalf : 0;
|
||||
|
||||
add(createHintTriangle(xOffset, 0, hintsNoteDirections[0], Math.floor(FlxG.width / 4), FlxG.height, hintsColors[0]));
|
||||
add(createHintTriangle(xOffset, FlxG.height / 2, hintsNoteDirections[1], Math.floor(FlxG.width / 2), Math.floor(FlxG.height / 2), hintsColors[1]));
|
||||
add(createHintTriangle(xOffset, 0, hintsNoteDirections[2], Math.floor(FlxG.width / 2), Math.floor(FlxG.height / 2), hintsColors[2]));
|
||||
add(createHintTriangle(xOffset + Math.floor(FlxG.width / 4), 0, hintsNoteDirections[3], Math.floor(FlxG.width / 4), FlxG.height, hintsColors[3]));
|
||||
}
|
||||
case FunkinHitboxControlSchemes.DoubleThumbSquare:
|
||||
final screenHalf:Int = Math.floor(FlxG.width / 2);
|
||||
|
||||
final hintWidth:Int = Math.floor((FlxG.width / hintsNoteDirections.length) / 2);
|
||||
final hintHeight:Int = FlxG.height;
|
||||
|
||||
final boxWidth:Int = Math.floor(hintWidth * 2);
|
||||
final boxHeight:Int = Math.floor(hintHeight / 2);
|
||||
|
||||
for (i in 0...2)
|
||||
{
|
||||
final xOffset:Int = (i == 1) ? screenHalf : 0;
|
||||
|
||||
for (j in 0...hintsNoteDirections.length)
|
||||
{
|
||||
if (j == 1 || j == 2) add(createHintLane(xOffset + hintWidth, (j == 1) ? boxHeight : 0, hintsNoteDirections[j], boxWidth, boxHeight,
|
||||
hintsColors[j % hintsColors.length], false));
|
||||
else
|
||||
add(createHintLane(xOffset + (j == 0 ? 0 : hintWidth + boxWidth), 0, hintsNoteDirections[j], hintWidth, hintHeight,
|
||||
hintsColors[j % hintsColors.length], false));
|
||||
}
|
||||
}
|
||||
case FunkinHitboxControlSchemes.DoubleThumbDPad:
|
||||
final hintSize:Int = 80;
|
||||
final hintsAngles:Array<Float> = [Math.PI, Math.PI / 2, Math.PI * 1.5, 0];
|
||||
final hintsZoneRadius:Int = 115;
|
||||
|
||||
for (i in 0...2)
|
||||
{
|
||||
for (j in 0...hintsAngles.length)
|
||||
{
|
||||
final x:Float = ((i == 1) ? FlxG.width - (hintSize * 4) : hintSize * 2) + Math.cos(hintsAngles[j]) * hintsZoneRadius;
|
||||
final y:Float = (FlxG.height - (hintSize * 3.75)) + Math.sin(hintsAngles[j]) * hintsZoneRadius;
|
||||
|
||||
add(createHintCircle(x, y, hintsNoteDirections[j % hintsNoteDirections.length], hintSize, hintsColors[j % hintsColors.length]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scrollFactor.set();
|
||||
|
||||
ControlsHandler.setupHitbox(PlayerSettings.player1.controls, this, trackedInputs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new `FunkinHint` lane button along side a graphic label with specified properties.
|
||||
*
|
||||
* @param x The x position of the button.
|
||||
* @param y The y position of the button.
|
||||
* @param noteDirection The direction of the note the button represents (e.g. left, right).
|
||||
* @param width The width of the button.
|
||||
* @param height The height of the button.
|
||||
* @param id The ID of the button.
|
||||
* @param color The color of the button.
|
||||
* @return A new `FunkinHint` object.
|
||||
*/
|
||||
@:noCompletion
|
||||
private function createHintLane(x:Float, y:Float, noteDirection:NoteDirection, width:Int, height:Int, color:FlxColor = 0xFFFFFFFF,
|
||||
label:Bool = true):FunkinHint
|
||||
{
|
||||
final hint:FunkinHint = new FunkinHint(x, y, noteDirection, label ? createHintLaneLabelGraphic(width, height, Math.floor(height * 0.035), color) : null);
|
||||
hint.loadGraphic(createHintLaneGraphic(width, height, color));
|
||||
hint.onDown.add(() -> onHintDown.dispatch(hint));
|
||||
hint.onUp.add(() -> onHintUp.dispatch(hint));
|
||||
hint.onOut.add(() -> onHintUp.dispatch(hint));
|
||||
hint.initTween(INVISIBLE_TILL_PRESS);
|
||||
return hint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new `FunkinHint` triangle button with specified properties.
|
||||
*
|
||||
* @param x The x position of the triangle button.
|
||||
* @param y The y position of the triangle button.
|
||||
* @param noteDirection The direction of the note the button represents (e.g. left, right).
|
||||
* @param size The size of the triangle (base length).
|
||||
* @param upright A boolean indicating if the triangle is upright (true) or inverted (false).
|
||||
* @param id The unique ID of the triangle button.
|
||||
* @param color The color of the triangle button (default is white).
|
||||
* @return A new `FunkinHint` triangle object.
|
||||
*/
|
||||
@:noCompletion
|
||||
private function createHintTriangle(x:Float, y:Float, noteDirection:NoteDirection, width:Int, height:Int, color:FlxColor = 0xFFFFFFFF):FunkinHint
|
||||
{
|
||||
final hint:FunkinHint = new FunkinHint(x, y, noteDirection, null);
|
||||
hint.polygon = getTriangleVertices(width, height, noteDirection);
|
||||
hint.loadGraphic(createHintTriangleGraphic(width, height, noteDirection, color));
|
||||
hint.onDown.add(() -> onHintDown.dispatch(hint));
|
||||
hint.onUp.add(() -> onHintUp.dispatch(hint));
|
||||
hint.onOut.add(() -> onHintUp.dispatch(hint));
|
||||
hint.initTween(INVISIBLE_TILL_PRESS);
|
||||
return hint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new `FunkinHint` circular button with specified properties.
|
||||
*
|
||||
* @param x The x position of the circular button.
|
||||
* @param y The y position of the circular button.
|
||||
* @param noteDirection The direction of the note the button represents (e.g., left, right).
|
||||
* @param radius The radius of the circular button.
|
||||
* @param color The color of the circular button (default is white).
|
||||
* @return A new `FunkinHint` circular object.
|
||||
*/
|
||||
@:noCompletion
|
||||
private function createHintCircle(x:Float, y:Float, noteDirection:NoteDirection, radius:Float, color:FlxColor = 0xFFFFFFFF):FunkinHint
|
||||
{
|
||||
final hint:FunkinHint = new FunkinHint(x, y, noteDirection, null);
|
||||
hint.loadGraphic(createHintCircleGraphic(radius, color));
|
||||
hint.onDown.add(() -> onHintDown.dispatch(hint));
|
||||
hint.onUp.add(() -> onHintUp.dispatch(hint));
|
||||
hint.onOut.add(() -> onHintUp.dispatch(hint));
|
||||
hint.initTween(VISIBLE_TILL_PRESS);
|
||||
return hint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a lane graphic for a hint button.
|
||||
*
|
||||
* @param width The width of the graphic.
|
||||
* @param height The height of the graphic.
|
||||
* @param baseColor The base color of the graphic.
|
||||
* @return A `FlxGraphic` object representing the button graphic.
|
||||
*/
|
||||
@:noCompletion
|
||||
private function createHintLaneGraphic(width:Int, height:Int, baseColor:FlxColor = 0xFFFFFFFF, gradient:Bool = true):FlxGraphic
|
||||
{
|
||||
final shape:Shape = new Shape();
|
||||
|
||||
if (gradient)
|
||||
{
|
||||
final matrix:Matrix = new Matrix();
|
||||
matrix.createGradientBox(width, height, 0, 0, 0);
|
||||
shape.graphics.beginGradientFill(RADIAL, [baseColor.to24Bit(), baseColor.to24Bit()], [0, 1], [60, 255], matrix, PAD, RGB, 0);
|
||||
}
|
||||
else
|
||||
shape.graphics.beginFill(baseColor.to24Bit(), baseColor.alphaFloat);
|
||||
|
||||
shape.graphics.drawRect(0, 0, width, height);
|
||||
shape.graphics.endFill();
|
||||
|
||||
final graphicData:BitmapData = new BitmapData(width, height, true, 0);
|
||||
graphicData.draw(shape, true);
|
||||
return FlxGraphic.fromBitmapData(graphicData, false, null, false);
|
||||
}
|
||||
|
||||
@:noCompletion
|
||||
private function createHintLaneLabelGraphic(width:Int, height:Int, labelHeight:Int, baseColor:FlxColor = 0xFFFFFFFF):FlxGraphic
|
||||
{
|
||||
final shape:Shape = new Shape();
|
||||
|
||||
shape.graphics.beginFill(0, 0);
|
||||
shape.graphics.drawRect(0, 0, width, height);
|
||||
shape.graphics.endFill();
|
||||
|
||||
shape.graphics.beginFill(baseColor.to24Bit(), baseColor.alphaFloat);
|
||||
shape.graphics.drawRect(0, 0, width, labelHeight);
|
||||
shape.graphics.endFill();
|
||||
|
||||
shape.graphics.beginFill(baseColor.to24Bit(), baseColor.alphaFloat);
|
||||
shape.graphics.drawRect(0, height - labelHeight, width, labelHeight);
|
||||
shape.graphics.endFill();
|
||||
|
||||
final graphicData:BitmapData = new BitmapData(width, height, true, 0);
|
||||
graphicData.draw(shape, true);
|
||||
return FlxGraphic.fromBitmapData(graphicData, false, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a triangle graphic for a hint button.
|
||||
*
|
||||
* @param size The base length of the triangle.
|
||||
* @param upright A boolean indicating if the triangle is upright (true) or inverted (false).
|
||||
* @param baseColor The base color of the triangle graphic (default is white).
|
||||
* @return A `FlxGraphic` object representing the triangle button graphic.
|
||||
*/
|
||||
@:noCompletion
|
||||
private function createHintTriangleGraphic(width:Int, height:Int, facing:NoteDirection, baseColor:FlxColor = 0xFFFFFFFF, gradient:Bool = true):FlxGraphic
|
||||
{
|
||||
final shape:Shape = new Shape();
|
||||
|
||||
if (gradient)
|
||||
{
|
||||
final matrix:Matrix = new Matrix();
|
||||
matrix.createGradientBox(width, height, 0, 0, 0);
|
||||
shape.graphics.beginGradientFill(RADIAL, [baseColor.to24Bit(), baseColor.to24Bit()], [0, 1], [60, 255], matrix, PAD, RGB, 0);
|
||||
}
|
||||
else
|
||||
shape.graphics.beginFill(baseColor.to24Bit(), baseColor.alphaFloat);
|
||||
|
||||
shape.graphics.drawRect(width / 2, height / 2, width / 2, height / 2);
|
||||
shape.graphics.drawTriangles(Vector.ofArray(getTriangleVertices(width, height, facing)), Vector.ofArray([0, 1, 2]));
|
||||
shape.graphics.endFill();
|
||||
|
||||
final graphicData:BitmapData = new BitmapData(width, height, true, 0);
|
||||
graphicData.draw(shape, true);
|
||||
return FlxGraphic.fromBitmapData(graphicData, false, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a circular graphic for a hint button.
|
||||
*
|
||||
* @param radius The radius of the circle.
|
||||
* @param baseColor The base color of the circle graphic (default is white).
|
||||
* @return A `FlxGraphic` object representing the circular button graphic.
|
||||
*/
|
||||
@:noCompletion
|
||||
private function createHintCircleGraphic(radius:Float, baseColor:FlxColor = 0xFFFFFFFF):FlxGraphic
|
||||
{
|
||||
final shape:Shape = new Shape();
|
||||
shape.graphics.beginFill(baseColor.to24Bit(), baseColor.alphaFloat);
|
||||
shape.graphics.drawCircle(radius, radius, radius);
|
||||
shape.graphics.endFill();
|
||||
|
||||
final graphicData:BitmapData = new BitmapData(Math.floor(radius * 2), Math.floor(radius * 2), true, 0);
|
||||
graphicData.draw(shape, true);
|
||||
return FlxGraphic.fromBitmapData(graphicData, false, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Сalculates vertices in a given direction
|
||||
* @param width width of triangle
|
||||
* @param height height of triangle
|
||||
* @param facing The side the triangle faces
|
||||
* @return array of vertices
|
||||
*/
|
||||
@:noCompletion
|
||||
private function getTriangleVertices(width:Int, height:Int, facing:NoteDirection):Array<Float>
|
||||
{
|
||||
if (facing == UP) facing = DOWN;
|
||||
else if (facing == DOWN) facing = UP;
|
||||
|
||||
return switch (facing)
|
||||
{
|
||||
case UP: [width / 2, 0, 0, height, width, height];
|
||||
case DOWN: [0, 0, width, 0, width / 2, height];
|
||||
case LEFT: [0, 0, width, height / 2, 0, height];
|
||||
case RIGHT: [width, 0, 0, height / 2, width, height];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up memory used by the `FunkinHitbox`.
|
||||
*/
|
||||
public override function destroy():Void
|
||||
{
|
||||
if (trackedInputs != null && trackedInputs.length > 0) ControlsHandler.removeCachedInput(PlayerSettings.player1.controls, trackedInputs);
|
||||
|
||||
FlxDestroyUtil.destroy(onHintDown);
|
||||
FlxDestroyUtil.destroy(onHintUp);
|
||||
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
138
source/funkin/mobile/ui/FunkinPolygonButton.hx
Normal file
138
source/funkin/mobile/ui/FunkinPolygonButton.hx
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
package funkin.mobile.ui;
|
||||
|
||||
import flixel.FlxCamera;
|
||||
import flixel.FlxG;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.ui.FlxButton;
|
||||
import flixel.util.FlxDirectionFlags;
|
||||
import openfl.display.Graphics;
|
||||
|
||||
/**
|
||||
* The `FunkinPolygonButton` class represents a button with a non-standard polygonal hitbox.
|
||||
*/
|
||||
@:nullSafety
|
||||
class FunkinPolygonButton extends FunkinButton
|
||||
{
|
||||
/**
|
||||
* The vertices of the polygon defining the button's hitbox.
|
||||
* The array should contain points in the format: [x1, y1, x2, y2, ...].
|
||||
* If the array is empty, the polygon is ignored, and the default hitbox is used.
|
||||
*/
|
||||
public var polygon:Null<Array<Float>> = null;
|
||||
|
||||
public function overlapsPolygon(point:FlxPoint, inScreenSpace:Bool = false, ?camera:FlxCamera):Bool
|
||||
{
|
||||
if (polygon == null || polygon.length < 6 || polygon.length % 2 != 0) return false;
|
||||
|
||||
if (!inScreenSpace) return isPointInPolygon(polygon, point, FlxPoint.weak(x, y));
|
||||
|
||||
if (camera == null) camera = FlxG.camera;
|
||||
|
||||
final pos:FlxPoint = FlxPoint.weak(point.x - camera.scroll.x, point.y - camera.scroll.y);
|
||||
|
||||
point.putWeak();
|
||||
|
||||
return isPointInPolygon(polygon, pos, getScreenPosition(_point, camera));
|
||||
}
|
||||
|
||||
@:noCompletion
|
||||
private static function isPointInPolygon(polygon:Array<Float>, point:FlxPoint, ?offset:FlxPoint):Bool
|
||||
{
|
||||
if (offset == null) offset = FlxPoint.weak();
|
||||
|
||||
var inside:Bool = false;
|
||||
|
||||
final numsPoints:Int = Math.floor(polygon.length / 2);
|
||||
|
||||
for (i in 0...numsPoints)
|
||||
{
|
||||
final vertex1:FlxPoint = FlxPoint.weak(polygon[i * 2] + offset.x, polygon[i * 2 + 1] + offset.y);
|
||||
final vertex2:FlxPoint = FlxPoint.weak(polygon[(i + 1) % numsPoints * 2] + offset.x, polygon[(i + 1) % numsPoints * 2 + 1] + offset.y);
|
||||
|
||||
if (checkRayIntersection(vertex1, vertex2, point))
|
||||
{
|
||||
inside = !inside;
|
||||
}
|
||||
}
|
||||
|
||||
point.putWeak();
|
||||
offset.putWeak();
|
||||
|
||||
return inside;
|
||||
}
|
||||
|
||||
@:noCompletion
|
||||
private static inline function checkRayIntersection(vertex1:FlxPoint, vertex2:FlxPoint, point:FlxPoint):Bool
|
||||
{
|
||||
final result:Bool = (vertex1.y > point.y) != (vertex2.y > point.y)
|
||||
&& point.x < (vertex1.x + ((point.y - vertex1.y) / (vertex2.y - vertex1.y)) * (vertex2.x - vertex1.x));
|
||||
|
||||
vertex1.putWeak();
|
||||
vertex2.putWeak();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@:noCompletion
|
||||
private override function checkTouchOverlap():Bool
|
||||
{
|
||||
if (polygon != null && polygon.length >= 6 && polygon.length % 2 == 0)
|
||||
{
|
||||
for (camera in cameras)
|
||||
{
|
||||
for (touch in FlxG.touches.list)
|
||||
{
|
||||
final worldPos:FlxPoint = touch.getWorldPosition(camera, _point);
|
||||
|
||||
for (zone in deadZones)
|
||||
{
|
||||
if (zone != null && zone.overlapsPoint(worldPos, true, camera)) return false;
|
||||
}
|
||||
|
||||
if (overlapsPolygon(worldPos, false, camera))
|
||||
{
|
||||
updateStatus(touch);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return super.checkTouchOverlap();
|
||||
}
|
||||
|
||||
#if FLX_DEBUG
|
||||
public override function drawDebugOnCamera(camera:FlxCamera):Void
|
||||
{
|
||||
if (polygon != null && polygon.length >= 6 && polygon.length % 2 == 0)
|
||||
{
|
||||
if (!camera.visible || !camera.exists || !isOnScreen(camera)) return;
|
||||
|
||||
getScreenPosition(_point, camera);
|
||||
|
||||
final gfx:Graphics = beginDrawDebug(camera);
|
||||
|
||||
drawDebugPolygonColor(gfx, polygon, getDebugBoundingBoxColor(allowCollisions));
|
||||
|
||||
endDrawDebug(camera);
|
||||
}
|
||||
else
|
||||
super.drawDebugOnCamera(camera);
|
||||
}
|
||||
|
||||
@:noCompletion
|
||||
private function drawDebugPolygonColor(gfx:Graphics, polygon:Array<Float>, color:FlxColor):Void
|
||||
{
|
||||
gfx.lineStyle(2, color, 0.75);
|
||||
|
||||
for (i in 0...Math.floor(polygon.length / 2))
|
||||
{
|
||||
if (i == 0) gfx.moveTo(polygon[i * 2] + _point.x, polygon[i * 2 + 1] + _point.y);
|
||||
else
|
||||
gfx.lineTo(polygon[i * 2] + _point.x, polygon[i * 2 + 1] + _point.y);
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
||||
144
source/funkin/mobile/ui/mods/ModsManager.hx
Normal file
144
source/funkin/mobile/ui/mods/ModsManager.hx
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
package funkin.mobile.ui.mods;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.graphics.FlxGraphic;
|
||||
import flixel.text.FlxText;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxSpriteUtil;
|
||||
import funkin.mobile.util.TouchUtil;
|
||||
import funkin.util.FileUtil;
|
||||
import funkin.ui.MusicBeatSubState;
|
||||
import funkin.modding.PolymodHandler;
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.display.Shape;
|
||||
import openfl.geom.Matrix;
|
||||
|
||||
// #if android
|
||||
// import android.FileDialog;
|
||||
// import android.widget.Toast;
|
||||
// #end
|
||||
|
||||
/**
|
||||
* A Placeholder substate for simplefied mods management on Android targets
|
||||
*/
|
||||
class ModsManager extends MusicBeatSubState
|
||||
{
|
||||
var background:FlxSprite;
|
||||
var importSpr:FlxSprite;
|
||||
var deleteSpr:FlxSprite;
|
||||
var importTxt:FlxText;
|
||||
var deleteTxt:FlxText;
|
||||
var alphaSine:Float = 0.0;
|
||||
var curButton:Null<FlxSprite>;
|
||||
var isImport:Bool = false;
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
|
||||
// FileDialog.init();
|
||||
|
||||
// var matrix:Matrix = new Matrix();
|
||||
// matrix.createGradientBox(FlxG.width, FlxG.height, 0, 0, 0);
|
||||
|
||||
// var shape:Shape = new Shape();
|
||||
// shape.graphics.beginGradientFill(RADIAL, [0xFF373434, 0xFF373434], [0, 1], [20, 255], matrix, PAD, RGB, 0);
|
||||
// shape.graphics.drawRect(0, 0, FlxG.width, FlxG.height);
|
||||
// shape.graphics.endFill();
|
||||
|
||||
// var graphicData:BitmapData = new BitmapData(FlxG.width, FlxG.height, true, 0);
|
||||
// graphicData.draw(shape, true);
|
||||
|
||||
// background = new FlxSprite(0, 0, FlxGraphic.fromBitmapData(graphicData, false, null, false));
|
||||
// background.scrollFactor.set();
|
||||
// add(background);
|
||||
|
||||
// importSpr = FlxSpriteUtil.drawRoundRect(new FlxSprite().makeGraphic(250, 125, FlxColor.TRANSPARENT), 0, 0, 250, 125, 30, 30, FlxColor.WHITE);
|
||||
// importSpr.scrollFactor.set();
|
||||
// add(importSpr);
|
||||
|
||||
// deleteSpr = FlxSpriteUtil.drawRoundRect(new FlxSprite().makeGraphic(250, 125, FlxColor.TRANSPARENT), 0, 0, 250, 125, 30, 30, FlxColor.WHITE);
|
||||
// deleteSpr.scrollFactor.set();
|
||||
// add(deleteSpr);
|
||||
|
||||
// importTxt = new FlxText(0, 0, importSpr.width, "Import", 24);
|
||||
// importTxt.setFormat("VCR OSD Mono", 24, FlxColor.WHITE, FlxTextAlign.CENTER, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK);
|
||||
// importTxt.scrollFactor.set();
|
||||
// add(importTxt);
|
||||
|
||||
// deleteTxt = new FlxText(0, 0, deleteSpr.width, "Delete", 24);
|
||||
// deleteTxt.setFormat("VCR OSD Mono", 24, FlxColor.WHITE, FlxTextAlign.CENTER, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK);
|
||||
// deleteTxt.scrollFactor.set();
|
||||
// add(deleteTxt);
|
||||
|
||||
// importSpr.screenCenter();
|
||||
// importSpr.x -= 150;
|
||||
// deleteSpr.screenCenter();
|
||||
// deleteSpr.x += 150;
|
||||
|
||||
// importTxt.setPosition(importSpr.x + ((importSpr.width - importTxt.width) / 2), importSpr.y + ((importSpr.height - importTxt.height) / 2));
|
||||
// deleteTxt.setPosition(deleteSpr.x + ((deleteSpr.width - deleteTxt.width) / 2), deleteSpr.y + ((deleteSpr.height - deleteTxt.height) / 2));
|
||||
|
||||
// // THE COLORS AND ALPHA KEEP MESSING UP WHILE CONSTRUCTING THE SPRITE INSTANCE IDFK WHY
|
||||
// importSpr.color = 0xFF00FF0D;
|
||||
// deleteSpr.color = 0xFFFF0000;
|
||||
// deleteSpr.alpha = importSpr.alpha = 0.75;
|
||||
// background.alpha = 0.7;
|
||||
}
|
||||
|
||||
override public function update(elapsed:Float)
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
// if (controls.BACK || FlxG.android.justReleased.BACK)
|
||||
// {
|
||||
// FlxG.mouse.visible = false;
|
||||
// FlxG.state.persistentUpdate = true;
|
||||
// close();
|
||||
// }
|
||||
|
||||
// alphaSine += 255 * elapsed;
|
||||
|
||||
// if (curButton != null)
|
||||
// {
|
||||
// curButton.alpha = 1 - Math.sin((Math.PI * alphaSine) / 163);
|
||||
|
||||
// if (TouchUtil.justPressed)
|
||||
// {
|
||||
// if (TouchUtil.overlaps(curButton))
|
||||
// {
|
||||
// if (isImport)
|
||||
// {
|
||||
// trace('[NOTICE] launched SAF!');
|
||||
// FileDialog.launch(FileDialogType.OPEN_DOCUMENT, "application/x-zip-compressed");
|
||||
// FileDialog.onOpen.add(function(content:FileContent) {
|
||||
// trace(content.toString());
|
||||
// @:privateAccess
|
||||
// var aaa:String = PolymodHandler.MOD_FOLDER + '/' + content.name;
|
||||
// trace(aaa);
|
||||
// FileUtil.writeBytesToPath(aaa, content.bytes, Force);
|
||||
// }, true);
|
||||
// }
|
||||
// else {}
|
||||
// }
|
||||
|
||||
// if (!(TouchUtil.overlaps(importSpr) || TouchUtil.overlaps(deleteSpr)))
|
||||
// {
|
||||
// curButton.alpha = 0.75;
|
||||
// curButton = null;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// for (spr in [importSpr, deleteSpr])
|
||||
// {
|
||||
// if (curButton != null) return;
|
||||
// if (curButton == null && TouchUtil.overlaps(spr) && TouchUtil.justPressed)
|
||||
// {
|
||||
// if (spr == importSpr) isImport = true;
|
||||
// curButton = spr;
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
191
source/funkin/mobile/ui/options/MobileControlsSchemeMenu.hx
Normal file
191
source/funkin/mobile/ui/options/MobileControlsSchemeMenu.hx
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
package funkin.mobile.ui.options;
|
||||
|
||||
import flixel.FlxG;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import funkin.ui.MusicBeatSubState;
|
||||
import funkin.ui.AtlasText;
|
||||
import funkin.ui.mainmenu.MainMenuState;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.graphics.FunkinCamera;
|
||||
import funkin.graphics.shaders.AdjustColorShader;
|
||||
import funkin.audio.FunkinSound;
|
||||
import funkin.mobile.ui.options.objects.SchemeMenuButton;
|
||||
import funkin.mobile.ui.options.objects.HitboxShowcase;
|
||||
import funkin.mobile.ui.FunkinHitbox;
|
||||
import funkin.mobile.util.SwipeUtil;
|
||||
|
||||
// This is kinda BAD, will be remade later
|
||||
class MobileControlsSchemeMenu extends MusicBeatSubState
|
||||
{
|
||||
// Cameras
|
||||
var mainCamera:FunkinCamera;
|
||||
var camButtons:FunkinCamera;
|
||||
|
||||
// Objects
|
||||
var menuBG:FunkinSprite;
|
||||
var curSchemeText:AtlasText;
|
||||
|
||||
// Groups
|
||||
var buttonGroup:FlxTypedGroup<SchemeMenuButton>;
|
||||
|
||||
// Variables
|
||||
final availableSchemes:Array<String> = [
|
||||
FunkinHitboxControlSchemes.FourLanes,
|
||||
FunkinHitboxControlSchemes.DoubleThumbTriangle,
|
||||
FunkinHitboxControlSchemes.DoubleThumbSquare,
|
||||
FunkinHitboxControlSchemes.DoubleThumbDPad
|
||||
];
|
||||
final schemeNames:Array<String> = [
|
||||
'Four Lanes',
|
||||
'Double Thumb Triangle',
|
||||
'Double Thumb Square',
|
||||
'Double Thumb DPad'
|
||||
];
|
||||
var anyButtonBusy:Bool = false;
|
||||
var curSelected:Int = 0;
|
||||
var isDemoScreen:Bool;
|
||||
|
||||
// stores values of what the previous persistent draw/update stuff was, example if opened
|
||||
// from pause menu, we want to NOT draw persistently, but then resume drawing once closed
|
||||
var prevPersistentDraw:Bool;
|
||||
var prevPersistentUpdate:Bool;
|
||||
|
||||
override function create()
|
||||
{
|
||||
super.create();
|
||||
|
||||
prevPersistentDraw = FlxG.state.persistentDraw;
|
||||
prevPersistentUpdate = FlxG.state.persistentUpdate;
|
||||
|
||||
FlxG.state.persistentDraw = false;
|
||||
FlxG.state.persistentUpdate = false;
|
||||
|
||||
setupCameras();
|
||||
|
||||
var colorShader = new AdjustColorShader();
|
||||
colorShader.brightness = -200;
|
||||
|
||||
menuBG = FunkinSprite.create('menuDesat');
|
||||
menuBG.shader = colorShader;
|
||||
menuBG.setGraphicSize(Std.int(menuBG.width * 1.1));
|
||||
menuBG.updateHitbox();
|
||||
menuBG.screenCenter();
|
||||
menuBG.scrollFactor.set(0, 0);
|
||||
add(menuBG);
|
||||
|
||||
for (i in 0...availableSchemes.length)
|
||||
{
|
||||
if (availableSchemes[i] == Preferences.controlsScheme) curSelected = i;
|
||||
}
|
||||
|
||||
addSchemeMenuButtons();
|
||||
createHitboxCamera(availableSchemes[curSelected]);
|
||||
}
|
||||
|
||||
function setupCameras()
|
||||
{
|
||||
mainCamera = new FunkinCamera('mobileControlsSchemeMainCamera');
|
||||
mainCamera.bgColor = FlxColor.BLACK;
|
||||
FlxG.cameras.add(mainCamera);
|
||||
|
||||
camButtons = new FunkinCamera('camButtons');
|
||||
FlxG.cameras.add(camButtons, false);
|
||||
camButtons.bgColor = 0x0;
|
||||
}
|
||||
|
||||
function createHitboxCamera(controlsScheme:String)
|
||||
{
|
||||
var h:HitboxShowcase = new HitboxShowcase(-200, 0, 0, curSelected, controlsScheme);
|
||||
add(h);
|
||||
}
|
||||
|
||||
function createSchemeMenuButton(xPos:Float, yPos:Float, name:String, ?onClick:Void->Void = null)
|
||||
{
|
||||
var button:SchemeMenuButton = new SchemeMenuButton(xPos, yPos, name, onClick);
|
||||
button.cameras = [camButtons];
|
||||
|
||||
buttonGroup.add(button);
|
||||
}
|
||||
|
||||
function addSchemeMenuButtons(isDemoScreen:Bool = false)
|
||||
{
|
||||
buttonGroup = new FlxTypedGroup<SchemeMenuButton>();
|
||||
|
||||
if (isDemoScreen)
|
||||
{
|
||||
createSchemeMenuButton(FlxG.width * 0.83, FlxG.height * 0.1, 'BACK', onHitboxDemoBack);
|
||||
}
|
||||
else
|
||||
{
|
||||
createSchemeMenuButton(FlxG.width * 0.83, FlxG.height * 0.1, 'DEMO', onHitboxDemo);
|
||||
createSchemeMenuButton(FlxG.width * 0.83, FlxG.height * 0.82, 'EXIT', onExitButtonPressed);
|
||||
}
|
||||
|
||||
add(buttonGroup);
|
||||
}
|
||||
|
||||
function removeSchemeMenuButtons()
|
||||
{
|
||||
anyButtonBusy = false;
|
||||
|
||||
buttonGroup.forEachAlive(function(button:SchemeMenuButton) {
|
||||
buttonGroup.remove(button);
|
||||
});
|
||||
|
||||
remove(buttonGroup);
|
||||
}
|
||||
|
||||
function onHitboxDemo()
|
||||
{
|
||||
curSchemeText.visible = false;
|
||||
isDemoScreen = true;
|
||||
|
||||
addHitbox(true, false, availableSchemes[curSelected]);
|
||||
removeSchemeMenuButtons();
|
||||
addSchemeMenuButtons(true);
|
||||
|
||||
hitbox.forEachAlive(function(hint:FunkinHint) {
|
||||
buttonGroup.forEachAlive(function(button:SchemeMenuButton) {
|
||||
if (!hint.deadZones.contains(cast(button.body, FunkinSprite))) hint.deadZones.push(cast(button.body, FunkinSprite));
|
||||
});
|
||||
});
|
||||
|
||||
if (hitbox != null) hitbox.active = hitbox.visible = true;
|
||||
}
|
||||
|
||||
function onHitboxDemoBack()
|
||||
{
|
||||
curSchemeText.visible = true;
|
||||
isDemoScreen = false;
|
||||
|
||||
removeSchemeMenuButtons();
|
||||
addSchemeMenuButtons();
|
||||
|
||||
// (hitboxCamera != null) hitboxCamera.visible = true;
|
||||
if (hitbox != null) hitbox.active = hitbox.visible = false;
|
||||
}
|
||||
|
||||
function onExitButtonPressed()
|
||||
{
|
||||
Preferences.controlsScheme = availableSchemes[curSelected];
|
||||
FlxG.switchState(() -> new MainMenuState());
|
||||
}
|
||||
|
||||
override function update(elapsed:Float)
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
if (buttonGroup != null)
|
||||
{
|
||||
buttonGroup.forEachAlive(function(button:SchemeMenuButton) {
|
||||
if (anyButtonBusy)
|
||||
{
|
||||
button.busy = true;
|
||||
return;
|
||||
}
|
||||
anyButtonBusy = button.busy;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
101
source/funkin/mobile/ui/options/objects/HitboxShowcase.hx
Normal file
101
source/funkin/mobile/ui/options/objects/HitboxShowcase.hx
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
package funkin.mobile.ui.options.objects;
|
||||
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
import flixel.addons.display.shapes.FlxShapeBox;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.FlxG;
|
||||
import funkin.mobile.ui.FunkinHitbox;
|
||||
import funkin.graphics.FunkinCamera;
|
||||
import funkin.util.MathUtil;
|
||||
|
||||
/**
|
||||
* Represents a showcase hitbox in the scheme menu.
|
||||
*/
|
||||
@:nullSafety
|
||||
class HitboxShowcase extends FlxSpriteGroup
|
||||
{
|
||||
/**
|
||||
* The group's main camera.
|
||||
*/
|
||||
public var camHitbox:Null<FunkinCamera>;
|
||||
|
||||
/**
|
||||
* An array of values for lerping object's alpha.
|
||||
*/
|
||||
private static final HITBOX_SHOWCASE_ALPHA:Array<Float> = [0.3, 1];
|
||||
|
||||
/**
|
||||
* Object's own index.
|
||||
*/
|
||||
public var index:Int;
|
||||
|
||||
/**
|
||||
* Current selection's index from menu where this object is used.
|
||||
*/
|
||||
public var selectionIndex:Int;
|
||||
|
||||
/**
|
||||
* Indicates if object's index is equal to current selection's index.
|
||||
*/
|
||||
public var selected(get, never):Bool;
|
||||
|
||||
/**
|
||||
* Creates a new HitboxShowcase instance.
|
||||
*
|
||||
* @param x The x position of the object.
|
||||
* @param y The y position of the object.
|
||||
* @param index An integer used as object's index.
|
||||
* @param selectionIndex Menu's current selection index.
|
||||
* @param controlsScheme Hitbox's controls scheme.
|
||||
*/
|
||||
public function new(x:Int = 0, y:Int = 0, index:Int, selectionIndex:Int = 0, controlsScheme:String)
|
||||
{
|
||||
super(x, y);
|
||||
|
||||
this.index = index;
|
||||
this.selectionIndex = selectionIndex;
|
||||
|
||||
setupObjects(controlsScheme);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and setups every needed object.
|
||||
*
|
||||
* @param controlsScheme Hitbox's controls scheme.
|
||||
*/
|
||||
function setupObjects(controlsScheme:String)
|
||||
{
|
||||
camHitbox = new FunkinCamera('camHitbox' + index);
|
||||
camHitbox.bgColor = 0x0;
|
||||
camHitbox.setScale(0.5, 0.5);
|
||||
FlxG.cameras.add(camHitbox);
|
||||
|
||||
final bg:FlxShapeBox = new FlxShapeBox(0, 0, FlxG.width + 2, FlxG.height + 2, {thickness: 6, color: FlxColor.BLACK}, FlxColor.GRAY);
|
||||
bg.screenCenter();
|
||||
add(bg);
|
||||
|
||||
final hitbox:FunkinHitbox = new FunkinHitbox(controlsScheme);
|
||||
hitbox.screenCenter();
|
||||
hitbox.forEachAlive(function(hint:FunkinHint):Void {
|
||||
hint.alpha = 0.3;
|
||||
@:privateAccess
|
||||
if (hint.label != null) hint.label.alpha = 0.3;
|
||||
});
|
||||
hitbox.active = false;
|
||||
add(hitbox);
|
||||
|
||||
this.cameras = [camHitbox];
|
||||
}
|
||||
|
||||
public override function update(elapsed:Float)
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
alpha = MathUtil.smoothLerp(alpha, HITBOX_SHOWCASE_ALPHA[selected ? 1 : 0], elapsed, 0.5);
|
||||
}
|
||||
|
||||
function get_selected()
|
||||
{
|
||||
return index == selectionIndex;
|
||||
}
|
||||
}
|
||||
79
source/funkin/mobile/ui/options/objects/SchemeMenuButton.hx
Normal file
79
source/funkin/mobile/ui/options/objects/SchemeMenuButton.hx
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
package funkin.mobile.ui.options.objects;
|
||||
|
||||
import flixel.addons.display.shapes.FlxShapeBox;
|
||||
import flixel.effects.FlxFlicker;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
import flixel.util.FlxSignal;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.FlxSprite;
|
||||
import funkin.audio.FunkinSound;
|
||||
import funkin.mobile.util.TouchUtil;
|
||||
import funkin.ui.AtlasText;
|
||||
|
||||
/**
|
||||
* Represents a button in the scheme menu, specifically designed for mobile touch input.
|
||||
* The button displays text and allows selection through touch or an external callback.
|
||||
*/
|
||||
@:nullSafety
|
||||
class SchemeMenuButton extends FlxSpriteGroup
|
||||
{
|
||||
/**
|
||||
* The visual body of the button.
|
||||
*/
|
||||
public var body:Null<FlxShapeBox>;
|
||||
|
||||
/**
|
||||
* The text displayed on the button.
|
||||
*/
|
||||
public var text:Null<AtlasText>;
|
||||
|
||||
/**
|
||||
* Signal dispatched when the button is selected. Additional behavior can be added by subscribing to this signal.
|
||||
*/
|
||||
public var onSelect(default, null):FlxSignal = new FlxSignal();
|
||||
|
||||
/**
|
||||
* Indicates if the button is currently processing a selection (to avoid multiple triggers).
|
||||
*/
|
||||
public var busy:Bool = false;
|
||||
|
||||
/**
|
||||
* Creates a new SchemeMenuButton instance.
|
||||
*
|
||||
* @param xPos The x position of the button.
|
||||
* @param yPos The y position of the button.
|
||||
* @param labelText The text displayed on the button.
|
||||
* @param onClick An optional callback function that will be triggered when the button is clicked.
|
||||
*/
|
||||
public function new(?xPos:Float = 0, ?yPos:Float = 0, labelText:String, ?onClick:Void->Void = null):Void
|
||||
{
|
||||
super(xPos, yPos);
|
||||
|
||||
body = new FlxShapeBox(0, 0, 200, 100, {thickness: 4, color: FlxColor.BLACK}, FlxColor.WHITE);
|
||||
add(body);
|
||||
|
||||
text = new AtlasText(-150, -75, labelText, AtlasFont.DEFAULT);
|
||||
add(text);
|
||||
|
||||
updateHitbox();
|
||||
|
||||
if (onClick != null) onSelect.add(onClick);
|
||||
}
|
||||
|
||||
public override function update(elapsed:Float):Void
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
if (!busy && (TouchUtil.justPressed && TouchUtil.overlapsComplex(this)))
|
||||
{
|
||||
busy = true;
|
||||
|
||||
FunkinSound.playOnce(Paths.sound('confirmMenu'));
|
||||
|
||||
FlxFlicker.flicker(this, 1, 0.06, true, false, function(_) {
|
||||
busy = false;
|
||||
onSelect.dispatch();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
282
source/funkin/mobile/util/SwipeUtil.hx
Normal file
282
source/funkin/mobile/util/SwipeUtil.hx
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
package funkin.mobile.util;
|
||||
|
||||
#if FLX_POINTER_INPUT
|
||||
import flixel.FlxG;
|
||||
import flixel.input.FlxSwipe;
|
||||
#end
|
||||
import funkin.mobile.util.TouchUtil;
|
||||
import flixel.util.FlxTimer;
|
||||
|
||||
// Turning this into a library or something would be nice -Zack
|
||||
|
||||
/**
|
||||
* Utility class for handling swipe gestures in HaxeFlixel and dispatching signals for different swipe directions.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* ```haxe
|
||||
* if (SwipeUtil.justSwipedLeft) trace("Swiped left!");
|
||||
*
|
||||
* if (SwipeUtil.justSwipedRight) trace("Swiped right!");
|
||||
*
|
||||
* if (SwipeUtil.justSwipedUp) trace("Swiped up!");
|
||||
*
|
||||
* if (SwipeUtil.justSwipedDown) trace("Swiped down!");
|
||||
*
|
||||
* if (SwipeUtil.justSwipedAny) trace("Swiped in any direction!");
|
||||
* ```
|
||||
*/
|
||||
class SwipeUtil
|
||||
{
|
||||
/**
|
||||
* Boolean variable that tracks if a downward swipe has been detected.
|
||||
*/
|
||||
public static var swipeDown(get, never):Bool;
|
||||
|
||||
/**
|
||||
* Boolean variable that tracks if a leftward swipe has been detected.
|
||||
*/
|
||||
public static var swipeLeft(get, never):Bool;
|
||||
|
||||
/**
|
||||
* Boolean variable that tracks if an upward swipe has been detected.
|
||||
*/
|
||||
public static var swipeUp(get, never):Bool;
|
||||
|
||||
/**
|
||||
* Boolean variable that tracks if a rightward swipe has been detected.
|
||||
*/
|
||||
public static var swipeRight(get, never):Bool;
|
||||
|
||||
/**
|
||||
* Boolean variable that returns true if any swipe direction is detected (down, left, up, or right).
|
||||
*/
|
||||
public static var swipeAny(get, never):Bool;
|
||||
|
||||
/**
|
||||
* Indicates if there is a down swipe gesture detected.
|
||||
*/
|
||||
public static var justSwipedDown(get, never):Bool;
|
||||
|
||||
/**
|
||||
* Indicates if there is a left swipe gesture detected.
|
||||
*/
|
||||
public static var justSwipedLeft(get, never):Bool;
|
||||
|
||||
/**
|
||||
* Indicates if there is a right swipe gesture detected.
|
||||
*/
|
||||
public static var justSwipedRight(get, never):Bool;
|
||||
|
||||
/**
|
||||
* Indicates if there is an up swipe gesture detected.
|
||||
*/
|
||||
public static var justSwipedUp(get, never):Bool;
|
||||
|
||||
/**
|
||||
* Indicates if there is any swipe gesture detected.
|
||||
*/
|
||||
public static var justSwipedAny(get, never):Bool;
|
||||
|
||||
/**
|
||||
* Swipe sensitivity. Increase it for less sensitivity, decrease it for more.
|
||||
*/
|
||||
public static var swipeThreshold:Float = 100;
|
||||
|
||||
// Helper variables for handleSwipe()
|
||||
static var _startX:Float = 0;
|
||||
static var _startY:Float = 0;
|
||||
static var _isSwiping:Bool = false;
|
||||
|
||||
/**
|
||||
* Detects a downward swipe gesture. If detected, it resets swipe state variables.
|
||||
*
|
||||
* @return Bool True if a downward swipe passes the threshold, false otherwise.
|
||||
*/
|
||||
@:noCompletion
|
||||
inline static function get_swipeDown():Bool
|
||||
return handleSwipe(Down);
|
||||
|
||||
/**
|
||||
* Detects a leftward swipe gesture. If detected, it resets swipe state variables.
|
||||
*
|
||||
* @return Bool True if a leftward swipe passes the threshold, false otherwise.
|
||||
*/
|
||||
@:noCompletion
|
||||
inline static function get_swipeLeft():Bool
|
||||
return handleSwipe(Left);
|
||||
|
||||
/**
|
||||
* Detects a rightward swipe gesture. If detected, it resets swipe state variables.
|
||||
*
|
||||
* @return Bool True if a rightward swipe passes the threshold, false otherwise.
|
||||
*/
|
||||
@:noCompletion
|
||||
inline static function get_swipeRight():Bool
|
||||
return handleSwipe(Right);
|
||||
|
||||
/**
|
||||
* Detects an upward swipe gesture. If detected, it resets swipe state variables.
|
||||
*
|
||||
* @return Bool True if an upward swipe passes the threshold, false otherwise.
|
||||
*/
|
||||
@:noCompletion
|
||||
inline static function get_swipeUp():Bool
|
||||
return handleSwipe(Up);
|
||||
|
||||
/**
|
||||
* Determines if there is any swipe input.
|
||||
*
|
||||
* @return True if any swipe input is detected, false otherwise.
|
||||
*/
|
||||
@:noCompletion
|
||||
inline static function get_swipeAny():Bool
|
||||
return swipeDown || swipeLeft || swipeRight || swipeUp;
|
||||
|
||||
/**
|
||||
* Determines if there is a down swipe in the FlxG.swipes array.
|
||||
*
|
||||
* @return True if any swipe direction is down, false otherwise.
|
||||
*/
|
||||
@:noCompletion
|
||||
inline static function get_justSwipedDown():Bool
|
||||
{
|
||||
#if FLX_POINTER_INPUT
|
||||
final swipe:FlxSwipe = (FlxG.swipes.length > 0) ? FlxG.swipes[0] : null;
|
||||
return (swipe?.degrees > -135 && swipe?.degrees < -45 && swipe?.distance > 20);
|
||||
#else
|
||||
return false;
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if there is a right swipe in the FlxG.swipes array.
|
||||
*
|
||||
* @return True if any swipe direction is right, false otherwise.
|
||||
*/
|
||||
@:noCompletion
|
||||
inline static function get_justSwipedLeft():Bool
|
||||
{
|
||||
#if FLX_POINTER_INPUT
|
||||
final swipe:FlxSwipe = (FlxG.swipes.length > 0) ? FlxG.swipes[0] : null;
|
||||
return ((swipe?.degrees > 135 || swipe?.degrees < -135) && swipe?.distance > 20);
|
||||
#else
|
||||
return false;
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if there is a left swipe in the FlxG.swipes array.
|
||||
*
|
||||
* @return True if any swipe direction is left, false otherwise.
|
||||
*/
|
||||
@:noCompletion
|
||||
inline static function get_justSwipedRight():Bool
|
||||
{
|
||||
#if FLX_POINTER_INPUT
|
||||
final swipe:FlxSwipe = (FlxG.swipes.length > 0) ? FlxG.swipes[0] : null;
|
||||
return (swipe?.degrees > -45 && swipe?.degrees < 45 && swipe?.distance > 20);
|
||||
#else
|
||||
return false;
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if there is an up swipe in the FlxG.swipes array.
|
||||
*
|
||||
* @return True if any swipe direction is up, false otherwise.
|
||||
*/
|
||||
@:noCompletion
|
||||
inline static function get_justSwipedUp():Bool
|
||||
{
|
||||
#if FLX_POINTER_INPUT
|
||||
final swipe:FlxSwipe = (FlxG.swipes.length > 0) ? FlxG.swipes[0] : null;
|
||||
return (swipe?.degrees > 45 && swipe?.degrees < 135 && swipe?.distance > 20);
|
||||
#else
|
||||
return false;
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if there is any swipe in the FlxG.swipes array.
|
||||
*
|
||||
* @return True if any swipe input is detected, false otherwise.
|
||||
*/
|
||||
@:noCompletion
|
||||
inline static function get_justSwipedAny():Bool
|
||||
return justSwipedDown || justSwipedLeft || justSwipedRight || justSwipedUp;
|
||||
|
||||
// Calling a function for each check might be eh, but I think its clean enough.
|
||||
|
||||
/**
|
||||
* Handles swipe gestures and detects the direction of the swipe based on the input.
|
||||
* The function starts tracking the swipe when a touch is pressed and compares the
|
||||
* movement against a threshold to determine if the swipe is valid in the specified
|
||||
* direction (horizontal or vertical).
|
||||
*
|
||||
* @param direction The expected swipe direction, which can be one of the values from the SwipingDirection enum.
|
||||
* @return Bool True if the swipe in the given direction passes the threshold; false otherwise.
|
||||
*/
|
||||
@:noCompletion
|
||||
static function handleSwipe(direction:SwipingDirection):Bool
|
||||
{
|
||||
#if FLX_POINTER_INPUT
|
||||
final touch = TouchUtil.touch;
|
||||
|
||||
#if !mobile
|
||||
return false;
|
||||
#end
|
||||
|
||||
if (touch == null) return false;
|
||||
|
||||
// When touch is pressed, start tracking
|
||||
if (TouchUtil.pressed && !_isSwiping)
|
||||
{
|
||||
_startX = touch.viewX;
|
||||
_startY = touch.viewY;
|
||||
_isSwiping = true;
|
||||
}
|
||||
|
||||
// Reset swipe state when touch is released
|
||||
if (!TouchUtil.pressed) _isSwiping = false;
|
||||
|
||||
// If it's dragging
|
||||
if (_isSwiping)
|
||||
{
|
||||
final deltaX:Float = touch.viewX - _startX;
|
||||
final deltaY:Float = touch.viewY - _startY;
|
||||
|
||||
// Handle swipe input
|
||||
final swiped:Bool = switch (direction)
|
||||
{
|
||||
case Right: deltaX > swipeThreshold;
|
||||
case Down: deltaY < -swipeThreshold;
|
||||
case Left: deltaX < -swipeThreshold;
|
||||
case Up: deltaY > swipeThreshold;
|
||||
case None: false;
|
||||
};
|
||||
|
||||
// oldDirection = direction
|
||||
|
||||
if (swiped)
|
||||
{
|
||||
_isSwiping = false;
|
||||
_startX = _startY = 0;
|
||||
}
|
||||
|
||||
return swiped;
|
||||
}
|
||||
#end
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
enum SwipingDirection
|
||||
{
|
||||
Right;
|
||||
Left;
|
||||
Down;
|
||||
Up;
|
||||
None;
|
||||
}
|
||||
165
source/funkin/mobile/util/TouchUtil.hx
Normal file
165
source/funkin/mobile/util/TouchUtil.hx
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
// Hey!! Slight note from little me, Zack, I got rid of the for loops because essentially the functions just return the first touch they find back then.
|
||||
// Now it looks more clean but the descriptions for some variables are inaccurate. Well they were already inaccurate but still.
|
||||
// - Zack xoxo
|
||||
//
|
||||
// Another slight note from little me, it turns out it WAS accurate, my ass during clean up just completely forgot. My fault!! I gotta focus on multi-touch next time.
|
||||
// Easy to fix but I'll tackle it for later.
|
||||
// - Zack
|
||||
package funkin.mobile.util;
|
||||
|
||||
import flixel.FlxBasic;
|
||||
import flixel.FlxCamera;
|
||||
import flixel.FlxG;
|
||||
import flixel.FlxObject;
|
||||
#if FLX_TOUCH
|
||||
import flixel.input.touch.FlxTouch;
|
||||
#end
|
||||
import flixel.input.mouse.FlxMouse;
|
||||
import flixel.math.FlxPoint;
|
||||
|
||||
/**
|
||||
* Utility class for handling touch input within the FlxG context.
|
||||
*/
|
||||
class TouchUtil
|
||||
{
|
||||
/**
|
||||
* Indicates if any touch is currently pressed.
|
||||
*/
|
||||
public static var pressed(get, never):Bool;
|
||||
|
||||
/**
|
||||
* Indicates if any touch was just pressed this frame.
|
||||
*/
|
||||
public static var justPressed(get, never):Bool;
|
||||
|
||||
/**
|
||||
* Indicates if any touch was just released this frame.
|
||||
*/
|
||||
public static var justReleased(get, never):Bool;
|
||||
|
||||
/**
|
||||
* Indicates if any touch is released this frame.
|
||||
*/
|
||||
public static var released(get, never):Bool;
|
||||
|
||||
/**
|
||||
* Indicates if any touch is moved this frame.
|
||||
*/
|
||||
public static var justMoved(get, never):Bool;
|
||||
|
||||
/**
|
||||
* The first touch in the FlxG.touches list.
|
||||
*/
|
||||
#if mobile
|
||||
public static var touch(get, never):FlxTouch;
|
||||
#else
|
||||
public static var touch(get, never):FlxMouse;
|
||||
#end
|
||||
|
||||
/**
|
||||
* Checks if the specified object overlaps with any active touch.
|
||||
*
|
||||
* @param object The FlxBasic object to check for overlap.
|
||||
* @param camera Optional camera for the overlap check. Defaults to the object's camera.
|
||||
*
|
||||
* @return `true` if there is an overlap with any touch; `false` otherwise.
|
||||
*/
|
||||
public static function overlaps(object:FlxBasic, ?camera:FlxCamera):Bool
|
||||
{
|
||||
#if !mobile
|
||||
return false;
|
||||
#end
|
||||
if (object == null || touch == null) return false;
|
||||
|
||||
return touch.overlaps(object, camera ?? object.camera);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the specified object overlaps with any active touch using precise point checks.
|
||||
*
|
||||
* @param object The FlxObject to check for overlap.
|
||||
* @param camera Optional camera for the overlap check. Defaults to all cameras of the object.
|
||||
*
|
||||
* @return `true` if there is a precise overlap with any touch; `false` otherwise.
|
||||
*/
|
||||
public static function overlapsComplex(object:FlxObject, ?camera:FlxCamera):Bool
|
||||
{
|
||||
#if !mobile
|
||||
return false;
|
||||
#end
|
||||
if (object == null || touch == null) return false;
|
||||
|
||||
if (camera == null) camera = object.cameras[0];
|
||||
|
||||
@:privateAccess
|
||||
return object.overlapsPoint(touch.getWorldPosition(camera, object._point), true, camera);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the specified object overlaps with a specific point using precise point checks.
|
||||
*
|
||||
* @param object The FlxObject to check for overlap.
|
||||
* @param point The FlxPoint to check against the object.
|
||||
* @param inScreenSpace Whether to take scroll factors into account when checking for overlap.
|
||||
* @param camera Optional camera for the overlap check. Defaults to all cameras of the object.
|
||||
*
|
||||
* @return `true` if there is a precise overlap with the specified point; `false` otherwise.
|
||||
*/
|
||||
public static function overlapsComplexPoint(object:FlxObject, point:FlxPoint, ?inScreenSpace:Bool = false, ?camera:FlxCamera):Bool
|
||||
{
|
||||
#if !mobile
|
||||
return false;
|
||||
#end
|
||||
if (object == null || point == null) return false;
|
||||
|
||||
if (camera == null) camera = object.cameras[0];
|
||||
@:privateAccess
|
||||
if (object.overlapsPoint(point, inScreenSpace, camera))
|
||||
{
|
||||
point.putWeak();
|
||||
return true;
|
||||
}
|
||||
|
||||
point.putWeak();
|
||||
return false;
|
||||
}
|
||||
|
||||
// weird mix between "looks weird" and "looks neat" but i'll keep it for now -Zack
|
||||
|
||||
@:noCompletion
|
||||
inline static function get_justMoved():Bool
|
||||
return touch != null && touch.justMoved;
|
||||
|
||||
@:noCompletion
|
||||
inline static function get_pressed():Bool
|
||||
return touch != null && touch.pressed;
|
||||
|
||||
@:noCompletion
|
||||
inline static function get_justPressed():Bool
|
||||
return touch != null && touch.justPressed;
|
||||
|
||||
@:noCompletion
|
||||
inline static function get_justReleased():Bool
|
||||
return touch != null && touch.justReleased;
|
||||
|
||||
@:noCompletion
|
||||
static function get_released():Bool
|
||||
return touch != null && touch.released;
|
||||
|
||||
#if mobile
|
||||
@:noCompletion
|
||||
static function get_touch():FlxTouch
|
||||
{
|
||||
for (touch in FlxG.touches.list)
|
||||
{
|
||||
if (touch != null) return touch;
|
||||
}
|
||||
|
||||
return FlxG.touches.getFirst();
|
||||
}
|
||||
#else
|
||||
@:noCompletion
|
||||
inline static function get_touch():FlxMouse
|
||||
return FlxG.mouse;
|
||||
#end
|
||||
}
|
||||
|
|
@ -280,6 +280,12 @@ class PolymodHandler
|
|||
// Can load and execute compiled binaries.
|
||||
Polymod.blacklistImport('lime.system.JNI');
|
||||
|
||||
#if ("extension-androidtools")
|
||||
// `android.jni.JNICache`
|
||||
// Same as `lime.system.JNI`
|
||||
Polymod.blacklistImport('android.jni.JNICache');
|
||||
#end
|
||||
|
||||
// `lime.system.System`
|
||||
// System.load() can load malicious DLLs
|
||||
Polymod.blacklistImport('lime.system.System');
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import funkin.data.freeplay.player.PlayerRegistry;
|
|||
import flixel.FlxG;
|
||||
import flixel.FlxObject;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.input.touch.FlxTouch;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.audio.FunkinSound;
|
||||
|
|
@ -19,6 +18,8 @@ import funkin.ui.story.StoryMenuState;
|
|||
import funkin.util.MathUtil;
|
||||
import funkin.effects.RetroCameraFade;
|
||||
import flixel.math.FlxPoint;
|
||||
import funkin.mobile.util.TouchUtil;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
/**
|
||||
* A substate which renders over the PlayState when the player dies.
|
||||
|
|
@ -164,6 +165,10 @@ class GameOverSubState extends MusicBeatSubState
|
|||
|
||||
// The conductor now represents the BPM of the game over music.
|
||||
Conductor.instance.update(0);
|
||||
|
||||
#if mobile
|
||||
addBackButton(FlxG.width * 0.77, FlxG.height * 0.84, FlxColor.WHITE, goBack);
|
||||
#end
|
||||
}
|
||||
|
||||
@:nullSafety(Off)
|
||||
|
|
@ -211,6 +216,7 @@ class GameOverSubState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
var hasStartedAnimation:Bool = false;
|
||||
var canInput:Bool = false;
|
||||
|
||||
override function update(elapsed:Float):Void
|
||||
{
|
||||
|
|
@ -245,72 +251,16 @@ class GameOverSubState extends MusicBeatSubState
|
|||
// Handle user inputs.
|
||||
//
|
||||
|
||||
// MOBILE ONLY: Restart the level when tapping Boyfriend.
|
||||
if (FlxG.onMobile)
|
||||
{
|
||||
var touch:FlxTouch = FlxG.touches.getFirst();
|
||||
if (touch != null)
|
||||
{
|
||||
if (boyfriend == null || touch.overlaps(boyfriend))
|
||||
{
|
||||
confirmDeath();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// KEYBOARD ONLY: Restart the level when pressing the assigned key.
|
||||
if (controls.ACCEPT && blueballed && !mustNotExit)
|
||||
// Restart the level when pressing the assigned key.
|
||||
if ((controls.ACCEPT #if mobile || (TouchUtil.justPressed && !TouchUtil.overlaps(backButton) && canInput) #end)
|
||||
&& blueballed
|
||||
&& !mustNotExit)
|
||||
{
|
||||
blueballed = false;
|
||||
confirmDeath();
|
||||
}
|
||||
|
||||
// KEYBOARD ONLY: Return to the menu when pressing the assigned key.
|
||||
if (controls.BACK && !mustNotExit && !isEnding)
|
||||
{
|
||||
isEnding = true;
|
||||
blueballed = false;
|
||||
PlayState.instance.deathCounter = 0;
|
||||
// PlayState.seenCutscene = false; // old thing...
|
||||
if (gameOverMusic != null) gameOverMusic.stop();
|
||||
|
||||
// Stop death quotes immediately.
|
||||
hasPlayedDeathQuote = true;
|
||||
if (deathQuoteSound != null)
|
||||
{
|
||||
deathQuoteSound.stop();
|
||||
deathQuoteSound = null;
|
||||
}
|
||||
|
||||
if (isChartingMode)
|
||||
{
|
||||
this.close();
|
||||
if (FlxG.sound.music != null) FlxG.sound.music.pause(); // Don't reset song position!
|
||||
PlayState.instance.close(); // This only works because PlayState is a substate!
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
var targetState:funkin.ui.transition.StickerSubState->FlxState = (PlayStatePlaylist.isStoryMode) ? (sticker) ->
|
||||
new StoryMenuState(sticker) : (sticker) -> FreeplayState.build(sticker);
|
||||
|
||||
if (PlayStatePlaylist.isStoryMode)
|
||||
{
|
||||
PlayStatePlaylist.reset();
|
||||
}
|
||||
|
||||
var playerCharacterId = PlayerRegistry.instance.getCharacterOwnerId(PlayState.instance.currentChart.characters.player);
|
||||
var stickerSet = (playerCharacterId == "pico") ? "stickers-set-2" : "stickers-set-1";
|
||||
var stickerPack = switch (PlayState.instance.currentChart.song.id)
|
||||
{
|
||||
case "tutorial": "tutorial";
|
||||
case "darnell" | "lit-up" | "2hot": "weekend";
|
||||
default: "all";
|
||||
};
|
||||
|
||||
openSubState(new funkin.ui.transition.StickerSubState({targetState: targetState, stickerSet: stickerSet, stickerPack: stickerPack}));
|
||||
}
|
||||
}
|
||||
if (controls.BACK && !mustNotExit && !isEnding) goBack();
|
||||
|
||||
if (gameOverMusic != null && gameOverMusic.playing)
|
||||
{
|
||||
|
|
@ -345,6 +295,7 @@ class GameOverSubState extends MusicBeatSubState
|
|||
boyfriend.playAnimation('deathLoop' + animationSuffix);
|
||||
}
|
||||
}
|
||||
canInput = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -528,6 +479,31 @@ class GameOverSubState extends MusicBeatSubState
|
|||
}
|
||||
}
|
||||
|
||||
public function goBack()
|
||||
{
|
||||
isEnding = true;
|
||||
blueballed = false;
|
||||
PlayState.instance.deathCounter = 0;
|
||||
// PlayState.seenCutscene = false; // old thing...
|
||||
if (gameOverMusic != null) gameOverMusic.stop();
|
||||
|
||||
if (isChartingMode)
|
||||
{
|
||||
this.close();
|
||||
if (FlxG.sound.music != null) FlxG.sound.music.pause(); // Don't reset song position!
|
||||
PlayState.instance.close(); // This only works because PlayState is a substate!
|
||||
return;
|
||||
}
|
||||
else if (PlayStatePlaylist.isStoryMode)
|
||||
{
|
||||
openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new StoryMenuState(sticker)));
|
||||
}
|
||||
else
|
||||
{
|
||||
openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> FreeplayState.build(sticker)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Play the sound effect that occurs when
|
||||
* boyfriend's testicles get utterly annihilated.
|
||||
|
|
@ -535,6 +511,11 @@ class GameOverSubState extends MusicBeatSubState
|
|||
public static function playBlueBalledSFX():Void
|
||||
{
|
||||
blueballed = true;
|
||||
|
||||
#if mobile
|
||||
if (Preferences.vibration) lime.ui.Haptic.vibrate(500, 1000);
|
||||
#end
|
||||
|
||||
if (Assets.exists(Paths.sound('gameplay/gameover/fnf_loss_sfx' + blueBallSuffix)))
|
||||
{
|
||||
FunkinSound.playOnce(Paths.sound('gameplay/gameover/fnf_loss_sfx' + blueBallSuffix));
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@ import funkin.graphics.FunkinSprite;
|
|||
import funkin.ui.MusicBeatState;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
import funkin.ui.mainmenu.MainMenuState;
|
||||
#if mobile
|
||||
import funkin.mobile.util.TouchUtil;
|
||||
import funkin.mobile.util.SwipeUtil;
|
||||
#end
|
||||
|
||||
class GitarooPause extends MusicBeatState
|
||||
{
|
||||
|
|
@ -57,11 +61,19 @@ class GitarooPause extends MusicBeatState
|
|||
super.create();
|
||||
}
|
||||
|
||||
#if mobile
|
||||
function checkSelectionPress():Bool
|
||||
{
|
||||
var buttonOverlapCheck:Bool = replaySelect ? TouchUtil.overlapsComplex(replayButton) : TouchUtil.overlapsComplex(cancelButton);
|
||||
return buttonOverlapCheck && TouchUtil.justPressed && !SwipeUtil.swipeAny;
|
||||
}
|
||||
#end
|
||||
|
||||
override function update(elapsed:Float):Void
|
||||
{
|
||||
if (controls.UI_LEFT_P || controls.UI_RIGHT_P) changeThing();
|
||||
if (controls.UI_LEFT_P || controls.UI_RIGHT_P #if mobile || SwipeUtil.justSwipedLeft || SwipeUtil.justSwipedRight #end) changeThing();
|
||||
|
||||
if (controls.ACCEPT)
|
||||
if (controls.ACCEPT #if mobile || checkSelectionPress() #end)
|
||||
{
|
||||
if (replaySelect)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -18,6 +18,11 @@ import funkin.graphics.FunkinSprite;
|
|||
import funkin.play.cutscene.VideoCutscene;
|
||||
import funkin.ui.AtlasText;
|
||||
import funkin.ui.MusicBeatSubState;
|
||||
import funkin.ui.transition.StickerSubState;
|
||||
#if mobile
|
||||
import funkin.mobile.util.SwipeUtil;
|
||||
import funkin.mobile.util.TouchUtil;
|
||||
#end
|
||||
|
||||
/**
|
||||
* Parameters for initializing the PauseSubState.
|
||||
|
|
@ -407,16 +412,22 @@ class PauseSubState extends MusicBeatSubState
|
|||
{
|
||||
if (!allowInput) return;
|
||||
|
||||
if (controls.UI_UP_P)
|
||||
// Doing this just so it'd look better i guess.
|
||||
final upP:Bool = controls.UI_UP_P #if mobile || SwipeUtil.swipeUp #end;
|
||||
final downP:Bool = controls.UI_DOWN_P #if mobile || SwipeUtil.swipeDown #end;
|
||||
final accept:Bool = controls.ACCEPT #if mobile
|
||||
|| (TouchUtil.overlapsComplex(menuEntryText.members[currentEntry], camera) && TouchUtil.justReleased && !SwipeUtil.swipeAny) #end;
|
||||
|
||||
if (upP)
|
||||
{
|
||||
changeSelection(-1);
|
||||
}
|
||||
if (controls.UI_DOWN_P)
|
||||
if (downP)
|
||||
{
|
||||
changeSelection(1);
|
||||
}
|
||||
|
||||
if (controls.ACCEPT && !justOpened)
|
||||
if (accept && !justOpened)
|
||||
{
|
||||
currentMenuEntries[currentEntry].callback(this);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,10 @@ import funkin.ui.MusicBeatSubState;
|
|||
import funkin.ui.transition.LoadingState;
|
||||
import funkin.util.SerializerUtil;
|
||||
import haxe.Int64;
|
||||
#if mobile
|
||||
import funkin.mobile.util.TouchUtil;
|
||||
import funkin.mobile.ui.FunkinHitbox;
|
||||
#end
|
||||
#if FEATURE_DISCORD_RPC
|
||||
import funkin.api.discord.DiscordClient;
|
||||
#end
|
||||
|
|
@ -515,6 +519,13 @@ class PlayState extends MusicBeatSubState
|
|||
*/
|
||||
public var comboPopUps:PopUpStuff;
|
||||
|
||||
#if mobile
|
||||
/**
|
||||
* The pause button for the game, only appears in Mobile targets.
|
||||
*/
|
||||
var pauseButton:FunkinSprite;
|
||||
#end
|
||||
|
||||
/**
|
||||
* PROPERTIES
|
||||
*/
|
||||
|
|
@ -657,6 +668,11 @@ class PlayState extends MusicBeatSubState
|
|||
cameraFollowPoint = new FlxObject(0, 0);
|
||||
}
|
||||
|
||||
#if mobile
|
||||
// Force allowScreenTimeout to be disabled
|
||||
lime.system.System.allowScreenTimeout = false;
|
||||
#end
|
||||
|
||||
// Reduce physics accuracy (who cares!!!) to improve animation quality.
|
||||
FlxG.fixedTimestep = false;
|
||||
|
||||
|
|
@ -705,6 +721,11 @@ class PlayState extends MusicBeatSubState
|
|||
initStrumlines();
|
||||
initPopups();
|
||||
|
||||
#if mobile
|
||||
// Initialize the hitbox for mobile controls
|
||||
addHitbox(false);
|
||||
#end
|
||||
|
||||
#if FEATURE_DISCORD_RPC
|
||||
// Initialize Discord Rich Presence.
|
||||
initDiscord();
|
||||
|
|
@ -738,6 +759,25 @@ class PlayState extends MusicBeatSubState
|
|||
startCountdown();
|
||||
}
|
||||
|
||||
// Create the pause button.
|
||||
#if mobile
|
||||
pauseButton = FunkinSprite.createSparrow(0, 0, "fonts/bold");
|
||||
pauseButton.animation.addByPrefix("idle", "(", 24, true);
|
||||
pauseButton.animation.play("idle");
|
||||
pauseButton.color = FlxColor.WHITE;
|
||||
pauseButton.alpha = 0.65;
|
||||
pauseButton.updateHitbox();
|
||||
pauseButton.setPosition((FlxG.width - pauseButton.width) - 40, 3);
|
||||
pauseButton.cameras = [camControls];
|
||||
pauseButton.width *= 2;
|
||||
pauseButton.height *= 2;
|
||||
pauseButton.offset.set(-(pauseButton.width / 4), -(pauseButton.height / 4));
|
||||
add(pauseButton);
|
||||
hitbox.forEachAlive(function(hint:FunkinHint) {
|
||||
hint.deadZones.push(pauseButton);
|
||||
});
|
||||
#end
|
||||
|
||||
// Do this last to prevent beatHit from being called before create() is done.
|
||||
super.create();
|
||||
|
||||
|
|
@ -933,15 +973,23 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
}
|
||||
|
||||
var pauseButtonCheck:Bool = false;
|
||||
var androidPause:Bool = false;
|
||||
// So the player wouldn't miss when pressing the pause utton
|
||||
#if mobile
|
||||
pauseButtonCheck = TouchUtil.overlapsComplex(pauseButton) && TouchUtil.justPressed;
|
||||
#end
|
||||
|
||||
#if android
|
||||
androidPause = FlxG.android.justPressed.BACK;
|
||||
androidPause = FlxG.android.justReleased.BACK;
|
||||
#end
|
||||
|
||||
// Attempt to pause the game.
|
||||
if ((controls.PAUSE || androidPause) && isInCountdown && mayPauseGame && !justUnpaused)
|
||||
if ((controls.PAUSE || androidPause || pauseButtonCheck) && isInCountdown && mayPauseGame && !justUnpaused)
|
||||
{
|
||||
#if mobile
|
||||
pauseButton.alpha = 0;
|
||||
#end
|
||||
var event = new PauseScriptEvent(FlxG.random.bool(1 / 1000));
|
||||
|
||||
dispatchEvent(event);
|
||||
|
|
@ -999,6 +1047,10 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
}
|
||||
|
||||
#if mobile
|
||||
if (justUnpaused) pauseButton.alpha = 0.65;
|
||||
#end
|
||||
|
||||
// Cap health.
|
||||
if (health > Constants.HEALTH_MAX) health = Constants.HEALTH_MAX;
|
||||
if (health < Constants.HEALTH_MIN) health = Constants.HEALTH_MIN;
|
||||
|
|
@ -1112,6 +1164,10 @@ class PlayState extends MusicBeatSubState
|
|||
// Moving notes into position is now done by Strumline.update().
|
||||
if (!isInCutscene) processNotes(elapsed);
|
||||
|
||||
#if mobile
|
||||
if ((VideoCutscene.isPlaying() || isInCutscene) && !pauseButton.visible) pauseButton.visible = true;
|
||||
#end
|
||||
|
||||
justUnpaused = false;
|
||||
}
|
||||
|
||||
|
|
@ -2064,6 +2120,10 @@ class PlayState extends MusicBeatSubState
|
|||
{
|
||||
startingSong = false;
|
||||
|
||||
#if mobile
|
||||
hitbox.visible = true;
|
||||
#end
|
||||
|
||||
if (!overrideMusic && !isGamePaused && currentChart != null)
|
||||
{
|
||||
currentChart.playInst(1.0, currentInstrumental, false);
|
||||
|
|
@ -2868,16 +2928,30 @@ class PlayState extends MusicBeatSubState
|
|||
{
|
||||
if (isGamePaused) return;
|
||||
|
||||
var pauseButtonCheck:Bool = false;
|
||||
var androidPause:Bool = false;
|
||||
|
||||
#if android
|
||||
androidPause = FlxG.android.justPressed.BACK;
|
||||
#end
|
||||
|
||||
#if mobile
|
||||
pauseButtonCheck = TouchUtil.overlapsComplex(pauseButton) && TouchUtil.justPressed;
|
||||
#end
|
||||
|
||||
if (currentConversation != null)
|
||||
{
|
||||
// Pause/unpause may conflict with advancing the conversation!
|
||||
if (controls.CUTSCENE_ADVANCE && !justUnpaused)
|
||||
if ((controls.CUTSCENE_ADVANCE #if mobile || (!pauseButtonCheck && TouchUtil.justPressed) #end) && !justUnpaused)
|
||||
{
|
||||
currentConversation.advanceConversation();
|
||||
}
|
||||
else if (controls.PAUSE && !justUnpaused)
|
||||
else if ((controls.PAUSE || androidPause || pauseButtonCheck) && !justUnpaused)
|
||||
{
|
||||
currentConversation.pauseMusic();
|
||||
#if mobile
|
||||
pauseButton.alpha = 0;
|
||||
#end
|
||||
|
||||
var pauseSubState:FlxSubState = new PauseSubState({mode: Conversation});
|
||||
|
||||
|
|
@ -2891,9 +2965,12 @@ class PlayState extends MusicBeatSubState
|
|||
else if (VideoCutscene.isPlaying())
|
||||
{
|
||||
// This is a video cutscene.
|
||||
if (controls.PAUSE && !justUnpaused)
|
||||
if ((controls.PAUSE || androidPause || pauseButtonCheck) && !justUnpaused)
|
||||
{
|
||||
VideoCutscene.pauseVideo();
|
||||
#if mobile
|
||||
pauseButton.alpha = 0;
|
||||
#end
|
||||
|
||||
var pauseSubState:FlxSubState = new PauseSubState({mode: Cutscene});
|
||||
|
||||
|
|
@ -2930,6 +3007,11 @@ class PlayState extends MusicBeatSubState
|
|||
// Prevent ghost misses while the song is ending.
|
||||
disableKeys = true;
|
||||
|
||||
#if mobile
|
||||
// Hide the buttons while the song is ending.
|
||||
hitbox.visible = pauseButton.visible = false;
|
||||
#end
|
||||
|
||||
// Check if any events want to prevent the song from ending.
|
||||
var event = new ScriptEvent(SONG_END, true);
|
||||
dispatchEvent(event);
|
||||
|
|
@ -3183,6 +3265,11 @@ class PlayState extends MusicBeatSubState
|
|||
criticalFailure = true; // Stop game updates.
|
||||
performCleanup();
|
||||
super.close();
|
||||
|
||||
#if mobile
|
||||
// Syncing allowScreenTimeout with Preferences option.
|
||||
lime.system.System.allowScreenTimeout = Preferences.screenTimeout;
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -32,6 +32,9 @@ import funkin.play.components.ClearPercentCounter;
|
|||
#if FEATURE_NEWGROUNDS
|
||||
import funkin.api.newgrounds.Medals;
|
||||
#end
|
||||
#if mobile
|
||||
import funkin.mobile.util.TouchUtil;
|
||||
#end
|
||||
|
||||
/**
|
||||
* The state for the results screen after a song or week is finished.
|
||||
|
|
@ -716,7 +719,7 @@ class ResultState extends MusicBeatSubState
|
|||
}
|
||||
}
|
||||
|
||||
if (controls.PAUSE || controls.ACCEPT)
|
||||
if (controls.PAUSE || controls.ACCEPT #if mobile || TouchUtil.justPressed #end)
|
||||
{
|
||||
if (_parentState is funkin.ui.debug.results.ResultsDebugSubState)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@ package funkin.save;
|
|||
|
||||
import flixel.util.FlxSave;
|
||||
import funkin.input.Controls.Device;
|
||||
#if mobile
|
||||
import funkin.mobile.ui.FunkinHitbox;
|
||||
#end
|
||||
import funkin.play.scoring.Scoring;
|
||||
import funkin.play.scoring.Scoring.ScoringRank;
|
||||
import funkin.save.migrator.RawSaveData_v1_0_0;
|
||||
|
|
@ -78,6 +81,12 @@ class Save
|
|||
|
||||
public static function getDefault():RawSaveData
|
||||
{
|
||||
#if mobile
|
||||
var refreshRate:Int = FlxG.stage.window.displayMode.refreshRate;
|
||||
|
||||
if (refreshRate < 60) refreshRate = 60;
|
||||
#end
|
||||
|
||||
return {
|
||||
// Version number is an abstract(Array) internally.
|
||||
// This means it copies by reference, so merging save data overides the version number lol.
|
||||
|
|
@ -105,7 +114,7 @@ class Save
|
|||
options:
|
||||
{
|
||||
// Reasonable defaults.
|
||||
framerate: 60,
|
||||
framerate: #if mobile refreshRate #else 60 #end,
|
||||
naughtyness: true,
|
||||
downscroll: false,
|
||||
flashingLights: true,
|
||||
|
|
@ -143,6 +152,16 @@ class Save
|
|||
},
|
||||
},
|
||||
|
||||
#if mobile
|
||||
mobileOptions:
|
||||
{
|
||||
// Reasonable defaults.
|
||||
screenTimeout: false,
|
||||
vibration: true,
|
||||
controlsScheme: FunkinHitboxControlSchemes.FourLanes
|
||||
},
|
||||
#end
|
||||
|
||||
mods:
|
||||
{
|
||||
// No mods enabled.
|
||||
|
|
@ -192,6 +211,18 @@ class Save
|
|||
return data.options;
|
||||
}
|
||||
|
||||
#if mobile
|
||||
/**
|
||||
* NOTE: Modifications will not be saved without calling `Save.flush()`!
|
||||
*/
|
||||
public var mobileOptions(get, never):SaveDataMobileOptions;
|
||||
|
||||
function get_mobileOptions():SaveDataMobileOptions
|
||||
{
|
||||
return data.mobileOptions;
|
||||
}
|
||||
#end
|
||||
|
||||
/**
|
||||
* NOTE: Modifications will not be saved without calling `Save.flush()`!
|
||||
*/
|
||||
|
|
@ -1233,6 +1264,13 @@ typedef RawSaveData =
|
|||
|
||||
var unlocks:SaveDataUnlocks;
|
||||
|
||||
#if mobile
|
||||
/**
|
||||
* The user's preferences for mobile.
|
||||
*/
|
||||
var mobileOptions:SaveDataMobileOptions;
|
||||
#end
|
||||
|
||||
/**
|
||||
* The user's favorited songs in the Freeplay menu,
|
||||
* as a list of song IDs.
|
||||
|
|
@ -1457,6 +1495,30 @@ typedef SaveDataOptions =
|
|||
};
|
||||
};
|
||||
|
||||
#if mobile
|
||||
typedef SaveDataMobileOptions =
|
||||
{
|
||||
/**
|
||||
* If enabled, device will be able to sleep on its own.
|
||||
* @default `false`
|
||||
*/
|
||||
var screenTimeout:Bool;
|
||||
|
||||
/**
|
||||
* If enabled, vibration will be enabled.
|
||||
* @default `true`
|
||||
*/
|
||||
var vibration:Bool;
|
||||
|
||||
/**
|
||||
* Controls scheme for the hitbox.
|
||||
* @default `fourLanes`
|
||||
*/
|
||||
var controlsScheme:String;
|
||||
};
|
||||
|
||||
#end
|
||||
|
||||
/**
|
||||
* An anonymous structure containing a specific player's bound keys.
|
||||
* Each key is an action name and each value is an array of keycodes.
|
||||
|
|
|
|||
|
|
@ -1,10 +1,16 @@
|
|||
package funkin.ui;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.effects.FlxFlicker;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import flixel.util.FlxSignal.FlxTypedSignal;
|
||||
import funkin.audio.FunkinSound;
|
||||
#if mobile
|
||||
import funkin.mobile.util.TouchUtil;
|
||||
import funkin.mobile.util.SwipeUtil;
|
||||
#end
|
||||
import funkin.ui.options.OptionsState.PageName;
|
||||
|
||||
class MenuTypedList<T:MenuListItem> extends FlxTypedGroup<T>
|
||||
{
|
||||
|
|
@ -33,6 +39,16 @@ class MenuTypedList<T:MenuListItem> extends FlxTypedGroup<T>
|
|||
|
||||
// bit awkward because BACK is also a menu control and this doesn't affect that
|
||||
|
||||
#if mobile
|
||||
/** touchBuddy over here helps with the touch input! Because overlap for touch does not account for the graphic, only the hitbox.
|
||||
* And, `FlxG.pixelPerfectOverlap` uses two FlxSprites, so we can't use the `FlxTouch` object */
|
||||
public var touchBuddy:FlxSprite;
|
||||
#end
|
||||
|
||||
/** Only used in Options, basically acts the same as OptionsState's `currentName`, it's the current name of the current page in OptionsState.
|
||||
* Why is it needed? Because touch control's a bitch. Thats why. */
|
||||
public var currentPage:PageName = Options;
|
||||
|
||||
public function new(navControls:NavControls = Vertical, ?wrapMode:WrapMode)
|
||||
{
|
||||
this.navControls = navControls;
|
||||
|
|
@ -47,6 +63,11 @@ class MenuTypedList<T:MenuListItem> extends FlxTypedGroup<T>
|
|||
default: Both;
|
||||
}
|
||||
}
|
||||
|
||||
#if mobile
|
||||
// Make touchBuddy! No need to add them.
|
||||
touchBuddy = new FlxSprite().makeGraphic(10, 10);
|
||||
#end
|
||||
super();
|
||||
}
|
||||
|
||||
|
|
@ -83,15 +104,25 @@ class MenuTypedList<T:MenuListItem> extends FlxTypedGroup<T>
|
|||
|
||||
var wrapX = wrapMode.match(Horizontal | Both);
|
||||
var wrapY = wrapMode.match(Vertical | Both);
|
||||
var newIndex = switch (navControls)
|
||||
{
|
||||
case Vertical: navList(controls.UI_UP_P, controls.UI_DOWN_P, wrapY);
|
||||
case Horizontal: navList(controls.UI_LEFT_P, controls.UI_RIGHT_P, wrapX);
|
||||
case Both: navList(controls.UI_LEFT_P || controls.UI_UP_P, controls.UI_RIGHT_P || controls.UI_DOWN_P, !wrapMode.match(None));
|
||||
|
||||
case Columns(num): navGrid(num, controls.UI_LEFT_P, controls.UI_RIGHT_P, wrapX, controls.UI_UP_P, controls.UI_DOWN_P, wrapY);
|
||||
case Rows(num): navGrid(num, controls.UI_UP_P, controls.UI_DOWN_P, wrapY, controls.UI_LEFT_P, controls.UI_RIGHT_P, wrapX);
|
||||
}
|
||||
var newIndex = 0;
|
||||
|
||||
// Define unified input handlers
|
||||
final inputUp:Bool = controls.UI_UP_P #if mobile || SwipeUtil.swipeUp #end;
|
||||
final inputDown:Bool = controls.UI_DOWN_P #if mobile || SwipeUtil.swipeDown #end;
|
||||
final inputLeft:Bool = controls.UI_LEFT_P #if mobile || SwipeUtil.swipeLeft #end;
|
||||
final inputRight:Bool = controls.UI_RIGHT_P #if mobile || SwipeUtil.swipeRight #end;
|
||||
|
||||
// Keepin' these for keyboard/controller support on mobile platforms
|
||||
newIndex = switch (navControls)
|
||||
{
|
||||
case Vertical: navList(inputUp, inputDown, wrapY);
|
||||
case Horizontal: navList(inputLeft, inputRight, wrapX);
|
||||
case Both: navList(inputLeft || inputUp, inputRight || inputDown, !wrapMode.match(None));
|
||||
|
||||
case Columns(num): navGrid(num, inputLeft, inputRight, wrapX, inputUp, inputDown, wrapY);
|
||||
case Rows(num): navGrid(num, inputUp, inputDown, wrapY, inputLeft, inputRight, wrapX);
|
||||
};
|
||||
|
||||
if (newIndex != selectedIndex)
|
||||
{
|
||||
|
|
@ -99,8 +130,61 @@ class MenuTypedList<T:MenuListItem> extends FlxTypedGroup<T>
|
|||
selectItem(newIndex);
|
||||
}
|
||||
|
||||
#if mobile
|
||||
// Update touch position
|
||||
if (TouchUtil.pressed)
|
||||
{
|
||||
touchBuddy.setPosition(TouchUtil.touch.x, TouchUtil.touch.y);
|
||||
}
|
||||
|
||||
// TODO: Clean this? Does it need to be cleaned? isMainMenuState could be moved to new() instead perhaps.
|
||||
|
||||
final isMainMenuState:Bool = Std.isOfType(FlxG.state, funkin.ui.mainmenu.MainMenuState);
|
||||
|
||||
// Overlap checks for touch
|
||||
final isPixelOverlap:Bool = FlxG.pixelPerfectOverlap(touchBuddy, members[selectedIndex], 0)
|
||||
&& TouchUtil.justReleased
|
||||
&& !SwipeUtil.swipeAny;
|
||||
|
||||
final isRegularOverlap:Bool = TouchUtil.overlaps(members[selectedIndex]) && TouchUtil.justReleased && !SwipeUtil.swipeAny;
|
||||
|
||||
// making sure currentPage is valid and it doesnt fuck up somehow.
|
||||
final pageCheck:Bool = currentPage != null || currentPage != Preferences;
|
||||
|
||||
// The main touch input overlap check
|
||||
final isInputOverlap:Bool = (isMainMenuState && isPixelOverlap) || (!isMainMenuState && isRegularOverlap && pageCheck);
|
||||
|
||||
// refactor da loop
|
||||
if (TouchUtil.pressed)
|
||||
{
|
||||
//
|
||||
final selectedItem = members[selectedIndex];
|
||||
|
||||
for (i in 0...members.length)
|
||||
{
|
||||
final item = members[i];
|
||||
|
||||
// Streamline the checks just alil "memory friendly"
|
||||
final itemOverlaps:Bool = TouchUtil.overlaps(item) && !isMainMenuState;
|
||||
final itemPixelOverlap:Bool = FlxG.pixelPerfectOverlap(touchBuddy, selectedItem, 0) && isMainMenuState;
|
||||
|
||||
if ((itemOverlaps || itemPixelOverlap) && TouchUtil.justReleased && !SwipeUtil.swipeAny)
|
||||
{
|
||||
newIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** The reason why we're using pixelOverlap for MainMenuState is due to the offsets,
|
||||
* overlaps checks for the sprite's hitbox, not the graphic itself.
|
||||
* pixelOverlap however, does that. */
|
||||
|
||||
// Future zack here I have quite literally ZERO ideas other than making invisible objects on each menu item but the issue here is I do not know HOW to do that, its hard traversing this.
|
||||
#end
|
||||
|
||||
// Todo: bypass popup blocker on firefox
|
||||
if (controls.ACCEPT) accept();
|
||||
if (controls.ACCEPT #if mobile || isInputOverlap #end) accept();
|
||||
}
|
||||
|
||||
function navAxis(index:Int, size:Int, prev:Bool, next:Bool, allowWrap:Bool):Int
|
||||
|
|
|
|||
|
|
@ -13,6 +13,12 @@ import funkin.modding.events.ScriptEvent;
|
|||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.util.SortUtil;
|
||||
import funkin.input.Controls;
|
||||
#if mobile
|
||||
import funkin.graphics.FunkinCamera;
|
||||
import funkin.mobile.ui.FunkinHitbox;
|
||||
import funkin.mobile.input.PreciseInputHandler;
|
||||
import funkin.mobile.ui.FunkinBackspace;
|
||||
#end
|
||||
|
||||
/**
|
||||
* MusicBeatState actually represents the core utility FlxState of the game.
|
||||
|
|
@ -56,6 +62,52 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
|
|||
subStateClosed.add(onCloseSubStateComplete);
|
||||
}
|
||||
|
||||
#if mobile
|
||||
public var hitbox:FunkinHitbox;
|
||||
public var backButton:FunkinBackspace;
|
||||
public var camControls:FunkinCamera;
|
||||
|
||||
public function addHitbox(?visible:Bool = true, ?initInput:Bool = true, ?schemeOverride:String = null):Void
|
||||
{
|
||||
if (hitbox != null)
|
||||
{
|
||||
hitbox.kill();
|
||||
remove(hitbox);
|
||||
hitbox.destroy();
|
||||
}
|
||||
|
||||
if (camControls == null)
|
||||
{
|
||||
camControls = new FunkinCamera('camControls');
|
||||
FlxG.cameras.add(camControls, false);
|
||||
camControls.bgColor = 0x0;
|
||||
}
|
||||
|
||||
hitbox = new FunkinHitbox(schemeOverride);
|
||||
hitbox.cameras = [camControls];
|
||||
hitbox.visible = visible;
|
||||
add(hitbox);
|
||||
|
||||
if (initInput) PreciseInputHandler.initializeHitbox(hitbox);
|
||||
}
|
||||
|
||||
public function addBackButton(?xPos:Float = 0, ?yPos:Float = 0, ?color:FlxColor = FlxColor.WHITE, ?onClick:Void->Void = null):Void
|
||||
{
|
||||
if (backButton != null) remove(backButton);
|
||||
|
||||
if (camControls == null)
|
||||
{
|
||||
camControls = new FunkinCamera('camControls');
|
||||
FlxG.cameras.add(camControls, false);
|
||||
camControls.bgColor = 0x0;
|
||||
}
|
||||
|
||||
backButton = new FunkinBackspace(xPos, yPos, color, onClick);
|
||||
backButton.cameras = [camControls];
|
||||
add(backButton);
|
||||
}
|
||||
#end
|
||||
|
||||
override function create()
|
||||
{
|
||||
super.create();
|
||||
|
|
@ -69,6 +121,11 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
|
|||
public override function destroy():Void
|
||||
{
|
||||
super.destroy();
|
||||
|
||||
#if mobile
|
||||
if (camControls != null) FlxG.cameras.remove(camControls);
|
||||
#end
|
||||
|
||||
Conductor.beatHit.remove(this.beatHit);
|
||||
Conductor.stepHit.remove(this.stepHit);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,12 @@ import funkin.modding.PolymodHandler;
|
|||
import funkin.util.SortUtil;
|
||||
import flixel.util.FlxSort;
|
||||
import funkin.input.Controls;
|
||||
#if mobile
|
||||
import funkin.graphics.FunkinCamera;
|
||||
import funkin.mobile.ui.FunkinHitbox;
|
||||
import funkin.mobile.input.PreciseInputHandler;
|
||||
import funkin.mobile.ui.FunkinBackspace;
|
||||
#end
|
||||
|
||||
/**
|
||||
* MusicBeatSubState reincorporates the functionality of MusicBeatState into an FlxSubState.
|
||||
|
|
@ -41,6 +47,52 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler
|
|||
inline function get_controls():Controls
|
||||
return PlayerSettings.player1.controls;
|
||||
|
||||
#if mobile
|
||||
public var hitbox:FunkinHitbox;
|
||||
public var backButton:FunkinBackspace;
|
||||
public var camControls:FunkinCamera;
|
||||
|
||||
public function addHitbox(?visible:Bool = true, ?initInput:Bool = true, ?schemeOverride:String = null):Void
|
||||
{
|
||||
if (hitbox != null)
|
||||
{
|
||||
hitbox.kill();
|
||||
remove(hitbox);
|
||||
hitbox.destroy();
|
||||
}
|
||||
|
||||
if (camControls == null)
|
||||
{
|
||||
camControls = new FunkinCamera('camControls');
|
||||
FlxG.cameras.add(camControls, false);
|
||||
camControls.bgColor = 0x0;
|
||||
}
|
||||
|
||||
hitbox = new FunkinHitbox(schemeOverride);
|
||||
hitbox.cameras = [camControls];
|
||||
hitbox.visible = visible;
|
||||
add(hitbox);
|
||||
|
||||
if (initInput) PreciseInputHandler.initializeHitbox(hitbox);
|
||||
}
|
||||
|
||||
public function addBackButton(?xPos:Float = 0, ?yPos:Float = 0, ?color:FlxColor = FlxColor.WHITE, ?onClick:Void->Void = null):Void
|
||||
{
|
||||
if (backButton != null) remove(backButton);
|
||||
|
||||
if (camControls == null)
|
||||
{
|
||||
camControls = new FunkinCamera('camControls');
|
||||
FlxG.cameras.add(camControls, false);
|
||||
camControls.bgColor = 0x0;
|
||||
}
|
||||
|
||||
backButton = new FunkinBackspace(xPos, yPos, color, onClick);
|
||||
backButton.cameras = [camControls];
|
||||
add(backButton);
|
||||
}
|
||||
#end
|
||||
|
||||
public function new(bgColor:FlxColor = FlxColor.TRANSPARENT)
|
||||
{
|
||||
super();
|
||||
|
|
@ -70,6 +122,11 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler
|
|||
public override function destroy():Void
|
||||
{
|
||||
super.destroy();
|
||||
|
||||
#if mobile
|
||||
if (camControls != null) FlxG.cameras.remove(camControls);
|
||||
#end
|
||||
|
||||
Conductor.beatHit.remove(this.beatHit);
|
||||
Conductor.stepHit.remove(this.stepHit);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import flixel.system.debug.watch.Tracker.TrackerProfile;
|
|||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.util.FlxTimer;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.audio.FunkinSound;
|
||||
import funkin.data.freeplay.player.PlayerRegistry;
|
||||
import funkin.graphics.adobeanimate.FlxAtlasSprite;
|
||||
|
|
@ -34,9 +35,16 @@ import funkin.graphics.FunkinSprite;
|
|||
#if FEATURE_NEWGROUNDS
|
||||
import funkin.api.newgrounds.Medals;
|
||||
#end
|
||||
#if mobile
|
||||
import funkin.mobile.util.TouchUtil;
|
||||
import funkin.mobile.util.SwipeUtil;
|
||||
#end
|
||||
|
||||
class CharSelectSubState extends MusicBeatSubState
|
||||
{
|
||||
// what the actual hell
|
||||
// having a hard time trying to make my changes work so i chose to be less stubborn and just remove them for now. - Zack
|
||||
// Left this here so somebody can remind me
|
||||
var cursor:FlxSprite;
|
||||
|
||||
var cursorBlue:FlxSprite;
|
||||
|
|
@ -87,6 +95,10 @@ class CharSelectSubState extends MusicBeatSubState
|
|||
var bopInfo:FramesJSFLInfo;
|
||||
var blackScreen:FunkinSprite;
|
||||
|
||||
#if mobile
|
||||
var touchBuddy:Null<FlxSprite>;
|
||||
#end
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
|
|
@ -415,6 +427,15 @@ class CharSelectSubState extends MusicBeatSubState
|
|||
|
||||
Save.instance.oldChar = true;
|
||||
});
|
||||
|
||||
#if mobile
|
||||
touchBuddy = new FlxSprite().makeGraphic(10, 10, FlxColor.GREEN);
|
||||
touchBuddy.cameras = [charSelectCam]; // this is stupid but it works.
|
||||
|
||||
// addBackButton(FlxG.width * 0.96, FlxG.height * 0.84, FlxColor.WHITE, goBack);
|
||||
|
||||
// FlxTween.tween(backButton, {x: 824}, FlxG.random.float(0.5, 0.95), {ease: FlxEase.backOut});
|
||||
#end
|
||||
}
|
||||
|
||||
function checkNewChar():Void
|
||||
|
|
@ -439,8 +460,8 @@ class CharSelectSubState extends MusicBeatSubState
|
|||
|
||||
@:privateAccess
|
||||
gfChill.analyzer = new SpectralAnalyzer(FlxG.sound.music._channel.__audioSource, 7, 0.1);
|
||||
#if desktop
|
||||
// On desktop it uses FFT stuff that isn't as optimized as the direct browser stuff we use on HTML5
|
||||
#if !web
|
||||
// On native it uses FFT stuff that isn't as optimized as the direct browser stuff we use on HTML5
|
||||
// So we want to manually change it!
|
||||
@:privateAccess
|
||||
gfChill.analyzer.fftN = 512;
|
||||
|
|
@ -451,6 +472,9 @@ class CharSelectSubState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
var grpIcons:FlxSpriteGroup;
|
||||
#if mobile
|
||||
var grpHitboxes:FlxSpriteGroup;
|
||||
#end
|
||||
var grpXSpread(default, set):Float = 107;
|
||||
var grpYSpread(default, set):Float = 127;
|
||||
var nonLocks = [];
|
||||
|
|
@ -459,6 +483,10 @@ class CharSelectSubState extends MusicBeatSubState
|
|||
{
|
||||
grpIcons = new FlxSpriteGroup();
|
||||
add(grpIcons);
|
||||
#if mobile
|
||||
grpHitboxes = new FlxSpriteGroup();
|
||||
add(grpHitboxes);
|
||||
#end
|
||||
|
||||
FlxG.debugger.addTrackerProfile(new TrackerProfile(FlxSpriteGroup, ["x", "y"]));
|
||||
// FlxG.debugger.track(grpIcons, "iconGrp");
|
||||
|
|
@ -491,6 +519,11 @@ class CharSelectSubState extends MusicBeatSubState
|
|||
|
||||
grpIcons.add(temp);
|
||||
}
|
||||
#if mobile
|
||||
var hitTemp:FlxSprite = new FlxSprite(grpIcons.members[i].x, grpIcons.members[i].y).makeGraphic(64, 64, FlxColor.TRANSPARENT);
|
||||
hitTemp.active = false;
|
||||
grpHitboxes.add(hitTemp);
|
||||
#end
|
||||
}
|
||||
|
||||
updateIconPositions();
|
||||
|
|
@ -516,6 +549,7 @@ class CharSelectSubState extends MusicBeatSubState
|
|||
|
||||
var xThing = (copy - index - 2) * -1;
|
||||
// Look, I'd write better code but I had better aneurysms, my bad - Cheems
|
||||
// felt - Zack
|
||||
cursorY = yThing;
|
||||
cursorX = xThing;
|
||||
|
||||
|
|
@ -593,8 +627,8 @@ class CharSelectSubState extends MusicBeatSubState
|
|||
|
||||
@:privateAccess
|
||||
gfChill.analyzer = new SpectralAnalyzer(FlxG.sound.music._channel.__audioSource, 7, 0.1);
|
||||
#if desktop
|
||||
// On desktop it uses FFT stuff that isn't as optimized as the direct browser stuff we use on HTML5
|
||||
#if !web
|
||||
// On native it uses FFT stuff that isn't as optimized as the direct browser stuff we use on HTML5
|
||||
// So we want to manually change it!
|
||||
@:privateAccess
|
||||
gfChill.analyzer.fftN = 512;
|
||||
|
|
@ -615,8 +649,8 @@ class CharSelectSubState extends MusicBeatSubState
|
|||
|
||||
function updateIconPositions()
|
||||
{
|
||||
grpIcons.x = 450;
|
||||
grpIcons.y = 120;
|
||||
#if mobile grpHitboxes.x = #end grpIcons.x = 450;
|
||||
#if mobile grpHitboxes.y = #end grpIcons.y = 120;
|
||||
for (index => member in grpIcons.members)
|
||||
{
|
||||
var posX:Float = (index % 3);
|
||||
|
|
@ -628,6 +662,20 @@ class CharSelectSubState extends MusicBeatSubState
|
|||
member.x += grpIcons.x;
|
||||
member.y += grpIcons.y;
|
||||
}
|
||||
|
||||
#if mobile
|
||||
for (index => member in grpHitboxes.members)
|
||||
{
|
||||
var posX:Float = (index % 3);
|
||||
var posY:Float = Math.floor(index / 3);
|
||||
|
||||
member.x = posX * grpXSpread;
|
||||
member.y = posY * grpYSpread;
|
||||
|
||||
member.x += grpIcons.x + 40;
|
||||
member.y += grpIcons.y + 40;
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
var sync:Bool = false;
|
||||
|
|
@ -704,18 +752,59 @@ class CharSelectSubState extends MusicBeatSubState
|
|||
var spamLeft:Bool = false;
|
||||
var spamRight:Bool = false;
|
||||
|
||||
#if mobile
|
||||
var mobileDeny:Bool = false;
|
||||
#end
|
||||
|
||||
override public function update(elapsed:Float):Void
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
Conductor.instance.update();
|
||||
|
||||
if (controls.UI_UP_R || controls.UI_DOWN_R || controls.UI_LEFT_R || controls.UI_RIGHT_R) selectSound.pitch = 1;
|
||||
#if mobile
|
||||
var mobileAccept:Bool = false;
|
||||
if (TouchUtil.pressed) touchBuddy.setPosition(TouchUtil.touch.screenX, TouchUtil.touch.screenY);
|
||||
#end
|
||||
|
||||
if (controls.UI_UP_R || controls.UI_DOWN_R || controls.UI_LEFT_R || controls.UI_RIGHT_R #if mobile || TouchUtil.justReleased #end) selectSound.pitch = 1;
|
||||
|
||||
syncAudio(elapsed);
|
||||
|
||||
if (allowInput && !pressedSelect)
|
||||
{
|
||||
//
|
||||
#if mobile
|
||||
if (TouchUtil.pressed)
|
||||
{
|
||||
for (i in 0...grpHitboxes.members.length)
|
||||
{
|
||||
final tempBox:FlxSprite = grpHitboxes.members[i];
|
||||
if (!TouchUtil.overlaps(tempBox)) continue;
|
||||
|
||||
final indexCY:Int = Std.int(i / 3) - 1;
|
||||
final indexCX:Int = (i % 3) - 1;
|
||||
if (indexCY == cursorY && indexCX == cursorX)
|
||||
{
|
||||
// yeah this being separated is necessary
|
||||
// yeah i know it sucks
|
||||
if (TouchUtil.justPressed) mobileAccept = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
cursorX = indexCX;
|
||||
cursorY = indexCY;
|
||||
cursorDenied.visible = false;
|
||||
selectSound.play(true);
|
||||
}
|
||||
|
||||
trace("Index: " + i + ", Row: " + cursorY + ", Column: " + cursorX);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
//
|
||||
if (controls.UI_UP) holdTmrUp += elapsed;
|
||||
if (controls.UI_UP_R)
|
||||
{
|
||||
|
|
@ -805,8 +894,38 @@ class CharSelectSubState extends MusicBeatSubState
|
|||
{
|
||||
curChar = availableChars.get(getCurrentSelected());
|
||||
|
||||
if (allowInput && !pressedSelect && controls.ACCEPT)
|
||||
if (allowInput && pressedSelect && (controls.BACK #if mobile || (mobileDeny && TouchUtil.justPressed) #end))
|
||||
{
|
||||
#if mobile
|
||||
mobileDeny = false;
|
||||
#end
|
||||
cursorConfirmed.visible = false;
|
||||
grpCursors.visible = true;
|
||||
|
||||
FlxTween.globalManager.cancelTweensOf(FlxG.sound.music);
|
||||
FlxTween.tween(FlxG.sound.music, {pitch: 1.0, volume: 1.0}, 1, {ease: FlxEase.quartInOut});
|
||||
playerChill.playAnimation("deselect");
|
||||
gfChill.playAnimation("deselect");
|
||||
pressedSelect = false;
|
||||
FlxTween.tween(FlxG.sound.music, {pitch: 1.0}, 1,
|
||||
{
|
||||
ease: FlxEase.quartInOut,
|
||||
onComplete: (_) -> {
|
||||
if (playerChill.getCurrentAnimation() == "deselect loop start" || playerChill.getCurrentAnimation() == "deselect")
|
||||
{
|
||||
playerChill.playAnimation("idle", true, false, true);
|
||||
gfChill.playAnimation("idle", true, false, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
selectTimer.cancel();
|
||||
}
|
||||
|
||||
if (allowInput && !pressedSelect && (controls.ACCEPT #if mobile || mobileAccept #end))
|
||||
{
|
||||
#if mobile
|
||||
mobileDeny = false;
|
||||
#end
|
||||
spamUp = false;
|
||||
spamDown = false;
|
||||
spamLeft = false;
|
||||
|
|
@ -835,30 +954,11 @@ class CharSelectSubState extends MusicBeatSubState
|
|||
goToFreeplay();
|
||||
});
|
||||
}
|
||||
#if mobile
|
||||
else if (pressedSelect && TouchUtil.justPressed) mobileDeny = true;
|
||||
|
||||
if (allowInput && pressedSelect && controls.BACK)
|
||||
{
|
||||
cursorConfirmed.visible = false;
|
||||
grpCursors.visible = true;
|
||||
|
||||
FlxTween.globalManager.cancelTweensOf(FlxG.sound.music);
|
||||
FlxTween.tween(FlxG.sound.music, {pitch: 1.0, volume: 1.0}, 1, {ease: FlxEase.quartInOut});
|
||||
playerChill.playAnimation("deselect");
|
||||
gfChill.playAnimation("deselect");
|
||||
pressedSelect = false;
|
||||
FlxTween.tween(FlxG.sound.music, {pitch: 1.0}, 1,
|
||||
{
|
||||
ease: FlxEase.quartInOut,
|
||||
onComplete: (_) -> {
|
||||
if (playerChill.getCurrentAnimation() == "deselect loop start" || playerChill.getCurrentAnimation() == "deselect")
|
||||
{
|
||||
playerChill.playAnimation("idle", true, false, true);
|
||||
gfChill.playAnimation("idle", true, false, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
selectTimer.cancel();
|
||||
}
|
||||
mobileAccept = false;
|
||||
#end
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -866,7 +966,8 @@ class CharSelectSubState extends MusicBeatSubState
|
|||
|
||||
gfChill.visible = false;
|
||||
|
||||
if (allowInput && controls.ACCEPT)
|
||||
// what does this do -zack
|
||||
if (allowInput && controls.ACCEPT #if mobile || TouchUtil.overlapsComplex(cursor) && TouchUtil.justPressed #end)
|
||||
{
|
||||
cursorDenied.visible = true;
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@ import funkin.play.components.HealthIcon;
|
|||
import funkin.ui.freeplay.charselect.PlayableCharacter;
|
||||
import funkin.data.freeplay.player.PlayerRegistry;
|
||||
import funkin.ui.mainmenu.MainMenuState;
|
||||
#if mobile
|
||||
import funkin.mobile.util.TouchUtil;
|
||||
#end
|
||||
|
||||
using flixel.util.FlxSpriteUtil;
|
||||
|
||||
|
|
@ -109,7 +112,7 @@ class CharacterUnlockState extends MusicBeatState
|
|||
{
|
||||
super.update(elapsed);
|
||||
|
||||
if (controls.ACCEPT || controls.BACK && !busy)
|
||||
if (controls.ACCEPT || controls.BACK #if mobile || TouchUtil.justPressed #end && !busy)
|
||||
{
|
||||
busy = true;
|
||||
startClose();
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ class CreditsDataMacro
|
|||
#if macro
|
||||
static function fetchJSON():Null<String>
|
||||
{
|
||||
return sys.io.File.getContent(CreditsDataHandler.CREDITS_DATA_PATH);
|
||||
return sys.io.File.getContent(#if ios '../../../../../' + #end CreditsDataHandler.CREDITS_DATA_PATH);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import funkin.audio.FunkinSound;
|
|||
import flixel.FlxSprite;
|
||||
import funkin.ui.mainmenu.MainMenuState;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
import funkin.mobile.util.TouchUtil;
|
||||
|
||||
/**
|
||||
* The state used to display the credits scroll.
|
||||
|
|
@ -117,6 +118,10 @@ class CreditsState extends MusicBeatState
|
|||
loop: true
|
||||
});
|
||||
FlxG.sound.music.fadeIn(6, 0, 0.8);
|
||||
|
||||
#if mobile
|
||||
addBackButton(FlxG.width * 0.77, FlxG.height * 0.85, FlxColor.WHITE, exit);
|
||||
#end
|
||||
}
|
||||
|
||||
function buildCreditsGroup():Void
|
||||
|
|
@ -177,7 +182,7 @@ class CreditsState extends MusicBeatState
|
|||
if (!scrollPaused)
|
||||
{
|
||||
// TODO: Replace with whatever the special note button is.
|
||||
if (controls.ACCEPT || FlxG.keys.pressed.SPACE)
|
||||
if (controls.ACCEPT || FlxG.keys.pressed.SPACE #if mobile || TouchUtil.pressed && !TouchUtil.overlaps(backButton) #end)
|
||||
{
|
||||
// Move the whole group.
|
||||
creditsGroup.y -= CREDITS_SCROLL_FAST_SPEED * elapsed;
|
||||
|
|
|
|||
|
|
@ -130,7 +130,6 @@ class DebugMenuSubState extends MusicBeatSubState
|
|||
function openStageEditor()
|
||||
{
|
||||
trace('Stage Editor');
|
||||
FlxG.switchState(() -> new funkin.ui.debug.stageeditor.StageEditorState());
|
||||
}
|
||||
|
||||
function openTestResultsScreen():Void
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ import flixel.util.FlxColor;
|
|||
import flixel.util.FlxTimer;
|
||||
import flixel.text.FlxText;
|
||||
import flixel.text.FlxText.FlxTextAlign;
|
||||
#if mobile
|
||||
import funkin.mobile.util.TouchUtil;
|
||||
#end
|
||||
|
||||
@:nullSafety
|
||||
class CapsuleOptionsMenu extends FlxSpriteGroup
|
||||
|
|
@ -23,6 +26,8 @@ class CapsuleOptionsMenu extends FlxSpriteGroup
|
|||
var currentInstrumental:FlxText;
|
||||
|
||||
var busy:Bool = false;
|
||||
var leftArrow:InstrumentalSelector;
|
||||
var rightArrow:InstrumentalSelector;
|
||||
|
||||
public function new(parent:FreeplayState, x:Float = 0, y:Float = 0, instIds:Array<String>):Void
|
||||
{
|
||||
|
|
@ -41,8 +46,8 @@ class CapsuleOptionsMenu extends FlxSpriteGroup
|
|||
currentInstrumental.setFormat('VCR OSD Mono', 40, FlxTextAlign.CENTER, true);
|
||||
|
||||
final PAD = 4;
|
||||
var leftArrow = new InstrumentalSelector(parent, PAD, 30, false, parent.getControls());
|
||||
var rightArrow = new InstrumentalSelector(parent, capsuleMenuBG.width - leftArrow.width - PAD, 30, true, parent.getControls());
|
||||
leftArrow = new InstrumentalSelector(parent, PAD, 30, false, parent.getControls());
|
||||
rightArrow = new InstrumentalSelector(parent, capsuleMenuBG.width - leftArrow.width - PAD, 30, true, parent.getControls());
|
||||
|
||||
var label:FlxText = new FlxText(0, 5, capsuleMenuBG.width, 'INSTRUMENTAL');
|
||||
label.setFormat('VCR OSD Mono', 24, FlxTextAlign.CENTER, true);
|
||||
|
|
@ -73,23 +78,24 @@ class CapsuleOptionsMenu extends FlxSpriteGroup
|
|||
if (!busy)
|
||||
{
|
||||
@:privateAccess
|
||||
if (parent.controls.BACK)
|
||||
if (parent.controls.BACK #if mobile || TouchUtil.overlapsComplex(parent.backButton) && TouchUtil.justPressed #end)
|
||||
{
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (parent.getControls().UI_LEFT_P)
|
||||
if (parent.getControls().UI_LEFT_P #if mobile || TouchUtil.overlapsComplex(leftArrow) && TouchUtil.justPressed #end)
|
||||
{
|
||||
currentInstrumentalIndex = (currentInstrumentalIndex + 1) % instrumentalIds.length;
|
||||
changedInst = true;
|
||||
}
|
||||
if (parent.getControls().UI_RIGHT_P)
|
||||
if (parent.getControls().UI_RIGHT_P #if mobile || TouchUtil.overlapsComplex(rightArrow) && TouchUtil.justPressed #end)
|
||||
{
|
||||
currentInstrumentalIndex = (currentInstrumentalIndex - 1 + instrumentalIds.length) % instrumentalIds.length;
|
||||
changedInst = true;
|
||||
}
|
||||
if (parent.getControls().ACCEPT)
|
||||
if (parent.getControls().ACCEPT #if mobile || ((TouchUtil.overlapsComplex(currentInstrumental) && TouchUtil.justPressed)
|
||||
&& !(TouchUtil.overlapsComplex(leftArrow) || TouchUtil.overlapsComplex(rightArrow))) #end)
|
||||
{
|
||||
busy = true;
|
||||
onConfirm(instrumentalIds[currentInstrumentalIndex] ?? '');
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import flixel.FlxCamera;
|
|||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
||||
import flixel.input.touch.FlxTouch;
|
||||
import flixel.math.FlxAngle;
|
||||
import flixel.math.FlxMath;
|
||||
import flixel.math.FlxPoint;
|
||||
|
|
@ -48,6 +47,10 @@ import funkin.data.freeplay.style.FreeplayStyleRegistry;
|
|||
#if FEATURE_DISCORD_RPC
|
||||
import funkin.api.discord.DiscordClient;
|
||||
#end
|
||||
#if mobile
|
||||
import funkin.mobile.util.TouchUtil;
|
||||
import funkin.mobile.util.SwipeUtil;
|
||||
#end
|
||||
|
||||
/**
|
||||
* The state for the freeplay menu, allowing the player to select any song to play.
|
||||
|
|
@ -132,6 +135,9 @@ class FreeplayState extends MusicBeatSubState
|
|||
var curPlaying:Bool = false;
|
||||
|
||||
var dj:Null<FreeplayDJ> = null;
|
||||
#if mobile
|
||||
var djHitbox:FlxSprite = new FlxSprite(58, 358);
|
||||
#end
|
||||
|
||||
var ostName:FlxText;
|
||||
var albumRoll:AlbumRoll;
|
||||
|
|
@ -141,6 +147,9 @@ class FreeplayState extends MusicBeatSubState
|
|||
var letterSort:LetterSort;
|
||||
var exitMovers:ExitMoverData = new Map();
|
||||
|
||||
var diffSelLeft:Null<DifficultySelector> = null;
|
||||
var diffSelRight:Null<DifficultySelector> = null;
|
||||
|
||||
var exitMoversCharSel:ExitMoverData = new Map();
|
||||
|
||||
var stickerSubState:Null<StickerSubState> = null;
|
||||
|
|
@ -343,6 +352,13 @@ class FreeplayState extends MusicBeatSubState
|
|||
backingImage.shader = angleMaskShader;
|
||||
backingImage.visible = false;
|
||||
|
||||
#if mobile
|
||||
djHitbox = djHitbox.makeGraphic(400, 400, FlxColor.TRANSPARENT);
|
||||
if (dj != null) djHitbox.cameras = dj.cameras;
|
||||
djHitbox.active = false;
|
||||
add(djHitbox);
|
||||
#end
|
||||
|
||||
var blackOverlayBullshitLOLXD:FlxSprite = new FlxSprite(FlxG.width).makeGraphic(Std.int(backingImage.width), Std.int(backingImage.height), FlxColor.BLACK);
|
||||
add(blackOverlayBullshitLOLXD); // used to mask the text lol!
|
||||
|
||||
|
|
@ -431,7 +447,11 @@ class FreeplayState extends MusicBeatSubState
|
|||
charSelectHint.alignment = CENTER;
|
||||
charSelectHint.font = "5by7";
|
||||
charSelectHint.color = 0xFF5F5F5F;
|
||||
#if mobile
|
||||
charSelectHint.text = 'Press on the DJ to change characters';
|
||||
#else
|
||||
charSelectHint.text = 'Press [ ${controls.getDialogueNameFromControl(FREEPLAY_CHAR_SELECT, true)} ] to change characters';
|
||||
#end
|
||||
charSelectHint.y -= 100;
|
||||
FlxTween.tween(charSelectHint, {y: charSelectHint.y + 100}, 0.8, {ease: FlxEase.quartOut});
|
||||
|
||||
|
|
@ -537,12 +557,20 @@ class FreeplayState extends MusicBeatSubState
|
|||
wait: 0.1
|
||||
});
|
||||
|
||||
var diffSelLeft:DifficultySelector = new DifficultySelector(this, 20, grpDifficulties.y - 10, false, controls, styleData);
|
||||
var diffSelRight:DifficultySelector = new DifficultySelector(this, 325, grpDifficulties.y - 10, true, controls, styleData);
|
||||
diffSelLeft.visible = false;
|
||||
diffSelRight.visible = false;
|
||||
add(diffSelLeft);
|
||||
add(diffSelRight);
|
||||
diffSelLeft = new DifficultySelector(this, 20, grpDifficulties.y - 10, false, controls, styleData);
|
||||
diffSelRight = new DifficultySelector(this, 325, grpDifficulties.y - 10, true, controls, styleData);
|
||||
|
||||
if (diffSelLeft != null)
|
||||
{
|
||||
diffSelLeft.visible = false;
|
||||
add(diffSelLeft);
|
||||
}
|
||||
|
||||
if (diffSelRight != null)
|
||||
{
|
||||
diffSelRight.visible = false;
|
||||
add(diffSelRight);
|
||||
}
|
||||
|
||||
// putting these here to fix the layering
|
||||
add(overhangStuff);
|
||||
|
|
@ -581,22 +609,25 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
FlxTween.tween(grpDifficulties, {x: 90}, 0.6, {ease: FlxEase.quartOut});
|
||||
|
||||
diffSelLeft.visible = true;
|
||||
diffSelRight.visible = true;
|
||||
if (diffSelLeft != null) diffSelLeft.visible = true;
|
||||
if (diffSelRight != null) diffSelRight.visible = true;
|
||||
letterSort.visible = true;
|
||||
|
||||
exitMovers.set([diffSelLeft, diffSelRight],
|
||||
{
|
||||
x: -diffSelLeft.width * 2,
|
||||
speed: 0.26
|
||||
});
|
||||
if (diffSelLeft != null && diffSelRight != null)
|
||||
{
|
||||
exitMovers.set([diffSelLeft, diffSelRight],
|
||||
{
|
||||
x: -diffSelLeft.width * 2,
|
||||
speed: 0.26
|
||||
});
|
||||
|
||||
exitMoversCharSel.set([diffSelLeft, diffSelRight],
|
||||
{
|
||||
y: -270,
|
||||
speed: 0.8,
|
||||
wait: 0.1
|
||||
});
|
||||
exitMoversCharSel.set([diffSelLeft, diffSelRight],
|
||||
{
|
||||
y: -270,
|
||||
speed: 0.8,
|
||||
wait: 0.1
|
||||
});
|
||||
}
|
||||
|
||||
new FlxTimer().start(1 / 24, function(handShit) {
|
||||
fnfHighscoreSpr.visible = true;
|
||||
|
|
@ -659,6 +690,12 @@ class FreeplayState extends MusicBeatSubState
|
|||
rankBg.cameras = [rankCamera];
|
||||
rankBg.alpha = 0;
|
||||
|
||||
#if mobile
|
||||
addBackButton(FlxG.width * 0.96, FlxG.height * 0.84, FlxColor.WHITE, goBack);
|
||||
|
||||
FlxTween.tween(backButton, {x: 824}, FlxG.random.float(0.5, 0.95), {ease: FlxEase.backOut});
|
||||
#end
|
||||
|
||||
if (prepForNewRank)
|
||||
{
|
||||
rankCamera.fade(0xFF000000, 0, false, null, true);
|
||||
|
|
@ -761,6 +798,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
funnyMenu.forcePosition();
|
||||
|
||||
grpCapsules.add(funnyMenu);
|
||||
// add(funnyMenu.theActualHitbox);
|
||||
}
|
||||
|
||||
FlxG.console.registerFunction('changeSelection', changeSelection);
|
||||
|
|
@ -1155,6 +1193,11 @@ class FreeplayState extends MusicBeatSubState
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
#if mobile
|
||||
FlxTween.tween(backButton, {y: FlxG.height, alpha: 0.0001}, FlxG.random.float(0.2, 0.85), {ease: FlxEase.backIn});
|
||||
#end
|
||||
|
||||
fadeShader.fade(1.0, 0.0, 0.8, {ease: FlxEase.quadIn});
|
||||
FlxG.sound.music?.fadeOut(0.9, 0);
|
||||
new FlxTimer().start(0.9, _ -> {
|
||||
|
|
@ -1252,8 +1295,6 @@ class FreeplayState extends MusicBeatSubState
|
|||
var dyTouch:Float = 0;
|
||||
var velTouch:Float = 0;
|
||||
|
||||
var touchTimer:Float = 0;
|
||||
|
||||
var initTouchPos:FlxPoint = new FlxPoint();
|
||||
|
||||
var spamTimer:Float = 0;
|
||||
|
|
@ -1302,7 +1343,8 @@ class FreeplayState extends MusicBeatSubState
|
|||
}
|
||||
#end // ^<-- FEATURE_DEBUG_FUNCTIONS
|
||||
|
||||
if (controls.FREEPLAY_CHAR_SELECT && !busy)
|
||||
if ((controls.FREEPLAY_CHAR_SELECT #if mobile || (TouchUtil.overlaps(djHitbox) && TouchUtil.justReleased && !SwipeUtil.swipeAny) #end)
|
||||
&& !busy)
|
||||
{
|
||||
tryOpenCharSelect();
|
||||
}
|
||||
|
|
@ -1414,83 +1456,27 @@ class FreeplayState extends MusicBeatSubState
|
|||
{
|
||||
if (busy) return;
|
||||
|
||||
var upP:Bool = controls.UI_UP_P;
|
||||
var downP:Bool = controls.UI_DOWN_P;
|
||||
var accepted:Bool = controls.ACCEPT;
|
||||
|
||||
if (FlxG.onMobile)
|
||||
{
|
||||
for (touch in FlxG.touches.list)
|
||||
{
|
||||
if (touch.justPressed)
|
||||
{
|
||||
initTouchPos.set(touch.screenX, touch.screenY);
|
||||
}
|
||||
if (touch.pressed)
|
||||
{
|
||||
var dx:Float = initTouchPos.x - touch.screenX;
|
||||
var dy:Float = initTouchPos.y - touch.screenY;
|
||||
|
||||
var angle:Float = Math.atan2(dy, dx);
|
||||
var length:Float = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
FlxG.watch.addQuick('LENGTH', length);
|
||||
FlxG.watch.addQuick('ANGLE', Math.round(FlxAngle.asDegrees(angle)));
|
||||
}
|
||||
}
|
||||
|
||||
if (FlxG.touches.getFirst() != null)
|
||||
{
|
||||
if (touchTimer >= 1.5) accepted = true;
|
||||
|
||||
touchTimer += elapsed;
|
||||
var touch:FlxTouch = FlxG.touches.getFirst();
|
||||
|
||||
velTouch = Math.abs((touch.screenY - dyTouch)) / 50;
|
||||
|
||||
dyTouch = touch.screenY - touchY;
|
||||
dxTouch = touch.screenX - touchX;
|
||||
|
||||
if (touch.justPressed)
|
||||
{
|
||||
touchY = touch.screenY;
|
||||
dyTouch = 0;
|
||||
velTouch = 0;
|
||||
|
||||
touchX = touch.screenX;
|
||||
dxTouch = 0;
|
||||
}
|
||||
|
||||
if (Math.abs(dxTouch) >= 100)
|
||||
{
|
||||
touchX = touch.screenX;
|
||||
if (dxTouch != 0) dxTouch < 0 ? changeDiff(1) : changeDiff(-1);
|
||||
}
|
||||
|
||||
if (Math.abs(dyTouch) >= 100)
|
||||
{
|
||||
touchY = touch.screenY;
|
||||
|
||||
if (dyTouch != 0) dyTouch < 0 ? changeSelection(1) : changeSelection(-1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
touchTimer = 0;
|
||||
}
|
||||
}
|
||||
final upP:Bool = (controls.UI_UP_P #if mobile || SwipeUtil.swipeUp #end);
|
||||
final downP:Bool = (controls.UI_DOWN_P #if mobile || SwipeUtil.swipeDown #end);
|
||||
final accepted:Bool = controls.ACCEPT;
|
||||
|
||||
#if mobile
|
||||
for (touch in FlxG.touches.list)
|
||||
// Nested as fuck
|
||||
@:nullSafety(Off)
|
||||
if (TouchUtil.justReleased && !SwipeUtil.swipeAny && !TouchUtil.overlapsComplex(diffSelRight))
|
||||
{
|
||||
if (touch.justPressed)
|
||||
for (i in 0...grpCapsules.members.length)
|
||||
{
|
||||
// accepted = true;
|
||||
final capsuleHit:FlxSprite = grpCapsules.members[i].theActualHitbox;
|
||||
if (!TouchUtil.overlaps(capsuleHit)) continue;
|
||||
|
||||
(i == curSelected) ? grpCapsules.members[i].onConfirm() : changeSelection(i - curSelected);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
if ((controls.UI_UP || controls.UI_DOWN))
|
||||
if (upP || downP)
|
||||
{
|
||||
if (spamming)
|
||||
{
|
||||
|
|
@ -1498,7 +1484,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
{
|
||||
spamTimer = 0;
|
||||
|
||||
if (controls.UI_UP)
|
||||
if (upP)
|
||||
{
|
||||
changeSelection(-1);
|
||||
}
|
||||
|
|
@ -1514,7 +1500,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
}
|
||||
else if (spamTimer <= 0)
|
||||
{
|
||||
if (controls.UI_UP)
|
||||
if (upP)
|
||||
{
|
||||
changeSelection(-1);
|
||||
}
|
||||
|
|
@ -1533,106 +1519,49 @@ class FreeplayState extends MusicBeatSubState
|
|||
spamTimer = 0;
|
||||
}
|
||||
|
||||
#if !html5
|
||||
if (FlxG.mouse.wheel != 0)
|
||||
{
|
||||
if (dj != null) dj.resetAFKTimer();
|
||||
changeSelection(-Math.round(FlxG.mouse.wheel));
|
||||
}
|
||||
#else
|
||||
if (FlxG.mouse.wheel < 0)
|
||||
{
|
||||
if (dj != null) dj.resetAFKTimer();
|
||||
changeSelection(-Math.round(FlxG.mouse.wheel / 8));
|
||||
}
|
||||
else if (FlxG.mouse.wheel > 0)
|
||||
{
|
||||
if (dj != null) dj.resetAFKTimer();
|
||||
changeSelection(-Math.round(FlxG.mouse.wheel / 8));
|
||||
}
|
||||
#if desktop
|
||||
final wheelAmount:Float = #if !html5 FlxG.mouse.wheel #else FlxG.mouse.wheel / 8 #end;
|
||||
#elseif mobile
|
||||
final wheelAmount:Float = FlxG.touches.horizontalWheel / 2;
|
||||
#end
|
||||
|
||||
if (controls.UI_LEFT_P)
|
||||
if (wheelAmount != 0 #if mobile && !TouchUtil.pressed #end)
|
||||
{
|
||||
if (dj != null) dj.resetAFKTimer();
|
||||
changeSelection(-Math.round(wheelAmount));
|
||||
}
|
||||
|
||||
// FORGIVE ME FOR NOT PLACING THESE IN DifficultySelector BUT IT JUST DIDN'T WORK RIGHT
|
||||
if (controls.UI_LEFT_P #if mobile || (diffSelLeft != null && TouchUtil.overlapsComplex(diffSelLeft) && TouchUtil.justPressed) #end)
|
||||
{
|
||||
if (dj != null) dj.resetAFKTimer();
|
||||
changeDiff(-1);
|
||||
generateSongList(currentFilter, true);
|
||||
#if mobile
|
||||
if (diffSelLeft != null) diffSelLeft.setPress(true);
|
||||
#end
|
||||
}
|
||||
if (controls.UI_RIGHT_P)
|
||||
if (controls.UI_RIGHT_P #if mobile || (diffSelRight != null && TouchUtil.overlapsComplex(diffSelRight) && TouchUtil.justPressed) #end)
|
||||
{
|
||||
if (dj != null) dj.resetAFKTimer();
|
||||
changeDiff(1);
|
||||
generateSongList(currentFilter, true);
|
||||
#if mobile
|
||||
if (diffSelRight != null) diffSelRight.setPress(true);
|
||||
#end
|
||||
}
|
||||
|
||||
#if mobile
|
||||
if (diffSelLeft != null && diffSelRight != null && TouchUtil.justReleased)
|
||||
{
|
||||
diffSelRight.setPress(false);
|
||||
diffSelLeft.setPress(false);
|
||||
}
|
||||
#end
|
||||
|
||||
if (controls.BACK && !busy)
|
||||
{
|
||||
busy = true;
|
||||
FlxTween.globalManager.clear();
|
||||
FlxTimer.globalManager.clear();
|
||||
if (dj != null) dj.onIntroDone.removeAll();
|
||||
|
||||
FunkinSound.playOnce(Paths.sound('cancelMenu'));
|
||||
|
||||
var longestTimer:Float = 0;
|
||||
|
||||
backingCard?.disappear();
|
||||
|
||||
for (grpSpr in exitMovers.keys())
|
||||
{
|
||||
var moveData:Null<MoveData> = exitMovers.get(grpSpr);
|
||||
if (moveData == null) continue;
|
||||
|
||||
for (spr in grpSpr)
|
||||
{
|
||||
if (spr == null) continue;
|
||||
|
||||
var funnyMoveShit:MoveData = moveData;
|
||||
|
||||
var moveDataX = funnyMoveShit.x ?? spr.x;
|
||||
var moveDataY = funnyMoveShit.y ?? spr.y;
|
||||
var moveDataSpeed = funnyMoveShit.speed ?? 0.2;
|
||||
var moveDataWait = funnyMoveShit.wait ?? 0.0;
|
||||
|
||||
FlxTween.tween(spr, {x: moveDataX, y: moveDataY}, moveDataSpeed, {ease: FlxEase.expoIn});
|
||||
|
||||
longestTimer = Math.max(longestTimer, moveDataSpeed + moveDataWait);
|
||||
}
|
||||
}
|
||||
|
||||
for (caps in grpCapsules.members)
|
||||
{
|
||||
caps.doJumpIn = false;
|
||||
caps.doLerp = false;
|
||||
caps.doJumpOut = true;
|
||||
}
|
||||
|
||||
if (Type.getClass(_parentState) == MainMenuState)
|
||||
{
|
||||
_parentState.persistentUpdate = false;
|
||||
_parentState.persistentDraw = true;
|
||||
}
|
||||
|
||||
new FlxTimer().start(longestTimer, (_) -> {
|
||||
FlxTransitionableState.skipNextTransIn = true;
|
||||
FlxTransitionableState.skipNextTransOut = true;
|
||||
if (Type.getClass(_parentState) == MainMenuState)
|
||||
{
|
||||
FunkinSound.playMusic('freakyMenu',
|
||||
{
|
||||
overrideExisting: true,
|
||||
restartTrack: false,
|
||||
// Continue playing this music between states, until a different music track gets played.
|
||||
persist: true
|
||||
});
|
||||
FlxG.sound.music.fadeIn(4.0, 0.0, 1.0);
|
||||
close();
|
||||
}
|
||||
else
|
||||
{
|
||||
FlxG.switchState(() -> new MainMenuState());
|
||||
}
|
||||
});
|
||||
goBack();
|
||||
}
|
||||
|
||||
if (accepted && !busy)
|
||||
|
|
@ -1651,9 +1580,80 @@ class FreeplayState extends MusicBeatSubState
|
|||
public override function destroy():Void
|
||||
{
|
||||
super.destroy();
|
||||
// remove and destroy freeplay camera
|
||||
FlxG.cameras.remove(funnyCam);
|
||||
}
|
||||
|
||||
function goBack():Void
|
||||
{
|
||||
busy = true;
|
||||
FlxTween.globalManager.clear();
|
||||
FlxTimer.globalManager.clear();
|
||||
if (dj != null) dj.onIntroDone.removeAll();
|
||||
|
||||
FunkinSound.playOnce(Paths.sound('cancelMenu'));
|
||||
|
||||
var longestTimer:Float = 0;
|
||||
|
||||
backingCard?.disappear();
|
||||
|
||||
for (grpSpr in exitMovers.keys())
|
||||
{
|
||||
var moveData:Null<MoveData> = exitMovers.get(grpSpr);
|
||||
if (moveData == null) continue;
|
||||
|
||||
for (spr in grpSpr)
|
||||
{
|
||||
if (spr == null) continue;
|
||||
|
||||
var funnyMoveShit:MoveData = moveData;
|
||||
|
||||
var moveDataX = funnyMoveShit.x ?? spr.x;
|
||||
var moveDataY = funnyMoveShit.y ?? spr.y;
|
||||
var moveDataSpeed = funnyMoveShit.speed ?? 0.2;
|
||||
var moveDataWait = funnyMoveShit.wait ?? 0.0;
|
||||
|
||||
FlxTween.tween(spr, {x: moveDataX, y: moveDataY}, moveDataSpeed, {ease: FlxEase.expoIn});
|
||||
|
||||
longestTimer = Math.max(longestTimer, moveDataSpeed + moveDataWait);
|
||||
}
|
||||
}
|
||||
|
||||
for (caps in grpCapsules.members)
|
||||
{
|
||||
caps.doJumpIn = false;
|
||||
caps.doLerp = false;
|
||||
caps.doJumpOut = true;
|
||||
}
|
||||
|
||||
if (Type.getClass(_parentState) == MainMenuState)
|
||||
{
|
||||
_parentState.persistentUpdate = false;
|
||||
_parentState.persistentDraw = true;
|
||||
}
|
||||
|
||||
new FlxTimer().start(longestTimer, (_) -> {
|
||||
FlxTransitionableState.skipNextTransIn = true;
|
||||
FlxTransitionableState.skipNextTransOut = true;
|
||||
if (Type.getClass(_parentState) == MainMenuState)
|
||||
{
|
||||
FunkinSound.playMusic('freakyMenu',
|
||||
{
|
||||
overrideExisting: true,
|
||||
restartTrack: false,
|
||||
// Continue playing this music between states, until a different music track gets played.
|
||||
persist: true
|
||||
});
|
||||
FlxG.sound.music.fadeIn(4.0, 0.0, 1.0);
|
||||
close();
|
||||
}
|
||||
else
|
||||
{
|
||||
FlxG.switchState(() -> new MainMenuState());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* changeDiff is the root of both difficulty and variation changes/management.
|
||||
* It will check the difficulty of the current variation, all available variations, and all available difficulties per variation.
|
||||
|
|
@ -1663,7 +1663,6 @@ class FreeplayState extends MusicBeatSubState
|
|||
*/
|
||||
function changeDiff(change:Int = 0, force:Bool = false):Void
|
||||
{
|
||||
touchTimer = 0;
|
||||
var previousVariation:String = currentVariation;
|
||||
|
||||
// Available variations for current character. We get this since bf is usually `default` variation, and `pico` is `pico`
|
||||
|
|
@ -2154,6 +2153,24 @@ class DifficultySelector extends FlxSprite
|
|||
super.update(elapsed);
|
||||
}
|
||||
|
||||
#if mobile
|
||||
public function setPress(press:Bool):Void
|
||||
{
|
||||
if (!press)
|
||||
{
|
||||
scale.x = scale.y = 1;
|
||||
whiteShader.colorSet = false;
|
||||
updateHitbox();
|
||||
}
|
||||
else
|
||||
{
|
||||
offset.y -= 5;
|
||||
whiteShader.colorSet = true;
|
||||
scale.x = scale.y = 0.5;
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
function moveShitDown():Void
|
||||
{
|
||||
offset.y -= 5;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@ import flixel.FlxSprite;
|
|||
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.input.Controls;
|
||||
#if mobile
|
||||
import funkin.mobile.util.SwipeUtil;
|
||||
import funkin.mobile.util.TouchUtil;
|
||||
#end
|
||||
import funkin.graphics.adobeanimate.FlxAtlasSprite;
|
||||
|
||||
class LetterSort extends FlxTypedSpriteGroup<FlxSprite>
|
||||
|
|
@ -21,6 +25,10 @@ class LetterSort extends FlxTypedSpriteGroup<FlxSprite>
|
|||
|
||||
public var inputEnabled:Bool = true;
|
||||
|
||||
#if mobile
|
||||
var swipeBounds:FlxSprite;
|
||||
#end
|
||||
|
||||
public function new(x, y)
|
||||
{
|
||||
super(x, y);
|
||||
|
|
@ -60,7 +68,12 @@ class LetterSort extends FlxTypedSpriteGroup<FlxSprite>
|
|||
rightArrow = new FlxSprite(380, 15).loadGraphic(Paths.image("freeplay/miniArrow"));
|
||||
|
||||
// rightArrow.animation.play("arrow");
|
||||
add(rightArrow);
|
||||
|
||||
#if mobile
|
||||
swipeBounds = new FlxSprite(-20, -20).makeGraphic(420, 95, FlxColor.TRANSPARENT);
|
||||
swipeBounds.active = false;
|
||||
add(swipeBounds);
|
||||
#end
|
||||
|
||||
changeSelection(0);
|
||||
}
|
||||
|
|
@ -76,8 +89,8 @@ class LetterSort extends FlxTypedSpriteGroup<FlxSprite>
|
|||
|
||||
if (inputEnabled)
|
||||
{
|
||||
if (controls.FREEPLAY_LEFT) changeSelection(-1);
|
||||
if (controls.FREEPLAY_RIGHT) changeSelection(1);
|
||||
if (controls.FREEPLAY_LEFT #if mobile || (TouchUtil.overlaps(swipeBounds) && SwipeUtil.justSwipedRight) #end) changeSelection(-1);
|
||||
if (controls.FREEPLAY_RIGHT #if mobile || (TouchUtil.overlaps(swipeBounds) && SwipeUtil.justSwipedLeft) #end) changeSelection(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,12 @@ import flixel.addons.effects.FlxTrail;
|
|||
import funkin.play.scoring.Scoring.ScoringRank;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.ui.PixelatedIcon;
|
||||
#if mobile
|
||||
import funkin.mobile.util.TouchUtil;
|
||||
import funkin.mobile.util.SwipeUtil;
|
||||
#end
|
||||
|
||||
using StringTools;
|
||||
|
||||
class SongMenuItem extends FlxSpriteGroup
|
||||
{
|
||||
|
|
@ -74,6 +80,10 @@ class SongMenuItem extends FlxSpriteGroup
|
|||
|
||||
var sparkleTimer:FlxTimer;
|
||||
|
||||
#if mobile
|
||||
public var theActualHitbox:FlxSprite;
|
||||
#end
|
||||
|
||||
public function new(x:Float, y:Float)
|
||||
{
|
||||
super(x, y);
|
||||
|
|
@ -214,6 +224,13 @@ class SongMenuItem extends FlxSpriteGroup
|
|||
weekNumbers.push(weekNumber);
|
||||
|
||||
setVisibleGrp(false);
|
||||
|
||||
#if mobile
|
||||
theActualHitbox = new FlxSprite(capsule.x + 140,
|
||||
capsule.y - 40).makeGraphic(Std.int(capsule.width / 1.4), Std.int(capsule.height / 1.4), FlxColor.TRANSPARENT);
|
||||
theActualHitbox.cameras = cameras;
|
||||
theActualHitbox.active = false;
|
||||
#end
|
||||
}
|
||||
|
||||
function sparkleEffect(timer:FlxTimer):Void
|
||||
|
|
@ -635,6 +652,11 @@ class SongMenuItem extends FlxSpriteGroup
|
|||
y = MathUtil.coolLerp(y, targetPos.y, 0.4);
|
||||
}
|
||||
|
||||
#if mobile
|
||||
theActualHitbox.x = x;
|
||||
theActualHitbox.y = y;
|
||||
#end
|
||||
|
||||
super.update(elapsed);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@ package funkin.ui.mainmenu;
|
|||
import flixel.addons.transition.FlxTransitionableState;
|
||||
import funkin.ui.debug.DebugMenuSubState;
|
||||
import flixel.FlxObject;
|
||||
import flixel.FlxSubState;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.effects.FlxFlicker;
|
||||
import flixel.util.typeLimit.NextState;
|
||||
import flixel.input.touch.FlxTouch;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.tweens.FlxEase;
|
||||
import funkin.graphics.FunkinCamera;
|
||||
import funkin.audio.FunkinSound;
|
||||
|
|
@ -21,6 +22,7 @@ import funkin.ui.title.TitleState;
|
|||
import funkin.ui.story.StoryMenuState;
|
||||
import funkin.ui.Prompt;
|
||||
import funkin.util.WindowUtil;
|
||||
import funkin.mobile.util.TouchUtil;
|
||||
#if FEATURE_DISCORD_RPC
|
||||
import funkin.api.discord.DiscordClient;
|
||||
#end
|
||||
|
|
@ -162,6 +164,10 @@ class MainMenuState extends MusicBeatState
|
|||
|
||||
// FlxG.camera.setScrollBounds(bg.x, bg.x + bg.width, bg.y, bg.y + bg.height * 1.2);
|
||||
|
||||
#if mobile
|
||||
addBackButton(FlxG.width * 0.03, FlxG.height * 0.79, FlxColor.BLACK, goBack);
|
||||
#end
|
||||
|
||||
super.create();
|
||||
|
||||
// This has to come AFTER!
|
||||
|
|
@ -173,6 +179,12 @@ class MainMenuState extends MusicBeatState
|
|||
this.leftWatermarkText.text += ' | Newgrounds: Logged in as ${NG.core?.user?.name}';
|
||||
}
|
||||
#end
|
||||
|
||||
// Leave this here do not change it -Zack
|
||||
#if mobile
|
||||
camFollow.setPosition(640, 360);
|
||||
FlxG.camera.snapToTarget();
|
||||
#end
|
||||
}
|
||||
|
||||
function playMenuMusic():Void
|
||||
|
|
@ -215,6 +227,19 @@ class MainMenuState extends MusicBeatState
|
|||
super.closeSubState();
|
||||
}
|
||||
|
||||
override function openSubState(targetSubState:FlxSubState):Void
|
||||
{
|
||||
super.openSubState(targetSubState);
|
||||
// Leave this here do not change it -Zack
|
||||
#if mobile
|
||||
if (camFollow != null)
|
||||
{
|
||||
camFollow.setPosition(640, 360);
|
||||
FlxG.camera.snapToTarget();
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
override function finishTransIn():Void
|
||||
{
|
||||
super.finishTransIn();
|
||||
|
|
@ -281,24 +306,6 @@ class MainMenuState extends MusicBeatState
|
|||
{
|
||||
super.update(elapsed);
|
||||
|
||||
if (FlxG.onMobile)
|
||||
{
|
||||
var touch:FlxTouch = FlxG.touches.getFirst();
|
||||
|
||||
if (touch != null)
|
||||
{
|
||||
for (item in menuItems)
|
||||
{
|
||||
if (touch.overlaps(item))
|
||||
{
|
||||
if (menuItems.selectedIndex == item.ID && touch.justPressed) menuItems.accept();
|
||||
else
|
||||
menuItems.selectItem(item.ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Conductor.instance.update();
|
||||
|
||||
// Open the debug menu, defaults to ` / ~
|
||||
|
|
@ -417,7 +424,15 @@ class MainMenuState extends MusicBeatState
|
|||
|
||||
if (_exiting) menuItems.enabled = false;
|
||||
|
||||
if (controls.BACK && menuItems.enabled && !menuItems.busy)
|
||||
if (controls.BACK)
|
||||
{
|
||||
goBack();
|
||||
}
|
||||
}
|
||||
|
||||
public function goBack():Void
|
||||
{
|
||||
if (menuItems.enabled && !menuItems.busy)
|
||||
{
|
||||
FlxG.switchState(() -> new TitleState());
|
||||
FunkinSound.playOnce(Paths.sound('cancelMenu'));
|
||||
|
|
|
|||
|
|
@ -15,6 +15,12 @@ import funkin.input.Controls;
|
|||
#if FEATURE_NEWGROUNDS
|
||||
import funkin.api.newgrounds.NewgroundsClient;
|
||||
#end
|
||||
#if mobile
|
||||
import funkin.mobile.ui.FunkinBackspace;
|
||||
import funkin.mobile.util.TouchUtil;
|
||||
import funkin.mobile.ui.options.MobileControlsSchemeMenu;
|
||||
#end
|
||||
import flixel.util.FlxColor;
|
||||
|
||||
/**
|
||||
* The main options menu
|
||||
|
|
@ -54,147 +60,159 @@ class OptionsState extends MusicBeatState
|
|||
else
|
||||
{
|
||||
// No need to show Options page
|
||||
#if mobile
|
||||
preferences.onExit.add(exitToMainMenu);
|
||||
setPage(Preferences);
|
||||
#else
|
||||
controls.onExit.add(exitToMainMenu);
|
||||
optionsCodex.setPage(Controls);
|
||||
}
|
||||
} super.create();
|
||||
|
||||
super.create();
|
||||
}
|
||||
} function exitControls():Void
|
||||
|
||||
function exitControls():Void
|
||||
{
|
||||
// Apply any changes to the controls.
|
||||
PlayerSettings.reset();
|
||||
PlayerSettings.init();
|
||||
{
|
||||
// Apply any changes to the controls.
|
||||
PlayerSettings.reset();
|
||||
PlayerSettings.init();
|
||||
|
||||
optionsCodex.switchPage(Options);
|
||||
}
|
||||
optionsCodex.switchPage(Options);
|
||||
}
|
||||
|
||||
function exitToMainMenu()
|
||||
{
|
||||
optionsCodex.currentPage.enabled = false;
|
||||
// TODO: Animate this transition?
|
||||
FlxG.switchState(() -> new MainMenuState());
|
||||
}
|
||||
}
|
||||
function exitToMainMenu()
|
||||
{
|
||||
optionsCodex.currentPage.enabled = false;
|
||||
// TODO: Animate this transition?
|
||||
FlxG.switchState(() -> new MainMenuState());
|
||||
}
|
||||
} /**
|
||||
* Our default Page when we enter the OptionsState, a bit of the root
|
||||
*/
|
||||
|
||||
/**
|
||||
* Our default Page when we enter the OptionsState, a bit of the root
|
||||
*/
|
||||
class OptionsMenu extends Page<OptionsMenuPageName>
|
||||
{
|
||||
var items:TextMenuList;
|
||||
class OptionsMenu extends Page<OptionsMenuPageName>
|
||||
{
|
||||
var items:TextMenuList;
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
|
||||
add(items = new TextMenuList());
|
||||
createItem("PREFERENCES", function() codex.switchPage(Preferences));
|
||||
createItem("CONTROLS", function() codex.switchPage(Controls));
|
||||
createItem("INPUT OFFSETS", function() {
|
||||
#if web
|
||||
LoadingState.transitionToState(() -> new LatencyState());
|
||||
#else
|
||||
FlxG.state.openSubState(new LatencyState());
|
||||
#end
|
||||
});
|
||||
add(items = new TextMenuList());
|
||||
createItem("PREFERENCES", function() codex.switchPage(Preferences));
|
||||
#if mobile
|
||||
if (FlxG.gamepads.numActiveGamepads > 0)
|
||||
{
|
||||
createItem("CONTROLS", function() codex.switchPage(Controls));
|
||||
}
|
||||
createItem("CONTROL SCHEMES", function() {
|
||||
FlxG.state.openSubState(new MobileControlsSchemeMenu());
|
||||
});
|
||||
#else
|
||||
createItem("CONTROLS", function() codex.switchPage(Controls));
|
||||
#end
|
||||
|
||||
#if FEATURE_NEWGROUNDS
|
||||
if (NewgroundsClient.instance.isLoggedIn())
|
||||
{
|
||||
createItem("LOGOUT OF NG", function() {
|
||||
NewgroundsClient.instance.logout(function() {
|
||||
// Reset the options menu when logout succeeds.
|
||||
// This means the login option will be displayed.
|
||||
FlxG.resetState();
|
||||
}, function() {
|
||||
FlxG.log.warn("Newgrounds logout failed!");
|
||||
});
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
createItem("LOGIN TO NG", function() {
|
||||
NewgroundsClient.instance.login(function() {
|
||||
// Reset the options menu when login succeeds.
|
||||
// This means the logout option will be displayed.
|
||||
createItem("INPUT OFFSETS", function() {
|
||||
#if web
|
||||
LoadingState.transitionToState(() -> new LatencyState());
|
||||
#else
|
||||
FlxG.state.openSubState(new LatencyState());
|
||||
#end
|
||||
});
|
||||
|
||||
// NOTE: If the user presses login and opens the browser,
|
||||
// then navigates the UI
|
||||
FlxG.resetState();
|
||||
}, function() {
|
||||
FlxG.log.warn("Newgrounds login failed!");
|
||||
});
|
||||
});
|
||||
}
|
||||
#end
|
||||
#if FEATURE_NEWGROUNDS
|
||||
if (NewgroundsClient.instance.isLoggedIn())
|
||||
{
|
||||
createItem("LOGOUT OF NG", function() {
|
||||
NewgroundsClient.instance.logout(function() {
|
||||
// Reset the options menu when logout succeeds.
|
||||
// This means the login option will be displayed.
|
||||
FlxG.resetState();
|
||||
}, function() {
|
||||
FlxG.log.warn("Newgrounds logout failed!");
|
||||
});
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
createItem("LOGIN TO NG", function() {
|
||||
NewgroundsClient.instance.login(function() {
|
||||
// Reset the options menu when login succeeds.
|
||||
// This means the logout option will be displayed.
|
||||
|
||||
createItem("CLEAR SAVE DATA", function() {
|
||||
promptClearSaveData();
|
||||
});
|
||||
// NOTE: If the user presses login and opens the browser,
|
||||
// then navigates the UI
|
||||
FlxG.resetState();
|
||||
}, function() {
|
||||
FlxG.log.warn("Newgrounds login failed!");
|
||||
});
|
||||
});
|
||||
}
|
||||
#end
|
||||
|
||||
createItem("EXIT", exit);
|
||||
}
|
||||
createItem("CLEAR SAVE DATA", function() {
|
||||
promptClearSaveData();
|
||||
});
|
||||
|
||||
function createItem(name:String, callback:Void->Void, fireInstantly = false)
|
||||
{
|
||||
var item = items.createItem(0, 100 + items.length * 100, name, BOLD, callback);
|
||||
item.fireInstantly = fireInstantly;
|
||||
item.screenCenter(X);
|
||||
return item;
|
||||
}
|
||||
createItem("EXIT", exit);
|
||||
}
|
||||
|
||||
override function set_enabled(value:Bool)
|
||||
{
|
||||
items.enabled = value;
|
||||
return super.set_enabled(value);
|
||||
}
|
||||
function createItem(name:String, callback:Void->Void, fireInstantly = false)
|
||||
{
|
||||
var item = items.createItem(0, 100 + items.length * 100, name, BOLD, callback);
|
||||
item.fireInstantly = fireInstantly;
|
||||
item.screenCenter(X);
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if this page has multiple options, excluding the exit option.
|
||||
* If false, there's no reason to ever show this page.
|
||||
*/
|
||||
public function hasMultipleOptions():Bool
|
||||
{
|
||||
return items.length > 2;
|
||||
}
|
||||
override function set_enabled(value:Bool)
|
||||
{
|
||||
items.enabled = value;
|
||||
return super.set_enabled(value);
|
||||
}
|
||||
|
||||
var prompt:Prompt;
|
||||
/**
|
||||
* True if this page has multiple options, excluding the exit option.
|
||||
* If false, there's no reason to ever show this page.
|
||||
*/
|
||||
public function hasMultipleOptions():Bool
|
||||
{
|
||||
return items.length > 2;
|
||||
}
|
||||
|
||||
function promptClearSaveData():Void
|
||||
{
|
||||
if (prompt != null) return;
|
||||
var prompt:Prompt;
|
||||
|
||||
prompt = new Prompt("This will delete
|
||||
function promptClearSaveData():Void
|
||||
{
|
||||
if (prompt != null) return;
|
||||
|
||||
prompt = new Prompt("This will delete
|
||||
\nALL your save data.
|
||||
\nAre you sure?
|
||||
", Custom("Delete", "Cancel"));
|
||||
prompt.create();
|
||||
prompt.createBgFromMargin(100, 0xFFFAFD6D);
|
||||
prompt.back.scrollFactor.set(0, 0);
|
||||
add(prompt);
|
||||
prompt.create();
|
||||
prompt.createBgFromMargin(100, 0xFFFAFD6D);
|
||||
prompt.back.scrollFactor.set(0, 0);
|
||||
add(prompt);
|
||||
|
||||
prompt.onYes = function() {
|
||||
// Clear the save data.
|
||||
funkin.save.Save.clearData();
|
||||
prompt.onYes = function() {
|
||||
// Clear the save data.
|
||||
funkin.save.Save.clearData();
|
||||
|
||||
FlxG.switchState(() -> new funkin.InitState());
|
||||
}
|
||||
FlxG.switchState(() -> new funkin.InitState());
|
||||
}
|
||||
|
||||
prompt.onNo = function() {
|
||||
prompt.close();
|
||||
prompt.destroy();
|
||||
prompt = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
prompt.onNo = function() {
|
||||
prompt.close();
|
||||
prompt.destroy();
|
||||
prompt = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum abstract OptionsMenuPageName(String) to PageName
|
||||
{
|
||||
var Options = "options";
|
||||
var Controls = "controls";
|
||||
var Colors = "colors";
|
||||
var Mods = "mods";
|
||||
var Preferences = "preferences";
|
||||
}
|
||||
enum abstract OptionsMenuPageName(String) to PageName
|
||||
{
|
||||
var Options = "options";
|
||||
var Controls = "controls";
|
||||
var Colors = "colors";
|
||||
var Mods = "mods";
|
||||
var Preferences = "preferences";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ import flixel.FlxObject;
|
|||
import flixel.FlxSprite;
|
||||
import flixel.text.FlxText;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.FlxG;
|
||||
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
||||
import flixel.math.FlxPoint;
|
||||
import funkin.ui.AtlasText.AtlasFont;
|
||||
import funkin.ui.Page;
|
||||
import funkin.graphics.FunkinCamera;
|
||||
|
|
@ -14,6 +16,12 @@ import funkin.ui.TextMenuList.TextMenuItem;
|
|||
import funkin.ui.options.items.CheckboxPreferenceItem;
|
||||
import funkin.ui.options.items.NumberPreferenceItem;
|
||||
import funkin.ui.options.items.EnumPreferenceItem;
|
||||
import funkin.mobile.ui.FunkinBackspace;
|
||||
#if mobile
|
||||
import funkin.mobile.ui.FunkinHitbox;
|
||||
import funkin.mobile.util.TouchUtil;
|
||||
import funkin.mobile.util.SwipeUtil;
|
||||
#end
|
||||
|
||||
class PreferencesMenu extends Page<OptionsState.OptionsMenuPageName>
|
||||
{
|
||||
|
|
@ -65,6 +73,11 @@ class PreferencesMenu extends Page<OptionsState.OptionsMenuPageName>
|
|||
camFollow.y = selected.y;
|
||||
itemDesc.text = preferenceDesc[items.selectedIndex];
|
||||
});
|
||||
|
||||
#if mobile
|
||||
backButton = new FunkinBackspace(FlxG.width * 0.77, FlxG.height * 0.85, flixel.util.FlxColor.BLACK);
|
||||
add(backButton);
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -144,6 +157,14 @@ class PreferencesMenu extends Page<OptionsState.OptionsMenuPageName>
|
|||
createPrefItemNumber('JPEG Quality', 'The quality of JPEG screenshots.', function(value:Float) {
|
||||
Preferences.jpegQuality = Std.int(value);
|
||||
}, null, Preferences.jpegQuality, 0, 100, 5, 0);
|
||||
#if mobile
|
||||
createPrefItemCheckbox('Allow Screen Timeout', 'Toggle screen timeout', function(value:Bool):Void {
|
||||
Preferences.screenTimeout = value;
|
||||
}, Preferences.screenTimeout);
|
||||
createPrefItemCheckbox('Vibration', 'Toggle vibration', function(value:Bool):Void {
|
||||
Preferences.vibration = value;
|
||||
}, Preferences.vibration);
|
||||
#end
|
||||
}
|
||||
|
||||
override function update(elapsed:Float):Void
|
||||
|
|
@ -175,6 +196,19 @@ class PreferencesMenu extends Page<OptionsState.OptionsMenuPageName>
|
|||
|
||||
daItem.x = thyOffset;
|
||||
});
|
||||
|
||||
#if mobile
|
||||
if (items.enabled
|
||||
&& !items.busy
|
||||
&& TouchUtil.justReleased
|
||||
&& !SwipeUtil.swipeAny
|
||||
&& (TouchUtil.touch != null
|
||||
&& TouchUtil.overlapsComplexPoint(items.selectedItem,
|
||||
FlxPoint.weak(TouchUtil.touch.x, TouchUtil.touch.y + camFollow.y - ((items.selectedIndex == 0) ? 20 : 130)), false, menuCamera)))
|
||||
{
|
||||
items.accept();
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
// - Preference item creation methods -
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ package funkin.ui.options.items;
|
|||
import funkin.ui.TextMenuList.TextMenuItem;
|
||||
import funkin.ui.AtlasText;
|
||||
import funkin.input.Controls;
|
||||
#if mobile
|
||||
import funkin.mobile.util.SwipeUtil;
|
||||
#end
|
||||
|
||||
/**
|
||||
* Preference item that allows the player to pick a value from an enum (list of values)
|
||||
|
|
@ -55,8 +58,8 @@ class EnumPreferenceItem extends TextMenuItem
|
|||
// var fancyTextFancyColor:Color;
|
||||
if (selected)
|
||||
{
|
||||
var shouldDecrease:Bool = controls().UI_LEFT_P;
|
||||
var shouldIncrease:Bool = controls().UI_RIGHT_P;
|
||||
var shouldDecrease:Bool = controls().UI_LEFT_P #if mobile || SwipeUtil.justSwipedLeft #end;
|
||||
var shouldIncrease:Bool = controls().UI_RIGHT_P #if mobile || SwipeUtil.justSwipedRight #end;
|
||||
|
||||
if (shouldDecrease) index -= 1;
|
||||
if (shouldIncrease) index += 1;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ package funkin.ui.options.items;
|
|||
import funkin.ui.TextMenuList.TextMenuItem;
|
||||
import funkin.ui.AtlasText;
|
||||
import funkin.input.Controls;
|
||||
#if mobile
|
||||
import funkin.mobile.util.SwipeUtil;
|
||||
#end
|
||||
|
||||
/**
|
||||
* Preference item that allows the player to pick a value between min and max
|
||||
|
|
@ -75,8 +78,8 @@ class NumberPreferenceItem extends TextMenuItem
|
|||
changeRateTimer -= elapsed;
|
||||
}
|
||||
|
||||
var jpLeft:Bool = controls().UI_LEFT_P;
|
||||
var jpRight:Bool = controls().UI_RIGHT_P;
|
||||
var jpLeft:Bool = controls().UI_LEFT_P #if mobile || SwipeUtil.justSwipedLeft #end;
|
||||
var jpRight:Bool = controls().UI_RIGHT_P #if mobile || SwipeUtil.justSwipedRight #end;
|
||||
|
||||
if (jpLeft || jpRight)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ import funkin.ui.MusicBeatState;
|
|||
import funkin.ui.transition.LoadingState;
|
||||
import funkin.ui.transition.StickerSubState;
|
||||
import funkin.util.MathUtil;
|
||||
import funkin.mobile.util.SwipeUtil;
|
||||
import funkin.mobile.util.TouchUtil;
|
||||
import openfl.utils.Assets;
|
||||
#if FEATURE_DISCORD_RPC
|
||||
import funkin.api.discord.DiscordClient;
|
||||
|
|
@ -223,6 +225,10 @@ class StoryMenuState extends MusicBeatState
|
|||
// Updating Discord Rich Presence
|
||||
DiscordClient.instance.setPresence({state: 'In the Menus', details: null});
|
||||
#end
|
||||
|
||||
#if mobile
|
||||
addBackButton(FlxG.width * 0.77, FlxG.height * 0.85);
|
||||
#end
|
||||
}
|
||||
|
||||
function rememberSelection():Void
|
||||
|
|
@ -330,13 +336,13 @@ class StoryMenuState extends MusicBeatState
|
|||
{
|
||||
if (!selectedLevel)
|
||||
{
|
||||
if (controls.UI_UP_P)
|
||||
if (controls.UI_UP_P || (SwipeUtil.justSwipedUp))
|
||||
{
|
||||
changeLevel(-1);
|
||||
changeDifficulty(0);
|
||||
}
|
||||
|
||||
if (controls.UI_DOWN_P)
|
||||
if (controls.UI_DOWN_P || (SwipeUtil.justSwipedDown))
|
||||
{
|
||||
changeLevel(1);
|
||||
changeDifficulty(0);
|
||||
|
|
@ -359,17 +365,17 @@ class StoryMenuState extends MusicBeatState
|
|||
#end
|
||||
|
||||
// TODO: Querying UI_RIGHT_P (justPressed) after UI_RIGHT always returns false. Fix it!
|
||||
if (controls.UI_RIGHT_P)
|
||||
if (controls.UI_RIGHT_P || (TouchUtil.overlaps(rightDifficultyArrow) && TouchUtil.justPressed))
|
||||
{
|
||||
changeDifficulty(1);
|
||||
}
|
||||
|
||||
if (controls.UI_LEFT_P)
|
||||
if (controls.UI_LEFT_P || (TouchUtil.overlaps(leftDifficultyArrow) && TouchUtil.justPressed))
|
||||
{
|
||||
changeDifficulty(-1);
|
||||
}
|
||||
|
||||
if (controls.UI_RIGHT)
|
||||
if (controls.UI_RIGHT || TouchUtil.overlaps(rightDifficultyArrow))
|
||||
{
|
||||
rightDifficultyArrow.animation.play('press');
|
||||
}
|
||||
|
|
@ -378,7 +384,7 @@ class StoryMenuState extends MusicBeatState
|
|||
rightDifficultyArrow.animation.play('idle');
|
||||
}
|
||||
|
||||
if (controls.UI_LEFT)
|
||||
if (controls.UI_LEFT || TouchUtil.overlaps(leftDifficultyArrow))
|
||||
{
|
||||
leftDifficultyArrow.animation.play('press');
|
||||
}
|
||||
|
|
@ -388,13 +394,18 @@ class StoryMenuState extends MusicBeatState
|
|||
}
|
||||
}
|
||||
|
||||
if (controls.ACCEPT)
|
||||
if (controls.ACCEPT
|
||||
|| (TouchUtil.overlaps(levelTitles.members[levelList.indexOf(currentLevelId)])
|
||||
&& TouchUtil.justPressed
|
||||
&& !TouchUtil.overlaps(leftDifficultyArrow)))
|
||||
{
|
||||
selectLevel();
|
||||
}
|
||||
}
|
||||
|
||||
if (controls.BACK && !exitingMenu && !selectedLevel)
|
||||
if (((controls.BACK) #if mobile || (TouchUtil.overlaps(backButton) && TouchUtil.justReleased && !SwipeUtil.swipeAny) #end)
|
||||
&& !exitingMenu
|
||||
&& !selectedLevel)
|
||||
{
|
||||
exitingMenu = true;
|
||||
FlxG.switchState(() -> new MainMenuState());
|
||||
|
|
|
|||
|
|
@ -31,6 +31,10 @@ import funkin.api.newgrounds.Medals;
|
|||
import funkin.ui.freeplay.FreeplayState;
|
||||
import openfl.display.BlendMode;
|
||||
import funkin.save.Save;
|
||||
#if mobile
|
||||
import funkin.mobile.util.TouchUtil;
|
||||
import funkin.mobile.util.SwipeUtil;
|
||||
#end
|
||||
|
||||
#if desktop
|
||||
#end
|
||||
|
|
@ -231,15 +235,6 @@ class TitleState extends MusicBeatState
|
|||
|
||||
Conductor.instance.update();
|
||||
|
||||
/* if (FlxG.onMobile)
|
||||
{
|
||||
if (gfDance != null)
|
||||
{
|
||||
gfDance.x = (FlxG.width / 2) + (FlxG.accelerometer.x * (FlxG.width / 2));
|
||||
// gfDance.y = (FlxG.height / 2) + (FlxG.accelerometer.y * (FlxG.height / 2));
|
||||
}
|
||||
}
|
||||
*/
|
||||
if (FlxG.keys.justPressed.I)
|
||||
{
|
||||
FlxTween.tween(outlineShaderShit, {funnyX: 50, funnyY: 50}, 0.6, {ease: FlxEase.quartOut});
|
||||
|
|
@ -257,7 +252,7 @@ class TitleState extends MusicBeatState
|
|||
if (FlxG.sound.music != null) Conductor.instance.update(FlxG.sound.music.time);
|
||||
|
||||
// do controls.PAUSE | controls.ACCEPT instead?
|
||||
var pressedEnter:Bool = FlxG.keys.justPressed.ENTER;
|
||||
var pressedEnter:Bool = FlxG.keys.justPressed.ENTER #if mobile || (TouchUtil.justReleased && !SwipeUtil.justSwipedAny) #end;
|
||||
|
||||
var gamepad:FlxGamepad = FlxG.gamepads.lastActive;
|
||||
|
||||
|
|
@ -303,8 +298,9 @@ class TitleState extends MusicBeatState
|
|||
}
|
||||
if (pressedEnter && !skippedIntro && initialized) skipIntro();
|
||||
|
||||
if (controls.UI_LEFT) swagShader.update(-elapsed * 0.1);
|
||||
if (controls.UI_RIGHT) swagShader.update(elapsed * 0.1);
|
||||
// TODO: Maybe use the dxdy method for swiping instead.
|
||||
if (controls.UI_LEFT #if mobile || SwipeUtil.justSwipedLeft #end) swagShader.update(-elapsed * 0.1);
|
||||
if (controls.UI_RIGHT #if mobile || SwipeUtil.justSwipedRight #end) swagShader.update(elapsed * 0.1);
|
||||
if (!cheatActive && skippedIntro) cheatCodeShit();
|
||||
super.update(elapsed);
|
||||
}
|
||||
|
|
@ -320,13 +316,10 @@ class TitleState extends MusicBeatState
|
|||
|
||||
function cheatCodeShit():Void
|
||||
{
|
||||
if (FlxG.keys.justPressed.ANY)
|
||||
{
|
||||
if (controls.NOTE_DOWN_P || controls.UI_DOWN_P) codePress(FlxDirectionFlags.DOWN);
|
||||
if (controls.NOTE_UP_P || controls.UI_UP_P) codePress(FlxDirectionFlags.UP);
|
||||
if (controls.NOTE_LEFT_P || controls.UI_LEFT_P) codePress(FlxDirectionFlags.LEFT);
|
||||
if (controls.NOTE_RIGHT_P || controls.UI_RIGHT_P) codePress(FlxDirectionFlags.RIGHT);
|
||||
}
|
||||
if (controls.NOTE_DOWN_P || controls.UI_DOWN_P #if mobile || SwipeUtil.justSwipedUp #end) codePress(FlxDirectionFlags.DOWN);
|
||||
if (controls.NOTE_UP_P || controls.UI_UP_P #if mobile || SwipeUtil.justSwipedDown #end) codePress(FlxDirectionFlags.UP);
|
||||
if (controls.NOTE_LEFT_P || controls.UI_LEFT_P #if mobile || SwipeUtil.justSwipedLeft #end) codePress(FlxDirectionFlags.LEFT);
|
||||
if (controls.NOTE_RIGHT_P || controls.UI_RIGHT_P #if mobile || SwipeUtil.justSwipedRight #end) codePress(FlxDirectionFlags.RIGHT);
|
||||
}
|
||||
|
||||
function codePress(input:Int)
|
||||
|
|
@ -379,7 +372,9 @@ class TitleState extends MusicBeatState
|
|||
{
|
||||
if (credGroup == null || textGroup == null) return;
|
||||
|
||||
lime.ui.Haptic.vibrate(100, 100);
|
||||
#if mobile
|
||||
if (Preferences.vibration) lime.ui.Haptic.vibrate(100, 100);
|
||||
#end
|
||||
|
||||
var coolText:AtlasText = new AtlasText(0, 0, text.trim(), AtlasFont.BOLD);
|
||||
coolText.screenCenter(X);
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ class FunkinPreloader extends FlxBasePreloader
|
|||
|
||||
progressLines = new openfl.display.Sprite();
|
||||
progressLines.graphics.lineStyle(2, Constants.COLOR_PRELOADER_BAR);
|
||||
progressLines.graphics.drawRect(-2, 480, this._width + 4, 30);
|
||||
progressLines.graphics.drawRect(-2, this._height - BAR_PADDING - BAR_HEIGHT - 208, this._width + 4, 30);
|
||||
addChild(progressLines);
|
||||
|
||||
var progressBarPiece = new Sprite();
|
||||
|
|
@ -231,8 +231,8 @@ class FunkinPreloader extends FlxBasePreloader
|
|||
box.graphics.beginFill(Constants.COLOR_PRELOADER_BAR, 0.1);
|
||||
box.graphics.drawRoundRect(0, 0, 128, 20, 5, 5);
|
||||
box.graphics.endFill();
|
||||
box.x = 880;
|
||||
box.y = 440;
|
||||
box.x = this._width - BAR_PADDING - BAR_HEIGHT - 432;
|
||||
box.y = this._height - BAR_PADDING - BAR_HEIGHT - 244;
|
||||
addChild(box);
|
||||
|
||||
dspText.selectable = false;
|
||||
|
|
|
|||
|
|
@ -15,19 +15,27 @@ class CLIUtil
|
|||
public static function resetWorkingDir():Void
|
||||
{
|
||||
#if sys
|
||||
var exeDir:String = Path.addTrailingSlash(Path.directory(Sys.programPath()));
|
||||
#if mac
|
||||
exeDir = Path.addTrailingSlash(Path.join([exeDir, '../Resources/']));
|
||||
#end
|
||||
var cwd:String = Path.addTrailingSlash(Sys.getCwd());
|
||||
if (cwd == exeDir)
|
||||
var gameDir:String = '';
|
||||
#if android
|
||||
gameDir = Path.addTrailingSlash(android.os.Build.VERSION.SDK_INT > 30 ? android.content.Context.getObbDir() : android.content.Context.getExternalFilesDir());
|
||||
#elseif ios
|
||||
// Why? Because for some reason lime.system.System.documentsDirectory is returning a directory that's different and we're unable to read or write from, so it's disabled and no solution is found...
|
||||
trace('[WARN]: Reseting the Current Working Directory is unavailable on iOS targets');
|
||||
gameDir = cwd;
|
||||
#elseif mac
|
||||
gameDir = Path.addTrailingSlash(Path.join([Path.directory(Sys.programPath()), '../Resources/']));
|
||||
#else
|
||||
gameDir = Path.addTrailingSlash(Path.directory(Sys.programPath()));
|
||||
#end
|
||||
if (cwd == gameDir)
|
||||
{
|
||||
trace('Working directory is already correct.');
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Changing working directory from ${Sys.getCwd()} to ${exeDir}');
|
||||
Sys.setCwd(exeDir);
|
||||
trace('Changing working directory from ${Sys.getCwd()} to ${gameDir}');
|
||||
Sys.setCwd(gameDir);
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ class WindowUtil
|
|||
}
|
||||
}
|
||||
});
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ class HaxelibVersions
|
|||
{
|
||||
var result:Array<String> = [];
|
||||
|
||||
var hmmData:Dynamic = haxe.Json.parse(sys.io.File.getContent('hmm.json'));
|
||||
var hmmData:Dynamic = haxe.Json.parse(sys.io.File.getContent(#if ios '../../../../../' + #end 'hmm.json'));
|
||||
var dependencies:Array<Dynamic> = cast hmmData.dependencies;
|
||||
for (library in dependencies)
|
||||
{
|
||||
|
|
|
|||
149
source/lime/utils/Log.hx
Normal file
149
source/lime/utils/Log.hx
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
package lime.utils;
|
||||
|
||||
import haxe.PosInfos;
|
||||
|
||||
#if !lime_debug
|
||||
@:fileXml('tags="haxe,release"')
|
||||
@:noDebug
|
||||
#end
|
||||
class Log
|
||||
{
|
||||
public static var level:LogLevel;
|
||||
public static var throwErrors:Bool = true;
|
||||
|
||||
public static function debug(message:Dynamic, ?info:PosInfos):Void
|
||||
{
|
||||
if (level >= LogLevel.DEBUG)
|
||||
{
|
||||
#if js
|
||||
untyped #if haxe4 js.Syntax.code #else __js__ #end ("console").debug("[" + info.className + "] " + message);
|
||||
#else
|
||||
println("[" + info.className + "] " + Std.string(message));
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
public static function error(message:Dynamic, ?info:PosInfos):Void
|
||||
{
|
||||
if (level >= LogLevel.ERROR)
|
||||
{
|
||||
var message = "[" + info.className + "] ERROR: " + message;
|
||||
|
||||
if (throwErrors)
|
||||
{
|
||||
#if webassembly
|
||||
println(message);
|
||||
#end
|
||||
|
||||
#if (mobile && !macro)
|
||||
@:privateAccess
|
||||
funkin.util.logging.CrashHandler.logErrorMessage(message);
|
||||
#end
|
||||
|
||||
throw message;
|
||||
}
|
||||
else
|
||||
{
|
||||
#if js
|
||||
untyped #if haxe4 js.Syntax.code #else __js__ #end ("console").error(message);
|
||||
#else
|
||||
println(message);
|
||||
#end
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function info(message:Dynamic, ?info:PosInfos):Void
|
||||
{
|
||||
if (level >= LogLevel.INFO)
|
||||
{
|
||||
#if js
|
||||
untyped #if haxe4 js.Syntax.code #else __js__ #end ("console").info("[" + info.className + "] " + message);
|
||||
#else
|
||||
println("[" + info.className + "] " + Std.string(message));
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
public static inline function print(message:Dynamic):Void
|
||||
{
|
||||
#if sys
|
||||
Sys.print(Std.string(message));
|
||||
#elseif flash
|
||||
untyped __global__["trace"](Std.string(message));
|
||||
#elseif js
|
||||
untyped #if haxe4 js.Syntax.code #else __js__ #end ("console").log(message);
|
||||
#else
|
||||
trace(message);
|
||||
#end
|
||||
}
|
||||
|
||||
public static inline function println(message:Dynamic):Void
|
||||
{
|
||||
#if sys
|
||||
Sys.println(Std.string(message));
|
||||
#elseif flash
|
||||
untyped __global__["trace"](Std.string(message));
|
||||
#elseif js
|
||||
untyped #if haxe4 js.Syntax.code #else __js__ #end ("console").log(message);
|
||||
#else
|
||||
trace(Std.string(message));
|
||||
#end
|
||||
}
|
||||
|
||||
public static function verbose(message:Dynamic, ?info:PosInfos):Void
|
||||
{
|
||||
if (level >= LogLevel.VERBOSE)
|
||||
{
|
||||
println("[" + info.className + "] " + message);
|
||||
}
|
||||
}
|
||||
|
||||
public static function warn(message:Dynamic, ?info:PosInfos):Void
|
||||
{
|
||||
if (level >= LogLevel.WARN)
|
||||
{
|
||||
#if js
|
||||
untyped #if haxe4 js.Syntax.code #else __js__ #end ("console").warn("[" + info.className + "] WARNING: " + message);
|
||||
#else
|
||||
println("[" + info.className + "] WARNING: " + Std.string(message));
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
private static function __init__():Void
|
||||
{
|
||||
#if no_traces
|
||||
level = NONE;
|
||||
#elseif verbose
|
||||
level = VERBOSE;
|
||||
#else
|
||||
#if sys
|
||||
var args = Sys.args();
|
||||
if (args.indexOf("-v") > -1 || args.indexOf("-verbose") > -1)
|
||||
{
|
||||
level = VERBOSE;
|
||||
}
|
||||
else
|
||||
#end
|
||||
{
|
||||
#if debug
|
||||
level = DEBUG;
|
||||
#else
|
||||
level = INFO;
|
||||
#end
|
||||
}
|
||||
#end
|
||||
|
||||
#if js
|
||||
if (untyped #if haxe4 js.Syntax.code #else __js__ #end ("typeof console") == "undefined")
|
||||
{
|
||||
untyped #if haxe4 js.Syntax.code #else __js__ #end ("console = {}");
|
||||
}
|
||||
if (untyped #if haxe4 js.Syntax.code #else __js__ #end ("console").log == null)
|
||||
{
|
||||
untyped #if haxe4 js.Syntax.code #else __js__ #end ("console").log = function() {};
|
||||
}
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
-lib haxeui-core
|
||||
-lib haxeui-flixel
|
||||
-lib flxanimate
|
||||
-lib hxCodec
|
||||
-lib hxvlc
|
||||
-lib thx.semver
|
||||
-lib json2object
|
||||
-lib tink_json
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
-lib haxeui-core
|
||||
-lib haxeui-flixel
|
||||
-lib flxanimate
|
||||
-lib hxCodec
|
||||
-lib hxvlc
|
||||
-lib thx.semver
|
||||
-lib json2object
|
||||
-lib tink_json
|
||||
|
|
@ -48,7 +48,7 @@
|
|||
-lib haxeui-flixel
|
||||
-lib polymod
|
||||
-lib flxanimate
|
||||
-lib hxCodec
|
||||
-lib hxvlc
|
||||
-lib thx.semver
|
||||
-lib json2object
|
||||
-lib tink_json
|
||||
|
|
|
|||
Loading…
Reference in a new issue