diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
index b9bcd698a2..923fd37947 100644
--- a/.github/workflows/linux.yml
+++ b/.github/workflows/linux.yml
@@ -15,71 +15,83 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Pulling the new commit
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setting up Haxe
uses: krdlab/setup-haxe@v2
with:
haxe-version: ${{ env.HAXE_VERSION }}
- - name: Restore existing build cache for faster compilation
- uses: actions/cache@v4.2.3
- with:
- # not caching the bin folder to prevent asset duplication and stuff like that
- key: cache-build-linux
- path: |
- .haxelib/
- export/release/linux/haxe/
- export/release/linux/obj/
+ # - name: Restore existing build cache for faster compilation
+ # uses: actions/cache@v4.2.3
+ # with:
+ # # not caching the bin folder to prevent asset duplication and stuff like that
+ # key: cache-build-linux
+ # path: |
+ # .haxelib/
+ # export/release/linux/obj/
+ - name: Installing Packages for Building Lime (TEMPORARY)
+ run: |
+ sudo apt-get update
+ sudo apt-get install -qq \
+ libegl1-mesa-dev libgl1-mesa-dev libibus-1.0-dev libdbus-1-dev libdecor-0-dev libudev-dev libgbm-dev libdrm-dev \
+ libpng-dev libturbojpeg-dev libvorbis-dev libopenal-dev libsdl2-dev libglu1-mesa-dev libmbedtls-dev libuv1-dev libsqlite3-dev \
+ libx11-dev libxext-dev libxfixes-dev libxi-dev libxinerama-dev libxkbcommon-dev libxcursor-dev libxrandr-dev libxss-dev libxt-dev libxtst-dev libxv-dev libxxf86vm-dev \
+ libpulse-dev libasound2-dev libpipewire-0.3-dev \
+ zlib1g-dev
- name: Installing LibVLC
run: |
sudo apt-get install libvlc-dev libvlccore-dev
- name: Installing/Updating libraries
run: |
+ haxelib --global update haxelib
haxe -cp commandline -D analyzer-optimize --run Main setup -si
- name: Building the game
run: |
+ haxelib run lime setup -alias -y -nocffi
+ haxelib run lime rebuild hxcpp
+ haxelib run lime rebuild tools
+ haxelib run lime rebuild linux -nocolor -release
haxelib run lime build linux -DCOMPILE_EXPERIMENTAL
# - name: Tar files
# run: tar -zcvf CodenameEngine.tar.gz -C export/release/linux/bin .
- name: Uploading artifact (executable)
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v7
with:
name: Codename Engine (Executable Only)
path: export/release/linux/bin/CodenameEngine
- name: Uploading artifact (entire build)
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v7
with:
name: Codename Engine
path: export/release/linux/bin/
- - name: Clearing already existing cache
- uses: actions/github-script@v6
- with:
- script: |
- const caches = await github.rest.actions.getActionsCacheList({
- owner: context.repo.owner,
- repo: context.repo.repo,
- })
- for (const cache of caches.data.actions_caches) {
- if (cache.key == "cache-build-linux") {
- console.log('Clearing ' + cache.key + '...')
- await github.rest.actions.deleteActionsCacheById({
- owner: context.repo.owner,
- repo: context.repo.repo,
- cache_id: cache.id,
- })
- console.log("Cache cleared.")
- }
- }
- - name: Uploading new cache
- uses: actions/cache@v4.2.3
- with:
- # caching again since for some reason it doesnt work with the first post cache shit
- key: cache-build-linux
- path: |
- .haxelib/
- export/release/linux/haxe/
- export/release/linux/obj/
+ # - name: Clearing already existing cache
+ # uses: actions/github-script@v6
+ # with:
+ # script: |
+ # const caches = await github.rest.actions.getActionsCacheList({
+ # owner: context.repo.owner,
+ # repo: context.repo.repo,
+ # })
+ # for (const cache of caches.data.actions_caches) {
+ # if (cache.key == "cache-build-linux") {
+ # console.log('Clearing ' + cache.key + '...')
+ # await github.rest.actions.deleteActionsCacheById({
+ # owner: context.repo.owner,
+ # repo: context.repo.repo,
+ # cache_id: cache.id,
+ # })
+ # console.log("Cache cleared.")
+ # }
+ # }
+ # - name: Uploading new cache
+ # uses: actions/cache@v4.2.3
+ # with:
+ # # caching again since for some reason it doesnt work with the first post cache shit
+ # key: cache-build-linux
+ # path: |
+ # .haxelib/
+ # export/release/linux/obj/
# didnt compile debug in the same job or github would have said that job wasn't completed until debug was done too (debug uploads are not essential)
debug_build:
@@ -89,61 +101,73 @@ jobs:
needs: build # since its low priority, it'll run after, so actions will concentrate first on normal builds
steps:
- name: Pulling the new commit
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
- name: Setting up Haxe
uses: krdlab/setup-haxe@v2
with:
haxe-version: ${{ env.HAXE_VERSION }}
- - name: Restore existing build cache for faster compilation
- uses: actions/cache@v4.2.3
- with:
- # not caching the bin folder to prevent asset duplication and stuff like that
- key: cache-build-linux-debug
- path: |
- .haxelib/
- export/debug/linux/haxe/
- export/debug/linux/obj/
+ # - name: Restore existing build cache for faster compilation
+ # uses: actions/cache@v4.2.3
+ # with:
+ # # not caching the bin folder to prevent asset duplication and stuff like that
+ # key: cache-build-linux-debug
+ # path: |
+ # .haxelib/
+ # export/debug/linux/obj/
+ - name: Installing Packages for Building Lime (TEMPORARY)
+ run: |
+ sudo apt-get update
+ sudo apt-get install -qq \
+ libegl1-mesa-dev libgl1-mesa-dev libibus-1.0-dev libdbus-1-dev libdecor-0-dev libudev-dev libgbm-dev libdrm-dev \
+ libpng-dev libturbojpeg-dev libvorbis-dev libopenal-dev libsdl2-dev libglu1-mesa-dev libmbedtls-dev libuv1-dev libsqlite3-dev \
+ libx11-dev libxext-dev libxfixes-dev libxi-dev libxinerama-dev libxkbcommon-dev libxcursor-dev libxrandr-dev libxss-dev libxt-dev libxtst-dev libxv-dev libxxf86vm-dev \
+ libpulse-dev libasound2-dev libpipewire-0.3-dev \
+ zlib1g-dev
- name: Installing LibVLC
run: |
sudo apt-get install libvlc-dev libvlccore-dev
- name: Installing/Updating libraries
run: |
+ haxelib --global update haxelib
haxe -cp commandline -D analyzer-optimize --run Main setup -si
- name: Building the game
run: |
- haxelib run lime build linux -debug
+ haxelib run lime setup -alias -y -nocffi
+ haxelib run lime rebuild hxcpp
+ haxelib run lime rebuild tools
+ haxelib run lime rebuild linux -nocolor -release
+ haxelib run lime build linux -debug -DCOMPILE_EXPERIMENTAL
# - name: Tar files
# run: tar -zcvf CodenameEngine.tar.gz -C export/debug/linux/bin .
- name: Uploading artifact (entire build)
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v7
with:
name: Codename Engine Debug
path: export/debug/linux/bin/
- - name: Clearing already existing cache
- uses: actions/github-script@v6
- with:
- script: |
- const caches = await github.rest.actions.getActionsCacheList({
- owner: context.repo.owner,
- repo: context.repo.repo,
- })
- for (const cache of caches.data.actions_caches) {
- if (cache.key == "cache-build-linux-debug") {
- console.log('Clearing ' + cache.key + '...')
- await github.rest.actions.deleteActionsCacheById({
- owner: context.repo.owner,
- repo: context.repo.repo,
- cache_id: cache.id,
- })
- console.log("Cache cleared.")
- }
- }
- - name: Uploading new cache
- uses: actions/cache@v4.2.3
- with:
- # caching again since for some reason it doesnt work with the first post cache shit
- key: cache-build-linux-debug
- path: |
- .haxelib/
- export/debug/linux/haxe/
- export/debug/linux/obj/
+ # - name: Clearing already existing cache
+ # uses: actions/github-script@v6
+ # with:
+ # script: |
+ # const caches = await github.rest.actions.getActionsCacheList({
+ # owner: context.repo.owner,
+ # repo: context.repo.repo,
+ # })
+ # for (const cache of caches.data.actions_caches) {
+ # if (cache.key == "cache-build-linux-debug") {
+ # console.log('Clearing ' + cache.key + '...')
+ # await github.rest.actions.deleteActionsCacheById({
+ # owner: context.repo.owner,
+ # repo: context.repo.repo,
+ # cache_id: cache.id,
+ # })
+ # console.log("Cache cleared.")
+ # }
+ # }
+ # - name: Uploading new cache
+ # uses: actions/cache@v4.2.3
+ # with:
+ # # caching again since for some reason it doesnt work with the first post cache shit
+ # key: cache-build-linux-debug
+ # path: |
+ # .haxelib/
+ # export/debug/linux/obj/
diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml
index ecac5f1aba..9b4ae37a30 100644
--- a/.github/workflows/macos.yml
+++ b/.github/workflows/macos.yml
@@ -9,135 +9,367 @@ env:
HXCPP_COMPILE_CACHE: ${{ github.workspace }}/.hxcpp_cache
jobs:
- build:
- name: Mac OS Build
+ intel_build:
+ name: Mac OS x64 Build
permissions: write-all
- runs-on: macos-15
+ runs-on: macos-26
steps:
- name: Pulling the new commit
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setting up Haxe
uses: krdlab/setup-haxe@v2
with:
haxe-version: ${{ env.HAXE_VERSION }}
- - name: Restore existing build cache for faster compilation
- uses: actions/cache@v4.2.3
- with:
- # not caching the bin folder to prevent asset duplication and stuff like that
- key: cache-build-mac
- path: |
- .haxelib/
- export/release/macos/haxe/
- export/release/macos/obj/
+ # - name: Restore existing build cache for faster compilation
+ # uses: actions/cache@v4.2.3
+ # with:
+ # # not caching the bin folder to prevent asset duplication and stuff like that
+ # key: cache-build-mac
+ # path: |
+ # .haxelib/
+ # export/release/macos/obj/
- name: Installing/Updating libraries
run: |
+ brew trust aws/tap
+ arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
+ arch -x86_64 /usr/local/bin/brew install zlib
+ haxelib --global update haxelib
haxe -cp commandline -D analyzer-optimize --run Main setup -si
- name: Building the game
run: |
+ haxelib run lime setup -alias -y -nocffi
+ haxelib run lime rebuild hxcpp
+ haxelib run lime rebuild tools
+ haxelib run lime rebuild mac -64 -nocolor -release
arch -x86_64 haxelib run lime build mac -DCOMPILE_EXPERIMENTAL
- name: Tar files
run: tar -zcvf CodenameEngine.tar.gz -C export/release/macos/bin .
- - name: Uploading artifact (executable)
- uses: actions/upload-artifact@v4
+ - name: Uploading artifact (entire build)
+ uses: actions/upload-artifact@v7
with:
- name: Codename Engine (Executable Only)
- path: export/release/macos/bin/CodenameEngine.app/Contents/MacOS/CodenameEngine
+ name: Codename Engine-x64
+ path: CodenameEngine.tar.gz
+ # - name: Clearing already existing cache
+ # uses: actions/github-script@v6
+ # with:
+ # script: |
+ # const caches = await github.rest.actions.getActionsCacheList({
+ # owner: context.repo.owner,
+ # repo: context.repo.repo,
+ # })
+ # for (const cache of caches.data.actions_caches) {
+ # if (cache.key == "cache-build-mac") {
+ # console.log('Clearing ' + cache.key + '...')
+ # await github.rest.actions.deleteActionsCacheById({
+ # owner: context.repo.owner,
+ # repo: context.repo.repo,
+ # cache_id: cache.id,
+ # })
+ # console.log("Cache cleared.")
+ # }
+ # }
+ # - name: Uploading new cache
+ # uses: actions/cache@v4.2.3
+ # with:
+ # # caching again since for some reason it doesnt work with the first post cache shit
+ # key: cache-build-mac
+ # path: |
+ # .haxelib/
+ # export/release/macos/obj/
+
+ arm_build:
+ name: Mac OS ARM64 Build
+ permissions: write-all
+ runs-on: macos-26
+ steps:
+ - name: Pulling the new commit
+ uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+ - name: Setting up Haxe
+ uses: krdlab/setup-haxe@v2
+ with:
+ haxe-version: ${{ env.HAXE_VERSION }}
+ # - name: Restore existing build cache for faster compilation
+ # uses: actions/cache@v4.2.3
+ # with:
+ # # not caching the bin folder to prevent asset duplication and stuff like that
+ # key: cache-build-mac
+ # path: |
+ # .haxelib/
+ # export/release/macos/obj/
+ - name: Installing/Updating libraries
+ run: |
+ brew trust aws/tap
+ brew install zlib
+ haxelib --global update haxelib
+ haxe -cp commandline -D analyzer-optimize --run Main setup -si
+ - name: Building the game
+ run: |
+ haxelib run lime setup -alias -y -nocffi
+ haxelib run lime rebuild hxcpp
+ haxelib run lime rebuild tools
+ haxelib run lime rebuild mac -nocolor -release
+ haxelib run lime build mac -DCOMPILE_EXPERIMENTAL
+ - name: Tar files
+ run: tar -zcvf CodenameEngine.tar.gz -C export/release/macos/bin .
- name: Uploading artifact (entire build)
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v7
with:
- name: Codename Engine
+ name: Codename Engine-ARM64
path: CodenameEngine.tar.gz
- - name: Clearing already existing cache
- uses: actions/github-script@v6
- with:
- script: |
- const caches = await github.rest.actions.getActionsCacheList({
- owner: context.repo.owner,
- repo: context.repo.repo,
- })
- for (const cache of caches.data.actions_caches) {
- if (cache.key == "cache-build-mac") {
- console.log('Clearing ' + cache.key + '...')
- await github.rest.actions.deleteActionsCacheById({
- owner: context.repo.owner,
- repo: context.repo.repo,
- cache_id: cache.id,
- })
- console.log("Cache cleared.")
- }
- }
- - name: Uploading new cache
- uses: actions/cache@v4.2.3
- with:
- # caching again since for some reason it doesnt work with the first post cache shit
- key: cache-build-mac
- path: |
- .haxelib/
- export/release/macos/haxe/
- export/release/macos/obj/
+ # - name: Clearing already existing cache
+ # uses: actions/github-script@v6
+ # with:
+ # script: |
+ # const caches = await github.rest.actions.getActionsCacheList({
+ # owner: context.repo.owner,
+ # repo: context.repo.repo,
+ # })
+ # for (const cache of caches.data.actions_caches) {
+ # if (cache.key == "cache-build-mac") {
+ # console.log('Clearing ' + cache.key + '...')
+ # await github.rest.actions.deleteActionsCacheById({
+ # owner: context.repo.owner,
+ # repo: context.repo.repo,
+ # cache_id: cache.id,
+ # })
+ # console.log("Cache cleared.")
+ # }
+ # }
+ # - name: Uploading new cache
+ # uses: actions/cache@v4.2.3
+ # with:
+ # # caching again since for some reason it doesnt work with the first post cache shit
+ # key: cache-build-mac
+ # path: |
+ # .haxelib/
+ # export/release/macos/obj/
+
+ universal_build:
+ name: Mac OS Universal Build
+ permissions: write-all
+ runs-on: macos-26
+ needs:
+ - intel_build
+ - arm_build
+ steps:
+ - name: Download Mac OS x64 Full Build
+ uses: actions/download-artifact@v8
+ with:
+ name: Codename Engine-x64
+ path: intel
+ - name: Download Mac OS ARM64 Full Build
+ uses: actions/download-artifact@v8
+ with:
+ name: Codename Engine-ARM64
+ path: arm
+ - name: Tar files (x64)
+ run: tar -xvf intel/CodenameEngine.tar.gz -C intel
+ - name: Tar files (ARM64)
+ run: tar -xvf arm/CodenameEngine.tar.gz -C arm
+ - name: Removing ARM64 and x64 artifacts
+ uses: geekyeggo/delete-artifact@v6
+ with:
+ name: Codename Engine-*
+ - name: Creating Universal App Bundle
+ shell: bash
+ run: |
+ BIN="CodenameEngine.app/Contents/MacOS"
+ INTEL="intel/CodenameEngine.app/Contents/MacOS"
+ ARM="arm/CodenameEngine.app/Contents/MacOS"
+ cp -R intel/CodenameEngine.app CodenameEngine.app
+ lipo -create "$INTEL/CodenameEngine" "$ARM/CodenameEngine" -output "$BIN/CodenameEngine"
+ lipo -create "$INTEL/lime.ndll" "$ARM/lime.ndll" -output "$BIN/lime.ndll"
+ - name: Uploading Universal Mac OS bundle (executable)
+ uses: actions/upload-artifact@v7
+ with:
+ name: Codename Engine (Executable Only)
+ path: CodenameEngine.app/Contents/MacOS/CodenameEngine
+ - name: Uploading Universal Mac OS bundle
+ uses: actions/upload-artifact@v7
+ with:
+ name: Codename Engine
+ path: CodenameEngine.app
# didnt compile debug in the same job or github would have said that job wasn't completed until debug was done too (debug uploads are not essential)
- debug_build:
- name: Mac OS Debug Build
+ intel_debug_build:
+ name: Mac OS x64 Debug Build
permissions: write-all
- runs-on: macos-15
- needs: build # since its low priority, it'll run after, so actions will concentrate first on normal builds
+ runs-on: macos-26
+ needs: universal_build # since its low priority, it'll run after, so actions will concentrate first on normal builds
steps:
- name: Pulling the new commit
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
- name: Setting up Haxe
uses: krdlab/setup-haxe@v2
with:
haxe-version: ${{ env.HAXE_VERSION }}
- - name: Restore existing build cache for faster compilation
- uses: actions/cache@v4.2.3
- with:
- # not caching the bin folder to prevent asset duplication and stuff like that
- key: cache-build-mac-debug
- path: |
- .haxelib/
- export/debug/macos/haxe/
- export/debug/macos/obj/
+ # - name: Restore existing build cache for faster compilation
+ # uses: actions/cache@v4.2.3
+ # with:
+ # # not caching the bin folder to prevent asset duplication and stuff like that
+ # key: cache-build-mac-debug
+ # path: |
+ # .haxelib/
+ # export/debug/macos/obj/
- name: Installing/Updating libraries
run: |
+ brew trust aws/tap
+ arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
+ arch -x86_64 /usr/local/bin/brew install zlib
+ haxelib --global update haxelib
haxe -cp commandline -D analyzer-optimize --run Main setup -si
- name: Building the game
run: |
- arch -x86_64 haxelib run lime build mac -debug
+ haxelib run lime setup -alias -y -nocffi
+ haxelib run lime rebuild hxcpp
+ haxelib run lime rebuild tools
+ haxelib run lime rebuild mac -64 -nocolor -release
+ arch -x86_64 haxelib run lime build mac -debug -DCOMPILE_EXPERIMENTAL
- name: Tar files
run: tar -zcvf CodenameEngine.tar.gz -C export/debug/macos/bin .
- name: Uploading artifact (entire build)
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v7
with:
- name: Codename Engine Debug
+ name: Codename Engine-x64 Debug
+ path: CodenameEngine.tar.gz
+ # - name: Clearing already existing cache
+ # uses: actions/github-script@v6
+ # with:
+ # script: |
+ # const caches = await github.rest.actions.getActionsCacheList({
+ # owner: context.repo.owner,
+ # repo: context.repo.repo,
+ # })
+ # for (const cache of caches.data.actions_caches) {
+ # if (cache.key == "cache-build-mac-debug") {
+ # console.log('Clearing ' + cache.key + '...')
+ # await github.rest.actions.deleteActionsCacheById({
+ # owner: context.repo.owner,
+ # repo: context.repo.repo,
+ # cache_id: cache.id,
+ # })
+ # console.log("Cache cleared.")
+ # }
+ # }
+ # - name: Uploading new cache
+ # uses: actions/cache@v4.2.3
+ # with:
+ # # caching again since for some reason it doesnt work with the first post cache shit
+ # key: cache-build-mac-debug
+ # path: |
+ # .haxelib/
+ # export/debug/macos/obj/
+
+ arm_debug_build:
+ name: Mac OS ARM64 Debug Build
+ permissions: write-all
+ runs-on: macos-26
+ needs: universal_build # since its low priority, it'll run after, so actions will concentrate first on normal builds
+ steps:
+ - name: Pulling the new commit
+ uses: actions/checkout@v6
+ - name: Setting up Haxe
+ uses: krdlab/setup-haxe@v2
+ with:
+ haxe-version: ${{ env.HAXE_VERSION }}
+ # - name: Restore existing build cache for faster compilation
+ # uses: actions/cache@v4.2.3
+ # with:
+ # # not caching the bin folder to prevent asset duplication and stuff like that
+ # key: cache-build-mac-debug
+ # path: |
+ # .haxelib/
+ # export/debug/macos/obj/
+ - name: Installing/Updating libraries
+ run: |
+ brew trust aws/tap
+ brew install zlib
+ haxelib --global update haxelib
+ haxe -cp commandline -D analyzer-optimize --run Main setup -si
+ - name: Building the game
+ run: |
+ haxelib run lime setup -alias -y -nocffi
+ haxelib run lime rebuild hxcpp
+ haxelib run lime rebuild tools
+ haxelib run lime rebuild mac -nocolor -release
+ haxelib run lime build mac -debug -DCOMPILE_EXPERIMENTAL
+ - name: Tar files
+ run: tar -zcvf CodenameEngine.tar.gz -C export/debug/macos/bin .
+ - name: Uploading artifact (entire build)
+ uses: actions/upload-artifact@v7
+ with:
+ name: Codename Engine-ARM64 Debug
path: CodenameEngine.tar.gz
- - name: Clearing already existing cache
- uses: actions/github-script@v6
- with:
- script: |
- const caches = await github.rest.actions.getActionsCacheList({
- owner: context.repo.owner,
- repo: context.repo.repo,
- })
- for (const cache of caches.data.actions_caches) {
- if (cache.key == "cache-build-mac-debug") {
- console.log('Clearing ' + cache.key + '...')
- await github.rest.actions.deleteActionsCacheById({
- owner: context.repo.owner,
- repo: context.repo.repo,
- cache_id: cache.id,
- })
- console.log("Cache cleared.")
- }
- }
- - name: Uploading new cache
- uses: actions/cache@v4.2.3
- with:
- # caching again since for some reason it doesnt work with the first post cache shit
- key: cache-build-mac-debug
- path: |
- .haxelib/
- export/debug/macos/haxe/
- export/debug/macos/obj/
+ # - name: Clearing already existing cache
+ # uses: actions/github-script@v6
+ # with:
+ # script: |
+ # const caches = await github.rest.actions.getActionsCacheList({
+ # owner: context.repo.owner,
+ # repo: context.repo.repo,
+ # })
+ # for (const cache of caches.data.actions_caches) {
+ # if (cache.key == "cache-build-mac-debug") {
+ # console.log('Clearing ' + cache.key + '...')
+ # await github.rest.actions.deleteActionsCacheById({
+ # owner: context.repo.owner,
+ # repo: context.repo.repo,
+ # cache_id: cache.id,
+ # })
+ # console.log("Cache cleared.")
+ # }
+ # }
+ # - name: Uploading new cache
+ # uses: actions/cache@v4.2.3
+ # with:
+ # # caching again since for some reason it doesnt work with the first post cache shit
+ # key: cache-build-mac-debug
+ # path: |
+ # .haxelib/
+ # export/debug/macos/obj/
+
+ universal_debug_build:
+ name: Mac OS Universal Debug Build
+ permissions: write-all
+ runs-on: macos-26
+ needs:
+ - intel_debug_build
+ - arm_debug_build
+ steps:
+ - name: Download Mac OS x64 Full Build
+ uses: actions/download-artifact@v8
+ with:
+ name: Codename Engine-x64 Debug
+ path: intel
+ - name: Download Mac OS ARM64 Full Build
+ uses: actions/download-artifact@v8
+ with:
+ name: Codename Engine-ARM64 Debug
+ path: arm
+ - name: Tar files (x64)
+ run: tar -xvf intel/CodenameEngine.tar.gz -C intel
+ - name: Tar files (ARM64)
+ run: tar -xvf arm/CodenameEngine.tar.gz -C arm
+ - name: Removing ARM64 and x64 artifacts
+ uses: geekyeggo/delete-artifact@v6
+ with:
+ name: Codename Engine-*
+ - name: Creating Universal App Bundle
+ shell: bash
+ run: |
+ BIN="CodenameEngine.app/Contents/MacOS"
+ INTEL="intel/CodenameEngine.app/Contents/MacOS"
+ ARM="arm/CodenameEngine.app/Contents/MacOS"
+ cp -R intel/CodenameEngine.app CodenameEngine.app
+ lipo -create "$INTEL/CodenameEngine" "$ARM/CodenameEngine" -output "$BIN/CodenameEngine"
+ lipo -create "$INTEL/lime.ndll" "$ARM/lime.ndll" -output "$BIN/lime.ndll"
+ - name: Uploading Universal Mac OS bundle
+ uses: actions/upload-artifact@v7
+ with:
+ name: Codename Engine Debug
+ path: CodenameEngine.app
+
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 1d44b18914..3e78c83f20 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -21,12 +21,12 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Download Windows Full Build
- uses: dawidd6/action-download-artifact@v6
+ uses: dawidd6/action-download-artifact@v8
with:
workflow: windows.yml
name: Codename Engine
@@ -34,7 +34,7 @@ jobs:
allow_forks: false
- name: Download Windows Executable
- uses: dawidd6/action-download-artifact@v6
+ uses: dawidd6/action-download-artifact@v8
with:
workflow: windows.yml
name: Codename Engine (Executable Only)
@@ -42,7 +42,7 @@ jobs:
allow_forks: false
- name: Download Mac OS Full Build
- uses: dawidd6/action-download-artifact@v6
+ uses: dawidd6/action-download-artifact@v8
with:
workflow: macos.yml
name: Codename Engine
@@ -50,7 +50,7 @@ jobs:
allow_forks: false
- name: Download Mac OS Executable
- uses: dawidd6/action-download-artifact@v6
+ uses: dawidd6/action-download-artifact@v8
with:
workflow: macos.yml
name: Codename Engine (Executable Only)
@@ -58,7 +58,7 @@ jobs:
allow_forks: false
- name: Download Linux Full Build
- uses: dawidd6/action-download-artifact@v6
+ uses: dawidd6/action-download-artifact@v8
with:
workflow: linux.yml
name: Codename Engine
@@ -66,7 +66,7 @@ jobs:
allow_forks: false
- name: Download Linux Executable
- uses: dawidd6/action-download-artifact@v6
+ uses: dawidd6/action-download-artifact@v8
with:
workflow: linux.yml
name: Codename Engine (Executable Only)
diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml
index 2f1584327d..3c2850f027 100644
--- a/.github/workflows/windows.yml
+++ b/.github/workflows/windows.yml
@@ -15,66 +15,71 @@ jobs:
runs-on: windows-latest
steps:
- name: Pulling the new commit
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setting up Haxe
uses: krdlab/setup-haxe@v2
with:
haxe-version: ${{ env.HAXE_VERSION }}
- - name: Restore existing build cache for faster compilation
- uses: actions/cache@v4.2.3
- with:
- # not caching the bin folder to prevent asset duplication and stuff like that
- key: cache-build-windows
- path: |
- .haxelib/
- export/release/windows/haxe/
- export/release/windows/obj/
+ # - name: Restore existing build cache for faster compilation
+ # uses: actions/cache@v4.2.3
+ # with:
+ # # not caching the bin folder to prevent asset duplication and stuff like that
+ # key: cache-build-windows
+ # path: |
+ # .haxelib/
+ # export/release/windows/obj/
- name: Installing/Updating libraries
run: |
+ haxelib --global update haxelib
haxe -cp commandline -D analyzer-optimize --run Main setup -si --no-vscheck
- name: Building the game
run: |
+ haxelib run lime setup -alias -y -nocffi
+ haxelib run lime rebuild hxcpp
+ haxelib run lime rebuild tools
+ haxelib run lime rebuild windows -nocolor -release
haxelib run lime build windows -DCOMPILE_EXPERIMENTAL
- name: Uploading artifact (executable)
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v7
with:
name: Codename Engine (Executable Only)
- path: export/release/windows/bin/CodenameEngine.exe
+ path: |
+ export/release/windows/bin/CodenameEngine.exe
+ export/release/windows/bin/lime.ndll
- name: Uploading artifact (entire build)
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v7
with:
name: Codename Engine
path: export/release/windows/bin
- - name: Clearing already existing cache
- uses: actions/github-script@v6
- with:
- script: |
- const caches = await github.rest.actions.getActionsCacheList({
- owner: context.repo.owner,
- repo: context.repo.repo,
- })
- for (const cache of caches.data.actions_caches) {
- if (cache.key == "cache-build-windows") {
- console.log('Clearing ' + cache.key + '...')
- await github.rest.actions.deleteActionsCacheById({
- owner: context.repo.owner,
- repo: context.repo.repo,
- cache_id: cache.id,
- })
- console.log("Cache cleared.")
- }
- }
- - name: Uploading new cache
- uses: actions/cache@v4.2.3
- with:
- # caching again since for some reason it doesnt work with the first post cache shit
- key: cache-build-windows
- path: |
- .haxelib/
- export/release/windows/haxe/
- export/release/windows/obj/
+ # - name: Clearing already existing cache
+ # uses: actions/github-script@v6
+ # with:
+ # script: |
+ # const caches = await github.rest.actions.getActionsCacheList({
+ # owner: context.repo.owner,
+ # repo: context.repo.repo,
+ # })
+ # for (const cache of caches.data.actions_caches) {
+ # if (cache.key == "cache-build-windows") {
+ # console.log('Clearing ' + cache.key + '...')
+ # await github.rest.actions.deleteActionsCacheById({
+ # owner: context.repo.owner,
+ # repo: context.repo.repo,
+ # cache_id: cache.id,
+ # })
+ # console.log("Cache cleared.")
+ # }
+ # }
+ # - name: Uploading new cache
+ # uses: actions/cache@v4.2.3
+ # with:
+ # # caching again since for some reason it doesnt work with the first post cache shit
+ # key: cache-build-windows
+ # path: |
+ # .haxelib/
+ # export/release/windows/obj/
# didnt compile debug in the same job or github would have said that job wasn't completed until debug was done too (debug uploads are not essential)
debug_build:
@@ -84,56 +89,60 @@ jobs:
needs: build # since its low priority, it'll run after, so actions will concentrate first on normal builds
steps:
- name: Pulling the new commit
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
- name: Setting up Haxe
uses: krdlab/setup-haxe@v2
with:
haxe-version: ${{ env.HAXE_VERSION }}
- - name: Restore existing build cache for faster compilation
- uses: actions/cache@v4.2.3
- with:
- # not caching the bin folder to prevent asset duplication and stuff like that
- key: cache-build-windows-debug
- path: |
- .haxelib/
- export/debug/windows/haxe/
- export/debug/windows/obj/
+ # - name: Restore existing build cache for faster compilation
+ # uses: actions/cache@v4.2.3
+ # with:
+ # # not caching the bin folder to prevent asset duplication and stuff like that
+ # key: cache-build-windows-debug
+ # path: |
+ # .haxelib/
+ # export/debug/windows/haxe/
+ # export/debug/windows/obj/
- name: Installing/Updating libraries
run: |
+ haxelib --global update haxelib
haxe -cp commandline -D analyzer-optimize --run Main setup -si --no-vscheck
- name: Building the game
run: |
+ haxelib run lime setup -alias -y -nocffi
+ haxelib run lime rebuild hxcpp
+ haxelib run lime rebuild tools
+ haxelib run lime rebuild windows -nocolor -release
haxelib run lime build windows -debug
- name: Uploading artifact (entire build)
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v7
with:
name: Codename Engine Debug
path: export/debug/windows/bin
- - name: Clearing already existing cache
- uses: actions/github-script@v6
- with:
- script: |
- const caches = await github.rest.actions.getActionsCacheList({
- owner: context.repo.owner,
- repo: context.repo.repo,
- })
- for (const cache of caches.data.actions_caches) {
- if (cache.key == "cache-build-windows-debug") {
- console.log('Clearing ' + cache.key + '...')
- await github.rest.actions.deleteActionsCacheById({
- owner: context.repo.owner,
- repo: context.repo.repo,
- cache_id: cache.id,
- })
- console.log("Cache cleared.")
- }
- }
- - name: Uploading new cache
- uses: actions/cache@v4.2.3
- with:
- # caching again since for some reason it doesnt work with the first post cache shit
- key: cache-build-windows-debug
- path: |
- .haxelib/
- export/debug/windows/haxe/
- export/debug/windows/obj/
+ # - name: Clearing already existing cache
+ # uses: actions/github-script@v6
+ # with:
+ # script: |
+ # const caches = await github.rest.actions.getActionsCacheList({
+ # owner: context.repo.owner,
+ # repo: context.repo.repo,
+ # })
+ # for (const cache of caches.data.actions_caches) {
+ # if (cache.key == "cache-build-windows-debug") {
+ # console.log('Clearing ' + cache.key + '...')
+ # await github.rest.actions.deleteActionsCacheById({
+ # owner: context.repo.owner,
+ # repo: context.repo.repo,
+ # cache_id: cache.id,
+ # })
+ # console.log("Cache cleared.")
+ # }
+ # }
+ # - name: Uploading new cache
+ # uses: actions/cache@v4.2.3
+ # with:
+ # # caching again since for some reason it doesnt work with the first post cache shit
+ # key: cache-build-windows-debug
+ # path: |
+ # .haxelib/
+ # export/debug/windows/obj/
diff --git a/README.md b/README.md
index 02f9929bac..c9ffb7bfcd 100644
--- a/README.md
+++ b/README.md
@@ -47,7 +47,7 @@ The engine uses [HaxeFlixel](https://haxeflixel.com/) and it mainly features:
---
> [!NOTE]
-> Codename Engine as for now supports **Windows x64**, **Mac OS x64** and **Linux x64**.
+> Codename Engine as for now supports **Windows x64**, **Mac OS Universal** and **Linux x64**.
> More platforms will soon come, stay tuned!
> - [ ] **Web (HTML5) Support**
> - [ ] **Mobile Support**
diff --git a/assets/data/editors/layouts/stage/characterEditScreen.xml b/assets/data/editors/layouts/stage/characterEditScreen.xml
index 414973c3ba..fe1bf3ba65 100644
--- a/assets/data/editors/layouts/stage/characterEditScreen.xml
+++ b/assets/data/editors/layouts/stage/characterEditScreen.xml
@@ -95,7 +95,7 @@
import funkin.game.Character;
var curCharBox = new UIAutoCompleteTextBox(col(2), charTypeY, char.curCharacter, 145, 32);
- curCharBox.suggestItems = Character.getList(false);
+ curCharBox.suggestItems = Character.getList(false, false, null, true);
self.add(curCharBox);
diff --git a/assets/data/editors/layouts/stage/spriteEditScreen.xml b/assets/data/editors/layouts/stage/spriteEditScreen.xml
index 1e3bded91f..16f53219be 100644
--- a/assets/data/editors/layouts/stage/spriteEditScreen.xml
+++ b/assets/data/editors/layouts/stage/spriteEditScreen.xml
@@ -32,14 +32,29 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/images/editors/folder.png b/assets/images/editors/folder.png
new file mode 100644
index 0000000000..cb607f5c18
Binary files /dev/null and b/assets/images/editors/folder.png differ
diff --git a/assets/languages/en/Editors.xml b/assets/languages/en/Editors.xml
index 447b0549a6..87138fcaec 100644
--- a/assets/languages/en/Editors.xml
+++ b/assets/languages/en/Editors.xml
@@ -44,6 +44,8 @@
Select a song to modify the charts from.
+ Press ACCEPT to edit this folder.
+ Select a song to modify the charts from in {0}
Press ACCEPT to choose a difficulty to edit.
Press ACCEPT to choose a song variation to edit.
Press ACCEPT to edit the chart for the selected difficulty.
@@ -470,6 +472,8 @@
New Typeface
Press ACCEPT to create a new typeface.
Select a typeface to edit
+ Select a typeface to edit in {0}
+ Press ACCEPT to edit this folder.
New Typeface: Feature Not Implemented!
@@ -530,6 +534,8 @@
Press ACCEPT to edit this stage.
New Stage
Press ACCEPT to create a new stage.
+ Press ACCEPT to edit this folder.
+ Select a stage to edit in {0}
diff --git a/assets/languages/en/Options.xml b/assets/languages/en/Options.xml
index edf59dc7d4..0227aeb67a 100644
--- a/assets/languages/en/Options.xml
+++ b/assets/languages/en/Options.xml
@@ -61,6 +61,7 @@
Open Console
Reload State
+ Open Inspector
@@ -233,6 +234,9 @@
Legacy Memory Counter
If checked, the memory counter will show the Haxe GC memory usage and its peak (the original behavior) instead of the working set memory seen in Task Manager.
+
+ ImGui Multi Viewport
+ If checked, ImGui windows can be dragged out of the main viewport, disabled by default on Linux due to common bugs and issues.
\ No newline at end of file
diff --git a/assets/shaders/engine/editorWaveforms.frag b/assets/shaders/engine/editorWaveforms.frag
index c65452dc7f..e3b3d21260 100644
--- a/assets/shaders/engine/editorWaveforms.frag
+++ b/assets/shaders/engine/editorWaveforms.frag
@@ -1,6 +1,9 @@
-#pragma header
+// this causes issues apparently?? need to check GLSLSourceAssembler
+// but it could just be unnecessary too anyway
+// if not, put this also in editorWaveformsRainbow.frag
+//#version 120
-// Used in charter by waveforms
+#pragma header
const vec3 gradient1 = vec3(114.0/255.0, 81.0/255.0, 135.0/255.0);
const vec3 gradient2 = vec3(144.0/255.0, 80.0/255.0, 186.0/255.0);
@@ -18,6 +21,21 @@ uniform vec2 waveformSize;
uniform bool lowDetail;
+float catmullRom(
+ float p0, float p1, float p2, float p3,
+ float t
+) {
+ float t2 = t * t;
+ float t3 = t2 * t;
+
+ return 0.5 * (
+ (2.0 * p1) +
+ (-p0 + p2) * t +
+ (2.0*p0 - 5.0*p1 + 4.0*p2 - p3) * t2 +
+ (-p0 + 3.0*p1 - 3.0*p2 + p3) * t3
+ );
+}
+
float getAmplitude(vec2 pixel) {
float pixelID = floor((pixel.y+pixelOffset)/3.0);
diff --git a/building/alsoft.txt b/building/alsoft.txt
deleted file mode 100644
index 58d8c74cc1..0000000000
--- a/building/alsoft.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-[General]
-sample-type=float32
-stereo-mode=speakers
-hrtf=false
-cf_level=0
-output-limiter=false
-front-stabilizer=false
-volume-adjust=0
-period_size=441
-sources=512
-sends=64
-dither=false
-
-[decoder]
-hq-mode=true
-distance-comp=true
-nfc=false
\ No newline at end of file
diff --git a/building/libs.xml b/building/libs.xml
index 8888214090..993d4a89d8 100644
--- a/building/libs.xml
+++ b/building/libs.xml
@@ -3,36 +3,34 @@
Preparing installation...
-
-
-
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
@@ -41,15 +39,16 @@
+
-
+
+
+
+
diff --git a/project.xml b/project.xml
index 3bcdd92c26..585f8b1a6a 100644
--- a/project.xml
+++ b/project.xml
@@ -1,84 +1,70 @@
-
-
-
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
+
+
-
+
+
-
-
+
+
-
+
+
+
-
-
+
+
-
+
-
+
-
+
@@ -87,36 +73,60 @@
-
+
-
+
-
+
-
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -124,34 +134,49 @@
-
-
+
-
-
+
+
-
+
+
-
-
-
-
-
+
+
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
-
-
-
-
-
+
+
+
@@ -167,81 +192,70 @@
-
-
-
-
+
-
-
-
-
-
-
+
-
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
-
-
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
+
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/source/flixel/sound/FlxSound.hx b/source/flixel/sound/FlxSound.hx
deleted file mode 100644
index 9891bc934a..0000000000
--- a/source/flixel/sound/FlxSound.hx
+++ /dev/null
@@ -1,960 +0,0 @@
-package flixel.sound;
-
-import lime.media.AudioBuffer;
-import lime.media.AudioSource;
-import lime.media.AudioManager;
-
-import openfl.Assets;
-import openfl.events.IEventDispatcher;
-import openfl.events.Event;
-import openfl.media.Sound;
-import openfl.media.SoundChannel;
-import openfl.media.SoundTransform;
-import openfl.media.SoundMixer;
-import openfl.net.URLRequest;
-import openfl.utils.AssetType;
-#if flash11
-import openfl.utils.ByteArray;
-#end
-
-import flixel.math.FlxMath;
-import flixel.math.FlxPoint;
-import flixel.system.FlxAssets.FlxSoundAsset;
-import flixel.tweens.FlxTween;
-import flixel.util.FlxDestroyUtil;
-import flixel.util.FlxSignal;
-import flixel.util.FlxStringUtil;
-import flixel.FlxBasic;
-import flixel.FlxObject;
-import flixel.FlxG;
-
-@:access(flixel.FlxGame)
-class FlxSound extends FlxBasic {
- /**
- * The x position of this sound in world coordinates.
- * Only really matters if you are doing proximity/panning stuff.
- */
- public var x:Float;
-
- /**
- * The y position of this sound in world coordinates.
- * Only really matters if you are doing proximity/panning stuff.
- */
- public var y:Float;
-
- /**
- * Whether or not this sound should be automatically destroyed when you switch states.
- */
- public var persist:Bool;
-
- /**
- * Whether or not the sound is currently playing.
- */
- public var playing(get, never):Bool;
-
- /**
- * Set volume to a value between 0 and 1* to change how this sound is.
- */
- public var volume(get, set):Float;
-
- /**
- * Whether to make this sound muted or not.
- */
- public var muted(default, set):Bool;
-
- #if FLX_PITCH
- /**
- * Set pitch, which also alters the playback speed. Default is 1.
- * @since 5.0.0
- */
- public var pitch(get, set):Float;
-
- /**
- * Alters the pitch of the sound depends on the current FlxG.timeScale. Default is true.
- * @since raltyMod
- */
- public var timeScaleBased:Bool;
- #end
-
- /**
- * Pan amount. -1 = full left, 1 = full right. Proximity based panning overrides this.
- */
- public var pan(get, set):Float;
-
- /**
- * The position in runtime of the music playback in milliseconds.
- * If set while paused, changes only come into effect after a `resume()` call.
- */
- public var time(get, set):Float;
-
- /**
- * The offset for this FlxSound.
- * Useful for just generally offsetting this sound without affecting time.
- * @since raltyMod
- */
- public var offset(get, set):Float;
-
- /**
- * The length of the sound in milliseconds.
- * @since 4.2.0
- */
- public var length(get, never):Float;
-
- /**
- * The latency of the sound in milliseconds.
- * @since raltyMod
- */
- //public var latency(get, never):Float;
-
- /**
- * Whether or not this sound should loop.
- */
- public var looped(default, set):Bool;
-
- /**
- * In case of looping, the point (in milliseconds) from where to restart the sound when it loops back
- * @since 4.1.0
- */
- public var loopTime(default, set):Float;
-
- /**
- * At which point to stop playing the sound, in milliseconds.
- * If not set / `null`, the sound completes normally.
- * @since 4.2.0
- */
- public var endTime(default, set):Null;
-
- /**
- * The sound's "target" (for proximity and panning).
- * @since raltyMod
- */
- public var target:Null;
-
- /**
- * The maximum effective radius of this sound (for proximity and panning).
- * @since raltyMod
- */
- public var radius:Float;
-
- /**
- * Whether the proximity alters the pan or not.
- * @since raltyMod
- */
- public var proximityPan:Bool;
-
- /**
- * Controls how much this object is affected by camera scrolling. `0` = no movement (e.g. a static sound),
- * This is only useful if used with proximity (Initialized once proximity is used).
- * @since raltyMod
- */
- public var scrollFactor(default, null):FlxPoint;
-
- /**
- * Stores for how much channels are in the loaded sound.
- * @since raltyMod
- */
- public var channels(get, never):Int;
-
- /**
- * Whether or not this sound is stereo instead of mono.
- * @since raltyMod
- */
- public var stereo(get, never):Bool;
-
- /**
- * Whether or not this sound is loaded yet.
- * @since raltyMod
- */
- public var loaded(get, never):Bool;
-
- /**
- * Whether or not this sound is streamed, only vorbis though.
- * @since raltyMod
- */
- public var streamed(get, never):Bool;
-
- /**
- * Stores the average wave amplitude of both stereo channels.
- * @since raltyMod
- */
- public var amplitude(get, never):Float;
-
- /**
- * Just the amplitude of the left stereo channel
- * @since raltyMod
- */
- public var amplitudeLeft(get, never):Float;
-
- /**
- * Just the amplitude of the right stereo channel
- * @since raltyMod
- */
- public var amplitudeRight(get, never):Float;
-
- /**
- * The ID3 song name. Defaults to null. Currently only works for MP3 streamed sounds.
- */
- public var name(default, null):String;
-
- /**
- * The ID3 artist name. Defaults to null. Currently only works for MP3 streamed sounds.
- */
- public var artist(default, null):String;
-
- /**
- * Whether to call `destroy()` when the sound has finished playing.
- */
- public var autoDestroy:Bool;
-
- /**
- * Signal that is dispatched on sound complete.
- * Seperates the looping from onComplete.
- */
- public final onFinish:FlxSignal;
-
- /**
- * Tracker for sound complete callback. If assigned, will be called
- * each time when sound reaches its end.
- */
- //@:deprecated("`FlxSound.onComplete` is deprecated! Use `FlxSound.onFinish` instead.")
- public var onComplete:Void->Void;
-
- /**
- * The sound group this sound belongs to
- */
- public var group(default, set):FlxSoundGroup;
-
- /**
- * Stores the sound lime AudioBuffer if exists.
- * @since raltyMod
- */
- public var buffer(get, never):AudioBuffer;
-
- /**
- * The tween used to fade this sound's volume in and out (set via `fadeIn()` and `fadeOut()`)
- * @since 4.1.0
- */
- public var fadeTween:FlxTween;
-
- @:allow(flixel.system.frontEnds.SoundFrontEnd.load) var _sound:Sound;
- var _transform:SoundTransform;
- var _channel:SoundChannel;
- var _source:AudioSource;
- var _paused:Bool;
- var _volume:Float;
- var _volumeAdjust:Float;
- var _pan:Float;
- var _panAdjust:Float;
- var _time:Float;
- var _offset:Float;
- var _timeInterpolation:Float;
- var _lastTime:Null; // FlxG.game.getTicks(), in MS
- var _length:Float;
- #if FLX_PITCH
- var _pitch:Float;
- var _timeScaleAdjust:Float;
- var _realPitch:Float;
- #end
- var _amplitudeLeft:Float;
- var _amplitudeRight:Float;
- var _amplitudeUpdate:Bool;
- var _alreadyPaused:Null;
-
- public function new() {
- super();
- onFinish = new FlxSignal();
- revive();
- }
-
- /**
- * Resets this FlxSound properties.
- *
- * @param clean Whether if this FlxSound also needs to be cleaned up too.
- */
- public function reset():Void {
- if (_source != null) stop();
- onFinish.removeAll();
-
- x = y = 0;
- @:bypassAccessor muted = false;
- @:bypassAccessor looped = false;
- @:bypassAccessor loopTime = 0;
- @:bypassAccessor endTime = null;
- autoDestroy = false;
- visible = false;
- target = null;
- radius = 0;
- proximityPan = true;
- onComplete = null;
- timeScaleBased = true;
-
- _alreadyPaused = null;
- _cameras = null;
- _lastTime = null;
- _paused = false;
- _offset = _length = _time = 0;
- _volume = _volumeAdjust = 1;
- _amplitudeLeft = _amplitudeRight = 0;
- _amplitudeUpdate = true;
- #if FLX_PITCH _pitch = _realPitch = _timeScaleAdjust = 1; #end
-
- if (_transform == null) _transform = new SoundTransform();
- }
-
- /**
- * Internal cleanup function for cleaning up this FlxSound.
- * @since raltyMod
- */
- function cleanup(destroySound:Bool, resetPosition:Bool = true) @:privateAccess {
- active = false;
- _lastTime = null;
-
- if (destroySound) {
- scrollFactor = FlxDestroyUtil.put(scrollFactor);
-
- if (group != null) group.remove(this);
-
- if (_channel != null) {
- _channel.stop();
- _channel = null;
- }
- if (_source != null) {
- _source.onComplete.remove(stopped);
- _source.onLoop.remove(source_looped);
- _source.dispose();
- }
- _source = null;
- _sound = null;
-
- if (autoDestroy) persist = false;
- reset();
- }
- else if (_channel != null && _channel.__isValid) {
- if (resetPosition) {
- _source.stop();
-
- _time = 0;
- _paused = false;
- }
- else if (!_paused) {
- get_time();
- _source.pause();
- }
- }
- }
-
- /**
- * Handles fade out, fade in, panning, proximity, and amplitude operations each frame.
- */
- override function update(elapsed:Float):Void {
- if (!playing) return;
-
- var timeScaleTarget = timeScaleBased ? FlxG.timeScale : 1.0;
- if (_timeScaleAdjust != timeScaleTarget) {
- _timeScaleAdjust = timeScaleTarget;
- pitch = _pitch;
- if (_channel == null) return;
- }
-
- _amplitudeUpdate = true;
-
- // Distance-based volume control
- if (target != null) {
- var targetPosition = target.getPosition(), position = getPosition();
- var camera = camera;
- if (camera != null) {
- targetPosition.subtract(camera.scroll.x * target.scrollFactor.x, camera.scroll.y * target.scrollFactor.y);
- if (scrollFactor != null) position.subtract(camera.scroll.x * scrollFactor.x, camera.scroll.y * scrollFactor.y);
- else position.subtract(camera.scroll.x, camera.scroll.y);
- }
-
- var radialMultiplier = targetPosition.distanceTo(position) / radius;
-
- // Make it so it affects the 3d position of the source and not just the panning?
- _volumeAdjust = 1 - FlxMath.bound(radialMultiplier, 0, 1);
- if (proximityPan) _panAdjust = (position.x - targetPosition.x) / radius;
-
- targetPosition.put();
- position.put();
- }
- else
- _volumeAdjust = 1.0;
-
- updateTransform();
- }
-
- override function revive() {
- reset();
- super.revive();
- }
-
- override function kill() {
- super.kill();
- reset();
- }
-
- override function destroy() {
- super.destroy();
- cleanup(true);
- }
-
- /**
- * One of the main setup functions for sounds, this function loads a sound from an embedded MP3.
- *
- * @param embeddedSound An embedded Class object representing an MP3 file.
- * @param looped Whether or not this sound should loop endlessly.
- * @param autoDestroy Whether or not this FlxSound instance should be destroyed when the sound finishes playing.
- * Default value is false, but `FlxG.sound.play()` and `FlxG.sound.stream()` will set it to true by default.
- * @param onComplete Called when the sound finished playing
- * @return This FlxSound instance (nice for chaining stuff together, if you're into that).
- */
- public function loadEmbedded(embeddedSound:FlxSoundAsset, looped = false, autoDestroy = false, ?onComplete:Void->Void):FlxSound {
- if (!exists || embeddedSound == null) return this;
- cleanup(true);
-
- if ((embeddedSound is Sound)) _sound = embeddedSound;
- else if ((embeddedSound is Class)) _sound = Type.createInstance(embeddedSound, []);
- else if ((embeddedSound is String)) {
- if (Assets.exists(embeddedSound, AssetType.MUSIC) && this == FlxG.sound.music)
- _sound = Assets.getMusic(embeddedSound);
- else if (Assets.exists(embeddedSound, AssetType.SOUND))
- _sound = Assets.getSound(embeddedSound);
- else
- FlxG.log.error('Could not find a Sound asset with an ID of \'$embeddedSound\'.');
- }
-
- return init(looped, autoDestroy, onComplete);
- }
-
- /**
- * One of the main setup functions for sounds, this function loads a sound from a URL.
- *
- * @param soundURL A string representing the URL of the MP3 file you want to play.
- * @param looped Whether or not this sound should loop endlessly.
- * @param autoDestroy Whether or not this FlxSound instance should be destroyed when the sound finishes playing.
- * Default value is false, but `FlxG.sound.play()` and `FlxG.sound.stream()` will set it to true by default.
- * @param onComplete Called when the sound finished playing
- * @param onLoad Called when the sound finished loading.
- * @return This FlxSound instance (nice for chaining stuff together, if you're into that).
- */
- public function loadStream(soundURL:String, looped = false, autoDestroy = false, ?onComplete:Void->Void, ?onLoad:Void->Void):FlxSound {
- if (!exists) return this;
- cleanup(true);
-
- _sound = new Sound();
- _sound.addEventListener(Event.ID3, gotID3);
- var loadCallback:Event->Void = null;
- loadCallback = function(e:Event)
- {
- (e.target : IEventDispatcher).removeEventListener(e.type, loadCallback);
- // Check if the sound was destroyed before calling. Weak ref doesn't guarantee GC.
- if (_sound == e.target)
- {
- _length = _sound.length;
- if (onLoad != null)
- onLoad();
- }
- }
- // Use a weak reference so this can be garbage collected if destroyed before loading.
- _sound.addEventListener(Event.COMPLETE, loadCallback, false, 0, true);
- _sound.load(new URLRequest(soundURL));
-
- return init(looped, autoDestroy, onComplete);
- }
-
- #if flash11
- /**
- * One of the main setup functions for sounds, this function loads a sound from a ByteArray.
- *
- * @param bytes A ByteArray object.
- * @param looped Whether or not this sound should loop endlessly.
- * @param autoDestroy Whether or not this FlxSound instance should be destroyed when the sound finishes playing.
- * Default value is false, but `FlxG.sound.play()` and `FlxG.sound.stream()` will set it to true by default.
- * @return This FlxSound instance (nice for chaining stuff together, if you're into that).
- */
- public function loadByteArray(bytes:ByteArray, looped = false, autoDestroy = false, ?onComplete:Void->Void):FlxSound {
- if (!exists) return this;
- cleanup(true);
-
- _sound = new Sound();
- _sound.addEventListener(Event.ID3, gotID3);
- _sound.loadCompressedDataFromByteArray(bytes, bytes.length);
-
- return init(looped, autoDestroy, onComplete);
- }
- #end
-
- function init(?looped:Null, ?autoDestroy:Null, ?onComplete:NullVoid>):FlxSound {
- if (looped != null) this.looped = looped;
- if (autoDestroy != null) this.autoDestroy = autoDestroy;
- if (onComplete != null) this.onComplete = onComplete;
-
- if (_sound != null) makeChannel();
- else _length = 0;
-
- endTime = null;
-
- return this;
- }
-
- /**
- * Call inbetween init function if a sound asset does exists.
- * @since raltyMod
- */
- function makeChannel() @:privateAccess {
- if (_sound.__buffer == null) return;
- if (_source != null) _source.dispose();
-
- if (_channel == null) (_source = (_channel = new SoundChannel(null)).__source = new AudioSource(_sound.__buffer)).gain = 0;
- else {
- (_source = new AudioSource(_sound.__buffer)).gain = 0;
- _channel.__dispose();
- _channel.__source = _source;
- SoundMixer.__registerSoundChannel(_channel);
- }
- _length = _source.length;
-
- _source.onComplete.add(stopped);
- _source.onLoop.add(source_looped);
- _channel.__soundTransform = _transform;
- _channel.__isValid = true;
- }
-
- /**
- * Call after adjusting the volume to update the sound channel's settings.
- */
- @:allow(flixel.sound.FlxSoundGroup)
- function updateTransform() {
- _transform.volume = calcTransformVolume();
- _transform.pan = _pan + _panAdjust;
- if (_channel != null) _channel.soundTransform = _transform;
- }
-
- public function calcTransformVolume():Float {
- if (muted) return 0.0;
-
- #if FLX_SOUND_SYSTEM
- if (FlxG.sound.muted) return 0.0;
-
- // TODO: when flixel-cne is updated, enable this
- //return FlxG.sound.applySoundCurve(FlxG.sound.volume * volume);
- return FlxG.sound.volume * getActualVolume();
- #else
- return getActualVolume();
- #end
- }
-
- /**
- * Call this function if you want this sound's volume to change
- * based on distance from a particular FlxObject.
- *
- * @param X The X position of the sound.
- * @param Y The Y position of the sound.
- * @param TargetObject The object you want to track.
- * @param Radius The maximum distance this sound can travel.
- * @param Pan Whether panning should be used in addition to the volume changes.
- * @return This FlxSound instance (nice for chaining stuff together, if you're into that).
- */
- public function proximity(x = 0.0, y = 0.0, ?targetObject:FlxObject, ?radius:Float, pan = true, ?scrollFactor:FlxPoint):FlxSound {
- setPosition(x, y);
- if (targetObject != null) this.target = targetObject;
- if (radius != null) this.radius = radius;
- proximityPan = pan;
-
- if (this.scrollFactor == null) this.scrollFactor = FlxPoint.get(1, 1);
- if (scrollFactor != null) this.scrollFactor.copyFrom(scrollFactor);
-
- return this;
- }
-
- /**
- * Helper function to set the coordinates of this object.
- * Sound positioning is used in conjunction with proximity/panning.
- *
- * @param x The new x position
- * @param y The new y position
- */
- public function setPosition(x = 0.0, y = 0.0):Void {
- this.x = x;
- this.y = y;
- }
-
- /**
- * Returns the world position of this object.
- *
- * @param result Optional arg for the returning point.
- * @return The world position of this object.
- * @since raltyMod
- */
- public function getPosition(?result:FlxPoint):FlxPoint {
- if (result == null)
- result = FlxPoint.get();
-
- return result.set(x, y);
- }
-
- /**
- * Call this function to play the sound - also works on paused sounds.
- *
- * @param forceRestart Whether to start the sound over or not.
- * Default value is false, meaning if the sound is already playing or was
- * paused when you call play(), it will continue playing from its current
- * position, NOT start again from the beginning.
- * @param startTime At which point to start playing the sound, in milliseconds.
- * @param endTime At which point to stop playing the sound, in milliseconds.
- * If not set / `null`, the sound completes normally.
- */
- public function play(forceRestart = false, startTime = 0.0, ?endTime:Null):FlxSound {
- if (!exists) return this;
-
- if (forceRestart) cleanup(false, true);
- else if (playing) return this;
-
- if (endTime != null) this.endTime = endTime;
- if (_paused) resume();
- else startSound(startTime);
-
- return this;
- }
-
- /**
- * Unpause a sound. Only works on sounds that have been paused.
- * @return This FlxSound instance (nice for chaining stuff together, if you're into that).
- */
- public function resume():FlxSound {
- if (_paused) startSound(_time);
- return this;
- }
-
- /**
- * Call this function to pause this sound.
- * @return This FlxSound instance (nice for chaining stuff together, if you're into that).
- */
- public function pause():FlxSound {
- if (!playing) return this;
-
- cleanup(false, false);
- _paused = true;
- return this;
- }
-
- /**
- * Call this function to stop this sound.
- * @return This FlxSound instance (nice for chaining stuff together, if you're into that).
- */
- public function stop():FlxSound {
- cleanup(autoDestroy, true);
- return this;
- }
-
- /**
- * Helper function that tweens this sound's volume.
- *
- * @param duration The amount of time the fade-out operation should take.
- * @param to The volume to tween to, 0 by default.
- * @return This FlxSound instance (nice for chaining stuff together, if you're into that).
- */
- public function fadeOut(duration = 1.0, to = 0.0, ?onComplete:FlxTween->Void):FlxSound {
- if (fadeTween != null) fadeTween.cancel();
- fadeTween = FlxTween.num(volume, to, duration, {onComplete: onComplete}, volumeTween);
-
- return this;
- }
-
- /**
- * Helper function that tweens this sound's volume.
- * If the sound wasn't playing at all, it'll play before the tween starts.
- *
- * @param duration The amount of time the fade-in operation should take.
- * @param from The volume to tween from, 0 by default.
- * @param to The volume to tween to, 1 by default.
- * @return This FlxSound instance (nice for chaining stuff together, if you're into that).
- */
- public function fadeIn(duration = 1.0, from = 0.0, to = 1.0, ?onComplete:FlxTween->Void):FlxSound {
- if (!playing) play();
- if (fadeTween != null) fadeTween.cancel();
- fadeTween = FlxTween.num(from, to, duration, {onComplete: onComplete}, volumeTween);
-
- return this;
- }
-
- function volumeTween(f:Float) volume = f;
-
- /**
- * Returns the currently selected "real" volume of the sound (takes fades and proximity into account).
- *
- * @return The adjusted volume of the sound.
- */
- public inline function getActualVolume():Float
- return (group != null ? group.getVolume() : 1.0) * _volume * _volumeAdjust;
-
- #if FLX_PITCH
- /**
- * Returns the currently selected "real" pitch of the sound.
- *
- * @return The adjusted pitch of the sound.
- */
- public inline function getActualPitch():Float
- return _realPitch;
- #end
-
- /**
- * Returns the actual time coming from the internal, can be used for detecting sync error.
- *
- * @return The actual time of the sound.
- */
- public inline function getActualTime():Float {
- get_time();
- return _time;
- }
-
- /**
- * An internal helper function used to attempt to start playing
- * the sound and populate the _channel variable.
- */
- function startSound(startTime:Float) @:privateAccess {
- if (_sound == null) return;
-
- _paused = false;
- _time = startTime;
- _lastTime = FlxG.game.getTicks();
- if (_channel == null || !_channel.__isValid || _source == null #if lime_cffi || _source.__backend.disposed #end)
- makeChannel();
-
- if (_channel != null) {
- #if FLX_PITCH
- _timeScaleAdjust = timeScaleBased ? FlxG.timeScale : 1.0;
- pitch = _pitch;
- #end
-
- updateTransform();
- _channel.__lastPeakTime = -10;
- _channel.__leftPeak = 0;
- _channel.__rightPeak = 0;
-
- #if lime_cffi _source.__backend.playing = true; #end
- _source.offset = 0;
- _source.currentTime = startTime + _offset;
- #if !lime_cffi _source.play(); #end
-
- looped = looped;
- loopTime = loopTime;
- endTime = endTime;
-
- active = true;
- }
- else {
- exists = false;
- active = false;
- }
- }
-
- function stopped() {
- onFinish.dispatch();
-
- if (onComplete != null) onComplete();
-
- if (looped) {
- cleanup(false);
- play(false, loopTime, endTime);
- }
- else cleanup(autoDestroy);
- }
-
- function source_looped() {
- if (onComplete != null) onComplete();
-
- if (!looped) {
- cleanup(autoDestroy);
- _lastTime = FlxG.game.getTicks();
- _time = loopTime;
- }
- else if (_source != null)
- _source.loops = 999;
- }
-
- /**
- * Internal event handler for ID3 info (i.e. fetching the song name).
- */
- function gotID3(_) {
- name = _sound.id3.songName;
- artist = _sound.id3.artist;
- _sound.removeEventListener(Event.ID3, gotID3);
- }
-
- #if FLX_SOUND_SYSTEM
- @:allow(flixel.system.frontEnds.SoundFrontEnd)
- function onFocus() if (!_alreadyPaused) {
- resume();
- _alreadyPaused = null;
- }
-
- @:allow(flixel.system.frontEnds.SoundFrontEnd)
- function onFocusLost() if (_alreadyPaused == null && !(_alreadyPaused = _paused)) pause();
- #end
-
- function set_group(value:FlxSoundGroup):FlxSoundGroup {
- if (value != null) value.add(this);
- else group.remove(this);
- return group;
- }
-
- inline function get_playing():Bool @:privateAccess
- return _source != null && _source.playing;
-
- inline function get_volume():Float
- return _volume;
-
- inline function set_volume(v:Float):Float {
- _volume = FlxMath.bound(v, 0, 10);
- updateTransform();
- return _volume;
- }
-
- inline function set_muted(v:Bool):Bool {
- muted = v;
- updateTransform();
- return muted;
- }
-
- inline function get_pan():Float return _pan;
- inline function set_pan(v:Float):Float {
- _pan = FlxMath.bound(v, -1, 1);
- updateTransform();
- return _pan;
- }
-
- inline function get_loaded():Bool
- return buffer != null;
-
- inline function get_streamed():Bool
- @:privateAccess return #if lime_vorbis _sound != null && _sound.__buffer.__srcVorbisFile != null #else false #end;
-
- inline function get_buffer():AudioBuffer
- @:privateAccess return _sound != null ? _sound.__buffer : null;
-
- inline function update_amplitude():Void @:privateAccess {
- if (_channel != null && _channel.__updatePeaks(get_time()) && _amplitudeUpdate) {
- _amplitudeUpdate = false;
- _amplitudeLeft = _channel.__leftPeak;
- _amplitudeRight = _channel.__rightPeak;
- }
- }
-
- inline function get_amplitudeLeft():Float {
- update_amplitude();
- return _amplitudeLeft;
- }
-
- inline function get_amplitudeRight():Float {
- update_amplitude();
- return _amplitudeRight;
- }
-
- inline function get_amplitude():Float {
- update_amplitude();
- return Math.max(_amplitudeLeft, _amplitudeRight);//if (stereo) (_amplitudeLeft + _amplitudeRight) * 0.5; else _amplitudeLeft;
- }
-
- inline function get_channels():Int
- @:privateAccess return (buffer != null) ? buffer.channels : 0;
-
- inline function get_stereo():Bool
- return channels > 1;
-
- #if FLX_PITCH
- inline function get_pitch():Float
- return _pitch;
-
- inline function set_pitch(v:Float):Float {
- _realPitch = (_pitch = v) * _timeScaleAdjust;
- if (_source != null) _source.pitch = _realPitch;
- return _pitch;
- }
- #end
-
- function set_looped(v:Bool):Bool {
- if (playing) _source.loops = v ? 999 : 0;
- return looped = v;
- }
-
- function set_loopTime(v:Float):Float {
- if (playing) _source.loopTime = v;
- return loopTime = v;
- }
-
- function set_endTime(v:Null):Null {
- if (playing) {
- if (v != null && v > 0 && v < _length) _source.length = v;
- else _source.length = null;
- }
- return endTime = v;
- }
-
- inline function getFakeTime():Float {
- if (_source.playing && _realPitch > 0 && _lastTime != null)
- return _time + (FlxG.game.getTicks() - _lastTime) * _realPitch * _timeInterpolation;
- else
- return _time;
- }
- function get_time():Float {
- if (_source == null || /*AudioManager.context == null*/funkin.backend.system.Main.audioDisconnected) return _time;
-
- final sourceTime = _source.currentTime - _source.offset - _offset;
- if (!_source.playing || _realPitch <= 0) {
- _lastTime = null;
- return _time = sourceTime;
- }
-
- final fakeTime = getFakeTime();
- if (sourceTime != _time) {
- _lastTime = FlxG.game.getTicks();
- if ((_timeInterpolation = 1 - Math.min(fakeTime - sourceTime, 1000) * 0.001) < 1 && _timeInterpolation > .9)
- return _time = fakeTime;
- else {
- _timeInterpolation = 1;
- return _time = sourceTime;
- }
- }
- else
- return fakeTime;
- }
-
- function set_time(time:Float):Float @:privateAccess {
- time = FlxMath.bound(time, _offset, length - 1);
- if (_channel != null && _realPitch > 0) {
- if (!_channel.__isValid) {
- cleanup(false, true);
- startSound(time);
- }
- else {
- _source.offset = 0;
- _source.currentTime = time + _offset;
- }
- }
-
- _lastTime = null;
- return _time = time;
- }
-
- function get_offset():Float return _offset;
- function set_offset(offset:Float):Float {
- if (_offset == (_offset = offset)) return offset;
- //time = time + _offset;
- return offset;
- }
-
- function get_length():Float return _length - _offset;
-
- //function get_latency():Float return _source != null ? _source.latency : 0;
-
- override function toString():String {
- return FlxStringUtil.getDebugString([
- LabelValuePair.weak("playing", playing),
- LabelValuePair.weak("time", time),
- LabelValuePair.weak("length", length),
- LabelValuePair.weak("volume", volume),
- LabelValuePair.weak("pitch", pitch)
- ]);
- }
-}
\ No newline at end of file
diff --git a/source/flixel/sound/FlxSoundGroup.hx b/source/flixel/sound/FlxSoundGroup.hx
deleted file mode 100644
index 9c2fcc1cbd..0000000000
--- a/source/flixel/sound/FlxSoundGroup.hx
+++ /dev/null
@@ -1,120 +0,0 @@
-package flixel.sound;
-
-/**
- * A way of grouping sounds for things such as collective volume control
- */
-class FlxSoundGroup
-{
- /**
- * The sounds in this group
- */
- public var sounds:Array = [];
-
- /**
- * The volume of this group
- */
- public var volume(default, set):Float;
-
- /**
- * Whether or not this group is muted
- */
- public var muted(default, set):Bool;
-
- /**
- * Create a new sound group
- * @param volume The initial volume of this group
- */
- public function new(volume:Float = 1)
- {
- this.volume = volume;
- }
-
- /**
- * Add a sound to this group, will remove the sound from any group it is currently in
- * @param sound The sound to add to this group
- * @return True if sound was successfully added, false otherwise
- */
- public function add(sound:FlxSound):Bool
- {
- if (!sounds.contains(sound))
- {
- // remove from prev group
- if (sound.group != null)
- sound.group.sounds.remove(sound);
-
- sounds.push(sound);
- @:bypassAccessor
- sound.group = this;
- sound.updateTransform();
- return true;
- }
- return false;
- }
-
- /**
- * Remove a sound from this group
- * @param sound The sound to remove
- * @return True if sound was successfully removed, false otherwise
- */
- public function remove(sound:FlxSound):Bool
- {
- if (sounds.contains(sound))
- {
- @:bypassAccessor
- sound.group = null;
- sounds.remove(sound);
- sound.updateTransform();
- return true;
- }
- return false;
- }
-
- /**
- * Call this function to pause all sounds in this group.
- * @since 4.3.0
- */
- public function pause():Void
- {
- for (sound in sounds)
- sound.pause();
- }
-
- /**
- * Unpauses all sounds in this group. Only works on sounds that have been paused.
- * @since 4.3.0
- */
- public function resume():Void
- {
- for (sound in sounds)
- sound.resume();
- }
-
- /**
- * Returns the volume of this group, taking `muted` in account.
- * @return The volume of the group or 0 if the group is muted.
- */
- public function getVolume():Float
- {
- return muted ? 0.0 : volume;
- }
-
- function set_volume(volume:Float):Float
- {
- this.volume = volume;
- for (sound in sounds)
- {
- sound.updateTransform();
- }
- return volume;
- }
-
- function set_muted(value:Bool):Bool
- {
- muted = value;
- for (sound in sounds)
- {
- sound.updateTransform();
- }
- return muted;
- }
-}
diff --git a/source/flx3d/Flx3DCamera.hx b/source/flx3d/Flx3DCamera.hx
deleted file mode 100644
index 48089857b7..0000000000
--- a/source/flx3d/Flx3DCamera.hx
+++ /dev/null
@@ -1,188 +0,0 @@
-package flx3d;
-
-#if THREE_D_SUPPORT
-import away3d.cameras.Camera3D;
-import away3d.containers.ObjectContainer3D;
-import away3d.containers.View3D;
-import away3d.entities.Mesh;
-import away3d.entities.SegmentSet;
-import away3d.entities.TextureProjector;
-import away3d.events.Asset3DEvent;
-import away3d.events.LoaderEvent;
-import away3d.library.Asset3DLibrary;
-import away3d.library.Asset3DLibraryBundle;
-import away3d.library.assets.Asset3DType;
-import away3d.lights.LightBase;
-import away3d.loaders.misc.AssetLoaderContext;
-import away3d.loaders.misc.AssetLoaderToken;
-import away3d.loaders.parsers.*;
-import away3d.materials.TextureMaterial;
-import away3d.primitives.SkyBox;
-import away3d.utils.Cast;
-import away3d.utils.Utils.expect;
-import flixel.FlxG;
-import flx3d.Flx3DUtil;
-import haxe.io.Path;
-import openfl.Assets;
-#end
-import flixel.FlxCamera;
-
-class Flx3DCamera extends FlxCamera
-{
- #if THREE_D_SUPPORT
- private static var __3DIDS:Int = 0;
-
- public var view:View3D;
-
- var meshes:Array = [];
-
- public function new(X:Int = 0, Y:Int = 0, Width:Int = 0, Height:Int = 0, DefaultZoom:Float = 1)
- {
- if (!Flx3DUtil.is3DAvailable())
- throw "[Flx3DCamera] 3D is not available on this platform. Stages in use: "
- + Flx3DUtil.getTotal3D()
- + ", Max stages allowed: "
- + FlxG.stage.stage3Ds.length
- + ".";
- super(X, Y, Width, Height, DefaultZoom);
- __cur3DStageID = __3DIDS++;
-
- view = new View3D();
- view.width = this.width;
- view.height = this.height;
- view.visible = true;
-
- FlxG.stage.addChild(view);
- }
-
- public override function render()
- {
- super.render();
-
- view.x = FlxG.game.x + FlxG.game.scaleX * (flashSprite.x + flashSprite.scaleX * (_scrollRect.x + _scrollRect.scaleX * (_scrollRect.scrollRect.x)));
- view.x -= _scrollRect.scrollRect.width * ((zoom / initialZoom) - 1) / 2;
- view.y = FlxG.game.y + FlxG.game.scaleY * (flashSprite.y + flashSprite.scaleY * (_scrollRect.y + _scrollRect.scaleY * (_scrollRect.scrollRect.y)));
- view.y -= _scrollRect.scrollRect.height * ((zoom / initialZoom) - 1) / 2;
-
- view.width = _scrollRect.scrollRect.width / initialZoom;
- view.width += _scrollRect.scrollRect.width / initialZoom * ((zoom / initialZoom) - 1);
- view.height = _scrollRect.scrollRect.height / initialZoom;
- view.height += _scrollRect.scrollRect.height / initialZoom * ((zoom / initialZoom) - 1);
-
- view.render();
- }
-
- public function addModel(assetPath:String, callback:Asset3DEvent->Void, ?texturePath:String, smoothTexture:Bool = true)
- {
- var model = Assets.getBytes(assetPath);
- if (model == null)
- throw 'Model at ${assetPath} was not found.';
-
- var context = new AssetLoaderContext();
- var noExt = Path.withoutExtension(assetPath);
- trace(noExt);
- context.mapUrlToData(Path.withoutDirectory(noExt) + '.mtl', noExt + '.mtl');
-
- var material:TextureMaterial = null;
- if (texturePath != null)
- material = new TextureMaterial(Cast.bitmapTexture(Assets.getBitmapData(texturePath, true, false)), smoothTexture);
-
- return loadData(model, context, switch (Path.extension(assetPath).toLowerCase())
- {
- case "dae": new DAEParser();
- case "md2": new MD2Parser();
- case "md5": new MD5MeshParser();
- case "awd": new AWDParser();
- default: new OBJParser();
- }, (event:Asset3DEvent) ->
- {
- if (event.asset != null && event.asset.assetType == Asset3DType.MESH)
- {
- var mesh:Mesh = cast event.asset;
- if (material != null)
- mesh.material = material;
- meshes.push(mesh);
- }
- callback(event);
- });
- }
-
- private var __cur3DStageID:Int;
- private var _loaders:Map = [];
-
- private function loadData(data:Dynamic, context:AssetLoaderContext, parser:ParserBase, onAssetCallback:Asset3DEvent->Void):AssetLoaderToken
- {
- var token:AssetLoaderToken;
-
- var lib:Asset3DLibraryBundle = Asset3DLibraryBundle.getInstance('Flx3DView-${__cur3DStageID}');
- token = lib.loadData(data, context, null, parser);
-
- token.addEventListener(Asset3DEvent.ASSET_COMPLETE, (event:Asset3DEvent) ->
- {
- // ! Taken from Loader3D https://github.com/openfl/away3d/blob/master/away3d/loaders/Loader3D.hx#L207-L232
- if (event.type == Asset3DEvent.ASSET_COMPLETE)
- {
- var obj:ObjectContainer3D = switch (event.asset.assetType)
- {
- case Asset3DType.LIGHT: expect(event.asset, LightBase);
- case Asset3DType.CONTAINER: expect(event.asset, ObjectContainer3D);
- case Asset3DType.MESH: expect(event.asset, Mesh);
- case Asset3DType.SKYBOX: expect(event.asset, SkyBox);
- case Asset3DType.TEXTURE_PROJECTOR: expect(event.asset, TextureProjector);
- case Asset3DType.CAMERA: expect(event.asset, Camera3D);
- case Asset3DType.SEGMENT_SET: expect(event.asset, SegmentSet);
- default: null;
- }
- if (obj != null && obj.parent == null)
- view.scene.addChild(obj);
- }
-
- if (onAssetCallback != null)
- onAssetCallback(event);
- });
-
- token.addEventListener(LoaderEvent.RESOURCE_COMPLETE, (_) ->
- {
- trace("Loader Finished...");
- });
-
- _loaders.set(lib, token);
-
- return token;
- }
-
- override function destroy()
- {
- if (meshes != null)
- for (mesh in meshes)
- mesh.dispose();
-
- var bundle = Asset3DLibraryBundle.getInstance('Flx3DView-${__cur3DStageID}');
- bundle.stopAllLoadingSessions();
- @:privateAccess {
- if (bundle._loadingSessions != null)
- {
- for (load in bundle._loadingSessions)
- {
- load.dispose();
- }
- }
- Asset3DLibrary._instances.remove('Flx3DView-${__cur3DStageID}');
- }
-
- FlxG.stage.removeChild(view);
- try
- {
- view.dispose();
- }
- catch (e)
- {
- }
-
- super.destroy();
- }
-
- public function addChild(c)
- view.scene.addChild(c);
- #end
-}
diff --git a/source/flx3d/Flx3DUtil.hx b/source/flx3d/Flx3DUtil.hx
deleted file mode 100644
index 71706e35fb..0000000000
--- a/source/flx3d/Flx3DUtil.hx
+++ /dev/null
@@ -1,63 +0,0 @@
-package flx3d;
-
-#if THREE_D_SUPPORT
-import away3d.core.managers.Stage3DManager;
-import away3d.library.assets.IAsset;
-import flixel.FlxG;
-#end
-
-/**
- * @author lunarcleint
- * @see https://twitter.com/lunarcleint
- *
- * Help from Ne_Eo
- * @see https://twitter.com/Ne_Eo_Twitch
- */
-@:access(away3d.core.managers.Stage3DManager)
-final class Flx3DUtil
-{
- #if THREE_D_SUPPORT
- /**
- * Returns the total amount of 3D stages (not excluding the ones in use)
- * @return Int
- */
- public static inline function getTotal3D():Int
- {
- return FlxG.stage.stage3Ds.length;
- }
-
- /**
- * Returns the total amount of 3D stages that are in use
- * @return Int
- */
- public static inline function getUsed3D():Int
- {
- return Stage3DManager._numStageProxies;
- }
-
- /**
- * Returns if a Stage3D is available
- * @return Bool
- */
- public static inline function is3DAvailable():Bool
- {
- if (Stage3DManager._stageProxies == null)
- return true;
-
- return Stage3DManager._numStageProxies < Stage3DManager._stageProxies.length;
- }
-
- /**
- * Calls dispose() (destroy) on a Away3D asset
- * @param obj
- * @return null
- */
- public static inline function dispose(obj:Null):T
- {
- if (obj != null)
- obj.dispose();
-
- return null;
- }
- #end
-}
diff --git a/source/flx3d/Flx3DView.hx b/source/flx3d/Flx3DView.hx
deleted file mode 100644
index 9004d27af8..0000000000
--- a/source/flx3d/Flx3DView.hx
+++ /dev/null
@@ -1,152 +0,0 @@
-package flx3d;
-
-#if THREE_D_SUPPORT
-import away3d.cameras.Camera3D;
-import away3d.containers.ObjectContainer3D;
-import away3d.entities.Mesh;
-import away3d.entities.SegmentSet;
-import away3d.entities.TextureProjector;
-import away3d.events.Asset3DEvent;
-import away3d.events.LoaderEvent;
-import away3d.library.Asset3DLibrary;
-import away3d.library.Asset3DLibraryBundle;
-import away3d.library.assets.Asset3DType;
-import away3d.lights.LightBase;
-import away3d.loaders.misc.AssetLoaderContext;
-import away3d.loaders.misc.AssetLoaderToken;
-import away3d.loaders.parsers.*;
-import away3d.materials.TextureMaterial;
-import away3d.primitives.SkyBox;
-import away3d.utils.Cast;
-import flx3d.Flx3DUtil;
-import haxe.io.Path;
-import openfl.Assets;
-import away3d.utils.Utils.expect;
-#end
-
-// FlxView3D with helpers for easier updating
-class Flx3DView extends FlxView3D
-{
- #if THREE_D_SUPPORT
- private static var __3DIDS:Int = 0;
-
- var meshes:Array = [];
-
- public function new(x:Float = 0, y:Float = 0, width:Int = -1, height:Int = -1)
- {
- if (!Flx3DUtil.is3DAvailable())
- throw "[Flx3DView] 3D is not available on this platform. Stages in use: "
- + Flx3DUtil.getUsed3D()
- + ", Max stages allowed: "
- + Flx3DUtil.getTotal3D()
- + ".";
- super(x, y, width, height);
- __cur3DStageID = __3DIDS++;
- }
-
- public function addModel(assetPath:String, callback:Asset3DEvent->Void, ?texturePath:String, smoothTexture:Bool = true)
- {
- var model = Assets.getBytes(assetPath);
- if (model == null)
- throw 'Model at ${assetPath} was not found.';
-
- var context = new AssetLoaderContext();
- var noExt = Path.withoutExtension(assetPath);
- trace(noExt);
- context.mapUrlToData(Path.withoutDirectory(noExt) + '.mtl', noExt + '.mtl');
-
- var material:TextureMaterial = null;
- if (texturePath != null)
- material = new TextureMaterial(Cast.bitmapTexture(Assets.getBitmapData(texturePath, true, false)), smoothTexture);
-
- return loadData(model, context, switch (Path.extension(assetPath).toLowerCase())
- {
- case "dae": new DAEParser();
- case "md2": new MD2Parser();
- case "md5": new MD5MeshParser();
- case "awd": new AWDParser();
- default: new OBJParser();
- }, (event:Asset3DEvent) ->
- {
- if (event.asset != null && event.asset.assetType == Asset3DType.MESH)
- {
- var mesh:Mesh = cast event.asset;
- if (material != null)
- mesh.material = material;
- meshes.push(mesh);
- }
- callback(event);
- });
- }
-
- private var __cur3DStageID:Int;
- private var _loaders:Map = [];
-
- private function loadData(data:Dynamic, context:AssetLoaderContext, parser:ParserBase, onAssetCallback:Asset3DEvent->Void):AssetLoaderToken
- {
- var token:AssetLoaderToken;
-
- var lib:Asset3DLibraryBundle;
- lib = Asset3DLibraryBundle.getInstance('Flx3DView-${__cur3DStageID}');
- token = lib.loadData(data, context, null, parser);
-
- token.addEventListener(Asset3DEvent.ASSET_COMPLETE, (event:Asset3DEvent) ->
- {
- // ! Taken from Loader3D https://github.com/openfl/away3d/blob/master/away3d/loaders/Loader3D.hx#L207-L232
- if (event.type == Asset3DEvent.ASSET_COMPLETE)
- {
- var obj:ObjectContainer3D = switch (event.asset.assetType)
- {
- case Asset3DType.LIGHT: expect(event.asset, LightBase);
- case Asset3DType.CONTAINER: expect(event.asset, ObjectContainer3D);
- case Asset3DType.MESH: expect(event.asset, Mesh);
- case Asset3DType.SKYBOX: expect(event.asset, SkyBox);
- case Asset3DType.TEXTURE_PROJECTOR: expect(event.asset, TextureProjector);
- case Asset3DType.CAMERA: expect(event.asset, Camera3D);
- case Asset3DType.SEGMENT_SET: expect(event.asset, SegmentSet);
- default: null;
- }
- if (obj != null && obj.parent == null)
- view.scene.addChild(obj);
- }
-
- if (onAssetCallback != null)
- onAssetCallback(event);
- });
-
- token.addEventListener(LoaderEvent.RESOURCE_COMPLETE, (_) ->
- {
- trace("Loader Finished...");
- });
-
- _loaders.set(lib, token);
-
- return token;
- }
-
- override function destroy()
- {
- if (meshes != null)
- for (mesh in meshes)
- mesh.dispose();
-
- var bundle = Asset3DLibraryBundle.getInstance('Flx3DView-${__cur3DStageID}');
- bundle.stopAllLoadingSessions();
- @:privateAccess {
- if (bundle._loadingSessions != null)
- {
- for (load in bundle._loadingSessions)
- {
- load.dispose();
- }
- }
- Asset3DLibrary._instances.remove('Flx3DView-${__cur3DStageID}');
- }
-
- super.destroy();
- }
-
- public function addChild(c)
- view.scene.addChild(c);
- #end
-}
diff --git a/source/flx3d/FlxView3D.hx b/source/flx3d/FlxView3D.hx
deleted file mode 100644
index b07b78e213..0000000000
--- a/source/flx3d/FlxView3D.hx
+++ /dev/null
@@ -1,178 +0,0 @@
-package flx3d;
-
-#if THREE_D_SUPPORT
-import flx3d._internal.TextureView3D;
-import away3d.containers.View3D;
-import away3d.library.assets.IAsset;
-import flixel.FlxG;
-import openfl.display.BitmapData;
-#end
-import flixel.FlxSprite;
-
-/**
- * @author Ne_Eo
- * @see https://twitter.com/Ne_Eo_Twitch
- *
- * @author lunarcleint
- * @see https://twitter.com/lunarcleint
- */
-class FlxView3D extends FlxSprite
-{
- #if THREE_D_SUPPORT
- @:noCompletion private var bmp:BitmapData = null;
- private var _textureView:TextureView3D;
- private var legacyRender:Bool = false;
-
- // With new rendering, it seems to only work if 2 or more views are being rendered.
- // This workaround creates a second instance if there is only one view.
- // "There is nothing more permanent than a temporary solution"
- // -idk who said this i just wanted to quote it
- private static var workaroundInstance:FlxView3D;
- private static var createdWorkaround:Bool = false;
-
- private inline function createWorkaround()
- {
- if (!createdWorkaround && !legacyRender)
- {
- createdWorkaround = true;
- workaroundInstance = new FlxView3D(0, 0, 1, 1);
- FlxG.state.add(workaroundInstance);
- }
- }
-
- private inline function destroyWorkaround()
- {
- if (workaroundInstance == this)
- {
- workaroundInstance = null;
- createdWorkaround = false;
- }
- }
-
- /**
- * The Away3D View
- */
- public var view:View3D;
-
- /**
- * Set this flag to true to force the View3D to update during the `draw()` call.
- */
- public var dirty3D:Bool = true;
-
- /**
- * Creates a new instance of a View3D from Away3D and renders it as a FlxSprite
- * ! Call Flx3DUtil.is3DAvailable(); to make sure a 3D stage is usable
- * @param x
- * @param y
- * @param width Leave as -1 for screen width
- * @param height Leave as -1 for screen height
- */
- public function new(x:Float = 0, y:Float = 0, width:Int = -1, height:Int = -1)
- {
- legacyRender = false;
-
- super(x, y);
- if (legacyRender)
- {
- view = new View3D();
- }
- else
- {
- _textureView = new TextureView3D();
- _textureView.onUpdateBitmap = function(bitmap)
- {
- bmp = bitmap;
- loadGraphic(bmp);
- }
-
- view = _textureView;
- }
-
- view.visible = false;
-
- this.width = width == -1 ? FlxG.width : width;
- this.height = height == -1 ? FlxG.height : height;
- if (legacyRender)
- {
- bmp = new BitmapData(Std.int(view.width), Std.int(view.height), true, 0x0);
- loadGraphic(bmp);
- }
-
- view.backgroundAlpha = 0;
- FlxG.stage.addChildAt(view, 0);
-
- createWorkaround();
- }
-
- /**
- * Disposes (destroys) the asset and returns null
- * @param obj
- * @return T null
- */
- public static function dispose(obj:Null):T
- {
- return Flx3DUtil.dispose(obj);
- }
-
- /**
- * Disposes of all the Away3D assets associated with the FlxView3D
- */
- override function destroy()
- {
- FlxG.stage.removeChild(view);
- super.destroy();
-
- if (bmp != null)
- {
- bmp.dispose();
- bmp = null;
- }
-
- if (view != null)
- {
- view.dispose();
- view = null;
- }
-
- destroyWorkaround();
- }
-
- @:noCompletion override function draw()
- {
- if (dirty3D)
- {
- if (legacyRender)
- {
- view.visible = false;
- FlxG.stage.addChildAt(view, 0);
-
- var old = FlxG.game.filters;
- FlxG.game.filters = null;
-
- view.renderer.queueSnapshot(bmp);
- view.render();
-
- FlxG.game.filters = old;
- FlxG.stage.removeChild(view);
- }
- else
- {
- view.render();
- }
- }
- super.draw();
- }
-
- @:noCompletion override function set_width(newWidth:Float):Float
- {
- super.set_width(newWidth);
- return view != null ? view.width = width : width;
- }
-
- @:noCompletion override function set_height(newHeight:Float):Float
- {
- super.set_height(newHeight);
- return view != null ? view.height = height : height;
- }
- #end
-}
diff --git a/source/flx3d/_internal/TextureView3D.hx b/source/flx3d/_internal/TextureView3D.hx
deleted file mode 100644
index efce9b9a4f..0000000000
--- a/source/flx3d/_internal/TextureView3D.hx
+++ /dev/null
@@ -1,177 +0,0 @@
-package flx3d._internal;
-
-import flixel.FlxG;
-import haxe.Exception;
-import away3d.containers.View3D;
-import away3d.containers.Scene3D;
-import away3d.cameras.Camera3D;
-import away3d.core.render.RendererBase;
-import away3d.core.managers.Stage3DManager;
-import away3d.core.managers.Stage3DProxy;
-import openfl.display3D.textures.TextureBase;
-import openfl.display3D.textures.RectangleTexture;
-import openfl.display3D.Context3D;
-import openfl.display.BitmapData;
-import openfl.events.Event;
-import openfl.geom.Point;
-
-class TextureView3D extends View3D
-{
- public var bitmap:BitmapData;
-
- private var _framebuffer:TextureBase = null;
-
- public var onUpdateBitmap:(BitmapData) -> Void;
-
- /**
- * Prevents the engine from disposing Flixel's Stage3D/Context3D instance
- */
- public override function dispose() @:privateAccess {
- _stage3DProxy = null;
- super.dispose();
- }
-
- private override function set_height(value:Float):Float
- {
- if (_height == value)
- return value;
- super.set_height(value);
- _createFramebuffer();
- return value;
- }
-
- private override function set_width(value:Float):Float
- {
- if (_width == value)
- return value;
- super.set_width(value);
- _createFramebuffer();
- return value;
- }
-
- public function new(scene:Scene3D = null, camera:Camera3D = null, renderer:RendererBase = null, forceSoftware:Bool = false, profile:String = "baseline",
- contextIndex:Int = -1)
- {
- super(scene, camera, renderer, forceSoftware, profile, contextIndex);
-
- _stage3DProxy = Stage3DManager.getInstance(FlxG.stage).getStage3DProxy(0);
- _createFramebuffer();
- }
-
- private function _createFramebuffer()
- {
- if (width == 0 || height == 0)
- return;
- if (_framebuffer != null)
- _framebuffer.dispose();
- _framebuffer = FlxG.stage.context3D.createRectangleTexture(Std.int(_width), Std.int(_height), BGRA, true);
- bitmap = BitmapDataCrashFix.fromTextureCrashFix(_framebuffer);
- onUpdateBitmap(bitmap);
- }
-
- /**
- * Renders the view.
- */
- public override function render():Void
- {
- Stage3DProxy.drawTriangleCount = 0;
-
- // if context3D has Disposed by the OS,don't render at this frame
- if (stage3DProxy.context3D == null || !stage3DProxy.recoverFromDisposal())
- {
- _backBufferInvalid = true;
- return;
- }
-
- // reset or update render settings
- if (_backBufferInvalid)
- updateBackBuffer();
-
- if (_shareContext && _layeredView)
- stage3DProxy.clearDepthBuffer();
-
- if (!_parentIsStage)
- {
- var globalPos:Point = parent.localToGlobal(_localTLPos);
- if (_globalPos.x != globalPos.x || _globalPos.y != globalPos.y)
- {
- _globalPos = globalPos;
- _globalPosDirty = true;
- }
- }
-
- if (_globalPosDirty)
- updateGlobalPos();
-
- updateTime();
-
- updateViewSizeData();
-
- _entityCollector.clear();
-
- // collect stuff to render
- _scene.traversePartitions(_entityCollector);
-
- // update picking
- _mouse3DManager.updateCollider(this);
- _touch3DManager.updateCollider();
-
- if (_requireDepthRender)
- renderSceneDepthToTexture(_entityCollector);
-
- // todo: perform depth prepass after light update and before final render
- if (_depthPrepass)
- renderDepthPrepass(_entityCollector);
-
- @:privateAccess _renderer.clearOnRender = !_depthPrepass;
-
- if (_filter3DRenderer != null && _stage3DProxy.context3D != null)
- {
- _renderer.render(_entityCollector, _filter3DRenderer.getMainInputTexture(_stage3DProxy), _rttBufferManager.renderToTextureRect);
- _filter3DRenderer.render(_stage3DProxy, camera, _depthRender);
- }
- else
- {
- @:privateAccess _renderer.shareContext = _shareContext;
- if (_shareContext)
- _renderer.render(_entityCollector, _framebuffer, _scissorRect);
- else
- _renderer.render(_entityCollector, _framebuffer);
- }
-
- if (!_shareContext)
- {
- stage3DProxy.present();
-
- // fire collected mouse events
- _mouse3DManager.fireMouseEvents();
- _touch3DManager.fireTouchEvents();
- }
-
- // clean up data for this render
- _entityCollector.cleanUp();
-
- // register that a view has been rendered
- stage3DProxy.bufferClear = false;
- }
-}
-
-class BitmapDataCrashFix extends BitmapData
-{
- public static function fromTextureCrashFix(texture:TextureBase):BitmapDataCrashFix @:privateAccess {
- if (texture == null)
- return null;
-
- var bitmapData = new BitmapDataCrashFix(texture.__width, texture.__height, true, 0);
- bitmapData.readable = false;
- bitmapData.__texture = texture;
- bitmapData.__textureContext = texture.__textureContext;
- bitmapData.image = null;
- return bitmapData;
- }
-
- @:dox(hide) public override function getTexture(context:Context3D):TextureBase
- {
- return __texture;
- }
-}
diff --git a/source/funkin/backend/FunkinSprite.hx b/source/funkin/backend/FunkinSprite.hx
index 2ac888aff9..5a4d54eacf 100644
--- a/source/funkin/backend/FunkinSprite.hx
+++ b/source/funkin/backend/FunkinSprite.hx
@@ -120,7 +120,7 @@ class FunkinSprite extends FlxAnimate implements IBeatReceiver implements IOffse
if (casted != null) {
spr.skew.set(casted.skew.x, casted.skew.y);
- spr.animOffsets = casted.animOffsets.copy();
+ for (i => p in casted.animOffsets) spr.addOffset(i, p.x, p.y);
spr.zoomFactor = casted.zoomFactor;
spr.angleFactor = casted.angleFactor;
}
@@ -147,7 +147,6 @@ class FunkinSprite extends FlxAnimate implements IBeatReceiver implements IOffse
public function loadSprite(path:String, Unique:Bool = false, Key:String = null)
{
- var noExt = Path.withoutExtension(path);
frames = Paths.getFrames(path, true, null, null, animateSettings);
return this;
}
diff --git a/source/funkin/backend/FunkinText.hx b/source/funkin/backend/FunkinText.hx
index 09bd9dca97..76da9f66c6 100644
--- a/source/funkin/backend/FunkinText.hx
+++ b/source/funkin/backend/FunkinText.hx
@@ -3,6 +3,7 @@ package funkin.backend;
import animate.FlxAnimate;
import flixel.FlxCamera;
import flixel.FlxG;
+import flixel.math.FlxMath;
import flixel.math.FlxAngle;
import flixel.math.FlxMatrix;
import flixel.math.FlxPoint;
@@ -70,7 +71,7 @@ class FunkinText extends FlxText
override function drawComplex(camera:FlxCamera):Void
{
_frame.prepareMatrix(_matrix, ANGLE_0, checkFlipX(), checkFlipY());
- _matrix.translate(-origin.x, -origin.y);
+ _matrix.translate(-origin.x - _graphicOffset.x, -origin.y - _graphicOffset.y);
if (frameOffsetAngle != null && frameOffsetAngle != angle)
{
@@ -137,7 +138,7 @@ class FunkinText extends FlxText
override function getScreenBounds(?rect:FlxRect, ?camera:FlxCamera):FlxRect
{
- if (camera == null) camera = FlxG.camera;
+ if (camera == null) camera = getDefaultCamera();
rect = super.getScreenBounds(rect, camera);
if (zoomFactorEnabled && zoomFactor != 1) {
diff --git a/source/funkin/backend/assets/MultiFramesCollection.hx b/source/funkin/backend/assets/MultiFramesCollection.hx
deleted file mode 100644
index 2a03c64cdd..0000000000
--- a/source/funkin/backend/assets/MultiFramesCollection.hx
+++ /dev/null
@@ -1,72 +0,0 @@
-package funkin.backend.assets;
-
-import flixel.FlxG;
-import flixel.graphics.FlxGraphic;
-import flixel.graphics.frames.FlxFrame.FlxFrameAngle;
-import flixel.graphics.frames.FlxFrame.FlxFrameType;
-import flixel.graphics.frames.FlxFramesCollection;
-import flixel.math.FlxMath;
-import flixel.math.FlxPoint;
-import flixel.math.FlxRect;
-import flixel.util.FlxDestroyUtil;
-import flixel.util.FlxStringUtil;
-
-/**
- * Base class for all frame collections.
- */
-class MultiFramesCollection extends FlxFramesCollection
-{
- public var parentedFrames:Array = [];
-
- public function new(parent:FlxGraphic, ?border:FlxPoint)
- {
- super(parent, USER("MULTI"), border);
- }
-
- /**
- * Returns the `FlxAtlasFrame` of the specified `FlxGraphic` object.
- *
- * @param graphic `FlxGraphic` object to find the `FlxAtlasFrames` collection for.
- * @return `FlxAtlasFrames` collection for the specified `FlxGraphic` object
- * Could be `null` if `FlxGraphic` doesn't have it yet.
- */
- public static function findFrame(graphic:FlxGraphic, ?border:FlxPoint):MultiFramesCollection
- {
- if (border == null)
- border = FlxPoint.weak();
-
- var atlasFrames:Array = cast graphic.getFramesCollections(USER("MULTI"));
-
- for (atlas in atlasFrames)
- if (atlas.border.equals(border))
- return atlas;
-
- return null;
- }
-
- public function addFrames(collection:FlxFramesCollection) {
- if (collection == null || collection.frames == null) return;
-
- collection.parent.useCount++;
- parentedFrames.push(collection);
-
- for(f in collection.frames) {
- if (f != null) {
- pushFrame(f);
- f.parent = collection.parent;
- }
- }
- }
-
- public override function destroy():Void
- {
- if(parentedFrames != null) {
- for(collection in parentedFrames) {
- if(collection != null)
- collection.parent.useCount--;
- }
- parentedFrames = null;
- }
- super.destroy();
- }
-}
\ No newline at end of file
diff --git a/source/funkin/backend/assets/Paths.hx b/source/funkin/backend/assets/Paths.hx
index 8b47a62e27..151873e850 100644
--- a/source/funkin/backend/assets/Paths.hx
+++ b/source/funkin/backend/assets/Paths.hx
@@ -94,8 +94,8 @@ class Paths
return '$p$file.${e[0]}';
}
- public static inline function video(key:String, ?ext:String)
- return getPath('videos/$key.${ext != null ? ext : Flags.VIDEO_EXT}');
+ public static inline function video(key:String, ?ext:OneOfTwo>)
+ return getPath('videos/$key', null, ext != null ? ext : Flags.VIDEO_EXTS);
public static inline function ndll(key:String)
return getPath('ndlls/$key.ndll');
@@ -127,38 +127,37 @@ class Paths
public static inline function ps1(key:String, ?library:String)
return getPath('data/$key.ps1', library);
- static public function sound(key:String, ?library:String, ?ext:String)
- return getPath('sounds/$key.${ext != null ? ext : Flags.SOUND_EXT}', library);
+ static public function sound(key:String, ?library:String, ?ext:OneOfTwo>)
+ return getPath('sounds/$key', library, ext != null ? ext : Flags.SOUND_EXTS);
public static inline function soundRandom(key:String, min:Int, max:Int, ?library:String)
return sound(key + FlxG.random.int(min, max), library);
- inline static public function music(key:String, ?library:String, ?ext:String)
- return getPath('music/$key.${ext != null ? ext : Flags.SOUND_EXT}', library);
+ inline static public function music(key:String, ?library:String, ?ext:OneOfTwo>)
+ return getPath('music/$key', library, ext != null ? ext : Flags.SOUND_EXTS);
- inline static public function voices(song:String, ?difficulty:String, ?suffix:String = "", ?ext:String) {
+ inline static public function voices(song:String, ?difficulty:String, ?suffix:String = "", ?ext:OneOfTwo>) {
if (difficulty == null) difficulty = Flags.DEFAULT_DIFFICULTY;
- if (ext == null) ext = Flags.SOUND_EXT;
- var diff = getPath('songs/$song/song/Voices$suffix-${difficulty}.${ext}', null);
- return Assets.exists(diff) ? diff : getPath('songs/$song/song/Voices$suffix.${ext}', null);
+ var diff = getPath('songs/$song/song/Voices$suffix-${difficulty}', null, ext != null ? ext : Flags.SOUND_EXTS);
+ return Assets.exists(diff) ? diff : getPath('songs/$song/song/Voices$suffix', null, ext != null ? ext : Flags.SOUND_EXTS);
}
- inline static public function inst(song:String, ?difficulty:String, ?suffix:String = "", ?ext:String) {
+ inline static public function inst(song:String, ?difficulty:String, ?suffix:String = "", ?ext:OneOfTwo>) {
if (difficulty == null) difficulty = Flags.DEFAULT_DIFFICULTY;
- if (ext == null) ext = Flags.SOUND_EXT;
- var diff = getPath('songs/$song/song/Inst$suffix-${difficulty}.${ext}', null);
- return Assets.exists(diff) ? diff : getPath('songs/$song/song/Inst$suffix.${ext}', null);
+ var diff = getPath('songs/$song/song/Inst$suffix-${difficulty}', null, ext != null ? ext : Flags.SOUND_EXTS);
+ return Assets.exists(diff) ? diff : getPath('songs/$song/song/Inst$suffix', null, ext != null ? ext : Flags.SOUND_EXTS);
}
- static public function image(key:String, ?library:String, checkForAtlas:Bool = false, ?ext:String) {
- if (ext == null) ext = Flags.IMAGE_EXT;
+ static public function image(key:String, ?library:String, checkForAtlas:Bool = true, ?ext:OneOfTwo>) {
+ final defaultPath = getPath('images/$key', library, ext != null ? ext : Flags.IMAGE_EXTS);
if (checkForAtlas) {
- var atlasPath = getPath('images/$key/spritemap.$ext', library);
- var multiplePath = getPath('images/$key/1.$ext', library);
- if (atlasPath != null && Assets.exists(atlasPath)) return atlasPath.substr(0, atlasPath.length - 14);
- if (multiplePath != null && Assets.exists(multiplePath)) return multiplePath.substr(0, multiplePath.length - 6);
+ final ogExt = Path.extension(defaultPath);
+ var atlasPath = getPath('images/$key/spritemap1', library, ext != null ? ext : Flags.IMAGE_EXTS);
+ var multiplePath = getPath('images/$key/1', library, ext != null ? ext : Flags.IMAGE_EXTS);
+ if (atlasPath != null && Assets.exists(atlasPath)) return atlasPath.substr(0, atlasPath.length - 15) + '.$ogExt';
+ if (multiplePath != null && Assets.exists(multiplePath)) return multiplePath.substr(0, multiplePath.length - 6) + '.$ogExt';
}
- return getPath('images/$key.$ext', library);
+ return defaultPath;
}
public static inline function script(key:String, ?library:String, isAssetsPath:Bool = false) {
@@ -218,26 +217,26 @@ class Paths
return getPath('models/$key.awd');
}
- inline static public function getSparrowAtlas(key:String, ?library:String, ?ext:String)
+ inline static public function getSparrowAtlas(key:String, ?library:String, ?ext:OneOfTwo>)
return FlxAtlasFrames.fromSparrow(image(key, library, ext), file('images/$key.xml', library));
inline static public function getAnimateAtlasAlt(key:String, ?settings:FlxAnimateSettings)
return FlxAnimateFrames.fromAnimate(key, null, null, null, false, settings);
- inline static public function getSparrowAtlasAlt(key:String, ?ext:String)
- return FlxAtlasFrames.fromSparrow('$key.${ext != null ? ext : Flags.IMAGE_EXT}', '$key.xml');
+ inline static public function getSparrowAtlasAlt(key:String, ?ext:OneOfTwo>)
+ return FlxAtlasFrames.fromSparrow('$key.${ext != null ? ext : Flags.IMAGE_EXTS}', '$key.xml');
- inline static public function getPackerAtlas(key:String, ?library:String, ?ext:String)
+ inline static public function getPackerAtlas(key:String, ?library:String, ?ext:OneOfTwo>)
return FlxAtlasFrames.fromSpriteSheetPacker(image(key, library, ext), file('images/$key.txt', library));
- inline static public function getPackerAtlasAlt(key:String, ?ext:String)
- return FlxAtlasFrames.fromSpriteSheetPacker('$key.${ext != null ? ext : Flags.IMAGE_EXT}', '$key.txt');
+ inline static public function getPackerAtlasAlt(key:String, ?ext:OneOfTwo>)
+ return FlxAtlasFrames.fromSpriteSheetPacker('$key.${ext != null ? ext : Flags.IMAGE_EXTS}', '$key.txt');
- inline static public function getAsepriteAtlas(key:String, ?library:String, ?ext:String)
+ inline static public function getAsepriteAtlas(key:String, ?library:String, ?ext:OneOfTwo>)
return FlxAtlasFrames.fromAseprite(image(key, library, ext), file('images/$key.json', library));
- inline static public function getAsepriteAtlasAlt(key:String, ?ext:String)
- return FlxAtlasFrames.fromAseprite('$key.${ext != null ? ext : Flags.IMAGE_EXT}', '$key.json');
+ inline static public function getAsepriteAtlasAlt(key:String, ?ext:OneOfTwo>)
+ return FlxAtlasFrames.fromAseprite('$key.${ext != null ? ext : Flags.IMAGE_EXTS}', '$key.json');
static public function getAssetsRoot():String {
return if (ModsFolder.currentModFolder != null) '${ModsFolder.modsPath}${ModsFolder.currentModFolder}';
@@ -247,17 +246,81 @@ class Paths
/**
* Gets frames at specified path.
* @param key Path to the frames
+ * @param assetsPath (Additional) Whether's the key already a path to an asset.
* @param library (Additional) library to load the frames from.
*/
- public static function getFrames(key:String, assetsPath:Bool = false, ?library:String, ?ext:String = null, ?animateSettings:FlxAnimateSettings) {
+ public static function getFrames(key:String, assetsPath:Bool = false, ?library:String, ?ext:OneOfTwo> = null, ?animateSettings:FlxAnimateSettings) {
if (tempFramesCache.exists(key)) {
var frames = tempFramesCache[key];
- if (frames != null && frames.parent != null && frames.parent.bitmap != null && frames.parent.bitmap.readable)
- return frames;
- else
- tempFramesCache.remove(key);
+ if (frames != null && frames.parent != null && frames.parent.bitmap != null) return frames;
+ else tempFramesCache.remove(key);
}
- return tempFramesCache[key] = loadFrames(assetsPath ? key : Paths.image(key, library, true, ext), false, null, false, ext, animateSettings);
+ return tempFramesCache[key] = loadFrames(assetsPath ? key : Paths.image(key, library, true, ext), false, null, false, animateSettings);
+ }
+
+ /**
+ * Gets frames from multiple specified image with supporting all atlas types.
+ * @param sheets An array of paths to the images
+ * @param assetsPath (Additional) Whether's the key already a path to an asset.
+ * @param unique (Additional) Whenever the images should be unique in the cache.
+ * @param key (Additional) Key for the returned frames in the cache, although the frames are still cached normally.
+ * If, left undefined, the key will the be joint sheets argument that is passed
+ * @param skipMulti (Additional) Whenever the multi spritesheet check should be skipped.
+ * @param ext (Additional) Extension(s) of the images.
+ * @return FlxFramesCollection Frames
+ **/
+ public static function getMultiFrames(sheets:Array, assetsPath:Bool = false, ?unique:Bool = false, ?key:String = null,
+ ?skipMulti:Bool = false, ?ext:OneOfTwo> = null, ?animateSettings:FlxAnimateSettings):FlxFramesCollection
+ {
+ final assetKey = key != null ? key : "combo/" + sheets.join(",");
+
+ var asset:FlxAtlasFrames = cast tempFramesCache.get(assetKey);
+
+ if (asset != null && asset.parent != null && asset.parent.bitmap != null) return asset;
+ else tempFramesCache.remove(assetKey);
+
+ if (!unique) {
+ final graphic = FlxG.bitmap.get(assetKey);
+ if (graphic != null && (asset = graphic.atlasFrames) != null) {
+ tempFramesCache.set(assetKey, asset);
+ return asset;
+ }
+ }
+
+ final frameCollections:Array = [];
+
+ for (key in sheets) {
+ final path = assetsPath ? key : image(key, null, true, ext);
+
+ var frames:FlxFramesCollection;
+ if (tempFramesCache.exists(key)) {
+ if ((frames = tempFramesCache.get(key)) != null && frames.parent != null && frames.parent.bitmap != null) {
+ frameCollections.push(frames);
+ continue;
+ }
+ else
+ tempFramesCache.remove(key);
+ }
+
+ frames = loadFrames(path, unique, null, false, skipMulti, animateSettings);
+ if (frames == null) {
+ Logs.warn('There is no Bitmap asset for "$path". Skipping...');
+ continue;
+ }
+
+ frameCollections.push(frames);
+ }
+
+ if (frameCollections.length == 1 && !unique && (key == null || key == assetKey)) return frameCollections[0];
+
+ //asset = new FlxAnimateFrames(FlxGraphic.fromFrame(FlxG.bitmap.whitePixel, unique, assetKey));
+ //for (frames in frameCollections) asset.addAtlas(cast frames); // wont compile in hashlink because of mismatch type
+
+ // see FlxAnimateFrames#L495
+ asset = FlxAnimateFrames.combineAtlas(cast /* how do you make this good */ frameCollections);
+
+ if (!unique) tempFramesCache.set(assetKey, asset);
+ return asset;
}
/**
@@ -270,52 +333,52 @@ class Paths
**/
public static function framesExists(key:String, checkAtlas:Bool = false, checkMulti:Bool = true, assetsPath:Bool = false, ?library:String) {
var path = assetsPath ? key : Paths.image(key, library, true);
+
var noExt = Path.withoutExtension(path);
- if(checkAtlas && Assets.exists('$noExt/Animation.json'))
+ var ext = Path.extension(path);
+
+ if (checkAtlas && Assets.exists('$noExt/Animation.json'))
return true;
- if(checkMulti && Assets.exists('$noExt/1.png'))
+ if (checkMulti && Assets.exists('$noExt/1.$ext'))
return true;
- if(Assets.exists('$noExt.xml'))
+ if (Assets.exists('$noExt.xml'))
return true;
- if(Assets.exists('$noExt.txt'))
+ if (Assets.exists('$noExt.txt'))
return true;
- if(Assets.exists('$noExt.json'))
+ if (Assets.exists('$noExt.json'))
return true;
return false;
}
-
+
/**
+ * Unintended for future normal usage, use getMultiFrames or loadMultiFrames instead.
+ *
* Loads frames from a specific image path. Supports Sparrow Atlases, Packer Atlases, and multiple spritesheets.
* @param path Path to the image
* @param Unique Whenever the image should be unique in the cache
* @param Key Key to the image in the cache
* @param SkipAtlasCheck Whenever the atlas check should be skipped.
* @param SkipMultiCheck Whenever the multi spritesheet check should be skipped.
- * @param Ext Extension of the image.
* @return FlxFramesCollection Frames
*/
- static function loadFrames(path:String, Unique:Bool = false, Key:String = null, SkipAtlasCheck:Bool = false, SkipMultiCheck:Bool = false, ?Ext:String = null, ?animateSettings:FlxAnimateSettings):FlxFramesCollection {
+ static function loadFrames(path:String, Unique:Bool = false, Key:String = null, SkipAtlasCheck:Bool = false, SkipMultiCheck:Bool = false,
+ Ext:String = null, ?animateSettings:FlxAnimateSettings):FlxFramesCollection
+ {
var noExt = Path.withoutExtension(path);
- var ext = Ext != null ? Ext : Flags.IMAGE_EXT;
-
- if (!SkipMultiCheck && Assets.exists('$noExt/1.${ext}')) {
- // MULTIPLE SPRITESHEETS!!
-
- var graphic = FlxG.bitmap.add("flixel/images/logo/default.png", false, '$noExt/mult');
- var frames = MultiFramesCollection.findFrame(graphic);
- if (frames != null)
- return frames;
-
- trace("no frames yet for multiple atlases!!");
- var cur = 1;
- var finalFrames = new MultiFramesCollection(graphic);
- while(Assets.exists('$noExt/$cur.${ext}')) {
- var spr = loadFrames('$noExt/$cur.${ext}', false, null, false, true);
- finalFrames.addFrames(spr);
+ var ext = Ext != null ? Ext : Path.extension(path);
+
+ if (!SkipMultiCheck && Assets.exists('$noExt/1.$ext')) {
+ var cur:Int = 1;
+ final finalFrames = [];
+ while (Assets.exists('$noExt/$cur.$ext')) {
+ finalFrames.push('$noExt/$cur.$ext');
cur++;
}
- return finalFrames;
- } else if (Assets.exists('$noExt/Animation.json')) {
+ return getMultiFrames(finalFrames, true, true,
+ '$noExt/mult', true, ext, animateSettings);
+ } else if (!SkipAtlasCheck && Assets.exists('$noExt/Animation.json')) {
+ // ???
+ if (noExt.endsWith('/')) noExt = noExt.substr(0, noExt.length - 1);
return Paths.getAnimateAtlasAlt(noExt, animateSettings);
} else if (Assets.exists('$noExt.xml')) {
return Paths.getSparrowAtlasAlt(noExt, ext);
diff --git a/source/funkin/backend/assets/ZipFolderLibrary.hx b/source/funkin/backend/assets/ZipFolderLibrary.hx
index d12afb456d..ad5225e048 100644
--- a/source/funkin/backend/assets/ZipFolderLibrary.hx
+++ b/source/funkin/backend/assets/ZipFolderLibrary.hx
@@ -55,7 +55,7 @@ class ZipFolderLibrary extends AssetLibrary implements IModsAssetLibrary {
}
public function precacheVideos() {
- _videoExtensions = [Flags.VIDEO_EXT];
+ _videoExtensions = Flags.VIDEO_EXTS;
videoCacheRemap = [];
for (entry in zip.entries) {
@@ -70,7 +70,7 @@ class ZipFolderLibrary extends AssetLibrary implements IModsAssetLibrary {
}
// Now we have supports for videos in ZIP!!
- public var _videoExtensions:Array = [Flags.VIDEO_EXT];
+ public var _videoExtensions:Array = Flags.VIDEO_EXTS;
public var videoCacheRemap:Map = [];
public function getVideoRemap(originalPath:String):String {
if (!_videoExtensions.contains(Path.extension(_parsedAsset))) return originalPath;
diff --git a/source/funkin/backend/chart/Chart.hx b/source/funkin/backend/chart/Chart.hx
index d2c01ec3aa..3660540fb3 100644
--- a/source/funkin/backend/chart/Chart.hx
+++ b/source/funkin/backend/chart/Chart.hx
@@ -85,7 +85,7 @@ class Chart {
}
inline public static function defaultChartMetaFields(data:ChartMetaData):ChartMetaData {
- data.setFieldDefault("displayName", data.name);
+ data.setFieldDefault("displayName", ~/(.*[\/])/g.map(data.name, _->''));
data.setFieldDefault("bpm", Flags.DEFAULT_BPM);
data.setFieldDefault("beatsPerMeasure", Flags.DEFAULT_BEATS_PER_MEASURE);
diff --git a/source/funkin/backend/scripting/Script.hx b/source/funkin/backend/scripting/Script.hx
index a0dde86115..1df5063161 100644
--- a/source/funkin/backend/scripting/Script.hx
+++ b/source/funkin/backend/scripting/Script.hx
@@ -10,7 +10,8 @@ import lime.app.Application;
* Class used for scripting.
* Use `Script.create` to create a script.
*/
-class Script extends FlxBasic implements IFlxDestroyable {
+class Script extends FlxBasic implements IFlxDestroyable
+{
/**
* Use "static var thing = true;" in hscript to use those!!
* are reset every mod switch so once you're done with them make sure to make them null!!
@@ -20,7 +21,8 @@ class Script extends FlxBasic implements IFlxDestroyable {
/**
* Gets the default variables for a script.
*/
- public static function getDefaultVariables(?script:Script):Map {
+ public static function getDefaultVariables(?script:Script):Map
+ {
var vars = _defaultVariablesTemplate != null ? _defaultVariablesTemplate : (_defaultVariablesTemplate = buildDefaultVariables());
var copy = vars.copy();
copy.set("state", flixel.FlxG.state); // `state` changes on state switch, so it can't be cached
@@ -34,94 +36,117 @@ class Script extends FlxBasic implements IFlxDestroyable {
*/
private static var _defaultVariablesTemplate:Map = null;
- private static function buildDefaultVariables():Map {
+ private static function buildDefaultVariables():Map
+ {
return [
// Haxe related stuff
- "Std" => Std,
- "Math" => Math,
- "Reflect" => Reflect,
- "StringTools" => StringTools,
- "Json" => haxe.Json,
- "Xml" => Xml,
- "Type" => Type,
- "Date" => Date,
- "Lambda" => Lambda,
- #if sys "Sys" => Sys, #end
+ "Std" => Std,
+ "Math" => Math,
+ "Reflect" => Reflect,
+ "StringTools" => StringTools,
+ "Json" => haxe.Json,
+ "Xml" => Xml,
+ "Type" => Type,
+ "Date" => Date,
+ "Lambda" => Lambda,
+ #if sys "Sys" => Sys, #end
// OpenFL & Lime related stuff
- "BlendMode" => CoolUtil.getMacroAbstractClass("openfl.display.BlendMode"),
- "Assets" => openfl.utils.Assets,
- "Application" => lime.app.Application,
- "Main" => funkin.backend.system.Main,
+ "BlendMode" => CoolUtil.getMacroAbstractClass("openfl.display.BlendMode"),
+ "Assets" => openfl.utils.Assets,
+ "Application" => lime.app.Application,
+ "Main" => funkin.backend.system.Main,
// Flixel related stuff
- "FlxG" => flixel.FlxG,
- "FlxSprite" => flixel.FlxSprite,
- "FlxBasic" => flixel.FlxBasic,
- "FlxCamera" => flixel.FlxCamera,
- "FlxEase" => flixel.tweens.FlxEase,
- "FlxTween" => flixel.tweens.FlxTween,
- "FlxSound" => flixel.sound.FlxSound,
- "FlxAssets" => flixel.system.FlxAssets,
- "FlxMath" => flixel.math.FlxMath,
- "FlxGroup" => flixel.group.FlxGroup,
- "FlxTypedGroup" => flixel.group.FlxGroup.FlxTypedGroup,
- "FlxSpriteGroup" => flixel.group.FlxSpriteGroup,
- "FlxTypeText" => flixel.addons.text.FlxTypeText,
- "FlxText" => flixel.text.FlxText,
- "FlxTimer" => flixel.util.FlxTimer,
- "FlxPoint" => CoolUtil.getMacroAbstractClass("flixel.math.FlxPoint"),
- "FlxAxes" => CoolUtil.getMacroAbstractClass("flixel.util.FlxAxes"),
- "FlxColor" => CoolUtil.getMacroAbstractClass("flixel.util.FlxColor"),
+ "FlxG" => flixel.FlxG,
+ "FlxSprite" => flixel.FlxSprite,
+ "FlxBasic" => flixel.FlxBasic,
+ "FlxCamera" => flixel.FlxCamera,
+ "FlxEase" => flixel.tweens.FlxEase,
+ "FlxTween" => flixel.tweens.FlxTween,
+ "FlxSound" => flixel.sound.FlxSound,
+ "FlxAssets" => flixel.system.FlxAssets,
+ "FlxMath" => flixel.math.FlxMath,
+ "FlxGroup" => flixel.group.FlxGroup,
+ "FlxTypedGroup" => flixel.group.FlxGroup.FlxTypedGroup,
+ "FlxSpriteGroup" => flixel.group.FlxSpriteGroup,
+ "FlxTypeText" => flixel.addons.text.FlxTypeText,
+ "FlxText" => flixel.text.FlxText,
+ "FlxTimer" => flixel.util.FlxTimer,
+ "FlxPoint" => CoolUtil.getMacroAbstractClass("flixel.math.FlxPoint"),
+ "FlxAxes" => CoolUtil.getMacroAbstractClass("flixel.util.FlxAxes"),
+ "FlxColor" => CoolUtil.getMacroAbstractClass("flixel.util.FlxColor"),
+
+ #if (THREE_D_SUPPORT && foxlite)
+ // Foxlite stuff
+ "FoxScene" => foxlite.FoxScene, "FoxCamera" => foxlite.FoxCamera, "FoxFPSCamera" => foxlite.extras.FoxFPSCamera, "FoxRenderer" =>
+ foxlite.renderer.FoxRenderer, "FoxLoaderUtil" => foxlite.loaders.FoxLoaderUtil, "FoxModel" => foxlite.FoxModel, "FoxQuadMesh" =>
+ foxlite.mesh.FoxQuadMesh, "FoxCubeMesh" => foxlite.mesh.FoxCubeMesh, "FoxMaterial" => foxlite.material.FoxMaterial, "FoxShader" =>
+ foxlite.FoxShader, "FoxTexture" => foxlite.texture.FoxTexture, "FoxCache" => foxlite.FoxCache, "FoxRenderMetrics" =>
+ foxlite.flixel.FoxRenderMetrics, "FoxFunkinSprite" => foxlite.funkin.FoxFunkinSprite, "FoxFlxSprite" => foxlite.flixel.FoxFlxSprite,
+ "FoxPanoramaSky" => foxlite.sky.FoxPanoramaSky, "FoxStencilAction" => foxlite.stencil.FoxStencilAction, "FoxOBJLoader" =>
+ foxlite.loaders.FoxOBJLoader, "FoxMTLLoader" => foxlite.loaders.FoxMTLLoader, "FoxDirectionalLight" => foxlite.lights.FoxDirectionalLight,
+ "FoxLayer" => CoolUtil.getMacroAbstractClass("foxlite.FoxLayer"), "FoxEaseType" => CoolUtil.getMacroAbstractClass("foxlite.animation.FoxEaseType"),
+ "FoxInstanceUpdateMode" => CoolUtil.getMacroAbstractClass("foxlite.instancing.FoxInstanceUpdateMode"), "FoxAreaLightShape" =>
+ CoolUtil.getMacroAbstractClass("foxlite.lights.FoxAreaLightShape"), "FoxLightType" =>
+ CoolUtil.getMacroAbstractClass("foxlite.lights.FoxLightType"), "FoxBlendMode" => CoolUtil.getMacroAbstractClass("foxlite.material.FoxBlendMode"),
+ "FoxDepthCompareMode" => CoolUtil.getMacroAbstractClass("foxlite.material.FoxDepthCompareMode"), "FoxStencilCompareMode" =>
+ CoolUtil.getMacroAbstractClass("foxlite.stencil.FoxStencilCompareMode"), "FoxTriangleFace" =>
+ CoolUtil.getMacroAbstractClass("foxlite.material.FoxTriangleFace"), "FoxMeshBufferType" =>
+ CoolUtil.getMacroAbstractClass("foxlite.mesh.FoxMeshBufferType"), "FoxQuadFace" => CoolUtil.getMacroAbstractClass("foxlite.mesh.FoxQuadFace"),
+ "FoxStencilActionType" => CoolUtil.getMacroAbstractClass("foxlite.stencil.FoxStencilActionType"), "FoxCubemapSide" =>
+ CoolUtil.getMacroAbstractClass("foxlite.texture.FoxCubemapSide"), "FoxMipFilter" => CoolUtil.getMacroAbstractClass("foxlite.texture.FoxMipFilter"),
+ "FoxTextureFilter" => CoolUtil.getMacroAbstractClass("foxlite.texture.FoxTextureFilter"), "FoxWrapMode" =>
+ CoolUtil.getMacroAbstractClass("foxlite.texture.FoxWrapMode"),
+ #end
// Engine related stuff
- "engine" => {
+ "engine" => {
commit: Flags.COMMIT_NUMBER,
hash: Flags.COMMIT_HASH,
build: 2675, // 2675 being the last build num before it was removed
name: "Codename Engine"
},
- "ModState" => funkin.backend.scripting.ModState,
- "ModSubState" => funkin.backend.scripting.ModSubState,
- "PlayState" => funkin.game.PlayState,
- "GameOverSubstate" => funkin.game.GameOverSubstate,
- "HealthIcon" => funkin.game.HealthIcon,
- "HudCamera" => funkin.game.HudCamera,
- "Note" => funkin.game.Note,
- "Strum" => funkin.game.Strum,
- "StrumLine" => funkin.game.StrumLine,
- "Character" => funkin.game.Character,
- "Boyfriend" => funkin.game.Character, // for compatibility
- "PauseSubstate" => funkin.menus.PauseSubState,
- "FreeplayState" => funkin.menus.FreeplayState,
- "MainMenuState" => funkin.menus.MainMenuState,
- "PauseSubState" => funkin.menus.PauseSubState,
- "StoryMenuState" => funkin.menus.StoryMenuState,
- "TitleState" => funkin.menus.TitleState,
- "Options" => funkin.options.Options,
- "Paths" => funkin.backend.assets.Paths,
- "Conductor" => funkin.backend.system.Conductor,
- "FunkinShader" => funkin.backend.shaders.FunkinShader,
- "CustomShader" => funkin.backend.shaders.CustomShader,
- "FunkinText" => funkin.backend.FunkinText,
- "FlxAnimate" => animate.FlxAnimate,
- "FunkinSprite" => funkin.backend.FunkinSprite,
- "Alphabet" => funkin.menus.ui.Alphabet,
- "Flags" => funkin.backend.system.Flags,
-
- "CoolUtil" => funkin.backend.utils.CoolUtil,
- "IniUtil" => funkin.backend.utils.IniUtil,
- "XMLUtil" => funkin.backend.utils.XMLUtil,
- #if sys "ZipUtil" => funkin.backend.utils.ZipUtil, #end
- "MarkdownUtil" => funkin.backend.utils.MarkdownUtil,
- "EngineUtil" => funkin.backend.utils.EngineUtil,
- "ThreadUtil" => funkin.backend.utils.ThreadUtil,
- "MemoryUtil" => funkin.backend.utils.MemoryUtil,
- "BitmapUtil" => funkin.backend.utils.BitmapUtil,
+ "ModState" => funkin.backend.scripting.ModState,
+ "ModSubState" => funkin.backend.scripting.ModSubState,
+ "PlayState" => funkin.game.PlayState,
+ "GameOverSubstate" => funkin.game.GameOverSubstate,
+ "HealthIcon" => funkin.game.HealthIcon,
+ "HudCamera" => funkin.game.HudCamera,
+ "Note" => funkin.game.Note,
+ "Strum" => funkin.game.Strum,
+ "StrumLine" => funkin.game.StrumLine,
+ "Character" => funkin.game.Character,
+ "Boyfriend" => funkin.game.Character, // for compatibility
+ "PauseSubstate" => funkin.menus.PauseSubState,
+ "FreeplayState" => funkin.menus.FreeplayState,
+ "MainMenuState" => funkin.menus.MainMenuState,
+ "PauseSubState" => funkin.menus.PauseSubState,
+ "StoryMenuState" => funkin.menus.StoryMenuState,
+ "TitleState" => funkin.menus.TitleState,
+ "Options" => funkin.options.Options,
+ "Paths" => funkin.backend.assets.Paths,
+ "Conductor" => funkin.backend.system.Conductor,
+ "FunkinShader" => funkin.backend.shaders.FunkinShader,
+ "CustomShader" => funkin.backend.shaders.CustomShader, // deprecated
+ "FunkinText" => funkin.backend.FunkinText,
+ "FlxAnimate" => animate.FlxAnimate,
+ "FunkinSprite" => funkin.backend.FunkinSprite,
+ "Alphabet" => funkin.menus.ui.Alphabet,
+ "Flags" => funkin.backend.system.Flags,
+
+ "CoolUtil" => funkin.backend.utils.CoolUtil,
+ "IniUtil" => funkin.backend.utils.IniUtil,
+ "XMLUtil" => funkin.backend.utils.XMLUtil,
+ #if sys "ZipUtil" => funkin.backend.utils.ZipUtil, #end
+ "MarkdownUtil" => funkin.backend.utils.MarkdownUtil,
+ "EngineUtil" => funkin.backend.utils.EngineUtil,
+ "ThreadUtil" => funkin.backend.utils.ThreadUtil,
+ "MemoryUtil" => funkin.backend.utils.MemoryUtil,
+ "BitmapUtil" => funkin.backend.utils.BitmapUtil,
#if TRANSLATIONS_SUPPORT
- "TranslationUtil" => funkin.backend.utils.TranslationUtil,
- "translate" => funkin.backend.utils.TranslationUtil.get,
+ "TranslationUtil" => funkin.backend.utils.TranslationUtil, "translate" => funkin.backend.utils.TranslationUtil.get,
#end
];
}
@@ -131,43 +156,44 @@ class Script extends FlxBasic implements IFlxDestroyable {
* This gets set on `hscript.Interp.importRedirects`,
* if you wanna modify it, please edit `hscript.Interp.importRedirects` directly.
**/
- public static function getDefaultImportRedirects():Map {
+ public static function getDefaultImportRedirects():Map
+ {
var redirects:Map = [];
// Events
final events = "funkin.backend.scripting.events.";
- redirects[events + "CharacterNodeEvent"] = events + "character.CharacterNodeEvent";
- redirects[events + "CharacterXMLEvent"] = events + "character.CharacterXMLEvent";
- redirects[events + "DanceEvent"] = events + "character.DanceEvent";
- redirects[events + "DirectionAnimEvent"] = events + "character.DirectionAnimEvent";
- redirects[events + "DiscordPresenceUpdateEvent"] = events + "discord.DiscordPresenceUpdateEvent";
- redirects[events + "GameOverCreationEvent"] = events + "gameover.GameOverCreationEvent";
- redirects[events + "CamMoveEvent"] = events + "gameplay.CamMoveEvent";
- redirects[events + "CountdownEvent"] = events + "gameplay.CountdownEvent";
- redirects[events + "EventGameEvent"] = events + "gameplay.EventGameEvent";
- redirects[events + "GameOverEvent"] = events + "gameplay.GameOverEvent";
- redirects[events + "RatingUpdateEvent"] = events + "gameplay.RatingUpdateEvent";
- redirects[events + "HealthIconChangeEvent"] = events + "healthicon.HealthIconChangeEvent";
- redirects[events + "FreeplayAlphaUpdateEvent"] = events + "menu.freeplay.FreeplayAlphaUpdateEvent";
- redirects[events + "FreeplaySongSelectEvent"] = events + "menu.freeplay.FreeplaySongSelectEvent";
- redirects[events + "MenuChangeEvent"] = events + "menu.MenuChangeEvent";
- redirects[events + "PauseCreationEvent"] = events + "menu.pause.PauseCreationEvent";
- redirects[events + "WeekSelectEvent"] = events + "menu.storymenu.WeekSelectEvent";
- redirects[events + "InputSystemEvent"] = events + "note.InputSystemEvent";
- redirects[events + "NoteCreationEvent"] = events + "note.NoteCreationEvent";
- redirects[events + "NoteHitEvent"] = events + "note.NoteHitEvent";
- redirects[events + "NoteMissEvent"] = events + "note.NoteMissEvent";
- redirects[events + "NoteUpdateEvent"] = events + "note.NoteUpdateEvent";
- redirects[events + "SimpleNoteEvent"] = events + "note.SimpleNoteEvent";
- redirects[events + "StrumCreationEvent"] = events + "note.StrumCreationEvent";
- redirects[events + "SplashShowEvent"] = events + "splash.SplashShowEvent";
- redirects[events + "PlayAnimContext"] = events + "sprite.PlayAnimContext";
- redirects[events + "PlayAnimEvent"] = events + "sprite.PlayAnimEvent";
- redirects[events + "StageNodeEvent"] = events + "stage.StageNodeEvent";
- redirects[events + "StageXMLEvent"] = events + "stage.StageXMLEvent";
+ redirects[events + "CharacterNodeEvent"] = events + "character.CharacterNodeEvent";
+ redirects[events + "CharacterXMLEvent"] = events + "character.CharacterXMLEvent";
+ redirects[events + "DanceEvent"] = events + "character.DanceEvent";
+ redirects[events + "DirectionAnimEvent"] = events + "character.DirectionAnimEvent";
+ redirects[events + "DiscordPresenceUpdateEvent"] = events + "discord.DiscordPresenceUpdateEvent";
+ redirects[events + "GameOverCreationEvent"] = events + "gameover.GameOverCreationEvent";
+ redirects[events + "CamMoveEvent"] = events + "gameplay.CamMoveEvent";
+ redirects[events + "CountdownEvent"] = events + "gameplay.CountdownEvent";
+ redirects[events + "EventGameEvent"] = events + "gameplay.EventGameEvent";
+ redirects[events + "GameOverEvent"] = events + "gameplay.GameOverEvent";
+ redirects[events + "RatingUpdateEvent"] = events + "gameplay.RatingUpdateEvent";
+ redirects[events + "HealthIconChangeEvent"] = events + "healthicon.HealthIconChangeEvent";
+ redirects[events + "FreeplayAlphaUpdateEvent"] = events + "menu.freeplay.FreeplayAlphaUpdateEvent";
+ redirects[events + "FreeplaySongSelectEvent"] = events + "menu.freeplay.FreeplaySongSelectEvent";
+ redirects[events + "MenuChangeEvent"] = events + "menu.MenuChangeEvent";
+ redirects[events + "PauseCreationEvent"] = events + "menu.pause.PauseCreationEvent";
+ redirects[events + "WeekSelectEvent"] = events + "menu.storymenu.WeekSelectEvent";
+ redirects[events + "InputSystemEvent"] = events + "note.InputSystemEvent";
+ redirects[events + "NoteCreationEvent"] = events + "note.NoteCreationEvent";
+ redirects[events + "NoteHitEvent"] = events + "note.NoteHitEvent";
+ redirects[events + "NoteMissEvent"] = events + "note.NoteMissEvent";
+ redirects[events + "NoteUpdateEvent"] = events + "note.NoteUpdateEvent";
+ redirects[events + "SimpleNoteEvent"] = events + "note.SimpleNoteEvent";
+ redirects[events + "StrumCreationEvent"] = events + "note.StrumCreationEvent";
+ redirects[events + "SplashShowEvent"] = events + "splash.SplashShowEvent";
+ redirects[events + "PlayAnimContext"] = events + "sprite.PlayAnimContext";
+ redirects[events + "PlayAnimEvent"] = events + "sprite.PlayAnimEvent";
+ redirects[events + "StageNodeEvent"] = events + "stage.StageNodeEvent";
+ redirects[events + "StageXMLEvent"] = events + "stage.StageXMLEvent";
// Old State Names
- redirects["funkin.menus.BetaWarningState"] = "funkin.menus.WarningState";
+ redirects["funkin.menus.BetaWarningState"] = "funkin.menus.WarningState";
return redirects;
}
@@ -176,7 +202,8 @@ class Script extends FlxBasic implements IFlxDestroyable {
* Gets the default defines for a script.
* Includes all of the defines that the build was compiled with.
*/
- public static function getDefaultPreprocessors():Map {
+ public static function getDefaultPreprocessors():Map
+ {
var defines = funkin.backend.system.macros.DefinesMacro.defines;
defines.set("CODENAME_ENGINE", true);
defines.set("CODENAME_VER", Flags.VERSION);
@@ -184,14 +211,17 @@ class Script extends FlxBasic implements IFlxDestroyable {
defines.set("CODENAME_COMMIT", Flags.COMMIT_NUMBER);
return defines;
}
+
/**
* All available script extensions
*/
public static var scriptExtensions:Array = [
- "hx", "hscript", "hsc", "hxs",
+ "hx",
+ "hscript",
+ "hsc",
+ "hxs",
"pack", // combined file
- "lua" /** ACTUALLY NOT SUPPORTED, ONLY FOR THE MESSAGE **/
- ];
+ "lua" /** ACTUALLY NOT SUPPORTED, ONLY FOR THE MESSAGE **/];
/**
* Currently executing script.
@@ -232,9 +262,12 @@ class Script extends FlxBasic implements IFlxDestroyable {
* Creates a script from the specified asset path. The language is automatically determined.
* @param path Path in assets
*/
- public static function create(path:String):Script {
- if (Assets.exists(path)) {
- return switch(Path.extension(path).toLowerCase()) {
+ public static function create(path:String):Script
+ {
+ if (Assets.exists(path))
+ {
+ return switch (Path.extension(path).toLowerCase())
+ {
case "hx" | "hscript" | "hsc" | "hxs":
new HScript(path);
case "pack":
@@ -255,8 +288,10 @@ class Script extends FlxBasic implements IFlxDestroyable {
* @param code code
* @param path filename
*/
- public static function fromString(code:String, path:String):Script {
- return switch(Path.extension(path).toLowerCase()) {
+ public static function fromString(code:String, path:String):Script
+ {
+ return switch (Path.extension(path).toLowerCase())
+ {
case "hx" | "hscript" | "hsc" | "hxs":
new HScript(path).loadFromString(code);
case "lua":
@@ -271,7 +306,8 @@ class Script extends FlxBasic implements IFlxDestroyable {
* Creates a new instance of the script class.
* @param path
*/
- public function new(path:String) {
+ public function new(path:String)
+ {
super();
rawPath = path;
@@ -281,21 +317,24 @@ class Script extends FlxBasic implements IFlxDestroyable {
extension = Path.extension(path);
this.path = path;
onCreate(path);
- for(k=>e in getDefaultVariables(this)) {
+ for (k => e in getDefaultVariables(this))
+ {
set(k, e);
}
- set("disableScript", () -> {
+ set("disableScript", () ->
+ {
active = false;
});
set("__script__", this);
}
-
/**
* Loads the script
*/
- public function load() {
- if(didLoad) return;
+ public function load()
+ {
+ if (didLoad)
+ return;
var oldScript = curScript;
curScript = this;
@@ -309,38 +348,36 @@ class Script extends FlxBasic implements IFlxDestroyable {
* HSCRIPT ONLY FOR NOW
* Sets the "public" variables map for ScriptPack
*/
- public function setPublicMap(map:Map) {
-
+ public function setPublicMap(map:Map)
+ {
}
/**
* Hot-reloads the script, if possible
*/
- public function reload() {
-
+ public function reload()
+ {
}
/**
* Traces something as this script.
*/
- public function trace(v:Dynamic) {
+ public function trace(v:Dynamic)
+ {
var fileName = this.fileName;
- if(remappedNames.exists(fileName))
+ if (remappedNames.exists(fileName))
fileName = remappedNames.get(fileName);
- Logs.traceColored([
- Logs.logText(fileName + ': ', GREEN),
- Logs.logText(Std.string(v))
- ], TRACE);
+ Logs.traceColored([Logs.logText(fileName + ': ', GREEN), Logs.logText(Std.string(v))], TRACE);
}
-
/**
* Calls the function `func` defined in the script.
* @param func Name of the function
* @param parameters (Optional) Parameters of the function.
* @return Result (if void, then null)
*/
- public function call(func:String, ?parameters:Array):Dynamic {
+ public function call(func:String, ?parameters:Array):Dynamic
+ {
var oldScript = curScript;
curScript = this;
@@ -354,7 +391,8 @@ class Script extends FlxBasic implements IFlxDestroyable {
* Loads the code from a string, doesn't really work after the script has been loaded
* @param code The code.
*/
- public function loadFromString(code:String) {
+ public function loadFromString(code:String)
+ {
return this;
}
@@ -362,42 +400,45 @@ class Script extends FlxBasic implements IFlxDestroyable {
* Sets a script's parent object so that its properties can be accessed easily. Ex: Passing `PlayState.instance` will allow `boyfriend` to be typed instead of `PlayState.instance.boyfriend`.
* @param variable Parent variable.
*/
- public function setParent(variable:Dynamic) {}
+ public function setParent(variable:Dynamic)
+ {
+ }
/**
* Gets the variable `variable` from the script's variables.
* @param variable Name of the variable.
* @return Variable (or null if it doesn't exists)
*/
- public function get(variable:String):Dynamic {return null;}
+ public function get(variable:String):Dynamic
+ {
+ return null;
+ }
/**
* Sets the variable `variable` from the script's variables.
* @param variable Name of the variable.
* @return Variable (or null if it doesn't exists)
*/
- public function set(variable:String, value:Dynamic):Void {}
+ public function set(variable:String, value:Dynamic):Void
+ {
+ }
/**
* Shows an error from this script.
* @param text Text of the error (ex: Null Object Reference).
* @param additionalInfo Additional information you could provide.
*/
- public function error(text:String, ?additionalInfo:Dynamic):Void {
+ public function error(text:String, ?additionalInfo:Dynamic):Void
+ {
var fileName = this.fileName;
- if(remappedNames.exists(fileName))
+ if (remappedNames.exists(fileName))
fileName = remappedNames.get(fileName);
- Logs.traceColored([
- Logs.logText(fileName, RED),
- Logs.logText(text)
- ], ERROR);
+ Logs.traceColored([Logs.logText(fileName, RED), Logs.logText(text)], ERROR);
}
- override public function toString():String {
- return FlxStringUtil.getDebugString(didLoad ? [
- LabelValuePair.weak("path", path),
- LabelValuePair.weak("active", active),
- ] : [
+ override public function toString():String
+ {
+ return FlxStringUtil.getDebugString(didLoad ? [LabelValuePair.weak("path", path), LabelValuePair.weak("active", active),] : [
LabelValuePair.weak("path", path),
LabelValuePair.weak("active", active),
LabelValuePair.weak("loaded", didLoad),
@@ -407,17 +448,23 @@ class Script extends FlxBasic implements IFlxDestroyable {
/**
* PRIVATE HANDLERS - DO NOT TOUCH
*/
- private function onCall(func:String, parameters:Array):Dynamic {
+ private function onCall(func:String, parameters:Array):Dynamic
+ {
return null;
}
+
/**
* Called when the script is created.
* @param path Path to the script
*/
- public function onCreate(path:String) {}
+ public function onCreate(path:String)
+ {
+ }
/**
* Called when the script is loaded.
*/
- public function onLoad() {}
+ public function onLoad()
+ {
+ }
}
diff --git a/source/funkin/backend/shaders/BlendModeEffect.hx b/source/funkin/backend/shaders/BlendModeEffect.hx
deleted file mode 100644
index 2865829285..0000000000
--- a/source/funkin/backend/shaders/BlendModeEffect.hx
+++ /dev/null
@@ -1,36 +0,0 @@
-package funkin.backend.shaders;
-
-import flixel.util.FlxColor;
-import openfl.display.ShaderParameter;
-
-@:dox(hide)
-typedef BlendModeShader =
-{
- var uBlendColor:ShaderParameter;
-}
-
-@:dox(hide)
-class BlendModeEffect
-{
- public var shader(default, null):BlendModeShader;
-
- @:isVar
- public var color(default, set):FlxColor;
-
- public function new(shader:BlendModeShader, color:FlxColor):Void
- {
- shader.uBlendColor.value = [];
- this.shader = shader;
- this.color = color;
- }
-
- function set_color(color:FlxColor):FlxColor
- {
- shader.uBlendColor.value[0] = color.redFloat;
- shader.uBlendColor.value[1] = color.greenFloat;
- shader.uBlendColor.value[2] = color.blueFloat;
- shader.uBlendColor.value[3] = color.alphaFloat;
-
- return this.color = color;
- }
-}
diff --git a/source/funkin/backend/shaders/CustomShader.hx b/source/funkin/backend/shaders/CustomShader.hx
index 62308e69ee..61066575c2 100644
--- a/source/funkin/backend/shaders/CustomShader.hx
+++ b/source/funkin/backend/shaders/CustomShader.hx
@@ -2,39 +2,15 @@ package funkin.backend.shaders;
import openfl.Assets;
-/**
- * Class for custom shaders.
- *
- * To create one, create a `shaders` folder in your assets/mod folder, then add a file named `my-shader.frag` or/and `my-shader.vert`.
- *
- * Non-existent shaders will only load the default one, and throw a warning in the console.
- *
- * To access the shader's uniform variables, use `shader.variable`
- */
+@:deprecated("Use funkin.backend.shaders.FunkinShader.fromFile instead.")
class CustomShader extends FunkinShader {
- public var path:String = "";
+ @:isVar
+ public var path(get, set):String;
+ inline function get_path():String return path != null ? path : _fragmentFilePath + _vertexFilePath;
+ inline function set_path(v:Null):String return path = cast v;
- /**
- * Creates a new custom shader
- * @param name Name of the frag and vert files.
- * @param glslVersion GLSL version to use. Defaults to `120`.
- */
- public function new(name:String, glslVersion:String = null) {
- if (glslVersion == null) glslVersion = Flags.DEFAULT_GLSL_VERSION;
- var fragShaderPath = Paths.fragShader(name);
- var vertShaderPath = Paths.vertShader(name);
- var fragCode = Assets.exists(fragShaderPath) ? Assets.getText(fragShaderPath) : null;
- var vertCode = Assets.exists(vertShaderPath) ? Assets.getText(vertShaderPath) : null;
-
- fileName = name;
- fragFileName = fragShaderPath;
- vertFileName = vertShaderPath;
-
- path = fragShaderPath+vertShaderPath;
-
- if (fragCode == null && vertCode == null)
- Logs.error('Shader "$name" couldn\'t be found.');
-
- super(fragCode, vertCode, glslVersion);
+ public function new(name:String, ?glslVersion:String) {
+ super();
+ loadShaderFile(Paths.fragShader(name), Paths.vertShader(name), glslVersion);
}
}
\ No newline at end of file
diff --git a/source/funkin/backend/shaders/FunkinShader.hx b/source/funkin/backend/shaders/FunkinShader.hx
index 281616b03b..58fa9a4b8f 100644
--- a/source/funkin/backend/shaders/FunkinShader.hx
+++ b/source/funkin/backend/shaders/FunkinShader.hx
@@ -1,533 +1,65 @@
package funkin.backend.shaders;
-import flixel.graphics.FlxGraphic;
-import flixel.system.FlxAssets.FlxShader;
-import flixel.util.FlxSignal.FlxTypedSignal;
+import haxe.io.Path;
import haxe.Exception;
+
import hscript.IHScriptCustomBehaviour;
-import lime.utils.Float32Array;
+
import openfl.display.BitmapData;
-import openfl.display.ShaderInput;
+import openfl.display.Shader;
import openfl.display.ShaderParameter;
import openfl.display.ShaderParameterType;
+import openfl.display.ShaderPrecision;
+import openfl.display.ShaderInput;
import openfl.display3D._internal.GLProgram;
import openfl.display3D._internal.GLShader;
+import openfl.display3D.Program3D;
import openfl.utils._internal.Log;
+import openfl.utils.GLSLSourceAssembler;
+
+import flixel.addons.display.FlxRuntimeShader;
+import flixel.graphics.FlxGraphic;
+import flixel.util.FlxSignal.FlxTypedSignal;
+import flixel.util.FlxStringUtil;
-using StringTools;
@:access(openfl.display3D.Context3D)
@:access(openfl.display3D.Program3D)
@:access(openfl.display.ShaderInput)
@:access(openfl.display.ShaderParameter)
-class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
- private static var __instanceFields = Type.getInstanceFields(FunkinShader);
- private static var FRAGMENT_SHADER = 0;
- private static var VERTEX_SHADER = 1;
-
+class FunkinShader extends FlxRuntimeShader implements IHScriptCustomBehaviour {
public var onGLUpdate:FlxTypedSignalVoid> = new FlxTypedSignalVoid>();
- public var onProcessGLData:FlxTypedSignal<(String, String)->Void> = new FlxTypedSignal<(String, String)->Void>();
-
- public var glslVer:String = Flags.DEFAULT_GLSL_VERSION;
- public var fileName:String = "FunkinShader";
- public var fragFileName:String = "FunkinShader";
- public var vertFileName:String = "FunkinShader";
-
- public var shaderPrefix:String = "";
- public var fragmentPrefix:String = "";
- public var vertexPrefix:String = "";
-
- /**
- * Creates a new shader from the specified fragment and vertex source.
- * Accepts `#pragma header`.
- * @param frag Fragment source (pass `null` to use default)
- * @param vert Vertex source (pass `null` to use default)
- * @param glslVer Version of GLSL to use (defaults to 120)
- */
- public override function new(frag:String, vert:String, glslVer:String = null) {
- if (glslVer == null) glslVer = Flags.DEFAULT_GLSL_VERSION;
- if (frag == null) frag = ShaderTemplates.defaultFragmentSource;
- if (vert == null) vert = ShaderTemplates.defaultVertexSource;
- this.glFragmentSource = frag;
- this.glVertexSource = vert;
-
- this.glslVer = glslVer;
- super();
- }
-
- static var IMPORT_REGEX = ~/#import\s+<(.*)>/;
-
- private function processImports(value:String, type:Int):String
- {
- while(IMPORT_REGEX.match(value))
- {
- var importPath = IMPORT_REGEX.matched(1);
- var importSource = Assets.getText("assets/shaders/" + importPath);
- if(importSource == null) {
- var fileName = type == FRAGMENT_SHADER ? fragFileName : vertFileName;
- Logs.traceColored([
- Logs.logText('[Shader] ', RED),
- Logs.logText('Failed to import shader ${importPath} in ${fileName}', RED),
- ]);
- } else {
- value = value.replace(IMPORT_REGEX.matched(0), importSource);
- }
- }
- return value;
- }
-
- static var ERROR_POS_REGEX = ~/(\d+):(\d+): (.*)/g;
- static var ERROR_REGEX = ~/ERROR: (\d+):(\d+): (.*)/g;
- static var ERROR_REGEX_2 = ~/(\d+)\((\d+)\) : error ([^:]+): (.*)/g;
- @:noCompletion private override function __createGLShader(source:String, type:Int):GLShader
- {
- var gl = __context.gl;
-
- var shader = gl.createShader(type);
- gl.shaderSource(shader, source);
- gl.compileShader(shader);
- var shaderInfoLog = gl.getShaderInfoLog(shader);
- var hasInfoLog = shaderInfoLog != null && StringTools.trim(shaderInfoLog) != "";
- var compileStatus = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
-
- if (hasInfoLog || compileStatus == 0)
- {
- var isVertexShader = type == gl.VERTEX_SHADER;
- var messageBuf = new StringBuf();
- messageBuf.add((compileStatus == 0) ? "Error" : "Info");
- if(isVertexShader) {
- messageBuf.add(" compiling vertex shader");
- if(vertFileName != null && vertFileName.length > 0) {
- messageBuf.add(" (" + vertFileName + ")");
- }
- } else {
- messageBuf.add(" compiling fragment shader");
- if(fragFileName != null && fragFileName.length > 0) {
- messageBuf.add(" (" + fragFileName + ")");
- }
- }
- messageBuf.add("\n");
- var errorPositions = [];
- var regex = null;
- var tmp = shaderInfoLog;
- if(shaderInfoLog.contains(" : error ")) {
- regex = ERROR_REGEX_2;
-
- while(regex.match(tmp)) {
- errorPositions.push(new ShaderErrorPosition(regex.matched(2), regex.matched(1), regex.matched(4)));
- tmp = regex.matchedRight();
- }
- } else if(shaderInfoLog.contains("ERROR: ")) {
- regex = ERROR_REGEX;
-
- while(regex.match(tmp)) {
- errorPositions.push(new ShaderErrorPosition(regex.matched(2), regex.matched(1), regex.matched(3)));
- tmp = regex.matchedRight();
- }
- } else {
- regex = ERROR_POS_REGEX;
-
- while(regex.match(tmp)) {
- errorPositions.push(new ShaderErrorPosition(regex.matched(2), regex.matched(1), regex.matched(3)));
- tmp = regex.matchedRight();
- }
- }
- var splitSource = source.split("\n");
- for(error in errorPositions) {
- messageBuf.add("ERROR: Line: " + error.line);
- if(error.column > 0) {
- messageBuf.add(", Column: " + error.column);
- }
- messageBuf.add(", " + error.message);
- if(error.line < splitSource.length) {
- messageBuf.add("\nLine: ");
- messageBuf.add(splitSource[error.line-1].trim());
- }
- messageBuf.add("\n\n");
- }
- var hasErrorPosition = errorPositions.length > 0;
- if(hasErrorPosition) {
- messageBuf.add("Raw shader info log:\n");
- }
- messageBuf.add(shaderInfoLog);
- messageBuf.add("\n");
- messageBuf.add(source);
-
- var message = messageBuf.toString();
- if (compileStatus == 0) Log.error(message);
- else if (hasInfoLog) Log.debug(message);
- }
-
- return shader;
- }
-
- @:noCompletion private override function __createGLProgram(vertexSource:String, fragmentSource:String):GLProgram
- {
- var program:GLProgram = null;
- try
- {
- var gl = __context.gl;
-
- var vertexShader = __createGLShader(vertexSource, gl.VERTEX_SHADER);
- var fragmentShader = __createGLShader(fragmentSource, gl.FRAGMENT_SHADER);
-
- program = gl.createProgram();
-
- // Fix support for drivers that don't draw if attribute 0 is disabled
- for (param in __paramFloat)
- {
- if (param.name.indexOf("Position") > -1 && StringTools.startsWith(param.name, "openfl_"))
- {
- gl.bindAttribLocation(program, 0, param.name);
- break;
- }
- }
-
- gl.attachShader(program, vertexShader);
- gl.attachShader(program, fragmentShader);
- gl.linkProgram(program);
-
- if (gl.getProgramParameter(program, gl.LINK_STATUS) == 0)
- {
- var messageBuf = new StringBuf();
- messageBuf.add("Unable to initialize the shader program");
- messageBuf.add("\n");
- messageBuf.add(gl.getProgramInfoLog(program));
- var message = messageBuf.toString();
- Log.error(message);
- }
- }
- catch (error:Dynamic)
- {
- Logs.traceColored([
- Logs.logText('[Shader] ', BLUE),
- Logs.logText('Failed to compile shader ${fileName}: ', RED),
- Logs.logText(Std.string(error))
- ], TRACE);
- }
- return program;
-
- return program;
- }
- var glRawFragmentSource:String;
- var glRawVertexSource:String;
-
- @:noCompletion override private function set_glFragmentSource(value:String):String
- {
- if(value == null)
- value = ShaderTemplates.defaultFragmentSource;
- glRawFragmentSource = value;
- value = processImports(value, FRAGMENT_SHADER);
- value = value.replace("#pragma header", ShaderTemplates.fragHeader).replace("#pragma body", ShaderTemplates.fragBody);
- if (value != __glFragmentSource)
- {
- __glSourceDirty = true;
- }
-
- return __glFragmentSource = value;
- }
-
- @:noCompletion override private function set_glVertexSource(value:String):String
- {
- if(value == null)
- value = ShaderTemplates.defaultVertexSource;
- glRawVertexSource = value;
- value = processImports(value, VERTEX_SHADER);
-
- var useBackCompat:Bool = true;
- for (regex in ShaderTemplates.vertBackCompatVarList) if (!regex.match(value)) {
- useBackCompat = false;
- break;
- }
-
- var header = useBackCompat ? ShaderTemplates.vertHeaderBackCompat : ShaderTemplates.vertHeader;
- var body = useBackCompat ? ShaderTemplates.vertBodyBackCompat : ShaderTemplates.vertBody;
-
- value = value.replace("#pragma header", header).replace("#pragma body", body);
-
- if (value != __glVertexSource)
- {
- __glSourceDirty = true;
- }
-
- return __glVertexSource = value;
- }
-
- override function __updateGL():Void {
- onGLUpdate.dispatch();
- super.__updateGL();
+ public function new(?fragmentSource:String, ?vertexSource:String, ?version:String) {
+ super(fragmentSource, vertexSource, version ?? (fragmentSource != null || vertexSource != null ? Flags.DEFAULT_GLSL_VERSION : null));
}
- @:noCompletion private override function __initGL():Void
- {
- if (__glSourceDirty || __paramBool == null)
- {
- __glSourceDirty = false;
- program = null;
-
- __inputBitmapData = new Array();
- __paramBool = new Array();
- __paramFloat = new Array();
- __paramInt = new Array();
-
- __processGLData(glVertexSource, "attribute");
- __processGLData(glVertexSource, "uniform");
- __processGLData(glFragmentSource, "uniform");
- }
-
- if (__context != null && program == null)
- {
- var prefixBuf = new StringBuf();
- prefixBuf.add('#version ${glslVer}\n');
- prefixBuf.add(shaderPrefix);
-
- var gl = __context.gl;
-
- prefixBuf.add("#ifdef GL_ES\n");
- if (precisionHint == FULL) {
- prefixBuf.add("#ifdef GL_FRAGMENT_PRECISION_HIGH\n");
- prefixBuf.add("precision highp float;\n");
- prefixBuf.add("#else\n");
- prefixBuf.add("precision mediump float;\n");
- prefixBuf.add("#endif\n");
- } else {
- prefixBuf.add("precision lowp float;\n");
- }
- prefixBuf.add("#endif\n");
-
- var prefix = prefixBuf.toString();
-
- var vertex = prefix + vertexPrefix + glVertexSource;
- var fragment = prefix + fragmentPrefix + glFragmentSource;
-
- var id = vertex + fragment;
-
- if (__context.__programs.exists(id))
- {
- program = __context.__programs.get(id);
- }
- else
- {
- program = __context.createProgram(GLSL);
- program.__glProgram = __createGLProgram(vertex, fragment);
- __context.__programs.set(id, program);
- }
-
- if (program != null)
- {
- glProgram = program.__glProgram;
-
- for (input in __inputBitmapData) {
-
- if (input.__isUniform) {
- input.index = gl.getUniformLocation(glProgram, input.name);
- } else {
- input.index = gl.getAttribLocation(glProgram, input.name);
- }
- }
-
- for (parameter in __paramBool) {
- if (parameter.__isUniform) {
- parameter.index = gl.getUniformLocation(glProgram, parameter.name);
- } else {
- parameter.index = gl.getAttribLocation(glProgram, parameter.name);
- }
- }
-
- for (parameter in __paramFloat) {
- if (parameter.__isUniform) {
- parameter.index = gl.getUniformLocation(glProgram, parameter.name);
- } else {
- parameter.index = gl.getAttribLocation(glProgram, parameter.name);
- }
- }
-
- for (parameter in __paramInt) {
- if (parameter.__isUniform) {
- parameter.index = gl.getUniformLocation(glProgram, parameter.name);
- } else {
- parameter.index = gl.getAttribLocation(glProgram, parameter.name);
- }
- }
- }
- // initInstance(vertex, fragment); // btw make sure to disable the prefixes for ._isInstance
- }
+ public static function fromFile(fragmentPath:String, ?vertexPath:String, ?version:String):FunkinShader {
+ return new FunkinShader().loadShaderFile(fragmentPath, vertexPath, version);
}
- var __cancelNextProcessGLData:Bool = false;
- @:noCompletion private override function __processGLData(source:String, storageType:String):Void
- {
- onProcessGLData.dispatch(source, storageType);
- if (__cancelNextProcessGLData != (__cancelNextProcessGLData = false))
- return;
- var lastMatch = 0, position, regex, name, type;
-
- if (storageType == "uniform")
- {
- regex = ~/uniform ([A-Za-z0-9]+) ([A-Za-z0-9_]+)/;
- }
- else
- {
- regex = ~/attribute ([A-Za-z0-9]+) ([A-Za-z0-9_]+)/;
+ public function loadShaderFile(fragmentPath:String, ?vertexPath:String, ?version:String):FunkinShader {
+ if (vertexPath == null) {
+ final idx = fragmentPath.lastIndexOf(".");
+ if (idx == -1) vertexPath = fragmentPath;
+ else vertexPath = fragmentPath.substr(0, idx);
}
- while (regex.matchSub(source, lastMatch))
- {
- type = regex.matched(1);
- name = regex.matched(2);
-
- if (StringTools.startsWith(name, "gl_"))
- {
- continue;
- }
+ fragmentPath = FlxRuntimeShader._getPath(fragmentPath, false);
+ vertexPath = FlxRuntimeShader._getPath(vertexPath, true);
+ _fromFile(fragmentPath, vertexPath, version ?? (fragmentPath != null || vertexPath != null ? Flags.DEFAULT_GLSL_VERSION : null));
- var isUniform = (storageType == "uniform");
- registerParameter(name, type, isUniform);
-
- position = regex.matchedPos();
- lastMatch = position.pos + position.len;
- }
+ return this;
}
- function registerParameter(name:String, type:String, isUniform:Bool):Void
- {
- if (StringTools.startsWith(type, "sampler"))
- {
- var input = new ShaderInput();
- input.name = name;
- input.__isUniform = isUniform;
- __inputBitmapData.push(input);
-
- switch (name)
- {
- case "openfl_Texture":
- __texture = input;
- case "bitmap":
- __bitmap = input;
- default:
- }
-
- Reflect.setField(__data, name, input);
- try{Reflect.setField(this, name, input);} catch(e) {}
- }
- else if (!Reflect.hasField(__data, name) || Reflect.field(__data, name) == null)
- {
- var parameterType:ShaderParameterType = switch (type)
- {
- case "bool": BOOL;
- case "double", "float": FLOAT;
- case "int", "uint": INT;
- case "bvec2": BOOL2;
- case "bvec3": BOOL3;
- case "bvec4": BOOL4;
- case "ivec2", "uvec2": INT2;
- case "ivec3", "uvec3": INT3;
- case "ivec4", "uvec4": INT4;
- case "vec2", "dvec2": FLOAT2;
- case "vec3", "dvec3": FLOAT3;
- case "vec4", "dvec4": FLOAT4;
- case "mat2", "mat2x2": MATRIX2X2;
- case "mat2x3": MATRIX2X3;
- case "mat2x4": MATRIX2X4;
- case "mat3x2": MATRIX3X2;
- case "mat3", "mat3x3": MATRIX3X3;
- case "mat3x4": MATRIX3X4;
- case "mat4x2": MATRIX4X2;
- case "mat4x3": MATRIX4X3;
- case "mat4", "mat4x4": MATRIX4X4;
- default: null;
- }
+ #if REGION /* IHScriptCustomBehaviour */
+ public function hget(name:String):Dynamic {
+ if (__glSourceDirty) __init();
- var length = switch (parameterType)
- {
- case BOOL2, INT2, FLOAT2: 2;
- case BOOL3, INT3, FLOAT3: 3;
- case BOOL4, INT4, FLOAT4, MATRIX2X2: 4;
- case MATRIX3X3: 9;
- case MATRIX4X4: 16;
- default: 1;
- }
+ if (__thisHasField(name) || __thisHasField('get_${name}')) return Reflect.getProperty(this, name);
+ else if (!Reflect.hasField(__data, name)) return null;
- var arrayLength = switch (parameterType)
- {
- case MATRIX2X2: 2;
- case MATRIX3X3: 3;
- case MATRIX4X4: 4;
- default: 1;
- }
+ final field:Dynamic = Reflect.field(__data, name);
- switch (parameterType)
- {
- case BOOL, BOOL2, BOOL3, BOOL4:
- var parameter = new ShaderParameter();
- parameter.name = name;
- parameter.type = parameterType;
- parameter.__arrayLength = arrayLength;
- parameter.__isBool = true;
- parameter.__isUniform = isUniform;
- parameter.__length = length;
- __paramBool.push(parameter);
-
- if (name == "openfl_HasColorTransform")
- {
- __hasColorTransform = parameter;
- }
-
- Reflect.setField(__data, name, parameter);
- try{Reflect.setField(this, name, parameter);} catch(e) {}
-
- case INT, INT2, INT3, INT4:
- var parameter = new ShaderParameter();
- parameter.name = name;
- parameter.type = parameterType;
- parameter.__arrayLength = arrayLength;
- parameter.__isInt = true;
- parameter.__isUniform = isUniform;
- parameter.__length = length;
- __paramInt.push(parameter);
- Reflect.setField(__data, name, parameter);
- try{Reflect.setField(this, name, parameter);} catch(e) {}
-
- default:
- var parameter = new ShaderParameter();
- parameter.name = name;
- parameter.type = parameterType;
- parameter.__arrayLength = arrayLength;
- #if lime
- if (arrayLength > 0) parameter.__uniformMatrix = new Float32Array(arrayLength * arrayLength);
- #end
- parameter.__isFloat = true;
- parameter.__isUniform = isUniform;
- parameter.__length = length;
- __paramFloat.push(parameter);
-
- if (StringTools.startsWith(name, "openfl_"))
- {
- switch (name)
- {
- case "openfl_Alpha": __alpha = parameter;
- case "openfl_ColorMultiplier": __colorMultiplier = parameter;
- case "openfl_ColorOffset": __colorOffset = parameter;
- case "openfl_Matrix": __matrix = parameter;
- case "openfl_Position": __position = parameter;
- case "openfl_TextureCoord": __textureCoord = parameter;
- case "openfl_TextureSize": __textureSize = parameter;
- default:
- }
- }
-
- Reflect.setField(__data, name, parameter);
- try{Reflect.setField(this, name, parameter);} catch(e) {}
- }
- }
- }
-
- public function hget(name:String):Dynamic {
- if (__instanceFields.contains(name) || __instanceFields.contains('get_${name}'))
- return Reflect.getProperty(this, name);
- if (!Reflect.hasField(data, name))
- return null;
- var field:Dynamic = Reflect.field(data, name);
var cl:String = Type.getClassName(Type.getClass(field));
// little problem we are facing boys...
@@ -546,17 +78,19 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
}
public function hset(name:String, val:Dynamic):Dynamic {
- if (__instanceFields.contains(name) || __instanceFields.contains('set_${name}')) {
+ if (__glSourceDirty) __init();
+
+ if (__thisHasField(name) || __thisHasField('set_${name}')) {
Reflect.setProperty(this, name, val);
return val;
}
-
- if (!Reflect.hasField(data, name)) {
- Reflect.setField(data, name, val);
+ else if (!Reflect.hasField(__data, name)) {
+ // ??? huh
+ Reflect.setField(__data, name, val);
return val;
}
- var field = Reflect.field(data, name);
+ var field = Reflect.field(__data, name);
var cl = Type.getClassName(Type.getClass(field));
var isNotNull = val != null;
// cant do "field is ShaderInput" for some reason
@@ -598,67 +132,142 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
return val;
}
-}
+ #end
-class ShaderTemplates {
- public static final fragHeader:String = "varying float openfl_Alphav;
-varying vec4 openfl_ColorMultiplierv;
-varying vec4 openfl_ColorOffsetv;
-varying vec2 openfl_TextureCoordv;
+ override function __updateGL():Void {
+ onGLUpdate.dispatch();
+ super.__updateGL();
+ }
-uniform bool openfl_HasColorTransform;
-uniform vec2 openfl_TextureSize;
-uniform sampler2D bitmap;
+ override function __createAssembler():Void {
+ __glSourceAssembler = new FunkinShaderSourceAssembler(this);
+ }
-uniform bool hasTransform;
-uniform bool hasColorTransform;
+ override function toString():String {
+ return __cacheProgramId != null ? 'FunkinShader(${__cacheProgramId})' : 'FunkinShader';
+ }
+
+ #if REGION /* Deprecated */
+ public var shaderPrefix:String = "";
+ public var fragmentPrefix:String = "";
+ public var vertexPrefix:String = "";
+ #end
+
+ #if REGION /* Backward Compatibility */
+ private static var __instanceFields = Type.getInstanceFields(FunkinShader);
+ private static var FRAGMENT_SHADER = 0;
+ private static var VERTEX_SHADER = 1;
+
+ public var fileName(get, set):String;
+ inline function get_fileName():String return _fragmentFilePath ?? _vertexFilePath ?? "FunkinShader";
+ inline function set_fileName(v:String):String return _fragmentFilePath = _vertexFilePath = v;
+
+ public var fragFileName(get, set):String;
+ inline function get_fragFileName():String return _fragmentFilePath ?? "FunkinShader";
+ inline function set_fragFileName(v:String):String return _fragmentFilePath = v;
-vec4 applyFlixelEffects(vec4 color) {
- if(!hasTransform) {
- return color;
+ public var vertFileName(get, set):String;
+ inline function get_vertFileName():String return _vertexFilePath ?? "FunkinShader";
+ inline function set_vertFileName(v:String):String return _vertexFilePath = v;
+
+ public var glslVer(get, set):String;
+ inline function get_glslVer():String return glVersion;
+ inline function set_glslVer(v:String):String return glVersion = v;
+
+ public var glRawFragmentSource(get, set):String;
+ inline function get_glRawFragmentSource():String return __glFragmentSourceRaw;
+ inline function set_glRawFragmentSource(v:String):String return __glFragmentSourceRaw = v;
+
+ public var glRawVertexSource(get, set):String;
+ inline function get_glRawVertexSource():String return __glVertexSourceRaw;
+ inline function set_glRawVertexSource(v:String):String return __glVertexSourceRaw = v;
+
+ function thisHasField(v:String):Bool return __thisHasField(v);
+
+ function registerParameter(name:String, type:String, isUniform:Bool) {
+ __registerParameter(name, Shader.getParameterTypeFromGLSL(type, false), StringTools.startsWith(type, "sampler"), 1, null, isUniform, null);
}
- if(color.a == 0.0) {
- return vec4(0.0, 0.0, 0.0, 0.0);
+ // Unused... cne-openfl uses a different system
+ var __cancelNextProcessGLData:Bool = false;
+ public var onProcessGLData:FlxTypedSignal<(String, String)->Void> = new FlxTypedSignal<(String, String)->Void>();
+ #end
+}
+
+class FunkinShaderSourceAssembler extends FlxRuntimeShader.FlxShaderSourceAssembler {
+ final funkinParent:FunkinShader;
+
+ public function new(parent:FunkinShader) {
+ super(funkinParent = parent);
}
- if(!hasColorTransform) {
- return color * openfl_Alphav;
+ override function __appendIncludes(source:String, isVertex:Bool, ?includedKeys:Map):String
+ {
+ if (includedKeys == null) includedKeys = [];
+
+ source = GLSLSourceAssembler.__getIncludeFinder().map(source, (regex:EReg) ->
+ {
+ var key = regex.matched(1);
+ if (includedKeys.get(key)) return '/*Recursive include $key*/\n';
+
+ var include = __getIncludeSource(key, isVertex);
+ if (include == null) return '/*Unknown include $key*/\n';
+
+ includedKeys.set(key, true);
+ return '/*include $key*/\n' + __appendIncludes(include, isVertex, includedKeys);
+ });
+
+ return __getImportCompatibilityFinder().map(source, (regex:EReg) ->
+ {
+ var key = regex.matched(1);
+ if (includedKeys.get(key)) return '/*Recursive import $key*/\n';
+
+ var include = __getIncludeSource(key, isVertex);
+ if (include == null) return '/*Unknown import $key*/\n';
+
+ includedKeys.set(key, true);
+ return '/*import $key*/\n' + __appendIncludes(include, isVertex, includedKeys);
+ });
}
- color.rgb = color.rgb / color.a;
- color = clamp(openfl_ColorOffsetv + (color * openfl_ColorMultiplierv), 0.0, 1.0);
+ override function __getIncludeSource(include:String, fromVertex:Bool):Null {
+ final path = Paths.getPath('shaders/' + include);
+ if (Assets.exists(path)) return Assets.getText(path);
- if(color.a > 0.0) {
- return vec4(color.rgb * color.a * openfl_Alphav, color.a * openfl_Alphav);
+ final fallback = __getIncludeSource(include, fromVertex);
+ if (fallback != null) return fallback;
+
+ Logs.traceColored([
+ Logs.logText('[Shader] ', RED),
+ Logs.logText('Failed to import shader $include', RED),
+ ]);
+ return null;
}
- return vec4(0.0, 0.0, 0.0, 0.0);
-}
-vec4 flixel_texture2D(sampler2D bitmap, vec2 coord) {
- vec4 color = texture2D(bitmap, coord);
- return applyFlixelEffects(color);
-}
+ override function __appendPrefix(source:String, versionNumber:Int, versionProfile:String, extensions:Map, isVertex:Bool,
+ precisionHint:Null):String
+ {
+ var result = super.__appendPrefix(null, versionNumber, versionProfile, extensions, isVertex, precisionHint) + "\n";
-uniform vec4 _camSize;
+ result += funkinParent.shaderPrefix + "\n" + (isVertex ? funkinParent.vertexPrefix : funkinParent.fragmentPrefix) + "\n";
-float map(float value, float min1, float max1, float min2, float max2) {
- return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
-}
+ if (source != null) {
+ if (!isVertex && versionNumber >= 300 && versionProfile != "compatibility" && !StringTools.contains(source, "out vec4")) {
+ result += "out vec4 openfl_FragColor;\n";
+ }
+ result += source;
+ }
-vec2 getCamPos(vec2 pos) {
- vec4 size = _camSize / vec4(openfl_TextureSize, openfl_TextureSize);
- return vec2(map(pos.x, size.x, size.x + size.z, 0.0, 1.0), map(pos.y, size.y, size.y + size.w, 0.0, 1.0));
-}
-vec2 camToOg(vec2 pos) {
- vec4 size = _camSize / vec4(openfl_TextureSize, openfl_TextureSize);
- return vec2(map(pos.x, 0.0, 1.0, size.x, size.x + size.z), map(pos.y, 0.0, 1.0, size.y, size.y + size.w));
+ return result;
+ }
+
+ private static inline function __getImportCompatibilityFinder():EReg {
+ return ~/#import\s+(?|"([^"]+)"|'([^']+)'|<(.*)>|([^\s]+))/g;
+ }
}
-vec4 textureCam(sampler2D bitmap, vec2 pos) {
- return flixel_texture2D(bitmap, camToOg(pos));
-}";
- public static final fragBody:String = "gl_FragColor = flixel_texture2D(bitmap, openfl_TextureCoordv);";
+#if REGION /* Backward Compatibility */
+class ShaderTemplates {
public static final vertHeader:String = "attribute float openfl_Alpha;
attribute vec4 openfl_ColorMultiplier;
attribute vec4 openfl_ColorOffset;
@@ -679,32 +288,97 @@ attribute vec4 colorMultiplier;
attribute vec4 colorOffset;
uniform bool hasColorTransform;";
- public static final vertBody:String = "openfl_Alphav = openfl_Alpha;
-openfl_TextureCoordv = openfl_TextureCoord;
+ public static final vertBody:String = "openfl_TextureCoordv = openfl_TextureCoord;
-if(openfl_HasColorTransform) {
- openfl_ColorMultiplierv = openfl_ColorMultiplier;
- openfl_ColorOffsetv = openfl_ColorOffset / 255.0;
+if (hasColorTransform)
+{
+ openfl_Alphav = openfl_Alpha * colorMultiplier.a;
+ if (openfl_HasColorTransform)
+ {
+ openfl_ColorOffsetv = (openfl_ColorOffset / 255.0 * colorMultiplier) + (colorOffset / 255.0);
+ openfl_ColorMultiplierv = openfl_ColorMultiplier * vec4(colorMultiplier.rgb, 1.0);
+ }
+ else
+ {
+ openfl_ColorOffsetv = colorOffset / 255.0;
+ openfl_ColorMultiplierv = vec4(colorMultiplier.rgb, 1.0);
+ }
}
+else
+{
+ openfl_Alphav = openfl_Alpha * alpha;
+ if (openfl_HasColorTransform)
+ {
+ openfl_ColorOffsetv = (openfl_ColorOffset + colorOffset) / 255.0;
+ openfl_ColorMultiplierv = openfl_ColorMultiplier;
+ }
+ else
+ {
+ openfl_ColorOffsetv = colorOffset / 255.0;
+ openfl_ColorMultiplierv = vec4(1.0);
+ }
+}";
+
+ public static final fragHeader:String = "varying float openfl_Alphav;
+varying vec4 openfl_ColorMultiplierv;
+varying vec4 openfl_ColorOffsetv;
+varying vec2 openfl_TextureCoordv;
+
+uniform bool openfl_HasColorTransform;
+uniform vec2 openfl_TextureSize;
+uniform sampler2D bitmap;
+uniform bool hasTransform;
+uniform bool hasColorTransform;
+uniform bool premultiplyAlpha;
-openfl_Alphav = openfl_Alpha * alpha;
+vec4 apply_flixel_transform(vec4 color)
+{
+ if (!hasTransform) return color;
+ else if (color.a <= 0.0 || openfl_Alphav == 0.0) return vec4(0.0);
-if(hasColorTransform) {
- openfl_ColorOffsetv = colorOffset / 255.0;
- openfl_ColorMultiplierv = colorMultiplier;
+ // this is just solely for ASTC compressed textures.
+ // ...also in flixel_texture2D, it also converts to linear alpha anyway.
+ if (!premultiplyAlpha) color.rgb /= color.a;
+
+ color = clamp(openfl_ColorOffsetv + (color * openfl_ColorMultiplierv), 0.0, 1.0);
+ return vec4(color.rgb * color.a * openfl_Alphav, color.a * openfl_Alphav);
}
+#define applyFlixelEffects(color) apply_flixel_transform(color)
-gl_Position = openfl_Matrix * openfl_Position;";
+vec4 flixel_texture2D(sampler2D bitmap, vec2 coord)
+{
+ return apply_flixel_transform(texture2D(bitmap, coord));
+}
-// TODO: make this ignore comments
-public static final vertBackCompatVarList:Array = [
- ~/attribute float alpha/,
- ~/attribute vec4 colorMultiplier/,
- ~/attribute vec4 colorOffset/,
- ~/uniform bool hasColorTransform/
-];
+uniform vec4 _camSize;
+
+float map(float value, float min1, float max1, float min2, float max2) {
+ return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
+}
-public static final vertHeaderBackCompat:String = "attribute float openfl_Alpha;
+vec2 getCamPos(vec2 pos) {
+ vec4 size = _camSize / vec4(openfl_TextureSize, openfl_TextureSize);
+ return vec2(map(pos.x, size.x, size.x + size.z, 0.0, 1.0), map(pos.y, size.y, size.y + size.w, 0.0, 1.0));
+}
+vec2 camToOg(vec2 pos) {
+ vec4 size = _camSize / vec4(openfl_TextureSize, openfl_TextureSize);
+ return vec2(map(pos.x, 0.0, 1.0, size.x, size.x + size.z), map(pos.y, 0.0, 1.0, size.y, size.y + size.w));
+}
+vec4 textureCam(sampler2D bitmap, vec2 pos) {
+ return flixel_texture2D(bitmap, camToOg(pos));
+}";
+
+ public static final fragBody:String = "gl_FragColor = flixel_texture2D(bitmap, openfl_TextureCoordv);
+if (gl_FragColor.a == 0.0) discard;";
+
+ public static final vertBackCompatVarList:Array = [
+ ~/attribute float alpha/,
+ ~/attribute vec4 colorMultiplier/,
+ ~/attribute vec4 colorOffset/,
+ ~/uniform bool hasColorTransform/
+ ];
+
+ public static final vertHeaderBackCompat:String = "attribute float openfl_Alpha;
attribute vec4 openfl_ColorMultiplier;
attribute vec4 openfl_ColorOffset;
attribute vec4 openfl_Position;
@@ -719,7 +393,7 @@ uniform mat4 openfl_Matrix;
uniform bool openfl_HasColorTransform;
uniform vec2 openfl_TextureSize;";
-public static final vertBodyBackCompat:String = "openfl_Alphav = openfl_Alpha;
+ public static final vertBodyBackCompat:String = "openfl_Alphav = openfl_Alpha;
openfl_TextureCoordv = openfl_TextureCoord;
if(openfl_HasColorTransform) {
@@ -728,19 +402,8 @@ if(openfl_HasColorTransform) {
}
gl_Position = openfl_Matrix * openfl_Position;";
-
- public static final defaultVertexSource:String = "#pragma header
-
-void main(void) {
- #pragma body
-}";
-
- public static final defaultFragmentSource:String = "#pragma header
-
-void main(void) {
- #pragma body
-}";
}
+#end
class ShaderTypeException extends Exception {
var has:Class;
@@ -753,16 +416,4 @@ class ShaderTypeException extends Exception {
this.name = name;
super('ShaderTypeException - Tried to set the shader uniform "${name}" as a ${Type.getClassName(has)}, but the shader uniform is a ${Std.string(want)}.');
}
-}
-
-class ShaderErrorPosition {
- public var column:Int;
- public var line:Int;
- public var message:String;
-
- public function new(line:String, column:String, message:String) {
- this.line = Std.parseInt(line);
- this.column = Std.parseInt(column);
- this.message = message;
- }
}
\ No newline at end of file
diff --git a/source/funkin/backend/shaders/FunkinShaderOLD._hx b/source/funkin/backend/shaders/FunkinShaderOLD._hx
new file mode 100644
index 0000000000..1853813dde
--- /dev/null
+++ b/source/funkin/backend/shaders/FunkinShaderOLD._hx
@@ -0,0 +1,471 @@
+package funkin.backend.shaders;
+
+import haxe.Exception;
+import haxe.io.Path;
+import flixel.graphics.FlxGraphic;
+import flixel.system.FlxAssets.FlxShader;
+import flixel.util.FlxSignal.FlxTypedSignal;
+import flixel.util.FlxStringUtil;
+import hscript.IHScriptCustomBehaviour;
+import openfl.display.BitmapData;
+import openfl.display.ShaderInput;
+import openfl.display.ShaderParameter;
+import openfl.display.ShaderParameterType;
+import openfl.display.Shader;
+import openfl.display3D._internal.GLProgram;
+import openfl.display3D._internal.GLShader;
+import openfl.display3D.Program3D;
+import openfl.utils._internal.Log;
+
+using StringTools;
+@:access(openfl.display3D.Context3D)
+@:access(openfl.display3D.Program3D)
+@:access(openfl.display.ShaderInput)
+@:access(openfl.display.ShaderParameter)
+class FunkinShader extends FlxShader implements IHScriptCustomBehaviour {
+ #if REGION /* Backward Compatibility */
+ private static var __instanceFields = Type.getInstanceFields(FunkinShader);
+ private static var FRAGMENT_SHADER = 0;
+ private static var VERTEX_SHADER = 1;
+
+ public var glslVer(get, set):String;
+ inline function get_glslVer():String return glVersion;
+ inline function set_glslVer(v:String):String return glVersion = v;
+
+ public var glRawFragmentSource(get, set):String;
+ inline function get_glRawFragmentSource():String return __glFragmentSourceRaw;
+ inline function set_glRawFragmentSource(v:String):String return __glFragmentSourceRaw = v;
+
+ public var glRawVertexSource(get, set):String;
+ inline function get_glRawVertexSource():String return __glVertexSourceRaw;
+ inline function set_glRawVertexSource(v:String):String return __glVertexSourceRaw = v;
+
+ // Unused... cne-openfl uses a different system
+ var __cancelNextProcessGLData:Bool = false;
+ public var onProcessGLData:FlxTypedSignal<(String, String)->Void> = new FlxTypedSignal<(String, String)->Void>();
+ #end
+
+ public static function getShaderCode(key:String, isFragment = true):Null {
+ var path = "shaders/" + key;
+ key = Path.withoutExtension(key);
+
+ final ext = Path.extension(path);
+ if (ext == "") path = path + (isFragment ? ".frag" : ".vert");
+ else isFragment = ext != "vert";
+
+ path = Paths.getPath(path);
+ return Assets.exists(path) ? Assets.getText(path) : null;
+ }
+
+ private static function processGLSLText(source:String, glVersion:String, isFragment:Bool, ?pragmas:Map):String
+ return Shader.processGLSLText(_processGLSLText(source, glVersion, isFragment, pragmas), glVersion, isFragment);
+
+ private static function _processGLSLText(source:String, glVersion:String, isFragment:Bool, ?pragmas:Map):String {
+ if (pragmas != null) {
+ final pragmaKeyword = ~/#pragma\s+(\w+)/g;
+ source = pragmaKeyword.map(source, (_) -> {
+ var name = pragmaKeyword.matched(1), pragma:String;
+ if (pragmas.exists(name)) pragma = pragmas.get(name);
+ else {
+ if (name != "header" && name != "body") return '#pragma $name';
+ pragma = "";
+ }
+ return _processGLSLText(pragma, glVersion, isFragment, pragmas);
+ });
+ }
+
+ inline function tryGetShaderCode(key:String) {
+ final s = getShaderCode(key, isFragment);
+ if (s == null) {
+ Logs.traceColored([
+ Logs.logText('[Shader] ', RED),
+ Logs.logText('Failed to import shader $key', RED),
+ ]);
+ return "";
+ }
+ return s;
+ }
+
+ final includeKeyword = ~/#include ['"](.+)['"]/g;
+ final importKeyword = ~/#import\s+<(.*)>/g;
+ source = importKeyword.map(source, (_) ->
+ return _processGLSLText(tryGetShaderCode(importKeyword.matched(1)), glVersion, isFragment, pragmas) ?? "");
+
+ return source = includeKeyword.map(source, (_) ->
+ return _processGLSLText(tryGetShaderCode(includeKeyword.matched(1)), glVersion, isFragment, pragmas) ?? "");
+ }
+
+ private static var __defaultsAvailable:Bool;
+ private static var __glFragmentSourceDefault:String;
+ private static var __glVertexSourceDefault:String;
+ private static var __glFragmentPragmasDefault:Map;
+ private static var __glVertexPragmasDefault:Map;
+ private static var __glFragmentExtensionsDefault:Array;
+ private static var __glVertexExtensionsDefault:Array;
+
+ public var onGLUpdate:FlxTypedSignalVoid> = new FlxTypedSignalVoid>();
+
+ public var fileName:String = "FunkinShader";
+ public var fragFileName:String = "FunkinShader";
+ public var vertFileName:String = "FunkinShader";
+
+ public var shaderPrefix:String = "";
+ public var fragmentPrefix:String = "";
+ public var vertexPrefix:String = "";
+
+ private var __immediate:Bool;
+
+ public function new(?fragmentSource:String, ?vertexSource:String, ?version:String,
+ ?fragmentExtensions:Array, ?vertexExtensions:Array, immediate = false
+ ) {
+ if (!__defaultsAvailable) {
+ __glFragmentSourceDefault = __glFragmentSourceRaw;
+ __glVertexSourceDefault = __glVertexSourceRaw;
+ __glFragmentPragmasDefault = __glFragmentPragmas.copy();
+ __glVertexPragmasDefault = __glVertexPragmas.copy();
+ __glFragmentExtensionsDefault = __glFragmentExtensions ?? [];
+ __glVertexExtensionsDefault = __glVertexExtensions ?? [];
+ __defaultsAvailable = true;
+ }
+
+ __immediate = immediate;
+
+ if (version != null) glVersion = version;
+ if (vertexExtensions != null) glVertexExtensions = vertexExtensions;
+ if (fragmentExtensions != null) glFragmentExtensions = fragmentExtensions;
+ if (vertexSource != null) glVertexSource = vertexSource;
+ if (fragmentSource != null) glFragmentSource = fragmentSource;
+
+ super();
+
+ if (!__isGenerated) {
+ __isGenerated = true;
+ __init();
+ }
+ }
+
+ public function loadShader(name:String, ?version:String, immediate = false):FunkinShader {
+ final fragment = getShaderCode(name, true), vertex = getShaderCode(name, false);
+
+ glVersion = version;
+ glVertexSource = vertex ?? __glVertexSourceDefault;
+ glFragmentSource = fragment ?? __glFragmentSourceDefault;
+
+ if (immediate) __init();
+ return this;
+ }
+
+ override function __initGL():Void {
+ if (__immediate) {
+ __context = FlxG.stage.context3D;
+ __enable();
+ }
+ super.__initGL();
+ }
+
+ override function __updateGL():Void {
+ onGLUpdate.dispatch();
+ super.__updateGL();
+ }
+
+ public function hget(name:String):Dynamic {
+ if (__glSourceDirty || __data == null) __init();
+
+ if (thisHasField(name) || thisHasField('get_${name}')) return Reflect.getProperty(this, name);
+ else if (!Reflect.hasField(__data, name)) return null;
+
+ final field:Dynamic = Reflect.field(__data, name);
+
+ var cl:String = Type.getClassName(Type.getClass(field));
+
+ // little problem we are facing boys...
+
+ // cant do "field is ShaderInput" because ShaderInput has the @:generic metadata
+ // aka instead of ShaderInput it gets built as ShaderInput_Float
+ // this should be fine tho because we check the class, and the fields don't vary based on the type
+
+ // thanks for looking in the code cne fans :D!! -lunar
+
+ if (cl.startsWith("openfl.display.ShaderParameter"))
+ return (field.__length > 1) ? field.value : field.value[0];
+ else if (cl.startsWith("openfl.display.ShaderInput"))
+ return field.input;
+ return field;
+ }
+
+ public function hset(name:String, val:Dynamic):Dynamic {
+ if (__glSourceDirty || __data == null) __init();
+
+ if (thisHasField(name) || thisHasField('set_${name}')) {
+ Reflect.setProperty(this, name, val);
+ return val;
+ }
+ else if (!Reflect.hasField(__data, name)) {
+ // ??? huh
+ Reflect.setField(__data, name, val);
+ return val;
+ }
+
+ var field = Reflect.field(__data, name);
+ var cl = Type.getClassName(Type.getClass(field));
+ var isNotNull = val != null;
+ // cant do "field is ShaderInput" for some reason
+ if (cl.startsWith("openfl.display.ShaderParameter")) {
+ if (field.__length <= 1) {
+ // that means we wait for a single number, instead of an array
+ if (field.__isInt && isNotNull && !(val is Int)) {
+ throw new ShaderTypeException(name, Type.getClass(val), 'Int');
+ return null;
+ } else
+ if (field.__isBool && isNotNull && !(val is Bool)) {
+ throw new ShaderTypeException(name, Type.getClass(val), 'Bool');
+ return null;
+ } else
+ if (field.__isFloat && isNotNull && !(val is Float)) {
+ throw new ShaderTypeException(name, Type.getClass(val), 'Float');
+ return null;
+ }
+ return field.value = isNotNull ? [val] : null;
+ } else {
+ if (isNotNull && !(val is Array)) {
+ throw new ShaderTypeException(name, Type.getClass(val), Array);
+ return null;
+ }
+ return field.value = val;
+ }
+ } else if (cl.startsWith("openfl.display.ShaderInput")) {
+ // shader input!!
+ var bitmap:BitmapData;
+ if (!isNotNull) bitmap = null;
+ else if (val is BitmapData) bitmap = val;
+ else if (val is FlxGraphic) bitmap = val.bitmap;
+ else {
+ throw new ShaderTypeException(name, Type.getClass(val), BitmapData);
+ return null;
+ }
+ field.input = bitmap;
+ }
+
+ return val;
+ }
+
+ override function __buildSourcePrefix(isFragment:Bool):String {
+ var result = super.__buildSourcePrefix(isFragment) + '\n$shaderPrefix';
+ return isFragment ? result + '\n$fragmentPrefix' : result + '\n$vertexPrefix';
+ }
+
+ override function set_glFragmentExtensions(value:Array):Array {
+ if (value == null) value = __glFragmentExtensionsDefault;
+ if (value != __glFragmentExtensions) __glSourceDirty = true;
+ return __glFragmentExtensions = value;
+ }
+
+ override function set_glVertexExtensions(value:Array):Array {
+ if (value == null) value = __glVertexExtensionsDefault;
+ if (value != __glVertexExtensions) __glSourceDirty = true;
+ return __glVertexExtensions = value;
+ }
+
+ override function set_glVersion(value:Null):String {
+ if (value == null || value == "") value = Flags.DEFAULT_GLSL_VERSION;
+ if ((__glVersionRaw = value) != __glVersion) {
+ __glSourceDirty = true;
+ if (__glVertexSourceRaw != null) __glVertexSource = processGLSLText(__glVertexSourceRaw, value, false, __glVertexPragmas);
+ if (__glFragmentSourceRaw != null) __glFragmentSource = processGLSLText(__glFragmentSourceRaw, value, true, __glFragmentPragmas);
+ }
+
+ return __glVersion = value;
+ }
+
+ override function set_glFragmentSource(value:String):String {
+ if (value == null || value == "") value = __glFragmentSourceDefault;
+ if ((__glFragmentSourceRaw = value) != null) {
+ if (__glVersion != (__glVersion = Shader.getGLSLTextVersion(value, __glVersionRaw)))
+ __glSourceDirty = true;
+
+ value = processGLSLText(value, __glVersion, true, __glFragmentPragmas);
+ }
+
+ if (value != __glFragmentSource) __glSourceDirty = true;
+ return __glFragmentSource = value;
+ }
+
+ override function set_glVertexSource(value:String):String {
+ if (value == null || value == "") value = __glVertexSourceDefault;
+ if ((__glVertexSourceRaw = value) != null) {
+ if (__glVersion != (__glVersion = Shader.getGLSLTextVersion(value, __glVersionRaw)))
+ __glSourceDirty = true;
+
+ value = processGLSLText(value, __glVersion, false, __glVertexPragmas);
+ }
+
+ if (value != __glVertexSource) __glSourceDirty = true;
+ return __glVertexSource = value;
+ }
+
+ override function set_glFragmentPragmas(value:Map):Map {
+ if (value == null) value = __glFragmentPragmasDefault;
+ if (value != __glFragmentPragmas)
+ __glSourceDirty = true;
+
+ return __glFragmentPragmas = value;
+ }
+
+ override function set_glVertexPragmas(value:Map):Map {
+ if (value == null) value = __glVertexPragmasDefault;
+ if (value != __glVertexPragmas)
+ __glSourceDirty = true;
+
+ return __glVertexPragmas = value;
+ }
+
+ function registerParameter(name:String, type:String, isUniform:Bool):Void {
+ __registerParameter(name, Program3D.getParameterTypeFromGLString(type, 1), 1, -1, isUniform, false, null);
+ }
+
+ public function toString():String
+ return FlxStringUtil.getDebugString([for (field in Reflect.fields(data)) LabelValuePair.weak(field, Reflect.field(data, field))]);
+}
+
+class ShaderTemplates {
+ #if REGION /* Backward Compatibility */
+ public static final vertHeader:String = "attribute float openfl_Alpha;
+attribute vec4 openfl_ColorMultiplier;
+attribute vec4 openfl_ColorOffset;
+attribute vec4 openfl_Position;
+attribute vec2 openfl_TextureCoord;
+
+varying float openfl_Alphav;
+varying vec4 openfl_ColorMultiplierv;
+varying vec4 openfl_ColorOffsetv;
+varying vec2 openfl_TextureCoordv;
+
+uniform mat4 openfl_Matrix;
+uniform bool openfl_HasColorTransform;
+uniform vec2 openfl_TextureSize;
+
+attribute float alpha;
+attribute vec4 colorMultiplier;
+attribute vec4 colorOffset;
+
+uniform bool hasColorTransform;";
+
+ public static final vertBody:String = "openfl_TextureCoordv = openfl_TextureCoord;
+
+if (hasColorTransform) {
+ openfl_Alphav = openfl_Alpha * colorMultiplier.a;
+ if (openfl_HasColorTransform) {
+ openfl_ColorOffsetv = (openfl_ColorOffset / 255.0 * colorMultiplier) + (colorOffset / 255.0);
+ openfl_ColorMultiplierv = openfl_ColorMultiplier * vec4(colorMultiplier.rgb, 1.0);
+ }
+ else {
+ openfl_ColorOffsetv = colorOffset / 255.0;
+ openfl_ColorMultiplierv = vec4(colorMultiplier.rgb, 1.0);
+ }
+}
+else {
+ openfl_Alphav = openfl_Alpha * alpha;
+ if (openfl_HasColorTransform) {
+ openfl_ColorOffsetv = (openfl_ColorOffset + colorOffset) / 255.0;
+ openfl_ColorMultiplierv = openfl_ColorMultiplier;
+ }
+ else {
+ openfl_ColorOffsetv = colorOffset / 255.0;
+ openfl_ColorMultiplierv = vec4(1.0);
+ }
+}";
+
+ public static final fragHeader:String = "varying float openfl_Alphav;
+varying vec4 openfl_ColorMultiplierv;
+varying vec4 openfl_ColorOffsetv;
+varying vec2 openfl_TextureCoordv;
+
+uniform bool openfl_HasColorTransform;
+uniform vec2 openfl_TextureSize;
+uniform sampler2D bitmap;
+
+uniform bool hasTransform;
+uniform bool hasColorTransform;
+
+vec4 apply_flixel_transform(vec4 color) {
+ if (!hasTransform) return color;
+ else if (color.a <= 0.0 || openfl_Alphav == 0.0) return vec4(0.0);
+
+ color.rgb /= color.a;
+ color = clamp(openfl_ColorOffsetv + (color * openfl_ColorMultiplierv), 0.0, 1.0);
+ return vec4(color.rgb * color.a * openfl_Alphav, color.a * openfl_Alphav);
+}
+
+#define applyFlixelEffects(color) apply_flixel_transform(color)
+
+vec4 flixel_texture2D(sampler2D bitmap, vec2 coord) {
+ return apply_flixel_transform(texture2D(bitmap, coord));
+}
+
+uniform vec4 _camSize;
+
+float map(float value, float min1, float max1, float min2, float max2) {
+ return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
+}
+
+vec2 getCamPos(vec2 pos) {
+ vec4 size = _camSize / vec4(openfl_TextureSize, openfl_TextureSize);
+ return vec2(map(pos.x, size.x, size.x + size.z, 0.0, 1.0), map(pos.y, size.y, size.y + size.w, 0.0, 1.0));
+}
+vec2 camToOg(vec2 pos) {
+ vec4 size = _camSize / vec4(openfl_TextureSize, openfl_TextureSize);
+ return vec2(map(pos.x, 0.0, 1.0, size.x, size.x + size.z), map(pos.y, 0.0, 1.0, size.y, size.y + size.w));
+}
+vec4 textureCam(sampler2D bitmap, vec2 pos) {
+ return flixel_texture2D(bitmap, camToOg(pos));
+}";
+
+ public static final fragBody:String = "gl_FragColor = flixel_texture2D(bitmap, openfl_TextureCoordv);
+if (gl_FragColor.a == 0.0) discard;";
+
+ public static final vertBackCompatVarList:Array = [
+ ~/attribute float alpha/,
+ ~/attribute vec4 colorMultiplier/,
+ ~/attribute vec4 colorOffset/,
+ ~/uniform bool hasColorTransform/
+ ];
+
+ public static final vertHeaderBackCompat:String = "attribute float openfl_Alpha;
+attribute vec4 openfl_ColorMultiplier;
+attribute vec4 openfl_ColorOffset;
+attribute vec4 openfl_Position;
+attribute vec2 openfl_TextureCoord;
+
+varying float openfl_Alphav;
+varying vec4 openfl_ColorMultiplierv;
+varying vec4 openfl_ColorOffsetv;
+varying vec2 openfl_TextureCoordv;
+
+uniform mat4 openfl_Matrix;
+uniform bool openfl_HasColorTransform;
+uniform vec2 openfl_TextureSize;";
+
+ public static final vertBodyBackCompat:String = "openfl_Alphav = openfl_Alpha;
+openfl_TextureCoordv = openfl_TextureCoord;
+
+if(openfl_HasColorTransform) {
+ openfl_ColorMultiplierv = openfl_ColorMultiplier;
+ openfl_ColorOffsetv = openfl_ColorOffset / 255.0;
+}
+
+gl_Position = openfl_Matrix * openfl_Position;";
+ #end
+}
+
+class ShaderTypeException extends Exception {
+ var has:Class;
+ var want:Class;
+ var name:String;
+
+ public function new(name:String, has:Class, want:Dynamic) {
+ this.has = has;
+ this.want = want;
+ this.name = name;
+ super('ShaderTypeException - Tried to set the shader uniform "${name}" as a ${Type.getClassName(has)}, but the shader uniform is a ${Std.string(want)}.');
+ }
+}
\ No newline at end of file
diff --git a/source/funkin/backend/system/Controls.hx b/source/funkin/backend/system/Controls.hx
index c4c80d5308..14a9518263 100644
--- a/source/funkin/backend/system/Controls.hx
+++ b/source/funkin/backend/system/Controls.hx
@@ -31,6 +31,7 @@ enum Control
DEV_ACCESS;
DEV_CONSOLE;
DEV_RELOAD;
+ DEV_INSPECTOR;
}
enum KeyboardScheme
@@ -185,6 +186,12 @@ class Controls extends FlxActionSet
@:pressed("dev-reload") public var DEV_RELOAD_HOLD(get, set): Bool;
@:justReleased("dev-reload") public var DEV_RELOAD_R(get, set): Bool;
+ @:devModeOnly
+ @:gamepad([])
+ @:justPressed("dev-inspector") public var DEV_INSPECTOR(get, set): Bool;
+ @:pressed("dev-inspector") public var DEV_INSPECTOR_HOLD(get, set): Bool;
+ @:justReleased("dev-inspector") public var DEV_INSPECTOR_R(get, set): Bool;
+
@:allow(funkin.backend.utils.ControlsUtil)
var byName:Map = [];
diff --git a/source/funkin/backend/system/FakeCamera.hx b/source/funkin/backend/system/FakeCamera.hx
index 99d0eb1b88..114af05544 100644
--- a/source/funkin/backend/system/FakeCamera.hx
+++ b/source/funkin/backend/system/FakeCamera.hx
@@ -11,9 +11,14 @@ import openfl.display.BlendMode;
import openfl.geom.ColorTransform;
import openfl.geom.Point;
import openfl.geom.Rectangle;
+import openfl.display.TriangleCulling;
+import openfl.display3D.Context3DWrapMode;
+import openfl.display3D.Context3DCompareMode;
+import openfl.filters.BitmapFilter;
+import openfl.filters.ShaderFilter;
class FakeCamera extends FlxCamera {
- public static final instance = new FakeCamera();
+ public static var instance:FakeCamera;
public function new() {
super();
@@ -21,14 +26,14 @@ class FakeCamera extends FlxCamera {
visible = false;
}
- override function startTrianglesBatch(graphic:FlxGraphic, smoothing:Bool = false, isColored:Bool = false, ?blend:BlendMode, ?hasColorOffsets:Bool, ?shader:FlxShader) { return null;}
- override function startQuadBatch(graphic:FlxGraphic, colored:Bool, hasColorOffsets:Bool = false, ?blend:BlendMode, smooth:Bool = false, ?shader:FlxShader) { return null;}
+ override function startTrianglesBatch(graphic:FlxGraphic, smoothing:Bool = false, isColored:Bool = false, ?blend:BlendMode, ?hasColorOffsets:Bool, ?shader:FlxShader, ?wrapMode:Context3DWrapMode, ?depthCompareMode:Context3DCompareMode, ?culling:TriangleCulling) { return null;}
+ override function startQuadBatch(graphic:FlxGraphic, colored:Bool, hasColorOffsets:Bool = false, ?blend:BlendMode, smooth:Bool = false, ?shader:FlxShader, ?wrapMode:Context3DWrapMode, ?depthCompareMode:Context3DCompareMode) { return null;}
override function clearDrawStack() {}
override function render() {}
- public override function drawPixels(?frame:FlxFrame, ?pixels:BitmapData, matrix:FlxMatrix, ?transform:ColorTransform, ?blend:BlendMode, ?smoothing:Bool = false, ?shader:FlxShader) {}
- public override function copyPixels(?frame:FlxFrame, ?pixels:BitmapData, ?sourceRect:Rectangle, destPoint:Point, ?transform:ColorTransform, ?blend:BlendMode, ?smoothing:Bool = false, ?shader:FlxShader) {}
- public override function drawTriangles(graphic:FlxGraphic, vertices:DrawData, indices:DrawData, uvtData:DrawData, ?colors:DrawData, ?position:FlxPoint, ?blend:BlendMode, repeat:Bool = false, smoothing:Bool = false, ?transform:ColorTransform, ?shader:FlxShader):Void {}
+ public override function drawPixels(?frame:FlxFrame, ?pixels:BitmapData, matrix:FlxMatrix, ?transform:ColorTransform, ?blend:BlendMode, ?smoothing:Bool = false, ?shader:FlxShader, ?wrapMode:Context3DWrapMode, ?depthCompareMode:Context3DCompareMode) {}
+ public override function copyPixels(?frame:FlxFrame, ?pixels:BitmapData, ?sourceRect:Rectangle, destPoint:Point, ?transform:ColorTransform, ?blend:BlendMode, ?smoothing:Bool = false, ?shader:FlxShader, ?wrapMode:Context3DWrapMode, ?depthCompareMode:Context3DCompareMode) {}
+ public override function drawTriangles(graphic:FlxGraphic, vertices:DrawData, indices:DrawData, uvtData:DrawData, ?colors:DrawData, ?position:FlxPoint, ?blend:BlendMode, repeat:Bool = false, smoothing:Bool = false, ?transform:ColorTransform, ?shader:FlxShader, ?wrapMode:Context3DWrapMode, ?depthCompareMode:Context3DCompareMode, ?culling:TriangleCulling):Void {}
public override function update(elapsed:Float) {}
@@ -38,13 +43,13 @@ class FakeCamera extends FlxCamera {
}
class FakeCallCamera extends FakeCamera {
- public static final instance = new FakeCallCamera();
+ public static var instance:FakeCallCamera;
public var ignoreDraws:Bool = false;
- public dynamic function onDraw(?frame:FlxFrame, ?pixels:BitmapData, matrix:FlxMatrix, ?transform:ColorTransform, ?blend:BlendMode, ?smoothing:Bool = false, ?shader:FlxShader) {
+ public dynamic function onDraw(?frame:FlxFrame, ?pixels:BitmapData, matrix:FlxMatrix, ?transform:ColorTransform, ?blend:BlendMode, ?smoothing:Bool = false, ?shader:FlxShader, ?wrapMode:Context3DWrapMode, ?depthCompareMode:Context3DCompareMode) {
}
- override function drawPixels(?frame:FlxFrame, ?pixels:BitmapData, matrix:FlxMatrix, ?transform:ColorTransform, ?blend:BlendMode, ?smoothing:Bool = false, ?shader:FlxShader) {
- if (!ignoreDraws) onDraw(frame, pixels, matrix, transform, blend, smoothing, shader);
+ override function drawPixels(?frame:FlxFrame, ?pixels:BitmapData, matrix:FlxMatrix, ?transform:ColorTransform, ?blend:BlendMode, ?smoothing:Bool = false, ?shader:FlxShader, ?wrapMode:Context3DWrapMode, ?depthCompareMode:Context3DCompareMode) {
+ if (!ignoreDraws) onDraw(frame, pixels, matrix, transform, blend, smoothing, shader, wrapMode, depthCompareMode);
}
}
\ No newline at end of file
diff --git a/source/funkin/backend/system/Flags.hx b/source/funkin/backend/system/Flags.hx
index 83448e61e2..d04aa7e7f1 100644
--- a/source/funkin/backend/system/Flags.hx
+++ b/source/funkin/backend/system/Flags.hx
@@ -66,12 +66,16 @@ class Flags {
public static var PATHS_UNIX_FIX:Bool = true;
/**
- * Preferred sound extension for the game's audio files.
- * Currently is set to `mp3` for web targets, and `ogg` for other targets.
+ * Preferred file extensions for the game's audio files.
*/
- public static var SOUND_EXT:String = #if web "mp3" #else "ogg" #end; // we also support wav
- public static var VIDEO_EXT:String = "mp4";
- public static var IMAGE_EXT:String = "png"; // we also support jpg
+ public static var SOUND_EXTS:Array = [#if web "mp3", "ogg", #else "ogg", "mp3", #end "flac", "opus", "wav"];
+ public static var VIDEO_EXTS:Array = ["mp4", "webm", "mkv", "mov"]; // is there any more? // yes frakits
+ public static var IMAGE_EXTS:Array = ["png", "jpg", "jpeg"]; // TODO: Add more after another lime rebases for SDLImage
+
+ // DEPRECATED
+ @:lazy public static var SOUND_EXT:Null = null;
+ @:lazy public static var VIDEO_EXT:Null = null;
+ @:lazy public static var IMAGE_EXT:Null = null;
public static var DEFAULT_DISCORD_LOGO_KEY:String = "icon";
public static var DEFAULT_DISCORD_CLIENT_ID:String = "1383853614589673472";
@@ -111,6 +115,11 @@ class Flags {
public static var DEFAULT_STEPS_PER_BEAT:Int = 4;
public static var DEFAULT_LOOP_TIME:Float = 0.0;
public static var ICONS_AUTOPOSITION:Bool = true;
+
+ @:lazy public static var DEFAULT_SOUND_TIME_SCALED_PITCH:Null = null;
+ @:lazy public static var USE_SOUND_VOLUME_CURVE:Null = null;
+ @:lazy public static var USE_FLXTRAIL_FRAMES:Null = null;
+
public static var SUPPORTED_CHART_RUNTIME_FORMATS:Array = ["Legacy", "Psych Engine"];
public static var SUPPORTED_CHART_FORMATS:Array = ["BaseGame"];
@@ -289,7 +298,7 @@ class Flags {
public static var DEFAULT_CHARACTER_GHOSTDISABLE_SOUND:String = "editors/character/ghostDisable";
public static var DEFAULT_CHARACTER_GHOSTENABLE_SOUND:String = "editors/character/ghostEnable";
- public static var DEFAULT_GLSL_VERSION:String = "120";
+ @:lazy public static var DEFAULT_GLSL_VERSION:String = null;
@:also(funkin.backend.utils.HttpUtil.userAgent)
public static var USER_AGENT:String = 'request';
// -- End of Codename's Default Flags --
@@ -323,8 +332,28 @@ class Flags {
if (WINDOW_TITLE_USE_MOD_NAME == null) WINDOW_TITLE_USE_MOD_NAME = !overridenFlags.exists('TITLE') && overridenFlags.exists('MOD_NAME');
if (USE_LEGACY_TIMING == null) USE_LEGACY_TIMING = MOD_API_VERSION < 2;
if (SUSTAINS_AS_ONE_NOTE == null) SUSTAINS_AS_ONE_NOTE = MOD_API_VERSION >= 2;
+ if (DEFAULT_GLSL_VERSION == null) {
+ if (MOD_API_VERSION < 2) {
+ DEFAULT_GLSL_VERSION = #if (android || mac || web) "100" #else "120" #end;
+ Logs.warn("Blend Mode Extensions won't work in MOD_API_VERSION below than 2");
+ }
+ else {
+ DEFAULT_GLSL_VERSION = openfl.utils.GLSLSourceAssembler.getDefaultVersion();
+ }
+ }
+ if (DEFAULT_SOUND_TIME_SCALED_PITCH == null) DEFAULT_SOUND_TIME_SCALED_PITCH = MOD_API_VERSION >= 2;
+ if (USE_SOUND_VOLUME_CURVE == null) USE_SOUND_VOLUME_CURVE = MOD_API_VERSION >= 2;
+ if (USE_FLXTRAIL_FRAMES == null) USE_FLXTRAIL_FRAMES = MOD_API_VERSION < 2;
+
+ flixel.sound.FlxSound.defaultTimeScaledPitch = cast DEFAULT_SOUND_TIME_SCALED_PITCH;
+ flixel.addons.effects.FlxTrail.defaultDelayBackwardCompatibility = cast USE_FLXTRAIL_FRAMES;
+
if (USE_LEGACY_CENTER_CAM == null) USE_LEGACY_CENTER_CAM = MOD_API_VERSION < 3;
if (USE_LEGACY_FLXANIMATE_STAGE_MATRIX == null) USE_LEGACY_FLXANIMATE_STAGE_MATRIX = MOD_API_VERSION < 3;
+
+ if (SOUND_EXT == null) SOUND_EXT = SOUND_EXTS[0]; else SOUND_EXTS = [SOUND_EXT];
+ if (VIDEO_EXT == null) VIDEO_EXT = VIDEO_EXTS[0]; else VIDEO_EXTS = [VIDEO_EXT];
+ if (IMAGE_EXT == null) IMAGE_EXT = IMAGE_EXTS[0]; else IMAGE_EXTS = [IMAGE_EXT];
}
public static function loadFromDatas(datas:Array):Map {
diff --git a/source/funkin/backend/system/FunkinGame.hx b/source/funkin/backend/system/FunkinGame.hx
index b754c8d41d..44122aee39 100644
--- a/source/funkin/backend/system/FunkinGame.hx
+++ b/source/funkin/backend/system/FunkinGame.hx
@@ -3,9 +3,11 @@ package funkin.backend.system;
import flixel.FlxGame;
import flixel.FlxG;
import openfl.events.KeyboardEvent;
+import openfl.events.Event;
class FunkinGame extends FlxGame {
var skipNextTickUpdate:Bool = false;
+ var manualPause:Bool = false; //fake autopause
#if desktop
var fullscreenListener:KeyboardEvent->Void;
@@ -28,12 +30,45 @@ class FunkinGame extends FlxGame {
super.switchState();
// draw once to put all images in gpu then put the last update time to now to prevent lag spikes or whatever
draw();
- _total = ticks = getTicks();
+ ticks = getTicks();
skipNextTickUpdate = true;
}
- public override function onEnterFrame(t) {
- if (skipNextTickUpdate != (skipNextTickUpdate = false)) _total = ticks = getTicks();
- super.onEnterFrame(t);
+ override function __enterFrame(deltaTime:Float) {
+ if (skipNextTickUpdate != (skipNextTickUpdate = false)) ticks = getTicks();
+ if (manualPause) {
+ var prevAutoPause = FlxG.autoPause;
+ FlxG.autoPause = true;
+ super.__enterFrame(deltaTime);
+ FlxG.autoPause = prevAutoPause;
+ draw();
+ return;
+ }
+ super.__enterFrame(deltaTime);
+ }
+
+
+ override public function onFocus(_):Void {
+ if (manualPause) return;
+ super.onFocus(_);
+ }
+
+ override public function onFocusLost(event:Event):Void {
+ if (manualPause) return;
+ super.onFocusLost(event);
+ }
+
+ public function toggleManualPause() {
+ var prevAutoPause = FlxG.autoPause;
+ FlxG.autoPause = true;
+
+ if (!manualPause) {
+ onFocusLost(null);
+ manualPause = true;
+ } else {
+ manualPause = false;
+ onFocus(null);
+ }
+ FlxG.autoPause = prevAutoPause;
}
}
diff --git a/source/funkin/backend/system/GraphicCacheSprite.hx b/source/funkin/backend/system/GraphicCacheSprite.hx
index 6ff61905b3..cba41b61bd 100644
--- a/source/funkin/backend/system/GraphicCacheSprite.hx
+++ b/source/funkin/backend/system/GraphicCacheSprite.hx
@@ -36,7 +36,7 @@ class GraphicCacheSprite extends FlxSprite {
if (graphic == null) return;
// make their useCount one time higher to prevent them from auto being cleared from cache
- graphic.useCount++;
+ graphic.incrementUseCount();
graphic.destroyOnNoUse = false;
cachedGraphics.push(graphic);
nonRenderedCachedGraphics.push(graphic);
@@ -45,7 +45,7 @@ class GraphicCacheSprite extends FlxSprite {
public override function destroy() {
for(g in cachedGraphics) {
g.destroyOnNoUse = true;
- g.useCount--;
+ g.decrementUseCount();
}
graphic = null;
super.destroy();
diff --git a/source/funkin/backend/system/Logs.hx b/source/funkin/backend/system/Logs.hx
index 1913e4abc6..fe5bc386b3 100644
--- a/source/funkin/backend/system/Logs.hx
+++ b/source/funkin/backend/system/Logs.hx
@@ -2,6 +2,7 @@ package funkin.backend.system;
import flixel.system.debug.log.LogStyle;
import flixel.system.frontEnds.LogFrontEnd;
+import funkin.backend.system.console.ConsoleUI;
import funkin.backend.utils.NativeAPI.ConsoleColor;
import funkin.backend.utils.NativeAPI;
import haxe.Log;
@@ -9,18 +10,26 @@ import haxe.Log;
final class Logs {
private static var __showing:Bool = false;
+ public static final LOG_WARNING_TEXT = ' WARNING ';
+ public static final LOG_ERROR_TEXT = ' ERROR ';
+ public static final LOG_TRACE_TEXT = ' TRACE ';
+ public static final LOG_VERBOSE_TEXT = ' VERBOSE ';
+ public static final LOG_SUCCESS_TEXT = ' SUCCESS ';
+ public static final LOG_FAILURE_TEXT = ' FAILURE ';
+ public static final LOG_INFORMATION_TEXT = ' INFORMATION ';
+
public static var nativeTrace = Log.trace;
public static function init() {
Log.trace = function(v:Dynamic, ?infos:Null) {
var data = [
- logText('${infos.fileName}:${infos.lineNumber}: ', CYAN),
- logText(Std.string(v))
+ logText('${infos.fileName}:${infos.lineNumber}: ', CYAN, TRACE),
+ logText(Std.string(v), LIGHTGRAY, TRACE)
];
if (infos.customParams != null) {
for (i in infos.customParams) {
data.push(
- logText("," + Std.string(i))
+ logText("," + Std.string(i), LIGHTGRAY, TRACE)
);
}
}
@@ -77,30 +86,31 @@ final class Logs {
public static function prepareColoredTrace(text:Array, level:Level = INFO) {
var time = Date.now();
var superCoolText = [
- logText('[ '),
- logText('${Std.string(time.getHours()).addZeros(2)}:${Std.string(time.getMinutes()).addZeros(2)}:${Std.string(time.getSeconds()).addZeros(2)}', DARKMAGENTA),
- logText(' |'),
+ logText('[ ', LIGHTGRAY, level),
+ logText('${Std.string(time.getHours()).addZeros(2)}:${Std.string(time.getMinutes()).addZeros(2)}:${Std.string(time.getSeconds()).addZeros(2)}', DARKMAGENTA, level),
+ logText(' |', LIGHTGRAY, level),
switch (level)
{
- case WARNING: logText(' WARNING ', DARKYELLOW);
- case ERROR: logText(' ERROR ', DARKRED);
- case TRACE: logText(' TRACE ', GRAY);
- case VERBOSE: logText(' VERBOSE ', DARKMAGENTA);
- case SUCCESS: logText(' SUCCESS ', GREEN);
- case FAILURE: logText(' FAILURE ', RED);
- default: logText(' INFORMATION ', CYAN);
+ case WARNING: logText(LOG_WARNING_TEXT, DARKYELLOW, level);
+ case ERROR: logText(LOG_ERROR_TEXT, DARKRED, level);
+ case TRACE: logText(LOG_TRACE_TEXT, GRAY, level);
+ case VERBOSE: logText(LOG_VERBOSE_TEXT, DARKMAGENTA, level);
+ case SUCCESS: logText(LOG_SUCCESS_TEXT, GREEN, level);
+ case FAILURE: logText(LOG_FAILURE_TEXT, RED, level);
+ default: logText(LOG_INFORMATION_TEXT, CYAN, level);
},
- logText('] ')
+ logText('] ', LIGHTGRAY, level)
];
for(k=>e in superCoolText)
text.insert(k, e);
return text;
}
- public static function logText(text:String, color:ConsoleColor = LIGHTGRAY):LogText {
+ public static function logText(text:String, color:ConsoleColor = LIGHTGRAY, level:Level = INFO):LogText {
return {
text: text,
- color: color
+ color: color,
+ level: level
};
}
@@ -115,7 +125,7 @@ final class Logs {
Sys.print(t.text);
}
NativeAPI.setConsoleColors();
- Sys.print("\r\n");
+ Sys.println("\r");
__showing = false;
#elseif mobile
while(__showing) {
@@ -129,19 +139,24 @@ final class Logs {
@:privateAccess
nativeTrace([for(t in text) t.text].join(""));
#end
+
+ #if IMGUI_ENABLED
+ @:privateAccess
+ ConsoleUI.instance.addToConsole(text);
+ #end
}
public inline static function traceColored(text:Array, level:Level = INFO)
__showInConsole(prepareColoredTrace(text, level));
public static function trace(text:String, level:Level = INFO, color:ConsoleColor = LIGHTGRAY, ?prefix:String) {
- var text = [logText(text, color)];
- if(prefix != null) text.insert(0, getPrefix(prefix));
+ var text = [logText(text, color, level)];
+ if(prefix != null) text.insert(0, getPrefix(prefix, level));
traceColored(text, level);
}
- public inline static function getPrefix(prefix:String)
- return logText('[${prefix}] ', BLUE);
+ public inline static function getPrefix(prefix:String, level:Level = INFO)
+ return logText('[${prefix}] ', BLUE, level);
public inline static function infos(text:String, color:ConsoleColor = LIGHTGRAY, ?prefix:String)
Logs.trace(text, INFO, color, prefix);
@@ -169,4 +184,5 @@ enum abstract Level(Int) {
typedef LogText = {
var text:String;
var color:ConsoleColor;
+ var level:Level;
}
diff --git a/source/funkin/backend/system/Main.hx b/source/funkin/backend/system/Main.hx
index 60e25798b5..40d4a84802 100644
--- a/source/funkin/backend/system/Main.hx
+++ b/source/funkin/backend/system/Main.hx
@@ -6,10 +6,13 @@ import flixel.addons.transition.TransitionData;
import flixel.graphics.FlxGraphic;
import flixel.math.FlxPoint;
import flixel.math.FlxRect;
+import funkin.backend.system.FakeCamera;
+import funkin.backend.system.FakeCamera.FakeCallCamera;
import flixel.system.ui.FlxSoundTray;
import funkin.backend.assets.AssetSource;
import funkin.backend.assets.AssetsLibraryList;
import funkin.backend.assets.ModsFolder;
+import funkin.backend.system.console.ConsoleUI;
import funkin.backend.system.framerate.Framerate;
import funkin.backend.system.framerate.SystemInfo;
import funkin.backend.system.modules.*;
@@ -24,8 +27,13 @@ import openfl.utils.AssetLibrary;
import sys.FileSystem;
import sys.io.File;
#if android
-import android.content.Context;
-import android.os.Build;
+import extension.androidtools.content.Context;
+import extension.androidtools.os.Build;
+#end
+
+#if IMGUI_ENABLED
+import lime.tools.imgui.ImGuiFlags;
+import lime.tools.imgui.ImGuiTypes;
#end
class Main extends Sprite
@@ -33,7 +41,7 @@ class Main extends Sprite
public static var instance:Main;
public static var modToLoad:String = null;
- public static var forceGPUOnlyBitmapsOff:Bool = #if desktop false #else true #end;
+ public static var forceGPUOnlyBitmapsOff:Bool = false;
public static var noTerminalColor:Bool = false;
public static var verbose:Bool = false;
public static var goToSong:String = null;
@@ -62,9 +70,11 @@ class Main extends Sprite
// You can pretty much ignore everything from here on - your code should go in your states.
public static function preInit() {
+ #if sys
funkin.backend.utils.NativeAPI.registerAsDPICompatible();
funkin.backend.system.CommandLineHandler.parseCommandLine(Sys.args());
funkin.backend.system.Main.fixWorkingDirectory();
+ #end
}
public function new()
@@ -73,7 +83,16 @@ class Main extends Sprite
instance = this;
+ #if IMGUI_ENABLED
+ initImGui();
+ addChild(ImGuiHandler.instance);
+ #end
CrashHandler.init();
+ ConsoleUI.init();
+
+ // i hate you hxcpp
+ FakeCamera.instance = new FakeCamera();
+ FakeCallCamera.instance = new FakeCallCamera();
addChild(game = new FunkinGame(gameWidth, gameHeight, MainState, Options.framerate, Options.framerate, skipSplash, startFullscreen));
@@ -99,16 +118,10 @@ class Main extends Sprite
// DEPRECATED
@:dox(hide) public static function execAsync(func:Void->Void) ThreadUtil.execAsync(func);
- private static function getTimer():Int {
- return time = Lib.getTimer();
- }
-
public static function loadGameSettings() {
WindowUtils.init();
SaveWarning.init();
MemoryUtil.init();
- @:privateAccess
- FlxG.game.getTimer = getTimer;
FunkinCache.init();
Paths.assetsTree = new AssetsLibraryList();
@@ -133,12 +146,13 @@ class Main extends Sprite
funkin.options.PlayerSettings.init();
Options.load();
+ game.focusLostFramerate = 30;
FlxG.fixedTimestep = false;
-
FlxG.scaleMode = scaleMode = new FunkinRatioScaleMode();
+ FlxG.sound.applySoundCurve = applySoundCurve;
+ FlxG.sound.reverseSoundCurve = reverseSoundCurve;
Conductor.init();
- AudioSwitchFix.init();
EventManager.init();
FlxG.signals.focusGained.add(onFocus);
FlxG.signals.preStateSwitch.add(onStateSwitch);
@@ -161,6 +175,16 @@ class Main extends Sprite
initTransition();
}
+ public static function applySoundCurve(volume:Float) {
+ return Flags.USE_SOUND_VOLUME_CURVE ? Math.pow(volume, 1.75) : volume;
+ }
+
+ public static function reverseSoundCurve(curvedVolume:Float) {
+ return Flags.USE_SOUND_VOLUME_CURVE ? Math.pow(curvedVolume, 0.5714285714285714) : curvedVolume;
+ }
+
+ static var persistShaderKeys:Map;
+
public static function refreshAssets() @:privateAccess {
FunkinCache.instance.clearSecondLayer();
@@ -177,6 +201,18 @@ class Main extends Sprite
}
game.addChildAt(game.soundTray = daSndTray, index);
+
+ if (persistShaderKeys == null) {
+ persistShaderKeys = [for (k in @:privateAccess Lib.current.stage.context3D.__programs.keys()) k => true];
+ }
+ else {
+ for (key => program in @:privateAccess Lib.current.stage.context3D.__programs) {
+ if (persistShaderKeys.get(key) || Type.resolveClass(key) != null) continue;
+
+ program.dispose();
+ @:privateAccess Lib.current.stage.context3D.__programs.remove(key);
+ }
+ }
}
public static function initTransition() {
@@ -197,10 +233,11 @@ class Main extends Sprite
private static function onStateSwitch() {
scaleMode.resetSize();
}
-
public static function onUpdate() {
+ #if !IMGUI_ENABLED
if (PlayerSettings.solo.controls.DEV_CONSOLE)
NativeAPI.allocConsole();
+ #end
if (PlayerSettings.solo.controls.FPS_COUNTER && Options.fpsCounter)
Framerate.debugMode = (Framerate.debugMode + 1) % 3;
@@ -210,16 +247,6 @@ class Main extends Sprite
// manual asset clearing since base openfl one does'nt clear lime one
// does'nt clear bitmaps since flixel fork does it auto
- @:privateAccess {
- // clear uint8 pools
- for(length=>pool in openfl.display3D.utils.UInt8Buff._pools) {
- for(b in pool.clear())
- b.destroy();
- }
-
- openfl.display3D.utils.UInt8Buff._pools.clear();
- }
-
MemoryUtil.clearMajor();
}
@@ -240,4 +267,75 @@ class Main extends Sprite
public static function get_timeSinceFocus():Float {
return (FlxG.game.ticks - _tickFocused) / 1000;
}
+
+ #if IMGUI_ENABLED
+ private static var imGuiActiveLastFrame:Bool = true;
+ #end
+ private static function initImGui() {
+ #if IMGUI_ENABLED
+ //codename styled
+ var vcrFont = ImGuiIO.fonts.addFontFromFileTTF("assets/fonts/vcr.ttf");
+ ImGuiIO.fontDefault = vcrFont;
+ var style = ImGui.getStyle();
+ style.windowBorderSize = 2;
+ style.childBorderSize = 2;
+ style.popupBorderSize = 2;
+ style.frameBorderSize = 2;
+ style.windowRounding = 6;
+ style.childRounding = 6;
+ style.popupRounding = 6;
+ style.frameRounding = 6;
+ style.scrollbarRounding = 6;
+ style.grabRounding = 6;
+ style.setColor(ImGuiCol.WindowBg, new ImVec4(0.11, 0.00, 0.16, 0.8));
+ style.setColor(ImGuiCol.Border, new ImVec4(0.59, 0.59, 0.59, 0.50));
+ style.setColor(ImGuiCol.FrameBg, new ImVec4(0.13, 0.00, 0.19, 0.54));
+ style.setColor(ImGuiCol.FrameBgHovered, new ImVec4(0.33, 0.15, 0.42, 0.40));
+ style.setColor(ImGuiCol.FrameBgActive, new ImVec4(0.33, 0.15, 0.42, 0.67));
+ style.setColor(ImGuiCol.TitleBg, new ImVec4(0.38, 0.36, 0.40, 0.32));
+ style.setColor(ImGuiCol.TitleBgActive, new ImVec4(0.38, 0.36, 0.40, 0.72));
+ style.setColor(ImGuiCol.CheckMark, new ImVec4(0.80, 0.60, 1.00, 1.00));
+ style.setColor(ImGuiCol.SliderGrab, new ImVec4(0.33, 0.30, 0.35, 1.00));
+ style.setColor(ImGuiCol.SliderGrabActive, new ImVec4(0.80, 0.60, 1.00, 1.00));
+ style.setColor(ImGuiCol.Button, new ImVec4(0.14, 0.13, 0.13, 0.99));
+ style.setColor(ImGuiCol.ButtonHovered, new ImVec4(0.39, 0.00, 0.59, 1.00));
+ style.setColor(ImGuiCol.ButtonActive, new ImVec4(0.76, 0.00, 1.00, 1.00));
+ style.setColor(ImGuiCol.Header, new ImVec4(0.15, 0.13, 0.13, 0.8));
+ style.setColor(ImGuiCol.HeaderHovered, new ImVec4(0.39, 0.00, 0.59, 0.80));
+ style.setColor(ImGuiCol.HeaderActive, new ImVec4(0.76, 0.00, 1.00, 1.00));
+ style.setColor(ImGuiCol.SeparatorHovered, new ImVec4(0.39, 0.00, 0.59, 0.78));
+ style.setColor(ImGuiCol.SeparatorActive, new ImVec4(0.76, 0.00, 1.00, 1.00));
+ style.setColor(ImGuiCol.ResizeGrip, new ImVec4(0.15, 0.13, 0.13, 0.20));
+ style.setColor(ImGuiCol.ResizeGripHovered, new ImVec4(0.39, 0.00, 0.59, 0.67));
+ style.setColor(ImGuiCol.ResizeGripActive, new ImVec4(0.76, 0.00, 1.00, 0.95));
+ style.setColor(ImGuiCol.InputTextCursor, new ImVec4(0.68, 0.13, 0.96, 1.00));
+ style.setColor(ImGuiCol.TabHovered, new ImVec4(0.39, 0.00, 0.59, 0.80));
+ style.setColor(ImGuiCol.Tab, new ImVec4(0.21, 0.19, 0.19, 0.86));
+ style.setColor(ImGuiCol.TabSelected, new ImVec4(0.76, 0.00, 1.00, 1.00));
+ style.setColor(ImGuiCol.TabSelectedOverline, new ImVec4(0.76, 0.00, 1.00, 1.00));
+ style.setColor(ImGuiCol.TabDimmed, new ImVec4(0.36, 0.18, 0.41, 1.00));
+ style.setColor(ImGuiCol.TabDimmedSelected, new ImVec4(0.46, 0.21, 0.54, 1.00));
+ style.setColor(ImGuiCol.DockingPreview, new ImVec4(0.56, 0.11, 0.71, 1.00));
+
+ ImGuiHandler.instance.addCallback(function() {
+ if ((ImGuiIO.configFlags & ImGuiConfigFlags.ViewportsEnable) != 0)
+ {
+ if (ImGuiIO.metricsRenderWindows > 2) { //debug window + dockspace
+ if (!imGuiActiveLastFrame) {
+ imGuiActiveLastFrame = true;
+ FlxG.autoPause = false;
+ FlxG.game.focusLostFramerate = FlxG.drawFramerate;
+ }
+ } else {
+ if (imGuiActiveLastFrame) {
+ imGuiActiveLastFrame = false;
+ FlxG.autoPause = Options.autoPause;
+ //FlxG.game.focusLostFramerate = 30; //just keep as draw fps, some timing issues with window focusing that keep it from working correctly
+ }
+ }
+ }
+ ImGui.dockSpaceOverViewport(0, null, ImGuiDockNodeFlags.PassthruCentralNode);
+ });
+ #end
+ }
}
diff --git a/source/funkin/backend/system/MainState.hx b/source/funkin/backend/system/MainState.hx
index 03c260209e..97e812c485 100644
--- a/source/funkin/backend/system/MainState.hx
+++ b/source/funkin/backend/system/MainState.hx
@@ -1,5 +1,6 @@
package funkin.backend.system;
+import funkin.backend.system.console.ConsoleCommandManager;
#if MOD_SUPPORT
import sys.FileSystem;
#end
@@ -36,8 +37,10 @@ class MainState extends FlxState {
Options.save();
ControlsUtil.resetCustomControls();
+ ConsoleCommandManager.unregisterModdedCommands();
FlxG.bitmap.reset();
FlxG.sound.destroy(true);
+ FlxG.sound.resetCache();
Paths.assetsTree.reset();
@@ -169,7 +172,7 @@ class MainState extends FlxState {
}
initiated = true;
- if (@:privateAccess FlxG.game._requestedState == null || @:privateAccess FlxG.game._requestedState == this) {
+ if (@:privateAccess FlxG.game._nextState == null) {
var startState:Class = Flags.DISABLE_WARNING_SCREEN ? TitleState : funkin.menus.WarningState;
var outdatedAPI:Bool = (Flags.MOD_API_VERSION ?? Flags.CURRENT_API_VERSION) < Flags.CURRENT_API_VERSION;
// In this case if the mod we just loaded a compressed modpack, we can't edit or modify files without decompressing it.
diff --git a/source/funkin/backend/system/OptimizedBitmapData.hx b/source/funkin/backend/system/OptimizedBitmapData.hx
index f6cf1117a9..510ebf14e2 100644
--- a/source/funkin/backend/system/OptimizedBitmapData.hx
+++ b/source/funkin/backend/system/OptimizedBitmapData.hx
@@ -1,62 +1,14 @@
package funkin.backend.system;
import lime.graphics.Image;
-import lime.graphics.cairo.CairoImageSurface;
import openfl.display.BitmapData;
-import openfl.geom.Rectangle;
+@:deprecated("Use openfl.display.BitmapData.toHardware instead.")
class OptimizedBitmapData extends BitmapData {
@SuppressWarnings("checkstyle:Dynamic")
@:noCompletion private override function __fromImage(image:#if lime Image #else Dynamic #end):Void
{
- #if lime
- if (image != null && image.buffer != null)
- {
- this.image = image;
-
- width = image.width;
- height = image.height;
- rect = new Rectangle(0, 0, image.width, image.height);
-
- __textureWidth = width;
- __textureHeight = height;
-
- #if sys
- image.format = BGRA32;
- image.premultiplied = true;
- #end
-
- __isValid = true;
- readable = true;
-
- if(FlxG.stage.context3D != null) {
- lock();
- getTexture(FlxG.stage.context3D);
- getSurface();
-
- readable = true;
- this.image = null;
-
- // @:privateAccess
- // if (FlxG.bitmap.__doNotDelete)
- // MemoryUtil.clearMinor();
- }
- }
- #end
- }
-
- @SuppressWarnings("checkstyle:Dynamic")
- @:dox(hide) public override function getSurface():#if lime CairoImageSurface #else Dynamic #end
- {
- #if lime
- if (__surface == null)
- {
- __surface = CairoImageSurface.fromImage(image);
- }
-
- return __surface;
- #else
- return null;
- #end
+ super.__fromImage(image);
+ toHardware();
}
}
\ No newline at end of file
diff --git a/source/funkin/backend/system/RotatingSpriteGroup.hx b/source/funkin/backend/system/RotatingSpriteGroup.hx
index ad3eea575e..520e77417f 100644
--- a/source/funkin/backend/system/RotatingSpriteGroup.hx
+++ b/source/funkin/backend/system/RotatingSpriteGroup.hx
@@ -8,7 +8,12 @@ class RotatingSpriteGroup extends FlxSpriteGroup {
if (maxSize <= 0)
return super.recycle(ObjectClass, ObjectFactory, Force, Revive);
if (group.members.length < maxSize)
- return group.recycleCreateObject(ObjectClass, ObjectFactory);
+ {
+ if (ObjectFactory != null) return add(ObjectFactory());
+ if (ObjectClass != null) return add(Type.createInstance(ObjectClass, []));
+
+ return null;
+ }
var spr = group.members.shift();
group.members.push(spr);
if (Revive)
diff --git a/source/funkin/backend/system/console/BuiltInCommands.hx b/source/funkin/backend/system/console/BuiltInCommands.hx
new file mode 100644
index 0000000000..63e9f2030b
--- /dev/null
+++ b/source/funkin/backend/system/console/BuiltInCommands.hx
@@ -0,0 +1,178 @@
+package funkin.backend.system.console;
+import funkin.backend.scripting.ModState;
+import funkin.menus.credits.CreditsMain;
+import funkin.editors.stage.StageEditor;
+import funkin.game.Stage;
+import funkin.game.Character;
+import funkin.editors.character.CharacterEditor;
+import funkin.options.OptionsMenu;
+import funkin.editors.charter.Charter;
+import funkin.backend.chart.Chart;
+import funkin.menus.TitleState;
+import funkin.menus.MainMenuState;
+import funkin.menus.FreeplayState;
+import funkin.menus.StoryMenuState;
+import funkin.backend.system.console.ConsoleCommand;
+
+
+class BuiltInCommands {
+ static var help = new FuncCommand("help", "[object]", "(Shows full list of commands or list of fields on an object or class)", function(args) {
+ if (args.length == 0) {
+ var strBuf:StringBuf = new StringBuf();
+ strBuf.add("\nCommands:");
+ for (cmdName in ConsoleCommandManager.commandsStringList) { //use list to go in order
+ var cmd = ConsoleCommandManager.getCommand(cmdName);
+ strBuf.add("\n\t");
+ strBuf.add(cmd.name);
+ strBuf.add(" ");
+ strBuf.add(cmd.argsDesc);
+ strBuf.add("\n\t\t");
+ strBuf.add(cmd.desc);
+ }
+ Logs.infos(strBuf.toString());
+ } else {
+ @:privateAccess
+ var fields = ConsoleUI.instance.consoleHscript.tryGetFields(args[0]);
+ if (fields.length == 0) {
+ Logs.infos("Failed to find fields for: " + args[0]);
+ return;
+ }
+ var strBuf:StringBuf = new StringBuf();
+ strBuf.add("\n");
+ strBuf.add(args[0]);
+ strBuf.add(":");
+ for (f in fields) {
+ strBuf.add("\n\t");
+ strBuf.add(f.name);
+ strBuf.add(":");
+ strBuf.add(f.type);
+ if (f.value != "") {
+ strBuf.add(" = ");
+ strBuf.add(f.value);
+ }
+ }
+ Logs.infos(strBuf.toString());
+ }
+ });
+ static var clear = new FuncCommand("clear", "", "(Clears the console log)", function(args) { @:privateAccess ConsoleUI.instance.clearConsole(); });
+ static var pause = new FuncCommand("pause", "", "(Toggles pause on game)", function(args) {
+ var game:FunkinGame = cast FlxG.game;
+ game.toggleManualPause();
+ });
+ static var loadSong = new FuncCommand("loadSong", "[song] [diff] [variation] [opponentMode] [coopMode]", "(Loads a new song and switches to PlayState)", function(args) {
+ if (args.length == 0) {
+ Logs.error("Can't load a song with no name!");
+ return;
+ }
+
+ var name:String = args[0];
+ var diff:String = args[1];
+ var variation:String = args[2] != null ? (args[2] == "default" ? null : args[2]) : null;
+
+ var chartPath:String = Paths.chart(name, diff, variation);
+ if (Assets.exists(chartPath)) {
+ PlayState.loadSong(name, diff, variation, args[3] != null ? args[3] == "true" : false, args[4] != null ? args[4] == "true" : false);
+ FlxG.switchState(new PlayState());
+ } else {
+ Logs.error('Chart for song $name at "$chartPath" was not found.');
+ }
+ });
+ static var endSong = new FuncCommand("endSong", "", "(Ends the current song if in PlayState)", function(args) {
+ if (PlayState.instance != null) {
+ PlayState.instance.endSong();
+ }
+ });
+ static var loadWeek = new FuncCommand("loadWeek", "[name] [diff]", "(Loads a new week and switches to PlayState)", function(args) {
+ if (args.length == 0) return;
+ var weeklist = StoryWeeklist.get(true, false);
+ for (w in weeklist.weeks) {
+ if (args[0] == w.id) {
+ var diff = args[1] != null ? args[1] : w.difficulties[w.difficulties.length-1];
+ if (w.difficulties.contains(diff)) {
+ PlayState.loadWeek(w, diff);
+ FlxG.switchState(new PlayState());
+ return;
+ }
+ Logs.error('Difficulty $diff for week ${w.id} was not found.');
+ return;
+ }
+ }
+ Logs.error('Week with id ${args[0]} was not found.');
+ });
+ static var switchMod = new FuncCommand("switchMod", "[name]", "(Switches to another mod, inputting nothing will disable mod)", function(args) { funkin.backend.assets.ModsFolder.switchMod(args[0]); });
+ static var reloadMod = new FuncCommand("reloadMod", "", "(Reload current mod)", function(args) { funkin.backend.assets.ModsFolder.reloadMods(); });
+
+ static var reloadState = new FuncCommand("reloadState", "", "(Reload current state)", function(args) { FlxG.resetState(); });
+ static var goToPlayState = new FuncCommand("goToPlayState", "", "(switches state to PlayState, only works if a song is already loaded)", function(args) { if (PlayState.SONG != null) FlxG.switchState(new PlayState()); });
+ static var goToMainMenu = new FuncCommand("goToMainMenu", "", "(switches state to MainMenuState)", function(args) { FlxG.switchState(new MainMenuState()); });
+ static var goToStoryMode = new FuncCommand("goToStoryMode", "", "(switches state to StoryMenuState)", function(args) { FlxG.switchState(new StoryMenuState()); });
+ static var goToFreeplay = new FuncCommand("goToFreeplay", "", "(switches state to FreeplayState)", function(args) { FlxG.switchState(new FreeplayState()); });
+ static var goToOptions = new FuncCommand("goToOptions", "", "(switches state to OptionsMenu)", function(args) { FlxG.switchState(new OptionsMenu()); });
+ static var goToCredits = new FuncCommand("goToCredits", "", "(switches state to CreditsMain)", function(args) { FlxG.switchState(new CreditsMain()); });
+ static var goToTitle = new FuncCommand("goToTitle", "[reset]", "(switches state to TitleState)", function(args) {
+ @:privateAccess
+ if (args[0] != null && args[0] == "true") TitleState.initialized = false;
+ FlxG.switchState(new TitleState());
+ });
+ static var goToModState = new FuncCommand("goToModState", "[name]", "(switches state to a custom ModState)", function(args) {
+ FlxG.switchState(new ModState(args[0]));
+ });
+
+ static var goToCharter = new FuncCommand("goToCharter", "[song] [diff] [variation]", "(opens chart editor, inputting nothing will load existing song)", function(args) {
+ if (args.length == 0) {
+ if (PlayState.SONG == null) {
+ Logs.error("Charter doesn't have any song data loaded!");
+ } else {
+ @:privateAccess
+ if (Charter.__song == null || (PlayState.SONG != null && PlayState.SONG.meta != null && PlayState.SONG.meta.name != Charter.__song) || PlayState.difficulty != Charter.__diff || PlayState.variation != Charter.__variant) {
+ FlxG.switchState(new Charter(PlayState.SONG.meta.name, PlayState.difficulty, PlayState.variation)); //load fresh charter, can't load previous because PlayState.SONG probably changed
+ } else {
+ @:privateAccess
+ FlxG.switchState(new Charter(Charter.__song, Charter.__diff, Charter.__variant, false));
+ }
+ }
+ return;
+ }
+
+ var name:String = args[0];
+ var diff:String = args[1];
+ var variation:String = args[2] != null ? (args[2] == "default" ? null : args[2]) : null;
+
+ var chartPath:String = Paths.chart(name, diff, variation);
+ if (Assets.exists(chartPath)) {
+ FlxG.switchState(new Charter(name, diff, variation));
+ } else {
+ Logs.error('Chart for song $name at "$chartPath" was not found.');
+ }
+ });
+
+ static var goToCharacterEditor = new FuncCommand("goToCharacterEditor", "[name]", "(opens character editor)", function(args) {
+ if (args.length == 0) {
+ Logs.error("Can't load a character with no name!");
+ return;
+ }
+ var name:String = args[0];
+ var list = Character.getList(false, false, null, true);
+ if (!list.contains(name)) {
+ Logs.error('Character $name was not found.');
+ return;
+ }
+
+ FlxG.switchState(new CharacterEditor(name));
+ });
+
+ static var goToStageEditor = new FuncCommand("goToStageEditor", "[name]", "(opens stage editor)", function(args) {
+ if (args.length == 0) {
+ Logs.error("Can't load a stage with no name!");
+ return;
+ }
+ var name:String = args[0];
+ var list = Stage.getList(false, true);
+ if (!list.contains(name)) {
+ Logs.error('stage $name was not found.');
+ return;
+ }
+
+ FlxG.switchState(new StageEditor(name));
+ });
+}
\ No newline at end of file
diff --git a/source/funkin/backend/system/console/ConsoleCommand.hx b/source/funkin/backend/system/console/ConsoleCommand.hx
new file mode 100644
index 0000000000..0455c1d375
--- /dev/null
+++ b/source/funkin/backend/system/console/ConsoleCommand.hx
@@ -0,0 +1,76 @@
+package funkin.backend.system.console;
+import funkin.backend.system.console.ConsoleCommandManager;
+
+class BaseCommand {
+ public var name:String;
+ public var argsDesc:String;
+ public var desc:String;
+
+ @:allow(funkin.backend.system.console.ConsoleCommandManager)
+ private var _nameLower:String;
+
+ public function new(name:String, argsDesc:String, desc:String, autoRegister:Bool = true) {
+ this.name = name;
+ this.argsDesc = argsDesc;
+ this.desc = desc;
+ _nameLower = name.toLowerCase();
+ if (autoRegister) ConsoleCommandManager.registerCommand(this);
+ }
+ public function onRegister() {};
+ public function onUnregister() {};
+ public function execute(raw:String) {};
+
+ public static function parseArgs(raw:String) {
+ var args:Array = [];
+ if (raw == "") return args;
+
+ var i:Int = 0;
+ var curStr = "";
+ var parseState = 0;
+ while(true) {
+ var char = raw.charAt(i);
+ if (parseState == 0 && char == " ") {
+ if (curStr != "") args.push(curStr);
+ curStr = "";
+ } else if (parseState == 0 && char == "\"") {
+ parseState = 1;
+ } else if (parseState == 1 && char == "\"") {
+ parseState = 0;
+ } else if (parseState == 0 && char == "'") {
+ parseState = 2;
+ } else if (parseState == 2 && char == "'") {
+ parseState = 0;
+ } else {
+ curStr += char;
+ }
+
+ i++;
+ if (i > raw.length-1) {
+ if (curStr != "") args.push(curStr);
+ break;
+ }
+ }
+ return args;
+ }
+}
+
+class FuncCommand extends BaseCommand {
+ public var func:Array->Void;
+ override public function new (name:String, argsDesc:String, desc:String, func:Array->Void, autoRegister:Bool = true) {
+ super(name, argsDesc, desc, autoRegister);
+ this.func = func;
+ }
+ override public function execute(raw:String) {
+ func(BaseCommand.parseArgs(raw));
+ }
+}
+
+//auto removed on mod switch, intended for mod global scripts
+class ModFuncCommand extends FuncCommand {
+ override public function onRegister() {
+ Logs.infos("Registered modded command: " + name, LIGHTGRAY, "Console");
+ }
+ override public function onUnregister() {
+ Logs.infos("Unregistered modded command: " + name, LIGHTGRAY, "Console");
+ }
+}
diff --git a/source/funkin/backend/system/console/ConsoleCommandManager.hx b/source/funkin/backend/system/console/ConsoleCommandManager.hx
new file mode 100644
index 0000000000..302ec27d96
--- /dev/null
+++ b/source/funkin/backend/system/console/ConsoleCommandManager.hx
@@ -0,0 +1,63 @@
+package funkin.backend.system.console;
+import funkin.backend.system.Logs;
+import funkin.backend.system.console.ConsoleCommand;
+
+class ConsoleCommandManager {
+
+ public static var commands:Map = [];
+ public static var commandsStringList:Array = [];
+
+ public static function registerCommand(cmd:BaseCommand) {
+ if (commands.exists(cmd._nameLower)) {
+ Logs.warn("Trying to register console command that already exists! (" + cmd.name + ")");
+ return;
+ }
+ cmd.onRegister();
+ commands.set(cmd._nameLower, cmd);
+ commandsStringList.push(cmd._nameLower);
+ }
+
+ public static function unregisterCommand(cmd:BaseCommand) {
+ if (!commands.exists(cmd._nameLower)) {
+ Logs.warn("Trying to unregister console command that isn't registered! (" + cmd.name + ")");
+ return;
+ }
+ cmd.onUnregister();
+ commandsStringList.remove(cmd._nameLower);
+ commands.remove(cmd._nameLower);
+ }
+
+ public static function getCommand(name:String) {
+ if (commands.exists(name.toLowerCase())) {
+ return commands.get(name.toLowerCase());
+ }
+ return null;
+ }
+
+ public static function tryExecute(str:String) {
+ var strLower = str.toLowerCase();
+ var targetCmd = strLower;
+ var rawArgs = "";
+ if (strLower.indexOf(" ") != -1) {
+ targetCmd = strLower.substring(0, strLower.indexOf(" "));
+ rawArgs = str.substring(str.indexOf(" ")+1, str.length);
+ }
+
+ if (ConsoleCommandManager.commands.exists(targetCmd)) {
+ Logs.infos("Executing command: " + str, LIGHTGRAY, "Console");
+ ConsoleCommandManager.commands.get(targetCmd).execute(rawArgs);
+ return true;
+ }
+ return false;
+ }
+
+ public static function unregisterModdedCommands() {
+ var commandsToRemove:Array = [];
+ for (name => cmd in commands) {
+ if (cmd is ModFuncCommand) {
+ unregisterCommand(cmd);
+ }
+ }
+ }
+}
+
diff --git a/source/funkin/backend/system/console/ConsoleHscript.hx b/source/funkin/backend/system/console/ConsoleHscript.hx
new file mode 100644
index 0000000000..d96823b4c3
--- /dev/null
+++ b/source/funkin/backend/system/console/ConsoleHscript.hx
@@ -0,0 +1,281 @@
+package funkin.backend.system.console;
+import haxe.io.Path;
+import funkin.backend.scripting.HScript;
+import funkin.backend.scripting.ScriptPack;
+import funkin.backend.scripting.Script;
+
+import hscript.*;
+import hscript.Expr.Error;
+import hscript.Parser;
+import hscript.CustomClass;
+
+typedef ConsoleHscriptField = {
+ var name:String;
+ var type:String;
+ var isStatic:Bool;
+ var isScriptVar:Bool;
+ var value:String;
+ var ?autoComplete:String;
+ var ?extraDesc:String;
+};
+
+class ConsoleHscript {
+ //mods can add their own imports if needed
+ public static var extraImports:Map = [
+ "Script" => Script
+ ];
+
+ private static final scriptVariableBlackList:Array = ["__script__", "importScript", "true", "false", "null", "disableScript", "trace"];
+ private static final customClassVariableBlacklist:Array = ["hset", "hget"];
+
+ public var interp:Interp;
+ public var parser:Parser;
+ public var expr:Expr;
+ public var variableFields:Array = [];
+
+ public function new() {
+ parser = new Parser();
+ parser.allowJSON = parser.allowMetadata = parser.allowTypes = true;
+
+ interp = new Interp();
+ interp.importEnabled = false;
+
+ resetVariables();
+ FlxG.signals.postStateSwitch.add(function() {
+ resetVariables();
+ });
+ }
+
+ private function resetVariables() {
+ @:privateAccess
+ interp.resetVariables();
+ variableFields = [];
+ var defaults = Script.getDefaultVariables(null);
+ for(k=>e in defaults) {
+ set(k, e);
+ }
+ for (k=>e in extraImports) {
+ set(k, e);
+ }
+
+ var scriptPacks:Array = [];
+ var scriptPackPaths:Array = [];
+ if (FlxG.state is MusicBeatState) {
+ var state:MusicBeatState = cast FlxG.state;
+ scriptPacks.push(state.stateScripts);
+ scriptPackPaths.push("stateScripts");
+ set("stateScripts", state.stateScripts);
+ if (PlayState.instance != null) {
+ scriptPacks.push(PlayState.instance.scripts);
+ scriptPackPaths.push("scripts");
+ set("scripts", PlayState.instance.scripts);
+ }
+ }
+
+ for (k=>e in Script.staticVariables) {
+ variableFields.push({
+ name: k,
+ type: getFieldTypeName(e),
+ isStatic: false,
+ isScriptVar: true,
+ value: getFieldTypeValue(e),
+ autoComplete: "Script.staticVariables[\"" + k + "\"]",
+ extraDesc: "Static"
+ });
+ }
+
+ for (i => s in scriptPacks) {
+ for (k=>e in s.publicVariables) {
+ variableFields.push({
+ name: k,
+ type: getFieldTypeName(e),
+ isStatic: false,
+ isScriptVar: true,
+ value: getFieldTypeValue(e),
+ autoComplete: scriptPackPaths[i] + ".publicVariables[\"" + k + "\"]",
+ extraDesc: "Public, " + scriptPackPaths[i]
+ });
+ }
+
+ for (scriptIndex => script in s.scripts) {
+ if (script is HScript) {
+ var hscript:HScript = cast script;
+ var name = "_"+Path.withoutExtension(script.fileName);
+ while(interp.varExists(name)) name += "0"; //hopefully prevent duplicates
+ name = name.replace(" ", "_");
+ name = name.replace(".", "_");
+ set(name, hscript.interp.variables);
+ for (k=>e in hscript.interp.variables) {
+ if (!defaults.exists(k) && !(e is Class) && !scriptVariableBlackList.contains(k)) {
+ variableFields.push({
+ name: k,
+ type: getFieldTypeName(e),
+ isStatic: false,
+ isScriptVar: true,
+ value: getFieldTypeValue(e),
+ autoComplete: name + "[\"" + k + "\"]",
+ extraDesc: "Local, " + script.fileName
+ });
+ }
+ }
+ }
+
+ }
+ }
+ }
+
+ public function set(val:String, value:Dynamic) {
+ interp.variables.set(val, value);
+ variableFields.push({
+ name: val,
+ type: value is Class ? Type.getClassName(value) : getFieldTypeName(value),
+ isStatic: (value is Class),
+ isScriptVar: false,
+ value: ""
+ });
+ }
+
+ public function prepareString(str:String) {
+ if (str.endsWith(";")) str = str.substr(0, -1);
+ return str;
+ }
+ public function getObj(str:String) {
+ var obj:Dynamic = null;
+ try {
+ obj = interp.expr(parser.parseString(str, "console"));
+ } catch(e:Error) {
+ //ignore errors while getting fields
+ } catch(e) {
+
+ }
+ return obj;
+ }
+
+ public function getFieldTypeName(obj:Dynamic) {
+ switch(Type.typeof(obj)) {
+ case TBool:
+ return "Bool";
+ case TInt:
+ return "Int";
+ case TFloat:
+ return "Float";
+ case TFunction:
+ return "Function";
+ case TObject:
+ return "Dynamic";
+ case TEnum(c):
+ return Type.getEnumName(c);
+ case TClass(c):
+ return Type.getClassName(c);
+ default:
+ return Type.getClassName(Type.getClass(obj));
+ }
+ return "Unknown";
+ }
+
+ public function getFieldTypeValue(obj:Dynamic) {
+ switch(Type.typeof(obj)) {
+ case TBool:
+ return Std.string(obj);
+ case TInt:
+ return Std.string(obj);
+ case TFloat:
+ return Std.string(obj);
+ case TFunction:
+ return "";
+ case TObject:
+ return "";
+ case TEnum(c):
+ return Std.string(obj);
+ case TClass(c):
+ if (getFieldTypeName(obj) == "Array" && obj != null) {
+ return "Length: " + obj.length + (obj.length > 0 ? ", " + getFieldTypeName(obj[0]) : ""); //guess the type based on first element
+ } else if (getFieldTypeName(obj) == "String") {
+ return "\"" + Std.string(obj) + "\"";
+ } else if (Reflect.hasField(obj, "toString")) {
+ return Std.string(obj);
+ }
+ default:
+ return "";
+ }
+ return "";
+ }
+
+ public function tryGetFields(str:String) {
+ var obj:Dynamic = getObj(prepareString(str));
+ if (obj == null) return [];
+
+ var returnFields:Array = [];
+ var fields:Array = [];
+ var isStatic = (obj is Class);
+ var isEnum = (obj is Enum);
+ var isObject = Reflect.isObject(obj);
+ var hasClass = Type.getClass(obj) != null;
+
+ if (isStatic) {
+ fields = Type.getClassFields(obj);
+ } else if (isEnum) {
+ fields = Type.getEnumConstructs(obj);
+ } else if (obj is CustomClass) {
+ var customClass:CustomClass = cast obj;
+ @:privateAccess
+ for (f in customClass.getSuperFields()) {
+ if (!f.startsWith("_HX_SUPER__") && !customClassVariableBlacklist.contains(f)) {
+ fields.push(f);
+ }
+ }
+ } else if (hasClass) {
+ fields = Type.getInstanceFields(Type.getClass(obj));
+ } else if (isObject) { //usually for dynamic structs
+ fields = Reflect.fields(obj);
+ }
+
+ var filteredFields:Array = [];
+ for (field in fields) {
+ if (field.startsWith("get_") || field.startsWith("set_")) {
+ var name = field.substr(4);
+ if (!fields.contains(name) && !filteredFields.contains(name))
+ filteredFields.push(name);
+ }
+ else {
+ filteredFields.push(field);
+ }
+ }
+
+ for (field in filteredFields) {
+ var fieldObj = getObj(prepareString(str) + "." + field);
+ if (fieldObj != null) {
+ returnFields.push({
+ name: field,
+ type: getFieldTypeName(fieldObj),
+ isStatic: isStatic,
+ isScriptVar: false,
+ value: getFieldTypeValue(fieldObj)
+ });
+ } else {
+ returnFields.push({
+ name: field,
+ type: "Unknown",
+ isStatic: isStatic,
+ isScriptVar: false,
+ value: ""
+ });
+ }
+ }
+ return returnFields;
+ }
+
+ public function tryExecute(cmd:String) {
+ cmd = cmd.trim();
+ if (!cmd.endsWith(";")) cmd += ";";
+ var ret = null;
+ try {
+ ret = interp.execute(parser.parseString(cmd, "console"));
+ } catch(e:Error) {
+ ret = e.toString();
+ } catch(e) {
+ ret = e.toString();
+ }
+ return ret;
+ }
+}
\ No newline at end of file
diff --git a/source/funkin/backend/system/console/ConsoleUI.hx b/source/funkin/backend/system/console/ConsoleUI.hx
new file mode 100644
index 0000000000..abae22c11e
--- /dev/null
+++ b/source/funkin/backend/system/console/ConsoleUI.hx
@@ -0,0 +1,699 @@
+package funkin.backend.system.console;
+
+#if IMGUI_ENABLED
+import lime.tools.imgui.ImGui;
+import lime.tools.imgui.ImGuiIO;
+import lime.tools.imgui.ImGuiStyle;
+import lime.tools.imgui.ImGuiHandler;
+import lime.tools.imgui.ImGuiFlags;
+import lime.tools.imgui.ImGuiTypes;
+import lime.tools.imgui.ImGuiPtr;
+import lime.tools.imgui.ImGuiInputTextCallbackData;
+#end
+import openfl.Lib;
+import funkin.backend.system.Logs;
+import funkin.backend.utils.NativeAPI.ConsoleColor;
+import funkin.backend.system.console.inspector.ConsoleInspector;
+
+using funkin.backend.utils.ImGuiUtil;
+
+typedef ConsoleSearchData = {
+ var name:String;
+ var desc:String;
+ var ?argsDesc:String;
+ var searchIndex:Int;
+ var isCommand:Bool;
+ var ?autoComplete:String;
+}
+
+typedef ConsoleLogData = {
+ var log:Array;
+ var times:Int;
+}
+
+class ConsoleUI {
+
+ static final CONSOLE_BG_COLOR = 0xFF380051;
+ static final SEARCH_HIGHLIGHT_COLOR = 0xFF00FF00;
+ static final SEARCH_SELECTED_COLOR = 0xFFE2D302;
+ static final SEARCH_DESC_COLOR = 0xFF636363;
+ static final SEARCH_ARGS_COLOR = 0xFF58804F;
+
+ static final CONSOLE_MAX_OUPUT = 100;
+ static final SEARCH_MAX_OUTPUT = 25;
+
+ public static var instance(default, null):ConsoleUI;
+
+ private var active:Bool = false;
+ private var inspectorActive:Bool = false;
+ #if IMGUI_ENABLED
+ private var style:ImGuiStyle;
+ #end
+ private var consoleHscript:ConsoleHscript;
+ private var consoleInspector:ConsoleInspector;
+
+ var commandSearch:Array = [];
+ var prevCommands:Array = [];
+ var forceFocusTextInput = false;
+ var cyclingCommands = false;
+ var cyclingPrevCommands = false;
+ var cycleIndex = 0;
+
+ #if IMGUI_ENABLED
+ var consoleInputString:ImGuiStringPtr = new ImGuiStringPtr("");
+ var consoleInputTextCallback:ImGuiInputTextCallback;
+ #end
+ var consoleOutput:Array = [];
+ var consoleOutputCurrentIndex:Int = 0;
+ var wrapConsoleOutput = false;
+ var autoScrollNextFrame = false;
+
+
+ #if IMGUI_ENABLED
+ var settingsOpen:ImGuiBoolPtr = new ImGuiBoolPtr(false);
+
+ var timeFilter:ImGuiBoolPtr = new ImGuiBoolPtr(true);
+ var typeFilter:ImGuiBoolPtr = new ImGuiBoolPtr(true);
+
+ var infoFilter:ImGuiBoolPtr = new ImGuiBoolPtr(true);
+ var warningFilter:ImGuiBoolPtr = new ImGuiBoolPtr(true);
+ var errorFilter:ImGuiBoolPtr = new ImGuiBoolPtr(true);
+ var traceFilter:ImGuiBoolPtr = new ImGuiBoolPtr(true);
+ var verboseFilter:ImGuiBoolPtr = new ImGuiBoolPtr(true);
+
+ var commandsFilter:ImGuiBoolPtr = new ImGuiBoolPtr(true);
+ var hscriptClassFilter:ImGuiBoolPtr = new ImGuiBoolPtr(true);
+ var hscriptFunctionFilter:ImGuiBoolPtr = new ImGuiBoolPtr(true);
+ var hscriptBasicTypesFilter:ImGuiBoolPtr = new ImGuiBoolPtr(true);
+ var hscriptObjectsFilter:ImGuiBoolPtr = new ImGuiBoolPtr(true);
+ var hscriptScriptsFilter:ImGuiBoolPtr = new ImGuiBoolPtr(true);
+
+ var countDuplicatedOutput:ImGuiBoolPtr = new ImGuiBoolPtr(false);
+
+ function saveSettings() {
+ Options.consoleTimeFilter = timeFilter.value;
+ Options.consoleTypeFilter = typeFilter.value;
+ Options.consoleInfoFilter = infoFilter.value;
+ Options.consoleWarningFilter = warningFilter.value;
+ Options.consoleErrorFilter = errorFilter.value;
+ Options.consoleTraceFilter = traceFilter.value;
+ Options.consoleVerboseFilter = verboseFilter.value;
+ Options.consoleCommandsFilter = commandsFilter.value;
+ Options.consoleClassFilter = hscriptClassFilter.value;
+ Options.consoleFunctionFilter = hscriptFunctionFilter.value;
+ Options.consoleBasicTypesFilter = hscriptBasicTypesFilter.value;
+ Options.consoleObjectsFilter = hscriptObjectsFilter.value;
+ Options.consoleScriptsFilter = hscriptScriptsFilter.value;
+ Options.consoleCountDuplicatedOutput = countDuplicatedOutput.value;
+ }
+ function loadSettings() {
+ //console is created before options are loaded
+ timeFilter.value = Options.consoleTimeFilter;
+ typeFilter.value = Options.consoleTypeFilter;
+ infoFilter.value = Options.consoleInfoFilter;
+ warningFilter.value = Options.consoleWarningFilter;
+ errorFilter.value = Options.consoleErrorFilter;
+ traceFilter.value = Options.consoleTraceFilter;
+ verboseFilter.value = Options.consoleVerboseFilter;
+ commandsFilter.value = Options.consoleCommandsFilter;
+ hscriptClassFilter.value = Options.consoleClassFilter;
+ hscriptFunctionFilter.value = Options.consoleFunctionFilter;
+ hscriptBasicTypesFilter.value = Options.consoleBasicTypesFilter;
+ hscriptObjectsFilter.value = Options.consoleObjectsFilter;
+ hscriptScriptsFilter.value = Options.consoleScriptsFilter;
+ countDuplicatedOutput.value = false; //Options.consoleCountDuplicatedOutput; //temp disable
+ }
+ #end
+
+ public static function init() {
+ ConsoleUI.instance = new ConsoleUI();
+ #if IMGUI_ENABLED
+ ImGuiHandler.instance.addCallback(ConsoleUI.instance.displayUI);
+ #end
+ }
+
+ public function new() {
+ #if IMGUI_ENABLED
+ style = ImGui.getStyle();
+ consoleInputTextCallback = new ImGuiInputTextCallback(onInputTextCallback);
+ #end
+
+ for (i in 0...CONSOLE_MAX_OUPUT) {
+ consoleOutput.push({log: [], times: 0});
+ }
+ };
+
+ private function addToConsole(text:Array) {
+ #if IMGUI_ENABLED
+ //check for repeats
+ var min = consoleOutputCurrentIndex-10;
+ if (min < 0) min = 0;
+ if (countDuplicatedOutput.value) //currently disabled
+ {
+ for (i in min...consoleOutputCurrentIndex) {
+ var matching = true;
+ if (text.length > 4 && text.length == consoleOutput[i].log.length) {
+ for (textIndex in 3...consoleOutput[i].log.length) {
+ if (text[textIndex].text != consoleOutput[i].log[textIndex].text) matching = false;
+ }
+ } else {
+ matching = false;
+ }
+ if (matching) {
+ consoleOutput[i].log = text;
+ consoleOutput[i].times++;
+ if (i != consoleOutputCurrentIndex-1) { //TODO: this should push to bottom instead of swapping since it doesnt always work
+ var temp = consoleOutput[consoleOutputCurrentIndex-1];
+ consoleOutput[consoleOutputCurrentIndex-1] = consoleOutput[i];
+ consoleOutput[i] = temp;
+ }
+ return;
+ }
+ }
+ }
+ addToConsoleOutput(text);
+ autoScrollNextFrame = true;
+ #end
+ }
+
+ private inline function addToConsoleOutput(text:Array) {
+ consoleOutput[consoleOutputCurrentIndex] = {log: text, times: 1};
+ consoleOutputCurrentIndex++;
+
+ if (consoleOutputCurrentIndex >= CONSOLE_MAX_OUPUT) { //wrap around like a ring buffer
+ consoleOutputCurrentIndex = 0;
+ wrapConsoleOutput = true;
+ }
+ }
+
+ public function toggleUI() {
+ #if IMGUI_ENABLED
+ active = !active;
+
+ if (active) {
+ loadSettings();
+ forceFocusTextInput = true;
+ if (consoleHscript == null) consoleHscript = new ConsoleHscript();
+ } else {
+ ImGui.setKeyboardFocusHere(0);
+ }
+ #end
+ }
+
+ public function toggleInspector() {
+ #if IMGUI_ENABLED
+ inspectorActive = !inspectorActive;
+ if (inspectorActive) {
+ if (consoleHscript == null) consoleHscript = new ConsoleHscript();
+ if (consoleInspector == null) consoleInspector = new ConsoleInspector(consoleHscript);
+ }
+ #end
+ }
+
+ public function displayUI() {
+ #if IMGUI_ENABLED
+ if (!Options.devMode) {
+ if (active) toggleUI();
+ if (inspectorActive) toggleInspector();
+ return;
+ }
+
+ var toggled:Bool = false;
+ for (key in Options.SOLO_DEV_CONSOLE) {
+ if (ImGui.isKeyPressed(key.toImGuiKey(), false)) toggled = true;
+ }
+ if (toggled) toggleUI();
+
+ var toggledInspector:Bool = false;
+ for (key in Options.SOLO_DEV_INSPECTOR) {
+ if (ImGui.isKeyPressed(key.toImGuiKey(), false)) toggledInspector = true;
+ }
+ if (toggledInspector) toggleInspector();
+ if (inspectorActive) consoleInspector.displayUI();
+
+ if (!active) return;
+
+ if (settingsOpen.value) {
+ ImGui.setNextWindowPos(ImGuiUtil.getWindowSpaceX() + Lib.application.window.width - 240, ImGuiUtil.getWindowSpaceY(), ImGuiCond.FirstUseEver);
+ ImGui.setNextWindowSize(240, 405, ImGuiCond.FirstUseEver);
+ if (ImGui.begin("Console Settings", settingsOpen, 0)) {
+ //ImGui.checkbox("Count duplicated output##Console Settings", countDuplicatedOutput); ImGui.setItemTooltip("Toggles if duplicated logs add a counter instead of displaying again");
+ ImGui.separatorText("Log Filter:");
+ ImGui.checkbox("Time##Console Settings", timeFilter); ImGui.setItemTooltip("Toggles if timestamp shows in each log");
+ ImGui.checkbox("Type##Console Settings", typeFilter); ImGui.setItemTooltip("Toggles if the type shows in each log");
+ ImGui.separator();
+ ImGui.checkbox("Info##Console Settings", infoFilter); ImGui.setItemTooltip("Toggles if info logs show");
+ ImGui.checkbox("Warning##Console Settings", warningFilter); ImGui.setItemTooltip("Toggles if warning logs show");
+ ImGui.checkbox("Error##Console Settings", errorFilter); ImGui.setItemTooltip("Toggles if error logs show");
+ ImGui.checkbox("Trace##Console Settings", traceFilter); ImGui.setItemTooltip("Toggles if trace logs show");
+ ImGui.checkbox("Verbose##Console Settings", verboseFilter); ImGui.setItemTooltip("Toggles if verbose logs show");
+ ImGui.separatorText("Search Filter:");
+ ImGui.checkbox("Commands##Console Settings", commandsFilter); ImGui.setItemTooltip("Toggles if commands show in the autofill");
+ ImGui.checkbox("Classes##Console Settings", hscriptClassFilter); ImGui.setItemTooltip("Toggles if classes show in the autofill");
+ ImGui.checkbox("Functions##Console Settings", hscriptFunctionFilter); ImGui.setItemTooltip("Toggles if functions show in the autofill");
+ ImGui.checkbox("Basic Types##Console Settings", hscriptBasicTypesFilter); ImGui.setItemTooltip("Toggles if basic types (Int, Float, String) show in the autofill");
+ ImGui.checkbox("Objects##Console Settings", hscriptObjectsFilter); ImGui.setItemTooltip("Toggles if objects show in the autofill");
+ ImGui.checkbox("Script Variables##Console Settings", hscriptScriptsFilter); ImGui.setItemTooltip("Toggles if script variables show in the autofill");
+ }
+ ImGui.end();
+ saveSettings();
+ }
+
+ ImGui.setNextWindowPos(ImGuiUtil.getWindowSpaceX(), ImGuiUtil.getWindowSpaceY() + (Lib.application.window.height-250), ImGuiCond.FirstUseEver);
+ ImGui.setNextWindowSize(Lib.application.window.width, 250, ImGuiCond.FirstUseEver);
+ ImGui.setNextWindowDockID(0, ImGuiCond.FirstUseEver);
+ if (ImGui.begin("Console", null, ImGuiWindowFlags.NoScrollbar)) {
+ displayOutput();
+ ImGui.separator();
+ displayInput();
+ }
+ ImGui.end();
+ #end
+ }
+
+ #if IMGUI_ENABLED
+ private function displayOutput() {
+ var windowWidth = ImGui.getWindowWidth();
+ var windowHeight = ImGui.getWindowHeight();
+ var outputHeight = windowHeight - (40 * style.fontScaleDpi); //I think scaling the dpi should be good enough? //TODO: should probably look at this again
+ //var outputHeight = windowHeight - (ImGui.getFrameHeightWithSpacing()*2);
+ //if (ImGui.isWindowDocked()) windowHeight -= ImGui.getFrameHeightWithSpacing();
+ if (outputHeight > 0) {
+ ImGui.pushTextWrapPos();
+ if (ImGui.beginChild("Console output", windowWidth, outputHeight)) {
+ if (wrapConsoleOutput) {
+ outputLog(consoleOutputCurrentIndex, CONSOLE_MAX_OUPUT);
+ }
+ outputLog(0, consoleOutputCurrentIndex);
+ }
+ if (autoScrollNextFrame) {
+ ImGui.setScrollHereY(1.0);
+ autoScrollNextFrame = false;
+ }
+ ImGui.endChild();
+ ImGui.popTextWrapPos();
+ }
+ }
+
+ private inline function outputLog(start:Int, end:Int) {
+ var indicesToIgnore:Array = [];
+ if (!timeFilter.value && !typeFilter.value) indicesToIgnore = [0, 1, 2, 3, 4];
+ else if (!timeFilter.value) indicesToIgnore = [1, 2];
+ else if (!typeFilter.value) indicesToIgnore = [2, 3];
+
+ var i = start;
+ while (i < end) {
+ if (consoleOutput[i].log.length > 0) {
+ var type = consoleOutput[i].log[0].level;
+ switch(type) {
+ case INFO:
+ if (!infoFilter.value) { i++; continue; }
+ case WARNING:
+ if (!warningFilter.value) { i++; continue; }
+ case ERROR:
+ if (!errorFilter.value) { i++; continue; }
+ case TRACE:
+ if (!traceFilter.value) { i++; continue; }
+ case VERBOSE:
+ if (!verboseFilter.value) { i++; continue; }
+ default:
+
+ }
+ }
+
+ for (textIndex => t in consoleOutput[i].log) {
+ if (indicesToIgnore.contains(textIndex)) continue;
+ if (!timeFilter.value && textIndex == 0) { //fix padding
+ ImGui.sameLine(0, 0);
+ ImGui.pushStyleColor(ImGuiCol.Text, consoleColorToImColor(t.color));
+ ImGui.textUnformatted("[");
+ ImGui.popStyleColor();
+ continue;
+ } else if (!typeFilter.value && textIndex == 4) {
+ ImGui.sameLine(0, 0);
+ ImGui.pushStyleColor(ImGuiCol.Text, consoleColorToImColor(t.color));
+ ImGui.textUnformatted(" ] ");
+ ImGui.popStyleColor();
+ continue;
+ }
+
+ ImGui.sameLine(0, 0);
+ ImGui.pushStyleColor(ImGuiCol.Text, consoleColorToImColor(t.color));
+ var lines = t.text.split("\n");
+ for (i => l in lines) {
+ ImGui.textUnformatted(l);
+ }
+ ImGui.popStyleColor();
+ }
+
+ if (countDuplicatedOutput.value && consoleOutput[i].times > 1) {
+ ImGui.sameLine(0, 0);
+ ImGui.textUnformatted(" ("+consoleOutput[i].times +"x)");
+ }
+
+ ImGui.newLine();
+ i++;
+ }
+ }
+
+ private function displayInput() {
+ var inputTextCursorPos = ImGui.getCursorScreenPos();
+ var currentInputString = consoleInputString.value;
+ final flags = ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.CallbackCompletion | ImGuiInputTextFlags.CallbackHistory | ImGuiInputTextFlags.CallbackEdit | ImGuiInputTextFlags.CallbackAlways;
+ if (ImGui.inputText("Input##Console", consoleInputString, flags | (forceFocusTextInput ? ImGuiInputTextFlags.ReadOnly : 0), consoleInputTextCallback)) {
+ if (consoleInputString.value != "") {
+ tryExecuteCommand(consoleInputString.value);
+ if (prevCommands[0] != consoleInputString.value) prevCommands.insert(0, consoleInputString.value);
+ consoleInputString.value = "";
+ ImGui.setKeyboardFocusHere(-1);
+ autoScrollNextFrame = true;
+ cyclingCommands = false;
+ cyclingPrevCommands = false;
+ commandSearch = [];
+ }
+ }
+ if (forceFocusTextInput) {
+ ImGui.setKeyboardFocusHere(-1);
+ forceFocusTextInput = false;
+ }
+
+ if (consoleInputString.value != currentInputString) {
+ if ((cyclingCommands && getConsoleInputStringForSearch(commandSearch[cycleIndex]) == getCommandSearchAutoComplete(commandSearch[cycleIndex])) || (cyclingPrevCommands && consoleInputString.value == prevCommands[cycleIndex])) {
+
+ } else {
+ searchCommands(consoleInputString.value, consoleInputString.value.length < currentInputString.length || currentInputString.length == 0);
+ }
+ }
+ ImGui.setScrollHereY(1.0);
+
+ ImGui.sameLine();
+ var settingButtonWidth = ImGui.calcTextSize("Settings").x + style.framePaddingX * 2;
+ var pos = ImGui.getCursorPos();
+ ImGui.setCursorPos(pos.x + ImGui.getContentRegionAvail().x - settingButtonWidth, pos.y);
+ if (ImGui.button("Settings")) {
+ settingsOpen.value = !settingsOpen.value;
+ }
+
+ if (commandSearch.length > 0) {
+ var targetIndex = cyclingCommands ? cycleIndex : commandSearch.length;
+ var start = targetIndex - (SEARCH_MAX_OUTPUT-1);
+ var end = targetIndex+1;
+ if (start < 0) start = 0;
+ if (end < SEARCH_MAX_OUTPUT) end = SEARCH_MAX_OUTPUT;
+ if (end > commandSearch.length) end = commandSearch.length;
+
+ ImGui.pushStyleColor(ImGuiCol.WindowBg, CONSOLE_BG_COLOR);
+ ImGui.setNextWindowPos(inputTextCursorPos.x, inputTextCursorPos.y - (ImGui.getTextLineHeightWithSpacing() * (end-start))-12);
+ if (ImGui.begin("Command Search window", null, ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoFocusOnAppearing)) {
+ for (i in start...end) {
+ var cmd = commandSearch[i];
+ if (!cyclingCommands) {
+ var cmdName:String = cmd.name;
+ var inputName:String = getConsoleInputStringForSearch(cmd);
+ var before:String = cmdName.substring(0, cmd.searchIndex);
+ var after:String = cmdName.substring(cmd.searchIndex+inputName.length, cmdName.length);
+
+ if (before != "") {
+ if (i == commandSearch.length-1) ImGui.pushStyleColor(ImGuiCol.Text, SEARCH_SELECTED_COLOR);
+ ImGui.textUnformatted(before);
+ ImGui.sameLine(0, 0);
+ if (i == commandSearch.length-1) ImGui.popStyleColor();
+ }
+ ImGui.pushStyleColor(ImGuiCol.Text, SEARCH_HIGHLIGHT_COLOR);
+ ImGui.textUnformatted(cmdName.substring(cmd.searchIndex, cmd.searchIndex+inputName.length));
+ ImGui.popStyleColor();
+ if (after != "") {
+ if (i == commandSearch.length-1) ImGui.pushStyleColor(ImGuiCol.Text, SEARCH_SELECTED_COLOR);
+ ImGui.sameLine(0, 0);
+ ImGui.textUnformatted(after);
+ if (i == commandSearch.length-1) ImGui.popStyleColor();
+ }
+
+ if (inputName.length == cmd.name.length && cmd.autoComplete != null) {
+ ImGui.sameLine();
+ ImGui.textUnformatted("(TAB to Autocomplete)");
+ }
+ } else {
+ if (i == cycleIndex) ImGui.pushStyleColor(ImGuiCol.Text, SEARCH_SELECTED_COLOR);
+ ImGui.textUnformatted(cmd.name);
+ if (i == cycleIndex) ImGui.popStyleColor();
+ }
+
+ if (cmd.argsDesc != null && cmd.argsDesc != "") {
+ ImGui.sameLine();
+ ImGui.pushStyleColor(ImGuiCol.Text, SEARCH_ARGS_COLOR);
+ ImGui.textUnformatted(cmd.argsDesc);
+ ImGui.popStyleColor();
+ }
+ if (cmd.desc != "") {
+ ImGui.sameLine();
+ ImGui.pushStyleColor(ImGuiCol.Text, SEARCH_DESC_COLOR);
+ ImGui.textUnformatted(cmd.desc);
+ ImGui.popStyleColor();
+ }
+ }
+ }
+ ImGui.popStyleColor();
+ ImGui.end();
+ }
+ }
+
+ var thingsThatMeanTheresANewThing = " +-*/=|();,"; //TODO: name this something better
+ var currentCursorPos = 0;
+ var currentHscriptVarStart = 0;
+ var currentHscriptVarEnd = 0;
+
+ private function searchCommands(str:String, fullSearch:Bool) {
+ cyclingCommands = false;
+ cyclingPrevCommands = false;
+ if (fullSearch) commandSearch = [];
+ if (str == "") return;
+
+ //for commands only, allow any case
+ var strLower = str.toLowerCase();
+ if (strLower.indexOf(" ") != -1) {
+ strLower = strLower.substring(0, strLower.indexOf(" "));
+ }
+
+ //figure out the current hscript variable where the cursor is
+ var pos:Int = currentCursorPos;
+ if (pos < 0) pos = 0;
+ if (pos > str.length-1) pos = str.length-1;
+ var begin:Int = 0;
+ var end:Int = str.length;
+ for (i in 0...thingsThatMeanTheresANewThing.length) {
+ var c = thingsThatMeanTheresANewThing.charAt(i);
+ var b = str.lastIndexOf(c, pos);
+ var e = str.indexOf(c, pos);
+ if (b != -1 && b+1 > begin+1) begin = b+1;
+ if (e != -1 && e < end) end = e;
+ }
+ var scriptVarStr = str.substring(begin, end);
+ currentHscriptVarStart = begin;
+ currentHscriptVarEnd = end;
+
+
+ if (!fullSearch) { //TODO: something better to restore search when pressing backspace
+ var prev = commandSearch;
+ commandSearch = [];
+ for (cmd in prev) {
+ cmd.searchIndex = cmd.isCommand ? cmd.name.toLowerCase().indexOf(strLower) : cmd.name.indexOf(scriptVarStr);
+ if (cmd.searchIndex != -1) {
+ commandSearch.push(cmd);
+ }
+ }
+ } else {
+ if (commandsFilter.value) for (cmdName in ConsoleCommandManager.commandsStringList) {
+ var searchIndex = cmdName.indexOf(strLower);
+ if (searchIndex != -1) {
+ var cmd = ConsoleCommandManager.commands.get(cmdName);
+ commandSearch.push({
+ name: cmd.name,
+ desc: cmd.desc,
+ argsDesc: cmd.argsDesc,
+ searchIndex: searchIndex,
+ isCommand: true
+ });
+ }
+ }
+ }
+
+ //root hscript variables/classes
+ if (fullSearch || scriptVarStr.length == 1) if (scriptVarStr.indexOf(".") == -1) {
+ for (f in consoleHscript.variableFields) {
+ if (!hscriptScriptsFilter.value && f.isScriptVar) continue;
+ if (f.isStatic) {
+ if (!hscriptClassFilter.value) continue;
+ } else if (f.type == "Function") {
+ if (!hscriptFunctionFilter.value) continue;
+ } else if (f.type == "Int" || f.type == "Float" || f.type == "String") {
+ if (!hscriptBasicTypesFilter.value) continue;
+ } else {
+ if (!hscriptObjectsFilter.value) continue;
+ }
+
+ var searchIndex = f.name.indexOf(scriptVarStr);
+ if (searchIndex != -1) {
+ commandSearch.push({
+ name: f.name,
+ desc: (f.isStatic ? "Class (" + f.type + ")" : f.type) + (f.value != "" ? " (" + f.value + ")" : "") + (f.extraDesc != null ? " (" + f.extraDesc + ")" : ""),
+ searchIndex: searchIndex,
+ isCommand: false,
+ autoComplete: f.autoComplete
+ });
+ }
+ }
+ }
+
+ //hscript var fields (after pressing .)
+ if (scriptVarStr.charAt(scriptVarStr.length-1) == ".") {
+ var fields = consoleHscript.tryGetFields(scriptVarStr.substr(0, -1));
+ for (f in fields) {
+ if (f.isStatic) {
+ if (!hscriptClassFilter.value) continue;
+ } else if (f.type == "Function") {
+ if (!hscriptFunctionFilter.value) continue;
+ } else if (f.type == "Int" || f.type == "Float" || f.type == "String") {
+ if (!hscriptBasicTypesFilter.value) continue;
+ } else {
+ if (!hscriptObjectsFilter.value) continue;
+ }
+ var full = scriptVarStr + f.name;
+ var searchIndex = full.indexOf(scriptVarStr);
+ if (searchIndex != -1) {
+ commandSearch.push({
+ name: full,
+ desc: f.type + (f.value != "" ? " (" + f.value + ")" : "") + (f.extraDesc != null ? " (" + f.extraDesc + ")" : ""),
+ searchIndex: searchIndex,
+ isCommand: false
+ });
+ }
+ }
+ }
+ commandSearch.sort(function(a, b) {
+ if (a.searchIndex < b.searchIndex) return 1;
+ else if (a.searchIndex > b.searchIndex) return -1;
+ else {
+ if (a.name.length < b.name.length) return 1;
+ else if (a.name.length > b.name.length) return -1;
+ else return 0;
+ }
+ });
+ }
+
+ private inline function getCommandSearchAutoComplete(cmd:ConsoleSearchData) {
+ return cmd.autoComplete != null ? cmd.autoComplete : cmd.name;
+ }
+ private inline function getConsoleInputStringForSearch(cmd:ConsoleSearchData) {
+ return cmd.isCommand ? consoleInputString.value : consoleInputString.value.substring(currentHscriptVarStart, currentHscriptVarEnd);
+ }
+
+ private function onInputTextCallback(data:ImGuiInputTextCallbackData) {
+
+ if (data.eventFlag == ImGuiInputTextFlags.CallbackHistory) { //when pressing up/down
+
+ var justStartedCycling = false;
+ if (commandSearch.length > 0 && !cyclingCommands && !cyclingPrevCommands) {
+ cyclingCommands = true;
+ cycleIndex = (data.eventKey == ImGuiKey.DownArrow) ? commandSearch.length-1 : 0;
+ justStartedCycling = true;
+ } else if (commandSearch.length == 0 && !cyclingPrevCommands && !cyclingCommands && consoleInputString.value == "" && prevCommands.length > 0 && data.eventKey == ImGuiKey.UpArrow) {
+ cyclingPrevCommands = true;
+ cycleIndex = 0;
+ justStartedCycling = true;
+ }
+
+ if (cyclingCommands) {
+ if (data.eventKey == ImGuiKey.UpArrow) {
+ cycleIndex--;
+ if (cycleIndex < 0) cycleIndex = commandSearch.length-1;
+ if (justStartedCycling && commandSearch.length > 1) cycleIndex = commandSearch.length-2; //feels better to go the next one above
+ } else {
+ cycleIndex++;
+ if (cycleIndex > commandSearch.length-1) cycleIndex = 0;
+ }
+
+ var autoComplete = getCommandSearchAutoComplete(commandSearch[cycleIndex]);
+ if (commandSearch[cycleIndex].isCommand) {
+ data.deleteChars(0, data.bufTextLen);
+ data.insertChars(0, autoComplete);
+ data.setSelection(data.bufTextLen, data.bufTextLen);
+ currentHscriptVarEnd = currentHscriptVarStart+autoComplete.length;
+ } else {
+ data.deleteChars(currentHscriptVarStart, currentHscriptVarEnd-currentHscriptVarStart);
+ data.insertChars(currentHscriptVarStart, autoComplete);
+ currentHscriptVarEnd = currentHscriptVarStart+autoComplete.length;
+ data.setSelection(currentHscriptVarEnd, currentHscriptVarEnd);
+ }
+ }
+ if (cyclingPrevCommands) {
+ if (data.eventKey == ImGuiKey.DownArrow) {
+ if (!justStartedCycling) cycleIndex--;
+ if (cycleIndex < 0) cycleIndex = 0;
+ } else {
+ if (!justStartedCycling) cycleIndex++;
+ if (cycleIndex > prevCommands.length-1) cycleIndex = prevCommands.length-1;
+ }
+ data.deleteChars(0, data.bufTextLen);
+ data.insertChars(0, prevCommands[cycleIndex]);
+ data.setSelection(data.bufTextLen, data.bufTextLen);
+ }
+ }
+ else if (data.eventFlag == ImGuiInputTextFlags.CallbackCompletion) { //when pressing tab
+ if (commandSearch.length > 0 && consoleInputString.value != getCommandSearchAutoComplete(commandSearch[commandSearch.length-1])) {
+ var autoComplete = getCommandSearchAutoComplete(commandSearch[commandSearch.length-1]);
+ if (commandSearch[commandSearch.length-1].isCommand) {
+ data.deleteChars(0, data.bufTextLen);
+ data.insertChars(0, autoComplete);
+ data.setSelection(data.bufTextLen, data.bufTextLen);
+ } else {
+ data.deleteChars(currentHscriptVarStart, currentHscriptVarEnd-currentHscriptVarStart);
+ data.insertChars(currentHscriptVarStart, autoComplete);
+ currentHscriptVarEnd = currentHscriptVarStart+autoComplete.length;
+ data.setSelection(currentHscriptVarEnd, currentHscriptVarEnd);
+ }
+ }
+ }
+ else if (data.eventFlag == ImGuiInputTextFlags.CallbackEdit) {
+ cyclingCommands = false;
+ cyclingPrevCommands = false;
+ } else if (data.eventFlag == ImGuiInputTextFlags.CallbackAlways) {
+ currentCursorPos = data.cursorPos;
+ }
+ }
+ #end
+
+ private function clearConsole() {
+ commandSearch = [];
+ consoleOutputCurrentIndex = 0;
+ wrapConsoleOutput = false;
+ }
+
+ private function tryExecuteCommand(str:String) {
+ if (!ConsoleCommandManager.tryExecute(str)) { //run hscript if there was no command
+ Logs.infos("Executing hscript: " + str, LIGHTGRAY, "Console");
+ Logs.infos(Std.string(consoleHscript.tryExecute(str)), LIGHTGRAY, "Console");
+ }
+ }
+
+ public static function consoleColorToImColor(color:ConsoleColor) {
+ return switch(color) {
+ case BLACK: 0xFF000000;
+ case DARKBLUE: 0xFF0909C3;
+ case DARKGREEN: 0xFF008800;
+ case DARKCYAN: 0xFF008888;
+ case DARKRED: 0xFF880000;
+ case DARKMAGENTA: 0xFF9A1CC5;
+ case DARKYELLOW: 0xFFBCBC00;
+ case LIGHTGRAY: 0xFFD4D4D4;
+ case GRAY: 0xFFA4A4A4;
+ case BLUE: 0xFF2A4AEA;
+ case GREEN: 0xFF00FF00;
+ case CYAN: 0xFF008CFF;
+ case RED: 0xFFFF0000;
+ case MAGENTA: 0xFFFF00FF;
+ case YELLOW: 0xFFFFFF00;
+ case WHITE | _: 0xFFFFFFFF;
+ }
+ }
+}
\ No newline at end of file
diff --git a/source/funkin/backend/system/console/inspector/ConsoleInspector.hx b/source/funkin/backend/system/console/inspector/ConsoleInspector.hx
new file mode 100644
index 0000000000..70c326d789
--- /dev/null
+++ b/source/funkin/backend/system/console/inspector/ConsoleInspector.hx
@@ -0,0 +1,350 @@
+package funkin.backend.system.console.inspector;
+
+//WIP
+
+import openfl.Lib;
+import flixel.FlxState;
+import flixel.group.FlxSpriteGroup;
+import funkin.backend.scripting.HScript;
+import funkin.backend.scripting.ScriptPack;
+import funkin.game.Stage;
+
+#if IMGUI_ENABLED
+import lime.tools.imgui.ImGuiFlags;
+import lime.tools.imgui.ImGuiTypes;
+import lime.tools.imgui.ImGuiPtr;
+#end
+#if foxlite
+import foxlite.FoxScene;
+import foxlite.group.FoxTypedGroup;
+import foxlite.group.FoxObjectGroup;
+import foxlite.FoxBasic;
+import foxlite.FoxObject;
+import foxlite.FoxModel;
+#end
+
+using funkin.backend.utils.ImGuiUtil;
+
+typedef InspectorObject = {
+ var obj:Dynamic;
+ var name:String;
+ var type:String;
+ var members:Array;
+ var memberIndex:Int;
+ var ?groupParent:InspectorObject;
+}
+
+class ConsoleInspector {
+
+ var hscript:ConsoleHscript;
+ var cachedInstanceFields:Map> = [];
+ public function new(hscript:ConsoleHscript) {
+ this.hscript = hscript;
+ }
+
+ #if IMGUI_ENABLED
+ var selectedObject:Dynamic = null;
+ var selectedObjectData:InspectorObject = null;
+ var selectedObjectValidThisFrame:Bool = false;
+ var justChangedObject:Bool = false;
+
+ var objectProperties:InspectorObjectProperties = new InspectorObjectProperties();
+ var gizmo:InspectorGizmo = new InspectorGizmo();
+ #if foxlite
+ var gizmo3D:InspectorGizmo3D = new InspectorGizmo3D();
+ #end
+
+ var currentStateObjects:Array = [];
+ var inspectorObjectsThatNeedUpdating:Array = [];
+
+ function updateObjects() {
+ var states:Array = [FlxG.state];
+ var stateToCheck:FlxState = FlxG.state;
+ while(stateToCheck.subState != null) {
+ states.push(stateToCheck.subState);
+ stateToCheck = stateToCheck.subState;
+ }
+
+ if (currentStateObjects.length > states.length) {
+ currentStateObjects.resize(states.length);
+ }
+ for (index => state in states) {
+ if (currentStateObjects[index] == null || currentStateObjects[index].obj != state) {
+
+ var packageName = hscript.getFieldTypeName(state);
+ if (!cachedInstanceFields.exists(packageName)) {
+ cachedInstanceFields.set(packageName, Type.getInstanceFields(Type.getClass(state)));
+ }
+ var packageSplit = packageName.split(".");
+ var stateName = packageSplit[packageSplit.length-1];
+
+ currentStateObjects[index] = {
+ obj: state,
+ name: stateName,
+ type: packageName,
+ memberIndex: index,
+ members: []
+ };
+ }
+ }
+
+ for (index => inspectorObject in currentStateObjects) {
+ updateObjectMembers(index, inspectorObject);
+ }
+ }
+
+ function updateObjectMembers(index:Int, inspectorObject:InspectorObject, fromGroup:Bool = false) {
+ var objMembers:Array = inspectorObject.obj.members;
+ #if foxlite
+ if (inspectorObject.obj is FoxScene) {
+ objMembers = inspectorObject.obj.foxGroup.members;
+ }
+ #end
+ if (objMembers == null) {
+ return;
+ }
+
+ //sort and remove if needed
+ var membersToRemove:Array = [];
+ for (member in inspectorObject.members) {
+ var currentIndex:Int = objMembers.indexOf(member.obj);
+ if (currentIndex == -1) {
+ membersToRemove.push(member);
+ }
+ member.memberIndex = currentIndex;
+ }
+ for (m in membersToRemove) inspectorObject.members.remove(m);
+ inspectorObject.members.sort(function(a, b) {
+ if(a.memberIndex < b.memberIndex) return -1;
+ else if(a.memberIndex > b.memberIndex) return 1;
+ else return 0;
+ });
+
+ //we have new members to add
+ if (inspectorObject.members.length != objMembers.length) {
+ var newList:Array = [];
+ var oldListIndex:Int = 0;
+ for (i in 0...objMembers.length) {
+ if (inspectorObject.members[oldListIndex] == null || inspectorObject.members[oldListIndex].memberIndex != i || inspectorObject.members[oldListIndex].obj != objMembers[i]) {
+ var member:Dynamic = objMembers[i];
+ var memberPackage = hscript.getFieldTypeName(objMembers[i]);
+ var memberPackageSplit = memberPackage.split(".");
+ var memberType = memberPackageSplit[memberPackageSplit.length-1];
+ var memberName:String = fromGroup ? inspectorObject.name + ".members[" + i + "]" : figureOutObjectName(inspectorObject.type, inspectorObject.obj, member);
+ var newObj = {
+ obj: objMembers[i],
+ name: memberName,
+ type: memberType,
+ memberIndex: i,
+ members: [],
+ groupParent: fromGroup ? inspectorObject : null
+ };
+ newList.push(newObj);
+ if (member is FlxTypedGroup || member is FlxTypedSpriteGroup #if foxlite || member is FoxScene || member is FoxTypedGroup || member is FoxObjectGroup #end) {
+ updateObjectMembers(i, newObj, true);
+ }
+ } else {
+ newList.push(inspectorObject.members[oldListIndex]);
+ oldListIndex++;
+ }
+ }
+ inspectorObject.members = newList;
+ }
+ }
+
+ public function displayUI() {
+
+ updateObjects();
+
+ FlxG.mouse.visible = true; //TODO: rework this, temp force on
+
+ ImGui.setNextWindowPos(ImGuiUtil.getWindowSpaceX(), ImGuiUtil.getWindowSpaceY(), ImGuiCond.FirstUseEver);
+ ImGui.setNextWindowSize(300, Lib.application.window.height, ImGuiCond.FirstUseEver);
+ if (ImGui.begin("Inspector")) {
+ ImGui.separatorText("Tools/Gizmo");
+ ImGui.indent();
+ if (ImGui.selectable("None (Q)", gizmo.gizmoMode == -1)) gizmo.gizmoMode = -1;
+ if (ImGui.selectable("Position (W)", gizmo.gizmoMode == 0)) gizmo.gizmoMode = 0;
+ if (ImGui.selectable("Rotation (E)", gizmo.gizmoMode == 1)) gizmo.gizmoMode = 1;
+ if (ImGui.selectable("Scale (R)", gizmo.gizmoMode == 2)) gizmo.gizmoMode = 2;
+ ImGui.unindent();
+ ImGui.separatorText("States");
+ for (index => member in currentStateObjects) {
+ var nodeID = member.name + index;
+ var flags = ImGuiTreeNodeFlags.DefaultOpen;
+ if (member.obj == selectedObject) flags |= ImGuiTreeNodeFlags.Selected;
+ if (ImGui.treeNodeEx(nodeID, flags, member.name + " (" + member.type + ")")) {
+ if (ImGui.isItemClicked()) {
+ selectObject(member.obj);
+ }
+ if (member.members.length > 0) {
+ generateTreeForMembers(nodeID, member);
+ }
+ ImGui.treePop();
+ }
+ }
+ //ImGui.separatorText("Cameras");
+ }
+ ImGui.end();
+
+ if (selectedObject != null) {
+ selectedObjectValidThisFrame = false;
+ for (member in currentStateObjects) {
+ if (member.obj == selectedObject) {
+ selectedObjectValidThisFrame = true;
+ selectedObjectData = member;
+ break;
+ }
+ checkForSelectedObjectThisFrame(member);
+ }
+
+ if (selectedObjectValidThisFrame) {
+ objectProperties.show(selectedObjectData, justChangedObject);
+ if (selectedObject is FlxObject) {
+ gizmo.show(selectedObjectData, cast selectedObject, justChangedObject);
+ } #if foxlite else if (selectedObject is FoxObject) {
+ gizmo3D.show(selectedObjectData, cast selectedObject, justChangedObject);
+ }
+ #end
+ justChangedObject = false;
+ } else {
+ selectedObject = null;
+ selectedObjectData = null;
+ }
+ }
+ }
+
+ function checkForSelectedObjectThisFrame(object:InspectorObject) {
+ if (selectedObjectValidThisFrame) return;
+ for (member in object.members) {
+ if (member.obj == selectedObject) {
+ selectedObjectValidThisFrame = true;
+ selectedObjectData = member;
+ return;
+ }
+ if (member.members.length > 0) {
+ checkForSelectedObjectThisFrame(member);
+ }
+ }
+ }
+
+ function generateTreeForMembers(id:String, object:InspectorObject) {
+ for (index => member in object.members) {
+ var valid = member.obj != null;
+ var nodeID = id + object.name + index;
+ var flags = ImGuiTreeNodeFlags.None;
+ if (member.members.length == 0) flags |= ImGuiTreeNodeFlags.Leaf;
+ if (valid && member.obj == selectedObject) flags |= ImGuiTreeNodeFlags.Selected;
+ if (ImGui.treeNodeEx(nodeID, flags, member.name + (valid ? " (" + member.type + ")" : ""))) {
+ if (valid && ImGui.isItemClicked()) {
+ selectObject(member.obj);
+ }
+ if (valid && member.members.length > 0) {
+ generateTreeForMembers(nodeID, member);
+ }
+ ImGui.treePop();
+ }
+ }
+ }
+
+ function selectObject(obj:Dynamic) {
+ if (selectedObject != obj) {
+ selectedObject = obj;
+ justChangedObject = true;
+ }
+ }
+
+ public function figureOutObjectName(packageName:String, parent:Dynamic, object:FlxBasic) {
+ if (object == null) return "Null Member";
+ if (!cachedInstanceFields.exists(packageName)) {
+ cachedInstanceFields.set(packageName, Type.getInstanceFields(Type.getClass(parent)));
+ }
+ var instanceFields:Array = cachedInstanceFields.get(packageName);
+ var uselessFields:Array = [];
+ for (field in instanceFields) {
+ if (field == "members") {
+ uselessFields.push(field);
+ continue;
+ }
+
+ var fieldObj:Dynamic = Reflect.getProperty(parent, field);
+ if (fieldObj != null) {
+ if (fieldObj is FlxBasic) {
+ if (object == fieldObj) {
+ return field;
+ } else if (fieldObj is Stage) {
+ var stage:Stage = cast fieldObj;
+ for (name => stageObj in stage.stageSprites) {
+ if (object == stageObj) return name;
+ }
+ for (name => posObj in stage.characterPoses) {
+ if (object == posObj) return name;
+ }
+ }
+ } else if (fieldObj is Array) {
+ var arr:Array = cast fieldObj;
+ var firstMember = arr[0];
+ if (firstMember != null && firstMember is FlxBasic) {
+ for (index => arrayObj in arr) {
+ if (object == arrayObj) {
+ return field + "[" + index + "]";
+ }
+ }
+ }
+ } else {
+ uselessFields.push(field);
+ }
+ }
+ }
+ if (uselessFields.length > 0) { //these aren't flxbasic
+ for (field in uselessFields) {
+ instanceFields.remove(field);
+ }
+ cachedInstanceFields.set(packageName, instanceFields);
+ }
+
+ var scriptPacksToCheck:Array = [];
+ if (parent is MusicBeatState) {
+ var state:MusicBeatState = cast parent;
+ scriptPacksToCheck.push(state.stateScripts);
+ }
+ if (parent is PlayState) {
+ var playstate:PlayState = cast parent;
+ for (strumLineIndex => strumLine in playstate.strumLines.members) {
+ for (charIndex => char in strumLine.characters) {
+ if (object == char) {
+ return "strumLines[" + strumLineIndex + "].characters[" + charIndex + "] (" + char.curCharacter + ")";
+ }
+ }
+ }
+ scriptPacksToCheck.push(playstate.scripts);
+ }
+
+ for (pack in scriptPacksToCheck) {
+
+ for (script in pack.scripts) {
+ if (script is HScript) {
+ var hscript:HScript = cast script;
+ for (name => scriptObj in hscript.interp.variables) {
+ if (scriptObj is FlxBasic) {
+ if (scriptObj == object) return name;
+ } else if (scriptObj is Array) {
+ var arr:Array = cast scriptObj;
+ var firstMember = arr[0];
+ if (firstMember != null && firstMember is FlxBasic) {
+ for (index => arrayObj in arr) {
+ if (object == arrayObj) {
+ return name + "[" + index + "]";
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return "Unknown" + object.ID;
+ }
+ #end
+}
\ No newline at end of file
diff --git a/source/funkin/backend/system/console/inspector/InspectorGizmo.hx b/source/funkin/backend/system/console/inspector/InspectorGizmo.hx
new file mode 100644
index 0000000000..2a2ce0fda7
--- /dev/null
+++ b/source/funkin/backend/system/console/inspector/InspectorGizmo.hx
@@ -0,0 +1,495 @@
+package funkin.backend.system.console.inspector;
+
+import flixel.math.FlxMatrix;
+import flixel.math.FlxPoint;
+import openfl.Lib;
+import funkin.backend.system.console.inspector.ConsoleInspector.InspectorObject;
+
+#if IMGUI_ENABLED
+import lime.tools.imgui.ImGuiFlags;
+import lime.tools.imgui.ImGuiTypes;
+import lime.tools.imgui.ImGuiPtr;
+#end
+
+class InspectorGizmo {
+
+ public var gizmoMode:Int = 0;
+ var lastViewportID:Int = 0;
+ var positionActive:Bool = false;
+ var positionX:Float = 0;
+ var positionY:Float = 0;
+ var rotationActive:Bool = false;
+ var rotationStartX:Float = 0;
+ var rotationStartY:Float = 0;
+ var rotationStartAngle:Float = 0;
+ var scaleActive:Bool = false;
+ var scaleX:Float = 0;
+ var scaleY:Float = 0;
+
+ var size:Float = 120;
+ var arrowWidth = 10;
+ var scaleBoxWidth = 15;
+
+ var moveSpeedPos:Float = 1;
+ var moveSpeedScale:Float = 0.05;
+
+ var snapPos:Float = 50;
+ var snapAngle:Float = 15;
+ var snapScale:Float = 0.25;
+
+ public function new() {}
+
+ #if IMGUI_ENABLED
+ inline function transformFlxPointToWindowSpace(point:FlxPoint) {
+ if ((ImGuiIO.configFlags & ImGuiConfigFlags.ViewportsEnable) != 0) {
+ point.x = (Lib.application.window.x + FlxG.scaleMode.offset.x) + (point.x * FlxG.scaleMode.scale.x);
+ point.y = (Lib.application.window.y + FlxG.scaleMode.offset.y) + (point.y * FlxG.scaleMode.scale.y);
+ } else {
+ point.x = (FlxG.scaleMode.offset.x) + (point.x * FlxG.scaleMode.scale.x);
+ point.y = (FlxG.scaleMode.offset.y) + (point.y * FlxG.scaleMode.scale.y);
+ }
+ }
+ inline function transformFlxPointOntoCamera(point:FlxPoint, camera:FlxCamera) {
+ point.subtract(camera.viewMarginLeft, camera.viewMarginTop);
+ point.x *= camera.zoom;
+ point.y *= camera.zoom;
+ }
+
+ function prepareObjectCamera(objectData:InspectorObject, basic:FlxBasic) {
+ var parentsList:Array = [];
+ var oldDefaultCamerasList:Array> = [];
+
+ var parent = objectData.groupParent;
+ while(parent != null) {
+ parentsList.insert(0, parent.obj);
+ parent = parent.groupParent;
+ }
+
+ @:privateAccess
+ for (p in parentsList) {
+ oldDefaultCamerasList.push(FlxCamera._defaultCameras);
+ var group:FlxBasic = cast p;
+ if (group._cameras != null) FlxCamera._defaultCameras = group._cameras;
+ }
+
+ var camera = basic.getDefaultCamera();
+
+ @:privateAccess
+ if (oldDefaultCamerasList.length > 0) FlxCamera._defaultCameras = oldDefaultCamerasList[0]; //no point looping back, just grab first
+
+ return camera;
+ }
+
+ public function show(objectData:InspectorObject, object:FlxObject, justChanged:Bool) {
+ var sprite:FlxSprite = cast object;
+ var drawList = ImGui.getBackgroundDrawList(ImGui.getMainViewport());
+
+ if (justChanged) {
+ positionActive = false;
+ rotationActive = false;
+ scaleActive = false;
+ }
+
+ var camera = prepareObjectCamera(objectData, object);
+ var bounds = object.getScreenPosition(null, camera);
+ var position = bounds.clone();
+ var origin = bounds.clone();
+ if (sprite != null) {
+ bounds.subtractPoint(sprite.offset);
+ origin.addPoint(sprite.origin);
+ }
+ transformFlxPointOntoCamera(bounds, camera);
+ transformFlxPointToWindowSpace(bounds);
+ transformFlxPointOntoCamera(position, camera);
+ transformFlxPointToWindowSpace(position);
+ transformFlxPointOntoCamera(origin, camera);
+ transformFlxPointToWindowSpace(origin);
+
+ if (sprite == null)
+ {
+ var x = bounds.x;
+ var y = bounds.y;
+ var right = x + (object.width * camera.zoom);
+ var bottom = y + (object.height * camera.zoom);
+ /*var minX = Lib.application.window.x + FlxG.scaleMode.offset.x;
+ var minY = Lib.application.window.y + FlxG.scaleMode.offset.y;
+ var maxX = Lib.application.window.x + FlxG.scaleMode.offset.x + FlxG.scaleMode.gameSize.x;
+ var maxY = Lib.application.window.y + FlxG.scaleMode.offset.y + FlxG.scaleMode.gameSize.y;
+
+ if (x < minX) x = minX;
+ if (y < minY) y = minY;
+ if (right > maxX) right = maxX;
+ if (bottom > maxY) bottom = maxY;*/
+ drawList.addRect([x, y, right, bottom], 0xFFB922F5, 0, 4);
+ }
+ else
+ {
+ if (sprite.frame != null) {
+ @:privateAccess
+ var matrix:FlxMatrix = sprite._matrix;
+
+ var pointTL = FlxPoint.get(0, 0);
+ var pointTR = FlxPoint.get(0 + sprite.frame.frame.width, 0);
+ var pointBL = FlxPoint.get(0, 0 + sprite.frame.frame.height);
+ var pointBR = FlxPoint.get(0 + sprite.frame.frame.width, 0 + sprite.frame.frame.height);
+
+ pointTL = pointTL.transform(matrix);
+ pointTR = pointTR.transform(matrix);
+ pointBL = pointBL.transform(matrix);
+ pointBR = pointBR.transform(matrix);
+ transformFlxPointOntoCamera(pointTL, camera);
+ transformFlxPointOntoCamera(pointTR, camera);
+ transformFlxPointOntoCamera(pointBL, camera);
+ transformFlxPointOntoCamera(pointBR, camera);
+ transformFlxPointToWindowSpace(pointTL);
+ transformFlxPointToWindowSpace(pointTR);
+ transformFlxPointToWindowSpace(pointBL);
+ transformFlxPointToWindowSpace(pointBR);
+ drawList.addQuad([pointTL.x, pointTL.y, pointTR.x, pointTR.y, pointBR.x, pointBR.y, pointBL.x, pointBL.y], 0xFFB922F5, 4);
+
+ pointTL.put();
+ pointTR.put();
+ pointBL.put();
+ pointBR.put();
+ }
+ }
+
+ if (ImGui.isKeyPressed(ImGuiKey.Q)) gizmoMode = -1;
+ if (ImGui.isKeyPressed(ImGuiKey.W)) gizmoMode = 0;
+ if (ImGui.isKeyPressed(ImGuiKey.E)) gizmoMode = 1;
+ if (ImGui.isKeyPressed(ImGuiKey.R)) gizmoMode = 2;
+
+ if (gizmoMode > -1) {
+ var windowX = position.x-(size/2);
+ var windowY = position.y-(size/2);
+ ImGui.setNextWindowPos(windowX, windowY);
+ ImGui.setNextWindowSize(size, size);
+ var flags = ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.NoFocusOnAppearing;
+ if ((ImGuiIO.configFlags & ImGuiConfigFlags.ViewportsEnable) == 0 || lastViewportID == ImGui.getMainViewport().id) {
+ flags |= ImGuiWindowFlags.NoBackground;
+ ImGui.setNextWindowBGAlpha(0);
+ }
+ ImGui.begin("2dGizmo", null, flags);
+ if ((ImGuiIO.configFlags & ImGuiConfigFlags.ViewportsEnable) != 0) lastViewportID = ImGui.getWindowViewport().id;
+
+ if (gizmoMode == 0) {
+ showPositionGizmo(object, position);
+ } else if (gizmoMode == 1) {
+ showRotationGizmo(object, position);
+ } else if (gizmoMode == 2 && sprite != null) {
+ showScaleGizmo(sprite, position);
+ }
+
+ ImGui.end();
+ }
+
+ }
+
+ function showPositionGizmo(object:FlxObject, position:FlxPoint) {
+
+ var windowDrawList = ImGui.getWindowDrawList();
+ var windowX = position.x-(size/2);
+ var windowY = position.y-(size/2);
+ var halfSize = size/2;
+ var halfArrow = arrowWidth/2;
+ var halfSizeMinusHalfArrow = halfSize-halfArrow;
+ var halfSizePlusHalfArrow = halfSize+halfArrow;
+
+ ImGui.setCursorPos(halfSizeMinusHalfArrow, 0);
+ ImGui.invisibleButton("##vertArrow", arrowWidth, halfSizeMinusHalfArrow);
+ var vertHover = ImGui.isItemHovered();
+ var vertActive = ImGui.isItemActive();
+ var vertDragged = ImGui.isItemActive() && ImGui.isMouseDragging(0);
+ if (vertDragged) {
+ if (!positionActive) {
+ positionX = object.x;
+ positionY = object.y;
+ positionActive = true;
+ }
+ positionY += ImGuiIO.mouseDeltaY*moveSpeedPos;
+ if (ImGui.isKeyDown(ImGuiKey.LeftCtrl)) {
+ object.y = Math.fround(positionY / snapPos) * snapPos;
+ } else {
+ object.y = positionY;
+ }
+ }
+
+ ImGui.setCursorPos(halfSizePlusHalfArrow, halfSizeMinusHalfArrow);
+ ImGui.invisibleButton("##horiArrow", halfSizeMinusHalfArrow, arrowWidth);
+ var horiHover = ImGui.isItemHovered();
+ var horiActive = ImGui.isItemActive();
+ var horiDragged = ImGui.isItemActive() && ImGui.isMouseDragging(0);
+ if (horiDragged) {
+ if (!positionActive) {
+ positionX = object.x;
+ positionY = object.y;
+ positionActive = true;
+ }
+ positionX += ImGuiIO.mouseDeltaX*moveSpeedPos;
+ if (ImGui.isKeyDown(ImGuiKey.LeftCtrl)) {
+ object.x = Math.fround(positionX / snapPos) * snapPos;
+ } else {
+ object.x = positionX;
+ }
+ }
+
+ ImGui.setCursorPos(halfSizeMinusHalfArrow, halfSizeMinusHalfArrow);
+ ImGui.invisibleButton("##centerArrow", arrowWidth, arrowWidth);
+ var centerHover = ImGui.isItemHovered();
+ var centerActive = ImGui.isItemActive();
+ var centerDragged = ImGui.isItemActive() && ImGui.isMouseDragging(0);
+ if (centerDragged) {
+ if (!positionActive) {
+ positionX = object.x;
+ positionY = object.y;
+ positionActive = true;
+ }
+ positionX += ImGuiIO.mouseDeltaX*moveSpeedPos;
+ positionY += ImGuiIO.mouseDeltaY*moveSpeedPos;
+ if (ImGui.isKeyDown(ImGuiKey.LeftCtrl)) {
+ object.x = Math.fround(positionX / snapPos) * snapPos;
+ object.y = Math.fround(positionY / snapPos) * snapPos;
+ } else {
+ object.x = positionX;
+ object.y = positionY;
+ }
+ }
+
+ if (vertActive || horiActive || centerActive) {
+ var text = FlxMath.roundDecimal(object.x, 2) + ", " + FlxMath.roundDecimal(object.y, 2);
+ var size = ImGui.calcTextSize(text);
+ var offset = halfSize - (size.x/2);
+ ImGui.setCursorPos(offset, halfSizePlusHalfArrow);
+ windowDrawList.addRectFilled([windowX + offset, windowY + halfSizePlusHalfArrow, windowX + size.x + offset, windowY + halfSizePlusHalfArrow + size.y], 0x94000000, 2);
+ ImGui.text(text);
+ } else {
+ positionActive = false;
+ }
+
+ {
+ var color = 0xFF76BC02;
+ if (vertDragged) color = 0xFFC1F273;
+ else if (vertHover) color = 0xFF8ED914;
+ windowDrawList.addLine([windowX + halfSize,
+ windowY + arrowWidth,
+ windowX + halfSize,
+ windowY + halfSizeMinusHalfArrow], color, arrowWidth/2);
+ windowDrawList.addTriangleFilled([windowX + halfSize, windowY,
+ windowX + halfSize, windowY + arrowWidth,
+ windowX + halfSize - arrowWidth, windowY + arrowWidth], color);
+ windowDrawList.addTriangleFilled([windowX + halfSize, windowY,
+ windowX + halfSize, windowY + arrowWidth,
+ windowX + halfSize + arrowWidth, windowY + arrowWidth], color);
+ }
+
+ {
+ var color = 0xFFD72C47;
+ if (horiDragged) color = 0xFFFB899A;
+ else if (horiHover) color = 0xFFF35069;
+ windowDrawList.addLine([windowX + size - arrowWidth,
+ windowY + halfSize,
+ windowX + halfSizePlusHalfArrow,
+ windowY + halfSize], color, arrowWidth/2);
+ windowDrawList.addTriangleFilled([windowX + size, windowY + halfSize,
+ windowX + size - arrowWidth, windowY + halfSize,
+ windowX + size - arrowWidth, windowY + halfSize - arrowWidth], color);
+ windowDrawList.addTriangleFilled([windowX + size, windowY + halfSize,
+ windowX + size - arrowWidth, windowY + halfSize,
+ windowX + size - arrowWidth, windowY + halfSize + arrowWidth], color);
+ }
+
+ {
+ var color = 0xFFA2A2A2;
+ if (centerDragged) color = 0xFFFFFFFF;
+ else if (centerHover) color = 0xFFC4C3C3;
+ windowDrawList.addRectFilled([windowX + halfSizeMinusHalfArrow, windowY + halfSizeMinusHalfArrow,
+ windowX + halfSizePlusHalfArrow, windowY + halfSizePlusHalfArrow], color, 2);
+ }
+ }
+
+
+ function showRotationGizmo(object:FlxObject, position:FlxPoint) {
+
+ var windowDrawList = ImGui.getWindowDrawList();
+ var windowX = position.x-(size/2);
+ var windowY = position.y-(size/2);
+ var halfSize = size/2;
+ var halfArrow = arrowWidth/2;
+ var lineLength = halfSize-halfArrow;
+ var halfSizeMinusHalfArrow = halfSize-halfArrow;
+ var halfSizePlusHalfArrow = halfSize+halfArrow;
+
+ ImGui.setCursorPos(0, 0);
+ ImGui.invisibleButton("##rotation", size, size);
+ var hover = ImGui.isItemHovered();
+ var active = ImGui.isItemActive();
+ var dragged = ImGui.isItemActive() && ImGui.isMouseDragging(0);
+ if (dragged) {
+ var mousePos = ImGui.getMousePos();
+ if (!rotationActive) {
+ rotationActive = true;
+ rotationStartX = mousePos.x;
+ rotationStartY = mousePos.y;
+ rotationStartAngle = object.angle;
+ } else {
+ var start = FlxPoint.get(rotationStartX - position.x, rotationStartY - position.y);
+ var cur = FlxPoint.get(mousePos.x - position.x, mousePos.y - position.y);
+ start = start.normalize();
+ cur = cur.normalize();
+
+ object.angle = rotationStartAngle + (cur.degrees - start.degrees);
+ if (ImGui.isKeyDown(ImGuiKey.LeftCtrl)) {
+ object.angle = Math.fround(object.angle / snapAngle) * snapAngle;
+ }
+
+ var rad = (rotationStartAngle-90) * (Math.PI/180);
+ windowDrawList.addLine([windowX + halfSize, windowY + halfSize,
+ windowX + halfSize + (Math.cos(rad)*lineLength), windowY + halfSize + (Math.sin(rad)*lineLength)], 0xFF737373, halfArrow);
+
+ start.put();
+ cur.put();
+ }
+ } else {
+ rotationActive = false;
+ }
+
+ var rad = (object.angle-90) * (Math.PI/180);
+ windowDrawList.addLine([windowX + halfSize, windowY + halfSize,
+ windowX + halfSize + (Math.cos(rad)*lineLength), windowY + halfSize + (Math.sin(rad)*lineLength)], 0xFFCDCDCD, halfArrow);
+
+ var color = 0xFF298cf5;
+ if (dragged) color = 0xff7fb6f1;
+ else if (hover) color = 0xff4a9aef;
+ windowDrawList.addCircle(windowX + halfSize, windowY + halfSize, halfSize-(halfArrow/2), color, 0, halfArrow);
+
+ if (active) {
+ var text = FlxMath.roundDecimal(object.angle, 2) + "°";
+ var size = ImGui.calcTextSize(text);
+ var offset = halfSize - (size.x/2);
+ ImGui.setCursorPos(offset, halfSizePlusHalfArrow);
+ windowDrawList.addRectFilled([windowX + offset, windowY + halfSizePlusHalfArrow, windowX + size.x + offset, windowY + halfSizePlusHalfArrow + size.y], 0x94000000, 2);
+ ImGui.text(text);
+ } else {
+ rotationActive = false;
+ }
+ }
+
+ function showScaleGizmo(sprite:FlxSprite, position:FlxPoint) {
+
+ var windowDrawList = ImGui.getWindowDrawList();
+ var windowX = position.x-(size/2);
+ var windowY = position.y-(size/2);
+ var halfSize = size/2;
+ var halfBox = scaleBoxWidth/2;
+ var halfSizeMinusHalfBox = halfSize-halfBox;
+ var halfSizePlusHalfBox = halfSize+halfBox;
+ var halfArrow = arrowWidth/2;
+ var halfSizeMinusHalfArrow = halfSize-halfArrow;
+ var halfSizePlusHalfArrow = halfSize+halfArrow;
+
+ ImGui.setCursorPos(halfSizeMinusHalfArrow, 0);
+ ImGui.invisibleButton("##vertArrow", arrowWidth, halfSizeMinusHalfArrow);
+ var vertHover = ImGui.isItemHovered();
+ var vertActive = ImGui.isItemActive();
+ var vertDragged = ImGui.isItemActive() && ImGui.isMouseDragging(0);
+ if (vertDragged) {
+ if (!scaleActive) {
+ scaleX = sprite.scale.x;
+ scaleY = sprite.scale.y;
+ scaleActive = true;
+ }
+ scaleY -= ImGuiIO.mouseDeltaY*moveSpeedScale;
+ if (ImGui.isKeyDown(ImGuiKey.LeftCtrl)) {
+ sprite.scale.y = Math.fround(scaleY / snapScale) * snapScale;
+ } else {
+ sprite.scale.y = scaleY;
+ }
+ }
+
+ ImGui.setCursorPos(halfSizePlusHalfArrow, halfSizeMinusHalfArrow);
+ ImGui.invisibleButton("##horiArrow", halfSizeMinusHalfArrow, arrowWidth);
+ var horiHover = ImGui.isItemHovered();
+ var horiActive = ImGui.isItemActive();
+ var horiDragged = ImGui.isItemActive() && ImGui.isMouseDragging(0);
+ if (horiDragged) {
+ if (!scaleActive) {
+ scaleX = sprite.scale.x;
+ scaleY = sprite.scale.y;
+ scaleActive = true;
+ }
+ scaleX += ImGuiIO.mouseDeltaX*moveSpeedScale;
+ if (ImGui.isKeyDown(ImGuiKey.LeftCtrl)) {
+ sprite.scale.x = Math.fround(scaleX / snapScale) * snapScale;
+ } else {
+ sprite.scale.x = scaleX;
+ }
+ }
+
+ ImGui.setCursorPos(halfSizeMinusHalfBox, halfSizeMinusHalfBox);
+ ImGui.invisibleButton("##scaleCenter", scaleBoxWidth, scaleBoxWidth);
+ var centerHover = ImGui.isItemHovered();
+ var centerActive = ImGui.isItemActive();
+ var centerDragged = ImGui.isItemActive() && ImGui.isMouseDragging(0);
+ if (centerDragged) {
+ if (!scaleActive) {
+ scaleX = sprite.scale.x;
+ scaleY = sprite.scale.y;
+ scaleActive = true;
+ }
+ scaleX += ImGuiIO.mouseDeltaX*moveSpeedScale;
+ scaleY -= ImGuiIO.mouseDeltaY*moveSpeedScale;
+ if (ImGui.isKeyDown(ImGuiKey.LeftCtrl)) {
+ sprite.scale.x = Math.fround(scaleX / snapScale) * snapScale;
+ sprite.scale.y = Math.fround(scaleY / snapScale) * snapScale;
+ } else {
+ sprite.scale.x = scaleX;
+ sprite.scale.y = scaleY;
+ }
+ }
+
+ if (vertActive || horiActive || centerActive) {
+ var text = FlxMath.roundDecimal(sprite.scale.x, 2) + ", " + FlxMath.roundDecimal(sprite.scale.y, 2);
+ var size = ImGui.calcTextSize(text);
+ var offset = halfSize - (size.x/2);
+ ImGui.setCursorPos(offset, halfSizePlusHalfArrow);
+ windowDrawList.addRectFilled([windowX + offset, windowY + halfSizePlusHalfArrow, windowX + size.x + offset, windowY + halfSizePlusHalfArrow + size.y], 0x94000000, 2);
+ ImGui.text(text);
+ } else {
+ scaleActive = false;
+ }
+
+ {
+ var color = 0xFF76BC02;
+ if (vertDragged) color = 0xFFC1F273;
+ else if (vertHover) color = 0xFF8ED914;
+ windowDrawList.addLine([windowX + halfSize,
+ windowY + arrowWidth,
+ windowX + halfSize,
+ windowY + halfSizeMinusHalfArrow], color, arrowWidth/2);
+ windowDrawList.addRectFilled([windowX + halfSizeMinusHalfBox, windowY,
+ windowX + halfSizePlusHalfBox, windowY + scaleBoxWidth], color);
+ }
+
+ {
+ var color = 0xFFD72C47;
+ if (horiDragged) color = 0xFFFB899A;
+ else if (horiHover) color = 0xFFF35069;
+ windowDrawList.addLine([windowX + size - arrowWidth,
+ windowY + halfSize,
+ windowX + halfSizePlusHalfArrow,
+ windowY + halfSize], color, arrowWidth/2);
+ windowDrawList.addRectFilled([windowX + size - scaleBoxWidth, windowY + halfSizeMinusHalfBox,
+ windowX + size, windowY + halfSizePlusHalfBox], color);
+ }
+
+ {
+ var color = 0xFFA2A2A2;
+ if (centerDragged) color = 0xFFFFFFFF;
+ else if (centerHover) color = 0xFFC4C3C3;
+ windowDrawList.addRectFilled([windowX + halfSizeMinusHalfBox, windowY + halfSizeMinusHalfBox,
+ windowX + halfSizePlusHalfBox, windowY + halfSizePlusHalfBox], color);
+ }
+ }
+
+ #end
+}
\ No newline at end of file
diff --git a/source/funkin/backend/system/console/inspector/InspectorGizmo3D.hx b/source/funkin/backend/system/console/inspector/InspectorGizmo3D.hx
new file mode 100644
index 0000000000..ca0999f582
--- /dev/null
+++ b/source/funkin/backend/system/console/inspector/InspectorGizmo3D.hx
@@ -0,0 +1,174 @@
+package funkin.backend.system.console.inspector;
+
+import flixel.math.FlxMatrix;
+import flixel.math.FlxPoint;
+import openfl.Lib;
+import lime.math.Vector2;
+import openfl.geom.Vector3D;
+import funkin.backend.system.console.inspector.ConsoleInspector.InspectorObject;
+
+#if IMGUI_ENABLED
+import lime.tools.imgui.ImGuiFlags;
+import lime.tools.imgui.ImGuiTypes;
+import lime.tools.imgui.ImGuiPtr;
+#end
+#if foxlite
+import foxlite.FoxScene;
+import foxlite.FoxBasic;
+import foxlite.FoxObject;
+import foxlite.FoxModel;
+import foxlite.FoxCamera;
+#end
+
+class InspectorGizmo3D {
+
+ var gizmoMode:Int = 0;
+ var lastViewportID:Int = 0;
+
+ var size:Float = 120;
+ var arrowWidth = 10;
+
+ var snapPos:Float = 0.1;
+ var snapAngle:Float = 15;
+ var snapScale:Float = 0.1;
+
+ public function new() {}
+
+ #if (IMGUI_ENABLED && foxlite)
+
+ inline function transformVector3DScreenSpaceToWindowSpace(point:Vector3D) {
+ if ((ImGuiIO.configFlags & ImGuiConfigFlags.ViewportsEnable) != 0) {
+ point.x = (Lib.application.window.x + FlxG.scaleMode.offset.x) + (point.x * FlxG.scaleMode.scale.x);
+ point.y = (Lib.application.window.y + FlxG.scaleMode.offset.y) + (point.y * FlxG.scaleMode.scale.y);
+ } else {
+ point.x = (FlxG.scaleMode.offset.x) + (point.x * FlxG.scaleMode.scale.x);
+ point.y = (FlxG.scaleMode.offset.y) + (point.y * FlxG.scaleMode.scale.y);
+ }
+ }
+
+ inline function transformVector3DWorldSpaceToScreenSpace(point:Vector3D, camera:FoxCamera, scene:FoxScene, clamp:Bool = false) {
+ var screen = camera.getScreenPoint(point);
+ point.x = screen.x;
+ point.y = screen.y;
+ point.z = screen.z;
+ point.x = ((point.x*0.5) + 0.5);
+ point.y = (-((point.y*0.5) + 0.5) + 1.0);
+ if (clamp) {
+ point.x = FlxMath.bound(point.x, 0, 1);
+ point.y = FlxMath.bound(point.y, 0, 1);
+ }
+ point.x *= scene.__width;
+ point.y *= scene.__height;
+ }
+
+ function figureOutScene(objectData:InspectorObject, object:FoxObject) {
+ var parent = objectData.groupParent;
+ while(parent != null) {
+ if (parent.obj is FoxScene) {
+ return parent.obj;
+ } else if (parent.obj is FoxBasic) {
+ var basic:FoxBasic = cast parent.obj;
+ if (basic.scene != null) return basic.scene;
+ }
+ parent = parent.groupParent;
+ }
+ return null;
+ }
+
+ public function show(objectData:InspectorObject, object:FoxObject, justChanged:Bool) {
+ var model:FoxModel = cast object;
+ var drawList = ImGui.getBackgroundDrawList(ImGui.getMainViewport());
+ var scene:FoxScene = object.scene;
+ if (scene == null) scene = figureOutScene(objectData, object);
+ var camera:FoxCamera = scene.foxCameras[0];
+ if (camera == null) return;
+
+ var position = camera.getScreenPoint(object.position);
+ position.x = ((position.x*0.5) + 0.5) * scene.__width;
+ position.y = (-((position.y*0.5) + 0.5) + 1.0) * scene.__height;
+
+ var position = object.position.clone();
+ transformVector3DWorldSpaceToScreenSpace(position, camera, scene);
+ transformVector3DScreenSpaceToWindowSpace(position);
+ if (position.z < 1.0) drawList.addCircleFilled(position.x, position.y, 10, 0xFFFF0000);
+
+ if (model != null) {
+ for (mesh in model.meshes) {
+ if (mesh.bounds != null) {
+
+ var topTopLeft = model.transform.transformVector(new Vector3D(mesh.bounds.center.x - mesh.bounds.extents.x, mesh.bounds.center.y - mesh.bounds.extents.y, mesh.bounds.center.z - mesh.bounds.extents.z));
+ var topTopRight = model.transform.transformVector(new Vector3D(mesh.bounds.center.x + mesh.bounds.extents.x, mesh.bounds.center.y - mesh.bounds.extents.y, mesh.bounds.center.z - mesh.bounds.extents.z));
+ var topBottomLeft = model.transform.transformVector(new Vector3D(mesh.bounds.center.x - mesh.bounds.extents.x, mesh.bounds.center.y - mesh.bounds.extents.y, mesh.bounds.center.z + mesh.bounds.extents.z));
+ var topBottomRight = model.transform.transformVector(new Vector3D(mesh.bounds.center.x + mesh.bounds.extents.x, mesh.bounds.center.y - mesh.bounds.extents.y, mesh.bounds.center.z + mesh.bounds.extents.z));
+
+ var bottomTopLeft = model.transform.transformVector(new Vector3D(mesh.bounds.center.x - mesh.bounds.extents.x, mesh.bounds.center.y + mesh.bounds.extents.y, mesh.bounds.center.z - mesh.bounds.extents.z));
+ var bottomTopRight = model.transform.transformVector(new Vector3D(mesh.bounds.center.x + mesh.bounds.extents.x, mesh.bounds.center.y + mesh.bounds.extents.y, mesh.bounds.center.z - mesh.bounds.extents.z));
+ var bottomBottomLeft = model.transform.transformVector(new Vector3D(mesh.bounds.center.x - mesh.bounds.extents.x, mesh.bounds.center.y + mesh.bounds.extents.y, mesh.bounds.center.z + mesh.bounds.extents.z));
+ var bottomBottomRight = model.transform.transformVector(new Vector3D(mesh.bounds.center.x + mesh.bounds.extents.x, mesh.bounds.center.y + mesh.bounds.extents.y, mesh.bounds.center.z + mesh.bounds.extents.z));
+
+ transformVector3DWorldSpaceToScreenSpace(topTopLeft, camera, scene); transformVector3DScreenSpaceToWindowSpace(topTopLeft);
+ transformVector3DWorldSpaceToScreenSpace(topTopRight, camera, scene); transformVector3DScreenSpaceToWindowSpace(topTopRight);
+ transformVector3DWorldSpaceToScreenSpace(topBottomLeft, camera, scene); transformVector3DScreenSpaceToWindowSpace(topBottomLeft);
+ transformVector3DWorldSpaceToScreenSpace(topBottomRight, camera, scene); transformVector3DScreenSpaceToWindowSpace(topBottomRight);
+ transformVector3DWorldSpaceToScreenSpace(bottomTopLeft, camera, scene); transformVector3DScreenSpaceToWindowSpace(bottomTopLeft);
+ transformVector3DWorldSpaceToScreenSpace(bottomTopRight, camera, scene); transformVector3DScreenSpaceToWindowSpace(bottomTopRight);
+ transformVector3DWorldSpaceToScreenSpace(bottomBottomLeft, camera, scene); transformVector3DScreenSpaceToWindowSpace(bottomBottomLeft);
+ transformVector3DWorldSpaceToScreenSpace(bottomBottomRight, camera, scene); transformVector3DScreenSpaceToWindowSpace(bottomBottomRight);
+
+ //drawList.addQuad([topTopLeft.x, topTopLeft.y, topTopRight.x, topTopRight.y, topBottomRight.x, topBottomRight.y, topBottomLeft.x, topBottomLeft.y], 0xFFB922F5, 4);
+ if (topTopLeft.z < 1.0 && topTopRight.z < 1.0) drawList.addLine([topTopLeft.x, topTopLeft.y, topTopRight.x, topTopRight.y], 0xFFB922F5, 4);
+ if (topTopRight.z < 1.0 && topBottomRight.z < 1.0) drawList.addLine([topTopRight.x, topTopRight.y, topBottomRight.x, topBottomRight.y], 0xFFB922F5, 4);
+ if (topBottomRight.z < 1.0 && topBottomLeft.z < 1.0) drawList.addLine([topBottomRight.x, topBottomRight.y, topBottomLeft.x, topBottomLeft.y], 0xFFB922F5, 4);
+ if (topBottomLeft.z < 1.0 && topTopLeft.z < 1.0) drawList.addLine([topBottomLeft.x, topBottomLeft.y, topTopLeft.x, topTopLeft.y], 0xFFB922F5, 4);
+
+ if (topTopLeft.z < 1.0 && bottomTopLeft.z < 1.0) drawList.addLine([topTopLeft.x, topTopLeft.y, bottomTopLeft.x, bottomTopLeft.y], 0xFFB922F5, 4);
+ if (topTopRight.z < 1.0 && bottomTopRight.z < 1.0) drawList.addLine([topTopRight.x, topTopRight.y, bottomTopRight.x, bottomTopRight.y], 0xFFB922F5, 4);
+ if (topBottomLeft.z < 1.0 && bottomBottomLeft.z < 1.0) drawList.addLine([topBottomLeft.x, topBottomLeft.y, bottomBottomLeft.x, bottomBottomLeft.y], 0xFFB922F5, 4);
+ if (topBottomRight.z < 1.0 && bottomBottomRight.z < 1.0) drawList.addLine([topBottomRight.x, topBottomRight.y, bottomBottomRight.x, bottomBottomRight.y], 0xFFB922F5, 4);
+
+ if (bottomTopLeft.z < 1.0 && bottomTopRight.z < 1.0) drawList.addLine([bottomTopLeft.x, bottomTopLeft.y, bottomTopRight.x, bottomTopRight.y], 0xFFB922F5, 4);
+ if (bottomTopRight.z < 1.0 && bottomBottomRight.z < 1.0) drawList.addLine([bottomTopRight.x, bottomTopRight.y, bottomBottomRight.x, bottomBottomRight.y], 0xFFB922F5, 4);
+ if (bottomBottomRight.z < 1.0 && bottomBottomLeft.z < 1.0) drawList.addLine([bottomBottomRight.x, bottomBottomRight.y, bottomBottomLeft.x, bottomBottomLeft.y], 0xFFB922F5, 4);
+ if (bottomBottomLeft.z < 1.0 && bottomTopLeft.z < 1.0) drawList.addLine([bottomBottomLeft.x, bottomBottomLeft.y, bottomTopLeft.x, bottomTopLeft.y], 0xFFB922F5, 4);
+ //drawList.addQuad([bottomTopLeft.x, bottomTopLeft.y, bottomTopRight.x, bottomTopRight.y, bottomBottomRight.x, bottomBottomRight.y, bottomBottomLeft.x, bottomBottomLeft.y], 0xFFB922F5, 4);
+ }
+ }
+ }
+
+ if (justChanged) {
+
+ }
+
+ if (ImGui.isKeyPressed(ImGuiKey.Q)) gizmoMode = -1;
+ if (ImGui.isKeyPressed(ImGuiKey.W)) gizmoMode = 0;
+ if (ImGui.isKeyPressed(ImGuiKey.E)) gizmoMode = 1;
+ if (ImGui.isKeyPressed(ImGuiKey.R)) gizmoMode = 2;
+
+ if (gizmoMode > -1 && position.z < 1.0) {
+ var windowX = position.x-(size/2);
+ var windowY = position.y-(size/2);
+ ImGui.setNextWindowPos(windowX, windowY);
+ ImGui.setNextWindowSize(size, size);
+ var flags = ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.NoFocusOnAppearing;
+ if ((ImGuiIO.configFlags & ImGuiConfigFlags.ViewportsEnable) == 0 || lastViewportID == ImGui.getMainViewport().id) {
+ flags |= ImGuiWindowFlags.NoBackground;
+ ImGui.setNextWindowBGAlpha(0);
+ }
+ ImGui.begin("3dGizmo", null, flags);
+ if ((ImGuiIO.configFlags & ImGuiConfigFlags.ViewportsEnable) != 0) lastViewportID = ImGui.getWindowViewport().id;
+
+ if (gizmoMode == 0) {
+ //showPositionGizmo(object, position);
+ } else if (gizmoMode == 1) {
+ //showRotationGizmo(object, position);
+ } else if (gizmoMode == 2) {
+ //showScaleGizmo(sprite, position);
+ }
+
+ ImGui.end();
+ }
+
+ }
+
+ #end
+}
\ No newline at end of file
diff --git a/source/funkin/backend/system/console/inspector/InspectorObjectProperties.hx b/source/funkin/backend/system/console/inspector/InspectorObjectProperties.hx
new file mode 100644
index 0000000000..fbf052e856
--- /dev/null
+++ b/source/funkin/backend/system/console/inspector/InspectorObjectProperties.hx
@@ -0,0 +1,716 @@
+package funkin.backend.system.console.inspector;
+
+import openfl.Lib;
+import flixel.text.FlxText;
+import flixel.util.FlxColor;
+import funkin.backend.system.console.inspector.ConsoleInspector.InspectorObject;
+
+#if IMGUI_ENABLED
+import lime.tools.imgui.ImGuiFlags;
+import lime.tools.imgui.ImGuiTypes;
+import lime.tools.imgui.ImGuiPtr;
+#end
+#if foxlite
+import foxlite.FoxScene;
+import foxlite.group.FoxTypedGroup;
+import foxlite.group.FoxObjectGroup;
+import foxlite.FoxBasic;
+import foxlite.FoxObject;
+import foxlite.FoxModel;
+import foxlite.mesh.buffer.FoxVertexBuffer;
+import foxlite.mesh.buffer.FoxVertexBufferType;
+#end
+
+using funkin.backend.utils.ImGuiUtil;
+
+class InspectorObjectProperties {
+
+ #if IMGUI_ENABLED
+ var boolPool:ImGuiPtrPool = new ImGuiPtrPool(function() {return new ImGuiBoolPtr(false);});
+ var floatPool:ImGuiPtrPool = new ImGuiPtrPool(function() {return new ImGuiFloatPtr(0.0);});
+ var intPool:ImGuiPtrPool = new ImGuiPtrPool(function() {return new ImGuiIntPtr(0);});
+ var float4Pool:ImGuiPtrPool = new ImGuiPtrPool(function() {return new ImGuiFloat4Ptr(0, 0, 0, 0);});
+ var stringPool:ImGuiPtrPool = new ImGuiPtrPool(function() {return new ImGuiStringPtr("");});
+
+ var mainImageViewer:ImGuiImageViewer = new ImGuiImageViewer();
+ var imageViewerMap:Map = [];
+
+ var tableFlags = ImGuiTableFlags.SizingStretchSame | ImGuiTableFlags.Resizable | ImGuiTableFlags.BordersOuter | ImGuiTableFlags.BordersV | ImGuiTableFlags.RowBg;
+
+ public function new() {}
+
+ public function show(objectData:InspectorObject, justChanged:Bool) {
+ boolPool.reset();
+ floatPool.reset();
+ intPool.reset();
+ float4Pool.reset();
+ stringPool.reset();
+
+ if (justChanged) {
+ mainImageViewer.viewReset = true;
+ imageViewerMap.clear();
+ }
+
+ var selectedObject:Dynamic = objectData.obj;
+
+ ImGui.setNextWindowPos(ImGuiUtil.getWindowSpaceX() + Lib.application.window.width - 300, ImGuiUtil.getWindowSpaceY(), ImGuiCond.FirstUseEver);
+ ImGui.setNextWindowSize(300, Lib.application.window.height, ImGuiCond.FirstUseEver);
+ if (ImGui.begin("Object Properties")) {
+ ImGui.text(objectData.name + " - " + objectData.type);
+
+ var basic:FlxBasic = selectedObject is FlxBasic ? cast selectedObject : null;
+ if (basic != null) {
+ showFlxBasicProperties(basic);
+ //TODO: script stuff here
+ }
+ #if foxlite
+ var foxbasic:FoxBasic = selectedObject is FoxBasic ? cast selectedObject : null;
+ if (foxbasic != null) {
+ showFoxBasicProps(foxbasic);
+ }
+ #end
+ }
+ ImGui.end();
+ }
+
+ function showFlxBasicProperties(basic:FlxBasic) {
+ var object:FlxObject = basic is FlxObject ? cast basic : null;
+ var sprite:FlxSprite = basic is FlxSprite ? cast basic : null;
+ var text:FlxText = basic is FlxText ? cast basic : null;
+ var funkinSprite:FunkinSprite = basic is FunkinSprite ? cast basic : null;
+ var funkinText:FunkinText = basic is FunkinText ? cast basic : null;
+
+ if (object != null) {
+ showFlxObjectTransformProperties(object, sprite, funkinSprite, funkinText);
+ if (sprite != null) {
+ showFlxSpriteGraphicsProperties(sprite);
+ showFlxSpriteAnimationProperties(sprite);
+ }
+ showFlxObjectPhysicsProperties(object);
+ if (text != null) {
+ showFlxTextProperties(text);
+ }
+ }
+
+ if (ImGui.collapsingHeader("Basic")) {
+ if (ImGui.beginTable("Basic##1", 2, tableFlags))
+ {
+ checkboxField("Active", "active", basic);
+ checkboxField("Visible", "visible", basic);
+ checkboxField("Alive", "alive", basic);
+ checkboxField("Exists", "exists", basic);
+ ImGui.endTable();
+ }
+ }
+ }
+
+ function showFlxObjectTransformProperties(object:FlxObject, ?sprite:FlxSprite = null, ?funkinSprite:FunkinSprite = null, ?funkinText:FunkinText = null) {
+ if (ImGui.collapsingHeader("Transform")) {
+ if (ImGui.beginTable("TransformTable", 2, tableFlags))
+ {
+ dragFloat2Field("Position", "x", "y", object);
+ dragFloat2Field("Width/Height", "width", "height", object);
+ if (sprite != null) {
+ dragFloat2Field("Scale", "x", "y", sprite.scale, 0.05);
+ dragFloat2Field("Origin", "x", "y", sprite.origin, 0.1);
+ dragFloat2Field("Offset", "x", "y", sprite.offset);
+ }
+ dragFloatField("Angle", "angle", object);
+ dragFloat2Field("Scroll Factor", "x", "y", object.scrollFactor, 0.05);
+ if (funkinSprite != null || funkinText != null) {
+ dragFloatField("Zoom Factor", "zoomFactor", object, 0.05);
+ checkboxField("Enabled", "zoomFactorEnabled", object);
+ dragFloatField("Angle Factor", "angleFactor", object, 0.05);
+ checkboxField("Enabled", "angleFactorEnabled", object);
+
+ dragFloat2Field("Skew", "x", "y", funkinSprite != null ? funkinSprite.skew : funkinText.skew, 0.05);
+ }
+
+ //TODO: scripting
+
+ ImGui.endTable();
+ }
+ }
+ }
+ function showFlxSpriteGraphicsProperties(sprite:FlxSprite) {
+ if (ImGui.collapsingHeader("Graphics")) {
+ if (sprite.graphic != null) {
+ ImGui.text("Key: " + sprite.graphic.key);
+ var wid = ImGui.getContentRegionAvail().x;
+ if (sprite.graphic.bitmap != null) {
+ mainImageViewer.drawCanvas(wid, 300, ImTextureID.fromBitmapData(sprite.graphic.bitmap), sprite.graphic.width, sprite.graphic.height);
+ }
+ }
+
+ if (ImGui.beginTable("GraphicsTable", 2, tableFlags))
+ {
+ colorField("Color", "color", sprite);
+ sliderFloatField("Alpha", "alpha", sprite, 0, 1);
+
+ checkboxField("Flip X", "flipX", sprite);
+ checkboxField("Flip Y", "flipY", sprite);
+ checkboxField("Antialiasing", "antialiasing", sprite);
+
+ enumAbstractField("Blend Mode", "blend", sprite, "openfl.display.BlendMode");
+
+ ImGui.endTable();
+ }
+ }
+ }
+
+ function showFlxSpriteAnimationProperties(sprite:FlxSprite) {
+ if (ImGui.collapsingHeader("Animation")) {
+ //TODO
+ ImGui.text("Not yet implemented");
+ }
+ }
+
+ function showFlxObjectPhysicsProperties(object:FlxObject) {
+ if (ImGui.collapsingHeader("Physics")) {
+ if (ImGui.beginTable("PhysicsTable", 2, tableFlags))
+ {
+ checkboxField("Moves", "moves", object);
+ checkboxField("Immovable", "immovable", object);
+ checkboxField("Solid", "solid", object);
+
+ dragFloat2Field("Velocity", "x", "y", object.velocity);
+ dragFloat2Field("Acceleration", "x", "y", object.acceleration);
+ dragFloat2Field("Drag", "x", "y", object.drag);
+ dragFloatField("Mass", "mass", object);
+ dragFloatField("Elasticity", "elasticity", object);
+ dragFloatField("Angular Velocity", "angularVelocity", object);
+ dragFloatField("Angular Acceleration", "angularAcceleration", object);
+ dragFloatField("Angular Drag", "angularDrag", object);
+ dragFloat2Field("Max Velocity", "x", "y", object.maxVelocity);
+ dragFloatField("Max Angular", "maxAngular", object);
+ ImGui.endTable();
+ }
+ }
+ }
+
+ function showFlxTextProperties(text:FlxText) {
+ if (ImGui.collapsingHeader("Text")) {
+ if (ImGui.beginTable("TextTable", 2, tableFlags))
+ {
+ inputTextFieldMultiline("Text", "text", text, 400, 200);
+ dragIntField("Size", "size", text);
+ inputTextField("Font", "font", text);
+ dragFloat2Field("Field Width/Height", "fieldWidth", "fieldHeight", text);
+ enumAbstractField("Alignment", "alignment", text, "flixel.text.FlxTextAlign");
+ dragFloatField("Letter Spacing", "letterSpacing", text);
+
+ colorField("Border Color", "borderColor", text);
+
+ {
+ ImGui.tableNextRow();
+ ImGui.tableSetColumnIndex(0);
+ ImGui.text("Border Style");
+ ImGui.tableSetColumnIndex(1);
+ var wid = ImGui.getContentRegionAvail().x;
+ ImGui.setNextItemWidth(wid);
+
+ var index = intPool.get();
+ var list = ["NONE", "SHADOW", "SHADOW_XY", "OUTLINE", "OUTLINE_FAST", "OUTLINE_CARDINAL"];
+ switch(text.borderStyle) { //not that easy to automate due to args on SHADOW_XY
+ case NONE:
+ index.value = 0;
+ case SHADOW:
+ index.value = 1;
+ case SHADOW_XY(offsetX, offsetY):
+ index.value = 2;
+ case OUTLINE:
+ index.value = 3;
+ case OUTLINE_FAST:
+ index.value = 4;
+ case OUTLINE_CARDINAL:
+ index.value = 5;
+ }
+ if (ImGui.combo("##Border StyleborderStyle", index, list)) {
+ switch(index.value) {
+ case 0:
+ text.borderStyle = NONE;
+ case 1:
+ text.borderStyle = SHADOW;
+ case 2:
+ text.borderStyle = SHADOW_XY(0, 0);
+ case 3:
+ text.borderStyle = OUTLINE;
+ case 4:
+ text.borderStyle = OUTLINE_FAST;
+ case 5:
+ text.borderStyle = OUTLINE_CARDINAL;
+ }
+ }
+
+ switch(text.borderStyle) {
+ case SHADOW_XY(offsetX, offsetY):
+ //todo
+ default:
+ }
+ }
+ dragFloatField("Border Size", "borderSize", text);
+ dragFloatField("Border Quality", "borderQuality", text);
+
+ checkboxField("Bold", "bold", text);
+ checkboxField("Underline", "underline", text);
+ checkboxField("Italic", "italic", text);
+ checkboxField("Word Wrap", "wordWrap", text);
+ checkboxField("Auto Size", "autoSize", text);
+ ImGui.endTable();
+ }
+ }
+ }
+
+ #if foxlite
+ function showFoxBasicProps(basic:FoxBasic) {
+ var object:FoxObject = basic is FoxObject ? cast basic : null;
+ var model:FoxModel = basic is FoxModel ? cast basic : null;
+
+ if (object != null) {
+ ImGui.separator();
+ showFoxObjectTransformProperties(object);
+ if (model != null) showFoxModelGraphicsProperties(model);
+ }
+
+ if (ImGui.collapsingHeader("Basic")) {
+ if (ImGui.beginTable("Basic##1", 2, tableFlags))
+ {
+ textField("Name", basic.name);
+ checkboxField("Active", "active", basic);
+ checkboxField("Visible", "visible", basic);
+ ImGui.endTable();
+ }
+ }
+ }
+
+ function showFoxObjectTransformProperties(object:FoxObject) {
+ if (ImGui.collapsingHeader("Transform")) {
+ if (ImGui.beginTable("TransformTable", 2, tableFlags))
+ {
+ var dirty = false;
+ if (dragFloat3Field("Position", "x", "y", "z", object.position, 0.05)) dirty = true;
+ if (dragFloat3Field("Rotation", "angleX", "angleY", "angleZ", object)) dirty = true;
+ if (dragFloat3Field("Scale", "x", "y", "z", object.scale, 0.05)) dirty = true;
+ if (dirty) {
+ object.update(0.0); //force update transform
+ }
+ ImGui.endTable();
+ }
+ }
+ }
+
+ function showFoxModelGraphicsProperties(model:FoxModel) {
+ if (model != null && ImGui.collapsingHeader("Graphics")) {
+ if (ImGui.beginTable("Model##1", 2, tableFlags))
+ {
+ checkboxField("Frustrum Culling", "frustumCulling", model);
+ checkboxField("Cast Shadows", "castShadows", model);
+ checkboxField("Cast Colored Shadows", "castColoredShadows", model);
+ ImGui.endTable();
+ }
+
+ ImGui.separator();
+
+ for (i => mesh in model.meshes) {
+ var nodeID = "mesh" + i;
+ if (ImGui.treeNode(nodeID, mesh.assetsKey != null ? mesh.assetsKey : "Mesh " + i)) {
+ ImGui.pushIDFromInt(i);
+ if (ImGui.beginTable("MeshTable" + i, 2, tableFlags))
+ {
+ textField("Key", mesh.assetsKey);
+ textField("Is Copy", mesh.__isCopy ? "True" : "False");
+ @:privateAccess {
+ final vertexBuffer = mesh.buffers[FoxVertexBufferType.VERTICES],
+ uvBuffer = mesh.buffers[FoxVertexBufferType.UVS],
+ normalBuffer = mesh.buffers[FoxVertexBufferType.NORMALS],
+ tangentBuffer = mesh.buffers[FoxVertexBufferType.TANGENTS],
+ colorBuffer = mesh.buffers[FoxVertexBufferType.COLORS],
+ boneWeights = mesh.buffers[FoxVertexBufferType.WEIGHTS],
+ boneIndices = mesh.buffers[FoxVertexBufferType.BONE_INDICES],
+ indexBuffer = mesh.buffers[FoxVertexBufferType.INDICES];
+ // this should be better
+ inline function stride(buffer:FoxVertexBuffer) return buffer.components * buffer.bytesPerElement;
+ if (vertexBuffer != null) textField("Vertex Buffer", "Num: " + vertexBuffer.count + ", Stride: " + stride(vertexBuffer) + ", Size: " + (vertexBuffer.count * stride(vertexBuffer) ));
+ if (uvBuffer != null) textField("UV Buffer", "Num: " + uvBuffer.count + ", Stride: " + stride(uvBuffer) + ", Size: " + (uvBuffer.count * stride(uvBuffer) ));
+ if (indexBuffer != null) textField("Index Buffer", "Num: " + indexBuffer.count + ", Size: " + (indexBuffer.count * stride(indexBuffer) ));
+ if (normalBuffer != null) textField("Normal Buffer", "Num: " + normalBuffer.count + ", Stride: " + stride(normalBuffer) + ", Size: " + (normalBuffer.count * stride(normalBuffer) ));
+ if (tangentBuffer != null) textField("Tangent Buffer", "Num: " + tangentBuffer.count + ", Stride: " + stride(tangentBuffer) + ", Size: " + (tangentBuffer.count * stride(tangentBuffer)));
+ if (colorBuffer != null) textField("Color Buffer", "Num: " + colorBuffer.count + ", Stride: " + stride(colorBuffer) + ", Size: " + (colorBuffer.count * stride(colorBuffer) ));
+ if (boneWeights != null) textField("Bone Weights Buffer", "Num: " + boneWeights.count + ", Stride: " + stride(boneWeights) + ", Size: " + (boneWeights.count * stride(boneWeights) ));
+ if (boneIndices != null) textField("Bone Indices Buffer", "Num: " + boneIndices.count + ", Stride: " + stride(boneIndices) + ", Size: " + (boneIndices.count * stride(boneIndices) ));
+ }
+ ImGui.endTable();
+ }
+
+ if (mesh.material != null) {
+ ImGui.separatorText("Material");
+ if (ImGui.beginTable("MaterialTable" + i, 2, tableFlags))
+ {
+ textField("Name", mesh.material.name);
+ textField("Key", mesh.material.assetsKey);
+ dragIntField("Render Priority", "renderPriority", mesh.material);
+ checkboxField("Depth Test", "depthTest", mesh.material);
+ enumAbstractField("Depth Func", "depthFunc", mesh.material, "foxlite.material.FoxDepthCompareMode");
+ checkboxField("Depth Write", "depthWrite", mesh.material);
+ checkboxField("Color Write", "colorWrite", mesh.material);
+ enumAbstractField("Culling", "culling", mesh.material, "foxlite.material.FoxTriangleFace");
+ enumAbstractField("Shadow Culling", "shadowCulling", mesh.material, "foxlite.material.FoxTriangleFace");
+ enumAbstractField("Blend Mode", "blendMode", mesh.material, "foxlite.material.FoxBlendMode");
+ sliderFloatField("Alpha Scissor", "alphaScissor", mesh.material, 0, 1);
+ ImGui.endTable();
+ }
+
+
+
+ if (ImGui.treeNode(nodeID + "textures", "Textures")) {
+ for (texName => tex in mesh.material.textures) {
+ ImGui.separatorText(texName);
+ var wid = ImGui.getContentRegionAvail().x;
+ @:privateAccess
+ var id:Int = tex.glTexture.__getTexture().id;
+ if (id != 0) {
+ if (!imageViewerMap.exists(id)) imageViewerMap.set(id, new ImGuiImageViewer());
+ imageViewerMap.get(id).drawCanvas(wid, 300, new ImTextureID(id), tex.width, tex.height);
+ }
+ }
+ ImGui.treePop();
+ }
+ if (ImGui.treeNode(nodeID + "params", "Parameters")) {
+ if (ImGui.beginTable("ParamsTable" + i, 2, tableFlags)) {
+ for (name => value in mesh.material.params) {
+ textField(name, Std.string(value));
+ }
+ ImGui.endTable();
+ }
+ ImGui.treePop();
+ }
+ }
+ ImGui.popID();
+ ImGui.treePop();
+ }
+ }
+ }
+ }
+
+ #end
+
+ ////////////////////////////////////
+
+ inline function dragFloatField(name:String, field:String, object:Dynamic, speed:Float = 1.0, min:Float = 0.0, max:Float = 0.0, format:String = "%.3f", flags:ImGuiSliderFlags = 0) {
+ var didChange:Bool = false;
+ ImGui.tableNextRow();
+ ImGui.tableSetColumnIndex(0);
+ ImGui.text(name);
+ ImGui.tableSetColumnIndex(1);
+
+ var wid = ImGui.getContentRegionAvail().x;
+ ImGui.setNextItemWidth(wid);
+
+ var f = floatPool.get();
+ f.value = Reflect.getProperty(object, field);
+ if (ImGui.dragFloat("##" + name + field, f, speed, min, max, format, flags)) {
+ Reflect.setProperty(object, field, f.value);
+ didChange = true;
+ }
+ return didChange;
+ }
+ inline function dragFloat2Field(name:String, field:String, field2:String, object:Dynamic, speed:Float = 1.0, min:Float = 0.0, max:Float = 0.0, format:String = "%.3f", flags:ImGuiSliderFlags = 0) {
+ var didChange:Bool = false;
+ ImGui.tableNextRow();
+ ImGui.tableSetColumnIndex(0);
+ ImGui.text(name);
+ ImGui.tableSetColumnIndex(1);
+ var wid = ImGui.getContentRegionAvail().x / 2;
+ ImGui.setNextItemWidth(wid);
+
+ var f = floatPool.get();
+ f.value = Reflect.getProperty(object, field);
+ if (ImGui.dragFloat("##" + name + field, f, speed, min, max, format, flags)) {
+ Reflect.setProperty(object, field, f.value);
+ didChange = true;
+ }
+
+ ImGui.sameLine();
+ ImGui.setNextItemWidth(wid);
+
+ var f2 = floatPool.get();
+ f2.value = Reflect.getProperty(object, field2);
+ if (ImGui.dragFloat("##" + name + field2, f2, speed, min, max, format, flags)) {
+ Reflect.setProperty(object, field2, f2.value);
+ didChange = true;
+ }
+ return didChange;
+ }
+ inline function dragFloat3Field(name:String, field:String, field2:String, field3:String, object:Dynamic, speed:Float = 1.0, min:Float = 0.0, max:Float = 0.0, format:String = "%.3f", flags:ImGuiSliderFlags = 0) {
+ var didChange:Bool = false;
+ ImGui.tableNextRow();
+ ImGui.tableSetColumnIndex(0);
+ ImGui.text(name);
+ ImGui.tableSetColumnIndex(1);
+ var wid = ImGui.getContentRegionAvail().x / 3;
+ ImGui.setNextItemWidth(wid);
+
+ var f = floatPool.get();
+ f.value = Reflect.getProperty(object, field);
+ if (ImGui.dragFloat("##" + name + field, f, speed, min, max, format, flags)) {
+ Reflect.setProperty(object, field, f.value);
+ didChange = true;
+ }
+
+ ImGui.sameLine();
+ ImGui.setNextItemWidth(wid);
+
+ var f2 = floatPool.get();
+ f2.value = Reflect.getProperty(object, field2);
+ if (ImGui.dragFloat("##" + name + field2, f2, speed, min, max, format, flags)) {
+ Reflect.setProperty(object, field2, f2.value);
+ didChange = true;
+ }
+
+ ImGui.sameLine();
+ ImGui.setNextItemWidth(wid);
+
+ var f3 = floatPool.get();
+ f3.value = Reflect.getProperty(object, field3);
+ if (ImGui.dragFloat("##" + name + field3, f3, speed, min, max, format, flags)) {
+ Reflect.setProperty(object, field3, f3.value);
+ didChange = true;
+ }
+ return didChange;
+ }
+ inline function sliderFloatField(name:String, field:String, object:Dynamic, min:Float, max:Float, format:String = "%.3f", flags:ImGuiSliderFlags = 0) {
+ var didChange:Bool = false;
+ ImGui.tableNextRow();
+ ImGui.tableSetColumnIndex(0);
+ ImGui.text(name);
+ ImGui.tableSetColumnIndex(1);
+ var wid = ImGui.getContentRegionAvail().x;
+ ImGui.setNextItemWidth(wid);
+
+ var f = floatPool.get();
+ f.value = Reflect.getProperty(object, field);
+ if (ImGui.sliderFloat("##" + name + field, f, min, max, format, flags)) {
+ Reflect.setProperty(object, field, f.value);
+ didChange = true;
+ }
+ return didChange;
+ }
+ inline function dragIntField(name:String, field:String, object:Dynamic, speed:Float = 1.0, min:Int = 0, max:Int = 0, format:String = "%d", flags:ImGuiSliderFlags = 0) {
+ var didChange:Bool = false;
+ ImGui.tableNextRow();
+ ImGui.tableSetColumnIndex(0);
+ ImGui.text(name);
+ ImGui.tableSetColumnIndex(1);
+ var wid = ImGui.getContentRegionAvail().x;
+ ImGui.setNextItemWidth(wid);
+
+ var i = intPool.get();
+ i.value = Reflect.getProperty(object, field);
+ if (ImGui.dragInt("##" + name + field, i, speed, min, max, format, flags)) {
+ Reflect.setProperty(object, field, i.value);
+ didChange = true;
+ }
+ return didChange;
+ }
+ inline function checkboxField(name:String, field:String, object:Dynamic) {
+ var didChange:Bool = false;
+ ImGui.tableNextRow();
+ ImGui.tableSetColumnIndex(0);
+ ImGui.text(name);
+ ImGui.tableSetColumnIndex(1);
+ var b = boolPool.get(); b.value = Reflect.getProperty(object, field);
+ if (ImGui.checkbox("##" + name + field, b)) {
+ Reflect.setProperty(object, field, b.value);
+ didChange = true;
+ }
+ return didChange;
+ }
+ inline function textField(name:String, text:String) {
+ ImGui.tableNextRow();
+ ImGui.tableSetColumnIndex(0);
+ ImGui.text(name);
+ ImGui.tableSetColumnIndex(1);
+ ImGui.text(text);
+ }
+ inline function colorField(name:String, field:String, object:Dynamic, flags:ImGuiColorEditFlags = 0) {
+ var didChange:Bool = false;
+ ImGui.tableNextRow();
+ ImGui.tableSetColumnIndex(0);
+ ImGui.text(name);
+ ImGui.tableSetColumnIndex(1);
+ var wid = ImGui.getContentRegionAvail().x;
+ ImGui.setNextItemWidth(wid);
+
+ var color:FlxColor = Reflect.getProperty(object, field);
+ var float4 = float4Pool.get();
+ float4.values[0] = color.redFloat;
+ float4.values[1] = color.greenFloat;
+ float4.values[2] = color.blueFloat;
+ float4.values[3] = color.alphaFloat;
+ if (ImGui.colorEdit4("##" + name + field, float4, flags)) {
+ Reflect.setProperty(object, field, FlxColor.fromRGBFloat(float4.values[0], float4.values[1], float4.values[2], float4.values[3]));
+ didChange = true;
+ }
+ return didChange;
+ }
+ inline function inputTextField(name:String, field:String, object:Dynamic, flags:ImGuiInputTextFlags = 0) {
+
+ ImGui.tableNextRow();
+ ImGui.tableSetColumnIndex(0);
+ ImGui.text(name);
+ ImGui.tableSetColumnIndex(1);
+
+ var wid = ImGui.getContentRegionAvail().x;
+ ImGui.setNextItemWidth(wid);
+
+ var s = stringPool.get();
+ s.value = Reflect.getProperty(object, field);
+ if (ImGui.inputText("##" + name + field, s, flags)) {
+ Reflect.setProperty(object, field, s.value);
+ }
+ }
+ inline function inputTextFieldMultiline(name:String, field:String, object:Dynamic, width:Float, height:Float, flags:ImGuiInputTextFlags = 0) {
+ ImGui.tableNextRow();
+ ImGui.tableSetColumnIndex(0);
+ ImGui.text(name);
+ ImGui.tableSetColumnIndex(1);
+ var s = stringPool.get();
+ s.value = Reflect.getProperty(object, field);
+ if (ImGui.inputTextMultiline("##" + name + field, s, width, height, flags)) {
+ Reflect.setProperty(object, field, s.value);
+ }
+ }
+ inline function enumFieldString(name:String, field:String, object:Dynamic, list:Array) { //not working
+ var index = intPool.get();
+ index.value = list.indexOf(Std.string(Reflect.getProperty(object, field)));
+ if (ImGui.combo(name + "##" + field, index, list)) {
+ Reflect.setProperty(object, field, list[index.value]);
+ }
+ }
+ inline function enumAbstractField(name:String, field:String, object:Dynamic, type:String) {
+
+ ImGui.tableNextRow();
+ ImGui.tableSetColumnIndex(0);
+ ImGui.text(name);
+ ImGui.tableSetColumnIndex(1);
+
+ var t = Type.resolveClass(type + "_HSC");
+ if (t != null) {
+ var wid = ImGui.getContentRegionAvail().x;
+ ImGui.setNextItemWidth(wid);
+
+ var curValue = Reflect.getProperty(object, field);
+ var index = intPool.get();
+
+ var fields = Type.getClassFields(t);
+ var filteredFields:Array = [];
+ for (f in fields) {
+ if (!f.startsWith("from") && !f.startsWith("to")) filteredFields.push(f);
+ }
+ index.value = -1;
+ for (i => f in filteredFields) {
+ if (Reflect.getProperty(t, f) == curValue) index.value = i;
+ }
+
+ if (ImGui.combo("##" + name + field, index, filteredFields)) {
+ Reflect.setProperty(object, field, Reflect.getProperty(t, filteredFields[index.value]));
+ }
+
+ }
+ }
+ #end
+}
+
+#if IMGUI_ENABLED
+//quick class that handles imgui pointers for temp values
+class ImGuiPtrPool {
+ var members:Array = [];
+ var used:Int = 0;
+ var constructor:Void->T;
+ public function new(constructor:Void->T) {
+ this.constructor = constructor;
+ }
+ public function reset() {
+ used = 0;
+ }
+ public function get():T {
+ if (used >= members.length) {
+ members.push(constructor());
+ }
+ var obj = members[used];
+ used++;
+ return obj;
+ }
+}
+
+//https://github.com/ocornut/imgui/blob/master/imgui_demo.cpp#L841
+class ImGuiImageViewer {
+ var gridEnabled:ImGuiBoolPtr = new ImGuiBoolPtr(false);
+ public var viewReset:Bool = true;
+ var viewOffsetX:Float = 0;
+ var viewOffsetY:Float = 0;
+ var zoom:ImGuiFloatPtr = new ImGuiFloatPtr(10.0);
+ var zoom100:ImGuiFloatPtr = new ImGuiFloatPtr(10.0);
+ var zoomMin:Float = 0.1;
+ var zoomMax:Float = 10000;
+
+ public function new() {}
+
+ public function drawOptions() {
+ ImGui.setNextItemWidth(150);
+ zoom100.value = zoom.value * 100;
+ if (ImGui.dragFloat("Zoom", zoom100, 5.0, zoomMin * 100.0, zoomMax * 100, "%.0f%%", ImGuiSliderFlags.AlwaysClamp))
+ zoom.value = zoom100.value / 100.0;
+ }
+
+ public function drawCanvas(canvas_size_x:Float, canvas_size_y:Float, image_tex_ref:ImTextureID, image_w:Int, image_h:Int) {
+ var drawList = ImGui.getWindowDrawList();
+ ImGui.invisibleButton("##Canvas", canvas_size_x, canvas_size_y);
+ var canvas_min = ImGui.getItemRectMin();
+ var canvas_max = ImGui.getItemRectMax();
+
+ if (viewReset) {
+ var xZoom = canvas_size_x / image_w;
+ var yZoom = canvas_size_y / image_h;
+ zoom.value = (image_w > image_h ? xZoom : yZoom);
+ viewOffsetX = (canvas_size_x * 0.5 / xZoom) - 0.5;
+ viewOffsetY = (canvas_size_y * 0.5 / yZoom) - 0.5;
+ }
+ viewReset = false;
+
+ if (ImGui.setItemKeyOwner(ImGuiKey.MouseWheelY)) {
+ if (ImGuiIO.mouseWheel != 0.0) {
+ zoom.value = FlxMath.bound(zoom.value * (1.0 + ImGuiIO.mouseWheel * 0.10), zoomMin, zoomMax);
+ }
+ }
+ var zoomValue = zoom.value;
+ if (ImGui.isItemActive() && ImGui.isMouseDragging(0)) {
+ viewOffsetX -= ImGuiIO.mouseDeltaX / zoomValue;
+ viewOffsetY -= ImGuiIO.mouseDeltaY / zoomValue;
+ }
+
+ var minX:Float = Std.int((canvas_min.x - (viewOffsetX * zoomValue)) + (canvas_size_x * 0.5));
+ var minY:Float = Std.int((canvas_min.y - (viewOffsetY * zoomValue)) + (canvas_size_y * 0.5));
+ var maxX:Float = Std.int(minX + image_w * zoomValue);
+ var maxY:Float = Std.int(minY + image_h * zoomValue);
+ drawList.addRect([canvas_min.x - 1.0, canvas_min.y - 1.0, canvas_max.x + 1.0, canvas_max.y + 1.0], 0xFFFFFFFF);
+ drawList.pushClipRect(canvas_min.x, canvas_min.y, canvas_max.x, canvas_max.y, true);
+ drawList.addRectFilled([minX, minY, maxX, maxY], 0xFF646464);
+ drawList.addImage(image_tex_ref, [minX, minY, maxX, maxY]);
+
+ if (gridEnabled.value && zoomValue > 6.0)
+ {
+ var step:Float = zoomValue;
+ for (px in Std.int((canvas_min.x - minX) / step)...Std.int((canvas_max.x - minX) / step)) {
+ drawList.addLineV(minX + px * step, canvas_min.y, canvas_max.y, 0x64FFFFFF, 1.0);
+ }
+ for (py in Std.int((canvas_min.y - minY) / step)...Std.int((canvas_max.y - minY) / step)) {
+ drawList.addLineH(canvas_min.x, canvas_max.x, minY + py * step, 0x64FFFFFF, 1.0);
+ }
+ }
+ drawList.popClipRect();
+ }
+
+
+}
+#end
\ No newline at end of file
diff --git a/source/funkin/backend/system/framerate/AssetTreeInfo.hx b/source/funkin/backend/system/framerate/AssetTreeInfo.hx
index 4e63cec327..50984bd885 100644
--- a/source/funkin/backend/system/framerate/AssetTreeInfo.hx
+++ b/source/funkin/backend/system/framerate/AssetTreeInfo.hx
@@ -16,7 +16,7 @@ class AssetTreeInfo extends FramerateCategory {
visible = Options.fpsCounterAssets;
}
- public override function __enterFrame(t:Int) {
+ public override function __enterFrame(t:Float) {
if (alpha <= 0.05) return;
if ((lastUpdateTime += FlxG.rawElapsed) < 1)
diff --git a/source/funkin/backend/system/framerate/CodenameBuildField.hx b/source/funkin/backend/system/framerate/CodenameBuildField.hx
index 656c0fff9c..57bf752a2a 100644
--- a/source/funkin/backend/system/framerate/CodenameBuildField.hx
+++ b/source/funkin/backend/system/framerate/CodenameBuildField.hx
@@ -6,14 +6,17 @@ import openfl.text.TextField;
class CodenameBuildField extends TextField {
public function new() {
super();
- defaultTextFormat = Framerate.textFormat;
autoSize = LEFT;
multiline = wordWrap = false;
reload();
}
public function reload() {
- #if COMPILE_EXPERIMENTAL
+ defaultTextFormat = Framerate.textFormat;
+
+ #if TEST_BUILD
+ text = '${Flags.VERSION_MESSAGE} (Test Build)';
+ #elseif COMPILE_EXPERIMENTAL
text = '${Flags.VERSION_MESSAGE} (Experimental Build)';
#else
text = '${Flags.VERSION_MESSAGE}';
diff --git a/source/funkin/backend/system/framerate/ConductorInfo.hx b/source/funkin/backend/system/framerate/ConductorInfo.hx
index a63729e2ae..c1c68daefe 100644
--- a/source/funkin/backend/system/framerate/ConductorInfo.hx
+++ b/source/funkin/backend/system/framerate/ConductorInfo.hx
@@ -8,7 +8,7 @@ class ConductorInfo extends FramerateCategory {
visible = Options.fpsCounterConductor;
}
- public override function __enterFrame(t:Int) {
+ public override function __enterFrame(t:Float) {
if (alpha <= 0.05) return;
var buf = new StringBuf();
diff --git a/source/funkin/backend/system/framerate/FlixelInfo.hx b/source/funkin/backend/system/framerate/FlixelInfo.hx
index 05b8409996..42d3347f6b 100644
--- a/source/funkin/backend/system/framerate/FlixelInfo.hx
+++ b/source/funkin/backend/system/framerate/FlixelInfo.hx
@@ -9,7 +9,7 @@ class FlixelInfo extends FramerateCategory {
visible = Options.fpsCounterFlixel;
}
- public override function __enterFrame(t:Int) {
+ public override function __enterFrame(t:Float) {
if (alpha <= 0.05) return;
@:privateAccess {
diff --git a/source/funkin/backend/system/framerate/Framerate.hx b/source/funkin/backend/system/framerate/Framerate.hx
index 6ab4c12955..8787478563 100644
--- a/source/funkin/backend/system/framerate/Framerate.hx
+++ b/source/funkin/backend/system/framerate/Framerate.hx
@@ -83,6 +83,7 @@ class Framerate extends Sprite {
}
public function reload() {
+ textFormat = new TextFormat(fontName, 12, -1);
for(c in categories)
c.reload();
#if SHOW_BUILD_ON_FPS
@@ -107,7 +108,7 @@ class Framerate extends Sprite {
var debugAlpha:Float = 0;
- public override function __enterFrame(t:Int) {
+ public override function __enterFrame(t:Float) {
alpha = CoolUtil.fpsLerp(alpha, debugMode > 0 ? 1 : 0, 0.5);
debugAlpha = CoolUtil.fpsLerp(debugAlpha, debugMode > 1 ? 1 : 0, 0.5);
diff --git a/source/funkin/backend/system/framerate/FramerateCategory.hx b/source/funkin/backend/system/framerate/FramerateCategory.hx
index fddc40e06a..9a87f4783b 100644
--- a/source/funkin/backend/system/framerate/FramerateCategory.hx
+++ b/source/funkin/backend/system/framerate/FramerateCategory.hx
@@ -39,9 +39,11 @@ class FramerateCategory extends Sprite {
this.text.y = this.title.y + this.title.height + 2;
}
- public function reload() {}
+ public function reload() {
+ for(label in [this.title, this.text]) label.defaultTextFormat = new TextFormat(Framerate.fontName, label == this.title ? 18 : 12, -1);
+ }
- public override function __enterFrame(t:Int) {
+ public override function __enterFrame(t:Float) {
if (alpha <= 0.05) return;
super.__enterFrame(t);
diff --git a/source/funkin/backend/system/framerate/FramerateCounter.hx b/source/funkin/backend/system/framerate/FramerateCounter.hx
index 191440c230..cebfa9f51f 100644
--- a/source/funkin/backend/system/framerate/FramerateCounter.hx
+++ b/source/funkin/backend/system/framerate/FramerateCounter.hx
@@ -46,7 +46,7 @@ class FramerateCounter extends Sprite {
lastUpdateTime = 0;
}
- public override function __enterFrame(t:Int) {
+ public override function __enterFrame(t:Float) {
if (alpha <= 0.001) return;
super.__enterFrame(t);
diff --git a/source/funkin/backend/system/framerate/MemoryCounter.hx b/source/funkin/backend/system/framerate/MemoryCounter.hx
index 39962e2399..0530d0bb7b 100644
--- a/source/funkin/backend/system/framerate/MemoryCounter.hx
+++ b/source/funkin/backend/system/framerate/MemoryCounter.hx
@@ -23,7 +23,7 @@ class MemoryCounter extends Sprite {
label.y = 0;
label.text = "MEM";
label.multiline = label.wordWrap = false;
- label.defaultTextFormat = new TextFormat(Framerate.fontName, 12, -1);
+ label.defaultTextFormat = Framerate.textFormat;
label.selectable = false;
addChild(label);
}
@@ -33,11 +33,13 @@ class MemoryCounter extends Sprite {
#end
}
- public function reload() {}
+ public function reload() {
+ for(label in [memoryText, memoryPeakText]) label.defaultTextFormat = Framerate.textFormat;
+ }
private var usingLegacy:Bool = false;
- public override function __enterFrame(t:Int) {
+ public override function __enterFrame(t:Float) {
if (alpha <= 0.05) return;
super.__enterFrame(t);
diff --git a/source/funkin/backend/system/framerate/StatsInfo.hx b/source/funkin/backend/system/framerate/StatsInfo.hx
index 1dc6c3968e..63ef47270c 100644
--- a/source/funkin/backend/system/framerate/StatsInfo.hx
+++ b/source/funkin/backend/system/framerate/StatsInfo.hx
@@ -11,7 +11,7 @@ class StatsInfo extends FramerateCategory {
visible = Options.fpsCounterStats;
}
- public override function __enterFrame(t:Int) {
+ public override function __enterFrame(t:Float) {
if (alpha <= 0.05) return;
var buf = new StringBuf();
diff --git a/source/funkin/backend/system/framerate/SystemInfo.hx b/source/funkin/backend/system/framerate/SystemInfo.hx
index ce3fdaf280..603c06477a 100644
--- a/source/funkin/backend/system/framerate/SystemInfo.hx
+++ b/source/funkin/backend/system/framerate/SystemInfo.hx
@@ -182,7 +182,7 @@ class SystemInfo extends FramerateCategory {
visible = Options.fpsCounterSystem;
}
- public override function __enterFrame(t:Int) {
+ public override function __enterFrame(t:Float) {
if (alpha <= 0.05) return;
var buf = new StringBuf();
diff --git a/source/funkin/backend/system/macros/Macros.hx b/source/funkin/backend/system/macros/Macros.hx
index 801eda0c17..bc830a2fff 100644
--- a/source/funkin/backend/system/macros/Macros.hx
+++ b/source/funkin/backend/system/macros/Macros.hx
@@ -19,9 +19,28 @@ class Macros {
"flixel.addons.plugin", "flixel.addons.text", "flixel.addons.tile", "flixel.addons.transition",
"flixel.addons.util",
// OTHER LIBRARIES & STUFF
- #if THREE_D_SUPPORT "away3d", "flx3d", #end
+ #if THREE_D_SUPPORT
+ /*
+ supposed to be deprecated but
+ were keeping them in the source for now
+ commenting them out here will make them
+ not show up during compiling....
+ */
+ // "away3d", "flx3d",
+ #if foxlite
+ "foxlite",
+ "foxlite.animation", "foxlite.color", "foxlite.culling",
+ "foxlite.extra", "foxlite.flixel", "foxlite.funkin",
+ "foxlite.groups", "foxlite.instancing", "foxlite.lights",
+ "foxlite.loaders", "foxlite.materials", "foxlite.math",
+ "foxlite.mesh", "foxlite.polyfill", "foxlite.post",
+ "foxlite.renderer", "foxlite.skin", "foxlite.sky",
+ "foxlite.stencil", "foxlite.system", "foxlite.texture",
+ #end
+ #end
#if VIDEO_CUTSCENES "hxvlc.flixel", "hxvlc.openfl", #end
#if NAPE_ENABLED "nape", "flixel.addons.nape", #end
+ #if IMGUI_ENABLED "lime.tools.imgui", #end
// BASE HAXE
"DateTools", "EReg", "Lambda", "StringBuf", "haxe.crypto", "haxe.display", "haxe.exceptions", "haxe.extern", "scripting", "animate"
])
@@ -58,6 +77,9 @@ class Macros {
final macroPath = 'funkin.backend.system.macros.Macros';
Compiler.addMetadata('@:build($macroPath.buildLimeAssetLibrary())', 'lime.utils.AssetLibrary');
+ Compiler.addMetadata('@:build($macroPath.buildLimeApplication())', 'lime.app.Application');
+ Compiler.addMetadata('@:build($macroPath.buildLimeWindow())', 'lime.ui.Window');
+ Compiler.addMetadata('@:build($macroPath.buildOpenflAssets())', 'openfl.utils.Assets');
//Adds Compat for #if hscript blocks when you have hscript improved
if (Context.defined("hscript_improved") && !Context.defined("hscript")) {
@@ -73,5 +95,58 @@ class Macros {
return fields;
}
+
+ public static function buildLimeApplication():Array {
+ final fields:Array = Context.getBuildFields(), pos:Position = Context.currentPos();
+ for (f in fields) switch (f.kind) {
+ case FFun(func): switch (f.name) {
+ case "exec": switch (func.expr.expr) {
+ case EBlock(exprs): exprs.insert(1, macro funkin.backend.system.Main.preInit());
+ default:
+ }
+ }
+ default:
+ }
+
+ return fields;
+ }
+
+ public static function buildLimeWindow():Array {
+ final fields:Array = Context.getBuildFields(), pos:Position = Context.currentPos();
+ if (!Context.defined("DARK_MODE_WINDOW")) return fields;
+
+ for (f in fields) switch (f.kind) {
+ case FFun(func): switch (f.name) {
+ case "new": switch (func.expr.expr) {
+ case EBlock(exprs): exprs.push(macro funkin.backend.utils.NativeAPI.setDarkMode(title, true));
+ default:
+ }
+ }
+ default:
+ }
+
+ return fields;
+ }
+
+ public static function buildOpenflAssets():Array {
+ final fields:Array = Context.getBuildFields(), pos:Position = Context.currentPos();
+ for (f in fields) switch (f.name) {
+ case "allowHardwareTextures": fields.remove(f);
+ default:
+ }
+
+ fields.push({name: 'allowHardwareTextures', access: [APublic, AStatic], pos: pos, kind: FProp("get", "set", macro :Bool)});
+ fields.push({name: '__allowHardwareTextures', access: [APrivate, AStatic], pos: pos, kind: FVar(macro :Null)});
+
+ fields.push({name: "get_allowHardwareTextures", access: [APublic, AStatic, AInline], pos: pos, kind: FFun({ret: macro :Bool, args: [], expr: macro {
+ return __allowHardwareTextures != null ? __allowHardwareTextures : !funkin.backend.system.Main.forceGPUOnlyBitmapsOff && funkin.options.Options.gpuOnlyBitmaps;
+ }})});
+ fields.push({name: "set_allowHardwareTextures", access: [APublic, AStatic, AInline], pos: pos, kind: FFun({ret: macro :Bool, args: [{name: "value", type: macro :Bool}], expr: macro {
+ __allowHardwareTextures = value;
+ return get_allowHardwareTextures();
+ }})});
+
+ return fields;
+ }
}
#end
\ No newline at end of file
diff --git a/source/funkin/backend/system/modules/ALSoftConfig.hx b/source/funkin/backend/system/modules/ALSoftConfig.hx
deleted file mode 100644
index 0b1b57a035..0000000000
--- a/source/funkin/backend/system/modules/ALSoftConfig.hx
+++ /dev/null
@@ -1,31 +0,0 @@
-package funkin.backend.system.modules;
-
-import haxe.io.Path;
-
-/*
- * A class that simply points OpenALSoft to a custom configuration file when
- * the game starts up.
- *
- * The config overrides a few global OpenALSoft settings with the aim of
- * improving audio quality on desktop targets.
- */
-@:keepInit class ALSoftConfig
-{
- #if desktop
- static function __init__():Void
- {
- var origin:String = #if hl Sys.getCwd() #else Sys.programPath() #end;
-
- var configPath:String = Path.directory(Path.withoutExtension(origin));
- #if windows
- configPath += "/plugins/alsoft.ini";
- #elseif mac
- configPath = Path.directory(configPath) + "/Resources/plugins/alsoft.conf";
- #else
- configPath += "/plugins/alsoft.conf";
- #end
-
- Sys.putEnv("ALSOFT_CONF", configPath);
- }
- #end
-}
\ No newline at end of file
diff --git a/source/funkin/backend/system/modules/AudioSwitchFix.hx b/source/funkin/backend/system/modules/AudioSwitchFix.hx
deleted file mode 100644
index 3e89e373b4..0000000000
--- a/source/funkin/backend/system/modules/AudioSwitchFix.hx
+++ /dev/null
@@ -1,69 +0,0 @@
-package funkin.backend.system.modules;
-
-import flixel.FlxState;
-import flixel.sound.FlxSound;
-import funkin.backend.utils.NativeAPI;
-import lime.media.AudioManager;
-import lime.media.AudioSource;
-import haxe.Timer;
-
-/**
- * if you are stealing this keep this comment at least please lol
- *
- * hi gray itsa me yoshicrafter29 i fixed it hehe
- */
-@:dox(hide)
-class AudioSwitchFix {
- public static function onAudioDisconnected() @:privateAccess {
- var sources:Array<{source:AudioSource, playing:Bool, time:Float, gain:Float, pitch:Float, position:lime.math.Vector4}> = [];
- for (source in AudioSource.activeSources) {
- var wasPlaying = source.playing;
- sources.push({
- source: source,
- playing: wasPlaying,
- time: source.currentTime,
- gain: source.gain,
- pitch: source.pitch,
- position: source.position
- });
-
- source.__backend.dispose();
- if (wasPlaying) source.__backend.playing = true;
- }
-
- AudioManager.shutdown();
- AudioManager.init();
- // #if !lime_doc_gen
- // if (AudioManager.context.type == OPENAL)
- // {
- // var alc = AudioManager.context.openal;
-
- // var device = alc.openDevice();
- // var ctx = alc.createContext(device);
- // alc.makeContextCurrent(ctx);
- // alc.processContext(ctx);
- // }
- // #end
-
- for (d in sources) {
- d.source.__backend.init();
- d.source.currentTime = d.time;
- d.source.gain = d.gain;
- d.source.pitch = d.pitch;
- d.source.position = d.position;
-
- if (d.playing) d.source.play();
- }
-
- Main.changeID++;
- Main.audioDisconnected = false;
- }
-
- private static var timer:Timer;
-
- private static function onRun() if (Main.audioDisconnected) onAudioDisconnected();
- public static function init() {
- NativeAPI.registerAudio();
- if (timer == null) (timer = new Timer(1000)).run = onRun;
- }
-}
\ No newline at end of file
diff --git a/source/funkin/backend/utils/AudioAnalyzer.hx b/source/funkin/backend/utils/AudioAnalyzer.hx
index c6f02cd6b9..74f4c11b98 100644
--- a/source/funkin/backend/utils/AudioAnalyzer.hx
+++ b/source/funkin/backend/utils/AudioAnalyzer.hx
@@ -1,67 +1,94 @@
package funkin.backend.utils;
-import flixel.sound.FlxSound;
-import lime.media.AudioBuffer;
+#if lime_openal
+import sys.thread.Mutex;
+
import lime.utils.ArrayBufferView.ArrayBufferIO;
import lime.utils.ArrayBuffer;
-#if (lime_cffi && lime_vorbis)
-import lime.media.vorbis.Vorbis;
-import lime.media.vorbis.VorbisFile;
-#end
+import flixel.sound.FlxSound;
+import flixel.sound.FlxSoundData;
-#if (target.threaded)
-import sys.thread.Mutex;
-#end
+typedef ReadCallback = Int->Int->Void;
+typedef WindowFunction = Float->Float;
+
+final class WindowFunctions {
+ static inline final TWO_PI:Float = 6.283185307179586;
+ static inline final FOUR_PI:Float = 12.566370614359172;
+ static inline final SIX_PI:Float = 18.84955592153876;
+ static inline final EIGHT_PI:Float = 25.132741228718345;
+
+ public static inline function triangular(x:Float):Float
+ return 1.0 - Math.abs(x - 0.5) * 2.0;
-typedef AudioAnalyzerCallback = Int->Int->Void;
+ public static inline function hann(x:Float):Float
+ return 0.5 - 0.5 * FlxMath.fastCos(TWO_PI * x);
+
+ public static inline function hamming(x:Float):Float
+ return 0.53836 - 0.46164 * FlxMath.fastCos(TWO_PI * x);
+
+ public static inline function blackmanNuttall(x:Float):Float
+ return 0.3635819 - 0.4891775 * FlxMath.fastCos(TWO_PI * x) + 0.1365995 * FlxMath.fastCos(FOUR_PI * x)
+ - 0.0106411 * FlxMath.fastCos(SIX_PI * x);
+
+ public static inline function blackmanHarris(x:Float):Float
+ return 0.4243801 - 0.4973406 * FlxMath.fastCos(TWO_PI * x) + 0.0782793 * FlxMath.fastCos(FOUR_PI * x);
+
+ public static inline function flatTop(x:Float):Float
+ return 0.21557895 - 0.41663158 * FlxMath.fastCos(TWO_PI * x) + 0.277263158 * FlxMath.fastCos(FOUR_PI * x)
+ + 0.083578947 * FlxMath.fastCos(SIX_PI * x) + 0.006947368 * FlxMath.fastCos(EIGHT_PI * x);
+}
+
+enum abstract TimeUnit(Int) from Int to Int {
+ var MILLISECOND = 0;
+ var SECOND = 1;
+ var SAMPLE = 2;
+}
/**
- * An utility that analyze FlxSounds,
+ * An utility that analyze FlxSound,
* can be used to make waveform or real-time audio visualizer.
- *
- * FlxSound.amplitude works so if any case if your only checking for peak of current time, use that instead.
*/
final class AudioAnalyzer {
/**
* Get bytes from an audio buffer with specified position and wordSize
- * @param buffer The audio buffer to get byte from.
- * @param position The specified position to get the byte from the audio buffer.
- * @param wordSize How many bytes to get with to one byte (Usually it's bitsPerSample / 8 or bitsPerSample >> 3).
+ * @param buffer The audio buffer to get byte from.
+ * @param position The specified position to get the byte from the audio buffer.
+ * @param wordSize How many bytes to get with to one byte (Usually it's bitsPerSample / 8 or bitsPerSample >> 3).
* @return Byte from the audio buffer with specified position.
*/
public static function getByte(buffer:ArrayBuffer, position:Int, wordSize:Int):Int {
- if (wordSize == 2) return inline ArrayBufferIO.getInt16(buffer, position);
+ if (wordSize == 2) return ArrayBufferIO.getInt16(buffer, position);
else if (wordSize == 3) {
- var b = inline ArrayBufferIO.getUint16(buffer, position) | (buffer.get(position + 2) << 16);
- if (b & 0x800000 != 0) return b - 0x1000000;
- else return b;
+ wordSize = ArrayBufferIO.getUint16(buffer, position) | (buffer.get(position + 2) << 16);
+ if (wordSize & 0x800000 != 0) return wordSize - 0x1000000;
+ else return wordSize;
}
- else if (wordSize == 4) return inline ArrayBufferIO.getInt32(buffer, position);
- else return inline ArrayBufferIO.getUint8(buffer, position) - 128;
+ else if (wordSize == 4) return ArrayBufferIO.getInt32(buffer, position);
+ else return ArrayBufferIO.getInt8(buffer, position);
}
/**
- * Gets levels from the frequencies with specified sample rate.
- * @param frequencies Frequencies input.
- * @param sampleRate Sample Rate input.
- * @param barCount How much bars to get.
- * @param levels The output for getting the values, to avoid memory leaks (Optional).
- * @param ratio How much ratio for smoothen the values from the previous levels values (Optional, use CoolUtil.getFPSRatio(1 - ratio) to simulate web AnalyserNode.smoothingTimeConstant, 0.35 of smoothingTime works most of the time).
- * @param minDb The minimum decibels to cap (Optional, default -63.0, -120 is pure silence).
- * @param maxDb The maximum decibels to cap (Optional, default -10.0, Above 0 is not recommended).
- * @param minFreq The minimum frequency to cap (Optional, default 20.0, Below 8.0 is not recommended).
- * @param maxFreq The maximum frequency to cap (Optional, default 22000.0, Above 23000.0 is not recommended).
- * @return Output of levels/bars that ranges from 0 to 1.
+ * Gets spectrum from the frequencies with specified sample rate.
+ * @param frequencies Frequencies input.
+ * @param sampleRate Sample Rate input.
+ * @param barCount How much bars to get.
+ * @param spectrum The output for getting the values, to avoid memory leaks (Optional).
+ * @param ratio How much ratio for smoothen the values from the previous spectrum values (Optional, use FlxMath.getElapsedLerp(1 - ratio) to simulate web AnalyserNode.smoothingTimeConstant, 0.35 of smoothingTime works most of the time).
+ * @param minDb The minimum decibels to cap (Optional, default -63.0, -120 is pure silence).
+ * @param maxDb The maximum decibels to cap (Optional, default -10.0, Above 0 is not recommended).
+ * @param minFreq The minimum frequency to cap (Optional, default 20.0, Below 8.0 is not recommended).
+ * @param maxFreq The maximum frequency to cap (Optional, default 20000.0, Above 23000.0 is not recommended).
+ * @return Output of spectrum/bars that ranges from 0 to 1.
*/
- public static function getLevelsFromFrequencies(frequencies:Array, sampleRate:Int, barCount:Int, ?levels:Array, ratio = 0.0, minDb = -63.0, maxDb = -10.0, minFreq = 20.0, maxFreq = 22000.0):Array {
- if (levels == null) levels = [];
- levels.resize(barCount);
+ public static function getSpectrumFromFrequencies(frequencies:Array, sampleRate:Int, barCount:Int, ?spectrum:Array, ratio = 0.0, minDb = -63.0, maxDb = -10.0, minFreq = 20.0, maxFreq = 20000.0):Array {
+ if (spectrum == null) spectrum = [];
+ if (spectrum.length != barCount) spectrum.resize(barCount);
- var logMin = Math.log(minFreq), logMax = Math.log(maxFreq);
- var logRange = logMax - logMin, dbRange = maxDb - minDb, n = frequencies.length;
+ var logMin = Math.log(minFreq), n = frequencies.length - 1;
+ var logRange = Math.log(maxFreq) - logMin, dbRangeRate = 1 / (maxDb - minDb), rate = frequencies.length * 2 / sampleRate;
inline function calculateScale(i:Int)
- return CoolUtil.bound(Math.exp(logMin + (logRange * i / (barCount + 1))) * n * 2 / sampleRate, 0, n - 1);
+ return FlxMath.bound(Math.exp(logMin + (logRange * i / (barCount + 1))) * rate, 0, n);
var s1 = calculateScale(0), s2;
var i1 = Math.floor(s1), i2;
@@ -81,138 +108,146 @@ final class AudioAnalyzer {
}
i1 = Math.floor(s1 = s2);
- v = CoolUtil.bound(((20 * Math.log(v) / 2.302585092994046) - minDb) / dbRange, 0, 1);
- if (ratio > 0 && ratio < 1 && v < levels[i]) levels[i] -= (levels[i] - v) * ratio;
- else levels[i] = v;
+ v = FlxMath.bound((Math.log(v) * 8.685889638065035 - minDb) * dbRangeRate, 0, 1);
+ if (ratio > 0 && ratio < 1 && v < spectrum[i]) spectrum[i] -= (spectrum[i] - v) * ratio;
+ else spectrum[i] = v;
}
- return levels;
+ return spectrum;
}
- static var __reverseIndices:Array> = [];
- static var __windows:Array> = [];
- static var __twiddleReals:Array> = [];
- static var __twiddleImags:Array> = [];
- static var __freqReals:Array> = [];
- static var __freqImags:Array> = [];
- static var __freqCalculating:Int = 0;
- #if (target.threaded)
- static var __mutex:Mutex = new Mutex();
- #end
+ /**
+ * Gets levels from the frequencies with specified sample rate.
+ * @param frequencies Frequencies input.
+ * @param sampleRate Sample Rate input.
+ * @param barCount How much bars to get.
+ * @param levels The output for getting the values, to avoid memory leaks (Optional).
+ * @param ratio How much ratio for smoothen the values from the previous levels values (Optional, use CoolUtil.getFPSRatio(1 - ratio) to simulate web AnalyserNode.smoothingTimeConstant, 0.35 of smoothingTime works most of the time).
+ * @param minDb The minimum decibels to cap (Optional, default -63.0, -120 is pure silence).
+ * @param maxDb The maximum decibels to cap (Optional, default -10.0, Above 0 is not recommended).
+ * @param minFreq The minimum frequency to cap (Optional, default 20.0, Below 8.0 is not recommended).
+ * @param maxFreq The maximum frequency to cap (Optional, default 22000.0, Above 23000.0 is not recommended).
+ * @return Output of levels/bars that ranges from 0 to 1.
+ *
+ * deprecated, use getSpectrumFromFrequencies instead.
+ */
+ @:deprecated("Use getSpectrumFromFrequencies instead of getLevelsFromFrequencies.")
+ public static function getLevelsFromFrequencies(frequencies:Array, sampleRate:Int, barCount:Int, ?levels:Array, ratio = 0.0, minDb = -63.0, maxDb = -10.0, minFreq = 20.0, maxFreq = 22000.0):Array
+ return inline getSpectrumFromFrequencies(frequencies, sampleRate, barCount, levels, ratio, minDb, maxDb, minFreq, maxFreq);
+
+ static final _permutations:Map> = [];
+ static final _twiddleReals:Map> = [];
+ static final _twiddleImags:Map> = [];
+ static final _reals:Array> = [];
+ static final _imags:Array> = [];
+ static var _freqCalculating:Int = 0;
+ static final _mutex = new Mutex();
/**
* Gets frequencies from the samples.
- * @param samples The samples (can be from AudioAnalyzer.getSamples).
- * @param fftN How much samples for the fft to get, Has to be power of two, or it won't work.
- * @param useWindowing Should fft related stuff use blackman windowing? (Web AnalyzerNode windowing), Most of the time it's not worth it.
- * @param frequencies The output for getting the frequencies, to avoid memory leaks (Optional).
- * @return Output of frequencies.
+ * @param samples The samples (can be from FunkinAudioAnalyzer.getSamples).
+ * @param window The windowing function to use when passed.
+ * @param frequencies The output for getting the frequencies, to avoid memory leaks (Optional).
+ * @return Output of frequencies.
*/
- public static function getFrequenciesFromSamples(samples:Array, fftN = 2048, useWindowing = false, ?frequencies:Array):Array {
- var log = Math.floor(Math.log(fftN) / 0.6931471805599453);
- if (log == 0) throw "AudioAnalyzer.getFrequenciesFromSamples: Cannot insert a fftN of 1";
-
- var i = log - 1;
- fftN = 1 << log;
-
- #if (target.threaded) __mutex.acquire(); #end
- var reals:Array = __freqReals[__freqCalculating], imags:Array = __freqImags[__freqCalculating];
- if (reals == null) {
- __freqReals.push(reals = []);
- __freqImags.push(imags = []);
- }
- __freqCalculating++;
+ public static function getFrequenciesFromSamples(samples:Array, ?window:WindowFunction, ?frequencies:Array, ?fftN:Int):Array {
+ if (fftN == null) fftN = samples.length;
- var reverseIndices:Array = __reverseIndices[i];
- var windows:Array = __windows[i];
- var twiddleReals:Array = __twiddleReals[i];
- var twiddleImags:Array = __twiddleImags[i];
+ var bits = 0;
+ while ((fftN >>= 1) > 0) bits++;
+ if (bits == 0) throw "FunkinAudioAnalyzer.getFrequenciesFromSamples: Cannot insert a sample length or fftN of 1";
- if (reverseIndices == null) {
- __reverseIndices.resize(log);
- __windows.resize(log);
- __twiddleReals.resize(log);
- __twiddleImags.resize(log);
+ fftN = 1 << bits;
+ var fftN2 = fftN >> 1, n = fftN - 1;
- (reverseIndices = []).resize(fftN);
- (windows = []).resize(fftN);
- (twiddleReals = []).resize(fftN);
- (twiddleImags = []).resize(fftN);
+ var permutation:Array, twiddleReal:Array, twiddleImag:Array;
+ _mutex.acquire();
- var f;
+ var real:Array = _reals[_freqCalculating], imag:Array = _imags[_freqCalculating];
+ if (real == null) {
+ _reals.push(real = []);
+ _imags.push(imag = []);
+ }
+ _freqCalculating++;
+
+ if (_permutations.exists(bits)) {
+ permutation = _permutations.get(bits);
+ twiddleReal = _twiddleReals.get(bits);
+ twiddleImag = _twiddleImags.get(bits);
+ }
+ else {
+ (permutation = []).resize(fftN);
+ (twiddleReal = []).resize(fftN2);
+ (twiddleImag = []).resize(fftN2);
+
+ var ang:Float;
for (i in 0...fftN) {
- f = 2 * Math.PI * (i / fftN);
- windows[i] = 0.42 - 0.5 * Math.cos(f) + 0.08 * Math.cos(2 * f);
- reverseIndices[i] = __bitReverse(i, log);
- twiddleReals[i] = Math.cos(-f);
- twiddleImags[i] = Math.sin(-f);
+ permutation[i] = _bitReverse(i, bits);
+ if (i < fftN2) {
+ twiddleReal[i] = Math.cos((ang = -6.283185307179586 * i / n));
+ twiddleImag[i] = Math.sin(ang);
+ }
}
- __reverseIndices[i] = reverseIndices;
- __windows[i] = windows;
- __twiddleReals[i] = twiddleReals;
- __twiddleImags[i] = twiddleImags;
+ _permutations.set(bits, permutation);
+ _twiddleReals.set(bits, twiddleReal);
+ _twiddleImags.set(bits, twiddleImag);
}
- #if (target.threaded) __mutex.release(); #end
+ _mutex.release();
- if (fftN > reals.length) {
- reals.resize(fftN);
- imags.resize(fftN);
+ if (fftN > real.length) {
+ real.resize(fftN);
+ imag.resize(fftN);
}
if (frequencies == null) frequencies = [];
- frequencies.resize(1 << i);
+ if (frequencies.length != fftN2) frequencies.resize(fftN2);
- i = samples.length;
- while (i > 0) {
- i--;
- if (useWindowing) reals[reverseIndices[i]] = samples[i] * windows[i];
- else reals[reverseIndices[i]] = samples[i];
- imags[i] = 0;
+ var tr = 1 / n;
+ for (i in 0...fftN) {
+ real[permutation[i]] = samples[i];
+ if (window != null) real[permutation[i]] *= window(i * tr);
+ imag[i] = 0;
}
- var size = 1, n = fftN, half = 1, k, i0, i1, t, tr:Float, ti:Float;
- while ((size <<= 1) < fftN) {
- n >>= 1;
- i = 0;
- while (i < fftN) {
- k = 0;
- while (k < half) {
- i1 = (i0 = i + k) + half;
- t = (k * n) % fftN;
-
- tr = reals[i1] * twiddleReals[t] - imags[i1] * twiddleImags[t];
- ti = reals[i1] * twiddleImags[t] + imags[i1] * twiddleReals[t];
- reals[i1] = reals[i0] - tr;
- imags[i1] = imags[i0] - ti;
- reals[i0] += tr;
- imags[i0] += ti;
-
- k++;
+ var half = 1, g:Int, b:Int, r:Int, i0:Int, i1:Int, ti:Float;
+ while (fftN2 > 0) {
+ g = 0;
+ while (g < fftN) {
+ b = r = 0;
+ while (b < half) {
+ i1 = (i0 = g + b) + half;
+ tr = real[i1] * twiddleReal[r] - imag[i1] * twiddleImag[r];
+ ti = real[i1] * twiddleImag[r] + imag[i1] * twiddleReal[r];
+ real[i1] = real[i0] - tr;
+ imag[i1] = imag[i0] - ti;
+ real[i0] += tr;
+ imag[i0] += ti;
+ b++;
+ r += fftN2;
}
- i += size;
+ g += half << 1;
}
- half = size;
+ half <<= 1;
+ fftN2 >>= 1;
}
tr = 1.0 / fftN;
- i = 1 << (log - 1);
- while (i > 1) {
- i--;
- frequencies[i] = 2 * Math.sqrt(reals[i] * reals[i] + imags[i] * imags[i]) * tr;
- }
- frequencies[0] = Math.sqrt(reals[0] * reals[0] + imags[0] * imags[0]) * tr;
+ i0 = frequencies.length - 1;
+ for (i in 1...i0) frequencies[i] = 2 * Math.sqrt(real[i] * real[i] + imag[i] * imag[i]) * tr;
+ frequencies[0] = Math.sqrt(real[0] * real[0] + imag[0] * imag[0]) * tr;
+ frequencies[i0] = Math.sqrt(real[i0] * real[i0] + imag[i0] * imag[i0]) * tr;
- #if (target.threaded) __mutex.acquire(); #end
- __freqCalculating--;
- #if (target.threaded) __mutex.release(); #end
+ _mutex.acquire();
+ _freqCalculating--;
+ _mutex.release();
return frequencies;
}
- static function __bitReverse(x:Int, log:Int):Int {
- var y = 0, i = log;
+ static function _bitReverse(x:Int, bits:Int):Int {
+ var y = 0, i = bits;
while (i > 0) {
y = (y << 1) | (x & 1);
x >>= 1;
@@ -227,346 +262,357 @@ final class AudioAnalyzer {
public var sound:FlxSound;
/**
- * How much samples for the fft to get.
- * Usually for getting the levels or frequencies of the sound.
- *
- * Has to be power of two, or it won't work.
- */
- public var fftN:Int;
-
- /**
- * Should fft related stuff use blackman windowing? (Web AnalyzerNode windowing).
- * Most of the time looks bad with this.
+ * The current data from sound.
*/
- public var useWindowingFFT:Bool;
+ public var data(default, null):FlxSoundData;
/**
- * The current buffer from sound.
+ * How much samples for the fourier transform to get.
+ * Has to be power of two, or it won't work.
*/
- public var buffer(default, null):AudioBuffer;
+ public var fftN:Int;
/**
* The current byteSize from buffer.
- * Example the byteSize of 16 BitsPerSample is 32768 (1 << 16-1)
+ * Example the byteSize of 16 BitsPerSample is 32768 (1 << (16 - 1))
*/
public var byteSize(default, null):Int;
- var __toBits:Float;
- var __wordSize:Int;
- var __sampleSize:Int;
-
- #if (lime_cffi && lime_vorbis)
- var __vorbis:VorbisFile;
- var __buffer:ArrayBuffer;
- var __bufferSize:Int;
- var __bufferLastSize:Int;
- var __bufferTime:Float;
- var __bufferLastTime:Float;
- #end
-
- // analyze
- var __min:Array = [];
- var __max:Array = [];
- var __minByte:Int;
- var __maxByte:Int;
-
- // samples
- var __sampleIndex:Int;
- var __sampleChannel:Int;
- var __sampleToValue:Float;
- var __sampleOutputMerge:Bool;
- var __sampleOutputLength:Int;
- var __sampleOutput:Array;
-
- // frequencies
- var __freqSamples:Array;
- var __frequencies:Array;
-
- /**
- * Creates an analyzer for specified FlxSound
- * @param sound An FlxSound to analyze.
- * @param fftN How much samples for fft to get (Optional, default 2048, 4096 is recommended for highest quality).
- * @param useWindowingFFT Should fft related stuff use blackman windowing? (Web AnalyzerNode windowing).
- */
- public function new(sound:FlxSound, fftN = 2048, useWindowingFFT = false) {
+ var _sampleSize:Int;
+ var _mins:Array = [];
+ var _maxs:Array = [];
+ //var _decoder:FunkinAudioDecoder;
+ //var _buffer:ArrayBuffer;
+ //var _bufferLen:Int;
+ //var _bufferLastSize:Int;
+ //var _bufferLastSample:Int;
+ var _sampleIndex:Int;
+ var _sampleChannel:Int;
+ var _sampleValue:Int;
+ var _sampleValueGain:Float;
+ var _sampleOutputMerge:Bool;
+ var _sampleOutputLength:Int;
+ var _sampleOutput:Array;
+ var _freqSamples:Array;
+ var _frequencies:Array;
+
+ public function new(sound:FlxSound, fftN = 4096) {
this.sound = sound;
this.fftN = fftN;
- this.useWindowingFFT = useWindowingFFT;
- __check();
+ _check();
}
- function __check() if (sound != null && sound.buffer != buffer) {
- byteSize = 1 << ((buffer = sound.buffer).bitsPerSample - 1);
-
- #if (lime_cffi && lime_vorbis)
- __vorbis = null;
- __bufferLastSize = 0;
- __bufferTime = Math.NaN;
- __bufferLastTime = Math.NaN;
- #end
+ function _check() {
+ if (sound != null && !sound.data.isDestroyed) {
+ if (sound.data != data)
+ {
+ byteSize = 1 << ((data = sound.data).bitsPerSample - 1);
+ _sampleSize = data.channels * (data.bitsPerSample >> 3);
+ _mins.resize(data.channels);
+ _maxs.resize(data.channels);
+ //_decoder?.destroy();
+ }
+ }
+ else data = null;
+ }
- __toBits = buffer.sampleRate / 1000 * (__sampleSize = buffer.channels * (__wordSize = buffer.bitsPerSample >> 3));
- __min.resize(buffer.channels);
- __max.resize(buffer.channels);
+ /**
+ * Gets spectrum from an attached sound from position.
+ * @param pos Position to get (Optional).
+ * @param timeUnit TimeUnit to use for positions (Optional).
+ * @param gain How much gain multiplier will it affect the output. (Optional, default 1.0).
+ * @param barCount How much bars to get.
+ * @param spectrum The output for getting the values, to avoid memory leaks (Optional).
+ * @param ratio How much ratio for smoothen the values from the previous spectrum values (Optional, use FlxMath.getElapsedLerp(1 - ratio) to simulate web AnalyserNode.smoothingTimeConstant, 0.35 of smoothingTime works most of the time).
+ * @param minDb The minimum decibels to cap (Optional, default -63.0, -120 is pure silence).
+ * @param maxDb The maximum decibels to cap (Optional, default -10.0, Above 0 is not recommended).
+ * @param minFreq The minimum frequency to cap (Optional, default 20.0, Below 8.0 is not recommended).
+ * @param maxFreq The maximum frequency to cap (Optional, default 20000.0, Above 23000.0 is not recommended).
+ * @return Output of spectrum/bars that ranges from 0 to 1.
+ */
+ public function getSpectrum(?pos:Float, ?timeUnit:TimeUnit, ?gain:Float, ?window:WindowFunction, barCount:Int, ?spectrum:Array, ?ratio:Float, ?minDb:Float, ?maxDb:Float, ?minFreq:Float, ?maxFreq:Float):Array {
+ return getSpectrumFromFrequencies(_frequencies = getFrequencies(pos, timeUnit, gain, window, _frequencies), data.sampleRate, barCount, spectrum, ratio, minDb, maxDb, minFreq, maxFreq);
}
/**
* Gets levels from an attached FlxSound from startPos, basically a minimized of frequencies.
- * @param startPos Start Position to get from sound in milliseconds.
- * @param volume How much volume multiplier will it affect the output. (Optional, default 1.0).
- * @param barCount How much bars to get.
- * @param levels The output for getting the values, to avoid memory leaks (Optional).
- * @param ratio How much ratio for smoothen the values from the previous levels values (Optional, use CoolUtil.getFPSRatio(1 - ratio) to simulate web AnalyserNode.smoothingTimeConstant, 0.35 of smoothingTime works most of the time).
- * @param minDb The minimum decibels to cap (Optional, default -63.0, -120 is pure silence).
- * @param maxDb The maximum decibels to cap (Optional, default -10.0, Above 0 is not recommended).
- * @param minFreq The minimum frequency to cap (Optional, default 20.0, Below 8.0 is not recommended).
- * @param maxFreq The maximum frequency to cap (Optional, default 22000.0, Above 23000.0 is not recommended).
- * @return Output of levels/bars that ranges from 0 to 1.
+ * @param startPos Start Position to get from sound in milliseconds.
+ * @param volume How much volume multiplier will it affect the output. (Optional, default 1.0).
+ * @param barCount How much bars to get.
+ * @param levels The output for getting the values, to avoid memory leaks (Optional).
+ * @param ratio How much ratio for smoothen the values from the previous levels values (Optional, use CoolUtil.getFPSRatio(1 - ratio) to simulate web AnalyserNode.smoothingTimeConstant, 0.35 of smoothingTime works most of the time).
+ * @param minDb The minimum decibels to cap (Optional, default -63.0, -120 is pure silence).
+ * @param maxDb The maximum decibels to cap (Optional, default -10.0, Above 0 is not recommended).
+ * @param minFreq The minimum frequency to cap (Optional, default 20.0, Below 8.0 is not recommended).
+ * @param maxFreq The maximum frequency to cap (Optional, default 22000.0, Above 23000.0 is not recommended).
+ * @return Output of levels/bars that ranges from 0 to 1.
+ *
+ * deprecated, use getLevels instead.
*/
+ @:deprecated("Use getSpectrum instead of getLevels.")
public function getLevels(?startPos:Float, ?volume:Float, barCount:Int, ?levels:Array, ?ratio:Float, ?minDb:Float, ?maxDb:Float, ?minFreq:Float, ?maxFreq:Float):Array
- return inline getLevelsFromFrequencies(__frequencies = getFrequencies(startPos, volume, __frequencies), buffer.sampleRate, barCount, levels, ratio, minDb, maxDb, minFreq, maxFreq);
+ return inline getSpectrum(startPos, MILLISECOND, volume, null, barCount, levels, ratio, minDb, maxDb, minFreq, maxFreq);
/**
- * Gets frequencies from an attached FlxSound from startPos.
- * @param startPos Start Position to get from sound in milliseconds.
- * @param volume How much volume multiplier will it affect the output. (Optional, default 1.0).
- * @param frequencies The output for getting the frequencies, to avoid memory leaks (Optional).
- * @return Output of frequencies.
+ * Gets frequencies from an attached sound from position.
+ * @param pos Position to get. (Optional).
+ * @param timeUnit TimeUnit to use for positions. (Optional).
+ * @param gain How much gain multiplier will it affect the output. (Optional, default 1.0).
+ * @param window The windowing function to use when passed.
+ * @param frequencies The output for getting the frequencies, to avoid memory leaks (Optional).
+ * @return Output of frequencies.
*/
- public function getFrequencies(?startPos:Float, ?volume:Float, ?frequencies:Array):Array
- return inline getFrequenciesFromSamples(__freqSamples = getSamples(startPos != null ? startPos : sound.time, fftN, true, -1, volume, __freqSamples), fftN, useWindowingFFT, frequencies);
+ public function getFrequencies(?pos:Float, ?timeUnit:TimeUnit, ?gain:Float, ?window:WindowFunction, ?frequencies:Array):Array {
+ if (pos == null) {
+ if (sound == null) return frequencies;
+ _check();
+ if ((pos = sound.time / 1000 * data.sampleRate - fftN) < 0) pos = 0;
+ timeUnit = SAMPLE;
+ }
+ return getFrequenciesFromSamples(_freqSamples = getSamples(pos, timeUnit, fftN, true, -1, gain, _freqSamples), window, frequencies);
+ }
/**
- * Analyzes an attached FlxSound from startPos to endPos in milliseconds to get the amplitudes.
- * @param startPos Start Position to get from sound in milliseconds.
- * @param endPos End Position to get from sound in milliseconds.
- * @param outOrOutMin The output minimum value from the analyzer, indices is in channels (0 to -0.5 -> 0 to 0.5) (Optional, if outMax doesn't get passed in, it will be [min, max] with all channels combined instead).
- * @param outMax The output maximum value from the analyzer, indices is in channels (Optional).
- * @return Output of amplitude from given position.
+ * Analyzes an attached sound from startPos to endPos in milliseconds to get the amplitudes.
+ * @param startPos Start Position to get.
+ * @param endPos End Position to get.
+ * @param timeUnit TimeUnit to use for positions.
+ * @param outOrOutMins The output minimum value from the analyzer, indices is in channels (0 to -0.5 -> 0 to 0.5) (Optional, if outMax doesn't get passed in, it will be [min, max] with all channels combined instead).
+ * @param outMaxs The output maximum value from the analyzer, indices is in channels (Optional).
+ * @return Output of amplitude from given position.
*/
- public function analyze(startPos:Float, endPos:Float, ?outOrOutMin:Array, ?outMax:Array):Float {
- var hasOut = outOrOutMin != null;
- var hasTwoOut = hasOut && outMax != null;
-
- if (hasTwoOut) for (i in 0...buffer.channels) __min[i] = __max[i] = 0;
- __minByte = __maxByte = 0;
-
- __check();
- __read(startPos, endPos, hasTwoOut ? __analyzeCallback : __analyzeCallbackSimple);
-
- if (hasOut) {
- var f:Float;
- if (hasTwoOut) for (i in 0...buffer.channels) {
- if (outOrOutMin[i] < (f = __min[i] / byteSize)) outOrOutMin[i] = f;
- if (outMax[i] < (f = __max[i] / byteSize)) outMax[i] = f;
- }
- else {
- outOrOutMin.resize(2);
- if (outOrOutMin[0] < (f = __minByte / byteSize)) outOrOutMin[0] = f;
- if (outOrOutMin[1] < (f = __maxByte / byteSize)) outOrOutMin[1] = f;
+ public function analyze(startPos:Float, endPos:Float, ?timeUnit:TimeUnit, ?outOrOutMins:Array, ?outMaxs:Array):Float {
+ var hasOut = outOrOutMins != null;
+ var hasTwoOut = hasOut && outMaxs != null;
+
+ _check();
+ var conversion:Float = switch (timeUnit) {
+ case SAMPLE: 1;
+ case SECOND: data.sampleRate;
+ default: data.sampleRate / 1000;
+ }
+ for (i in 0...data.channels) _mins[i] = _maxs[i] = -0x7FFFFFFF;
+ if (startPos < endPos) _read(Math.floor(startPos * conversion), Math.floor(endPos * conversion), _analyzeRead);
+
+ var min = -0x7FFFFFFF, max = -0x7FFFFFFF, v = 1 / byteSize, f:Float;
+ for (i in 0...data.channels) {
+ if (hasTwoOut) {
+ if ((f = _mins[i] * v) > outOrOutMins[i]) outOrOutMins[i] = f;
+ if ((f = _maxs[i] * v) > outMaxs[i]) outMaxs[i] = f;
}
+ if (_maxs[i] > max) max = _maxs[i];
+ if (_mins[i] > min) min = _mins[i];
}
- return (__maxByte + __minByte) / byteSize;
+ if (hasOut && outMaxs == null) {
+ if ((f = min * v) > outOrOutMins[0]) outOrOutMins[0] = f;
+ if ((f = max * v) > outOrOutMins[1]) outOrOutMins[1] = f;
+ }
+ return (max + min) * v;
}
- function __analyzeCallback(b:Int, c:Int):Void
- ((b > __max[c]) ? (if ((__max[c] = b) > __maxByte) (__maxByte = b)) : (if (-b > __min[c]) (if ((__min[c] = -b) > __minByte) (__minByte = __min[c]))));
-
- function __analyzeCallbackSimple(b:Int, c:Int):Void
- ((b > __maxByte) ? (__maxByte = b) : (if (-b > __minByte) (__minByte = -b)));
+ function _analyzeRead(b:Int, c:Int) ((b > _maxs[c]) ? (_maxs[c] = b) : (if (-b > _mins[c]) (_mins[c] = -b)));
/**
* Gets samples from startPos with given length of samples.
- * @param startPos Start Position to get from sound in milliseconds.
- * @param length Length of Samples.
- * @param mono Merge all of the byte channels of samples in one channel instead (Optional).
- * @param channel What channels to get from? (-1 == All Channels, Optional, this will be ignored if mono is enabled).
- * @param volume How much volume multiplier will it affect the output. (Optional, default 1.0).
- * @param output An Output that gets passed into this function, usually for to avoid memory leaks (Optional).
- * @param outputMerge Merge with previous values (Optional, default false).
- * @return Output of samples.
+ * @param startPos Start Position to get.
+ * @param timeUnit TimeUnit to use for positions.
+ * @param length Length of Samples.
+ * @param mono Merge all of the byte channels of samples in one channel instead (Optional).
+ * @param channel What channels to get from? (-1 == All Channels, Optional, this will be ignored if mono is enabled).
+ * @param gain How much gain multiplier will it affect the output. (Optional, default 1.0).
+ * @param output An Output that gets passed into this function, usually for to avoid memory leaks (Optional).
+ * @param outputMerge Merge with previous values (Optional, default false).
+ * @return Output of samples.
*/
- public function getSamples(startPos:Float, length:Int, mono = true, channel = -1, volume = 1.0, ?output:Array, ?outputMerge = false):Array {
- ((!mono && (__sampleChannel = channel) == -1) ? (__sampleOutputLength = length * buffer.channels) : (__sampleOutputLength = length));
- ((output == null) ? (__sampleOutput = output = []) : (__sampleOutput = output)).resize(__sampleOutputLength);
- ((mono) ? (__sampleToValue = volume / (byteSize * buffer.channels)) : (__sampleToValue = 1.0 / byteSize));
- __sampleOutputMerge = outputMerge;
- __sampleIndex = 0;
-
- __check();
- __read(startPos, startPos + (length / __toBits * buffer.channels), mono ? __getSamplesCallbackMono : (channel == -1 ? __getSamplesCallback : __getSamplesCallbackChannel));
-
- __sampleOutput = null;
+ public function getSamples(startPos:Float, ?timeUnit:TimeUnit, length:Int, mono = true, channel = -1, gain = 1.0, ?output:Array, ?outputMerge = false):Array {
+ _check();
+ ((!mono && channel == -1) ? (_sampleOutputLength = length * data.channels) : (_sampleOutputLength = length));
+ if (((output == null) ? (_sampleOutput = output = []) : (_sampleOutput = output)).length != _sampleOutputLength) output.resize(_sampleOutputLength);
+ _sampleValueGain = gain;
+ _sampleOutputMerge = outputMerge;
+ _sampleIndex = 0;
+ _sampleValue = 0;
+
+ final samplePos = Math.floor(switch (timeUnit) {
+ case SAMPLE: startPos;
+ case SECOND: startPos * data.sampleRate;
+ default: startPos * data.sampleRate / 1000;
+ });
+ _sampleChannel = mono ? data.channels - 1 : channel;
+ if (length > 0) _read(samplePos, samplePos + length, mono ? _getSamplesCallbackMono : (channel == -1 ? _getSamplesCallback : _getSamplesCallbackChannel));
+
+ _sampleOutput = null;
return output;
}
- function __getSamplesCallbackMono(b:Int, c:Int):Void if (__sampleIndex < __sampleOutputLength) {
- if (c == 0) {
- if (__sampleOutputMerge) __sampleOutput[__sampleIndex] += b * __sampleToValue;
- else __sampleOutput[__sampleIndex] = b * __sampleToValue;
- }
- else if (c == buffer.channels) {
- __sampleOutput[__sampleIndex] += b * __sampleToValue;
- __sampleIndex++;
+ function _getSamplesCallbackMono(b:Int, c:Int):Void if (_sampleIndex < _sampleOutputLength) {
+ if (c == 0) _sampleValue = idiv(b, data.channels);
+ else _sampleValue += idiv(b, data.channels);
+
+ if (c == _sampleChannel) {
+ if (_sampleOutputMerge) _sampleOutput[_sampleIndex] += _sampleValue / byteSize;
+ else _sampleOutput[_sampleIndex] = _sampleValue / byteSize;
+ _sampleIndex++;
}
- else
- __sampleOutput[__sampleIndex] += b * __sampleToValue;
}
- function __getSamplesCallbackChannel(b:Int, c:Int):Void if (__sampleIndex < __sampleOutputLength) {
- if (c == __sampleChannel) {
- if (__sampleOutputMerge) __sampleOutput[__sampleIndex] += b * __sampleToValue;
- else __sampleOutput[__sampleIndex] = b * __sampleToValue;
- __sampleIndex++;
+ function _getSamplesCallbackChannel(b:Int, c:Int):Void if (_sampleIndex < _sampleOutputLength) {
+ if (c == _sampleChannel) {
+ if (_sampleOutputMerge) _sampleOutput[_sampleIndex] += b / byteSize;
+ else _sampleOutput[_sampleIndex] = b / byteSize;
+ _sampleIndex++;
}
}
- function __getSamplesCallback(b:Int, c:Int):Void if (__sampleIndex < __sampleOutputLength) {
- if (__sampleOutputMerge) __sampleOutput[__sampleIndex] += b * __sampleToValue;
- else __sampleOutput[__sampleIndex] = b * __sampleToValue;
- __sampleIndex++;
+ function _getSamplesCallback(b:Int, c:Int):Void if (_sampleIndex < _sampleOutputLength) {
+ if (_sampleOutputMerge) _sampleOutput[_sampleIndex] += b / byteSize;
+ else _sampleOutput[_sampleIndex] = b / byteSize;
+ _sampleIndex++;
}
/**
- * Read an attached FlxSound from startPos to endPos in milliseconds with a callback.
- * @param startPos Start Position to get from sound in milliseconds.
- * @param endPos End Position to get from sound in milliseconds.
- * @param callback Int->Int->Void Byte->Channels->Void Callback to get the byte of a sample.
+ * Read an attached sound from startPos to endPos in milliseconds with a callback.
+ * @param startPos Start Position to get.
+ * @param endPos End Position to get.
+ * @param timeUnitTimeUnit to use for positions.
+ * @param callback Byte:Int->Channels:Int->Void Callback to get the byte of a sample.
*/
- public function read(startPos:Float, endPos:Float, callback:AudioAnalyzerCallback) {
- __check();
- __read(startPos, endPos, callback);
+ public function read(startPos:Float, endPos:Float, ?timeUnit:TimeUnit, callback:ReadCallback) {
+ _check();
+ var conversion:Float = switch (timeUnit) {
+ case SAMPLE: 1;
+ case SECOND: data.sampleRate;
+ default: data.sampleRate / 1000;
+ }
+ if (startPos < endPos) _read(Math.floor(startPos * conversion), Math.floor(endPos * conversion), callback);
}
- inline function __read(startPos:Float, endPos:Float, callback:AudioAnalyzerCallback) {
- if (buffer.data != null) __readData(startPos, endPos, callback);
- #if lime_cffi
- else if (__canReadStream() && (startPos += __readStream(startPos, endPos, callback)) >= endPos) {}
- #if lime_vorbis
- else if (__prepareDecoder()) __readDecoder(startPos, endPos, callback);
- #end
- #end
- }
+ function _read(startSample:Int, endSample:Int, callback:ReadCallback) {
+ // use data in ram if available
+ if (data.buffer.data != null) _readData(startSample * _sampleSize, endSample * _sampleSize, callback);
+ // use decoded datas that have been used in streaming sound to reduce jumping disk seeking
+ // if not use decoder and use seeking instead*
+ else if (sound.loaded) _readStream(startSample, endSample, callback);
- inline function __readData(startPos:Float, endPos:Float, callback:AudioAnalyzerCallback) {
- var pos = Math.floor(startPos * __toBits), end = Math.min(Math.floor(endPos * __toBits), buffer.data.buffer.length), c = 0;
- pos -= pos % __sampleSize;
- end -= end % __sampleSize;
+ // TODO
+ //else if ((!sound.loaded || (startSample = _readStream(startSample, endSample, callback)) < endSample) && _prepareDecoder())
+ // _readDecoder(startSample, endSample, callback);
+ }
- while (pos < end) {
- callback(getByte(buffer.data.buffer, pos, __wordSize), c);
- if (++c > buffer.channels) c = 0;
- pos += __wordSize;
+ inline function _readData(startIndex:Int, endIndex:Int, callback:ReadCallback) {
+ if (endIndex > data.buffer.data.byteLength) endIndex = data.buffer.data.byteLength;
+ var buffer = data.buffer.data.buffer, byteRate = data.bitsPerSample >> 3, c = 0;
+ while (startIndex < endIndex) {
+ callback(getByte(buffer, startIndex, byteRate), c);
+ startIndex += byteRate;
+ if (++c == data.channels) c = 0;
}
}
- #if lime_cffi
- inline function __canReadStream():Bool
- @:privateAccess return sound._source != null && sound._source.__backend != null && sound._source.__backend.playing;
-
- inline function __readStream(startPos:Float, endPos:Float, callback:AudioAnalyzerCallback):Float @:privateAccess {
- final backend = sound._source.__backend;
-
- // TODO: Wrap it with try until i figured it out an effective way to do this...
- // So... sometimes it just uses the decoder even if it looks good?? please help
- var n = Math.floor((endPos - startPos) * __toBits);
- var i = backend.bufferLengths.length - backend.requestBuffers - 1, time:Float;
- while (++i < backend.bufferLengths.length) if (startPos >= (time = backend.bufferTimes[i] * 1000)) {
- var pos = Math.floor((startPos - time) * __toBits), buf = backend.bufferDatas[i].buffer, size = backend.bufferLengths[i], c = 0;
- while (pos >= size) {
- if (++i >= backend.bufferLengths.length) break;
- pos -= size;
- buf = backend.bufferDatas[i].buffer;
- size = backend.bufferLengths[i];
- }
- if (i >= backend.bufferLengths.length) break;
- if ((pos -= pos % __sampleSize) < 0) pos = 0;
- n -= pos % __sampleSize;
-
- while (n > 0) {
- callback(getByte(buf, pos, __wordSize), c);
- if (++c > buffer.channels) c = 0;
- if ((pos += __wordSize) >= size) {
- if (++i >= backend.bufferLengths.length) break;
- pos = 0;
- buf = backend.bufferDatas[i].buffer;
- size = backend.bufferLengths[i];
+ function _readStream(startSample:Int, endSample:Int, callback:ReadCallback):Int @:privateAccess {
+ final backend = sound.source.__backend;
+ if (backend.filledBuffers == 0) return startSample;
+
+ backend.mutex.acquire();
+
+ final max = backend.bufferViews.length;
+ var byteRate = data.bitsPerSample >> 3, i = max - backend.queuedBuffers, buffer:ArrayBuffer, bufferLen:Int, bufferSample:Int, pos:Int, c:Int;
+
+ while (i < max && startSample < endSample) {
+ if (startSample >= (bufferSample = backend.bufferCurs[i])) {
+ if ((pos = (startSample - bufferSample) * _sampleSize) < (bufferLen = backend.bufferLens[i])) {
+ buffer = backend.bufferViews[i].buffer;
+ c = 0;
+ while (startSample < endSample) {
+ callback(getByte(buffer, pos, byteRate), c);
+ if ((pos += byteRate) >= bufferLen) {
+ startSample++;
+ break;
+ }
+ else if (++c == data.channels) {
+ c = 0;
+ startSample++;
+ }
+ }
}
- n -= __wordSize;
}
-
- break;
+ i++;
}
- return endPos - (n / __toBits);
+ backend.mutex.release();
+
+ return startSample;
}
- #if lime_vorbis
- inline function __prepareDecoder():Bool @:privateAccess {
- if (buffer.__srcVorbisFile == null) return __vorbis != null;
- if (__vorbis != null) return true;
- if ((__vorbis = buffer.__srcVorbisFile.clone()) != null) { // IM HOPING IT HAVE A GC CLOSURE.
- __buffer = new ArrayBuffer(__bufferSize = (buffer.sampleRate >> 1) * __sampleSize); // 0.5 seconds of buffers.
+ // TODO: Fix this and _readDecoder in the future.
+ inline function _prepareDecoder():Bool {
+ return false;
+ /*
+ if (_decoder != null) return true;
+ if (data.decoder != null && (_decoder = data.decoder.clone()) != null) {
+ _bufferLen = (data.sampleRate >> 2) * _sampleSize;
+ #if cpp
+ if (_buffer != null) {
+ if (_buffer.length < _bufferLen) {
+ _buffer.getData().resize(_bufferLen);
+ _buffer.fill(_buffer.length, _bufferLen - _buffer.length, 0);
+ @:privateAccess _buffer.length = _bufferLen;
+ }
+ }
+ else
+ #end
+ _buffer = new ArrayBuffer(_bufferLen);
return true;
}
return false;
+ */
}
- inline function __readDecoder(startPos:Float, endPos:Float, callback:AudioAnalyzerCallback) {
- var n = Math.floor((endPos - startPos) * __toBits);
- if ((n -= n % __sampleSize) > 0) {
- var pos = Math.floor((startPos - __bufferTime * 1000) * __toBits);
- pos -= pos % __sampleSize;
-
- var doRead = __bufferLastSize == 0 || (pos < 0 && pos >= __bufferSize);
- if (doRead) {
- if (startPos < 1) {
- __vorbis.rawSeek(0);
- __bufferTime = 0;
- }
- else
- __vorbis.timeSeek(__bufferTime = startPos / 1000);
+ /*
+ function _readDecoder(startSample:Int, endSample:Int, callback:ReadCallback) {
+ var pos = (startSample - _bufferLastSample) * _sampleSize, n = endSample - startSample, c = 0;
- __bufferLastSize = pos = 0;
- }
+ var doDecode = _bufferLastSize == 0 || (pos < 0 && pos >= _bufferLastSize);
+ if (doDecode) {
+ _decoder.seek(startSample);
+ _bufferLastSize = pos = 0;
+ doDecode = true;
+ }
- var isBigEndian = lime.system.System.endianness == lime.system.Endian.BIG_ENDIAN, ranOut = false, c = 0, result;
- while (true) {
- if (doRead) {
- result = __vorbis.read(__buffer, pos, __bufferSize - pos, isBigEndian, __wordSize, true);
- if (result == Vorbis.HOLE) continue;
- else if (result < 0) break;
- else if (!(ranOut = result == 0)) {
- __bufferLastTime = __vorbis.timeTell();
- __bufferLastSize += result;
- while (pos < __bufferLastSize) {
- callback(getByte(__buffer, pos, __wordSize), c);
- if (++c > buffer.channels) c = 0;
- pos += __wordSize;
- if ((n -= __wordSize) <= 0) break;
- }
+ var result:Int;
+ while (n > 0) {
+ if (doDecode) {
+ _bufferLastSample = _decoder.tell();
+ result = _decoder.decode(_buffer, pos, _bufferLen - pos);
+ if (result == 0) break;
+
+ _bufferLastSize += result;
+ while (n > 0) {
+ callback(getByte(_buffer, pos, data.byteRate), c);
+ if (++c == data.channels) {
+ c = 0;
+ n--;
}
+ if ((pos += data.byteRate) >= _bufferLastSize) break;
}
- else {
- while (pos < __bufferLastSize) {
- callback(getByte(__buffer, pos, __wordSize), c);
- if (++c > buffer.channels) c = 0;
- pos += __wordSize;
- if ((n -= __wordSize) <= 0) break;
+ }
+ else {
+ while (n > 0) {
+ callback(getByte(_buffer, pos, data.byteRate), c);
+ if (++c == data.channels) {
+ c = 0;
+ n--;
}
- doRead = true;
- ranOut = pos >= __bufferSize;
- }
-
- if (n <= 0) break;
- else if (doRead && ranOut) {
- __bufferLastSize = pos = 0;
- __bufferTime = __bufferLastTime;
+ if ((pos += data.byteRate) >= _bufferLastSize) break;
}
+ doDecode = true;
+ _bufferLastSize = pos = 0;
}
}
}
- #end
- #end
-}
\ No newline at end of file
+ */
+
+ static inline function idiv(num:Int, denom:Int):Int return #if (cpp && !cppia) cpp.NativeMath.idiv(num, denom) #else Std.int(num / denom) #end;
+}
+#end
\ No newline at end of file
diff --git a/source/funkin/backend/utils/CoolUtil.hx b/source/funkin/backend/utils/CoolUtil.hx
index 382c476046..e7adfd9f1d 100644
--- a/source/funkin/backend/utils/CoolUtil.hx
+++ b/source/funkin/backend/utils/CoolUtil.hx
@@ -987,15 +987,13 @@ final class CoolUtil
* Returns the screen position of an object, while taking the camera zoom into account.
*
* @param object Any `FlxObject`
- * @param camera The desired "screen" coordinate space. If `null`, `FlxG.camera` is used.
+ * @param camera The desired "screen" coordinate space. If `null`, a default camera is used.
* @param result Optional arg for the returning point
* @return The screen position of the object.
*/
public static function worldToScreenPosition(object:FlxObject, ?camera:FlxCamera, ?result:FlxPoint) {
- if (result == null)
- result = FlxPoint.get();
- if (camera == null)
- camera = FlxG.camera;
+ if (result == null) result = FlxPoint.get();
+ if (camera == null) camera = object.getDefaultCamera();
result.set(object.x, object.y);
result.x = (((result.x - camera.scroll.x * object.scrollFactor.x) * camera.zoom) - ((camera.width * 0.5) * (camera.zoom - camera.initialZoom)));
diff --git a/source/funkin/backend/utils/ImGuiUtil.hx b/source/funkin/backend/utils/ImGuiUtil.hx
new file mode 100644
index 0000000000..119eebb91d
--- /dev/null
+++ b/source/funkin/backend/utils/ImGuiUtil.hx
@@ -0,0 +1,127 @@
+package funkin.backend.utils;
+
+import flixel.input.keyboard.FlxKey;
+import openfl.Lib;
+#if IMGUI_ENABLED
+import lime.tools.imgui.ImGuiFlags;
+import lime.tools.imgui.ImGuiTypes;
+#end
+
+class ImGuiUtil {
+ #if IMGUI_ENABLED
+ public static function toImGuiKey(key:FlxKey) {
+ switch(key) {
+ case FlxKey.ANY: return ImGuiKey.None;
+ case FlxKey.NONE: return ImGuiKey.None;
+ case FlxKey.A: return ImGuiKey.A;
+ case FlxKey.B: return ImGuiKey.B;
+ case FlxKey.C: return ImGuiKey.C;
+ case FlxKey.D: return ImGuiKey.D;
+ case FlxKey.E: return ImGuiKey.E;
+ case FlxKey.F: return ImGuiKey.F;
+ case FlxKey.G: return ImGuiKey.G;
+ case FlxKey.H: return ImGuiKey.H;
+ case FlxKey.I: return ImGuiKey.I;
+ case FlxKey.J: return ImGuiKey.J;
+ case FlxKey.K: return ImGuiKey.K;
+ case FlxKey.L: return ImGuiKey.L;
+ case FlxKey.M: return ImGuiKey.M;
+ case FlxKey.N: return ImGuiKey.N;
+ case FlxKey.O: return ImGuiKey.O;
+ case FlxKey.P: return ImGuiKey.P;
+ case FlxKey.Q: return ImGuiKey.Q;
+ case FlxKey.R: return ImGuiKey.R;
+ case FlxKey.S: return ImGuiKey.S;
+ case FlxKey.T: return ImGuiKey.T;
+ case FlxKey.U: return ImGuiKey.U;
+ case FlxKey.V: return ImGuiKey.V;
+ case FlxKey.W: return ImGuiKey.W;
+ case FlxKey.X: return ImGuiKey.X;
+ case FlxKey.Y: return ImGuiKey.Y;
+ case FlxKey.Z: return ImGuiKey.Z;
+ case FlxKey.ZERO: return ImGuiKey._0;
+ case FlxKey.ONE: return ImGuiKey._1;
+ case FlxKey.TWO: return ImGuiKey._2;
+ case FlxKey.THREE: return ImGuiKey._3;
+ case FlxKey.FOUR: return ImGuiKey._4;
+ case FlxKey.FIVE: return ImGuiKey._5;
+ case FlxKey.SIX: return ImGuiKey._6;
+ case FlxKey.SEVEN: return ImGuiKey._7;
+ case FlxKey.EIGHT: return ImGuiKey._8;
+ case FlxKey.NINE: return ImGuiKey._9;
+ case FlxKey.PAGEUP: return ImGuiKey.PageUp;
+ case FlxKey.PAGEDOWN: return ImGuiKey.PageDown;
+ case FlxKey.HOME: return ImGuiKey.Home;
+ case FlxKey.END: return ImGuiKey.End;
+ case FlxKey.INSERT: return ImGuiKey.Insert;
+ case FlxKey.ESCAPE: return ImGuiKey.Escape;
+ case FlxKey.MINUS: return ImGuiKey.Minus;
+ case FlxKey.PLUS: return ImGuiKey.Equal;
+ case FlxKey.DELETE: return ImGuiKey.Delete;
+ case FlxKey.BACKSPACE: return ImGuiKey.Backspace;
+ case FlxKey.LBRACKET: return ImGuiKey.LeftBracket;
+ case FlxKey.RBRACKET: return ImGuiKey.RightBracket;
+ case FlxKey.BACKSLASH: return ImGuiKey.Backslash;
+ case FlxKey.CAPSLOCK: return ImGuiKey.CapsLock;
+ case FlxKey.SCROLL_LOCK: return ImGuiKey.ScrollLock;
+ case FlxKey.NUMLOCK: return ImGuiKey.NumLock;
+ case FlxKey.SEMICOLON: return ImGuiKey.Semicolon;
+ case FlxKey.QUOTE: return ImGuiKey.Apostrophe;
+ case FlxKey.ENTER: return ImGuiKey.Enter;
+ case FlxKey.SHIFT: return ImGuiKey.LeftShift;
+ case FlxKey.COMMA: return ImGuiKey.Comma;
+ case FlxKey.PERIOD: return ImGuiKey.Period;
+ case FlxKey.SLASH: return ImGuiKey.Slash;
+ case FlxKey.GRAVEACCENT: return ImGuiKey.GraveAccent;
+ case FlxKey.CONTROL: return ImGuiKey.LeftCtrl;
+ case FlxKey.ALT: return ImGuiKey.LeftAlt;
+ case FlxKey.SPACE: return ImGuiKey.Space;
+ case FlxKey.UP: return ImGuiKey.UpArrow;
+ case FlxKey.DOWN: return ImGuiKey.DownArrow;
+ case FlxKey.LEFT: return ImGuiKey.LeftArrow;
+ case FlxKey.RIGHT: return ImGuiKey.RightArrow;
+ case FlxKey.TAB: return ImGuiKey.Tab;
+ case FlxKey.WINDOWS: return ImGuiKey.None; //no windows key
+ case FlxKey.MENU: return ImGuiKey.Menu;
+ case FlxKey.PRINTSCREEN: return ImGuiKey.PrintScreen;
+ case FlxKey.BREAK: return ImGuiKey.Pause;
+ case FlxKey.F1: return ImGuiKey.F1;
+ case FlxKey.F2: return ImGuiKey.F2;
+ case FlxKey.F3: return ImGuiKey.F3;
+ case FlxKey.F4: return ImGuiKey.F4;
+ case FlxKey.F5: return ImGuiKey.F5;
+ case FlxKey.F6: return ImGuiKey.F6;
+ case FlxKey.F7: return ImGuiKey.F7;
+ case FlxKey.F8: return ImGuiKey.F8;
+ case FlxKey.F9: return ImGuiKey.F9;
+ case FlxKey.F10: return ImGuiKey.F10;
+ case FlxKey.F11: return ImGuiKey.F11;
+ case FlxKey.F12: return ImGuiKey.F12;
+ case FlxKey.NUMPADZERO: return ImGuiKey.Keypad0;
+ case FlxKey.NUMPADONE: return ImGuiKey.Keypad1;
+ case FlxKey.NUMPADTWO: return ImGuiKey.Keypad2;
+ case FlxKey.NUMPADTHREE: return ImGuiKey.Keypad3;
+ case FlxKey.NUMPADFOUR: return ImGuiKey.Keypad4;
+ case FlxKey.NUMPADFIVE: return ImGuiKey.Keypad5;
+ case FlxKey.NUMPADSIX: return ImGuiKey.Keypad6;
+ case FlxKey.NUMPADSEVEN: return ImGuiKey.Keypad7;
+ case FlxKey.NUMPADEIGHT: return ImGuiKey.Keypad8;
+ case FlxKey.NUMPADNINE: return ImGuiKey.Keypad9;
+ case FlxKey.NUMPADMINUS: return ImGuiKey.KeypadSubtract;
+ case FlxKey.NUMPADPLUS: return ImGuiKey.KeypadAdd;
+ case FlxKey.NUMPADPERIOD: return ImGuiKey.KeypadDecimal;
+ case FlxKey.NUMPADMULTIPLY: return ImGuiKey.KeypadMultiply;
+ case FlxKey.NUMPADSLASH: return ImGuiKey.KeypadDivide;
+ default:
+ }
+ return ImGuiKey.None;
+ }
+
+ public static function getWindowSpaceX() {
+ return (ImGuiIO.configFlags & ImGuiConfigFlags.ViewportsEnable) != 0 ? Lib.application.window.x : 0;
+ }
+ public static function getWindowSpaceY() {
+ return (ImGuiIO.configFlags & ImGuiConfigFlags.ViewportsEnable) != 0 ? Lib.application.window.y : 0;
+ }
+ #end
+}
\ No newline at end of file
diff --git a/source/funkin/backend/utils/MathUtil.hx b/source/funkin/backend/utils/MathUtil.hx
index c71414edf3..5e6a3b4367 100644
--- a/source/funkin/backend/utils/MathUtil.hx
+++ b/source/funkin/backend/utils/MathUtil.hx
@@ -3,220 +3,232 @@ package funkin.backend.utils;
import haxe.macro.Expr;
final class MathUtil {
- public static inline var EULER:Float = 2.718281828459;
-
- /**
- * Returns the maximum value in the arguments.
- * @param args Array of values
- *
- * @return The maximum value
- **/
- public static function maxInt(...args:Int):Int {
- var max = args[0];
- for(i in 1...args.length) {
- var arg = args[i];
- if(arg > max)
- max = arg;
- }
- return max;
- }
-
- /**
- * Returns the minimum value in the arguments.
- * @param args Array of values
- *
- * @return The minimum value
- **/
- public static function minInt(...args:Int):Int {
- var min = args[0];
- for(i in 1...args.length) {
- var arg = args[i];
- if(arg < min)
- min = arg;
- }
- return min;
- }
-
- /**
- * Returns the maximum value in the arguments.
- *
- * NOTE: If you are using this in compile time, you should use `MathUtil.maxSmart` instead of this for better performance.
- *
- * @param args Array of values
- *
- * @return The maximum value
- **/
- public static function max(...args:Float):Float {
- var max = args[0];
- for(i in 1...args.length) {
- var arg = args[i];
- if(arg > max)
- max = arg;
- }
- return max;
- }
-
- /**
- * Returns the minimum value in the arguments.
- *
- * NOTE: If you are using this in compile time, you should use `MathUtil.minSmart` instead of this for better performance.
- *
- * @param args Array of values
- *
- * @return The minimum value
- **/
- public static function min(...args:Float):Float {
- var min = args[0];
- for(i in 1...args.length) {
- var arg = args[i];
- if(arg < min)
- min = arg;
- }
- return min;
- }
-
- /**
- * Checks if a is less than b with considering a margin of error.
- * * @param a Float
- * @param b Float
- * @param margin Float (Default: EPSILON)
- * * @return Bool
- **/
- public static function lessThan(a:Float, b:Float, margin:Float = 0.0000001):Bool {
- return a < b - margin;
- }
-
- /**
- * Checks if a is less than or equally b with considering a margin of error.
- * * @param a Float
- * @param b Float
- * @param margin Float (Default: EPSILON)
- * * @return Bool
- **/
- public static function lessThanEqual(a:Float, b:Float, margin:Float = 0.0000001):Bool {
- return a <= b - margin;
- }
-
- /**
- * Checks if a is greater than b with considering a margin of error.
- * * @param a Float
- * @param b Float
- * @param margin Float (Default: EPSILON)
- * * @return Bool
- **/
- public static function greaterThan(a:Float, b:Float, margin:Float = 0.0000001):Bool {
- return a > b + margin;
- }
-
- /**
- * Checks if a is greater than or equally b with considering a margin of error.
- * * @param a Float
- * @param b Float
- * @param margin Float (Default: EPSILON)
- * * @return Bool
- **/
- public static function greaterThanEqual(a:Float, b:Float, margin:Float = 0.0000001):Bool {
- return a >= b + margin;
- }
-
- /**
- * Checks if a is approximately equal to b.
- * * @param a Float
- * @param b Float
- * @param margin Float (Default: EPSILON)
- * * @return Bool
- **/
- public static function equal(a:Float, b:Float, margin:Float = 0.0000001):Bool {
- return Math.abs(a - b) <= margin;
- }
-
- /**
- * Checks if a are not approximately equal to b.
- * * @param a Float
- * @param b Float
- * @param margin Float (Default: EPSILON)
- * * @return Bool
- **/
- public static function notEqual(a:Float, b:Float, margin:Float = 0.0000001):Bool {
- return Math.abs(a - b) > margin;
- }
-
- /**
- * @param edge0 Float
- * @param edge1 Float
- * @param x Float
- * @return Float
- **/
- public static function smoothStep(edge0:Float, edge1:Float, x:Float):Float {
- var t = (x - edge0) / (edge1 - edge0);
- var clamped = t < 0.0 ? 0.0 : (t > 1.0 ? 1.0 : t);
- return clamped * clamped * (3.0 - 2.0 * clamped);
- }
-
- /**
- * @param a Float
- * @param b Float
- * @param v Float
- * @return Float
- **/
- public static function inverseLerp(a:Float, b:Float, v:Float):Float {
- return (v - a) / (b - a);
- }
-
- /**
- * @param v Float
- * @return Float
- **/
- public static function fract(v:Float):Float {
- return v - Math.floor(v);
- }
-
- /**
- * Shortcut to `Math.max` but with infinite amount of arguments
- *
- * Might not preserve the order of arguments, please test this.
- *
- * Dont use this in hscript, it doesnt work, it only works on compile time
- **/
- @:dox(hide) public static macro function maxSmart(..._args:Expr):Expr {
- return genericMinMaxSmart(_args.toArray(), "Math.max");
- }
-
- /**
- * Shortcut to `Math.min` but with infinite amount of arguments
- *
- * Might not preserve the order of arguments, please test this.
- *
- * Dont use this in hscript, it doesnt work, it only works on compile time
- **/
- @:dox(hide) public static macro function minSmart(..._args:Expr):Expr {
- return genericMinMaxSmart(_args.toArray(), "Math.min");
- }
-
- #if macro
- @:dox(hide) private static function genericMinMaxSmart(_args:Array, funcPath:String):Expr {
- var args = _args.copy();
- if (args.length == 0) return macro 0;
-
- var func = funcPath.split(".");
-
- function nested(lst:Array):Expr {
- if (lst.length == 1) {
- return macro ${lst[0]};
- } else if (lst.length == 2) {
- return macro $p{func}(${lst[0]}, ${lst[1]});
- } else {
- var mid = Std.int(lst.length / 2);
- return macro $p{func}(${nested(lst.slice(0, mid))}, ${nested(lst.slice(mid, lst.length))});
- }
- }
-
- var expr = nested(args);
-
- //var printer = new haxe.macro.Printer();
- //trace(printer.printExpr(expr));
-
- return macro $expr;
- }
- #end
+ public static inline var EULER:Float = 2.718281828459;
+
+ /**
+ * Returns the maximum value in the arguments.
+ * @param args Array of values
+ *
+ * @return The maximum value
+ **/
+ public static function maxInt(...args:Int):Int {
+ var max = args[0];
+ for(i in 1...args.length) {
+ var arg = args[i];
+ if(arg > max)
+ max = arg;
+ }
+ return max;
+ }
+
+ /**
+ * Returns the minimum value in the arguments.
+ * @param args Array of values
+ *
+ * @return The minimum value
+ **/
+ public static function minInt(...args:Int):Int {
+ var min = args[0];
+ for(i in 1...args.length) {
+ var arg = args[i];
+ if(arg < min)
+ min = arg;
+ }
+ return min;
+ }
+
+ /**
+ * Returns the maximum value in the arguments.
+ *
+ * NOTE: If you are using this in compile time, you should use `MathUtil.maxSmart` instead of this for better performance.
+ *
+ * @param args Array of values
+ *
+ * @return The maximum value
+ **/
+ public static function max(...args:Float):Float {
+ var max = args[0];
+ for(i in 1...args.length) {
+ var arg = args[i];
+ if(arg > max)
+ max = arg;
+ }
+ return max;
+ }
+
+ /**
+ * Returns the minimum value in the arguments.
+ *
+ * NOTE: If you are using this in compile time, you should use `MathUtil.minSmart` instead of this for better performance.
+ *
+ * @param args Array of values
+ *
+ * @return The minimum value
+ **/
+ public static function min(...args:Float):Float {
+ var min = args[0];
+ for(i in 1...args.length) {
+ var arg = args[i];
+ if(arg < min)
+ min = arg;
+ }
+ return min;
+ }
+
+ /**
+ * Checks if a is less than b with considering a margin of error.
+ *
+ * @param a Float
+ * @param b Float
+ * @param margin Float (Default: EPSILON)
+ *
+ * @return Bool
+ **/
+ public static function lessThan(a:Float, b:Float, margin:Float = 0.0000001):Bool {
+ return a < b - margin;
+ }
+
+ /**
+ * Checks if a is less than or equally b with considering a margin of error.
+ *
+ * @param a Float
+ * @param b Float
+ * @param margin Float (Default: EPSILON)
+ *
+ * @return Bool
+ **/
+ public static function lessThanEqual(a:Float, b:Float, margin:Float = 0.0000001):Bool {
+ return a <= b - margin;
+ }
+
+ /**
+ * Checks if a is greater than b with considering a margin of error.
+ *
+ * @param a Float
+ * @param b Float
+ * @param margin Float (Default: EPSILON)
+ *
+ * @return Bool
+ **/
+ public static function greaterThan(a:Float, b:Float, margin:Float = 0.0000001):Bool {
+ return a > b + margin;
+ }
+
+ /**
+ * Checks if a is greater than or equally b with considering a margin of error.
+ *
+ * @param a Float
+ * @param b Float
+ * @param margin Float (Default: EPSILON)
+ *
+ * @return Bool
+ **/
+ public static function greaterThanEqual(a:Float, b:Float, margin:Float = 0.0000001):Bool {
+ return a >= b + margin;
+ }
+
+ /**
+ * Checks if a is approximately equal to b.
+ *
+ * @param a Float
+ * @param b Float
+ * @param margin Float (Default: EPSILON)
+ *
+ * @return Bool
+ **/
+ public static function equal(a:Float, b:Float, margin:Float = 0.0000001):Bool {
+ return Math.abs(a - b) <= margin;
+ }
+
+ /**
+ * Checks if a are not approximately equal to b.
+ *
+ * @param a Float
+ * @param b Float
+ * @param margin Float (Default: EPSILON)
+ *
+ * @return Bool
+ **/
+ public static function notEqual(a:Float, b:Float, margin:Float = 0.0000001):Bool {
+ return Math.abs(a - b) > margin;
+ }
+
+ /**
+ * @param edge0 Float
+ * @param edge1 Float
+ * @param x Float
+ * @return Float
+ **/
+ public static function smoothStep(edge0:Float, edge1:Float, x:Float):Float {
+ var t = (x - edge0) / (edge1 - edge0);
+ var clamped = t < 0.0 ? 0.0 : (t > 1.0 ? 1.0 : t);
+ return clamped * clamped * (3.0 - 2.0 * clamped);
+ }
+
+ /**
+ * @param a Float
+ * @param b Float
+ * @param v Float
+ * @return Float
+ **/
+ public static function inverseLerp(a:Float, b:Float, v:Float):Float {
+ return (v - a) / (b - a);
+ }
+
+ /**
+ * @param v Float
+ * @return Float
+ **/
+ public static function fract(v:Float):Float {
+ return v - Math.floor(v);
+ }
+
+ /**
+ * Shortcut to `Math.max` but with infinite amount of arguments
+ *
+ * Might not preserve the order of arguments, please test this.
+ *
+ * Dont use this in hscript, it doesnt work, it only works on compile time
+ **/
+ @:dox(hide) public static macro function maxSmart(..._args:Expr):Expr {
+ return genericMinMaxSmart(_args.toArray(), "Math.max");
+ }
+
+ /**
+ * Shortcut to `Math.min` but with infinite amount of arguments
+ *
+ * Might not preserve the order of arguments, please test this.
+ *
+ * Dont use this in hscript, it doesnt work, it only works on compile time
+ **/
+ @:dox(hide) public static macro function minSmart(..._args:Expr):Expr {
+ return genericMinMaxSmart(_args.toArray(), "Math.min");
+ }
+
+ #if macro
+ @:dox(hide) private static function genericMinMaxSmart(_args:Array, funcPath:String):Expr {
+ var args = _args.copy();
+ if (args.length == 0) return macro 0;
+
+ var func = funcPath.split(".");
+
+ function nested(lst:Array):Expr {
+ if (lst.length == 1) {
+ return macro ${lst[0]};
+ } else if (lst.length == 2) {
+ return macro $p{func}(${lst[0]}, ${lst[1]});
+ } else {
+ var mid = Std.int(lst.length / 2);
+ return macro $p{func}(${nested(lst.slice(0, mid))}, ${nested(lst.slice(mid, lst.length))});
+ }
+ }
+
+ var expr = nested(args);
+
+ //var printer = new haxe.macro.Printer();
+ //trace(printer.printExpr(expr));
+
+ return macro $expr;
+ }
+ #end
}
diff --git a/source/funkin/backend/utils/NativeAPI.hx b/source/funkin/backend/utils/NativeAPI.hx
index d3b5e758e6..16e08c19b5 100644
--- a/source/funkin/backend/utils/NativeAPI.hx
+++ b/source/funkin/backend/utils/NativeAPI.hx
@@ -13,12 +13,6 @@ import flixel.util.FlxColor;
* Some functions might not have effect on some platforms.
*/
class NativeAPI {
- @:dox(hide) public static function registerAudio() {
- #if windows
- Windows.registerAudio();
- #end
- }
-
@:dox(hide) public static function registerAsDPICompatible() {
#if windows
Windows.registerAsDPICompatible();
@@ -185,16 +179,7 @@ class NativeAPI {
public static function setConsoleColors(foregroundColor:ConsoleColor = NONE, ?backgroundColor:ConsoleColor = NONE) {
if(Main.noTerminalColor) return;
- #if (windows && !hl)
- if(foregroundColor == NONE)
- foregroundColor = LIGHTGRAY;
- if(backgroundColor == NONE)
- backgroundColor = BLACK;
-
- var fg:Int = cast foregroundColor;
- var bg:Int = cast backgroundColor;
- Windows.setConsoleColors((bg * 16) + fg);
- #elseif sys
+ #if sys
Sys.print("\x1b[0m");
if(foregroundColor != NONE)
Sys.print("\x1b[" + Std.int(consoleColorToANSI(foregroundColor)) + "m");
diff --git a/source/funkin/backend/utils/XMLUtil.hx b/source/funkin/backend/utils/XMLUtil.hx
index ed30d6ba42..46f4086465 100644
--- a/source/funkin/backend/utils/XMLUtil.hx
+++ b/source/funkin/backend/utils/XMLUtil.hx
@@ -15,6 +15,8 @@ import funkin.backend.scripting.ScriptPack;
import flixel.util.typeLimit.OneOfTwo;
import funkin.backend.system.interfaces.IOffsetCompatible;
import haxe.xml.Access;
+import flixel.graphics.frames.FlxFramesCollection;
+import flixel.graphics.frames.FlxAtlasFrames;
import animate.FlxAnimateFrames;
using StringTools;
@@ -109,6 +111,41 @@ final class XMLUtil {
return OK;
}
+ /**
+ * Loads multiple sheets into 1 sprite,
+ * for every `characters/bf` there is.
+ * (Can also be spelled as ``, or ``)
+ * @param spr The sprite
+ * @param node The XML node
+ * @param parentFolder The parent folder
+ */
+ public static function appendSpriteSheetsFromXML(spr:FunkinSprite, node:Access, ?parentFolder:String = ''):FlxFramesCollection {
+ if (spr == null) return null;
+ var defaultPath = '$parentFolder${node.getAtt("sprite").getDefault(spr.name)}';
+ if (!node.hasNode.spritesheet && !node.hasNode.sheet) {
+ spr.loadSprite(Paths.image(defaultPath, null, true));
+ return spr.frames;
+ }
+ var seenSheets:Array = [defaultPath];
+ for (n in node.elements) {
+ if (n.name != 'spritesheet' && n.name != 'sheet') continue;
+ var path = n.x.get('path') ?? n.x.firstChild()?.nodeValue?.trim();
+ if (path == null) {
+ Logs.warn('Spritesheet node is missing text content or the path attribute. Skipping...');
+ continue;
+ }
+ if (seenSheets.contains(path)) {
+ Logs.warn('Spritesheet "${Paths.image(path)}" was already added. Skipping...');
+ continue;
+ }
+ if (!Paths.framesExists(path, true)) {
+ Logs.warn('Could not find a BitmapData asset with ID "${Paths.image(path)}". Skipping...');
+ continue;
+ }
+ seenSheets.push(path);
+ }
+ return spr.frames = Paths.getMultiFrames(seenSheets, false, false, null, false, null, spr.animateSettings);
+ }
/**
* Sets the properties of a sprite based on a XML node.
* @param spr The sprite
@@ -121,9 +158,9 @@ final class XMLUtil {
spr.name = node.getAtt("name");
spr.antialiasing = true;
- if (loadGraphic)
- spr.loadSprite(Paths.image('$parentFolder${node.getAtt("sprite").getDefault(spr.name)}', null, true));
-
+ if (loadGraphic) {
+ appendSpriteSheetsFromXML(spr, node, parentFolder);
+ }
spr.spriteAnimType = defaultAnimType;
if (node.has.type) {
spr.spriteAnimType = XMLAnimType.fromString(node.att.type, spr.spriteAnimType);
@@ -285,6 +322,7 @@ final class XMLUtil {
if (anim.has.forced) animData.forced = anim.att.forced == "true";
if (anim.has.indices) animData.indices = CoolUtil.parseNumberRange(anim.att.indices);
if (anim.has.label) animData.label = anim.att.label == "true";
+ if (anim.has.isAnimate) animData.isAnimate = anim.att.isAnimate == "true";
return animData;
}
@@ -311,7 +349,7 @@ final class XMLUtil {
if (animData.name != null) {
if (animData.fps <= 0 #if web || animData.fps == null #end) animData.fps = 24;
- if (sprite.frames is FlxAnimateFrames) {
+ if ((sprite.frames is FlxAnimateFrames) == (animData.isAnimate ?? true)) {
if(animData.anim == null)
return MISSING_PROPERTY;
@@ -485,6 +523,7 @@ typedef AnimData = {
var animType:XMLAnimType;
var label:Bool;
var ?forced:Bool;
+ var ?isAnimate:Bool;
}
typedef BeatAnim = {
diff --git a/source/funkin/backend/utils/native/Windows.hx b/source/funkin/backend/utils/native/Windows.hx
index 706eba21c2..026521597d 100644
--- a/source/funkin/backend/utils/native/Windows.hx
+++ b/source/funkin/backend/utils/native/Windows.hx
@@ -28,107 +28,10 @@ import funkin.backend.utils.NativeAPI.MessageBoxIcon;
#include
#include
#include