Author SHA1 Message Date
gabry a61abcde2d remove workflows 2026-07-19 16:02:32 +02:00
gabry a93a74aede make it compile on html5
Windows Builds / Windows Build (push) Has been cancelled
Windows Builds / Windows Debug Build (push) Has been cancelled
Mac OS Builds / Mac OS Build (push) Has been cancelled
Mac OS Builds / Mac OS Debug Build (push) Has been cancelled
Linux Builds / Linux Build (push) Has been cancelled
Linux Builds / Linux Debug Build (push) Has been cancelled
2026-07-19 15:58:56 +02:00
Jamextreme140andGitHub 9207e46105 Add IMPORT_BLACKLIST to Config.hx (#1026)
small change due to changes in hscript library
2026-07-15 23:27:15 -07:00
HEIHUAaandGitHub d113fc40cb BloomEffect (#894)
* Create BloomEffect.hx

* Make the shader compatible with more devices.

* Default

* Update BloomEffect.hx

* no __applyFilter

* Update BloomEffect.hx

* Update BloomEffect.hx

* Performance improvement: 1.35x.

* quality

* Implemented low-resolution bloom, achieving a 10x performance boost!!!

* 1

* useLowQualityExtract

* brightness

* 更新 BloomEffect.hx

* blendMode

Fixed memory growth
Added blendMode

* 更新 BloomEffect.hx

* Wrap-up

* 更新 BloomEffect.hx

* 更新 BloomEffect.hx

* 更新 BloomEffect.hx

* no...

* Fix color banding issue in high brightness areas.

* 更新 BloomEffect.hx

* Fix frequent GC (Garbage Collection) issues.

* smoothness

* noCustomClass
2026-07-01 14:02:45 +07:00
sillyborjaandGitHub 45a2f28cf6 read description (#1016)
* read description

- rename tween to playTween
- use the recycle function for positioning in displayRatingNumbers
- add "tween" variable (which is an FlxTween instance, not to be confused with playTween)
- fix returns

* i think this will be confusing but ok

* Revert "i think this will be confusing but ok"

This reverts commit 5a8c834220.
2026-06-28 11:59:38 +07:00
CircuitellaandGitHub 45d47677ac Add a flag related to Icon Movement on the Healthbar (#1005)
* add flag

* Make The Flag be used in PlayState
2026-06-28 09:32:23 +07:00
pharaotisRealandGitHub a485080c27 Ratings event addition to the engine (#1015)
* Update PlayState.hx

Implemented RatingsShowEvent

* Add files via upload

* Update pixel.hx

Update because of ratings commits

* Update PlayState.hx

put the flxpoints after they're used, forgot to use event.tweenDuration in displayRatingNumbers

* Update PlayState.hx

oops

* Update PlayState.hx

I'm so not careful

* Update PlayState.hx

Commited NoteHitEvent checks for all display rating functions

* Update PlayState.hx

oops 2

* Update NoteHitEvent.hx

made the rating vars null class

* Update PlayState.hx

juust in case

* Update PlayState.hx

oops 3

* Update RatingsShowEvent.hx

Implemented a rating var which allows you to change the rating sprite.

* Update PlayState.hx

Implemented a rating var (In RatingsShowEvent) which allows you to change the rating sprite.

* Update PlayState.hx

now it should support displayRating/Combo being set from note hit event

* Update PlayState.hx

fixed a possibility where if the display bool was set to false or the event was cancelled, then it would still display the sprite as a haxeflixel logo
2026-06-28 09:31:59 +07:00
HeroandGitHub cb4ecc521f ZoomFactor in FunkinText, Early Implementation (#1011) 2026-06-20 21:59:22 +07:00
SrtHero278 0e269ddfc7 addressing https://github.com/CodenameCrew/CodenameEngine/issues/1004 2026-06-13 16:43:56 -07:00
SrtHero278 80ca43b46e shits break combo because read desc
this engine is spam and get away with it central like i struggled to hit a shit no joke

and i did get owner approval to do this
2026-05-11 19:44:59 -07:00
ADA_FunniandGitHub 8f0583958d Change the default value of "Multiplicative?" in the "Camera Zoom" event to be false. (#974) 2026-05-03 10:28:50 +07:00
Ralty f82c8d7fb7 Remove linux-selfhosted, no longer needed. 2026-04-07 10:44:25 +07:00
Ralty 2a413071cd Disable Streamed Audio for now, better late than broken 2026-04-07 10:41:35 +07:00
Ralty 3fa3fbda14 Fix CoolUtil.playMenuSong volume 2026-04-07 10:41:34 +07:00
Nebula S. NovaandGitHub cd7c9f1afc Fix Commit Number in Main Menu (#950)
* Update checkout action to fetch full history

* Update linux.yml

* Update macos.yml

* Update release.yml

* Update windows.yml
2026-04-04 20:44:30 +07:00
rufiusandGitHub e458e868a4 Add ability to set custom default alternate animation suffix (#882)
The alt. animation toggle event currently does not support setting the
suffix directly,
but that's outside of the scope of adding this feature at the moment.
2026-04-04 13:32:24 +07:00
HEIHUAaandGitHub 6f8f43d3dc Optimize Note & Strum Rendering (#941)
* optimize

* Delete the unnecessary

* Delete __posPoint

* 更新 Note.hx

* @:dox(hide)

* 更新 Strum.hx

* Fix the bug
2026-04-03 21:35:27 +07:00
BittoandGitHub a8a5ae7a89 Fix game crash and fix(?) == to != (#943) 2026-04-02 06:27:47 +07:00
BittoandGitHub e0b5369eb5 "Delete Stacked Notes" functionality for the chart editor (#935)
* Delete Stacked Notes

* unstack selection

* Attempts to translate to italian and polish

i do not know these languages so if they're wrong im sorry
2026-03-30 04:51:57 +07:00
BittoandGitHub d874ea0348 Fix an oversight with the alphabet getting the wrong width of an component (#937) 2026-03-30 04:49:11 +07:00
FrakitsandGitHub d80b7b95cf selfhosted linux workflow because ubuntu stinks 2026-03-29 13:49:08 +03:00
270ed6b9b5 Note.noteAngle rewrite, fixes minus scrollSpeed (#857)
* fix 90% of issues with noteAngle and scrollSpeed of notes

cool  , sustains now dont look like ass when rotated and negative scroll speed doesnt mess tem up anymore

although the consequence of this is that noteAngle will go the other way around if on downscroll but i cant rlly get myself to fix that ;-;

* downscroll is done (hopefully

---------

Co-authored-by: Ralty <78720179+Raltyro@users.noreply.github.com>
2026-03-29 13:36:16 +07:00
HEIHUAaandGitHub 39968efb19 (Charter) Add an ability to copy selected items by Alt-Dragging (#918) 2026-03-29 05:03:58 +07:00
HeroandGitHub 64ea9db1c1 EULER, smoothStep, inverseLerp, fract in MathUtil (#913) 2026-03-29 05:02:42 +07:00
ItsLJcoolandGitHub 5957a55b96 Add Text displaying if the user is running an Experimental Version of CodenameEngine (#933)
* cmon github actions, do something

* this will make it easier to know if a user is running an experimental version :trollface:
2026-03-29 04:18:46 +07:00
Ralty 3266e22562 Correctly change the library rerefences 2026-03-28 17:41:53 +07:00
ItsLJcoolandGitHub a7b040152d Fixed / Refactored SysZip to actually work, and store compressed entries without uncompressing an entire zip (#761)
* well boys I fixed it. you can now have Zipped Mods!!!!!

* comment update

* quick fixes + optimizations

* fixed stream audio bug????

* found the issue ffs

* removing traces oops

* fixed videos from not being able to play. Inspired from VideoCutscene's fix for ZIP videos, and also removed that fix in favor for just fixing the issue entirely

* oop just in case no special characters that kill themself on other shit

* Implemented preloading every video (caching) when loading a ZipFolderLibrary.

Since videos have to be decompressed and be saved as a file anyways this reduces the time to do that, and what not.

* [UNTESTED] Allowed Zip Extensions for allowing any extension in the list to be passed as a `.zip`

* After some more testing loading Zip Addons works as well.

* Added the long awaited .cnemod file type!!!!! (it's just a folder with .cnemod with a .zip inside LMAO)

* Chalking up the Streamed Audio Bug to a mod issue, and also added capabilites to detect if a library is considered compressed

* Added warning to those who try to use the editor with compressed libraries

* ok instead of making it a .cnemod, the zip name will be `cnemod.zip` or what not.

* removing unnessesary traces oops

* clean Library accessor ig then

* fixed small issues and removed an idiot idea

* writing todos and fixing some context that left the room in comments

* Fixed an issue where if you had a file of 0 bytes (nothing) in a zipped library it would FUCKING CRASH the game

* Actually fixed the issue of decompressing 0 byte files

Also stopped saving the data for Folders in Zipped files, as they are 0 bytes anyways.

* Added the features that needed to exist, and removed a stupid line I saw in `ModsFolder` lol

For some reason animation offsets for characters are breaking and I can't tell if its my drive killing itself or cne so uh

* fix an issue that will force the game to crash if first ever mod being loaded is a ZipModLibrary
2026-03-15 14:24:47 +07:00
sillyborjaandGitHub a951e38dbb add "onPostEvent" call (#914)
* add preventSustainClip

* add "onPostEvent" call

idk why this wasn't here before but i added it
2026-03-01 21:19:37 +07:00
TheZoroForce240andGitHub 4fe3ba76e3 Charter/Editor additions and fixes (#793)
* camera highlights again

* option to mute all hitsounds

* context menus inside context menus and wip sliders in context menu

* wip volume slider stuff

* strumline preview fix with resizable editors

* more slider stuff

* editor sfx option should now actually work for everything

* fixed strumline vocals volume, fixed note type option keybinds and moved bookmarks and note types into submenus

* fix for saving difficulty specific meta json

* some extra misc changes

fixed camera highlights not updating when zooming in/out, optimized some of the highlight draw code so it doesn't do a full loop, and added the note scale flag into the strumline preview

* fix compiling after merge
2026-03-01 21:13:33 +07:00
FrakitsandGitHub f44d0192af my first ever commit in 2026
Removed the noCustomClass metadata from CancellableEvent.
2026-03-01 15:21:32 +02:00
viviandGitHub f8a06bf94c Improved text handling and keyboard input functionality for UITextBox (#900)
* Refactor UITextBox for improved text handling and keyboard input functionality

* use cut paste copy sounds
2026-03-01 16:25:19 +07:00
HEIHUAaandGitHub ec2a39995b optimize __notePerStrum (#901)
* Update StrumLine.hx

* Update StrumLine.hx

* Update StrumLine.hx

* Update StrumLine.hx
2026-03-01 16:25:02 +07:00
Ralty 3c15805c80 Switch to old library versions for this branch 2026-03-01 14:14:26 +07:00
Ralty 221d9dba1d Fork Flixel Animate to Fix frameOffsetAngle, layer 2026-03-01 14:06:24 +07:00
Mario HernandoandGitHub d87ac417f7 stress but cool (#911) 2026-02-28 09:44:24 +07:00
BittoandGitHub 3e084397a8 that was supposed to be an flxobject ok (#908)
* mouseOverlaps

* This makes more sense yeah

thinking of not even allowing disjointed overlap checks
keeping it for now

* oops forgot to define that var as flxcamera

* h
2026-02-20 20:15:53 +01:00
BittoandGitHub 370d5ede81 Better mouse overlapping function for CoolUtil (#813)
* mouseOverlaps

* This makes more sense yeah

thinking of not even allowing disjointed overlap checks
keeping it for now

* oops forgot to define that var as flxcamera
2026-02-20 19:10:05 +01:00
drfiffiandGitHub d7dbe5038d flixel-animate fix for cutscenes (#907)
* fixed flixel-animate name changes

* fixed guns

* fixed stress

still needs camera work
2026-02-20 16:19:39 +01:00
axelandGitHub 6c25431c75 Fix NaN check in RadioButton options, again (#899)
Comparing to NaN (even with itself) its always false, use Math.isNaN insted
2026-02-03 21:19:12 +07:00
💜 Rodney, An Imaginative Furball 💙andGitHub 37eb193e53 Fix Character uses one 'script' instead of multiple 'scripts' (#871) 2026-02-03 12:30:08 +07:00
ItsLJcoolandGitHub 0eeef88ac7 Re-implement onDraw functionality for FunkinSprite (#897)
As FlxAnimate didn't have the functionality before, so we have to do the check in FunkinSprite ourselves, as it itself stop's calling the `super.draw();` when it is a `isAnimate`
2026-02-03 12:28:11 +07:00
47a3526178 Allow string values for radio buttons in options menu (#896)
* Allow string values for radio buttons in options menu :D

NOT sure why this wasn't already a thing.... But it's here now

* Fix NaN check

---------

Co-authored-by: Ralty <78720179+Raltyro@users.noreply.github.com>
2026-02-03 12:21:09 +07:00
⍚~NexandGitHub bbb16f4c5c new shitty (thanks yosh :p) 2026-01-30 18:38:16 +01:00
axelandGitHub 5115067b49 Always use lastest FunkinModchart's release version (#895)
To avoid having to do PR every time I update FM, I suggest simply using the latest and most stable version (currently 1.2.5), which fixes many bugs, including a crash that occurred every single time you exited a song.
2026-01-30 17:30:40 +01:00
82 changed files with 15419 additions and 81565 deletions
-147
View File
@@ -1,147 +0,0 @@
name: Linux Builds
on:
push:
workflow_dispatch:
jobs:
build:
name: Linux Build
permissions: write-all
runs-on: ubuntu-24.04
steps:
- name: Pulling the new commit
uses: actions/checkout@v4
- name: Setting up Haxe
uses: krdlab/setup-haxe@v2
with:
haxe-version: 4.3.7
- 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/
- run: |
echo "HXCPP_COMPILE_CACHE=~/.hxcpp" >> $GITHUB_ENV
- name: Installing LibVLC
run: |
sudo apt-get install libvlc-dev libvlccore-dev
- name: Installing/Updating libraries
run: |
haxe -cp commandline -D analyzer-optimize --run Main setup -s
- name: Building the game
run: |
haxelib run lime build linux
# - name: Tar files
# run: tar -zcvf CodenameEngine.tar.gz -C export/release/linux/bin .
- name: Uploading artifact (executable)
uses: actions/upload-artifact@v4
with:
name: Codename Engine (Executable Only)
path: export/release/linux/bin/CodenameEngine
- name: Uploading artifact (entire build)
uses: actions/upload-artifact@v4
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/
# 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: Linux Debug Build
permissions: write-all
runs-on: ubuntu-24.04
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
- name: Setting up Haxe
uses: krdlab/setup-haxe@v2
with:
haxe-version: 4.3.7
- 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/
- run: |
echo "HXCPP_COMPILE_CACHE=~/.hxcpp" >> $GITHUB_ENV
- name: Installing LibVLC
run: |
sudo apt-get install libvlc-dev libvlccore-dev
- name: Installing/Updating libraries
run: |
haxe -cp commandline -D analyzer-optimize --run Main setup -s
- name: Building the game
run: |
haxelib run lime build linux -debug
# - name: Tar files
# run: tar -zcvf CodenameEngine.tar.gz -C export/debug/linux/bin .
- name: Uploading artifact (entire build)
uses: actions/upload-artifact@v4
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/
-141
View File
@@ -1,141 +0,0 @@
name: Mac OS Builds
on:
push:
workflow_dispatch:
jobs:
build:
name: Mac OS Build
permissions: write-all
runs-on: macos-14
steps:
- name: Pulling the new commit
uses: actions/checkout@v4
- name: Setting up Haxe
uses: krdlab/setup-haxe@v2
with:
haxe-version: 4.3.7
- 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/
- run: |
echo "HXCPP_COMPILE_CACHE=~/.hxcpp" >> $GITHUB_ENV
- name: Installing/Updating libraries
run: |
haxe -cp commandline -D analyzer-optimize --run Main setup -s
- name: Building the game
run: |
arch -x86_64 haxelib run lime build mac
- name: Tar files
run: tar -zcvf CodenameEngine.tar.gz -C export/release/macos/bin .
- name: Uploading artifact (executable)
uses: actions/upload-artifact@v4
with:
name: Codename Engine (Executable Only)
path: export/release/macos/bin/CodenameEngine.app/Contents/MacOS/CodenameEngine
- name: Uploading artifact (entire build)
uses: actions/upload-artifact@v4
with:
name: Codename Engine
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/
# 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
permissions: write-all
runs-on: macos-14
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
- name: Setting up Haxe
uses: krdlab/setup-haxe@v2
with:
haxe-version: 4.3.7
- 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/
- run: |
echo "HXCPP_COMPILE_CACHE=~/.hxcpp" >> $GITHUB_ENV
- name: Installing/Updating libraries
run: |
haxe -cp commandline -D analyzer-optimize --run Main setup -s
- name: Building the game
run: |
arch -x86_64 haxelib run lime build mac -debug
- name: Tar files
run: tar -zcvf CodenameEngine.tar.gz -C export/debug/macos/bin .
- name: Uploading artifact (entire build)
uses: actions/upload-artifact@v4
with:
name: Codename Engine 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/
-197
View File
@@ -1,197 +0,0 @@
name: Create Release Builds
on:
workflow_dispatch:
inputs:
tag_name:
description: "Release name (e.g. v1.0.5 or v1.2-rc1)"
required: true
prerelease:
description: "Is this a prerelease?"
required: true
type: boolean
custom_message:
description: "Optional pre-changelog message"
required: false
jobs:
collect-release:
name: Release ${{ github.event.inputs.tag_name }} Builds
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download Windows Full Build
uses: dawidd6/action-download-artifact@v6
with:
workflow: windows.yml
name: Codename Engine
path: artifacts/windows/full_build
allow_forks: false
- name: Download Windows Executable
uses: dawidd6/action-download-artifact@v6
with:
workflow: windows.yml
name: Codename Engine (Executable Only)
path: artifacts/windows/executable
allow_forks: false
- name: Download Mac OS Full Build
uses: dawidd6/action-download-artifact@v6
with:
workflow: macos.yml
name: Codename Engine
path: artifacts/macos/full_build
allow_forks: false
- name: Download Mac OS Executable
uses: dawidd6/action-download-artifact@v6
with:
workflow: macos.yml
name: Codename Engine (Executable Only)
path: artifacts/macos/executable
allow_forks: false
- name: Download Linux Full Build
uses: dawidd6/action-download-artifact@v6
with:
workflow: linux.yml
name: Codename Engine
path: artifacts/linux/full_build
allow_forks: false
- name: Download Linux Executable
uses: dawidd6/action-download-artifact@v6
with:
workflow: linux.yml
name: Codename Engine (Executable Only)
path: artifacts/linux/executable
allow_forks: false
- name: Prepare artifacts
run: |
mkdir -p renamed
# Windows
mv artifacts/windows/executable/CodenameEngine.exe renamed/update-windows.exe
cd artifacts/windows/full_build
zip -r ../../../renamed/"Codename Engine-Windows.zip" *
cd -
# Mac OS
mv artifacts/macos/executable/CodenameEngine renamed/update-mac
cd artifacts/macos/full_build
if [ -f CodenameEngine.tar.gz ]; then
# Keep tar.gz as-is
cp CodenameEngine.tar.gz ../../../renamed/"Codename Engine-Mac.tar.gz"
else
# Zip contents at root
zip -r ../../../renamed/"Codename Engine-Mac.zip" *
fi
cd -
# Linux
mv artifacts/linux/executable/CodenameEngine renamed/update-linux
cd artifacts/linux/full_build
zip -r ../../../renamed/"Codename Engine-Linux.zip" *
cd -
- name: Find Base Release
id: get_base_release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "Looking for base release..."
PRERELEASE="${{ github.event.inputs.prerelease }}"
if [[ "$PRERELEASE" == "true" ]]; then
BASE=$(gh release list --repo ${{ github.repository }} --json tagName,isPrerelease,isDraft \
-L 50 -q '.[] | select(.isPrerelease==false and .isDraft==false) | .tagName' | head -n 1)
if [ -z "$BASE" ]; then
echo "No stable release found, resorting to prereleases."
BASE=$(gh release list --repo ${{ github.repository }} --json tagName,isDraft \
-L 1 -q '.[] | select(.isDraft==false) | .tagName')
fi
else
BASE=$(gh release list --repo ${{ github.repository }} --json tagName,isPrerelease,isDraft \
-L 50 -q '.[] | select(.isPrerelease==false and .isDraft==false) | .tagName' | head -n 1)
fi
if [ -z "$BASE" ]; then
echo "No older release found; please make sure to have at least one older release containing the 'update-assets.zip' file in order to generate the differences."
exit 1
else
echo "Base release: $BASE"
echo "base_release=$BASE" >> $GITHUB_OUTPUT
fi
- name: Prepare update-assets.zip
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "Looking for 'update-assets.zip' from release '${{ steps.get_base_release.outputs.base_release }}'"
ASSETS=$(gh release view "${{ steps.get_base_release.outputs.base_release }}" \
--repo "${{ github.repository }}" \
--json assets \
| jq -r '.assets[].name')
if echo "$ASSETS" | grep -xq "update-assets.zip"; then
mkdir -p update_assets_temp renamed
echo "Downloading assets from release '${{ steps.get_base_release.outputs.base_release }}'"
gh release download "${{ steps.get_base_release.outputs.base_release }}" \
--repo ${{ github.repository }} \
--pattern "update-assets.zip" \
-D update_assets_temp
unzip -q update_assets_temp/update-assets.zip -d update_assets_temp
cd update_assets_temp
zip -r ../renamed/update-assets.zip . -i assets
else
echo "No 'update-assets.zip' file found in release '${{ steps.get_base_release.outputs.base_release }}'; please make sure to have this file in that older release in order to generate the differences."
exit 1
fi
- name: Prepare pre-changelog message
id: pre_changelog_message
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "Preparing a pre-changelog message..."
BODY="If you would like to download the full builds of the engine from this release, please ignore the files starting with 'update-' as they're needed for the engine's internal autoupdater.\n\n"
if [ -n "${{ github.event.inputs.custom_message }}" ]; then
BODY="${{ github.event.inputs.custom_message }}\n${BODY}"
fi
{
echo "body<<EOF"
echo -e "$BODY"
echo "EOF"
} >> $GITHUB_OUTPUT
echo -e "Final pre-changelog message:\n\n$BODY"
- name: Create GitHub Release with Assets
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.event.inputs.tag_name }}
name: Release ${{ github.event.inputs.tag_name }}
draft: true
prerelease: ${{ github.event.inputs.prerelease }}
body: ${{ steps.build_body.outputs.body }}
generate_release_notes: true
files: |
renamed/Codename Engine-Windows.zip
renamed/Codename Engine-Mac.zip
renamed/Codename Engine-Mac.tar.gz
renamed/Codename Engine-Linux.zip
renamed/update-assets.zip
renamed/update-windows.exe
renamed/update-mac
renamed/update-linux
-137
View File
@@ -1,137 +0,0 @@
name: Windows Builds
on:
push:
workflow_dispatch:
jobs:
build:
name: Windows Build
permissions: write-all
runs-on: windows-latest
steps:
- name: Pulling the new commit
uses: actions/checkout@v4
- name: Setting up Haxe
uses: krdlab/setup-haxe@v2
with:
haxe-version: 4.3.7
- 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/
- run: |
echo "HXCPP_COMPILE_CACHE=~/.hxcpp" >> $GITHUB_ENV
- name: Installing/Updating libraries
run: |
haxe -cp commandline -D analyzer-optimize --run Main setup -s --no-vscheck
- name: Building the game
run: |
haxelib run lime build windows
- name: Uploading artifact (executable)
uses: actions/upload-artifact@v4
with:
name: Codename Engine (Executable Only)
path: export/release/windows/bin/CodenameEngine.exe
- name: Uploading artifact (entire build)
uses: actions/upload-artifact@v4
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/
# 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: Windows Debug Build
permissions: write-all
runs-on: windows-latest
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
- name: Setting up Haxe
uses: krdlab/setup-haxe@v2
with:
haxe-version: 4.3.7
- 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/
- run: |
echo "HXCPP_COMPILE_CACHE=~/.hxcpp" >> $GITHUB_ENV
- name: Installing/Updating libraries
run: |
haxe -cp commandline -D analyzer-optimize --run Main setup -s --no-vscheck
- name: Building the game
run: |
haxelib run lime build windows -debug
- name: Uploading artifact (entire build)
uses: actions/upload-artifact@v4
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/
+1 -1
View File
@@ -75,7 +75,7 @@ function onCountdown(event) {
};
}
function onPlayerHit(event:NoteHitEvent) {
function onRatingsShown(event:RatingsShowEvent) {
if (!enablePixelUI) return;
event.ratingPrefix = "stages/school/ui/";
event.ratingScale = daPixelZoom * 0.7;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 804 B

After

Width:  |  Height:  |  Size: 1.0 KiB

File diff suppressed because it is too large Load Diff
@@ -1,145 +1,241 @@
{"ATLAS": {"SPRITES":[
{"SPRITE" : {"name": "0000","x":2847,"y":969,"w":327,"h":397,"rotated": false}},
{"SPRITE" : {"name": "0001","x":2441,"y":894,"w":110,"h":159,"rotated": false}},
{"SPRITE" : {"name": "0002","x":1709,"y":1631,"w":197,"h":185,"rotated": false}},
{"SPRITE" : {"name": "0003","x":408,"y":1773,"w":92,"h":155,"rotated": false}},
{"SPRITE" : {"name": "0004","x":236,"y":1639,"w":274,"h":132,"rotated": false}},
{"SPRITE" : {"name": "0005","x":3427,"y":1495,"w":133,"h":74,"rotated": false}},
{"SPRITE" : {"name": "0006","x":3270,"y":540,"w":121,"h":162,"rotated": false}},
{"SPRITE" : {"name": "0007","x":359,"y":649,"w":518,"h":294,"rotated": false}},
{"SPRITE" : {"name": "0008","x":1957,"y":2,"w":775,"h":483,"rotated": false}},
{"SPRITE" : {"name": "0009","x":1014,"y":2,"w":941,"h":607,"rotated": false}},
{"SPRITE" : {"name": "0010","x":2,"y":2,"w":1010,"h":645,"rotated": false}},
{"SPRITE" : {"name": "0011","x":4059,"y":2,"w":20,"h":142,"rotated": false}},
{"SPRITE" : {"name": "0012","x":4035,"y":616,"w":40,"h":83,"rotated": false}},
{"SPRITE" : {"name": "0013","x":2755,"y":1508,"w":64,"h":57,"rotated": false}},
{"SPRITE" : {"name": "0014","x":3176,"y":1171,"w":91,"h":48,"rotated": false}},
{"SPRITE" : {"name": "0015","x":3245,"y":1737,"w":88,"h":41,"rotated": false}},
{"SPRITE" : {"name": "0016","x":3064,"y":1368,"w":109,"h":93,"rotated": false}},
{"SPRITE" : {"name": "0017","x":3402,"y":2,"w":655,"h":434,"rotated": false}},
{"SPRITE" : {"name": "0018","x":1957,"y":487,"w":626,"h":405,"rotated": false}},
{"SPRITE" : {"name": "0019","x":3402,"y":438,"w":631,"h":438,"rotated": false}},
{"SPRITE" : {"name": "0020","x":1170,"y":1211,"w":393,"h":287,"rotated": false}},
{"SPRITE" : {"name": "0021","x":2847,"y":540,"w":421,"h":427,"rotated": false}},
{"SPRITE" : {"name": "0022","x":2734,"y":2,"w":666,"h":536,"rotated": false}},
{"SPRITE" : {"name": "0023","x":542,"y":1480,"w":224,"h":221,"rotated": false}},
{"SPRITE" : {"name": "0024","x":3657,"y":1736,"w":186,"h":175,"rotated": false}},
{"SPRITE" : {"name": "0025","x":4035,"y":764,"w":56,"h":52,"rotated": false}},
{"SPRITE" : {"name": "0026","x":502,"y":1876,"w":126,"h":104,"rotated": false}},
{"SPRITE" : {"name": "0027","x":1709,"y":1818,"w":135,"h":104,"rotated": false}},
{"SPRITE" : {"name": "0028","x":2,"y":1440,"w":232,"h":236,"rotated": false}},
{"SPRITE" : {"name": "0029","x":3938,"y":1437,"w":139,"h":109,"rotated": false}},
{"SPRITE" : {"name": "0030","x":2755,"y":1627,"w":235,"h":158,"rotated": false}},
{"SPRITE" : {"name": "0031","x":1147,"y":1500,"w":198,"h":233,"rotated": false}},
{"SPRITE" : {"name": "0032","x":2,"y":1860,"w":118,"h":117,"rotated": false}},
{"SPRITE" : {"name": "0033","x":1565,"y":1398,"w":262,"h":221,"rotated": false}},
{"SPRITE" : {"name": "0034","x":3845,"y":1736,"w":222,"h":145,"rotated": false}},
{"SPRITE" : {"name": "0035","x":979,"y":1480,"w":166,"h":292,"rotated": false}},
{"SPRITE" : {"name": "0036","x":3657,"y":1592,"w":276,"h":142,"rotated": false}},
{"SPRITE" : {"name": "0037","x":925,"y":1774,"w":203,"h":138,"rotated": false}},
{"SPRITE" : {"name": "0038","x":236,"y":1467,"w":304,"h":170,"rotated": false}},
{"SPRITE" : {"name": "0039","x":4081,"y":2,"w":1,"h":4,"rotated": false}},
{"SPRITE" : {"name": "0040","x":2441,"y":1055,"w":135,"h":127,"rotated": false}},
{"SPRITE" : {"name": "0041","x":2,"y":1678,"w":195,"h":180,"rotated": false}},
{"SPRITE" : {"name": "0042","x":2751,"y":1787,"w":160,"h":144,"rotated": false}},
{"SPRITE" : {"name": "0043","x":713,"y":945,"w":243,"h":261,"rotated": false}},
{"SPRITE" : {"name": "0044","x":2375,"y":1508,"w":378,"h":121,"rotated": false}},
{"SPRITE" : {"name": "0045","x":3064,"y":1495,"w":361,"h":128,"rotated": false}},
{"SPRITE" : {"name": "0046","x":713,"y":1211,"w":455,"h":267,"rotated": false}},
{"SPRITE" : {"name": "0047","x":2,"y":1112,"w":272,"h":326,"rotated": false}},
{"SPRITE" : {"name": "0048","x":3710,"y":878,"w":249,"h":557,"rotated": false}},
{"SPRITE" : {"name": "0049","x":2585,"y":540,"w":260,"h":701,"rotated": false}},
{"SPRITE" : {"name": "0050","x":1014,"y":611,"w":291,"h":598,"rotated": false}},
{"SPRITE" : {"name": "0051","x":1307,"y":611,"w":315,"h":540,"rotated": false}},
{"SPRITE" : {"name": "0052","x":2,"y":649,"w":355,"h":461,"rotated": false}},
{"SPRITE" : {"name": "0053","x":359,"y":945,"w":352,"h":373,"rotated": false}},
{"SPRITE" : {"name": "0054","x":3064,"y":1625,"w":347,"h":110,"rotated": false}},
{"SPRITE" : {"name": "0055","x":2039,"y":1248,"w":379,"h":216,"rotated": false}},
{"SPRITE" : {"name": "0056","x":2827,"y":1368,"w":235,"h":257,"rotated": false}},
{"SPRITE" : {"name": "0057","x":2100,"y":1879,"w":140,"h":92,"rotated": false}},
{"SPRITE" : {"name": "0058","x":776,"y":1886,"w":119,"h":79,"rotated": false}},
{"SPRITE" : {"name": "0059","x":1565,"y":1249,"w":434,"h":147,"rotated": false}},
{"SPRITE" : {"name": "0060","x":276,"y":1320,"w":433,"h":145,"rotated": false}},
{"SPRITE" : {"name": "0061","x":3270,"y":878,"w":438,"h":342,"rotated": false}},
{"SPRITE" : {"name": "0062","x":3176,"y":1222,"w":385,"h":271,"rotated": false}},
{"SPRITE" : {"name": "0063","x":3563,"y":1437,"w":373,"h":153,"rotated": false}},
{"SPRITE" : {"name": "0064","x":3245,"y":1786,"w":202,"h":122,"rotated": false}},
{"SPRITE" : {"name": "0065","x":3449,"y":1786,"w":202,"h":122,"rotated": false}},
{"SPRITE" : {"name": "0066","x":3064,"y":1861,"w":150,"h":91,"rotated": false}},
{"SPRITE" : {"name": "0067","x":3938,"y":1548,"w":151,"h":95,"rotated": false}},
{"SPRITE" : {"name": "0068","x":3845,"y":1883,"w":140,"h":90,"rotated": false}},
{"SPRITE" : {"name": "0069","x":2913,"y":1861,"w":149,"h":92,"rotated": false}},
{"SPRITE" : {"name": "0070","x":630,"y":1886,"w":144,"h":87,"rotated": false}},
{"SPRITE" : {"name": "0071","x":3935,"y":1645,"w":152,"h":87,"rotated": false}},
{"SPRITE" : {"name": "0072","x":2441,"y":1243,"w":384,"h":263,"rotated": false}},
{"SPRITE" : {"name": "0073","x":1624,"y":611,"w":248,"h":277,"rotated": false}},
{"SPRITE" : {"name": "0074","x":1624,"y":894,"w":413,"h":353,"rotated": false}},
{"SPRITE" : {"name": "0075","x":2039,"y":894,"w":400,"h":352,"rotated": false}},
{"SPRITE" : {"name": "0076","x":1829,"y":1466,"w":330,"h":163,"rotated": false}},
{"SPRITE" : {"name": "0077","x":3987,"y":1883,"w":90,"h":103,"rotated": false}},
{"SPRITE" : {"name": "0078","x":2161,"y":1466,"w":212,"h":238,"rotated": false}},
{"SPRITE" : {"name": "0079","x":768,"y":1480,"w":209,"h":236,"rotated": false}},
{"SPRITE" : {"name": "0080","x":2242,"y":1879,"w":86,"h":84,"rotated": false}},
{"SPRITE" : {"name": "0081","x":879,"y":649,"w":126,"h":148,"rotated": false}},
{"SPRITE" : {"name": "0082","x":1908,"y":1631,"w":216,"h":168,"rotated": false}},
{"SPRITE" : {"name": "0083","x":2575,"y":1631,"w":174,"h":192,"rotated": false}},
{"SPRITE" : {"name": "0084","x":1130,"y":1901,"w":106,"h":75,"rotated": false}},
{"SPRITE" : {"name": "0085","x":3961,"y":1367,"w":129,"h":63,"rotated": false}},
{"SPRITE" : {"name": "0086","x":1829,"y":1398,"w":158,"h":60,"rotated": false}},
{"SPRITE" : {"name": "0087","x":3270,"y":704,"w":123,"h":148,"rotated": false}},
{"SPRITE" : {"name": "0088","x":3961,"y":878,"w":123,"h":148,"rotated": false}},
{"SPRITE" : {"name": "0089","x":199,"y":1773,"w":207,"h":142,"rotated": false}},
{"SPRITE" : {"name": "0090","x":1238,"y":1901,"w":91,"h":53,"rotated": false}},
{"SPRITE" : {"name": "0091","x":276,"y":1112,"w":65,"h":96,"rotated": false}},
{"SPRITE" : {"name": "0092","x":276,"y":1210,"w":65,"h":96,"rotated": false}},
{"SPRITE" : {"name": "0093","x":2518,"y":1825,"w":136,"h":102,"rotated": false}},
{"SPRITE" : {"name": "0094","x":1537,"y":1857,"w":136,"h":102,"rotated": false}},
{"SPRITE" : {"name": "0095","x":3427,"y":1592,"w":228,"h":192,"rotated": false}},
{"SPRITE" : {"name": "0096","x":1541,"y":1621,"w":166,"h":234,"rotated": false}},
{"SPRITE" : {"name": "0097","x":512,"y":1703,"w":205,"h":171,"rotated": false}},
{"SPRITE" : {"name": "0098","x":2126,"y":1706,"w":205,"h":171,"rotated": false}},
{"SPRITE" : {"name": "0099","x":719,"y":1718,"w":204,"h":166,"rotated": false}},
{"SPRITE" : {"name": "0100","x":1147,"y":1735,"w":206,"h":164,"rotated": false}},
{"SPRITE" : {"name": "0101","x":1874,"y":611,"w":79,"h":185,"rotated": false}},
{"SPRITE" : {"name": "0102","x":3176,"y":969,"w":83,"h":200,"rotated": false}},
{"SPRITE" : {"name": "0103","x":3563,"y":1222,"w":86,"h":180,"rotated": false}},
{"SPRITE" : {"name": "0104","x":3961,"y":1028,"w":89,"h":186,"rotated": false}},
{"SPRITE" : {"name": "0105","x":2375,"y":1631,"w":198,"h":183,"rotated": false}},
{"SPRITE" : {"name": "0106","x":1908,"y":1801,"w":190,"h":120,"rotated": false}},
{"SPRITE" : {"name": "0107","x":2656,"y":1825,"w":86,"h":145,"rotated": false}},
{"SPRITE" : {"name": "0108","x":1355,"y":1724,"w":180,"h":170,"rotated": false}},
{"SPRITE" : {"name": "0109","x":3961,"y":1216,"w":111,"h":149,"rotated": false}},
{"SPRITE" : {"name": "0110","x":4035,"y":516,"w":39,"h":98,"rotated": false}},
{"SPRITE" : {"name": "0111","x":1307,"y":1153,"w":118,"h":47,"rotated": false}},
{"SPRITE" : {"name": "0112","x":1427,"y":1153,"w":120,"h":43,"rotated": false}},
{"SPRITE" : {"name": "0113","x":2441,"y":1184,"w":120,"h":41,"rotated": false}},
{"SPRITE" : {"name": "0114","x":3216,"y":1910,"w":95,"h":33,"rotated": false}},
{"SPRITE" : {"name": "0115","x":3563,"y":1404,"w":95,"h":31,"rotated": false}},
{"SPRITE" : {"name": "0116","x":3563,"y":1404,"w":95,"h":31,"rotated": false}},
{"SPRITE" : {"name": "0117","x":2330,"y":1906,"w":95,"h":34,"rotated": false}},
{"SPRITE" : {"name": "0118","x":2585,"y":487,"w":131,"h":47,"rotated": false}},
{"SPRITE" : {"name": "0119","x":4059,"y":258,"w":31,"h":18,"rotated": false}},
{"SPRITE" : {"name": "0120","x":1355,"y":1896,"w":122,"h":66,"rotated": false}},
{"SPRITE" : {"name": "0121","x":2992,"y":1737,"w":251,"h":122,"rotated": false}},
{"SPRITE" : {"name": "0122","x":879,"y":799,"w":114,"h":133,"rotated": false}},
{"SPRITE" : {"name": "0123","x":1347,"y":1500,"w":192,"h":222,"rotated": false}},
{"SPRITE" : {"name": "0124","x":4035,"y":818,"w":39,"h":34,"rotated": false}},
{"SPRITE" : {"name": "0125","x":4035,"y":701,"w":50,"h":61,"rotated": false}},
{"SPRITE" : {"name": "0126","x":1874,"y":798,"w":64,"h":78,"rotated": false}},
{"SPRITE" : {"name": "0127","x":4035,"y":438,"w":52,"h":76,"rotated": false}},
{"SPRITE" : {"name": "0128","x":958,"y":934,"w":46,"h":63,"rotated": false}},
{"SPRITE" : {"name": "0129","x":4059,"y":146,"w":30,"h":41,"rotated": false}},
{"SPRITE" : {"name": "0130","x":4059,"y":189,"w":23,"h":35,"rotated": false}},
{"SPRITE" : {"name": "0131","x":4059,"y":226,"w":20,"h":30,"rotated": false}},
{"SPRITE" : {"name": "0132","x":4059,"y":278,"w":17,"h":22,"rotated": false}},
{"SPRITE" : {"name": "0133","x":2333,"y":1816,"w":183,"h":88,"rotated": false}}
]},
"meta": {
"app": "Adobe Animate",
"version": "21.0.1.37179",
"image": "spritemap1.png",
"format": "RGBA8888",
"size": {"w":4093,"h":1988},
"resolution": "1"
}
{"ATLAS":{"SPRITES":[
{"SPRITE":{"name":"0","x":2564,"y":2,"w":23,"h":144,"rotated":false}},
{"SPRITE":{"name":"1","x":3024,"y":522,"w":42,"h":85,"rotated":false}},
{"SPRITE":{"name":"2","x":2598,"y":1211,"w":67,"h":60,"rotated":false}},
{"SPRITE":{"name":"3","x":3964,"y":932,"w":93,"h":51,"rotated":false}},
{"SPRITE":{"name":"4","x":3691,"y":1016,"w":91,"h":43,"rotated":false}},
{"SPRITE":{"name":"5","x":3877,"y":270,"w":111,"h":95,"rotated":false}},
{"SPRITE":{"name":"6","x":3449,"y":1124,"w":113,"h":127,"rotated":false}},
{"SPRITE":{"name":"7","x":367,"y":1129,"w":382,"h":123,"rotated":false}},
{"SPRITE":{"name":"8","x":2,"y":1122,"w":364,"h":131,"rotated":false}},
{"SPRITE":{"name":"9","x":2,"y":1122,"w":364,"h":131,"rotated":false}},
{"SPRITE":{"name":"10","x":2437,"y":1136,"w":31,"h":31,"rotated":false}},
{"SPRITE":{"name":"11","x":1599,"y":1133,"w":350,"h":113,"rotated":false}},
{"SPRITE":{"name":"12","x":1599,"y":1133,"w":350,"h":113,"rotated":false}},
{"SPRITE":{"name":"13","x":2670,"y":1175,"w":142,"h":94,"rotated":false}},
{"SPRITE":{"name":"14","x":3877,"y":366,"w":122,"h":82,"rotated":false}},
{"SPRITE":{"name":"15","x":1162,"y":1112,"w":436,"h":151,"rotated":false}},
{"SPRITE":{"name":"16","x":1599,"y":985,"w":435,"h":147,"rotated":false}},
{"SPRITE":{"name":"17","x":1599,"y":985,"w":435,"h":147,"rotated":false}},
{"SPRITE":{"name":"18","x":779,"y":692,"w":440,"h":344,"rotated":false}},
{"SPRITE":{"name":"19","x":1550,"y":711,"w":388,"h":273,"rotated":false}},
{"SPRITE":{"name":"20","x":2106,"y":2,"w":376,"h":155,"rotated":false}},
{"SPRITE":{"name":"21","x":2,"y":2,"w":932,"h":689,"rotated":false}},
{"SPRITE":{"name":"22","x":3907,"y":1171,"w":48,"h":36,"rotated":false}},
{"SPRITE":{"name":"23","x":3907,"y":1171,"w":48,"h":36,"rotated":false}},
{"SPRITE":{"name":"24","x":3858,"y":1206,"w":48,"h":40,"rotated":false}},
{"SPRITE":{"name":"25","x":3762,"y":1093,"w":48,"h":64,"rotated":false}},
{"SPRITE":{"name":"26","x":3713,"y":1060,"w":48,"h":66,"rotated":false}},
{"SPRITE":{"name":"27","x":3885,"y":1095,"w":48,"h":67,"rotated":false}},
{"SPRITE":{"name":"28","x":2106,"y":158,"w":48,"h":69,"rotated":false}},
{"SPRITE":{"name":"29","x":2481,"y":498,"w":48,"h":71,"rotated":false}},
{"SPRITE":{"name":"30","x":1162,"y":1037,"w":48,"h":73,"rotated":false}},
{"SPRITE":{"name":"31","x":4018,"y":188,"w":48,"h":76,"rotated":false}},
{"SPRITE":{"name":"32","x":3455,"y":856,"w":48,"h":78,"rotated":false}},
{"SPRITE":{"name":"33","x":3785,"y":1011,"w":48,"h":81,"rotated":false}},
{"SPRITE":{"name":"34","x":4002,"y":1164,"w":48,"h":82,"rotated":false}},
{"SPRITE":{"name":"35","x":3953,"y":1083,"w":48,"h":87,"rotated":false}},
{"SPRITE":{"name":"36","x":3904,"y":1004,"w":48,"h":90,"rotated":false}},
{"SPRITE":{"name":"37","x":4013,"y":1070,"w":48,"h":93,"rotated":false}},
{"SPRITE":{"name":"38","x":3964,"y":984,"w":48,"h":98,"rotated":false}},
{"SPRITE":{"name":"39","x":1550,"y":985,"w":48,"h":103,"rotated":false}},
{"SPRITE":{"name":"40","x":3848,"y":894,"w":50,"h":109,"rotated":false}},
{"SPRITE":{"name":"41","x":3797,"y":894,"w":50,"h":116,"rotated":false}},
{"SPRITE":{"name":"42","x":2777,"y":452,"w":50,"h":121,"rotated":false}},
{"SPRITE":{"name":"43","x":4002,"y":585,"w":50,"h":119,"rotated":false}},
{"SPRITE":{"name":"44","x":3913,"y":886,"w":50,"h":117,"rotated":false}},
{"SPRITE":{"name":"45","x":3059,"y":2,"w":52,"h":129,"rotated":false}},
{"SPRITE":{"name":"46","x":3964,"y":886,"w":123,"h":45,"rotated":false}},
{"SPRITE":{"name":"47","x":1770,"y":228,"w":123,"h":43,"rotated":false}},
{"SPRITE":{"name":"48","x":3424,"y":486,"w":99,"h":35,"rotated":false}},
{"SPRITE":{"name":"49","x":2979,"y":1141,"w":99,"h":34,"rotated":false}},
{"SPRITE":{"name":"50","x":2979,"y":1141,"w":99,"h":34,"rotated":false}},
{"SPRITE":{"name":"51","x":2590,"y":360,"w":99,"h":36,"rotated":false}},
{"SPRITE":{"name":"52","x":3709,"y":783,"w":35,"h":21,"rotated":false}},
{"SPRITE":{"name":"53","x":1220,"y":711,"w":329,"h":400,"rotated":false}},
{"SPRITE":{"name":"54","x":3042,"y":979,"w":112,"h":161,"rotated":false}},
{"SPRITE":{"name":"55","x":2929,"y":163,"w":199,"h":188,"rotated":false}},
{"SPRITE":{"name":"56","x":2175,"y":574,"w":96,"h":158,"rotated":false}},
{"SPRITE":{"name":"57","x":1623,"y":570,"w":277,"h":134,"rotated":false}},
{"SPRITE":{"name":"58","x":1550,"y":1089,"w":38,"h":22,"rotated":false}},
{"SPRITE":{"name":"59","x":3666,"y":152,"w":124,"h":165,"rotated":false}},
{"SPRITE":{"name":"60","x":1623,"y":272,"w":309,"h":297,"rotated":false}},
{"SPRITE":{"name":"61","x":3801,"y":2,"w":121,"h":91,"rotated":false}},
{"SPRITE":{"name":"62","x":3281,"y":522,"w":320,"h":77,"rotated":false}},
{"SPRITE":{"name":"63","x":3921,"y":755,"w":17,"h":22,"rotated":false}},
{"SPRITE":{"name":"64","x":1351,"y":272,"w":162,"h":74,"rotated":false}},
{"SPRITE":{"name":"65","x":3801,"y":94,"w":69,"h":50,"rotated":false}},
{"SPRITE":{"name":"66","x":2273,"y":574,"w":277,"h":174,"rotated":false}},
{"SPRITE":{"name":"67","x":2261,"y":1136,"w":175,"h":123,"rotated":false}},
{"SPRITE":{"name":"68","x":4058,"y":932,"w":32,"h":49,"rotated":false}},
{"SPRITE":{"name":"69","x":3791,"y":270,"w":66,"h":38,"rotated":false}},
{"SPRITE":{"name":"70","x":3079,"y":1141,"w":71,"h":40,"rotated":false}},
{"SPRITE":{"name":"71","x":2690,"y":360,"w":82,"h":29,"rotated":false}},
{"SPRITE":{"name":"72","x":3602,"y":516,"w":88,"h":69,"rotated":false}},
{"SPRITE":{"name":"73","x":4022,"y":806,"w":69,"h":27,"rotated":false}},
{"SPRITE":{"name":"74","x":1894,"y":228,"w":30,"h":40,"rotated":false}},
{"SPRITE":{"name":"75","x":3987,"y":654,"w":10,"h":13,"rotated":false}},
{"SPRITE":{"name":"76","x":3896,"y":1084,"w":7,"h":7,"rotated":false}},
{"SPRITE":{"name":"77","x":3834,"y":1074,"w":9,"h":9,"rotated":false}},
{"SPRITE":{"name":"78","x":3885,"y":1084,"w":10,"h":10,"rotated":false}},
{"SPRITE":{"name":"79","x":3441,"y":736,"w":12,"h":12,"rotated":false}},
{"SPRITE":{"name":"80","x":3811,"y":1128,"w":19,"h":17,"rotated":false}},
{"SPRITE":{"name":"81","x":3971,"y":654,"w":15,"h":14,"rotated":false}},
{"SPRITE":{"name":"82","x":3858,"y":1150,"w":7,"h":7,"rotated":false}},
{"SPRITE":{"name":"83","x":3794,"y":452,"w":5,"h":5,"rotated":false}},
{"SPRITE":{"name":"84","x":3326,"y":751,"w":32,"h":57,"rotated":false}},
{"SPRITE":{"name":"85","x":2583,"y":147,"w":6,"h":7,"rotated":false}},
{"SPRITE":{"name":"86","x":935,"y":617,"w":198,"h":66,"rotated":false}},
{"SPRITE":{"name":"87","x":426,"y":692,"w":352,"h":436,"rotated":false}},
{"SPRITE":{"name":"88","x":3814,"y":452,"w":156,"h":69,"rotated":false}},
{"SPRITE":{"name":"89","x":2829,"y":452,"w":194,"h":197,"rotated":false}},
{"SPRITE":{"name":"90","x":2670,"y":733,"w":181,"h":216,"rotated":false}},
{"SPRITE":{"name":"91","x":3791,"y":150,"w":93,"h":119,"rotated":false}},
{"SPRITE":{"name":"92","x":4000,"y":372,"w":86,"h":75,"rotated":false}},
{"SPRITE":{"name":"93","x":3587,"y":320,"w":113,"h":195,"rotated":false}},
{"SPRITE":{"name":"94","x":2483,"y":2,"w":80,"h":151,"rotated":false}},
{"SPRITE":{"name":"95","x":3493,"y":2,"w":177,"h":149,"rotated":false}},
{"SPRITE":{"name":"96","x":3327,"y":183,"w":9,"h":8,"rotated":false}},
{"SPRITE":{"name":"97","x":3787,"y":452,"w":6,"h":6,"rotated":false}},
{"SPRITE":{"name":"98","x":4002,"y":705,"w":57,"h":48,"rotated":false}},
{"SPRITE":{"name":"99","x":2574,"y":147,"w":8,"h":8,"rotated":false}},
{"SPRITE":{"name":"100","x":3814,"y":605,"w":8,"h":12,"rotated":false}},
{"SPRITE":{"name":"101","x":1589,"y":1089,"w":9,"h":10,"rotated":false}},
{"SPRITE":{"name":"102","x":3701,"y":318,"w":85,"h":140,"rotated":false}},
{"SPRITE":{"name":"103","x":2,"y":692,"w":423,"h":429,"rotated":false}},
{"SPRITE":{"name":"104","x":3533,"y":152,"w":132,"h":167,"rotated":false}},
{"SPRITE":{"name":"105","x":3249,"y":961,"w":66,"h":16,"rotated":false}},
{"SPRITE":{"name":"106","x":3576,"y":808,"w":52,"h":43,"rotated":false}},
{"SPRITE":{"name":"107","x":3858,"y":1163,"w":46,"h":42,"rotated":false}},
{"SPRITE":{"name":"108","x":3834,"y":1011,"w":13,"h":62,"rotated":false}},
{"SPRITE":{"name":"109","x":3654,"y":1025,"w":34,"h":29,"rotated":false}},
{"SPRITE":{"name":"110","x":3811,"y":1150,"w":46,"h":65,"rotated":false}},
{"SPRITE":{"name":"111","x":3701,"y":459,"w":112,"h":46,"rotated":false}},
{"SPRITE":{"name":"112","x":2035,"y":898,"w":226,"h":223,"rotated":false}},
{"SPRITE":{"name":"113","x":3233,"y":351,"w":190,"h":170,"rotated":false}},
{"SPRITE":{"name":"114","x":3654,"y":1060,"w":58,"h":55,"rotated":false}},
{"SPRITE":{"name":"115","x":3885,"y":94,"w":112,"h":94,"rotated":false}},
{"SPRITE":{"name":"116","x":3576,"y":706,"w":132,"h":101,"rotated":false}},
{"SPRITE":{"name":"117","x":1939,"y":493,"w":235,"h":239,"rotated":false}},
{"SPRITE":{"name":"118","x":3242,"y":1150,"w":141,"h":111,"rotated":false}},
{"SPRITE":{"name":"119","x":2822,"y":2,"w":236,"h":160,"rotated":false}},
{"SPRITE":{"name":"120","x":2475,"y":749,"w":194,"h":236,"rotated":false}},
{"SPRITE":{"name":"121","x":3455,"y":736,"w":120,"h":119,"rotated":false}},
{"SPRITE":{"name":"122","x":2551,"y":398,"w":225,"h":189,"rotated":false}},
{"SPRITE":{"name":"123","x":3923,"y":2,"w":130,"h":81,"rotated":false}},
{"SPRITE":{"name":"124","x":3691,"y":621,"w":132,"h":83,"rotated":false}},
{"SPRITE":{"name":"125","x":3531,"y":1025,"w":122,"h":80,"rotated":false}},
{"SPRITE":{"name":"126","x":3814,"y":522,"w":129,"h":82,"rotated":false}},
{"SPRITE":{"name":"127","x":3709,"y":705,"w":125,"h":77,"rotated":false}},
{"SPRITE":{"name":"128","x":3885,"y":189,"w":132,"h":76,"rotated":false}},
{"SPRITE":{"name":"129","x":2035,"y":1122,"w":225,"h":142,"rotated":false}},
{"SPRITE":{"name":"130","x":2777,"y":187,"w":151,"h":264,"rotated":false}},
{"SPRITE":{"name":"131","x":2551,"y":588,"w":277,"h":144,"rotated":false}},
{"SPRITE":{"name":"132","x":3327,"y":197,"w":205,"h":140,"rotated":false}},
{"SPRITE":{"name":"133","x":2175,"y":401,"w":305,"h":172,"rotated":false}},
{"SPRITE":{"name":"134","x":2841,"y":1136,"w":137,"h":129,"rotated":false}},
{"SPRITE":{"name":"135","x":3129,"y":172,"w":197,"h":178,"rotated":false}},
{"SPRITE":{"name":"136","x":2,"y":1122,"w":364,"h":131,"rotated":false}},
{"SPRITE":{"name":"137","x":3424,"y":338,"w":162,"h":147,"rotated":false}},
{"SPRITE":{"name":"138","x":1933,"y":228,"w":223,"h":264,"rotated":false}},
{"SPRITE":{"name":"139","x":1351,"y":2,"w":418,"h":269,"rotated":false}},
{"SPRITE":{"name":"140","x":1599,"y":1133,"w":350,"h":113,"rotated":false}},
{"SPRITE":{"name":"141","x":1599,"y":985,"w":435,"h":147,"rotated":false}},
{"SPRITE":{"name":"142","x":3921,"y":670,"w":80,"h":84,"rotated":false}},
{"SPRITE":{"name":"143","x":2564,"y":147,"w":9,"h":8,"rotated":false}},
{"SPRITE":{"name":"144","x":2437,"y":1168,"w":31,"h":27,"rotated":false}},
{"SPRITE":{"name":"145","x":3664,"y":808,"w":110,"h":88,"rotated":false}},
{"SPRITE":{"name":"146","x":3024,"y":608,"w":43,"h":39,"rotated":false}},
{"SPRITE":{"name":"147","x":3563,"y":1106,"w":55,"h":58,"rotated":false}},
{"SPRITE":{"name":"148","x":3971,"y":619,"w":19,"h":34,"rotated":false}},
{"SPRITE":{"name":"149","x":3416,"y":1250,"w":30,"h":23,"rotated":false}},
{"SPRITE":{"name":"150","x":1939,"y":898,"w":82,"h":85,"rotated":false}},
{"SPRITE":{"name":"151","x":4054,"y":39,"w":34,"h":41,"rotated":false}},
{"SPRITE":{"name":"152","x":3691,"y":516,"w":122,"h":104,"rotated":false}},
{"SPRITE":{"name":"153","x":3762,"y":1060,"w":17,"h":32,"rotated":false}},
{"SPRITE":{"name":"154","x":3745,"y":783,"w":28,"h":20,"rotated":false}},
{"SPRITE":{"name":"155","x":3524,"y":486,"w":45,"h":35,"rotated":false}},
{"SPRITE":{"name":"156","x":4000,"y":266,"w":74,"h":105,"rotated":false}},
{"SPRITE":{"name":"157","x":3775,"y":836,"w":137,"h":57,"rotated":false}},
{"SPRITE":{"name":"158","x":3384,"y":1250,"w":31,"h":23,"rotated":false}},
{"SPRITE":{"name":"159","x":3504,"y":908,"w":24,"h":24,"rotated":false}},
{"SPRITE":{"name":"160","x":4013,"y":984,"w":55,"h":85,"rotated":false}},
{"SPRITE":{"name":"161","x":4054,"y":2,"w":40,"h":36,"rotated":false}},
{"SPRITE":{"name":"162","x":3775,"y":783,"w":163,"h":52,"rotated":false}},
{"SPRITE":{"name":"163","x":1950,"y":1133,"w":73,"h":115,"rotated":false}},
{"SPRITE":{"name":"164","x":3531,"y":962,"w":159,"h":62,"rotated":false}},
{"SPRITE":{"name":"165","x":3811,"y":1093,"w":20,"h":34,"rotated":false}},
{"SPRITE":{"name":"166","x":4067,"y":230,"w":27,"h":32,"rotated":false}},
{"SPRITE":{"name":"167","x":4067,"y":188,"w":24,"h":41,"rotated":false}},
{"SPRITE":{"name":"168","x":3504,"y":856,"w":26,"h":51,"rotated":false}},
{"SPRITE":{"name":"169","x":2929,"y":352,"w":86,"h":98,"rotated":false}},
{"SPRITE":{"name":"170","x":3824,"y":605,"w":146,"h":64,"rotated":false}},
{"SPRITE":{"name":"171","x":1589,"y":1100,"w":9,"h":10,"rotated":false}},
{"SPRITE":{"name":"172","x":3327,"y":172,"w":8,"h":10,"rotated":false}},
{"SPRITE":{"name":"173","x":3939,"y":806,"w":82,"h":27,"rotated":false}},
{"SPRITE":{"name":"174","x":3944,"y":522,"w":141,"h":62,"rotated":false}},
{"SPRITE":{"name":"175","x":3024,"y":352,"w":208,"h":169,"rotated":false}},
{"SPRITE":{"name":"176","x":3129,"y":2,"w":208,"h":169,"rotated":false}},
{"SPRITE":{"name":"177","x":3042,"y":814,"w":206,"h":164,"rotated":false}},
{"SPRITE":{"name":"178","x":3072,"y":522,"w":208,"h":161,"rotated":false}},
{"SPRITE":{"name":"179","x":779,"y":1037,"w":382,"h":218,"rotated":false}},
{"SPRITE":{"name":"180","x":935,"y":357,"w":237,"h":259,"rotated":false}},
{"SPRITE":{"name":"181","x":3155,"y":979,"w":86,"h":203,"rotated":false}},
{"SPRITE":{"name":"182","x":3364,"y":751,"w":90,"h":183,"rotated":false}},
{"SPRITE":{"name":"183","x":3357,"y":961,"w":91,"h":188,"rotated":false}},
{"SPRITE":{"name":"184","x":2852,"y":814,"w":185,"h":122,"rotated":false}},
{"SPRITE":{"name":"185","x":3787,"y":318,"w":89,"h":133,"rotated":false}},
{"SPRITE":{"name":"186","x":2590,"y":187,"w":183,"h":172,"rotated":false}},
{"SPRITE":{"name":"187","x":3242,"y":979,"w":114,"h":151,"rotated":false}},
{"SPRITE":{"name":"188","x":4047,"y":834,"w":42,"h":36,"rotated":false}},
{"SPRITE":{"name":"189","x":1134,"y":617,"w":53,"h":63,"rotated":false}},
{"SPRITE":{"name":"190","x":3166,"y":1183,"w":67,"h":80,"rotated":false}},
{"SPRITE":{"name":"191","x":3848,"y":1004,"w":55,"h":79,"rotated":false}},
{"SPRITE":{"name":"192","x":3834,"y":1084,"w":50,"h":65,"rotated":false}},
{"SPRITE":{"name":"193","x":3629,"y":808,"w":33,"h":43,"rotated":false}},
{"SPRITE":{"name":"194","x":3493,"y":152,"w":26,"h":38,"rotated":false}},
{"SPRITE":{"name":"195","x":3971,"y":585,"w":23,"h":33,"rotated":false}},
{"SPRITE":{"name":"196","x":2530,"y":498,"w":20,"h":24,"rotated":false}},
{"SPRITE":{"name":"197","x":3939,"y":755,"w":121,"h":50,"rotated":false}},
{"SPRITE":{"name":"198","x":3913,"y":836,"w":133,"h":49,"rotated":false}},
{"SPRITE":{"name":"199","x":3971,"y":449,"w":119,"h":69,"rotated":false}},
{"SPRITE":{"name":"200","x":3072,"y":684,"w":253,"h":125,"rotated":false}},
{"SPRITE":{"name":"201","x":3441,"y":600,"w":116,"h":135,"rotated":false}},
{"SPRITE":{"name":"202","x":2475,"y":986,"w":194,"h":224,"rotated":false}},
{"SPRITE":{"name":"203","x":1770,"y":2,"w":335,"h":225,"rotated":false}},
{"SPRITE":{"name":"204","x":2157,"y":158,"w":217,"h":242,"rotated":false}},
{"SPRITE":{"name":"205","x":3249,"y":810,"w":114,"h":150,"rotated":false}},
{"SPRITE":{"name":"206","x":3326,"y":600,"w":114,"h":150,"rotated":false}},
{"SPRITE":{"name":"207","x":3835,"y":670,"w":85,"h":87,"rotated":false}},
{"SPRITE":{"name":"208","x":3671,"y":2,"w":129,"h":147,"rotated":false}},
{"SPRITE":{"name":"209","x":2852,"y":650,"w":219,"h":163,"rotated":false}},
{"SPRITE":{"name":"210","x":3338,"y":2,"w":154,"h":194,"rotated":false}},
{"SPRITE":{"name":"211","x":1514,"y":272,"w":108,"h":68,"rotated":false}},
{"SPRITE":{"name":"212","x":3664,"y":897,"w":132,"h":64,"rotated":false}},
{"SPRITE":{"name":"213","x":2437,"y":1211,"w":160,"h":62,"rotated":false}},
{"SPRITE":{"name":"214","x":3449,"y":935,"w":81,"h":188,"rotated":false}},
{"SPRITE":{"name":"215","x":2841,"y":950,"w":200,"h":185,"rotated":false}},
{"SPRITE":{"name":"216","x":3384,"y":1150,"w":63,"h":99,"rotated":false}},
{"SPRITE":{"name":"217","x":2481,"y":398,"w":63,"h":99,"rotated":false}},
{"SPRITE":{"name":"218","x":3558,"y":600,"w":132,"h":105,"rotated":false}},
{"SPRITE":{"name":"219","x":3531,"y":856,"w":132,"h":105,"rotated":false}},
{"SPRITE":{"name":"220","x":2590,"y":2,"w":231,"h":184,"rotated":false}},
{"SPRITE":{"name":"221","x":2670,"y":950,"w":170,"h":224,"rotated":false}},
{"SPRITE":{"name":"222","x":2273,"y":749,"w":198,"h":144,"rotated":false}},
{"SPRITE":{"name":"223","x":3691,"y":962,"w":93,"h":53,"rotated":false}},
{"SPRITE":{"name":"224","x":2979,"y":1183,"w":186,"h":91,"rotated":false}},
{"SPRITE":{"name":"225","x":1939,"y":733,"w":333,"h":164,"rotated":false}},
{"SPRITE":{"name":"226","x":935,"y":2,"w":415,"h":354,"rotated":false}},
{"SPRITE":{"name":"227","x":1220,"y":357,"w":402,"h":353,"rotated":false}},
{"SPRITE":{"name":"228","x":3998,"y":84,"w":90,"h":103,"rotated":false}},
{"SPRITE":{"name":"229","x":2375,"y":158,"w":214,"h":239,"rotated":false}},
{"SPRITE":{"name":"230","x":2262,"y":898,"w":212,"h":237,"rotated":false}}]},
"meta":{
"app":"Adobe Animate (Better TA Extension)",
"version":"22.0.3.179",
"image":"spritemap1.png",
"format":"RGBA8888",
"size":{"w":4096,"h":1280},
"resolution":"1"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 642 KiB

After

Width:  |  Height:  |  Size: 1.2 MiB

+9 -3
View File
@@ -203,6 +203,7 @@
<str id="paste">Paste</str>
<str id="cut">Cut</str>
<str id="delete">Delete</str>
<str id="deletestacked">Delete Stacked Notes</str>
</group>
<str id="topBar.chart">Chart</str>
@@ -223,6 +224,7 @@
<str id="resetZoom">Reset Zoom</str>
<str id="showSectionsSeparator">Show Sections Separator</str>
<str id="showBeatsSeparator">Show Beats Separator</str>
<str id="showCameraHighlights">Show Camera Highlights</str>
<str id="rainbowWaveforms">Rainbow Waveforms</str>
<str id="lowDetailWaveforms">Low Detail Waveforms</str>
<str id="scrollLeft">Scroll Left</str>
@@ -236,8 +238,9 @@
<str id="goEnd">Go forward to the end</str> <!-- used to be "Go to the end" -->
<str id="addOpponentCamera">Add camera on Opponent</str>
<str id="addPlayerCamera">Add camera on Player</str>
<str id="muteInst">Mute instrumental</str>
<str id="muteVoices">Mute voices</str>
<str id="inst">Instrumental</str>
<str id="voices">Voices</str>
<str id="hitsounds">Hitsounds</str>
</group>
<group name="BookmarkMenu" prefix="bookmarks.">
@@ -250,6 +253,7 @@
<str id="editBookmarkListTitle">Bookmark List</str>
<str id="editBookmarksTitle">Edit Bookmarks</str>
<str id="newBookmarkName">New Bookmark</str>
<str id="bookmarkList">Bookmark List</str>
</group>
<str id="topBar.note">Note</str>
@@ -258,6 +262,7 @@
<str id="subtractSustainLength">Subtract sustain length</str>
<str id="selectAll">Select all</str>
<str id="selectMeasure">Select measure</str>
<str id="noteTypesList">Note Types List</str>
<str id="editNoteTypesList">Edit Note Types List</str>
</group>
<str id="topBar.snap">Snap</str>
@@ -308,8 +313,9 @@
<group name="StrumlineOptions" prefix="strumLine.">
<str id="button-name">Options ↓</str>
<str id="waveforms">Waveforms</str>
<str id="hitsounds">Hitsounds</str>
<str id="muteVocals">Mute Vocals</str>
<str id="vocals">Vocals</str>
<str id="edit">Edit</str>
<str id="delete">Delete</str>
+2 -1
View File
@@ -16,7 +16,7 @@
<str id="animation">Animación</str>
<str id="atlas">Atlas</str>
<str id="spritemap">Mapa de sprites</str>
<str id="directory">Ubicación ({0}/)</str>
<str id="found">{0} {1} Encontrado</str>
<str id="foundplural">{0} {1} Encontrados</str>
@@ -188,6 +188,7 @@
<str id="paste">Pegar</str>
<str id="cut">Cortar</str>
<str id="delete">Eliminar</str>
<str id="deletestacked">Eliminar Notas Apiladas</str>
</group>
<str id="topBar.chart">Chart</str>
+1
View File
@@ -194,6 +194,7 @@
<str id="paste">Incolla</str>
<str id="cut">Taglia</str>
<str id="delete">Elimina</str>
<str id="deletestacked">Elimina Note Impilate</str>
</group>
<str id="topBar.chart">Chart</str>
+4 -3
View File
@@ -18,7 +18,7 @@
<str id="animation">Animacja</str>
<str id="atlas">Atlas</str>
<str id="spritemap">Mapa sprite'ów</str>
<str id="directory">Folder ({0}/)</str>
<str id="found">{0} {1} Znaleziono</str>
<str id="foundplural">{0} {1} Znaleziono</str>
@@ -87,7 +87,7 @@
<group name="CharterEventScreen" prefix="charterEventScreen.">
<str id="title-creating">Stwórz Grupę Wydarzeń</str>
<str id="title-editing">Edytuj Grupę Wydarzeń</str>
<str id="noEvent">Brak Wydarzenia</str>
<str id="strumLine.format">Struma #{0} ({1})</str>
@@ -197,6 +197,7 @@
<str id="paste">Wklej</str>
<str id="cut">Wytnij</str>
<str id="delete">Usuń</str>
<str id="deletestacked">Usuń Notatki Ułożone w Stos</str>
</group>
<str id="topBar.chart">Mapa</str>
@@ -223,7 +224,7 @@
<str id="scrollRight">Przewiń w Prawo</str>
<str id="scrollReset">Resetuj Przewinięcie</str>
</group>
<str id="topBar.song">Ścieżka</str>
<group name="SongMenu" prefix="song.">
<str id="goStart">Wróć do początku</str>
+1
View File
@@ -203,6 +203,7 @@
<str id="paste">Colar</str>
<str id="cut">Cortar</str>
<str id="delete">Apagar</str>
<str id="deletestacked">Apagar Notas Empilhadas</str>
</group>
<str id="topBar.chart">Mapa</str>
+2 -2
View File
@@ -12,7 +12,7 @@ function create() {
tankman = new FunkinSprite(game.dad.x + game.dad.globalOffset.x + 520, game.dad.y + game.dad.globalOffset.y + 225);
tankman.antialiasing = true;
tankman.loadSprite(Paths.image('game/cutscenes/tank/guns-tankman'));
tankman.animateAtlas.anim.addBySymbol('tank', 'TANK TALK 2', 0, false);
tankman.addAnim('tank', 'TANK TALK 2', 0, false);
game.insert(game.members.indexOf(game.dad), tankman);
game.dad.visible = false;
@@ -65,4 +65,4 @@ function destroy() {
for(thing in [tankman, tankTalk]) thing.destroy();
if(destroyDistorto) distorto.destroy();
FlxTween.tween(FlxG.camera, {zoom: game.defaultCamZoom}, 0.7, {ease: FlxEase.quadInOut, startDelay: 0});
}
}
+13 -9
View File
@@ -33,20 +33,20 @@ function create() {
tankman = new FunkinSprite(game.dad.x + game.dad.globalOffset.x + 520, game.dad.y + game.dad.globalOffset.y + 225);
tankman.antialiasing = true;
tankman.loadSprite(Paths.image('game/cutscenes/tank/stress-tankman'));
tankman.animateAtlas.anim.addBySymbol('p1', 'TANK TALK 3 P1 UNCUT', 0, false);
tankman.animateAtlas.anim.addBySymbol('p2', 'TANK TALK 3 P2 UNCUT', 0, false);
tankman.addAnim('p1', 'TANK TALK 3 P1 UNCUT', 0, false);
tankman.addAnim('p2', 'TANK TALK 3 P2 UNCUT', 0, false);
tankman.playAnim('p1');
pico = new FunkinSprite(game.gf.x + game.gf.globalOffset.x + 150, game.gf.y + game.gf.globalOffset.y + 395);
pico = new FunkinSprite(game.gf.x + game.gf.globalOffset.x - 615, game.gf.y + game.gf.globalOffset.y - 130);
pico.antialiasing = true;
pico.loadSprite(Paths.image('game/cutscenes/tank/stress-pico'));
pico.animateAtlas.anim.addBySymbol('die', 'GF Time to Die sequence', 24, false);
pico.animateAtlas.anim.addBySymbol('saves', 'Pico Saves them sequence', 24, false);
pico.animateAtlas.anim.addBySymbol('idle', 'Pico Dual Wield on Speaker idle', 24, true);
pico.addAnim('die', 'die', 24, false, null, null, 0, 0, null, true);
pico.addAnim('saves', 'saves', 24, false, null, null, 0, 0, null, true);
pico.addAnim('idle', 'idle', 24, true, null, null, 0, 0, null, true);
pico.scrollFactor.set(0.95, 0.95);
pico.playAnim("idle");
pico.visible = false;
game.insert(game.members.indexOf(game.gf) + 1, pico);
game.insert(game.members.indexOf(game.gf) + 2, pico);
game.insert(game.members.indexOf(game.dad) + 1, tankman);
game.dad.visible = false;
@@ -59,7 +59,11 @@ function update(elapsed) {
lipSync(tankman, 0, 16750);
if (stressCutscene.time > 15100) {
step = 1;
focusOn(game.gf);
//focusOn(game.gf);
game.camFollow.x += 350;
game.camFollow.y -= 200;
pico.visible = true;
pico.playAnim('die', true);
@@ -118,7 +122,7 @@ function update(elapsed) {
}
function lipSync(char:FunkinSprite, begin:Float, end:Float) {
char.animateAtlas.anim.curFrame = Std.int(FlxMath.remapToRange(stressCutscene.time, begin, end, 0, char.animateAtlas.anim.length-1));
char.anim.curAnim.curFrame = Std.int(FlxMath.remapToRange(stressCutscene.time, begin, end, 0, char.anim.curAnim.numFrames - 1));
}
function focusOn(char, snap:Bool = false) {
+3 -3
View File
@@ -12,8 +12,8 @@ function create() {
tankman = new FunkinSprite(game.dad.x + game.dad.globalOffset.x + 520, game.dad.y + game.dad.globalOffset.y + 225);
tankman.antialiasing = true;
tankman.loadSprite(Paths.image('game/cutscenes/tank/ugh-tankman'));
tankman.animateAtlas.anim.addBySymbol('1', 'TANK TALK 1 P1', 0, false);
tankman.animateAtlas.anim.addBySymbol('2', 'TANK TALK 1 P2', 0, false);
tankman.addAnim('1', 'TANK TALK 1 P1', 0, false);
tankman.addAnim('2', 'TANK TALK 1 P2', 0, false);
game.insert(game.members.indexOf(game.dad), tankman);
game.dad.visible = false;
@@ -77,4 +77,4 @@ function destroy() {
for(timer in timers) timer.cancel();
for(thing in [tankTalk1, tankTalk2, bfBeep, tankman]) thing.destroy();
if(destroyDistorto) distorto.destroy();
}
}
+6 -7
View File
@@ -6,16 +6,15 @@
<!-- OpenFL & Lime (Required for Flixel) -->
<git name="openfl" url="https://github.com/CodenameCrew/cne-openfl" ref="old" />
<lib name="lime" version="8.1.2" />
<!-- <git name="lime" url="https://github.com/CodenameCrew/cne-lime" /> disabled for now until fixed -->
<!-- Flixel -->
<git name="flixel" url="https://github.com/CodenameCrew/cne-flixel" />
<git name="flixel-addons" url="https://github.com/CodenameCrew/cne-flixel-addons" />
<git name="flixel" url="https://github.com/CodenameCrew/cne-flixel" ref="old" />
<git name="flixel-addons" url="https://github.com/CodenameCrew/cne-flixel-addons" ref="old" />
<!-- Other Libraries -->
<git name="hscript-improved" url="https://github.com/CodenameCrew/hscript-improved" ref="codename-dev" />
<git name="hxdiscord_rpc" url="https://github.com/CodenameCrew/cne-hxdiscord_rpc" skipDeps="true" />
<lib name="funkin-modchart" version="1.2.4" skipDeps="true" />
<git name="hxdiscord_rpc" url="https://github.com/CodenameCrew/cne-hxdiscord_rpc" ref="old" skipDeps="true" />
<lib name="funkin-modchart" skipDeps="true" />
<lib name="hxvlc" version="1.9.3" skipDeps="true" />
<!-- Documentation and other features -->
@@ -30,9 +29,9 @@
<lib global="true" name="hxp" /> <lib name="hxp" />
<lib global="true" name="format" /> <lib name="format" />
<git name="hxcpp" url="https://github.com/CodenameCrew/cne-hxcpp" />
<git name="hxcpp" url="https://github.com/CodenameCrew/cne-hxcpp" ref="old" />
<git name="flixel-animate" url="https://github.com/MaybeMaru/flixel-animate/" skipDeps="true"/>
<git name="flixel-animate" url="https://github.com/CodenameCrew/cne-flixel-animate/" ref="old" skipDeps="true"/>
</if>
<!-- Hxcpp Building -->
+3
View File
@@ -74,6 +74,9 @@
Reduces compilation time at the cost of limited HScript flexibility. !-->
<define name="CUSTOM_CLASSES" unless="STRIPPED_COMPILE" />
<!-- Comment this out to disable abstract support in hscript. !-->
<haxedef name="HSCRIPT_ABSTRACT_SUPPORT" unless="STRIPPED_COMPILE" />
<!-- Comment this out to disable multithreading !-->
<haxedef name="ALLOW_MULTITHREADING" unless="web || flash" />
+12 -18
View File
@@ -173,6 +173,17 @@ class FunkinSprite extends FlxAnimate implements IBeatReceiver implements IOffse
{
}
public override function draw() {
// re-implementing the `onDraw` functionality from `FlxSprite` since `FlxAnimate` didn't have this, so we have to add it back in ourselves
if (this.isAnimate && this.__drawOverrided) {
this.__drawOverrided = false;
this.onDraw(this);
this.__drawOverrided = true;
return;
}
super.draw();
}
// ANIMATE ATLAS DRAWING
#if REGION
@@ -355,26 +366,9 @@ class FunkinSprite extends FlxAnimate implements IBeatReceiver implements IOffse
}
override function prepareDrawMatrix(matrix:FlxMatrix, camera:FlxCamera):Void {
matrix.translate(-origin.x, -origin.y);
if (frameOffsetAngle != null && frameOffsetAngle != angle)
{
var angleOff = (frameOffsetAngle - angle) * FlxAngle.TO_RAD;
var cos = Math.cos(angleOff);
var sin = Math.sin(angleOff);
// cos doesnt need to be negated
matrix.rotateWithTrig(cos, -sin);
matrix.translate(-frameOffset.x, -frameOffset.y);
matrix.rotateWithTrig(cos, sin);
}
else
matrix.translate(-frameOffset.x, -frameOffset.y);
matrix.translate(origin.x, origin.y);
super.prepareDrawMatrix(matrix, camera);
if(__shouldDoZoomFactor()) {
if (__shouldDoZoomFactor()) {
__prepareZoomFactor(_rect2, camera);
matrix.setTo(
matrix.a * _rect2.width, matrix.b * _rect2.height,
+83 -4
View File
@@ -1,19 +1,98 @@
package funkin.backend;
import flixel.FlxCamera;
import flixel.FlxG;
import flixel.math.FlxMath;
import flixel.text.FlxText;
import flixel.util.FlxColor;
import funkin.backend.system.Flags;
class FunkinText extends FlxText {
public function new(X:Float = 0, Y:Float = 0, FieldWidth:Float = 0, ?Text:String, ?Size:Int, Border:Bool = true) {
if (Size == null) Size = Flags.DEFAULT_FONT_SIZE;
class FunkinText extends FlxText
{
public var zoomFactor:Float = 1;
public var zoomFactorEnabled:Bool = true;
public function new(X:Float = 0, Y:Float = 0, FieldWidth:Float = 0, ?Text:String, ?Size:Int, Border:Bool = true)
{
if (Size == null)
Size = Flags.DEFAULT_FONT_SIZE;
super(X, Y, FieldWidth, Text, Size);
setFormat(Paths.font(Flags.DEFAULT_FONT), Size, FlxColor.WHITE);
if (Border) {
if (Border)
{
borderStyle = OUTLINE;
borderSize = 1;
borderColor = 0xFF000000;
}
}
private inline function __shouldDoZoomFactor():Bool
{
return zoomFactorEnabled && zoomFactor != 1;
}
private inline function __getZoomScaleX(camera:FlxCamera):Float
{
return (camera.scaleX > 0 ? Math.max : Math.min)(0, FlxMath.lerp(1 / camera.scaleX, 1, zoomFactor));
}
private inline function __getZoomScaleY(camera:FlxCamera):Float
{
return (camera.scaleY > 0 ? Math.max : Math.min)(0, FlxMath.lerp(1 / camera.scaleY, 1, zoomFactor));
}
private inline function __getZoomAnchorX(camera:FlxCamera):Float
{
if (Flags.USE_LEGACY_ZOOM_FACTOR)
return camera.width * 0.5;
return camera.width * 0.5 + camera.scroll.x * scrollFactor.x;
}
private inline function __getZoomAnchorY(camera:FlxCamera):Float
{
if (Flags.USE_LEGACY_ZOOM_FACTOR)
return camera.height * 0.5;
return camera.height * 0.5 + camera.scroll.y * scrollFactor.y;
}
override public function draw():Void
{
if (!__shouldDoZoomFactor())
{
super.draw();
return;
}
var camera:FlxCamera = this.camera;
if (camera == null)
camera = FlxG.camera;
var oldX:Float = x;
var oldY:Float = y;
var oldScaleX:Float = scale.x;
var oldScaleY:Float = scale.y;
var zoomScaleX:Float = __getZoomScaleX(camera);
var zoomScaleY:Float = __getZoomScaleY(camera);
var anchorX:Float = __getZoomAnchorX(camera);
var anchorY:Float = __getZoomAnchorY(camera);
x = (x - anchorX) * zoomScaleX + anchorX;
y = (y - anchorY) * zoomScaleY + anchorY;
scale.set(scale.x * zoomScaleX, scale.y * zoomScaleY);
super.draw();
x = oldX;
y = oldY;
scale.set(oldScaleX, oldScaleY);
}
}
@@ -8,7 +8,19 @@ import lime.utils.AssetLibrary;
import haxe.ds.Map;
class AssetsLibraryList extends AssetLibrary {
public var libraries:Array<AssetLibrary> = [];
public var cleanLibraries(get, never):Array<AssetLibrary>;
function get_cleanLibraries():Array<AssetLibrary> {
return [for (l in libraries) getCleanLibrary(l)];
}
// is true if any library in `libraries` contains some kind of compressed library.
public var hasCompressedLibrary(get, never):Bool;
function get_hasCompressedLibrary():Bool {
for (l in libraries) if (getCleanLibrary(l).isCompressed) return true;
return false;
}
@:allow(funkin.backend.system.Main)
@:allow(funkin.backend.system.MainState)
+13 -21
View File
@@ -83,10 +83,11 @@ class ModsFolder {
*/
public static function loadModLib(path:String, force:Bool = false, ?modName:String) {
#if MOD_SUPPORT
if (FileSystem.exists('$path.zip'))
return loadLibraryFromZip('$path'.toLowerCase(), '$path.zip', force, modName);
else
return loadLibraryFromFolder('$path'.toLowerCase(), '$path', force, modName);
for (ext in Flags.ALLOWED_ZIP_EXTENSIONS) {
if (!FileSystem.exists('$path.$ext')) continue;
return loadLibraryFromZip('$path'.toLowerCase(), '$path.$ext', force, modName);
}
return loadLibraryFromFolder('$path'.toLowerCase(), '$path', force, modName);
#else
return null;
@@ -96,27 +97,16 @@ class ModsFolder {
public static function getModsList():Array<String> {
var mods:Array<String> = [];
#if MOD_SUPPORT
if (!FileSystem.exists(modsPath)) {
// Mods directory does not exist yet, create it
FileSystem.createDirectory(modsPath);
}
// Mods directory does not exist yet, create it
if (!FileSystem.exists(modsPath)) FileSystem.createDirectory(modsPath);
final modsList:Array<String> = FileSystem.readDirectory(modsPath);
if (modsList == null || modsList.length <= 0)
return mods;
if (modsList == null || modsList.length <= 0) return mods;
for (modFolder in modsList) {
if (FileSystem.isDirectory(modsPath + modFolder)) {
mods.push(modFolder);
} else {
var ext = Path.extension(modFolder).toLowerCase();
switch(ext) {
case 'zip':
// is a zip mod!!
mods.push(Path.withoutExtension(modFolder));
}
}
if (FileSystem.isDirectory(modsPath + modFolder)) mods.push(modFolder);
else if (Flags.ALLOWED_ZIP_EXTENSIONS.contains(Path.extension(modFolder))) mods.push(Path.withoutExtension(modFolder));
}
#end
return mods;
@@ -128,7 +118,9 @@ class ModsFolder {
#if TRANSLATIONS_SUPPORT
if(skipTranslated && (l is TranslatedAssetLibrary)) continue;
#end
if (l is ScriptedAssetLibrary || l is IModsAssetLibrary) libs.push(cast(l, IModsAssetLibrary));
// No need to check for it being a `ScriptedAssetLibrary`, if `ScriptedAssetLibrary` extends ModsFolderLibrary, which implements `IModsAssetLibrary`
// If you have to revert this change then uhhhhh wasn't me, trust 🙏
if (/*l is ScriptedAssetLibrary ||*/ l is IModsAssetLibrary) libs.push(cast(l, IModsAssetLibrary));
}
return libs;
}
@@ -1,11 +1,16 @@
package funkin.backend.assets;
import funkin.backend.system.Flags;
import haxe.io.Path;
import lime.graphics.Image;
import lime.media.AudioBuffer;
import lime.text.Font;
import lime.utils.Bytes;
import openfl.utils.AssetLibrary;
#if sys
import sys.io.File;
#end
#if MOD_SUPPORT
import funkin.backend.utils.SysZip.SysZipEntry;
@@ -15,15 +20,16 @@ class ZipFolderLibrary extends AssetLibrary implements IModsAssetLibrary {
public var basePath:String;
public var modName:String;
public var libName:String;
public var useImageCache:Bool = false;
public var prefix = 'assets/';
public var zip:SysZip;
public var assets:Map<String, SysZipEntry> = [];
public var lowerCaseAssets:Map<String, SysZipEntry> = [];
public var nameMap:Map<String, String> = [];
public function new(basePath:String, libName:String, ?modName:String) {
public var PRELOAD_VIDEOS:Bool = true;
public function new(basePath:String, libName:String, ?modName:String, ?preloadVideos:Bool = true) {
this.libName = libName;
this.basePath = basePath;
@@ -31,24 +37,59 @@ class ZipFolderLibrary extends AssetLibrary implements IModsAssetLibrary {
this.modName = (modName == null) ? libName : modName;
zip = SysZip.openFromFile(basePath);
zip.read();
for(entry in zip.entries) {
if (entry.fileName.length < 0 || entry.fileName.endsWith("/"))
continue;
if (entry.fileName.length < 0 || entry.fileName.endsWith("/")) continue;
lowerCaseAssets[entry.fileName.toLowerCase()] = assets[entry.fileName.toLowerCase()] = assets[entry.fileName] = entry;
nameMap.set(entry.fileName.toLowerCase(), entry.fileName);
var name:String = entry.fileName.toLowerCase(); // calling .toLowerCase a million times is never the solution
lowerCaseAssets[name] = assets[name] = assets[entry.fileName] = entry;
nameMap.set(name, entry.fileName);
}
super();
isCompressed = true;
// don't override default value of true if the file exists.
// by default `PRELOAD_VIDEOS` is true so you will never need to add this file, but in the case of it being false this is a backup method.
PRELOAD_VIDEOS = (!PRELOAD_VIDEOS) ? exists("assets/data/PRECACHE_VIDEOS", "TEXT") : PRELOAD_VIDEOS;
// if (PRELOAD_VIDEOS) precacheVideos(); // we do this in `MainState` now to handle for `Flags.VIDEO_EXT` :)
}
public function precacheVideos() {
_videoExtensions = [Flags.VIDEO_EXT];
videoCacheRemap = [];
for (entry in zip.entries) {
var name = entry.fileName.toLowerCase();
if (_videoExtensions.contains(Path.extension(name))) getPath(prefix+name);
}
var count:Int = 0;
for (_ in videoCacheRemap.keys()) count++;
if (count <= 0) return;
trace('Precached $count video${(count == 1) ? "" : "s"}');
}
// Now we have supports for videos in ZIP!!
public var _videoExtensions:Array<String> = [Flags.VIDEO_EXT];
public var videoCacheRemap:Map<String, String> = [];
public function getVideoRemap(originalPath:String):String {
if (!_videoExtensions.contains(Path.extension(_parsedAsset))) return originalPath;
if (videoCacheRemap.exists(originalPath)) return videoCacheRemap.get(originalPath);
// We adding the length of the string to counteract folder in folder naming duplicates.
var newPath = './.temp/${_parsedAsset.length}-zipvideo-${_parsedAsset.split("/").pop()}';
File.saveBytes(newPath, unzip(assets[_parsedAsset]));
videoCacheRemap.set(originalPath, newPath);
return newPath;
}
function toString():String {
return '(ZipFolderLibrary: $libName/$modName)';
return '(ZipFolderLibrary: $libName/$modName | ${zip.entries.length} entries | Detected Video Extensions: ${_videoExtensions.join(", ")})';
}
public var _parsedAsset:String;
public override function getAudioBuffer(id:String):AudioBuffer {
__parseAsset(id);
return AudioBuffer.fromBytes(unzip(assets[_parsedAsset]));
@@ -71,15 +112,12 @@ class ZipFolderLibrary extends AssetLibrary implements IModsAssetLibrary {
return getAssetPath();
}
public inline function unzip(f:SysZipEntry)
return f == null ? null : zip.unzipEntry(f);
public inline function unzip(f:SysZipEntry) return (f == null) ? null : zip.unzipEntry(f);
public function __parseAsset(asset:String):Bool {
if (!asset.startsWith(prefix)) return false;
_parsedAsset = asset.substr(prefix.length);
if(ModsFolder.useLibFile) {
if (ModsFolder.useLibFile) {
var file = new haxe.io.Path(_parsedAsset);
if(file.file.startsWith("LIB_")) {
var library = file.file.substr(4);
@@ -90,8 +128,7 @@ class ZipFolderLibrary extends AssetLibrary implements IModsAssetLibrary {
}
_parsedAsset = _parsedAsset.toLowerCase();
if(nameMap.exists(_parsedAsset))
_parsedAsset = nameMap.get(_parsedAsset);
if (nameMap.exists(_parsedAsset)) _parsedAsset = nameMap.get(_parsedAsset);
return true;
}
@@ -106,9 +143,8 @@ class ZipFolderLibrary extends AssetLibrary implements IModsAssetLibrary {
return assets[_parsedAsset] != null;
}
private function getAssetPath() {
trace('[ZIP]$basePath/$_parsedAsset');
return '[ZIP]$basePath/$_parsedAsset';
private inline function getAssetPath() {
return getVideoRemap('$basePath/$_parsedAsset');
}
// TODO: rewrite this to 1 function, like ModsFolderLibrary
@@ -157,18 +193,6 @@ class ZipFolderLibrary extends AssetLibrary implements IModsAssetLibrary {
return content;
}
public override function list(type:String):Array<String> {
return[for(k=>e in nameMap) '$prefix$e'];
}
// Backwards compat
@:noCompletion public var zipPath(get, set):String;
@:noCompletion private inline function get_zipPath():String {
return basePath;
}
@:noCompletion private inline function set_zipPath(value:String):String {
return basePath = value;
}
public override function list(type:String):Array<String> { return [for(k=>e in nameMap) '$prefix$e']; }
}
#end
+7 -3
View File
@@ -278,9 +278,13 @@ class Chart {
var filteredChart = filterChartForSaving(chart, saveSettings.saveMetaInChart, saveSettings.saveLocalEvents, saveSettings.saveGlobalEvents && saveSettings.seperateGlobalEvents != true);
#if sys
var songPath = saveSettings.songFolder == null ? 'songs/${chart.meta.name}' : saveSettings.songFolder, variantSuffix = variant != null && variant != "" ? '-$variant' : "";
var metaPath = 'meta$variantSuffix.json', prettyPrint = saveSettings.prettyPrint == true ? Flags.JSON_PRETTY_PRINT : null, temp:String;
if ((temp = Paths.assetsTree.getPath('assets/$songPath/$metaPath')) != null) {
var songPath = saveSettings.songFolder == null ? 'songs/${chart.meta.name}' : saveSettings.songFolder, variantSuffix = variant != null && variant != "" ? '-$variant' : "", difficultySuffix = difficulty != null && difficulty != "" ? '-$difficulty' : "";
var metaPath = 'meta$variantSuffix.json', altMetaPath = 'meta${variantSuffix}${difficultySuffix}.json', prettyPrint = saveSettings.prettyPrint == true ? Flags.JSON_PRETTY_PRINT : null, temp:String;
if ((temp = Paths.assetsTree.getPath('assets/$songPath/$altMetaPath')) != null) { //check for difficulty specific
songPath = temp.substr(0, temp.length - altMetaPath.length - 1);
metaPath = temp;
}
else if ((temp = Paths.assetsTree.getPath('assets/$songPath/$metaPath')) != null) {
songPath = temp.substr(0, temp.length - metaPath.length - 1);
metaPath = temp;
}
+1 -1
View File
@@ -75,7 +75,7 @@ class EventsData {
defValue: "In"
},
{name: "Mode", type: TDropDown(['direct', 'stage']), defValue: "direct"},
{name: "Multiplicative?", type: TBool, defValue: true}
{name: "Multiplicative?", type: TBool, defValue: false}
],
"Camera Modulo Change" => [
{name: "Modulo Interval", type: TInt(1, 9999999, 1), defValue: 4},
@@ -106,12 +106,15 @@ class GlobalScript {
public static function onModSwitch(newMod:String) {
destroy();
scripts = new ScriptPack("GlobalScript");
for (i in funkin.backend.assets.ModsFolder.getLoadedMods()) {
var path = Paths.script('data/global/LIB_$i');
for (lib in funkin.backend.assets.ModsFolder.getLoadedModsLibs()) {
var modName = lib.modName;
var path = Paths.script('data/global/LIB_$modName');
var script = Script.create(path);
if (script is DummyScript)
continue;
script.remappedNames.set(script.fileName, '$i:${script.fileName}');
if (script is DummyScript) continue;
script.remappedNames.set(script.fileName, '$modName:${script.fileName}');
// so you can get the current mod's library in GloablScript :)
// you should not make this a static variable then all scripts will try to reference the 1 static variable, which will be overwritten :yoikes:
script.set("MOD_LIBRARY", lib);
scripts.add(script);
script.load();
}
@@ -4,7 +4,6 @@ import flixel.util.FlxDestroyUtil.IFlxDestroyable;
@:allow(funkin.backend.scripting.ScriptPack)
@:autoBuild(funkin.backend.system.macros.EventMacro.build())
@:noCustomClass
class CancellableEvent implements IFlxDestroyable {
@:dox(hide) public var cancelled:Bool = false;
@:dox(hide) private var __continueCalls:Bool = true;
@@ -54,4 +53,4 @@ class CancellableEvent implements IFlxDestroyable {
public function destroy() {
data = null;
}
}
}
@@ -0,0 +1,96 @@
package funkin.backend.scripting.events.gameplay;
import flixel.math.FlxPoint;
import flixel.tweens.FlxTween;
final class RatingsShowEvent extends CancellableEvent
{
/**
* Rating sprite (may be null)
*/
public var ratingSprite:Null<FlxSprite>;
/**
* Number sprite (may be null)
*/
public var numberSprite:Null<FlxSprite>;
/**
* Combo sprite (may be null)
*/
public var comboSprite:Null<FlxSprite>;
/**
* Scale of combo numbers. (may be null)
*/
public var numScale:Null<Float> = 0.5;
/**
* Whenever antialiasing should be enabled on combo numbers. (may be null)
*/
public var numAntialiasing:Null<Bool> = true;
/**
* Scale of the rating sprites. (may be null)
*/
public var ratingScale:Null<Float> = 0.7;
/**
* Whenever antialiasing should be enabled on ratings. (may be null)
*/
public var ratingAntialiasing:Null<Bool> = true;
/**
* Prefix of the rating sprite path. Defaults to "game/score/"
*/
public var ratingPrefix:String;
/**
* Suffix of the rating sprite path.
*/
public var ratingSuffix:String;
/**
* The sprite's acceleration.
*/
public var acceleration:Float;
/**
* A FlxPoint which x or y properties preposition the sprites current velocity.
*/
public var velocity:FlxPoint;
/**
* The duration of the sprite's alpha tween.
*/
public var tweenDuration:Float;
/**
* The start delay of the sprite's alpha tween.
*/
public var startDelay:Float;
/**
* Whenever the Rating sprites should be shown or not.
*/
public var displayRating:Bool;
/**
* Whenever the Rating sprites should be shown or not.
*/
public var displayNumbers:Bool;
/**
* Whenever the Combo sprite should be shown or not (like old Week 7 patches).
*/
public var displayCombo:Bool;
/**
* Whether the sprite should be tweened or not.
*/
public var playTween:Bool;
/**
* The amount of spacing for the combo numbers. (may be null)
*/
public var numSpacing:Null<Float>;
/**
* The position of the sprite.
*/
public var position:FlxPoint;
/**
* Whether to reset the sprite or not.
*/
public var resetSprite:Bool;
/**
* The rating name of the rating sprite. (may be null)
*/
public var rating:Null<String>;
/**
* The FlxTween instance. (null before "onPostRatingsShown")
*/
public var tween:Null<FlxTween>;
}
@@ -34,11 +34,11 @@ final class NoteHitEvent extends CancellableEvent {
/**
* Whenever the Rating sprites should be shown or not.
*/
public var displayRating:Bool;
public var displayRating:Null<Bool>;
/**
* Whenever the Combo sprite should be shown or not (like old Week 7 patches).
*/
public var displayCombo:Bool;
public var displayCombo:Null<Bool>;
/**
* Note that has been pressed
*/
@@ -66,11 +66,11 @@ final class NoteHitEvent extends CancellableEvent {
/**
* Prefix of the rating sprite path. Defaults to "game/score/"
*/
public var ratingPrefix:String;
public var ratingPrefix:Null<String>;
/**
* Suffix of the rating sprite path.
*/
public var ratingSuffix:String;
public var ratingSuffix:Null<String>;
/**
* Direction of the press (0 = Left, 1 = Down, 2 = Up, 3 = Right)
*/
@@ -98,19 +98,19 @@ final class NoteHitEvent extends CancellableEvent {
/**
* Scale of combo numbers.
*/
public var numScale:Float = 0.5;
public var numScale:Null<Float>;
/**
* Whenever antialiasing should be enabled on combo number.
*/
public var numAntialiasing:Bool = true;
public var numAntialiasing:Null<Bool>;
/**
* Scale of ratings.
*/
public var ratingScale:Float = 0.7;
public var ratingScale:Null<Float>;
/**
* Whenever antialiasing should be enabled on ratings.
*/
public var ratingAntialiasing:Bool = true;
public var ratingAntialiasing:Null<Bool>;
/**
* Whenever the animation should be forced to play (if it's null it will be forced based on the sprite's data xml, if it has one).
*/
@@ -0,0 +1,766 @@
package funkin.backend.shaders;
import haxe.Timer;
import openfl.filters.BitmapFilter;
import openfl.filters.BitmapFilterShader;
import openfl.display.BitmapData;
import openfl.display.DisplayObjectRenderer;
import openfl.display.BlendMode;
import openfl.display.Shader;
import openfl.geom.Point;
import openfl.geom.Rectangle;
/**
This BloomEffect was modified by heihua based on openfl.filters.BlurFilter.
The BloomEffect class applies a bloom/glow visual effect to display objects.
A bloom effect extracts bright areas from an image, blurs them, and combines
them back to create a glowing halo around bright objects. This effect is
commonly used to simulate intense light, emissive materials, or to add a
dreamy, atmospheric quality to scenes.
The effect consists of three stages:
1. Extraction - Bright pixels above a threshold are extracted
2. Blurring - The extracted bright areas are blurred horizontally and vertically
3. Combination - The blurred result is blended back with the original image
**/
@:noCustomClass
class BloomEffect extends BitmapFilter
{
@:noCompletion private static var __blurShader:BlurShader;
@:noCompletion private static var __combineShader:CombineShader;
@:noCompletion private static var __extractShader:ExtractShader;
@:noCompletion private static var __extractLowShader:ExtractLowShader;
/**
Values that are a power of 2 (such as 2, 4, 8, 16 and 32) are optimized to render
more quickly than other values.
**/
public var blurX(get, set):Float;
/**
Values that are a power of 2 (such as 2, 4, 8, 16 and 32) are optimized to render
more quickly than other values.
**/
public var blurY(get, set):Float;
/**
The downscaling factor for bloom rendering. Higher values significantly reduce
GPU performance cost, but setting values too high may cause noticeable flickering.
Recommended range is 8-24.
**/
public var quality(get, set):Float;
/**
The intensity of the bloom effect. Higher values produce more pronounced bloom.
**/
public var strength(get, set):Float;
/**
The brightness threshold for bloom extraction. Pixels brighter than this value
will contribute to the bloom effect. Value range is 0.0 to 1.0.
**/
public var threshold(get, set):Float;
/**
The smoothness of the threshold transition in blur shader.
Higher values create a smoother transition for brightness correction.
Value range is 0.0 to 1.0. Default is 0.1.
**/
public var smoothness(get, set):Float;
/**
Enables extended rendering area to avoid edge artifacts. Enabling this option
will increase performance cost. Generally not required when rendering to camera.
**/
public var extension(get, set):Bool;
/**
Low-quality pixel sampling mode. Disabling it significantly reduces screen flickering,
with minimal performance impact on desktop platforms but a higher
performance cost on non-desktop platforms.
**/
public var useLowQualityExtract(get, set):Bool;
/**
The weights for calculating brightness (RGB to grayscale).
Order: [Red, Green, Blue]. Default is [0.2126, 0.7152, 0.0722].
**/
public var weights(get, set):Array<Float>;
/**
The blend mode used when combining the bloom with the original image.
BlendMode currently supports: (BlendMode.ADD, BlendMode.ALPHA, BlendMode.HARDLIGHT,
BlendMode.LIGHTEN, BlendMode.MULTIPLY, BlendMode.OVERLAY, BlendMode.SCREEN,
BlendMode.COLORDODGE, BlendMode.SOFTLIGHT).
Default is BlendMode.ADD.
**/
public var blendMode(get, set):BlendMode;
@:noCompletion private var __blurX:Float;
@:noCompletion private var __blurY:Float;
@:noCompletion private var __horizontalPasses:Int;
@:noCompletion private var __quality:Float;
@:noCompletion private var __verticalPasses:Int;
@:noCompletion private var __strength:Float;
@:noCompletion private var __threshold:Float;
@:noCompletion private var __smoothness:Float;
@:noCompletion private var __extension:Bool;
@:noCompletion private var __useLowQualityExtract:Bool;
@:noCompletion private var __weights:Array<Float>;
@:noCompletion private var __blendMode:BlendMode;
#if openfljs
@:noCompletion private static function __init__()
{
untyped Object.defineProperties(BloomEffect.prototype, {
"blurX": {
get: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function () { return this.get_blurX (); }"),
set: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function (v) { return this.set_blurX (v); }")
},
"blurY": {
get: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function () { return this.get_blurY (); }"),
set: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function (v) { return this.set_blurY (v); }")
},
"quality": {
get: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function () { return this.get_quality (); }"),
set: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function (v) { return this.set_quality (v); }")
},
"strength": {
get: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function () { return this.get_strength (); }"),
set: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function (v) { return this.set_strength (v); }")
},
"threshold": {
get: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function () { return this.get_threshold (); }"),
set: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function (v) { return this.set_threshold (v); }")
},
"useLowQualityExtract": {
get: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function () { return this.get_useLowQualityExtract (); }"),
set: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function (v) { return this.set_useLowQualityExtract (v); }")
},
"weights": {
get: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function () { return this.get_weights (); }"),
set: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function (v) { return this.set_weights (v); }")
},
"blendMode": {
get: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function () { return this.get_blendMode (); }"),
set: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function (v) { return this.set_blendMode (v); }")
},
});
}
#end
/**
Initializes the bloom filter with the specified parameters.
@param blurX The amount to blur horizontally.
@param blurY The amount to blur vertically.
@param quality The downscaling factor for bloom rendering (higher values reduce
GPU cost but may cause flickering if too high).
@param strength The intensity of the bloom effect.
@param threshold The brightness threshold for bloom extraction (0.0 to 1.0).
@param smoothness The smoothness of threshold transition in blur (0.0 to 1.0).
@param useLowQualityExtract Enables performance-optimized extraction with
potentially more flickering.
**/
public function new(blurX:Float = 50, blurY:Float = 50, quality:Float = 8, strength:Float = 0.6, threshold:Float = 0.6, smoothness:Float = 0.1, useLowQualityExtract:Bool = true)
{
super();
if (__blurShader == null) __blurShader = new BlurShader();
if (__combineShader == null) __combineShader = new CombineShader();
if (__extractShader == null) __extractShader = new ExtractShader();
if (__extractLowShader == null) __extractLowShader = new ExtractLowShader();
this.blurX = blurX;
this.blurY = blurY;
this.quality = quality;
this.strength = strength;
this.threshold = threshold;
this.smoothness = smoothness;
this.extension = false;
this.useLowQualityExtract = useLowQualityExtract;
this.weights = [0.2126, 0.7152, 0.0722];
this.blendMode = BlendMode.ADD;
__needSecondBitmapData = true;
__preserveObject = true;
__renderDirty = true;
}
public override function clone():BitmapFilter
{
var cloned = new BloomEffect(__blurX, __blurY, __quality, __strength, __threshold, __smoothness, __useLowQualityExtract);
cloned.weights = __weights != null ? __weights.copy() : [0.2126, 0.7152, 0.0722];
cloned.blendMode = __blendMode;
return cloned;
}
@:noCompletion private override function __applyFilter(bitmapData:BitmapData, sourceBitmapData:BitmapData, sourceRect:Rectangle, destPoint:Point):BitmapData
{
trace("Due to technical limitations, I'm unable to implement the bitmapData rendering method. If you know how to implement it, you're welcome to contribute this feature.");
return sourceBitmapData;
}
@:noCompletion private override function __initShader(renderer:DisplayObjectRenderer, pass:Int, sourceBitmapData:BitmapData):Shader
{
final numBlurPasses = __horizontalPasses + __verticalPasses;
switch pass
{
case 0:
if (__useLowQualityExtract)
{
__extractLowShader.uThreshold.value[0] = __threshold;
__extractLowShader.uSmoothness.value[0] = __smoothness;
__extractLowShader.uQuality.value[0] = __quality;
__extractLowShader.uWeights.value = __weights;
return __extractLowShader;
}
else
{
__extractShader.uThreshold.value[0] = __threshold;
__extractShader.uSmoothness.value[0] = __smoothness;
__extractShader.uQuality.value[0] = __quality;
__extractShader.uWeights.value = __weights;
return __extractShader;
}
case _ if (pass <= numBlurPasses):
final blurPass = pass - 1;
final isHorizontal = blurPass < __horizontalPasses;
final scalePass = isHorizontal ? blurPass : blurPass - __horizontalPasses;
final scale = Math.pow(0.5, scalePass >> 1);
final blurRadius = isHorizontal ? blurX * scale : blurY * scale;
if (isHorizontal)
{
__blurShader.uRadius.value[0] = blurRadius / __quality;
__blurShader.uRadius.value[1] = 0.0;
}
else
{
__blurShader.uRadius.value[0] = 0.0;
__blurShader.uRadius.value[1] = blurRadius / __quality;
}
__blurShader.uQuality.value[0] = __quality;
__blurShader.uStrength.value[0] = Math.pow(__strength, 1.0 / numBlurPasses);
return __blurShader;
default:
__combineShader.sourceBitmap.input = sourceBitmapData;
__combineShader.uThreshold.value[0] = __threshold;
__combineShader.uQuality.value[0] = __quality;
__combineShader.uBlendMode.value[0] = cast __blendMode;
return __combineShader;
}
}
// Get & Set Methods
@:noCompletion private function get_blurX():Float
{
return __blurX;
}
@:noCompletion private function set_blurX(value:Float):Float
{
if (value != __blurX)
{
__blurX = value;
__renderDirty = true;
if (!__extension)
{
// Setting it to 1 prevents bloom flickering at the screen edges
__leftExtension = 1;
__rightExtension = 1;
}
else
{
__leftExtension = (value > 0 ? Math.ceil(value) : 0);
__rightExtension = __leftExtension;
}
__horizontalPasses = (value <= 0) ? 0 : Math.ceil(value * 0.0625 / quality) + 1;
__numShaderPasses = __horizontalPasses + __verticalPasses + 2;
}
return value;
}
@:noCompletion private function get_blurY():Float
{
return __blurY;
}
@:noCompletion private function set_blurY(value:Float):Float
{
if (value != __blurY)
{
__blurY = value;
__renderDirty = true;
if (!__extension)
{
// Setting it to 1 prevents bloom flickering at the screen edges
__topExtension = 1;
__bottomExtension = 1;
}
else
{
__topExtension = (value > 0 ? Math.ceil(value) : 0);
__bottomExtension = __topExtension;
}
__verticalPasses = (value <= 0) ? 0 : Math.ceil(value * 0.0625 / quality) + 1;
__numShaderPasses = __horizontalPasses + __verticalPasses + 2;
}
return value;
}
@:noCompletion private function get_quality():Float
{
return __quality;
}
@:noCompletion private function set_quality(value:Float):Float
{
__horizontalPasses = (__blurX <= 0) ? 0 : Math.round(__blurX * 0.125 / value) + 1;
__verticalPasses = (__blurY <= 0) ? 0 : Math.round(__blurY * 0.125 / value) + 1;
__numShaderPasses = __horizontalPasses + __verticalPasses + 2;
if (value != __quality)
__renderDirty = true;
return __quality = value;
}
@:noCompletion private function get_strength():Float
{
return __strength;
}
@:noCompletion private function set_strength(value:Float):Float
{
if (value != __strength)
{
__strength = value;
__renderDirty = true;
}
return value;
}
@:noCompletion private function get_threshold():Float
{
return __threshold;
}
@:noCompletion private function set_threshold(value:Float):Float
{
if (value != __threshold)
{
__threshold = value;
__renderDirty = true;
}
return value;
}
@:noCompletion private function get_smoothness():Float
{
return __smoothness;
}
@:noCompletion private function set_smoothness(value:Float):Float
{
if (value != __smoothness)
{
__smoothness = value;
__renderDirty = true;
}
return value;
}
@:noCompletion private function get_extension():Bool
{
return __extension;
}
@:noCompletion private function set_extension(value:Bool):Bool
{
if (value != __extension)
{
__extension = value;
if (!value)
__leftExtension = __rightExtension = __topExtension = __bottomExtension = 0;
else
{
__leftExtension = __rightExtension = (__blurX > 0 ? Math.ceil(__blurX) : 0);
__topExtension = __bottomExtension = (__blurY > 0 ? Math.ceil(__blurY) : 0);
}
__renderDirty = true;
}
return value;
}
@:noCompletion private function get_useLowQualityExtract():Bool
{
return __useLowQualityExtract;
}
@:noCompletion private function set_useLowQualityExtract(value:Bool):Bool
{
if (value != __useLowQualityExtract)
{
__useLowQualityExtract = value;
__renderDirty = true;
}
return value;
}
@:noCompletion private function get_weights():Array<Float>
{
return __weights;
}
@:noCompletion private function set_weights(value:Array<Float>):Array<Float>
{
if (value != __weights)
{
__weights = value;
__renderDirty = true;
}
return value;
}
@:noCompletion private function get_blendMode():BlendMode
{
return __blendMode;
}
@:noCompletion private function set_blendMode(value:BlendMode):BlendMode
{
if (value != __blendMode)
{
__blendMode = value;
__renderDirty = true;
}
return value;
}
}
private class BlurShader extends BitmapFilterShader
{
@:glFragmentSource("
uniform sampler2D openfl_Texture;
uniform float uStrength;
varying mat2 vBlurCoord0;
varying mat2 vBlurCoord1;
varying vec2 vBlurCoord2;
varying mat2 vBlurCoord3;
varying mat2 vBlurCoord4;
varying float invQuality;
void main(void) {
if ((all(greaterThanEqual(vBlurCoord2, vec2(0.0))) && all(lessThanEqual(vBlurCoord2, vec2(1.0)))) == false) return;
vec4 sum = texture2D(openfl_Texture, clamp(vBlurCoord0[0], 0.0, invQuality)) * 0.028532;
sum += texture2D(openfl_Texture, clamp(vBlurCoord0[1], 0.0, invQuality)) * 0.067234;
sum += texture2D(openfl_Texture, clamp(vBlurCoord1[0], 0.0, invQuality)) * 0.124009;
sum += texture2D(openfl_Texture, clamp(vBlurCoord1[1], 0.0, invQuality)) * 0.179044;
sum += texture2D(openfl_Texture, clamp(vBlurCoord2, 0.0, invQuality)) * 0.202360;
sum += texture2D(openfl_Texture, clamp(vBlurCoord3[0], 0.0, invQuality)) * 0.179044;
sum += texture2D(openfl_Texture, clamp(vBlurCoord3[1], 0.0, invQuality)) * 0.124009;
sum += texture2D(openfl_Texture, clamp(vBlurCoord4[0], 0.0, invQuality)) * 0.067234;
sum += texture2D(openfl_Texture, clamp(vBlurCoord4[1], 0.0, invQuality)) * 0.028532;
gl_FragColor = sum * uStrength;
}
")
@:glVertexSource("
attribute vec4 openfl_Position;
attribute vec2 openfl_TextureCoord;
uniform mat4 openfl_Matrix;
uniform vec2 uRadius;
uniform vec2 uTextureSize;
uniform float uQuality;
varying mat2 vBlurCoord0;
varying mat2 vBlurCoord1;
varying vec2 vBlurCoord2;
varying mat2 vBlurCoord3;
varying mat2 vBlurCoord4;
varying float invQuality;
void main(void) {
vec4 pos = openfl_Position;
invQuality = 1.0 / uQuality;
pos.xy *= invQuality;
gl_Position = openfl_Matrix * pos;
vec2 r = uRadius / uTextureSize;
vec2 coord = openfl_TextureCoord * invQuality;
vBlurCoord0[0] = coord - r;
vBlurCoord0[1] = coord - r * 0.25;
vBlurCoord1[0] = coord - r * 0.5;
vBlurCoord1[1] = coord - r * 0.75;
vBlurCoord2 = coord;
vBlurCoord3[0] = coord + r * 0.25;
vBlurCoord3[1] = coord + r * 0.5;
vBlurCoord4[0] = coord + r * 0.75;
vBlurCoord4[1] = coord + r;
}
")
public function new()
{
super();
uStrength.value = [1.0];
uRadius.value = [0, 0];
uQuality.value = [8];
uTextureSize.value = [1, 1];
}
@:noCompletion private override function __update():Void
{
#if !macro
uTextureSize.value[0] = __texture.input.width;
uTextureSize.value[1] = __texture.input.height;
#end
super.__update();
}
}
private class ExtractLowShader extends BitmapFilterShader
{
@:glFragmentSource("
uniform sampler2D openfl_Texture;
uniform float uThreshold;
uniform float uSmoothness;
uniform vec3 uWeights;
varying vec2 vTexCoord;
void main(void) {
if ((all(greaterThanEqual(vTexCoord, vec2(0.0))) && all(lessThanEqual(vTexCoord, vec2(1.0)))) == false) return;
vec4 texel = texture2D(openfl_Texture, vTexCoord);
float brightness = min(dot(texel.rgb, uWeights), 1.0);
float mask = smoothstep(uThreshold, uThreshold + uSmoothness, brightness);
gl_FragColor = texel * mask;
}
")
@:glVertexSource("
attribute vec4 openfl_Position;
attribute vec2 openfl_TextureCoord;
uniform mat4 openfl_Matrix;
uniform vec2 openfl_TextureSize;
uniform float uQuality;
varying vec2 vTexCoord;
void main(void) {
vec4 pos = openfl_Position;
pos.xy /= uQuality;
gl_Position = openfl_Matrix * pos;
vTexCoord = openfl_TextureCoord;
}
")
public function new()
{
super();
uThreshold.value = [0.6];
uSmoothness.value = [0.1];
uQuality.value = [8];
uWeights.value = [0.2126, 0.7152, 0.0722];
}
}
private class ExtractShader extends BitmapFilterShader
{
@:glFragmentSource("
uniform sampler2D openfl_Texture;
uniform vec2 openfl_TextureSize;
uniform float uThreshold;
uniform float uSmoothness;
uniform float uQuality;
uniform vec3 uWeights;
varying vec2 vTexCoord;
varying vec4 border;
void main(void) {
if ((all(greaterThanEqual(vTexCoord, border.xy)) && all(lessThanEqual(vTexCoord, border.zw))) == false) return;
float quality = floor(uQuality) / 2.0;
vec2 texelSize = 1.0 / openfl_TextureSize;
vec4 accumulated = vec4(0.0);
int sampleCount = 0;
for (float dx = -quality; dx <= quality; dx += 2.0) {
for (float dy = -quality; dy <= quality; dy += 2.0) {
vec2 sampleCoord = vTexCoord + vec2(dx, dy) * texelSize;
vec4 texel = texture2D(openfl_Texture, sampleCoord);
float brightness = min(dot(texel.rgb, uWeights), 1.0);
float mask = smoothstep(uThreshold, uThreshold + uSmoothness, brightness);
accumulated += texel * mask;
sampleCount++;
}
}
gl_FragColor = accumulated / float(sampleCount);
}
")
@:glVertexSource("
attribute vec4 openfl_Position;
attribute vec2 openfl_TextureCoord;
uniform mat4 openfl_Matrix;
uniform vec2 openfl_TextureSize;
uniform float uQuality;
varying vec2 vTexCoord;
varying vec4 border;
void main(void) {
vec4 pos = openfl_Position;
pos.xy /= uQuality;
vec2 size = 1.0 / openfl_TextureSize * uQuality;
border = vec4(size, vec2(1.0) - size);
gl_Position = openfl_Matrix * pos;
vTexCoord = openfl_TextureCoord;
}
")
public function new()
{
super();
uThreshold.value = [0.6];
uSmoothness.value = [0.1];
uQuality.value = [8];
uWeights.value = [0.2126, 0.7152, 0.0722];
}
}
private class CombineShader extends BitmapFilterShader
{
@:glFragmentSource("
uniform sampler2D openfl_Texture;
uniform sampler2D sourceBitmap;
uniform float uThreshold;
uniform int uBlendMode;
varying vec4 textureCoords;
vec4 blendScreen(vec4 src, vec4 bloom) {
return vec4(1.0) - (vec4(1.0) - src) * (vec4(1.0) - bloom);
}
vec4 blendMultiply(vec4 src, vec4 bloom) {
return src * bloom;
}
vec4 blendLighten(vec4 src, vec4 bloom) {
return max(src, bloom);
}
vec4 blendOverlay(vec4 src, vec4 bloom) {
vec4 result = vec4(0.0);
for (int i = 0; i < 4; i++) {
if (src[i] < 0.5) {
result[i] = 2.0 * src[i] * bloom[i];
} else {
result[i] = 1.0 - 2.0 * (1.0 - src[i]) * (1.0 - bloom[i]);
}
}
return result;
}
vec4 blendColorDodge(vec4 src, vec4 bloom) {
vec4 result = vec4(0.0);
for (int i = 0; i < 4; i++) {
if (bloom[i] < 1.0) {
result[i] = min(1.0, src[i] / (1.0 - bloom[i]));
} else {
result[i] = 1.0;
}
}
return result;
}
vec4 blendSoftLight(vec4 src, vec4 bloom) {
vec4 result = vec4(0.0);
for (int i = 0; i < 4; i++) {
if (bloom[i] < 0.5) {
result[i] = src[i] - (1.0 - 2.0 * bloom[i]) * src[i] * (1.0 - src[i]);
} else {
float d = (src[i] <= 0.25) ? ((16.0 * src[i] - 12.0) * src[i] + 4.0) * src[i] : sqrt(src[i]);
result[i] = src[i] + (2.0 * bloom[i] - 1.0) * (d - src[i]);
}
}
return result;
}
vec4 blendAlpha(vec4 src, vec4 bloom) {
return src + bloom * (1.0 - src.a);
}
void main(void) {
vec4 src = texture2D(sourceBitmap, textureCoords.xy);
vec4 bloom = texture2D(openfl_Texture, textureCoords.zw);
vec4 result;
if(uBlendMode == 0)
result = src + bloom;
else if(uBlendMode == 1)
result = blendAlpha(src, bloom);
else if(uBlendMode == 5)
result = blendOverlay(src, bloom);
else if(uBlendMode == 8)
result = blendLighten(src, bloom);
else if(uBlendMode == 9)
result = blendMultiply(src, bloom);
else if(uBlendMode == 11)
result = blendOverlay(src, bloom);
else if(uBlendMode == 12)
result = blendScreen(src, bloom);
else if(uBlendMode == 15)
result = blendColorDodge(src, bloom);
else if(uBlendMode == 17)
result = blendSoftLight(src, bloom);
else
result = src + bloom;
gl_FragColor = result;
}
")
@:glVertexSource("
attribute vec4 openfl_Position;
attribute vec2 openfl_TextureCoord;
uniform mat4 openfl_Matrix;
uniform vec2 openfl_TextureSize;
uniform float uQuality;
varying vec4 textureCoords;
void main(void) {
gl_Position = openfl_Matrix * openfl_Position;
textureCoords = vec4(openfl_TextureCoord, openfl_TextureCoord / uQuality);
}
")
public function new()
{
super();
uQuality.value = [8];
uThreshold.value = [0.6];
uBlendMode.value = [0];
}
}
+9 -2
View File
@@ -19,9 +19,11 @@ class Flags {
// -- Codename's Addon Config --
@:bypass public static var addonFlags:Map<String, Dynamic> = [];
public static var CURRENT_API_VERSION:Int = 2;
// -- Codename's ZipFolderLibrary Config --
public static var ALLOWED_ZIP_EXTENSIONS:Array<String> = ["zip"];
// -- Codename's Mod Config --
public static var MOD_NAME:String = "";
public static var MOD_DESCRIPTION:String = "";
@@ -104,7 +106,7 @@ class Flags {
public static var DEFAULT_BEATS_PER_MEASURE:Int = 4;
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;
public static var SUPPORTED_CHART_RUNTIME_FORMATS:Array<String> = ["Legacy", "Psych Engine"];
public static var SUPPORTED_CHART_FORMATS:Array<String> = ["BaseGame"];
@@ -128,6 +130,9 @@ class Flags {
@:also(funkin.game.PlayState.opponentMode)
public static var DEFAULT_OPPONENT_MODE:Bool = false;
public static var EARLY_HIT_WINDOW_RANGE:Float = 1.0; // was 0.5 for easier early hitting, but now 1 to demotivate mashing and getting away with it.
public static var LATE_HIT_WINDOW_RANGE:Float = 1.0;
public static var SHITS_BREAK_COMBO:Bool = true;
public static var USE_LEGACY_TIMING:Null<Bool> = null;
public static var DEFAULT_NOTE_MS_LIMIT:Float = 1500;
@@ -161,6 +166,8 @@ class Flags {
// Font configuration
public static var DEFAULT_FONT:String = "vcr.ttf";
public static var DEFAULT_FONT_SIZE:Int = 16;
public static var DEFAULT_ALT_ANIM_SUFFIX:String = "-alt";
// to translate these you need to convert them into ids
// Resume -> pause.resume
+4
View File
@@ -21,8 +21,10 @@ import openfl.Lib;
import openfl.display.Sprite;
import openfl.text.TextFormat;
import openfl.utils.AssetLibrary;
#if sys
import sys.FileSystem;
import sys.io.File;
#end
#if android
import android.content.Context;
import android.os.Build;
@@ -59,7 +61,9 @@ class Main extends Sprite
public static function preInit() {
funkin.backend.utils.NativeAPI.registerAsDPICompatible();
#if sys
funkin.backend.system.CommandLineHandler.parseCommandLine(Sys.args());
#end
funkin.backend.system.Main.fixWorkingDirectory();
}
+47 -14
View File
@@ -7,12 +7,14 @@ import flixel.FlxState;
import funkin.backend.assets.AssetsLibraryList;
import funkin.backend.assets.ModsFolder;
import funkin.backend.assets.ModsFolderLibrary;
import funkin.backend.assets.ZipFolderLibrary;
import funkin.backend.chart.EventsData;
import funkin.backend.system.framerate.Framerate;
import funkin.editors.ModConfigWarning;
import funkin.menus.TitleState;
import haxe.io.Path;
@dox(hide)
typedef AddonInfo = {
var name:String;
@@ -57,12 +59,32 @@ class MainState extends FlxState {
var _highPriorityAddons:Array<AddonInfo> = [];
var _noPriorityAddons:Array<AddonInfo> = [];
var quick_modsPath = ModsFolder.modsPath + ModsFolder.currentModFolder;
// handing if the loading mod (before it's properly loaded) is a compressed mod
// we just need to use `Paths.assetsTree.hasCompressedLibrary` to complete valid checks for actual loaded compressed mods
var isZipMod = false;
// If we know it's a compressed mod, then we can check if it's using the `cnemod` folder path.
// All it is really is a folder with the mod's name, then a compressed file called "cnemod.[zip|7z|rar|etc]"
var isCneMod = false;
// We are doing it like this because think about it: it's 1 for loop lol
// We just need to know if any of these values is true, so if only one is true and we are not close to being done in the loop, that's fine.
//
for (ext in Flags.ALLOWED_ZIP_EXTENSIONS) {
if (FileSystem.exists(quick_modsPath+"."+ext)) isZipMod = true;
if (FileSystem.exists(quick_modsPath+"/cnemod."+ext)) isCneMod = true;
if (isZipMod && isCneMod) break;
}
// We get the addons folder from relative space (`./`) and then our mod's addons.
var addonPaths = [
ModsFolder.addonsPath,
(
ModsFolder.currentModFolder != null ?
ModsFolder.modsPath + ModsFolder.currentModFolder + "/addons/" :
null
// So to check the mod's addons folder, we need to decompress it. Which is impossible* in this stage of the loading library process.
// TODO: Write a function when the library is loaded to decompress the contents and then load the libraries :)
( (ModsFolder.currentModFolder != null && !isZipMod) ?
quick_modsPath + "/addons/" : null
)
];
@@ -72,12 +94,8 @@ class MainState extends FlxState {
for (addon in FileSystem.readDirectory(path)) {
if (!FileSystem.isDirectory(path + addon)) {
switch(Path.extension(addon).toLowerCase()) {
case 'zip':
addon = Path.withoutExtension(addon);
default:
continue;
}
if (Flags.ALLOWED_ZIP_EXTENSIONS.contains(Path.extension(addon))) addon = Path.withoutExtension(addon);
else continue;
}
var data:AddonInfo = {
@@ -100,9 +118,14 @@ class MainState extends FlxState {
#if MOD_SUPPORT
for (addon in _lowPriorityAddons)
loadLib(addon.path, ltrim(addon.name, "[LOW]"));
if (ModsFolder.currentModFolder != null)
loadLib(ModsFolder.modsPath + ModsFolder.currentModFolder, ModsFolder.currentModFolder);
if (ModsFolder.currentModFolder != null) {
// isCneMod is a guarentee to be a zip mod because we just checked for it, so this will always load as a CompressedLibrary
if (isCneMod)
loadLib(quick_modsPath + "/cnemod", ModsFolder.currentModFolder);
else
loadLib(quick_modsPath, ModsFolder.currentModFolder);
}
for (addon in _noPriorityAddons)
loadLib(addon.path, addon.name);
@@ -134,9 +157,18 @@ class MainState extends FlxState {
CoolUtil.safeAddAttributes('./.temp/', NativeAPI.FileAttribute.HIDDEN);
#end
#if MOD_SUPPORT
for (lib in ModsFolder.getLoadedModsLibs()) {
if (!(lib is ZipFolderLibrary)) continue;
if (cast(lib, ZipFolderLibrary).PRELOAD_VIDEOS) cast(lib, ZipFolderLibrary).precacheVideos();
}
#end
var startState:Class<FlxState> = Flags.DISABLE_WARNING_SCREEN ? TitleState : funkin.menus.WarningState;
if (Options.devMode && Options.allowConfigWarning) {
#if MOD_SUPPORT
// In this case if the mod we just loaded a compressed modpack, we can't edit or modify files without decompressing it.
if (Options.devMode && Options.allowConfigWarning && !isZipMod) {
var lib:ModsFolderLibrary;
for (e in Paths.assetsTree.libraries) if ((lib = cast AssetsLibraryList.getCleanLibrary(e)) is ModsFolderLibrary
&& lib.modName == ModsFolder.currentModFolder)
@@ -147,6 +179,7 @@ class MainState extends FlxState {
return;
}
}
#end
FlxG.switchState(cast Type.createInstance(startState, []));
}
@@ -13,8 +13,13 @@ class CodenameBuildField extends TextField {
}
public function reload() {
#if COMPILE_EXPERIMENTAL
text = '${Flags.VERSION_MESSAGE} (Experimental Build)';
#else
text = '${Flags.VERSION_MESSAGE}';
#if debug
#end
#if (debug || COMPILE_EXPERIMENTAL)
text += '\n${Flags.COMMIT_MESSAGE}';
#end
}
@@ -69,6 +69,7 @@ class Macros {
final fields:Array<Field> = Context.getBuildFields(), pos:Position = Context.currentPos();
fields.push({name: 'tag', access: [APublic], pos: pos, kind: FVar(macro :funkin.backend.assets.AssetSource)});
fields.push({name: 'isCompressed', access: [APublic], pos: pos, kind: FVar(macro :Bool, macro false)});
return fields;
}
+21 -1
View File
@@ -2,7 +2,9 @@ package funkin.backend.utils;
import flixel.sound.FlxSound;
import lime.media.AudioBuffer;
#if !js
import lime.utils.ArrayBufferView.ArrayBufferIO;
#end
import lime.utils.ArrayBuffer;
#if (lime_cffi && lime_vorbis)
@@ -30,6 +32,7 @@ final class AudioAnalyzer {
* @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.
*/
#if sys
public static function getByte(buffer:ArrayBuffer, position:Int, wordSize:Int):Int {
if (wordSize == 2) return inline ArrayBufferIO.getInt16(buffer, position);
else if (wordSize == 3) {
@@ -40,6 +43,19 @@ final class AudioAnalyzer {
else if (wordSize == 4) return inline ArrayBufferIO.getInt32(buffer, position);
else return inline ArrayBufferIO.getUint8(buffer, position) - 128;
}
#elseif js
public static function getByte(buffer:lime.utils.UInt8Array, position:Int, wordSize:Int):Int {
var view = new lime.utils.DataView(buffer.buffer);
if (wordSize == 2) return inline view.getInt16(position, true);
else if (wordSize == 3) {
var b = inline view.getUint16(position, true) | (view.getUint8(position + 2) << 16);
if (b & 0x800000 != 0) return b - 0x1000000;
else return b;
}
else if (wordSize == 4) return inline view.getInt32(position, true);
else return inline view.getUint8(position) - 128;
}
#end
/**
* Gets levels from the frequencies with specified sample rate.
@@ -450,12 +466,16 @@ final class AudioAnalyzer {
}
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;
var pos = Math.floor(startPos * __toBits), end = Math.min(Math.floor(endPos * __toBits), buffer.data.buffer.byteLength), c = 0;
pos -= pos % __sampleSize;
end -= end % __sampleSize;
while (pos < end) {
#if sys
callback(getByte(buffer.data.buffer, pos, __wordSize), c);
#elseif js
callback(getByte(buffer.data, pos, __wordSize), c);
#end
if (++c > buffer.channels) c = 0;
pos += __wordSize;
}
+25 -11
View File
@@ -1,5 +1,6 @@
package funkin.backend.utils;
import lime.utils.AssetType;
#if cpp
import cpp.Float64;
#end
@@ -440,11 +441,9 @@ final class CoolUtil
* @param fadeIn
*/
@:noUsing public static function playMenuSong(fadeIn:Bool = false) {
if (FlxG.sound.music == null || !FlxG.sound.music.playing)
{
if (FlxG.sound.music == null || !FlxG.sound.music.playing) {
playMusic(Paths.music(Flags.DEFAULT_MENU_MUSIC), true, fadeIn ? 0 : 1, true, 102);
if (fadeIn)
FlxG.sound.music.fadeIn(4, 0, 0.7);
if (fadeIn) FlxG.sound.music.fadeIn(4, 0, 1);
}
}
@@ -880,7 +879,7 @@ final class CoolUtil
*/
public static inline function browsePath(path:String) {
var formattedPath:String = Path.normalize(path);
#if windows
formattedPath = formattedPath.replace("/", "\\");
Sys.command("explorer", [formattedPath]);
@@ -1027,6 +1026,20 @@ final class CoolUtil
return result;
}
/**
* Returns if the mouse is overlapping the sprite on a given camera, taking the camera's position, scroll and zoom into account.
*
* @param sprite Any `FlxObject`
* @param camera The camera you want to check overlap on. Uses the sprite's camera by default.
* @return Bool
*/
public static function mouseOverlaps(sprite:FlxObject, ?camera:FlxCamera) {
var camToCheck:FlxCamera = camera ?? sprite.camera;
var posthing:FlxPoint = FlxG.mouse.getWorldPosition(camToCheck);
return posthing != null && FlxMath.inBounds(posthing.x, sprite.x, sprite.x + sprite.width) && FlxMath.inBounds(posthing.y, sprite.y, sprite.y + sprite.height);
}
/**
* Sorts an array alphabetically.
* @param array Array to sort
@@ -1077,7 +1090,7 @@ final class CoolUtil
r.add(str);
return r.toString();
}
public static inline function bound(Value:Float, Min:Float, Max:Float):Float {
#if cpp
var _hx_tmp1:Float = Value;
@@ -1224,13 +1237,13 @@ final class CoolUtil
/**
* ! REQUIRES FULL PATH!!!
* @param path
* @return Bool
* @param path
* @return Bool
*/
public static function imageHasFrameData(path:String):String {
if (FileSystem.exists(Path.withExtension(path, "xml"))) return "xml";
if (FileSystem.exists(Path.withExtension(path, "txt"))) return "txt";
if (FileSystem.exists(Path.withExtension(path, "json"))) return "json";
if (Paths.assetsTree.existsSpecific(Path.withExtension(path, "xml"), AssetType.TEXT)) return "xml";
if (Paths.assetsTree.existsSpecific(Path.withExtension(path, "txt"), AssetType.TEXT)) return "txt";
if (Paths.assetsTree.existsSpecific(Path.withExtension(path, "json"), AssetType.TEXT)) return "json";
return null;
}
@@ -1442,6 +1455,7 @@ final class CoolUtil
return toProperty.setValue(fromProperty.getValue());
}
}
class PropertyInfo {
+200 -180
View File
@@ -3,200 +3,220 @@ package funkin.backend.utils;
import haxe.macro.Expr;
final class MathUtil {
/**
* 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;
}
public static inline var EULER:Float = 2.718281828459;
/**
* 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.
* @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 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.
* @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 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;
}
/**
* 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;
}
/**
* 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;
}
/**
* 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 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 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 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 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 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 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 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 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 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;
}
/**
* 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;
}
/**
* 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");
}
/**
* 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;
}
/**
* 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");
}
/**
* @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);
}
#if macro
@:dox(hide) private static function genericMinMaxSmart(_args:Array<Expr>, funcPath:String):Expr {
var args = _args.copy();
if (args.length == 0) return macro 0;
/**
* @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);
}
var func = funcPath.split(".");
/**
* @param v Float
* @return Float
**/
public static function fract(v:Float):Float {
return v - Math.floor(v);
}
function nested(lst:Array<Expr>):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))});
}
}
/**
* 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");
}
var expr = nested(args);
/**
* 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");
}
//var printer = new haxe.macro.Printer();
//trace(printer.printExpr(expr));
#if macro
@:dox(hide) private static function genericMinMaxSmart(_args:Array<Expr>, funcPath:String):Expr {
var args = _args.copy();
if (args.length == 0) return macro 0;
return macro $expr;
}
#end
}
var func = funcPath.split(".");
function nested(lst:Array<Expr>):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
}
+97 -111
View File
@@ -2,157 +2,143 @@ package funkin.backend.utils;
#if sys
import haxe.io.Input;
import haxe.zip.Entry;
import haxe.zip.InflateImpl;
import haxe.zip.Reader;
import sys.io.File;
import sys.io.FileInput;
import haxe.io.Bytes;
/**
* Class that extends Reader allowing you to load ZIP entries without blowing your RAM up!!
* Half of the code is taken from haxe libraries btw
* ~~Half of the code is taken from haxe libraries btw~~ Reworked by ItsLJcool to actually work for zip files.
*/
class SysZip extends Reader {
var input:Input;
class SysZip {
var fileInput:FileInput;
var filePath:String;
public var entries:List<SysZipEntry>;
public var entries:List<SysZipEntry> = new List();
/**
* Opens a zip from a specified path.
* @param path Path to the zip file.
* @param path Path to the zip file. (With the extension)
*/
public static function openFromFile(path:String) {
return new SysZip(File.read(path, true));
}
public static function openFromFile(path:String) { return new SysZip(path); } // keeping for compatibility.
/**
* Creates a new SysZip from a specified file input.
* @param input File input.
* Creates a new SysZip from a specified path.
* @param path Path to the zip file. (With the extension)
*/
public function new(input:FileInput) {
super(input);
fileInput = input;
}
public function new(path:String) {
this.filePath = path;
fileInput = File.read(path, true);
/**
* Reads all the data present in a specified entry.
* NOTE: If the entry is compressed, the data won't be decompressed. For decompression, use `unzipEntry`.
* @param e Entry
*/
public function readEntryData(e:SysZipEntry) {
var bytes:haxe.io.Bytes = null;
var buf = null;
var tmp = null;
fileInput.seek(e.seekPos, SeekBegin);
if (e.crc32 == null) {
if (e.compressed) {
#if neko
// enter progressive mode : we use a different input which has
// a temporary buffer, this is necessary since we have to uncompress
// progressively, and after that we might have pending read data
// that needs to be processed
var bufSize = 65536;
if (buf == null) {
buf = new haxe.io.BufferInput(i, haxe.io.Bytes.alloc(bufSize));
tmp = haxe.io.Bytes.alloc(bufSize);
i = buf;
}
var out = new haxe.io.BytesBuffer();
var z = new neko.zip.Uncompress(-15);
z.setFlushMode(neko.zip.Flush.SYNC);
while (true) {
if (buf.available == 0)
buf.refill();
var p = bufSize - buf.available;
if (p != buf.pos) {
// because of lack of "srcLen" in zip api, we need to always be stuck to the buffer end
buf.buf.blit(p, buf.buf, buf.pos, buf.available);
buf.pos = p;
}
var r = z.execute(buf.buf, buf.pos, tmp, 0);
out.addBytes(tmp, 0, r.write);
buf.pos += r.read;
buf.available -= r.read;
if (r.done)
break;
}
bytes = out.getBytes();
#else
var bufSize = 65536;
if (tmp == null)
tmp = haxe.io.Bytes.alloc(bufSize);
var out = new haxe.io.BytesBuffer();
var z = new InflateImpl(i, false, false);
while (true) {
var n = z.readBytes(tmp, 0, bufSize);
out.addBytes(tmp, 0, n);
if (n < bufSize)
break;
}
bytes = out.getBytes();
#end
} else
bytes = i.read(e.dataSize);
e.crc32 = i.readInt32();
if (e.crc32 == 0x08074b50)
e.crc32 = i.readInt32();
e.dataSize = i.readInt32();
e.fileSize = i.readInt32();
// set data to uncompressed
e.dataSize = e.fileSize;
e.compressed = false;
} else
bytes = i.read(e.dataSize);
return bytes;
updateEntries(); // automatic but if you feel like you don't want it to be automatic, you can remove this.
}
/**
* Unzips and returns all of the data present in an entry.
* @param f Entry to read from.
*/
public function unzipEntry(f:SysZipEntry) {
var data = readEntryData(f);
public function unzipEntry(f:SysZipEntry):Bytes {
if (f.fileSize <= 0) return Bytes.alloc(0);
fileInput.seek(f.seekPos, SeekBegin);
var data = fileInput.read(f.compressedSize);
if (!f.compressed) return data;
if (!f.compressed)
return data;
var c = new haxe.zip.Uncompress(-15);
var s = haxe.io.Bytes.alloc(f.fileSize);
var s = Bytes.alloc(f.fileSize);
var r = c.execute(data, 0, s, 0);
c.close();
if (!r.done || r.read != data.length || r.write != f.fileSize)
throw "Invalid compressed data for " + f.fileName;
data = s;
return data;
if (!r.done || r.read != data.length || r.write != f.fileSize) throw 'Invalid compressed data for ${f.fileName} | ${f.compressedSize} -> ${f.fileSize}';
return s;
}
public override function read():List<Entry> {
if (entries != null)
return entries;
entries = new List();
/**
* Updates the `entries` list with the current contents of the zip file.
* This is done when the zip is read from SysZip the first time, but if you REALLY need to re-update the entries, you can call this again.
*
* Note: Calling this function will hold up the game as it has to read the ENTIRE zip, so if it's large like 1GiB or more, it might take a second or more.
*/
public function updateEntries() {
if (entries.length > 0) {
entries.clear();
entries = new List();
}
// --- locate End of Central Directory (EOCD) ---
var fileSize:Int = sys.FileSystem.stat(this.filePath).size; // probably need a better way to check the size of the file.
var scanSize:Int = (65535 < fileSize) ? 65535 : fileSize;
// It seems this usually ends up being 0 anyways, but for cases where it might not be?? I'd just make sure. but Someone do some digging I don't know if this required.
fileInput.seek(fileSize - scanSize, SeekBegin);
var buf = fileInput.read(scanSize);
var b = new haxe.io.BytesInput(buf);
// I LOVE USING MAGIC NUMBERS AND FORGETTING WHAT THEY DO 🔥🔥🔥🔥🔥🔥
b.position = (buf.length - 22) + 16; // offset to start of central directory
// --- read central directory ---
fileInput.seek(b.readInt32(), SeekBegin);
while (true) {
var e = readEntryHeader();
if (e == null)
break;
if (fileInput.readInt32() != 0x02014b50) break; // central dir file header signature
fileInput.seek(6, SeekCur); // version/flags
var compression_method = fileInput.readUInt16();
fileInput.seek(8, SeekCur); // time/date + CRC32 (4, 4)
var compressed_size = fileInput.readInt32();
var uncompressed_size = fileInput.readInt32();
var nameLen = fileInput.readUInt16();
var extraLen = fileInput.readUInt16();
var commentLen = fileInput.readUInt16();
fileInput.seek(8, SeekCur); // skip disk number/start attrs
var localHeaderOffset = fileInput.readInt32();
var name = fileInput.read(nameLen).toString();
// skip central directory extra/comment
fileInput.seek(extraLen + commentLen, SeekCur);
// --- compute correct seekPos using local header ---
var curPos = fileInput.tell();
// I also forgor what the `+ 26` is for, so uh my b chat
fileInput.seek(localHeaderOffset + 26, SeekBegin);
var localNameLen = fileInput.readUInt16();
var localExtraLen = fileInput.readUInt16();
fileInput.seek(curPos, SeekBegin);
// I completely forgot that we don't really need to log the FOLDER of the content because we only care about where the contents are.
// the folders are labled as 0 bytes anyways so this will save on storing non-required data.
if (name.endsWith("/")) continue;
var zipEntry:SysZipEntry = cast e;
zipEntry.seekPos = fileInput.tell();
var zipEntry:SysZipEntry = {
fileName: name,
fileSize: uncompressed_size,
// I don't remember what the `+ 30` is for, but probably to offset something
seekPos: (localHeaderOffset + 30 + localNameLen + localExtraLen),
compressedSize: compressed_size,
compressed: (compression_method == 8),
};
entries.add(zipEntry);
fileInput.seek(e.dataSize, SeekCur);
}
return entries;
}
/**
* calling `dispose` doesn't actually kill the class, you can still access the entries.
* disposing of SysZip will free the compressed file from being used by the engine.
*/
public function dispose() {
if (input != null)
input.close();
if (fileInput != null) fileInput.close();
}
}
typedef SysZipEntry = {
> Entry,
var fileName:String;
var fileSize:Int;
var seekPos:Int;
var compressedSize:Int;
var compressed:Bool;
}
#end
+3 -3
View File
@@ -1,5 +1,8 @@
package funkin.backend.utils;
#if !macro
import funkin.backend.system.Logs;
#end
#if (target.threaded)
import sys.thread.Deque;
import sys.thread.Thread;
@@ -8,9 +11,6 @@ import sys.thread.Mutex;
private typedef Thread = Dynamic;
#end
#if !macro
import funkin.backend.system.Logs;
#end
final class ThreadUtil {
inline static function error(text:String) {
@@ -254,6 +254,7 @@ final class TranslationUtil
// todo make it load the default languages in a second string map
#if MOD_SUPPORT
for(mod in ModsFolder.getLoadedModsLibs(true)) for(file in mod.getFiles("assets/" + mainPath).sortAlphabetically().map((v)->'$mainPath/$v')) {
if(Path.extension(file).toLowerCase() != "xml") continue;
@@ -277,6 +278,7 @@ final class TranslationUtil
parseXml(langNode, prefix);
}
#end
for(pair in translations) {
var node = pair.node;
+17
View File
@@ -20,6 +20,8 @@ class EditorTreeMenu extends funkin.options.TreeMenu {
bg.antialiasing = true;
setBackgroundRotation(-5);
super.createPost();
if (Paths.assetsTree.hasCompressedLibrary) warnCompressLibrary();
}
public inline function setBackgroundRotation(rotation:Float) {
@@ -58,6 +60,21 @@ class EditorTreeMenu extends funkin.options.TreeMenu {
bg.colorTransform.greenMultiplier = FlxMath.lerp(1, color.greenFloat, 0.25);
bg.colorTransform.blueMultiplier = FlxMath.lerp(1, color.blueFloat, 0.25);
}
private function warnCompressLibrary() {
var warningMessage = "It seems you have libraries loaded that are compressed, and can not have files written to them.\n
This is just a friendly reminder that if you're loading a Mod and wish to edit files, you need to uncompress it to be able to use any editors!\n\nCompressed Libraries: ";
var compressedList = Paths.assetsTree.libraries.filter(l -> funkin.backend.assets.AssetsLibraryList.getCleanLibrary(l).isCompressed);
var modNameList = [for (l in compressedList) {
l = funkin.backend.assets.AssetsLibraryList.getCleanLibrary(l);
if (l is funkin.backend.assets.IModsAssetLibrary) cast(l, funkin.backend.assets.IModsAssetLibrary).modName;
}];
warningMessage += modNameList.join(", ");
var zipLibraryWarning = new funkin.editors.ui.UIWarningSubstate("Compressed Library Detected!", warningMessage, [{label: "Ok", color: 0x969533, onClick: (state) -> {} }], false);
openSubState(zipLibraryWarning);
}
}
class EditorTreeMenuScreen extends funkin.options.TreeMenuScreen {
@@ -403,12 +403,12 @@ class CharacterAnimButton extends UIButton {
public function toggleGhost() {
if (valid && parent.ghosts.indexOf(anim) == -1) {
FlxG.sound.play(Paths.sound(Flags.DEFAULT_CHARACTER_GHOSTENABLE_SOUND));
UIState.playEditorSound(Flags.DEFAULT_CHARACTER_GHOSTENABLE_SOUND);
parent.ghosts.push(anim);
ghostIcon.animation.play("alive", true);
ghostIcon.color = 0xFFFFFFFF;
} else {
FlxG.sound.play(Paths.sound(Flags.DEFAULT_CHARACTER_GHOSTDISABLE_SOUND));
UIState.playEditorSound(Flags.DEFAULT_CHARACTER_GHOSTDISABLE_SOUND);
parent.ghosts.remove(anim);
ghostIcon.animation.play("dead", true);
ghostIcon.color = 0xFFADADAD;
@@ -119,7 +119,7 @@ class CharacterAnimsWindow extends UIButtonList<CharacterAnimButton> {
}
public function deleteAnimation(button:CharacterAnimButton, addToUndo:Bool = true) {
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_DELETE_SOUND));
UIState.playEditorSound(Flags.DEFAULT_EDITOR_DELETE_SOUND);
if (buttons.members.length <= 1) return;
if (character.getAnimName() == button.anim)
@:privateAccess CharacterEditor.instance._animation_down(null);
@@ -17,8 +17,10 @@ import funkin.game.Character;
import haxe.xml.Access;
import haxe.xml.Printer;
import funkin.editors.ui.UIImageExplorer.ImageSaveData;
#if sys
import sys.FileSystem;
import sys.io.File;
#end
class CharacterEditor extends UIState {
static var __character:String;
@@ -453,7 +455,7 @@ class CharacterEditor extends UIState {
function _file_save(_) {
#if sys
FlxG.sound.play(Paths.sound('editors/save'));
UIState.playEditorSound(Flags.DEFAULT_EDITOR_SAVE_SOUND);
CoolUtil.safeSaveFile(
'${Paths.getAssetsRoot()}/data/characters/${character.curCharacter}.xml',
buildCharacter()
@@ -465,7 +467,7 @@ class CharacterEditor extends UIState {
}
function _file_saveas(_) {
FlxG.sound.play(Paths.sound('editors/save'));
UIState.playEditorSound(Flags.DEFAULT_EDITOR_SAVE_SOUND);
openSubState(new SaveSubstate(buildCharacter(), {
defaultSaveFile: '${character.curCharacter}.xml'
}));
@@ -499,7 +501,7 @@ class CharacterEditor extends UIState {
}
function _undo(undo:CharacterEditorChange) {
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_UNDO_SOUND));
UIState.playEditorSound(Flags.DEFAULT_EDITOR_UNDO_SOUND);
switch (undo) {
case null: // do nothing
case CCharEditPosition(oldPos, newPos):
@@ -518,6 +520,7 @@ class CharacterEditor extends UIState {
characterPropertiesWindow.editCharacterInfo(oldInfo, false);
case CCharEditSprite(fileID):
var cneisdPath:String = './.temp/__undo__${Type.getClassName(Type.getClass(FlxG.state))}__${fileID}.cneisd';
#if sys
if (FileSystem.exists(cneisdPath)) {
try {
var cneisdData:String = File.getContent(cneisdPath);
@@ -530,6 +533,7 @@ class CharacterEditor extends UIState {
trace('ERROR COMPLETING UNDO: $e');
};
}
#end
case CAnimCreate(animID, animData):
characterAnimsWindow.deleteAnimation(characterAnimsWindow.buttons.members[animID], false);
case CAnimDelete(animID, animData):
@@ -573,7 +577,7 @@ class CharacterEditor extends UIState {
}
function _redo(redo:CharacterEditorChange) {
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_REDO_SOUND));
UIState.playEditorSound(Flags.DEFAULT_EDITOR_REDO_SOUND);
switch (redo) {
case null: // do nothing
case CCharEditPosition(oldPos, newPos):
@@ -592,6 +596,7 @@ class CharacterEditor extends UIState {
characterPropertiesWindow.editCharacterInfo(newInfo, false);
case CCharEditSprite(fileID):
var cneisdPath:String = './.temp/__redo__${Type.getClassName(Type.getClass(FlxG.state))}__${fileID}.cneisd';
#if sys
if (FileSystem.exists(cneisdPath)) {
try {
var cneisdData:String = File.getContent(cneisdPath);
@@ -604,6 +609,7 @@ class CharacterEditor extends UIState {
trace('ERROR COMPLETING UNDO: $e');
};
}
#end
case CAnimCreate(animID, animData):
characterAnimsWindow.addAnimation(animData, animID, false);
playAnimation(animData.name);
+228 -66
View File
@@ -77,10 +77,10 @@ class Charter extends UIState {
public var strumlineLockButton:CharterStrumlineButton;
public var hitsound:FlxSound;
public var hitsoundGlobalVolume:Float = 1.0;
public var metronome:FlxSound;
public var vocals:FlxSound;
public var voicesMuted:Bool = false;
public var quant:Int = 16;
public var quants:Array<Int> = [4, 8, 12, 16, 20, 24, 32, 48, 64, 192]; // different quants
@@ -96,6 +96,7 @@ class Charter extends UIState {
public var rightEventRowText:UIText;
public var leftEventsGroup:CharterEventGroup = new CharterEventGroup();
public var rightEventsGroup:CharterEventGroup = new CharterEventGroup();
public var cameraMovementChanges:Array<CameraChange> = [];
public var charterCamera:FlxCamera;
public var uiCamera:FlxCamera;
@@ -231,6 +232,11 @@ class Charter extends UIState {
label: translate("edit.delete"),
keybind: [DELETE],
onSelect: _edit_delete
},
{
label: translate("edit.deletestacked"),
keybind: [SHIFT,DELETE],
onSelect: _edit_deletestacked
}
]
},
@@ -308,6 +314,11 @@ class Charter extends UIState {
onSelect: _view_showeventBeatSeparator,
icon: Options.charterShowBeats ? 1 : 0
},
{
label: translate("view.showCameraHighlights"),
onSelect: _view_showeventCameraHighlights,
icon: Options.charterShowCameraHighlights ? 1 : 0
},
null,
{
label: translate("view.rainbowWaveforms"),
@@ -453,7 +464,7 @@ class Charter extends UIState {
rightEventsGroup.eventsRowText = rightEventRowText;
// thank you neo for pointing out im stupid -lunar
// this is future lunar i completely forgot what neo pointed out but hes awesome go follow him on twitter
// this is future lunar i completely forgot what neo pointed out but hes awesome go follow him on twitter
add(gridBackdropDummy = new CameraHoverDummy(gridBackdrops, FlxPoint.weak(1, 0)));
selectionBox = new UISliceSprite(0, 0, 2, 2, 'editors/ui/selection');
@@ -515,7 +526,7 @@ class Charter extends UIState {
strumlineLockButton = new CharterStrumlineButton("editors/charter/lock-strumline", translate("lock-unlock"));
strumlineLockButton.onClick = function () {
FlxG.sound.play(Paths.sound(!strumLines.draggable ? Flags.DEFAULT_CHARTER_STRUMUNLOCK_SOUND : Flags.DEFAULT_CHARTER_STRUMLOCK_SOUND));
UIState.playEditorSound(!strumLines.draggable ? Flags.DEFAULT_CHARTER_STRUMUNLOCK_SOUND : Flags.DEFAULT_CHARTER_STRUMLOCK_SOUND);
if (strumLines != null) {
strumLines.draggable = !strumLines.draggable;
strumlineLockButton.textTweenColor.color = strumLines.draggable ? 0xFF5C95CA : 0xFFE16565;
@@ -685,6 +696,7 @@ class Charter extends UIState {
CharterGridSeperatorBase.lastConductorSprY = Math.NEGATIVE_INFINITY;
updateWaveforms();
updateCameraChanges();
}
public function getWavesToGenerate():Array<{name:String, sound:FlxSound}> {
@@ -740,6 +752,38 @@ class Charter extends UIState {
}
}
public function updateCameraChanges() {
if (!Options.charterShowCameraHighlights) return;
cameraMovementChanges = [];
for (grp in [leftEventsGroup, rightEventsGroup]) {
grp.filterEvents();
grp.sortEvents();
for(e in grp.members) {
for(event in e.events) {
if (event.name == "Camera Movement") {
cameraMovementChanges.push({
strumLineID: event.params[0],
step: e.step,
endStep: __endStep
});
}
}
}
}
//need to sort again for both local and global events to be used
cameraMovementChanges.sort(function(e1, e2) {
return FlxSort.byValues(FlxSort.ASCENDING, e1.step, e2.step);
});
//update previous change
if (cameraMovementChanges.length > 0) {
for (i in 1...cameraMovementChanges.length) {
cameraMovementChanges[i-1].endStep = cameraMovementChanges[i].step;
}
}
}
public override function beatHit(curBeat:Int) {
super.beatHit(curBeat);
if (FlxG.sound.music.playing) {
@@ -757,6 +801,7 @@ class Charter extends UIState {
public var mousePos:FlxPoint = new FlxPoint();
public var selectionDragging:Bool = false;
public var isSelecting:Bool = false;
public var isAltCopyDrag:Bool = false;
public function updateSelectionLogic() {
function select(s:ICharterSelectable) {
@@ -802,7 +847,7 @@ class Charter extends UIState {
else
Chart.save(PlayState.SONG, __diff.toLowerCase(), __variant, {saveMetaInChart: true, saveLocalEvents: true, seperateGlobalEvents: true, prettyPrint: Options.editorCharterPrettyPrint});
FlxG.sound.play(Paths.sound('editors/save'));
UIState.playEditorSound(Flags.DEFAULT_EDITOR_SAVE_SOUND);
undos.save();
}
autoSaveNotif.cancelled = false;
@@ -921,17 +966,24 @@ class Charter extends UIState {
if (!(verticalChange == 0 && horizontalChange == 0)) {
notesGroup.sortNotes();
undos.addToUndo(CChangeBundle([
CSelectionDrag(undoDrags),
updateEventsGroups(selection)
]));
var changes:Array<CharterChange> = [];
if (isAltCopyDrag) {
changes.push(CCreateSelection(selection.copy()));
isAltCopyDrag = false;
}
changes.push(CSelectionDrag(undoDrags));
changes.push(updateEventsGroups(selection));
undos.addToUndo(CChangeBundle(changes));
} else if (isAltCopyDrag) {
undos.addToUndo(CCreateSelection(selection.copy()));
isAltCopyDrag = false;
}
gridActionType = NONE;
currentCursor = ARROW;
}
case NONE:
if (FlxG.mouse.justPressed)
if (FlxG.mouse.justPressed)
FlxG.mouse.getWorldPosition(charterCamera, dragStartPos);
else if (FlxG.mouse.justPressedRight) {
closeCurrentContextMenu();
@@ -962,7 +1014,7 @@ class Charter extends UIState {
notesGroup.add(note);
selection = [note];
undos.addToUndo(CCreateSelection([note]));
FlxG.sound.play(Paths.sound(Flags.DEFAULT_CHARTER_NOTEPLACE_SOUND));
UIState.playEditorSound(Flags.DEFAULT_CHARTER_NOTEPLACE_SOUND);
}
isSelecting = false;
}
@@ -979,7 +1031,32 @@ class Charter extends UIState {
}
if ((Math.abs(mousePos.x - dragStartPos.x) > (noteSusDrag ? 1 : 5) || Math.abs(mousePos.y - dragStartPos.y) > (noteSusDrag ? 1 : 5))) {
if (noteHovered) gridActionType = noteHovered ? NOTE_DRAG : INVALID_DRAG;
if (noteHovered) {
if (FlxG.keys.pressed.ALT && selection.length > 0) {
var newSelection:Array<ICharterSelectable> = [];
for (s in selection) {
if (s is CharterNote) {
var n:CharterNote = cast s;
var newNote = new CharterNote();
newNote.updatePos(n.step, n.id, n.susLength, n.type, n.strumLine);
notesGroup.add(newNote);
newSelection.push(newNote);
} else if (s is CharterEvent) {
var e:CharterEvent = cast s;
var newEvent = new CharterEvent(e.step, [for (event in e.events) Reflect.copy(event)], e.global);
newEvent.refreshEventIcons();
(e.global ? rightEventsGroup : leftEventsGroup).add(newEvent);
newSelection.push(newEvent);
}
}
for (s in selection) s.selected = false;
selection = newSelection;
for (s in selection) s.selected = true;
isAltCopyDrag = true;
UIState.playEditorSound(Flags.DEFAULT_EDITOR_COPY_SOUND);
}
gridActionType = NOTE_DRAG;
}
if (noteSusDrag) gridActionType = SUSTAIN_DRAG;
}
}
@@ -1091,7 +1168,7 @@ class Charter extends UIState {
if (selected == null) return selected;
if (selected is CharterNote) {
FlxG.sound.play(Paths.sound(Flags.DEFAULT_CHARTER_NOTEDELETE_SOUND));
UIState.playEditorSound(Flags.DEFAULT_CHARTER_NOTEDELETE_SOUND);
var note:CharterNote = cast selected;
note.strumLineID = strumLines.members.indexOf(note.strumLine);
note.strumLine = null; // For static undos :D
@@ -1571,20 +1648,20 @@ class Charter extends UIState {
else {undos = null; FlxG.switchState(new CharterSelection()); Charter.instance.__clearStatics();}
}
function _file_save_all(_) {saveEverything(); FlxG.sound.play(Paths.sound('editors/save'));}
function _file_save(_) {saveChart(); FlxG.sound.play(Paths.sound('editors/save'));}
function _file_saveas(_) {saveChartAs(); FlxG.sound.play(Paths.sound('editors/save'));}
function _file_events_save(_) {saveEvents(); FlxG.sound.play(Paths.sound('editors/save'));}
function _file_events_saveas(_) {saveEventsAs(); FlxG.sound.play(Paths.sound('editors/save'));}
function _file_save_no_events(_) {saveChart(true, false); FlxG.sound.play(Paths.sound('editors/save'));}
function _file_saveas_no_events(_) {saveChartAs(true, false); FlxG.sound.play(Paths.sound('editors/save'));}
function _file_meta_save(_) {saveMeta(); FlxG.sound.play(Paths.sound('editors/save'));}
function _file_meta_saveas(_) {saveMetaAs(); FlxG.sound.play(Paths.sound('editors/save'));}
function _file_saveas_fnflegacy(_) {saveLegacyChartAs(); FlxG.sound.play(Paths.sound('editors/save'));}
function _file_saveas_psych(_) {savePsychChartAs(); FlxG.sound.play(Paths.sound('editors/save'));}
function _file_save_all(_) {saveEverything(); UIState.playEditorSound(Flags.DEFAULT_EDITOR_SAVE_SOUND);}
function _file_save(_) {saveChart(); UIState.playEditorSound(Flags.DEFAULT_EDITOR_SAVE_SOUND);}
function _file_saveas(_) {saveChartAs(); UIState.playEditorSound(Flags.DEFAULT_EDITOR_SAVE_SOUND);}
function _file_events_save(_) {saveEvents(); UIState.playEditorSound(Flags.DEFAULT_EDITOR_SAVE_SOUND);}
function _file_events_saveas(_) {saveEventsAs(); UIState.playEditorSound(Flags.DEFAULT_EDITOR_SAVE_SOUND);}
function _file_save_no_events(_) {saveChart(true, false); UIState.playEditorSound(Flags.DEFAULT_EDITOR_SAVE_SOUND);}
function _file_saveas_no_events(_) {saveChartAs(true, false); UIState.playEditorSound(Flags.DEFAULT_EDITOR_SAVE_SOUND);}
function _file_meta_save(_) {saveMeta(); UIState.playEditorSound(Flags.DEFAULT_EDITOR_SAVE_SOUND);}
function _file_meta_saveas(_) {saveMetaAs(); UIState.playEditorSound(Flags.DEFAULT_EDITOR_SAVE_SOUND);}
function _file_saveas_fnflegacy(_) {saveLegacyChartAs(); UIState.playEditorSound(Flags.DEFAULT_EDITOR_SAVE_SOUND);}
function _file_saveas_psych(_) {savePsychChartAs(); UIState.playEditorSound(Flags.DEFAULT_EDITOR_SAVE_SOUND);}
function _edit_copy(_, playSFX=true) {
if (playSFX) FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_COPY_SOUND));
if (playSFX) UIState.playEditorSound(Flags.DEFAULT_EDITOR_COPY_SOUND);
if(selection.length == 0) return;
var minStep:Float = selection[0].step;
@@ -1603,7 +1680,7 @@ class Charter extends UIState {
];
}
function _edit_paste(_) {
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_PASTE_SOUND));
UIState.playEditorSound(Flags.DEFAULT_EDITOR_PASTE_SOUND);
if (clipboard.length <= 0) return;
var minStep = curStep;
@@ -1631,7 +1708,7 @@ class Charter extends UIState {
}
function _edit_cut(_) {
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_CUT_SOUND));
UIState.playEditorSound(Flags.DEFAULT_EDITOR_CUT_SOUND);
if (selection == null || selection.length == 0) return;
_edit_copy(_, false);
@@ -1639,7 +1716,7 @@ class Charter extends UIState {
}
function _edit_delete(_) {
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_DELETE_SOUND));
UIState.playEditorSound(Flags.DEFAULT_EDITOR_DELETE_SOUND);
if (selection == null || selection.length == 0) return;
selection.loop((n:CharterNote) -> {
noteDeleteAnims.deleteNotes.push({note: n, time: noteDeleteAnims.deleteTime});
@@ -1647,8 +1724,27 @@ class Charter extends UIState {
selection = deleteSelection(selection, true);
}
function _edit_deletestacked(_) {
UIState.playEditorSound(Flags.DEFAULT_EDITOR_DELETE_SOUND);
if (notesGroup.members.length == 0) return;
var oldNote:CharterNote = null;
var selectionArray:Array<Dynamic> = ((selection.length != 0) ? selection : notesGroup.members.copy());
var toDelete:Selection = new Selection();
for (note in selectionArray) {
if (oldNote != null && oldNote.step == note.step && oldNote.strumLineID == note.strumLineID && oldNote.id == note.id) {
noteDeleteAnims.deleteNotes.push({note: oldNote, time: noteDeleteAnims.deleteTime});
toDelete.push(oldNote);
}
oldNote = note;
}
if (toDelete.length != 0) {
deleteSelection(toDelete);
if (selection.length != 0) for (i in toDelete) selection.remove(i); //crash prevention
}
}
function _undo(undo:CharterChange) {
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_UNDO_SOUND));
UIState.playEditorSound(Flags.DEFAULT_EDITOR_UNDO_SOUND);
switch(undo) {
case null: // do nothing
case CDeleteStrumLine(strumLineID, strumLine):
@@ -1697,7 +1793,11 @@ class Charter extends UIState {
case CEditSpecNotesType(notes, oldTypes, newTypes):
for(i=>note in notes) note.updatePos(note.step, note.id, note.susLength, oldTypes[i]);
case CChangeBundle(changes):
for (change in changes) _undo(change);
var i = changes.length - 1;
while (i >= 0) {
_undo(changes[i]);
i--;
}
}
}
@@ -1709,7 +1809,7 @@ class Charter extends UIState {
}
function _redo(redo:CharterChange) {
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_REDO_SOUND));
UIState.playEditorSound(Flags.DEFAULT_EDITOR_REDO_SOUND);
switch(redo) {
case null: // do nothing
case CDeleteStrumLine(strumLineID, strumLine):
@@ -1808,14 +1908,24 @@ class Charter extends UIState {
function _playback_metronome(t) {
t.icon = (Options.charterMetronomeEnabled = !Options.charterMetronomeEnabled) ? 1 : 0;
}
function _song_muteinst(t) {
FlxG.sound.music.volume = FlxG.sound.music.volume > 0 ? 0 : 1;
t.icon = 1 - Std.int(Math.ceil(FlxG.sound.music.volume));
public function _slider_mutetoggle(t:UIContextMenuOption) {
if (t.slider == null) return;
t.button.slider.value = t.button.slider.value > 0 ? 0 : 1;
}
function _song_mutevoices(t) {
vocals.volume = (voicesMuted = !voicesMuted) ? 0 : 1;
for (strumLine in strumLines.members) strumLine.updateVoicesVolume();
t.icon = voicesMuted ? 1 : 0;
function _song_instvolume(t) {
FlxG.sound.music.volume = t.slider.value;
t.icon = t.slider.value > 0.5 ? 7 : (t.slider.value > 0 ? 8 : 9);
}
function _song_voicesvolume(t) {
vocals.volume = t.slider.value;
for (strumLine in strumLines.members) strumLine.vocals.volume = t.slider.value * strumLine.vocalsVolume;
t.icon = t.slider.value > 0.5 ? 7 : (t.slider.value > 0 ? 8 : 9);
}
function _song_hitsoundvolume(t) {
hitsoundGlobalVolume = t.slider.value;
t.icon = t.slider.value > 0.5 ? 7 : (t.slider.value > 0 ? 8 : 9);
}
function _playback_back(_) {
if (FlxG.sound.music.playing) return;
@@ -1860,6 +1970,7 @@ class Charter extends UIState {
__event.refreshEventIcons();
(__event.global ? rightEventsGroup : leftEventsGroup).add(__event);
undos.addToUndo(CEditEvent(__event, [], __event.events));
updateCameraChanges();
}
public function getBookmarkList():Array<ChartBookmark> {
@@ -1868,7 +1979,7 @@ class Charter extends UIState {
if (PlayState.SONG.bookmarks != null)
bookmarks = PlayState.SONG.bookmarks;
} catch (e) {}
return bookmarks;
}
@@ -1878,9 +1989,9 @@ class Charter extends UIState {
var currentBookmarks:Array<ChartBookmark> = getBookmarkList();
var newBookmarks:Array<ChartBookmark> = getBookmarkList();
newBookmarks.push({time: daStep, name: name, color: color.toWebString()});
PlayState.SONG.bookmarks = newBookmarks;
updateBookmarks();
updateBookmarks();
undos.addToUndo(CEditBookmarks(currentBookmarks, newBookmarks));
}
@@ -1905,7 +2016,7 @@ class Charter extends UIState {
{
var bars:Array<FlxSprite> = bs[0];
var text:UIText = bs[1];
if (bars != null) {
for (spr in bars) {
if (spr == null) continue;
@@ -1959,7 +2070,7 @@ class Charter extends UIState {
0,
scrollBar.height
);
var bookmarkspr = new FlxSprite(scrollBar.x - 10, yPos).makeSolid(40, 4, bookmarkcolor);
uiGroup.add(bookmarkspr);
sprites.push(bookmarkspr);
@@ -2011,26 +2122,56 @@ class Charter extends UIState {
if (bookmarks.length > 0)
{
var bookmarkOptions:Array<UIContextMenuOption> = [];
var goToBookmark = TU.getRaw("charter.bookmarks.goTo");
for (b in bookmarks)
{
newChilds.push({
bookmarkOptions.push({
label: goToBookmark.format([b.name]),
onSelect: function(_) { Conductor.songPosition = Conductor.getTimeForStep(b.time); }
});
}
newChilds.push({
label: translate("bookmarks.bookmarkList"),
childs: bookmarkOptions
});
newChilds.push(null);
}
newChilds.push({
label: translate("song.muteInst"),
onSelect: _song_muteinst
label: translate("song.inst"),
slider: {
min: 0,
max: 1,
value: 1,
onChange: _song_instvolume
},
onIconClick: _slider_mutetoggle,
icon: 7
});
newChilds.push({
label: translate("song.muteVoices"),
onSelect: _song_mutevoices
label: translate("song.voices"),
slider: {
min: 0,
max: 1,
value: 1,
onChange: _song_voicesvolume
},
onIconClick: _slider_mutetoggle,
icon: 7
});
newChilds.push({
label: translate("song.hitsounds"),
slider: {
min: 0,
max: 1,
value: 1,
onChange: _song_hitsoundvolume
},
onIconClick: _slider_mutetoggle,
icon: 7
});
if (songTopButton != null) songTopButton.contextMenu = newChilds;
@@ -2055,6 +2196,10 @@ class Charter extends UIState {
function _view_showeventBeatSeparator(t) {
t.icon = (Options.charterShowBeats = !Options.charterShowBeats) ? 1 : 0;
}
function _view_showeventCameraHighlights(t) {
t.icon = (Options.charterShowCameraHighlights = !Options.charterShowCameraHighlights) ? 1 : 0;
updateCameraChanges();
}
function _view_switchWaveformRainbow(t) {
t.icon = (Options.charterRainbowWaveforms = !Options.charterRainbowWaveforms) ? 1 : 0;
@@ -2080,8 +2225,8 @@ class Charter extends UIState {
inline function _snap_decreasesnap(_) changequant(-1);
inline function _snap_resetsnap(_) setquant(16);
inline function changequant(change:Int) {FlxG.sound.play(Paths.sound(Flags.DEFAULT_CHARTER_SNAPPINGCHANGE_SOUND)); quant = quants[FlxMath.wrap(quants.indexOf(quant) + change, 0, quants.length-1)]; buildSnapsUI();};
inline function setquant(newQuant:Int) {FlxG.sound.play(Paths.sound(Flags.DEFAULT_CHARTER_SNAPPINGCHANGE_SOUND)); quant = newQuant; buildSnapsUI();}
inline function changequant(change:Int) {UIState.playEditorSound(Flags.DEFAULT_CHARTER_SNAPPINGCHANGE_SOUND); quant = quants[FlxMath.wrap(quants.indexOf(quant) + change, 0, quants.length-1)]; buildSnapsUI();};
inline function setquant(newQuant:Int) {UIState.playEditorSound(Flags.DEFAULT_CHARTER_SNAPPINGCHANGE_SOUND); quant = newQuant; buildSnapsUI();}
function buildSnapsUI():Array<UIContextMenuOption> {
var snapsTopButton:UITopMenuButton = topMenuSpr == null ? null : cast topMenuSpr.members[snapIndex];
@@ -2117,12 +2262,12 @@ class Charter extends UIState {
}
inline function _note_addsustain(t) {
FlxG.sound.play(Paths.sound(Flags.DEFAULT_CHARTER_SUSTAINADD_SOUND));
UIState.playEditorSound(Flags.DEFAULT_CHARTER_SUSTAINADD_SOUND);
changeNoteSustain(1);
}
inline function _note_subtractsustain(t) {
FlxG.sound.play(Paths.sound(Flags.DEFAULT_CHARTER_SUSTAINDELETE_SOUND));
UIState.playEditorSound(Flags.DEFAULT_CHARTER_SUSTAINDELETE_SOUND);
changeNoteSustain(-1);
}
@@ -2199,16 +2344,18 @@ class Charter extends UIState {
keybind: [CONTROL, SHIFT, A],
onSelect: _note_selectmeasure
},
null,
{
label: "(0) " + translate("noteTypes.default"),
keybind: [ZERO],
onSelect: (_) -> {changeNoteType(0);},
icon: this.noteType == 0 ? 1 : 0
}
null
];
var noteKeys:Array<FlxKey> = [ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE];
var noteTypeOptions:Array<UIContextMenuOption> = [{
label: "(0) " + translate("noteTypes.default"),
keybind: [ZERO],
onSelect: (_) -> {changeNoteType(0);},
icon: this.noteType == 0 ? 1 : 0
}];
final noteKeys:Array<Array<Array<FlxKey>>> = [[[ZERO], [NUMPADZERO]], [[ONE], [NUMPADONE]], [[TWO], [NUMPADTWO]], [[THREE], [NUMPADTHREE]], [[FOUR], [NUMPADFOUR]], [[FIVE], [NUMPADFIVE]],
[[SIX], [NUMPADSIX]], [[SEVEN], [NUMPADSEVEN]], [[EIGHT], [NUMPADEIGHT]], [[NINE], [NUMPADNINE]]];
for (i=>type in noteTypes) {
var realNoteID:Int = i+1; // Default Note not stored
var newChild:UIContextMenuOption = {
@@ -2217,9 +2364,13 @@ class Charter extends UIState {
onSelect: (_) -> {changeNoteType(realNoteID);},
icon: this.noteType == realNoteID ? 1 : 0
};
if (realNoteID <= 9) newChild.keybind = [noteKeys[realNoteID]];
newChilds.push(newChild);
if (realNoteID <= 9) newChild.keybinds = noteKeys[realNoteID];
noteTypeOptions.push(newChild);
}
newChilds.push({
label: translate("note.noteTypesList"),
childs: noteTypeOptions
});
newChilds.push({
label: translate("note.editNoteTypesList"),
color: 0xFF959829, icon: 4,
@@ -2227,6 +2378,7 @@ class Charter extends UIState {
onSelect: editNoteTypesList
});
if (noteTopButton != null) noteTopButton.contextMenu = newChilds;
if (topMenu != null && topMenu[noteIndex] != null) topMenu[noteIndex].childs = newChilds;
return newChilds;
}
@@ -2314,8 +2466,12 @@ class Charter extends UIState {
}
}
public inline function hitsoundsEnabled(id:Int)
return strumLines.members[id] != null && strumLines.members[id].hitsounds;
public inline function playHitsound(id:Int) {
if (strumLines.members[id] != null && strumLines.members[id].hitsoundVolume > 0 && hitsoundGlobalVolume > 0) {
hitsound.volume = hitsoundGlobalVolume * strumLines.members[id].hitsoundVolume;
hitsound.replay();
}
}
public inline function __fixSelection(selection:Selection):Selection {
var newSelection:Selection = new Selection();
@@ -2406,7 +2562,7 @@ class Charter extends UIState {
quantSelected: quant,
noteTypeSelected: noteType,
strumlinesDraggable: strumLines.draggable,
hitSounds: [for (strumLine in strumLines.members) strumLine.hitsounds],
hitSounds: [for (strumLine in strumLines.members) strumLine.hitsoundVolume > 0],
mutedVocals: [for (strumLine in strumLines.members) !(strumLine.vocals.volume > 0)],
waveforms: [for (strumLine in strumLines.members) strumLine.selectedWaveform]
}
@@ -2422,7 +2578,7 @@ class Charter extends UIState {
strumLines.draggable = playtestInfo.strumlinesDraggable;
for (i => strumLine in strumLines.members)
strumLine.hitsounds = playtestInfo.hitSounds[i];
strumLine.hitsoundVolume = playtestInfo.hitSounds[i] ? 1 : 0;
for (i => strumLine in strumLines.members)
strumLine.vocals.volume = playtestInfo.mutedVocals[i] ? 0 : 1;
for (i => strumLine in strumLines.members)
@@ -2514,3 +2670,9 @@ typedef PlaytestInfo = {
var mutedVocals:Array<Bool>;
var waveforms:Array<Int>;
}
typedef CameraChange = {
var strumLineID:Int;
var step:Float;
var endStep:Float;
}
@@ -82,7 +82,7 @@ class CharterAutoSaveUI extends UISliceSprite {
icon.animation.curAnim.curFrame = 1;
for (member in [this, progressBar, progressBarBack, autosavingText]) member.color = 0xFFA3EC95;
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_AUTOSAVE_SOUND));
UIState.playEditorSound(Flags.DEFAULT_EDITOR_AUTOSAVE_SOUND);
(new FlxTimer()).start(1, (_) -> {disappearAnimation();});
});
@@ -178,6 +178,8 @@ class CharterBackdrop extends FlxTypedGroup<FlxBasic> {
public var gridShader:CustomShader = new CustomShader("engine/charterGrid");
var __lastKeyCount:Int = 4;
public var cameraHighlight:CameraHighlight;
public function new() {
super();
@@ -187,6 +189,10 @@ class CharterBackdrop extends FlxTypedGroup<FlxBasic> {
add(gridBackDrop);
gridShader.hset("segments", 4);
cameraHighlight = new CameraHighlight(this);
cameraHighlight.makeSolid(1, 1, 0xFFFFFFFF);
add(cameraHighlight);
waveformSprite = new FlxSprite().makeSolid(1, 1, 0xFF000000);
waveformSprite.scale.set(160, 1);
waveformSprite.updateHitbox();
@@ -247,10 +253,11 @@ class CharterBackdrop extends FlxTypedGroup<FlxBasic> {
x = strumLine.x;
alpha = strumLine.strumLine.visible ? 0.9 : 0.4;
keyCount = strumLine.keyCount;
cameraHighlight.color = strumLine.highlightColor;
} else alpha = 0.9;
for (spr in [gridBackDrop, beatSeparator, topLimit, bottomLimit,
topSeparator, bottomSeparator, conductorFollowerSpr, waveformSprite]) {
topSeparator, bottomSeparator, conductorFollowerSpr, waveformSprite, cameraHighlight]) {
spr.x = x; if (spr != waveformSprite) spr.alpha = alpha;
spr.cameras = this.cameras;
}
@@ -272,6 +279,9 @@ class CharterBackdrop extends FlxTypedGroup<FlxBasic> {
spr.updateHitbox();
}
cameraHighlight.scale.x = (keyCount * 40)-2;
cameraHighlight.x += 1;
waveformSprite.visible = waveformSprite.shader != null;
if (waveformSprite.shader == null) return;
@@ -311,16 +321,19 @@ class CharterGridSeperatorBase extends FlxSprite {
private static var lastMaxMeasure:Float = -1;
public static var lastConductorSprY:Float = Math.NEGATIVE_INFINITY;
public static var lastCameraZoom:Float = -1;
private static var beatStepTimes:Array<Float> = [];
private static var measureStepTimes:Array<Float> = [];
private static var timeSignatureChangeGaps:Array<Float> = [];
private function recalculateBeats() {
@:privateAccess
var currentZoom = Charter.instance.__camZoom;
var conductorSprY = Charter.instance.gridBackdrops.conductorSprY;
if (conductorSprY == lastConductorSprY) return;
if (conductorSprY == lastConductorSprY && currentZoom == lastCameraZoom) return; //only update if song pos or camera zoom has changed
var zoomOffset = ((FlxG.height * (1/cameras[0].zoom)) * 0.5);
var zoomOffset = ((FlxG.height * (1/currentZoom)) * 0.5);
minStep = (conductorSprY - zoomOffset)/40;
maxStep = (conductorSprY + zoomOffset)/40;
@@ -359,6 +372,7 @@ class CharterGridSeperatorBase extends FlxSprite {
}
lastConductorSprY = conductorSprY;
lastCameraZoom = currentZoom;
}
private inline function calculateTimeSignatureGaps() {
@@ -446,6 +460,77 @@ class CharterGridSeperator extends CharterGridSeperatorBase {
}
}
class CameraHighlight extends FlxSprite {
private var grid:CharterBackdrop;
private var _currentCameraMovementIndex:Int = 0;
public function new(grid:CharterBackdrop) {
this.grid = grid;
super();
}
override public function draw() {
if (!Options.charterShowCameraHighlights || grid.strumLine == null) return;
@:privateAccess
var minStep = CharterGridSeperatorBase.minStep;
@:privateAccess
var maxStep = CharterGridSeperatorBase.maxStep;
@:privateAccess
var strumLineID = Charter.instance.strumLines.isDragging ? Charter.instance.strumLines.__pastStrumlines.indexOf(grid.strumLine) : Charter.instance.gridBackdrops.members.indexOf(grid);
//first default camera change
if ((Charter.instance.cameraMovementChanges[0] == null || Charter.instance.cameraMovementChanges[0].step != 0) && strumLineID == 0) {
var endStep = Charter.instance.cameraMovementChanges[0] != null ? Charter.instance.cameraMovementChanges[0].step : Charter.instance.__endStep;
if (endStep >= minStep) {
setupHighlight(0, endStep); super.draw();
setupLine(endStep); super.draw();
}
}
//update index if gone backwards
while(_currentCameraMovementIndex > 0) {
if (Charter.instance.cameraMovementChanges[_currentCameraMovementIndex] == null || Charter.instance.cameraMovementChanges[_currentCameraMovementIndex].endStep >= minStep) {
_currentCameraMovementIndex--;
} else {
break;
}
}
var seenFirstVisible = false;
for (i in _currentCameraMovementIndex...Charter.instance.cameraMovementChanges.length) {
var change = Charter.instance.cameraMovementChanges[i];
if (change.endStep >= minStep) {
if (!seenFirstVisible) {
_currentCameraMovementIndex = i; //remember the index for the next frame, so we dont need to loop through everything every time
seenFirstVisible = true;
}
if (change.strumLineID == strumLineID) {
setupHighlight(change.step, change.endStep); super.draw();
setupLine(change.step); super.draw();
setupLine(change.endStep); super.draw();
}
}
if (change.endStep > maxStep) break;
}
}
private inline function setupHighlight(step:Float, endStep:Float) {
y = step * 40;
alpha = 0.15;
scale.y = (endStep -step) * 40;
updateHitbox();
}
private inline function setupLine(step:Float) {
y = (step * 40)-1;
alpha = 0.8;
scale.y = 2;
updateHitbox();
}
}
class EventBackdrop extends FlxBackdrop {
public var eventBeatSeparator:CharterEventGridSeperator;
+3 -2
View File
@@ -161,8 +161,9 @@ class CharterNote extends UISprite implements ICharterSelectable {
}
if (__passed != (__passed = step < Conductor.curStepFloat + (Options.songOffsetAffectEditors ? (Conductor.songOffset / Conductor.stepCrochet) : 0))) {
if (__passed && FlxG.sound.music.playing && Charter.instance.hitsoundsEnabled(strumLineID))
Charter.instance.hitsound.replay();
if (__passed && FlxG.sound.music.playing) {
Charter.instance.playHitsound(strumLineID);
}
}
if (strumLine != null) {
@@ -21,7 +21,7 @@ class CharterPreviewStrumLine extends FlxTypedGroup<FlxSprite>
for (i in 0...keyCount){
var strum = new FlxSprite();
strum.frames = Paths.getFrames("game/notes/default");
strum.setGraphicSize(Std.int((strum.width * 0.7) * scale));
strum.setGraphicSize(Std.int((strum.width * Flags.DEFAULT_NOTE_SCALE) * scale));
strum.updateHitbox();
var animPrefix = strumAnimPrefix[i % 4];
@@ -33,7 +33,7 @@ class CharterPreviewStrumLine extends FlxTypedGroup<FlxSprite>
note = new FlxSprite();
note.frames = Paths.getFrames("game/notes/default");
note.setGraphicSize(Std.int((note.width * 0.7) * scale));
note.setGraphicSize(Std.int((note.width * Flags.DEFAULT_NOTE_SCALE) * scale));
note.updateHitbox();
note.animation.addByPrefix('purple', 'purple0');
note.animation.play('purple');
@@ -41,7 +41,7 @@ class CharterPreviewStrumLine extends FlxTypedGroup<FlxSprite>
add(note);
}
var noteTime:Float = FlxG.height;
var noteTime:Float = FlxG.initialHeight;
var scroll:Float = 1.0;
public function updatePos(x:Float, y:Float, scale:Float, spacing:Float, keyCount:Int, scrollSpeed:Float){
@@ -53,7 +53,7 @@ class CharterPreviewStrumLine extends FlxTypedGroup<FlxSprite>
strum.x = CoolUtil.fpsLerp(strum.x, x + (Note.swagWidth * scale * spacing * i), 0.2);
strum.y = CoolUtil.fpsLerp(strum.y, y + (Note.swagWidth*0.5) - (Note.swagWidth * scale * 0.5), 0.2);
strum.scale.x = strum.scale.y = CoolUtil.fpsLerp(strum.scale.x, 0.7 * scale, 0.2);
strum.scale.x = strum.scale.y = CoolUtil.fpsLerp(strum.scale.x, Flags.DEFAULT_NOTE_SCALE * scale, 0.2);
strum.updateHitbox();
}
@@ -61,7 +61,7 @@ class CharterPreviewStrumLine extends FlxTypedGroup<FlxSprite>
scroll = CoolUtil.fpsLerp(scroll, scrollSpeed, 0.2);
noteTime -= FlxG.elapsed * scroll * 1000 * 0.45;
if (noteTime <= 0.0)
noteTime = FlxG.height;
noteTime = FlxG.initialHeight;
note.x = members[0].x;
note.y = members[0].y + noteTime;
@@ -146,11 +146,10 @@ class CharterSelectionScreen extends EditorTreeMenuScreen {
var screen = parent.tree.last();
var idx = 0;
while (!(screen.members[idx] is Separator)) idx++;
while (!(screen.members[idx] is Separator || idx >= screen.members.length)) idx++;
screen.insert(idx, makeChartOption(name, curSong.variant != null && curSong.variant != "" ? curSong.variant : null, curSong.name));
// Add to Meta
var metaPath = '$songFolder/meta${curSong.variant != null && curSong.variant == "" ? "-" + curSong.variant : ""}.json';
var metaPath = '$songFolder/meta${curSong.variant != null && curSong.variant != "" ? "-" + curSong.variant : ""}.json';
CoolUtil.safeSaveFile(metaPath, Chart.makeMetaSaveable(curSong));
}
#end
@@ -105,6 +105,7 @@ class CharterStrumLineGroup extends FlxTypedGroup<CharterStrumline> {
draggingObj = null;
fixEvents();
refreshStrumlineIDs();
Charter.instance.updateCameraChanges();
}
public inline function fixEvents() {
@@ -1,5 +1,7 @@
package funkin.editors.charter;
import funkin.editors.ui.UIContextMenu.UIContextMenuOption;
import flixel.util.FlxColor;
import flixel.group.FlxSpriteGroup;
import flixel.sound.FlxSound;
import funkin.backend.chart.ChartData.ChartStrumLine;
@@ -10,11 +12,11 @@ import funkin.game.HealthIcon;
class CharterStrumline extends UISprite {
public var strumLine:ChartStrumLine;
public var hitsounds:Bool = true;
public var draggingSprite:UISprite;
public var healthIcons:FlxSpriteGroup;
public var button:CharterStrumlineOptions;
public var highlightColor:FlxColor;
public var draggable:Bool = false;
public var dragging:Bool = false;
@@ -22,7 +24,9 @@ class CharterStrumline extends UISprite {
public var curMenu:UIContextMenu = null;
public var vocals:FlxSound;
public var voicesMuted:Bool = false;
public var hasVocals:Bool = false;
public var vocalsVolume:Float = 1;
public var hitsoundVolume:Float = 1;
public var keyCount:Int = 4;
public var startingID(get, null):Int;
@@ -142,15 +146,22 @@ class CharterStrumline extends UISprite {
if (asset != null) {
vocals.reset();
vocals.loadEmbedded(asset);
hasVocals = true;
}
else {
vocals.destroy();
hasVocals = false;
}
vocals.group = FlxG.sound.defaultMusicGroup;
}
public function updateVoicesVolume() {
vocals.volume = (voicesMuted || (Charter.instance?.voicesMuted ?? false)) ? 0 : 1;
highlightColor = 0xFFFFFFFF;
if (icons[0] != null) {
var characterXML = Character.getXMLFromCharName(icons[0]);
if (characterXML != null && characterXML.x.exists("color")) highlightColor = FlxColor.fromString(characterXML.x.get("color"));
//make darker colors more visible for the highlight
highlightColor.brightness = Math.max(highlightColor.brightness, 0.65);
}
}
}
@@ -173,18 +184,17 @@ class CharterStrumlineOptions extends UITopMenuButton {
contextMenu = [
{
label: TU.translate("charter.strumLine.hitsounds"),
onSelect: function(_) {
strLine.hitsounds = !strLine.hitsounds;
slider: {
min: 0,
max: 1,
value: strLine.hitsoundVolume,
onChange: function(t) {
strLine.hitsoundVolume = t.slider.value;
t.icon = t.slider.value > 0.5 ? 7 : (t.slider.value > 0 ? 8 : 9);
}
},
icon: strLine.hitsounds ? 1 : 0
},
{
label: TU.translate("charter.strumLine.muteVocals"),
onSelect: function(_) {
strLine.voicesMuted = !strLine.voicesMuted;
strLine.updateVoicesVolume();
},
icon: strLine.voicesMuted ? 1 : 0
onIconClick: Charter.instance._slider_mutetoggle,
icon: 7
},
null,
{
@@ -205,20 +215,44 @@ class CharterStrumlineOptions extends UITopMenuButton {
}
];
contextMenu.insert(0, {
label: TU.translate("charter.strumLine.noWaveform"),
onSelect: function(_) {strLine.selectedWaveform = -1;},
icon: strLine.selectedWaveform == -1 ? 1 : 0
});
if (strLine.hasVocals) {
contextMenu.insert(1, {
label: TU.translate("charter.strumLine.vocals"),
slider: {
min: 0,
max: 1,
value: strLine.vocalsVolume,
onChange: function(t) {
strLine.vocalsVolume = t.slider.value;
strLine.vocals.volume = Charter.instance.vocals.volume * strLine.vocalsVolume;
t.icon = t.slider.value > 0.5 ? 7 : (t.slider.value > 0 ? 8 : 9);
}
},
onIconClick: Charter.instance._slider_mutetoggle,
icon: 7
});
}
var waveformOptions:Array<UIContextMenuOption> = [
{
label: TU.translate("charter.strumLine.noWaveform"),
onSelect: function(_) {strLine.selectedWaveform = -1;},
icon: strLine.selectedWaveform == -1 ? 1 : 0
}
];
for (i => name in Charter.waveformHandler.waveformList)
contextMenu.insert(1+i, {
waveformOptions.push({
label: name,
onSelect: function(_) {strLine.selectedWaveform = i;},
icon: strLine.selectedWaveform == i ? 6 : 5
});
contextMenu.insert(1+Charter.waveformHandler.waveformList.length, null);
contextMenu.insert(0, {
label: TU.translate("charter.strumLine.waveforms"),
childs: waveformOptions
});
contextMenu.insert(1, null);
var cam = Charter.instance.charterCamera;
var point = CoolUtil.worldToScreenPosition(this, cam);
@@ -33,6 +33,8 @@ class CharterStrumlineScreen extends UISubstateWindow {
public var strumLineCam:HudCamera;
public var previewStrumLine:CharterPreviewStrumLine;
public var previewBorder:Array<FlxSprite> = [];
private final borderGap:Int = 5;
private var onSave:ChartStrumLine -> Void = null;
@@ -176,20 +178,30 @@ class CharterStrumlineScreen extends UISubstateWindow {
addLabelOn(vocalsSuffixDropDown, TU.translate("charterStrumLine.vocalSuffix"));
keyCountStepper = new UINumericStepper(stagePositionDropdown.x, vocalsSuffixDropDown.y, strumLine.keyCount != null ? strumLine.keyCount : 4, 1, 0, 1, 1000, 84);
// if (Flags.CHARTER_ADVANCED_SETTINGS) {
add(keyCountStepper);
addLabelOn(keyCountStepper, TU.translate("charterStrumLine.keyCount"));
// }
add(keyCountStepper);
addLabelOn(keyCountStepper, TU.translate("charterStrumLine.keyCount"));
strumLineCam = new HudCamera();
strumLineCam.downscroll = Options.downscroll;
strumLineCam.bgColor = 0;
strumLineCam.alpha = 0;
FlxG.cameras.add(strumLineCam, false);
updateStrumlineCam(FlxG.width, FlxG.height);
previewStrumLine = new CharterPreviewStrumLine(0, 0, 0, 1, 4, 0);
previewStrumLine.camera = strumLineCam;
add(previewStrumLine);
FlxTween.tween(strumLineCam, {alpha: 1}, 0.25, {ease: FlxEase.cubeOut});
//preview border
previewBorder.push(new FlxSprite(-borderGap, -borderGap).makeSolid(FlxG.initialWidth + (borderGap*2), borderGap, 0x88FFFFFF));
previewBorder.push(new FlxSprite(-borderGap, 0).makeSolid(borderGap, FlxG.initialHeight, 0x88FFFFFF));
previewBorder.push(new FlxSprite(FlxG.initialWidth, 0).makeSolid(borderGap, FlxG.initialHeight, 0x88FFFFFF));
previewBorder.push(new FlxSprite(-borderGap, FlxG.initialHeight).makeSolid(FlxG.initialWidth + (borderGap*2), borderGap, 0x88FFFFFF));
for (border in previewBorder) {
border.camera = strumLineCam;
add(border);
}
}
function saveStrumline() {
@@ -222,7 +234,7 @@ class CharterStrumlineScreen extends UISubstateWindow {
previewStrumLine.visible = visibleCheckbox.checked;
var xOffset:Float = StrumLine.calculateStartingXPos(hudXStepper.value, hudScaleStepper.value, hudSpacingStepper.value, Std.int(keyCountStepper.value));
var xOffset:Float = StrumLine.calculateStartingXPosFromInitialWidth(hudXStepper.value, hudScaleStepper.value, hudSpacingStepper.value, Std.int(keyCountStepper.value));
previewStrumLine.updatePos(xOffset, hudYStepper.value, hudScaleStepper.value, hudSpacingStepper.value, Std.int(keyCountStepper.value), scrollSpeed);
super.update(elapsed);
@@ -233,6 +245,26 @@ class CharterStrumlineScreen extends UISubstateWindow {
FlxTween.cancelTweensOf(strumLineCam);
FlxG.cameras.remove(strumLineCam);
}
public override function onResize(width:Int, height:Int) {
super.onResize(width, height);
if (!UIState.resolutionAware) return;
if ((width < FlxG.initialWidth || height < FlxG.initialHeight) && !Options.bypassEditorsResize) {
width = FlxG.initialWidth; height = FlxG.initialHeight;
}
updateStrumlineCam(width, height);
}
private function updateStrumlineCam(windowWidth:Int, windowHeight:Int) {
strumLineCam.width = FlxG.initialWidth + (borderGap*2);
strumLineCam.height = FlxG.initialHeight + (borderGap*2);
strumLineCam.x = (windowWidth/2) - (strumLineCam.width/2);
strumLineCam.y = (windowHeight/2) - (strumLineCam.height/2);
strumLineCam.scroll.x = -borderGap;
strumLineCam.scroll.y = -borderGap;
}
}
class CharacterButton extends UIButton {
@@ -371,12 +371,14 @@ class SongCreationScreen extends UISubstateWindow {
{
case 2 /*"V-Slice Project (.fnfc)"*/:
var files:Map<String, Any> = [];
#if sys
for (field in new ZipReader(new BytesInput(importChartFile.file)).read()) {
var fileName = field.fileName;
var fileContent = ZipUtil.unzip(field);
files.set(fileName, fileContent);
}
saveFromVSlice(files);
#end
case 1 /*"V-Slice"*/:
var songId = importIdTextBox.label.text;
var files:Map<String, Any> = [];
+4 -4
View File
@@ -508,7 +508,7 @@ class StageEditor extends UIState {
function _file_save(_) {
#if sys
FlxG.sound.play(Paths.sound('editors/save'));
UIState.playEditorSound(Flags.DEFAULT_EDITOR_SAVE_SOUND);
CoolUtil.safeSaveFile(
'${Paths.getAssetsRoot()}/data/stages/${__stage}.xml',
buildStage()
@@ -520,7 +520,7 @@ class StageEditor extends UIState {
}
function _file_saveas(_) {
FlxG.sound.play(Paths.sound('editors/save'));
UIState.playEditorSound(Flags.DEFAULT_EDITOR_SAVE_SOUND);
openSubState(new SaveSubstate(buildStage(), {
defaultSaveFile: '${__stage}.xml'
}));
@@ -730,7 +730,7 @@ class StageEditor extends UIState {
}
function _edit_undo(_) {
FlxG.sound.play(Flags.DEFAULT_EDITOR_UNDO_SOUND);
UIState.playEditorSound(Flags.DEFAULT_EDITOR_UNDO_SOUND);
var undo = undos.undo();
switch(undo) {
case null:
@@ -756,7 +756,7 @@ class StageEditor extends UIState {
}
function _edit_redo(_) {
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_REDO_SOUND));
UIState.playEditorSound(Flags.DEFAULT_EDITOR_REDO_SOUND);
var redo = undos.redo();
switch(redo) {
case null:
+1 -1
View File
@@ -30,7 +30,7 @@ class UIButton extends UISliceSprite {
super.onHovered();
if (FlxG.mouse.justPressed) {
hasBeenPressed = true;
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_BUTTONCLICK_SOUND));
UIState.playEditorSound(Flags.DEFAULT_EDITOR_BUTTONCLICK_SOUND);
}
if (FlxG.mouse.justReleased && callback != null && shouldPress && hasBeenPressed) {
callback();
+226 -32
View File
@@ -16,6 +16,12 @@ class UIContextMenu extends MusicBeatSubstate {
public var contextMenuOptions:Array<UIContextMenuOptionSpr> = [];
public var separators:Array<FlxSprite> = [];
public var childContextMenu:UIContextMenu = null;
public var parentContextMenu:UIContextMenu = null;
private var childContextMenuOptionIndex:Int = -1;
@:allow(funkin.editors.ui.UIContextMenu)
private var lastHoveredOptionIndex:Int = -1;
var scroll:Float = 0.0;
var flipped:Bool = false;
@@ -96,6 +102,10 @@ class UIContextMenu extends MusicBeatSubstate {
for(o in separators)
o.x -= bg.bWidth;
}
for(o in contextMenuOptions) {
o.postCreate();
}
}
public function select(option:UIContextMenuOption) {
@@ -105,12 +115,12 @@ class UIContextMenu extends MusicBeatSubstate {
if (callback != null)
callback(this, index, option);
if (option.closeOnSelect == null ? true : option.closeOnSelect)
close();
closeWithParents();
}
public override function update(elapsed:Float) {
if (__oobDeletion && FlxG.mouse.justPressed && !bg.hoveredByChild)
close();
if (__oobDeletion && FlxG.mouse.justPressed && !bg.hoveredByChild && !hoveringAnyChildren())
closeWithParents();
__oobDeletion = true;
@@ -121,6 +131,14 @@ class UIContextMenu extends MusicBeatSubstate {
contextCam.scroll.y = CoolUtil.fpsLerp(contextCam.scroll.y, scroll, 0.5);
contextCam.alpha = CoolUtil.fpsLerp(contextCam.alpha, 1, 0.25);
if (parentContextMenu != null) {
if (hoveringAnyParents() && parentContextMenu.lastHoveredOptionIndex != parentContextMenu.childContextMenuOptionIndex) {
closeWithChildren();
parentContextMenu.childContextMenuOptionIndex = -1;
}
}
}
public override function destroy() {
@@ -129,6 +147,56 @@ class UIContextMenu extends MusicBeatSubstate {
if (UIState.state.curContextMenu == this)
UIState.state.curContextMenu = null;
}
public function openChildContextMenu(optionSpr:UIContextMenuOptionSpr) {
var index = contextMenuOptions.indexOf(optionSpr);
if (index != childContextMenuOptionIndex) {
childContextMenuOptionIndex = index;
var child = new UIContextMenu(optionSpr.option.childs, null, optionSpr.x + optionSpr.bWidth + 4, optionSpr.y - 4);
persistentDraw = true;
persistentUpdate = true;
child.parentContextMenu = this;
childContextMenu = child;
openSubState(child);
}
}
public function closeWithParents() {
close();
if (parentContextMenu != null) {
parentContextMenu.closeWithParents();
}
}
public function closeWithChildren() {
if (childContextMenu != null) {
childContextMenu.closeWithChildren();
}
close();
}
public function hoveringAnyParents() {
if (parentContextMenu != null) {
return parentContextMenu.bg.hoveredByChild || parentContextMenu.hoveringAnyParents();
}
return false;
}
public function hoveringAnyChildren() {
if (childContextMenu != null) {
return childContextMenu.bg.hoveredByChild || childContextMenu.hoveringAnyChildren();
}
return false;
}
}
typedef UIContextMenuSliderOptionData = {
var min:Float;
var max:Float;
var value:Float;
var ?onChange:UIContextMenuOption->Void;
//default = 120, ignored if sameLine = false
var ?width:Float;
//disables stepper and text if false, default = false
var ?showValues:Bool;
//if true, the slider will be on the same line as the label text, otherwise it will be on the next line below the label
var ?sameLine:Bool;
}
typedef UIContextMenuCallback = UIContextMenu->Int->UIContextMenuOption->Void;
@@ -144,13 +212,24 @@ typedef UIContextMenuOption = {
var ?button:UIContextMenuOptionSpr;
var ?onCreate:UIContextMenuOptionSpr->Void;
var ?childs:Array<UIContextMenuOption>;
var ?slider:UIContextMenuSliderOptionData;
var ?onIconClick:UIContextMenuOption->Void;
}
enum abstract UIContextMenuOptionType(Int) from Int {
var DEFAULT = 0;
var SUBMENU = 1;
var SLIDER = 2;
}
class UIContextMenuOptionSpr extends UISliceSprite {
public var label:UIText;
public var labelKeybind:UIText;
public var icon:FlxSprite;
public var icon:UIContextMenuOptionIcon;
public var option:UIContextMenuOption;
public var optionType:UIContextMenuOptionType = DEFAULT;
public var slider:UISlider = null;
var parent:UIContextMenu;
@@ -160,40 +239,96 @@ class UIContextMenuOptionSpr extends UISliceSprite {
this.parent = parent;
this.color = option.color;
if (option.icon != null && option.icon > 0) {
icon = new FlxSprite(0, 0).loadGraphic(Paths.image('editors/ui/context-icons'), true, 20, 20);
icon.animation.add('icon', [option.icon-1], 0, true);
icon.animation.play('icon');
}
var w:Int = label.frameWidth + 22;
var h:Int = label.frameHeight;
if (option.keybinds == null) {
if (option.keybind != null) {
option.keybinds = [option.keybind];
}
}
if (option.childs != null) optionType = SUBMENU;
if (option.slider != null) optionType = SLIDER;
if (option.keybinds != null || option.keybindText != null) {
var text = if(option.keybindText == null) {
var textKeys:Array<String> = [];
for (o in option.keybinds[0]) {
if (Std.int(o) > 0) {
textKeys.push(o.toUIString());
switch(optionType) {
case SUBMENU:
labelKeybind = new UIText(label.x + label.frameWidth + 10, 2, 0, ">");
case SLIDER:
labelKeybind = new UIText(label.x + label.frameWidth + 10, 2, 0, "");
//slider needs to be created after so that it can match the menu width (when not on the same line)
if (option.slider.sameLine != null && option.slider.sameLine) {
var sliderWidth = option.slider.width != null ? Std.int(option.slider.width) : 120;
w += 120 + slider.barWidth;
} else {
h *= 2;
}
default:
if (option.keybinds == null) {
if (option.keybind != null) {
option.keybinds = [option.keybind];
}
}
textKeys.join("+");
} else {
option.keybindText;
}
labelKeybind = new UIText(label.x + label.frameWidth + 10, 2, 0, text);
labelKeybind.alpha = 0.75;
if (option.keybinds != null || option.keybindText != null) {
var text = if(option.keybindText == null) {
var textKeys:Array<String> = [];
for (o in option.keybinds[0]) {
if (Std.int(o) > 0) {
textKeys.push(o.toUIString());
}
}
textKeys.join("+");
} else {
option.keybindText;
}
labelKeybind = new UIText(label.x + label.frameWidth + 10, 2, 0, text);
labelKeybind.alpha = 0.75;
w = Std.int(labelKeybind.x + labelKeybind.frameWidth + 10);
}
}
super(x, y, labelKeybind != null ? Std.int(labelKeybind.x + labelKeybind.frameWidth + 10) : (label.frameWidth + 22), label.frameHeight, 'editors/ui/menu-item');
super(x, y, w, h, 'editors/ui/menu-item');
members.push(label);
if (icon != null)
members.push(icon);
updateIcon();
if (labelKeybind != null)
members.push(labelKeybind);
members.push(labelKeybind);
}
//Called after all options are created and the context menu width/height is final
public function postCreate() {
switch(optionType) {
case SLIDER:
var sliderWidth = bWidth-50;
if (option.slider.sameLine != null && option.slider.sameLine) {
option.slider.width != null ? Std.int(option.slider.width) : 120;
}
slider = new UISlider(0, 0, sliderWidth, option.slider.value,
[{start: option.slider.min, end: option.slider.max, size: option.slider.max-option.slider.min}], false);
slider.onChange = function(v) {
option.slider.value = v;
if (option.slider.onChange != null) option.slider.onChange(option);
updateIcon(); //check if icon has changed
@:privateAccess
labelKeybind.text = '${CoolUtil.quantize(slider.__barProgress * 100, 1)}%';
};
slider.value = option.slider.value;
if (option.slider.showValues == null || !option.slider.showValues) {
slider.startText.visible = false;
slider.endText.visible = false;
slider.valueStepper.visible = false;
slider.valueStepper.selectable = false;
}
members.push(slider);
case SUBMENU:
default:
}
}
public override function draw() {
@@ -205,12 +340,71 @@ class UIContextMenuOptionSpr extends UISliceSprite {
icon.follow(this, 0, 0);
if (labelKeybind != null)
labelKeybind.follow(this, bWidth - 10 - labelKeybind.frameWidth, 2);
if (slider != null) {
if (option.slider.sameLine != null && option.slider.sameLine) {
slider.follow(this, bWidth - 18 - slider.barWidth - (slider.endText.visible ? slider.endText.width : 0), 5);
} else {
slider.follow(this, 20, 5 + label.frameHeight);
}
}
super.draw();
}
public override function onHovered() {
super.onHovered();
if (FlxG.mouse.justReleased)
parent.select(option);
parent.lastHoveredOptionIndex = parent.contextMenuOptions.indexOf(this);
switch(optionType) {
case SUBMENU:
parent.openChildContextMenu(this);
case SLIDER:
default:
if (FlxG.mouse.justReleased)
parent.select(option);
}
}
public function updateIcon() {
var currentIcon = option.icon != null ? option.icon : 0;
if (icon == null && currentIcon > 0) {
members.push(icon = new UIContextMenuOptionIcon(option));
}
if (icon != null) {
icon.updateIconState(currentIcon);
}
}
}
class UIContextMenuOptionIcon extends UISprite {
private var option:UIContextMenuOption;
private var _lastState:Int = 0;
override public function new(option:UIContextMenuOption) {
super();
this.option = option;
loadGraphic(Paths.image('editors/ui/context-icons'), true, 20, 20);
selectable = option.onIconClick != null;
cursor = option.onIconClick != null ? CLICK : ARROW;
}
public function updateIconState(state:Int) {
if (_lastState == state) return;
_lastState = state;
visible = state > 0;
if (state > 0) {
animation.add('icon', [state-1], 0, true);
animation.play('icon');
}
}
public override function onHovered() {
super.onHovered();
if (FlxG.mouse.justReleased && option.onIconClick != null) {
option.onIconClick(option);
}
}
}
+1 -1
View File
@@ -87,7 +87,7 @@ class UIDropDown extends UISliceSprite {
}
public function openContextMenu() {
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_DROPDOWNAPPEAR_SOUND));
UIState.playEditorSound(Flags.DEFAULT_EDITOR_DROPDOWNAPPEAR_SOUND);
var screenPos = getScreenPosition(null, __lastDrawCameras[0] == null ? FlxG.camera : __lastDrawCameras[0]);
curMenu = UIState.state.openContextMenu([
for(k=>o in items) {
@@ -62,7 +62,11 @@ class UIFileExplorer extends UISliceSprite {
}
public function loadFile(path:String) {
#if sys
file = cast sys.io.File.getBytes(filePath = path);
#else
file = null;
#end
deleteButton.visible = deleteButton.selectable = deleteIcon.visible = !(uploadButton.visible = uploadButton.selectable = false);
if (this.onFile != null) this.onFile(filePath, file);
+47 -1
View File
@@ -8,8 +8,10 @@ import haxe.Json;
import haxe.io.Bytes;
import haxe.io.Path;
import openfl.display.BitmapData;
#if sys
import sys.FileSystem;
import sys.io.File;
#end
import animate.FlxAnimateJson;
using StringTools;
@@ -60,13 +62,19 @@ class UIImageExplorer extends UIFileExplorer {
directoryBG.members.push(directoryTextBox);
if (image != null) {
#if sys
var fullImagePath:String = '${Path.normalize(Sys.getCwd())}/${Paths.image(image)}'.replace('/', '\\');
#else
var fullImagePath = null;
#end
var noExt = Path.withoutExtension(fullImagePath);
#if sys
if (FileSystem.exists('$noExt\\spritemap1.png'))
fullImagePath = '$noExt\\spritemap1.png';
if (FileSystem.exists(fullImagePath))
loadFile(fullImagePath);
#end
}
allowDirectories = CoolUtil.isMapEmpty(imageFiles); __firstLoad = false;
@@ -100,7 +108,11 @@ class UIImageExplorer extends UIFileExplorer {
// CHECK ATLAS
if(allowAtlases && ANIMATE_ATLAS_REGEX.match(fileName)) {
// check if directory has the other files
#if sys
files = FileSystem.readDirectory(directoryPath);
#else
files = null;
#end
var hasAnimationJson:Bool = false;
var hasSpritemapJson:Bool = false;
for(file in files) {
@@ -116,12 +128,20 @@ class UIImageExplorer extends UIFileExplorer {
}
} else if(allowAtlases && Path.extension(fileName) == "png") {
// check if the spritemap json files point to the image
#if sys
files = FileSystem.readDirectory(directoryPath);
#else
files = null;
#end
var hasAnimationJson:Bool = false;
var foundSpritemapJson:Bool = false;
for(file in files) {
if(SPRITEMAP_JSON_REGEX.match(file)) {
#if sys
var content:String = CoolUtil.removeBOM(File.getContent(Path.join([directoryPath, file])));
#else
var content = null;
#end
var json:SpritemapJson = Json.parse(content);
if(json.meta.image.toLowerCase() == fileName.toLowerCase())
foundSpritemapJson = true;
@@ -146,7 +166,11 @@ class UIImageExplorer extends UIFileExplorer {
spritemaps.sort(Reflect.compare);
for(spritemap in spritemaps) {
#if sys
var content:String = CoolUtil.removeBOM(File.getContent(Path.join([directoryPath, spritemap])));
#else
var content = null;
#end
var json:SpritemapJson = Json.parse(content);
var imageToFind:String = json.meta.image.toLowerCase();
for(file in files) {
@@ -168,17 +192,23 @@ class UIImageExplorer extends UIFileExplorer {
if (isAtlas) {
var dataPath:String = '$directoryPath/Animation.json'.replace('/', '\\');
#if sys
if (FileSystem.exists(dataPath)) {
var dataPathFile:String = File.getContent(dataPath);
animationList = CoolUtil.getAnimsListFromAtlas(cast haxe.Json.parse(dataPathFile));
imageFiles.set(Path.withoutDirectory(dataPath), dataPathFile);
}
#end
} else {
var dataPathExt:String = CoolUtil.imageHasFrameData(imagePath);
var dataPath:String = Path.withExtension(imagePath, dataPathExt);
#if sys
var dataPathFile:String = !isAtlas && dataPathExt != null ? File.getContent(dataPath) : null;
#else
var dataPathFile = null;
#end
if (dataPathExt != null) {
frames = CoolUtil.loadFramesFromData(dataPathFile, dataPathExt);
animationList = CoolUtil.getAnimsListFromFrames(frames, dataPathExt);
@@ -200,14 +230,24 @@ class UIImageExplorer extends UIFileExplorer {
for(spritemap in spritemapImages) {
var spritemapPath:String = Path.join([directoryPath, spritemap]);
#if sys
var info = FileSystem.stat(spritemapPath);
#else
var info = null;
#end
size += info.size;
spritemapPath = spritemapPath.replace('/', '\\');
#if sys
imageFiles.set(Path.withoutDirectory(spritemapPath), sys.io.File.getBytes(spritemapPath));
#end
}
#if sys
file = cast sys.io.File.getBytes(filePath = spritemapPath);
#else
file = null;
#end
image = BitmapData.fromBytes(file).crop();
} else {
@@ -321,12 +361,16 @@ class UIImageExplorer extends UIFileExplorer {
var alreadlyExistingFiles:Array<String> = [];
for (name => file in imageData.imageFiles)
#if sys
if (FileSystem.exists('$directory/$name'))
alreadlyExistingFiles.push('$directory/$name');
#end
function deleteExistingFiles() {
#if sys
for (file in alreadlyExistingFiles)
FileSystem.deleteFile(file);
#end
alreadlyExistingFiles = [];
}
@@ -346,8 +390,10 @@ class UIImageExplorer extends UIFileExplorer {
label: TU.translate("uiImageExplorer.override"),
color: 0xFFFF0000,
onClick: (_) -> {
#if sys
deleteExistingFiles();
acuttalySaveFiles();
#end
}
},
{
+8 -3
View File
@@ -106,7 +106,7 @@ class UIState extends MusicBeatState {
}
if (FlxG.mouse.justPressed) {
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_CLICK_SOUND));
playEditorSound(Flags.DEFAULT_EDITOR_CLICK_SOUND);
}
if (FlxG.mouse.justReleased)
@@ -147,7 +147,7 @@ class UIState extends MusicBeatState {
}
public function closeCurrentContextMenu() {
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_WINDOWCLOSE_SOUND));
playEditorSound(Flags.DEFAULT_EDITOR_WINDOWCLOSE_SOUND);
if(curContextMenu != null) {
curContextMenu.close();
curContextMenu = null;
@@ -155,7 +155,7 @@ class UIState extends MusicBeatState {
}
public function openContextMenu(options:Array<UIContextMenuOption>, ?callback:UIContextMenuCallback, ?x:Float, ?y:Float, ?w:Int) {
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_WINDOWAPPEAR_SOUND));
playEditorSound(Flags.DEFAULT_EDITOR_WINDOWAPPEAR_SOUND);
var state = FlxG.state;
while(state.subState != null && !(state._requestSubStateReset && state._requestedSubState == null))
state = state.subState;
@@ -191,4 +191,9 @@ class UIState extends MusicBeatState {
resolutionAware = true;
FlxG.scaleMode = uiScaleMode;
}
public static function playEditorSound(path:String) {
if (!Options.editorSFX) return;
FlxG.sound.play(Paths.sound(path));
}
}
+1 -1
View File
@@ -86,7 +86,7 @@ class UISubstateWindow extends MusicBeatSubstate {
}
public override function destroy() {
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_WINDOWCLOSE_SOUND));
UIState.playEditorSound(Flags.DEFAULT_EDITOR_WINDOWCLOSE_SOUND);
super.destroy();
for(e in camShaders)
e.removeShader(blurShader);
+116 -16
View File
@@ -60,7 +60,7 @@ class UITextBox extends UISliceSprite implements IUIFocusable {
var selected = selectable && focused;
if (autoAlpha) {
if(selectable) {
if (selectable) {
alpha = label.alpha = 1;
} else {
alpha = label.alpha = 0.4;
@@ -68,19 +68,19 @@ class UITextBox extends UISliceSprite implements IUIFocusable {
}
var off = multiline ? 4 : ((bHeight - label.height) / 2);
label.follow(this, label.autoSize ? (bWidth-label.textField.width)/2 : 4, off);
label.follow(this, label.autoSize ? (bWidth - label.textField.width) / 2 : 4, off);
framesOffset = (selected ? 18 : (hovered ? 9 : 0));
@:privateAccess {
if (selected) {
__wasFocused = true;
caretSpr.alpha = (FlxG.game.ticks % 666) >= 333 ? 1 : 0;
var curPos = switch(position) {
var curPos = switch (position) {
case 0:
FlxPoint.get(0, 0);
default:
if (position >= label.text.length) {
label.textField.__getCharBoundaries(label.text.length-1, cacheRect);
label.textField.__getCharBoundaries(label.text.length - 1, cacheRect);
FlxPoint.get(cacheRect.x + cacheRect.width, cacheRect.y);
} else {
label.textField.__getCharBoundaries(position, cacheRect);
@@ -100,60 +100,160 @@ class UITextBox extends UISliceSprite implements IUIFocusable {
}
}
private static var seperators:Array<String> = [
" ", "\n", "\t", "\r", "-", "_", "=", "+", "/", "\\", "|", ",", ".", ";", ":", "!", "?", "@", "#", "$", "%", "^", "&", "*", "(", ")", "[", "]", "{",
"}",
];
public inline static function isSeperator(char:String):Bool
return seperators.contains(char);
public inline static function findWholeWord(text:String, pos:Int, ?isDelete:Bool = false):Null<Array<Int>> {
if (text.length == 0)
return null;
var start = pos;
var end = pos;
while (!isDelete && start > 0 && !isSeperator(text.charAt(start - 1)))
start--;
while (end < text.length && !isSeperator(text.charAt(end)))
end++;
if (end == pos && isSeperator(text.charAt(end - 1)))
start--;
return [start, end];
}
public function onKeyDown(e:KeyCode, modifier:KeyModifier) {
switch(e) {
switch (e) {
case RETURN:
focused = false;
if (onChange != null) onChange(label.text);
if (onChange != null)
onChange(label.text);
case LEFT:
if (modifier.ctrlKey) {
if (position == 0)
return;
var wordBounds = findWholeWord(label.text, position);
if (wordBounds != null) {
position = position == wordBounds[0] ? wordBounds[0] - 1 : wordBounds[0];
} else {
position = 0;
}
return;
}
changeSelection(-1);
case RIGHT:
if (modifier.ctrlKey) {
if (position == label.text.length)
return;
var wordBounds = findWholeWord(label.text, position);
if (wordBounds != null) {
position = position == wordBounds[1] ? wordBounds[1] + 1 : wordBounds[1];
} else {
position = label.text.length;
}
return;
}
changeSelection(1);
case BACKSPACE:
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_TEXTREMOVE_SOUND));
UIState.playEditorSound(Flags.DEFAULT_EDITOR_TEXTREMOVE_SOUND);
if (modifier.ctrlKey) {
var wordBounds = findWholeWord(label.text, position);
if (wordBounds != null) {
label.text = label.text.substr(0, wordBounds[0]) + label.text.substr(wordBounds[1]);
position = wordBounds[0];
}
return;
}
if (position > 0) {
label.text = label.text.substr(0, position-1) + label.text.substr(position);
label.text = label.text.substr(0, position - 1) + label.text.substr(position);
changeSelection(-1);
}
case DELETE:
UIState.playEditorSound(Flags.DEFAULT_EDITOR_TEXTREMOVE_SOUND);
if (modifier.ctrlKey) {
var wordBounds = findWholeWord(label.text, position, true);
if (wordBounds != null) {
label.text = label.text.substr(0, wordBounds[0]) + label.text.substr(wordBounds[1]);
position = wordBounds[0];
}
return;
}
if (position < label.text.length) {
label.text = label.text.substr(0, position) + label.text.substr(position + 1);
}
case HOME:
position = 0;
case END:
position = label.text.length;
case V:
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_TEXTTYPE_SOUND));
UIState.playEditorSound(Flags.DEFAULT_EDITOR_PASTE_SOUND);
// Hey lj here, fixed copying because before we checked if the modifier was left or right ctrl
// but somehow it gave a int outside of the KeyModifier's range :sob:
// apparently there is a boolean that just checks for you. yw :D
// if we are not holding ctrl, ignore
if (!modifier.ctrlKey) return;
if (!modifier.ctrlKey)
return;
// we pasting
var data:String = Clipboard.generalClipboard.getData(TEXT_FORMAT);
if (data != null) onTextInput(data);
if (data != null)
onTextInput(data);
case C:
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_TEXTTYPE_SOUND));
UIState.playEditorSound(Flags.DEFAULT_EDITOR_COPY_SOUND);
// if we are not holding ctrl, ignore
if (!modifier.ctrlKey) return;
if (!modifier.ctrlKey)
return;
// copying
Clipboard.generalClipboard.setData(TEXT_FORMAT, label.text);
case X:
UIState.playEditorSound(Flags.DEFAULT_EDITOR_CUT_SOUND);
// if we are not holding ctrl, ignore
if (!modifier.ctrlKey)
return;
// cutting
Clipboard.generalClipboard.setData(TEXT_FORMAT, label.text);
position = 0;
label.text = "";
default:
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_TEXTTYPE_SOUND));
if (modifier.ctrlKey || modifier.altKey || modifier.shiftKey)
return;
UIState.playEditorSound(Flags.DEFAULT_EDITOR_TEXTTYPE_SOUND);
}
}
public function changeSelection(change:Int) {
position = FlxMath.wrap(position + change, 0, label.text.length);
position = Std.int(FlxMath.bound(position + change, 0, label.text.length));
}
public function onKeyUp(e:KeyCode, modifier:KeyModifier) {}
public function onTextInput(text:String):Void {
label.text = label.text.substr(0, position) + text + label.text.substr(position);
position += text.length;
}
// untested, but this should be a fix for if the text wont type
public function onTextEdit(text:String, start:Int, end:Int):Void {
label.text = label.text.substr(0, position) + text + label.text.substr(position);
position += text.length;
}
}
}
+1 -1
View File
@@ -15,7 +15,7 @@ class UIWindow extends UISliceSprite {
content = new FlxTypedGroup<FlxBasic>();
members.push(content);
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_WINDOWAPPEAR_SOUND));
UIState.playEditorSound(Flags.DEFAULT_EDITOR_WINDOWAPPEAR_SOUND);
}
public override function update(elapsed:Float) {
+8 -6
View File
@@ -1,6 +1,8 @@
package funkin.game;
#if sys
import sys.FileSystem;
#end
import flixel.util.FlxSpriteUtil;
import openfl.display.Graphics;
import flixel.util.typeLimit.OneOfTwo;
@@ -165,7 +167,7 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
public function tryDance() {
var event = new CancellableEvent();
script.call("onTryDance", [event]);
scripts.call("onTryDance", [event]);
if (event.cancelled)
return;
@@ -197,7 +199,7 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
}
public override function measureHit(curMeasure:Int)
script.call("measureHit", [curMeasure]);
scripts.call("measureHit", [curMeasure]);
public override function stepHit(curStep:Int)
scripts.call("stepHit", [curStep]);
@@ -254,13 +256,13 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
public var ghostDraw:Bool = false;
public override function draw() {
var e = EventManager.get(DrawEvent).recycle();
script.call("draw", [e]);
scripts.call("draw", [e]);
preDraw();
super.draw();
postDraw();
script.call("postDraw", [e]);
scripts.call("postDraw", [e]);
}
public var singAnims = ["singLEFT", "singDOWN", "singUP", "singRIGHT"];
@@ -273,7 +275,7 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
public function playSingAnim(direction:Int, suffix:String = "", Context:PlayAnimContext = SING, ?Force:Null<Bool> = null, Reversed:Bool = false, Frame:Int = 0)
{
var event = EventManager.get(DirectionAnimEvent).recycle(getSingAnim(direction, suffix), direction, suffix, Context, Reversed, Frame, Force);
script.call("onPlaySingAnim", [event]);
scripts.call("onPlaySingAnim", [event]);
if (event.cancelled) return;
playSingAnimUnsafe(event.direction, hasAnimation(event.animName) ? event.suffix : "", event.context, event.force, event.reversed, event.frame);
@@ -281,7 +283,7 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset
public function playSingAnimUnsafe(direction:Int, suffix:String = "", Context:PlayAnimContext = SING, Force:Bool = true, Reversed:Bool = false, Frame:Int = 0) {
var event = EventManager.get(DirectionAnimEvent).recycle(getSingAnim(direction, suffix), direction, suffix, Context, Reversed, Frame, Force);
script.call("playSingAnimUnsafe", [event]);
scripts.call("playSingAnimUnsafe", [event]);
if (event.cancelled) return;
playAnim(event.animName, event.force, event.context, event.reversed, event.frame);
+81 -46
View File
@@ -1,6 +1,7 @@
package funkin.game;
import flixel.math.FlxPoint;
import flixel.math.FlxAngle;
import flixel.math.FlxRect;
import funkin.backend.chart.ChartData;
import funkin.backend.scripting.events.note.NoteCreationEvent;
@@ -125,8 +126,7 @@ class Note extends FlxSprite
static var DEFAULT_FIELDS:Array<String> = ["time", "id", "type", "sLen"];
public function new(strumLine:StrumLine, noteData:ChartNote, sustain:Bool = false, sustainLength:Float = 0, sustainOffset:Float = 0, ?prev:Note)
{
public function new(strumLine:StrumLine, noteData:ChartNote, sustain:Bool = false, sustainLength:Float = 0, sustainOffset:Float = 0, ?prev:Note) {
super();
moves = false;
@@ -240,60 +240,85 @@ class Note extends FlxSprite
*/
public var strumRelativePos:Bool = true;
override function drawComplex(camera:FlxCamera) {
var downscrollCam = (camera is HudCamera ? ({var _:HudCamera=cast camera;_;}).downscroll : false);
if (updateFlipY) flipY = (isSustainNote && flipSustain) && (downscrollCam != (__strum != null && __strum.getScrollSpeed(this) < 0));
if (downscrollCam) {
frameOffset.y += __notePosFrameOffset.y * 2;
super.drawComplex(camera);
frameOffset.y -= __notePosFrameOffset.y * 2;
} else
super.drawComplex(camera);
}
static var __notePosFrameOffset:FlxPoint = new FlxPoint();
static var __posPoint:FlxPoint = new FlxPoint();
@:dox(hide) static var __lastAngle:Float = Math.NaN;
@:dox(hide) static var __lastAngleSin:Float = 0;
@:dox(hide) static var __lastAngleCos:Float = 0;
@:dox(hide) static var __lastStrumW:Float = Math.NaN;
@:dox(hide) static var __lastStrumH:Float = Math.NaN;
@:dox(hide) static var __lastStrumHalfW:Float = 0;
@:dox(hide) static var __lastStrumHalfH:Float = 0;
override function draw() {
@:privateAccess var oldDefaultCameras = FlxCamera._defaultCameras;
@:privateAccess if (__strumCameras != null) FlxCamera._defaultCameras = __strumCameras;
var negativeScroll = isSustainNote && nextSustain != null && lastScrollSpeed < 0;
if (negativeScroll) offset.y *= -1;
var negativeScroll = isSustainNote && strumRelativePos && lastScrollSpeed < 0;
if (negativeScroll) y -= height;
if (__strum != null && strumRelativePos) {
var pos = __posPoint.set(x, y);
final originalX = x;
final originalY = y;
setPosition(__strum.x, __strum.y);
if (__noteAngle != __lastAngle) {
__lastAngle = __noteAngle;
final result = FlxMath.fastSinCos((__noteAngle + 90) * FlxAngle.TO_RAD);
__lastAngleSin = result.sin;
__lastAngleCos = result.cos;
}
__notePosFrameOffset.set(pos.x / scale.x, pos.y / scale.y);
frameOffset.x -= __notePosFrameOffset.x;
frameOffset.y -= __notePosFrameOffset.y;
this.frameOffsetAngle = __noteAngle;
if (__strum.width != __lastStrumW || __strum.height != __lastStrumH) {
__lastStrumW = __strum.width;
__lastStrumH = __strum.height;
__lastStrumHalfW = __strum.width * 0.5;
__lastStrumHalfH = __strum.height * 0.5;
}
x = -origin.x + offset.x + (originalY * __lastAngleCos) + __strum.x + __lastStrumHalfW;
y = -origin.y + offset.y + (originalY * __lastAngleSin) + __strum.y + __lastStrumHalfH;
super.draw();
this.frameOffsetAngle = 0;
frameOffset.x += __notePosFrameOffset.x;
frameOffset.y += __notePosFrameOffset.y;
setPosition(pos.x, pos.y);
//pos.put();
x = originalX;
y = originalY;
} else {
__notePosFrameOffset.set(0, 0);
super.draw();
}
if (negativeScroll) y += height;
if (negativeScroll) offset.y *= -1;
@:privateAccess FlxCamera._defaultCameras = oldDefaultCameras;
}
// The * 0.5 is so that it's easier to hit them too late, instead of too early
public var earlyPressWindow:Float = 0.5;
public var latePressWindow:Float = 1;
var __lastDownscrollCam:Bool = false;
var __lastX:Float = 0;
@:noCompletion @:dox(hide) override function isOnScreen(?camera:FlxCamera):Bool {
var downscrollCam = (Std.isOfType(camera, HudCamera) ? cast(camera, HudCamera).downscroll : false);
if (downscrollCam == __lastDownscrollCam)
return super.isOnScreen(camera);
else
__lastX = x;
if (updateFlipY) flipY = (isSustainNote && flipSustain) && (downscrollCam != (__strum != null && __strum.getScrollSpeed(this) < 0));
if (downscrollCam && __strum != null) {
x = -x + 2 * (__strum.x - origin.x + offset.x) + __strum.width;
}
final isOnScreen = super.isOnScreen(camera);
return isOnScreen;
}
override function drawComplex(camera:FlxCamera):Void {
super.drawComplex(camera);
if (__lastDownscrollCam) {
__lastDownscrollCam = false;
x = __lastX;
}
}
public function isOnScreenOriginal(?camera:FlxCamera):Bool {
return super.isOnScreen(camera);
}
public var earlyPressWindow:Float = Flags.EARLY_HIT_WINDOW_RANGE;
public var latePressWindow:Float = Flags.LATE_HIT_WINDOW_RANGE;
public function updateSustain(strum:Strum) {
var scrollSpeed = strum.getScrollSpeed(this);
@@ -312,17 +337,27 @@ class Note extends FlxSprite
public function updateSustainClip() if (wasGoodHit && !noSustainClip) {
var t = CoolUtil.bound((Conductor.songPosition - strumTime) / height * 0.45 * lastScrollSpeed, 0, 1);
var rect = clipRect == null ? FlxRect.get() : clipRect;
clipRect = rect.set(0, frameHeight * t, frameWidth, frameHeight * (1 - t));
@:bypassAccessor {
if (clipRect == null) clipRect = FlxRect.get();
clipRect.set(0, frameHeight * t, frameWidth, frameHeight * (1 - t));
}
@:privateAccess {
if (frame != null && _frame != null)
_frame = frame.clipTo(clipRect, _frame);
}
}
@:noCompletion
override function set_clipRect(rect:FlxRect):FlxRect
{
clipRect = rect;
override function set_clipRect(rect:FlxRect):FlxRect {
@:bypassAccessor clipRect = rect;
if (frames != null)
frame = frames.frames[animation.frameIndex];
@:privateAccess if (frame != null) {
if (rect != null && _frame != null)
_frame = frame.clipTo(rect, _frame);
else if (_frame != null)
_frame = frame.copyTo(_frame);
dirty = true;
}
return rect;
}
+147 -66
View File
@@ -623,6 +623,18 @@ class PlayState extends MusicBeatState
curRating = event.rating;
}
private function onRatingChange(rating:Rating) {
if (!hits.exists(rating.name))
hits.set(rating.name, 0);
if (Options.ghostTapping) {
comboBreaks = false;
for (rating in ratingManager.ratingData)
comboBreaks = comboBreaks || rating.breaksCombo;
} else
comboBreaks = true;
}
private inline function set_health(v:Float)
return health = FlxMath.bound(v, 0, maxHealth);
private inline function set_maxHealth(v:Float) {
@@ -643,7 +655,7 @@ class PlayState extends MusicBeatState
public inline function callOnCharacters(func:String, ?parameters:Array<Dynamic>) {
if(strumLines != null) strumLines.forEachAlive(function (strLine:StrumLine) {
if (strLine.characters != null) for (character in strLine.characters)
if (character != null) character.script.call(func, parameters);
if (character != null) character.scripts.call(func, parameters);
});
}
@@ -690,6 +702,14 @@ class PlayState extends MusicBeatState
detailsText = isStoryMode ? ("Story Mode: " + storyWeek.name) : "Freeplay";
for (rating in [for (i in ratingManager.ratingData) i.name]) hits.set(rating, 0); // Ensure all keys exist as to prevent null errors.
if (Options.ghostTapping) {
comboBreaks = false;
for (rating in ratingManager.ratingData)
comboBreaks = comboBreaks || rating.breaksCombo;
} else
comboBreaks = true;
ratingManager.onRatingAdded.add(onRatingChange);
ratingManager.onRatingRemoved.add(onRatingChange);
// Checks if cutscene files exists
var cutscenePath = Paths.script('songs/${SONG.meta.name}/cutscene');
@@ -923,7 +943,7 @@ class PlayState extends MusicBeatState
// Make icons appear in the correct spot during cutscenes
healthBar.update(0);
if (updateIconPositions != null)
if (updateIconPositions != null && Flags.ICONS_AUTOPOSITION)
updateIconPositions();
__updateNote_event = EventManager.get(NoteUpdateEvent);
@@ -1667,7 +1687,7 @@ class PlayState extends MusicBeatState
if (strLine.characters != null) // Alt anim Idle
for (character in strLine.characters) {
if (character == null) continue;
character.idleSuffix = event.params[1] ? "-alt" : "";
character.idleSuffix = event.params[1] ? strLine.defaultAnimSuffix : "";
}
}
case "Play Animation":
@@ -1676,6 +1696,8 @@ class PlayState extends MusicBeatState
if (char != null && char.hasAnim(event.params[1])) char.playAnim(event.params[1], event.params[2], event.params[3] == "NONE" ? null : event.params[3]);
case "Unknown": // nothing
}
gameAndCharsEvent("onPostEvent", e);
}
@:dox(hide)
@@ -1944,9 +1966,9 @@ class PlayState extends MusicBeatState
var event:NoteHitEvent;
if (strumLine != null && !strumLine.cpu)
event = EventManager.get(NoteHitEvent).recycle(false, !note.isSustainNote, !note.isSustainNote, null, defaultDisplayRating, defaultDisplayCombo, note, strumLine.characters, true, note.noteType, note.animSuffix.getDefault(note.strumID < strumLine.members.length ? strumLine.members[note.strumID].animSuffix : strumLine.animSuffix), "game/score/", "", note.strumID, rating.score, note.isSustainNote ? null : rating.accuracy, 0.023, rating.name, Options.splashesEnabled && !note.isSustainNote && rating.splash, 0.5, true, 0.7, true, true, iconP1);
event = EventManager.get(NoteHitEvent).recycle(rating.breaksCombo, !note.isSustainNote, !note.isSustainNote, null, null, null, note, strumLine.characters, true, note.noteType, note.animSuffix.getDefault(note.strumID < strumLine.members.length ? strumLine.members[note.strumID].animSuffix : strumLine.animSuffix), null, null, note.strumID, rating.score, note.isSustainNote ? null : rating.accuracy, rating.health, rating.name, Options.splashesEnabled && !note.isSustainNote && rating.splash, null, null, null, null, null, iconP1);
else
event = EventManager.get(NoteHitEvent).recycle(false, false, false, null, defaultDisplayRating, defaultDisplayCombo, note, strumLine.characters, false, note.noteType, note.animSuffix.getDefault(note.strumID < strumLine.members.length ? strumLine.members[note.strumID].animSuffix : strumLine.animSuffix), "game/score/", "", note.strumID, 0, null, 0, rating.name, false, 0.5, true, 0.7, true, true, iconP2);
event = EventManager.get(NoteHitEvent).recycle(rating.breaksCombo, false, false, null, null, null, note, strumLine.characters, false, note.noteType, note.animSuffix.getDefault(note.strumID < strumLine.members.length ? strumLine.members[note.strumID].animSuffix : strumLine.animSuffix), null, null, note.strumID, 0, null, 0, rating.name, false, null, null, null, null, true, iconP2);
event.deleteNote = !note.isSustainNote; // work around, to allow sustain notes to be deleted
event = scripts.event(strumLine != null && !strumLine.cpu ? "onPlayerHit" : "onDadHit", event);
strumLine.onHit.dispatch(event);
@@ -1962,13 +1984,17 @@ class PlayState extends MusicBeatState
totalAccuracyAmount += event.accuracy;
updateRating();
}
if (event.countAsCombo) combo++;
if (event.misses) {
combo = 0;
misses++;
} else if (event.countAsCombo)
combo++;
if (event.showRating || (event.showRating == null && event.player))
{
displayCombo(event);
if (event.displayRating)
displayRating(event.rating, event);
displayRatingNumbers(event);
displayRating(event.rating, event);
ratingNum += 1;
}
if (event.player) hits[rating.name] += 1;
@@ -2005,81 +2031,136 @@ class PlayState extends MusicBeatState
gameAndCharsEvent("onPostNoteHit", event);
}
public function displayRating(myRating:String, ?evt:NoteHitEvent = null):Void {
var hasEvent = evt != null;
var pre:String = hasEvent ? evt.ratingPrefix : "";
var suf:String = hasEvent ? evt.ratingSuffix : "";
public function displayRating(myRating:String, ?evt:NoteHitEvent):Void
{
var event:RatingsShowEvent = EventManager.get(RatingsShowEvent).recycle(comboGroup.recycleLoop(FlxSprite), null, null, null, null, 0.7, true, "game/score/", "", 550, FlxPoint.get(FlxG.random.int(0, 10), FlxG.random.int(140, 175)), 0.2, (Conductor.crochet * 0.001), true, false, false, true, null, FlxPoint.get(comboGroup.x + -40, comboGroup.y + -60), true, myRating, null);
gameAndCharsEvent("onRatingsShown", event);
var rating:FlxSprite = comboGroup.recycleLoop(FlxSprite);
CoolUtil.resetSprite(rating, comboGroup.x + -40, comboGroup.y + -60);
rating.loadAnimatedGraphic(Paths.image('${pre}${myRating}${suf}'));
rating.acceleration.y = 550;
rating.velocity.y -= FlxG.random.int(140, 175);
rating.velocity.x -= FlxG.random.int(0, 10);
if (hasEvent) {
rating.scale.set(evt.ratingScale, evt.ratingScale);
rating.antialiasing = evt.ratingAntialiasing;
if (event.cancelled || !event.displayRating) { // TODO: Find a better way for this?
event.ratingSprite.kill();
return;
}
var hasEvent:Bool = evt != null;
var pre:String = hasEvent && evt.ratingPrefix != null ? evt.ratingPrefix : event.ratingPrefix;
var suf:String = hasEvent && evt.ratingSuffix != null ? evt.ratingSuffix : event.ratingSuffix;
var ratingScale:Float = hasEvent && evt.ratingScale != null ? evt.ratingScale : event.ratingScale;
var rating:FlxSprite = event.ratingSprite.loadAnimatedGraphic(Paths.image('${pre}${event.rating}${suf}'));
if (event.resetSprite) {
CoolUtil.resetSprite(rating, event.position.x, event.position.y);
}
rating.acceleration.y = event.acceleration;
rating.velocity.y -= event.velocity.y;
rating.velocity.x -= event.velocity.x;
rating.scale.set(ratingScale, ratingScale);
rating.antialiasing = hasEvent && evt.ratingAntialiasing != null ? evt.ratingAntialiasing : event.ratingAntialiasing;
rating.updateHitbox();
FlxTween.tween(rating, {alpha: 0}, 0.2, {
startDelay: Conductor.crochet * 0.001,
onComplete: function(tween:FlxTween) {
rating.kill();
}
});
if (event.playTween) {
event.tween = FlxTween.tween(rating, {alpha: 0}, event.tweenDuration, {
startDelay: event.startDelay,
onComplete: function(tween:FlxTween) {
rating.kill();
}
});
}
gameAndCharsEvent("onPostRatingsShown", event);
event.velocity.put();
event.position.put();
}
public function displayCombo(?evt:NoteHitEvent = null):Void {
public function displayCombo(?evt:NoteHitEvent):Void {
if (minDigitDisplay >= 0 && (combo == 0 || combo >= minDigitDisplay)) {
var hasEvent = evt != null;
var pre:String = hasEvent ? evt.ratingPrefix : "";
var suf:String = hasEvent ? evt.ratingSuffix : "";
var event:RatingsShowEvent = EventManager.get(RatingsShowEvent).recycle(null, null, comboGroup.recycleLoop(FlxSprite), null, null, 0.7, true, "game/score/", "", 600, FlxPoint.get(FlxG.random.int(0, 10), 150), 0.2, (Conductor.crochet * 0.001), false, false, evt != null && evt.displayCombo != null ? evt.displayCombo : defaultDisplayCombo, true, null, FlxPoint.get(comboGroup.x, comboGroup.y), true, null, null);
gameAndCharsEvent("onRatingsShown", event);
if (evt.displayCombo) {
var comboSpr:FlxSprite = comboGroup.recycleLoop(FlxSprite).loadAnimatedGraphic(Paths.image('${pre}combo${suf}'));
CoolUtil.resetSprite(comboSpr, comboGroup.x, comboGroup.y);
comboSpr.acceleration.y = 600;
comboSpr.velocity.y -= 150;
comboSpr.velocity.x += FlxG.random.int(1, 10);
if (hasEvent) {
comboSpr.scale.set(evt.ratingScale, evt.ratingScale);
comboSpr.antialiasing = evt.ratingAntialiasing;
}
comboSpr.updateHitbox();
FlxTween.tween(comboSpr, {alpha: 0}, 0.2, {
onComplete: function(tween:FlxTween)
{
comboSpr.kill();
},
startDelay: Conductor.crochet * 0.001
});
if (event.cancelled || !event.displayCombo) { // TODO: Find a better way for this?
event.comboSprite.kill();
return;
}
var hasEvent:Bool = evt != null;
var pre:String = hasEvent && evt.ratingPrefix != null ? evt.ratingPrefix : event.ratingPrefix;
var suf:String = hasEvent && evt.ratingSuffix != null ? evt.ratingSuffix : event.ratingSuffix;
var ratingScale:Float = hasEvent && evt.ratingScale != null ? evt.ratingScale : event.ratingScale;
var comboSpr:FlxSprite = event.comboSprite.loadAnimatedGraphic(Paths.image('${pre}combo${suf}'));
if (event.resetSprite) {
CoolUtil.resetSprite(comboSpr, event.position.x, event.position.y);
}
comboSpr.acceleration.y = event.acceleration;
comboSpr.velocity.y -= event.velocity.y;
comboSpr.velocity.x += event.velocity.x;
comboSpr.scale.set(ratingScale, ratingScale);
comboSpr.antialiasing = hasEvent && evt.ratingAntialiasing != null ? evt.ratingAntialiasing : event.ratingAntialiasing;
comboSpr.updateHitbox();
if (event.playTween) {
event.tween = FlxTween.tween(comboSpr, {alpha: 0}, event.tweenDuration, {
onComplete: function(tween:FlxTween) {
comboSpr.kill();
},
startDelay: event.startDelay
});
}
gameAndCharsEvent("onPostRatingsShown", event);
event.velocity.put();
event.position.put();
}
}
public function displayRatingNumbers(?evt:NoteHitEvent):Void {
if (minDigitDisplay >= 0 && (combo == 0 || combo >= minDigitDisplay)) {
var separatedScore:String = Std.string(combo).addZeros(3);
for (i in 0...separatedScore.length)
{
var numScore:FlxSprite = comboGroup.recycleLoop(FlxSprite).loadAnimatedGraphic(Paths.image('${pre}num${separatedScore.charAt(i)}${suf}'));
CoolUtil.resetSprite(numScore, comboGroup.x + (43 * i) - 90, comboGroup.y + 80);
if (hasEvent) {
numScore.antialiasing = evt.numAntialiasing;
numScore.scale.set(evt.numScale, evt.numScale);
var event:RatingsShowEvent = EventManager.get(RatingsShowEvent).recycle(null, comboGroup.recycleLoop(FlxSprite), null, 0.5, true, null, null, "game/score/", "", FlxG.random.int(200, 300), FlxPoint.get(FlxG.random.float(-5, 5), FlxG.random.int(140, 160)), 0.2, (Conductor.crochet * 0.002), false, true, false, true, 43, FlxPoint.get(comboGroup.x - 90, comboGroup.y + 80), true, null, null);
gameAndCharsEvent("onRatingsShown", event);
if (event.cancelled || !event.displayNumbers) { // TODO: Find a better way for this?
event.numberSprite.kill();
continue;
}
var hasEvent:Bool = evt != null;
var pre:String = hasEvent && evt.ratingPrefix != null ? evt.ratingPrefix : event.ratingPrefix;
var suf:String = hasEvent && evt.ratingSuffix != null ? evt.ratingSuffix : event.ratingSuffix;
var numScale:Float = hasEvent && evt.numScale != null ? evt.numScale : event.numScale;
var numScore:FlxSprite = event.numberSprite.loadAnimatedGraphic(Paths.image('${pre}num${separatedScore.charAt(i)}${suf}'));
event.position.x += event.numSpacing * i;
if (event.resetSprite) {
CoolUtil.resetSprite(numScore, event.position.x, event.position.y);
}
numScore.antialiasing = hasEvent && evt.numAntialiasing != null ? evt.numAntialiasing : event.numAntialiasing;
numScore.scale.set(numScale, numScale);
numScore.updateHitbox();
numScore.acceleration.y = FlxG.random.int(200, 300);
numScore.velocity.y -= FlxG.random.int(140, 160);
numScore.velocity.x = FlxG.random.float(-5, 5);
numScore.acceleration.y = event.acceleration;
numScore.velocity.y -= event.velocity.y;
numScore.velocity.x = event.velocity.x;
FlxTween.tween(numScore, {alpha: 0}, 0.2, {
onComplete: function(tween:FlxTween)
{
numScore.kill();
},
startDelay: Conductor.crochet * 0.002
});
if (event.playTween) {
event.tween = FlxTween.tween(numScore, {alpha: 0}, event.tweenDuration, {
onComplete: function(tween:FlxTween) {
numScore.kill();
},
startDelay: event.startDelay
});
}
gameAndCharsEvent("onPostRatingsShown", event);
event.velocity.put();
event.position.put();
}
}
}
+71 -34
View File
@@ -1,6 +1,8 @@
package funkin.game;
import flixel.math.FlxPoint;
import flixel.math.FlxAngle;
import flixel.util.typeLimit.OneOfTwo;
import funkin.backend.system.Conductor;
class Strum extends FlxSprite {
@@ -51,8 +53,27 @@ class Strum extends FlxSprite {
public var updateNotesPosY:Bool = true;
public var extraCopyFields(default, set):Array<String> = [];
private inline function set_extraCopyFields(val:Array<String>)
return extraCopyFields = val == null ? [] : val;
@:noCompletion public var __cachedCopyFields:Array<Array<OneOfTwo<String, Int>>> = null;
private function set_extraCopyFields(val:Array<String>) {
extraCopyFields = val == null ? [] : val;
__cachedCopyFields = null;
return extraCopyFields;
}
private inline function __initCachedCopyFields() {
if (__cachedCopyFields != null) return;
__cachedCopyFields = [for (field in extraCopyFields) CoolUtil.parsePropertyString(field)];
}
private inline function __applyCopyFields(daNote:Note) {
for (i in 0...extraCopyFields.length) {
final parsed = __cachedCopyFields[i];
final fromProp = CoolUtil.parseProperty(this, parsed);
final toProp = CoolUtil.parseProperty(daNote, parsed);
toProp.setValue(fromProp.getValue());
}
}
/**
* Whenever the strum is pressed.
@@ -128,13 +149,28 @@ class Strum extends FlxSprite {
}
public override function draw() {
lastDrawCameras = cameras.copy();
if (cameras.length == 1) {
if (lastDrawCameras.length != 1 || lastDrawCameras[0] != cameras[0]) {
lastDrawCameras = [cameras[0]];
}
} else {
lastDrawCameras = cameras.copy();
}
super.draw();
}
@:noCompletion public static inline final PIX180:Float = 565.4866776461628; // 180 * Math.PI
@:noCompletion public static final N_WIDTHDIV2:Float = Note.swagWidth / 2; // DEPRECATED
static var __lastStrumW:Float = Math.NaN;
static var __lastStrumH:Float = Math.NaN;
static var __lastStrumHalfW:Float = 0;
static var __lastStrumHalfH:Float = 0;
static var __noteOffset:FlxPoint = FlxPoint.get();
static var __lastNoteAngle:Float = Math.NaN;
static var __lastAngleCos:Float = 0;
static var __lastAngleSin:Float = 0;
/**
* Updates the position of a note.
* @param daNote The note
@@ -152,8 +188,10 @@ class Strum extends FlxSprite {
}
updateNotePos(daNote);
for (field in extraCopyFields)
CoolUtil.cloneProperty(daNote, field, this); // TODO: make this cached to reduce the reflection calls - Neo
if (extraCopyFields.length > 0) {
__initCachedCopyFields();
__applyCopyFields(daNote);
}
}
private inline function updateNotePos(daNote:Note) {
@@ -162,41 +200,40 @@ class Strum extends FlxSprite {
if (shouldX || shouldY) {
if (daNote.strumRelativePos) {
if (shouldX) daNote.x = (this.width - daNote.width) * 0.5;
if (shouldX) daNote.x = 0;
if (shouldY) {
daNote.y = (daNote.strumTime - Conductor.songPosition) * (0.45 * getScrollSpeed(daNote));
if (daNote.isSustainNote) daNote.y += height * 0.5;
daNote.y = ((daNote.strumTime - Conductor.songPosition) * 0.45 * getScrollSpeed(daNote));
if (daNote.isSustainNote) daNote.y += daNote.height * 0.5;
}
} else {
var offset = FlxPoint.get(0, (Conductor.songPosition - daNote.strumTime) * (0.45 * getScrollSpeed(daNote)));
var realOffset = FlxPoint.get(0, 0);
if (daNote.isSustainNote) offset.y -= height * 0.5;
if (Std.int(daNote.__noteAngle % 360) != 0) {
var noteAngle = FlxMath.fastSinCos(daNote.__noteAngle / PIX180);
var noteAngleCos = noteAngle.cos;
var noteAngleSin = noteAngle.sin;
var aOffset:FlxPoint = FlxPoint.get(
(daNote.origin.x / daNote.scale.x) - daNote.offset.x,
(daNote.origin.y / daNote.scale.y) - daNote.offset.y
);
realOffset.x = -aOffset.x + (noteAngleCos * (offset.x + aOffset.x)) + (noteAngleSin * (offset.y + aOffset.y));
realOffset.y = -aOffset.y + (noteAngleSin * (offset.x + aOffset.x)) + (noteAngleCos * (offset.y + aOffset.y));
aOffset.put();
} else {
realOffset.x = offset.x;
realOffset.y = offset.y;
if (width != __lastStrumW || height != __lastStrumH) {
__lastStrumW = width;
__lastStrumH = height;
__lastStrumHalfW = width * 0.5;
__lastStrumHalfH = height * 0.5;
}
realOffset.y *= -1;
if (shouldX) daNote.x = x + realOffset.x;
if (shouldY) daNote.y = y + realOffset.y;
if (daNote.__noteAngle != __lastNoteAngle) {
__lastNoteAngle = daNote.__noteAngle;
final result = FlxMath.fastSinCos((__lastNoteAngle + 90) * FlxAngle.TO_RAD);
__lastAngleCos = result.cos;
__lastAngleSin = result.sin;
}
offset.put();
realOffset.put();
final speed = getScrollSpeed(daNote);
final distance = (daNote.strumTime - Conductor.songPosition) * 0.45 * speed;
__noteOffset.set(__lastAngleCos * distance, __lastAngleSin * distance);
__noteOffset.x += -daNote.origin.x + daNote.offset.x;
__noteOffset.y += -daNote.origin.y + daNote.offset.y;
if (daNote.isSustainNote) {
final m = (daNote.height * 0.5 * (speed < 0 ? -1 : 1));
__noteOffset.x += __lastAngleCos * m;
__noteOffset.y += __lastAngleSin * m;
}
__noteOffset.x += x + __lastStrumHalfW;
__noteOffset.y += y + __lastStrumHalfH;
if (shouldX) daNote.x = __noteOffset.x;
if (shouldY) daNote.y = __noteOffset.y;
}
}
}
+49 -28
View File
@@ -74,6 +74,10 @@ class StrumLine extends FlxTypedGroup<Strum> {
* Which animation suffix on characters that should be used when hitting notes.
*/
public var animSuffix(default, set):String = "";
/**
* The current animation suffix the strumline should use. Note that setting this will only take effect upon the alt. animation being reset.
*/
public var defaultAnimSuffix:String = Flags.DEFAULT_ALT_ANIM_SUFFIX;
/**
* TODO: Write documentation about this being a variable that can help when making multi key
*/
@@ -258,21 +262,28 @@ class StrumLine extends FlxTypedGroup<Strum> {
}
}
function __inputProcessJustPressed(note:Note) {
if (__justPressed[note.strumID] && !note.isSustainNote && !note.wasGoodHit && note.canBeHit) {
var cur = __notePerStrum[note.strumID];
var songPos = __updateNote_songPos;
var noteDist = Math.abs(note.strumTime - songPos);
var curDist = cur != null ? Math.abs(cur.strumTime - songPos) : 999999;
var notePenalty = note.avoid ? 1 : 0;
var curPenalty = (cur != null && cur.avoid) ? 1 : 0;
if (cur == null
|| notePenalty < curPenalty
|| (notePenalty == curPenalty && noteDist < curDist))
__notePerStrum[note.strumID] = note;
var strumID = note.strumID;
if (!__justPressed[strumID] || note.isSustainNote || note.wasGoodHit || !note.canBeHit) return;
var cur = __notePerStrum[strumID];
if (cur == null) {
__notePerStrum[strumID] = note;
return;
}
var songPos = __updateNote_songPos;
var noteDist = Math.abs(note.strumTime - songPos);
var noteShouldAvoid = note.avoid;
var curShouldAvoid = cur.avoid;
if (!noteShouldAvoid && curShouldAvoid) {
__notePerStrum[strumID] = note;
return;
}
var curDist = Math.abs(cur.strumTime - songPos);
if (noteShouldAvoid == curShouldAvoid && noteDist < curDist)
__notePerStrum[strumID] = note;
}
/**
@@ -284,13 +295,18 @@ class StrumLine extends FlxTypedGroup<Strum> {
if (cpu) return;
if (__pressed.length != members.length) {
__pressed.resize(members.length);
__justPressed.resize(members.length);
__justReleased.resize(members.length);
final membersLength = members.length;
if (__pressed.length != membersLength) {
__pressed.resize(membersLength);
__justPressed.resize(membersLength);
__justReleased.resize(membersLength);
}
for (i in 0...members.length) {
if (__notePerStrum.length != membersLength)
__notePerStrum = cast new haxe.ds.Vector(membersLength); // [for(_ in 0...members.length) null];
for (i in 0...membersLength) {
__pressed[i] = members[i].__getPressed(this);
__justPressed[i] = members[i].__getJustPressed(this);
__justReleased[i] = members[i].__getJustReleased(this);
@@ -304,18 +320,20 @@ class StrumLine extends FlxTypedGroup<Strum> {
__justPressed = CoolUtil.getDefault(event.justPressed, []);
__justReleased = CoolUtil.getDefault(event.justReleased, []);
__notePerStrum = cast new haxe.ds.Vector(members.length); // [for(_ in 0...members.length) null];
if (__pressed.contains(true)) {
if (__justPressed.contains(true)) {
notes.forEachAlive(__inputProcessJustPressed);
if (!ghostTapping) for (k => pr in __justPressed) if (pr && __notePerStrum[k] == null)
PlayState.instance.noteMiss(this, null, k, ID); // FUCK YOU
for (k => pr in __justPressed)
{
var note = __notePerStrum[k];
for (e in __notePerStrum)
if (e != null)
PlayState.instance.goodNoteHit(this, e);
if (note != null) {
PlayState.instance.goodNoteHit(this, note);
__notePerStrum[k] = null;
} else if (pr && !ghostTapping)
PlayState.instance.noteMiss(this, null, k, ID);
}
}
for (c in characters)
@@ -433,6 +451,9 @@ class StrumLine extends FlxTypedGroup<Strum> {
public static inline function calculateStartingXPos(hudXRatio:Float, scale:Float, spacing:Float, keyCount:Int) {
return (FlxG.width * hudXRatio) - ((Note.swagWidth * scale * ((keyCount/2)-0.5) * spacing) + Note.swagWidth * 0.5 * scale);
}
public static inline function calculateStartingXPosFromInitialWidth(hudXRatio:Float, scale:Float, spacing:Float, keyCount:Int) {
return (FlxG.initialWidth * hudXRatio) - ((Note.swagWidth * scale * ((keyCount/2)-0.5) * spacing) + Note.swagWidth * 0.5 * scale);
}
/**
* SETTERS & GETTERS
@@ -451,11 +472,11 @@ class StrumLine extends FlxTypedGroup<Strum> {
return animSuffix = str;
}
private inline function set_altAnim(b:Bool):Bool {
animSuffix = b ? "-alt" : "";
animSuffix = b ? defaultAnimSuffix : "";
return b;
}
private inline function get_altAnim():Bool {
return animSuffix == "-alt";
return animSuffix == defaultAnimSuffix;
}
#end
}
@@ -103,14 +103,9 @@ class VideoCutscene extends Cutscene {
FlxTween.tween(loadingBackdrop, {alpha: 1}, 0.5, {ease: FlxEase.sineInOut});
Main.execAsync(function() {
if (localPath.startsWith("[ZIP]")) {
// ZIP PATH: EXPORT
// TODO: this but better and more ram friendly
localPath = './.temp/video-${curVideo++}.mp4';
File.saveBytes(localPath, Assets.getBytes(path));
}
if (video.load(localPath)) new FlxTimer().start(0.001, function(_) { mutex.acquire(); onReady(); mutex.release(); });
if (video.load(localPath)) new FlxTimer().start(0.001, function(_) {
mutex.acquire(); onReady(); mutex.release();
});
else { mutex.acquire(); close(); mutex.release(); }
});
@@ -182,6 +177,7 @@ class VideoCutscene extends Cutscene {
}
public inline function onReady() {
trace("VideoCutscene: Ready");
FlxTween.cancelTweensOf(loadingBackdrop);
FlxTween.tween(loadingBackdrop, {alpha: 0}, 0.7, {ease: FlxEase.sineInOut, onComplete: function(_) {
loadingBackdrop.destroy();
+25 -4
View File
@@ -2,6 +2,7 @@ package funkin.game.scoring;
import funkin.game.scoring.*;
import funkin.game.scoring.HitWindowData.WindowPreset;
import flixel.util.FlxSignal;
import haxe.ds.StringMap;
@@ -10,6 +11,9 @@ import haxe.ds.StringMap;
*/
class RatingManager
{
public var onRatingAdded:FlxTypedSignal<Rating->Void> = new FlxTypedSignal();
public var onRatingRemoved:FlxTypedSignal<Rating->Void> = new FlxTypedSignal();
public var hitWindows:StringMap<Float>;
public var ratingData:Array<Rating> = [];
public var lastHitWindow:Float = -1;
@@ -50,9 +54,9 @@ class RatingManager
}
addRating({name: "sick", window: getWindow("sick"), accuracy: 1, score: 300, splash: true});
addRating({name: "good", window: getWindow("good"), accuracy: 0.75, score: 200});
addRating({name: "bad", window: getWindow("bad"), accuracy: 0.45, score: 100});
addRating({name: "shit", window: getWindow("shit"), accuracy: 0.25, score: 50});
addRating({name: "good", window: getWindow("good"), accuracy: 0.75, score: 200, health: 0.015});
addRating({name: "bad", window: getWindow("bad"), accuracy: 0.45, score: 100, health: 0});
addRating({name: "shit", window: getWindow("shit"), accuracy: 0.25, score: 50, health: -0.05, breaksCombo: Flags.SHITS_BREAK_COMBO});
}
public function addRating(data:Dynamic)
@@ -71,7 +75,9 @@ class RatingManager
window: window,
accuracy: data.accuracy != null ? data.accuracy : 1,
score: data.score != null ? data.score : 0,
health: data.health != null ? data.health : 0.023,
splash: data.splash == true,
breaksCombo: data.breaksCombo == true,
hittable: data.hittable != null ? data.hittable : true
};
@@ -86,13 +92,18 @@ class RatingManager
ratingData.push(newRating);
ratingData.sort((a, b) -> Reflect.compare(a.window, b.window));
onRatingAdded.dispatch(newRating);
}
public function removeRating(name:String):Void
{
if (name == null) return;
name = name.toLowerCase();
ratingData = ratingData.filter(r -> r.name != name);
var toRemove = ratingData.filter(r -> r.name == name);
for (rating in toRemove) {
ratingData.remove(rating);
onRatingRemoved.dispatch(rating);
}
}
public function getHitWindow(name:String):Float
@@ -126,11 +137,21 @@ final class Rating
*/
public var score:Int = 0;
/**
* Amount of health given when earning this rating.
*/
public var health:Float = 0.023;
/**
* If this rating was hit, a note splash will appear.
*/
@:optional public var splash:Bool = false;
/**
* Whether the rating will break your combo or not.
*/
@:optional public var breaksCombo:Bool = false;
/**
* Whether the rating is hittable or not.
*/
+3 -3
View File
@@ -261,7 +261,7 @@ class FreeplayState extends MusicBeatState
if (!disableAutoPlay && !songInstPlaying && (autoplayElapsed > timeUntilAutoplay)) {
if (curPlayingInst != (curPlayingInst = Paths.inst(curSong.name, curDifficulties[curDifficulty], curSong.instSuffix))) {
var streamed = false;
if (Options.streamedMusic) {
/*if (Options.streamedMusic) {
var sound = Assets.getMusic(curPlayingInst, true, false);
streamed = sound != null;
@@ -269,7 +269,7 @@ class FreeplayState extends MusicBeatState
FlxG.sound.playMusic(sound, 0);
Conductor.changeBPM(curSong.bpm, curSong.beatsPerMeasure, curSong.stepsPerBeat);
}
}
}*/
if (!streamed) {
var huh:Void->Void = function() {
@@ -290,7 +290,7 @@ class FreeplayState extends MusicBeatState
}
}
songInstPlaying = true;
if (disableAsyncLoading && !Options.streamedMusic) dontPlaySongThisFrame = true;
if (disableAsyncLoading/* && !Options.streamedMusic*/) dontPlaySongThisFrame = true;
}
#end
+1 -1
View File
@@ -34,7 +34,7 @@ class ModSwitchMenu extends MusicBeatSubstate {
alphabets = new FlxTypedGroup<Alphabet>();
for(mod in mods) {
var a = new Alphabet(0, 0, mod == null ? TU.translate("mods.disableMods") : mod, "bold");
var a = new Alphabet(0, 0, mod == null ? TU.translate("mods.disableMods") : Path.withoutExtension(mod), "bold");
if(mod == ModsFolder.currentModFolder)
a.color = FlxColor.LIME;
a.isMenuItem = true;
+7 -7
View File
@@ -36,7 +36,7 @@ final class AlphabetComponent {
public var cos:Float;
public var scaleX:Float;
public var scaleY:Float;
public var flipX:Bool;
public var flipY:Bool;
@@ -281,7 +281,7 @@ class Alphabet extends FlxSprite {
var anim = getLetterAnim(letter, data, __component, i);
//if (cantrace)
//trace(anim.name + " | " + __component.anim + " | " + frames.frames[anim.frames[0]]);
advance = (Math.isNaN(advance)) ? getAdvance(letter, anim, data) : advance;
advance = (Math.isNaN(advance) && i >= data.startIndex) ? getAdvance(letter, anim, data) : advance;
if (anim == null || __renderData.alpha <= 0.0)
continue;
@@ -551,7 +551,7 @@ class Alphabet extends FlxSprite {
angle: angle,
cos: angleCos,
sin: angleSin,
flipX: node.get("flipX") == "true",
flipY: node.get("flipY") == "true",
@@ -607,7 +607,7 @@ class Alphabet extends FlxSprite {
angle: angle,
cos: angleCos,
sin: angleSin,
flipX: xFlip,
flipY: yFlip,
@@ -630,7 +630,7 @@ class Alphabet extends FlxSprite {
angle: angle,
cos: angleCos,
sin: angleSin,
flipX: xFlip,
flipY: yFlip,
@@ -664,7 +664,7 @@ class Alphabet extends FlxSprite {
var xScale:Float = Std.parseFloat(node.get("scaleX")).getDefaultFloat(1.0);
var yScale:Float = Std.parseFloat(node.get("scaleY")).getDefaultFloat(1.0);
var advance:Float = Std.parseFloat(node.get("advance"));
var xFlip = node.get("flipX") == "true";
var yFlip = node.get("flipY") == "true";
@@ -685,7 +685,7 @@ class Alphabet extends FlxSprite {
angle: angle,
cos: angleCos,
sin: angleSin,
flipX: xFlip,
flipY: yFlip,
+3 -2
View File
@@ -43,8 +43,8 @@ class Options
public static var framerate:Int = 120;
public static var gpuOnlyBitmaps:Bool = #if (mac || web) false #else true #end; // causes issues on mac and web
public static var language = "en"; // default to english, Flags.DEFAULT_LANGUAGE should not modify this
public static var streamedMusic:Bool = true;
public static var streamedVocals:Bool = true;
public static var streamedMusic:Bool = false;
public static var streamedVocals:Bool = false;
public static var quality:Int = 1;
public static var allowConfigWarning:Bool = true;
#if MODCHARTING_FEATURES
@@ -84,6 +84,7 @@ class Options
public static var charterMetronomeEnabled:Bool = false;
public static var charterShowSections:Bool = true;
public static var charterShowBeats:Bool = true;
public static var charterShowCameraHighlights:Bool = true;
public static var charterEnablePlaytestScripts:Bool = true;
public static var charterRainbowWaveforms:Bool = false;
public static var charterLowDetailWaveforms:Bool = false;
+3 -3
View File
@@ -202,8 +202,8 @@ class OptionsMenu extends TreeMenu {
Logs.warn("A radio option requires an \"id\" for option saving.");
continue;
}
var v:Dynamic = Std.parseFloat(node.att.value);
options.push(new RadioButton(screen, name, desc, node.att.id, v != null ? v : node.att.value, null, FlxG.save.data, node.has.forId ? node.att.forId : null));
var f = Std.parseFloat(node.att.value);
options.push(new RadioButton(screen, name, desc, node.att.id, Math.isNaN(f) ? node.att.value : f, null, FlxG.save.data, node.has.forId ? node.att.forId : null));
case 'slider':
if (!node.has.id) {
Logs.warn("A slider option requires an \"id\" for option saving.");
@@ -223,4 +223,4 @@ class OptionsMenu extends TreeMenu {
return options;
}
}
}
@@ -4,7 +4,7 @@ import flixel.util.FlxTimer;
import funkin.backend.system.Conductor;
class GameplayOptions extends TreeMenuScreen {
var __metronome = FlxG.sound.load(Paths.sound('editors/charter/metronome'));
var __metronome = FlxG.sound.load(Paths.sound(Flags.DEFAULT_CHARTER_METRONOME_SOUND));
var offsetSetting:NumOption;
public function new() {
@@ -67,7 +67,14 @@ class AdvancedGameplayOptions extends TreeMenuScreen {
public function new() {
super('optionsMenu.advanced', 'optionsTree.gameplay.advanced-desc', 'GameplayOptions.Advanced.');
add(new Checkbox(getNameID('streamedMusic'), getDescID('streamedMusic'), 'streamedMusic'));
add(new Checkbox(getNameID('streamedVocals'), getDescID('streamedVocals'), 'streamedVocals'));
// Remove locked whenever this PR from FunkinCrew is merged.
// https://github.com/FunkinCrew/lime/pull/57
for (checkbox in [
new Checkbox(getNameID('streamedMusic'), getDescID('streamedMusic'), 'streamedMusic'),
new Checkbox(getNameID('streamedVocals'), getDescID('streamedVocals'), 'streamedVocals')
]) {
checkbox.locked = true;
add(checkbox);
}
}
}
+6 -1
View File
@@ -35,4 +35,9 @@ class Config {
public static final DISALLOW_ABSTRACT_AND_ENUM = [
"funkin.backend.scripting.events.sprite.PlayAnimContext", // Error: expected member name or ';' after declaration specifiers, Due to define macro from math.h
];
}
@:unreflective
public static final IMPORT_BLACKLIST:Array<String> = [
];
}
@@ -15,6 +15,7 @@ class HTML5AudioSource
private var parent:AudioSource;
private var playing:Bool;
private var position:Vector4;
private var loopTime:Float;
public function new(parent:AudioSource)
{
+5 -4
View File
@@ -247,14 +247,15 @@ class Assets
var sound = cache.getSound(id);
if (isValidSound(sound)) return sound;
}
#if (lime_vorbis && lime > "7.9.0" && !macro)
/*#if (lime_vorbis && lime > "7.9.0" && !macro)
if (Options.streamedMusic) {
var path = getPath(id);
var bytes = getBytes(id);
if (bytes == null) return null;
// TODO: What if it is a WAV or non-Vorbis file?
var vorbisFile = VorbisFile.fromFile(path);
var vorbisFile = VorbisFile.fromBytes(bytes);
if (vorbisFile != null) return Sound.fromAudioBuffer(AudioBuffer.fromVorbisFile(vorbisFile));
}
#end
#end*/
return if (staticFallback) getSound(id, useCache); else null;
}