Compare commits

...
132 Commits
Author SHA1 Message Date
HundrecandGitHub e62c5a223c Merge to sync commit histories
Build and Upload nightly game builds / gather-changes (push) Canceled after 0s
Build and Upload nightly game builds / gather-tags (push) Canceled after 0s
Build and Upload nightly game builds / build-game-on-host (macos, macos) (push) Canceled after 0s
Build and Upload nightly game builds / build-game-on-host (windows, windows) (push) Canceled after 0s
Build and Upload nightly game builds / docker-image (push) Canceled after 0s
Build and Upload nightly game builds / build-game-in-container (html5) (push) Canceled after 0s
Build and Upload nightly game builds / build-game-in-container (linux) (push) Canceled after 0s
2025-09-19 01:33:29 +08:00
Hundrec 5fc7491121 Merge changes from main 2025-09-19 01:28:01 +08:00
Hundrec 9598455118 KoloInDaKrib 2025-09-19 01:27:42 +08:00
Nightfall-16andHundrec 0e4b9753a0 contribute I guess 2025-09-19 01:26:12 +08:00
Nightfall-16andHundrec 2bbe3d788d crash 2025-09-19 01:26:12 +08:00
Nightfall-16andHundrec cd731d7e40 work 2025-09-19 01:26:12 +08:00
Nightfall-16andHundrec e11e3d51c2 please work 2025-09-19 01:26:12 +08:00
Nightfall-16andHundrec b19b78df5d please work now 2025-09-19 01:26:12 +08:00
Hundrec 1a061cbbcc 0.7.5 changelog and version bump
Set the date for the 0.7.5 patch
2025-09-18 16:24:18 +08:00
MoonDroid 9908c8be32 Fix html5 builds from crashing and not compiling 2025-09-18 14:55:52 +07:00
MoonDroid 8522deab64 Adjust warning message 2025-09-18 14:55:52 +07:00
MoonDroid 7e93521666 Add custom context warnings for environment errors
woo!! im proud of this !!
2025-09-18 14:55:52 +07:00
MoonDroidandMihai Alexandru 5aeb3a28fb Add AnsiUtil for ease of ansi use
Co-Authored-By: Mihai Alexandru <77043862+MAJigsaw77@users.noreply.github.com>
2025-09-18 14:55:52 +07:00
MAJigsaw77andMoonDroid b5db93726a Rewrite Environment macro. 2025-09-18 14:55:52 +07:00
EliteMasterEricandHundrec 20de8db41b Update to the latest commit on the hxcpp-debugger fork. 2025-09-18 14:43:08 +08:00
Hundrec 5d73d65fdc Fix Eggnog Erect cutscene crash 2025-09-18 03:03:31 +08:00
8c750d368d Update openfl and flixel for ASTC fixes.
Co-authored-by: MoonDroid <81515012+moondroidcoder@users.noreply.github.com>
2025-09-18 01:44:25 +07:00
Hundrec 8d328ace84 Fix HTML5 compile error 2025-09-18 01:03:00 +08:00
MAJigsaw77andHundrec 9d825f00aa Use Sys.println instead of trace` for Admob events. 2025-09-16 22:56:11 +08:00
Honton129andAbnormal c7b68507a8 Typo Fix on PlayStatePlaylist
that was bothering me
2025-09-15 05:08:37 -05:00
Hyper_andHundrec 970ccf2f4a fix: Chart loading fixes
* Prevents charter from deleting `default` variation when there's more than one variation available.
* Fixes song variation list not being saved into `default` metadata file.
* Fixes chart data not being assigned its respective variation when loading from FNFC.
2025-09-15 03:12:43 +08:00
PixelandHundrec 479036a400 Add ability to pause during Credits scroll 2025-09-15 03:09:33 +08:00
KoloandHundrec 80d7ace714 remove the stupid chud gibberish 2025-09-15 03:06:44 +08:00
Hundrec a7d5736e14 Adjust Stage Editor exit confirm prompt 2025-09-15 03:02:15 +08:00
LasercarandHundrec b31be1043c chart name in backup filename 2025-09-15 03:02:14 +08:00
Hundrec 6c4d27a842 Nudge Chart Editor menubar for new debug display 2025-09-15 02:55:39 +08:00
Hundrec fdab26ce56 Fix Pico not shooting in Stress upon restarting 2025-09-15 02:26:22 +08:00
VioletSnowLeopardandHundrec 8ead5eeb08 change save shortcuts to CTRL+S and CTRL+SHIFT+S 2025-09-15 02:18:53 +08:00
Hundrec c4ebd3175d Remove beatHit() flash from Freeplay backing card
It's a cool idea, but too distracting, especially for Pico's blue backing card
2025-09-15 02:13:45 +08:00
e593bc8a61 Fix Debug Display memory leak on Linux
Co-authored-by: Linus Torvalds <torvalds@linux-foundation.org>
2025-09-15 02:03:55 +08:00
Hyper_andHundrec cb554bd5f7 the one line fix 2025-09-15 01:59:37 +08:00
Hyper_andHundrec dd54acfbd1 this pointless condition was pissing me off 2025-09-15 01:59:37 +08:00
LasercarandHundrec 6783f04139 No Chudding allowed 2025-09-15 01:56:03 +08:00
PurSnakeandHundrec 11eb4060d0 duh 2025-09-15 01:56:03 +08:00
VioletSnowLeopardandHundrec b5357acf98 Fallback to default if metadata voices don't exist 2025-09-15 01:50:09 +08:00
Hyper_andHundrec f85f2f03d9 another one liner god damn it
Fixes a crash when there's some previously focused text fields,
and the user tries to exit via the confirmation dialog.
Before a state switch HaxeUI tries to reset the tooltip manager and
remove all components, which in this case causes it to refocus a text field,
which tries to access a destroyed camera to retrieve the bounds.
Doing this ensures there's no text fields for it to do that.
2025-09-15 01:41:38 +08:00
VioletSnowLeopardandHundrec 146a6cde68 Fix typo - pause *button* 2025-09-14 18:23:41 +08:00
AbnormalandHundrec 65105df9ac disable smoothing on the baked bitmaps 2025-09-14 17:31:01 +08:00
AbnormalandHundrec 9106ef215a add the lighting to pico's rank animation (had to bake the blendmode lol) 2025-09-14 17:31:01 +08:00
Megalives6193andHundrec 38426bc5d7 [GITHUB] Fix Logo In README.md 2025-09-14 04:35:22 +08:00
KoloandHundrec 79e8b5d71e fix: Prevent crashing by replacing the state constructor if needed 2025-09-14 03:14:38 +08:00
MoonDroidandHundrec 6e954cfac0 Update astc compression rate for results excellent bf 2025-09-13 21:56:48 +08:00
MAJigsaw77andAbnormal 041d33b2d3 Fixes to the audio config stuff. 2025-09-12 16:26:15 -05:00
MoonDroid 1b5b0925ed chore: Rename the define and remove redundant block 2025-09-11 03:39:25 +07:00
Karim AkraandMoonDroid cd0bc85b76 added FEATURE_LOG_TRACE_FILE for logging all the logs into a text file 2025-09-11 03:39:25 +07:00
MAJigsaw77andHundrec d4929164b6 Update hxvlc. 2025-09-10 12:35:35 +08:00
Abnormal 7bc06861b5 add an extra line 2025-09-09 12:39:19 -05:00
Abnormal 3ccbed2cbb clarify dev build in outdated libraries 2025-09-09 12:39:19 -05:00
MAJigsaw77andAbnormal 29c0b0f8eb Add support for dev libs in project.hxp haxelibs check. 2025-09-09 12:39:19 -05:00
AbnormalandEric 7ff0753472 scripted freeplay and popups shot dead on august 32nd, 2031 with no traces found 2025-09-09 05:35:35 -04:00
AbnormalandEric 19328abb9e and then there were 7 2025-09-09 05:35:35 -04:00
AbnormalandEric feb0863358 and then there were 8 2025-09-09 05:35:35 -04:00
AbnormalandEric 43e51e9a4a erm......sorry modders you're gonna have to do the work yourself lol 2025-09-09 05:35:35 -04:00
AbnormalandEric 3b610d8910 add a TON of modding stuff 2025-09-09 05:35:35 -04:00
PurSnakeandHundrec 63ec749c7a Disable Freeplay intro when coming from stickers 2025-09-09 03:53:51 +08:00
Hyper_andHundrec c8e6a78154 don't check libraries on display 2025-09-09 03:45:48 +08:00
Az989YTandHundrec 89b2b7fcc3 change event dispatching to match comment order 2025-09-09 03:14:55 +08:00
kade-githubandAbnormal 1b1c781426 bump polymod 2025-09-08 14:11:42 -05:00
kade-githubandEric 5c8a888b77 remove includes that dont matter 2025-09-08 15:00:28 -04:00
kade-githubandEric 257576b4d3 Allow for scripted state reloading 2025-09-08 15:00:28 -04:00
sector-aandEric 56f0a7780d Debug Display HTML5 Fixes 2025-09-08 13:02:48 -04:00
MAJigsaw77andEric faa47e8f3e More tweaks 2025-09-08 13:02:48 -04:00
MAJigsaw77andEric b99d599bce Fix iOS compile. 2025-09-08 13:02:48 -04:00
92bc856f5b Fix macos compile.
Co-Authored-By: MoonDroid <81515012+moondroidcoder@users.noreply.github.com>
2025-09-08 13:02:48 -04:00
sector-aandEric 0490ca8145 Debug display bg opacity preference 2025-09-08 13:02:48 -04:00
sector-aandEric c9b8f21f61 Debug Display modes!!! 2025-09-08 13:02:48 -04:00
MAJigsaw77andEric 60caa80a84 One more graph adjustment 2025-09-08 13:02:48 -04:00
MAJigsaw77andEric 477f85a71e Adjust graphs width. 2025-09-08 13:02:48 -04:00
MAJigsaw77andEric b5aed5fb4f Adjusted the graphs. 2025-09-08 13:02:48 -04:00
MAJigsaw77andEric 7d714ecfeb Add MemoryUtil for Apple external. 2025-09-08 13:02:48 -04:00
MAJigsaw77andEric f3ff132e90 Adjust the external API a little. 2025-09-08 13:02:48 -04:00
MAJigsaw77andEric 1912155b32 Organized some small stuff 2025-09-08 13:02:48 -04:00
sector-aandEric daa87c8c58 The new debug display is mostly done! 2025-09-08 13:02:48 -04:00
sector-aandEric ad87832662 The graph is done 2025-09-08 13:02:48 -04:00
MAJigsaw77andEric b8b0a5bf73 Added task mem and adjusted the counter. 2025-09-08 13:02:48 -04:00
sector-aandEric 5a8b681e2f FPS Graph (unfinished) 2025-09-08 13:02:48 -04:00
sector-aandEric 5a6a3f5599 Small tweaks 2025-09-08 13:02:48 -04:00
sector-aandEric bab422029b FunkinCounter (unfinished) 2025-09-08 13:02:48 -04:00
Hyper_andHundrec 88fc5f9ed6 refactor: use null-safe list functions in Medals and Leaderboards 2025-09-08 22:45:32 +08:00
Hundrec f3c7cfd799 Hide the A-Bot visualizer when restarting 2025-09-08 17:10:38 +08:00
Hundrec 231439d808 Adjust haxelib warning message 2025-09-08 16:51:37 +08:00
AbnormalandHundrec d6f80730a5 check the library versions and warn the user of any outdated ones 2025-09-08 16:51:37 +08:00
AwesomezPro99andHundrec 9cfee0398a Fix stress pico bricks 2025-09-07 14:29:39 +08:00
Hyper_andAbnormal aa5956a30b Force texture to be uploaded to the GPU
Fixes an issue where multi-sparrow characters cause a stutter when the alternative sprites are displayed
2025-09-06 22:15:28 -05:00
Hundrec b9d68957e0 Cache Stress Tankmen into FunkinMemory 2025-09-07 02:37:57 +08:00
kade-githubandHundrec 1b9f5c62fb Fix a bug with Lag Adjustment strumline 2025-09-07 02:23:59 +08:00
kade-githubandHundrec a0a57ca94d Make strumlineNotes and noteHoldCovers public 2025-09-07 02:23:59 +08:00
sector-aandHundrec 271451b9df Update assets 2025-09-07 01:39:13 +08:00
sector-aandHundrec 21458f26f2 Fix dialogue proceeding when pressing the pause button 2025-09-07 01:39:13 +08:00
kade-github c1b4d4d53e space... STATE_CREATE 2025-09-06 09:41:24 -07:00
kade-github 28d71dc2fe add onStateCreate to Module 2025-09-06 09:41:24 -07:00
AwesomezPro99andHundrec a10bbf60ac Fix menu music fade in 2025-09-06 16:37:08 +08:00
KoloandHundrec 4e572b171f cancel login if ongoing 2025-09-06 16:15:36 +08:00
Hundrec cc768c83aa Remove extra line 2025-09-06 16:09:06 +08:00
LasercarandHundrec 035f3770d5 No more audio playback while dragging playheads
This commit specifically fixes the note preview playhead.
2025-09-06 16:09:06 +08:00
LasercarandHundrec 2d95754d49 Chart editor audio playback while dragging playheads fix
Two down, one to go
2025-09-06 16:09:06 +08:00
VioletSnowLeopardandHundrec 47b1cee6d5 don't open welcome dialog multiple times 2025-09-06 15:59:38 +08:00
lemz1andHundrec 4ebd00bc06 haxe ui focus check 2025-09-06 15:40:00 +08:00
AwesomezPro99andHundrec 66554ec4b6 Fix backing card beat hit check 2025-09-06 15:22:24 +08:00
MAJigsaw77andHundrec bfc798c629 Update extension-admob. 2025-09-06 15:11:46 +08:00
EliteMasterEric 6b334b941a Add some import aliases so scripts don't break. 2025-09-05 17:16:54 -04:00
sector-aandEric b06090a477 Move CharacterData to data/character 2025-09-05 17:16:54 -04:00
VioletSnowLeopardandHundrec ca5fbb65e5 add *the* checkbox to crash template 2025-09-06 05:08:28 +08:00
KoloandEric c2305f32f0 this bug seems kinda old why has nobody reported it before 2025-09-05 15:51:55 -04:00
VioletSnowLeopardandEric 747446e2e4 rank anim only plays when changing difficulties 2025-09-05 14:15:56 -04:00
MrMaderaandEric 250d2188b9 Add ability to press back to exit Character Select 2025-09-05 13:23:51 -04:00
kade-github 61c710fe20 some fixes and goodness 2025-09-05 09:45:56 -07:00
KoloandKade 0fd6f5b3b7 that fucking ghost that i hate 2025-09-05 09:45:56 -07:00
HundrecandAbnormal 8a204c606a Bring back Freeplay rank slamming animation
Another Freeplay fix caused by the same commit!

Manually reverts changes from https://github.com/FunkinCrew/Funkin/commit/f7a48bcb3b9ab0f5393552b828b8687c6ae6926c where Cam converted the FreeplayRank class into a FlxSpriteGroup.
2025-09-04 14:04:28 -05:00
MAJigsaw77andMoonDroid 4401ea0c9a Organize ios project folder a little. 2025-09-04 13:32:06 +07:00
sector-aandMoonDroid 12ace54b2b Update assets 2025-09-04 13:15:58 +07:00
f15cd73dff revert changes to cache in flxpartialsound
Co-authored-by: MoonDroid <81515012+moondroidcoder@users.noreply.github.com>
2025-09-04 10:49:40 +07:00
b39d7bb5a0 Don't log info if the command is clean (or display)
Co-authored-by: MoonDroid <81515012+moondroidcoder@users.noreply.github.com>
2025-09-04 10:12:20 +07:00
f1066a2b0d Fix Freeplay capsule offsetting once and for all
And nudge the Gold Perfect sparkles to where they used to be in 0.5.3

Both of these issues were introduced in 0.6.0 and have finally been uprooted. Huzzah!

Co-Authored-By: Lasercar <64717068+lasercar@users.noreply.github.com>
Co-Authored-By: Kolo <67389779+koloindacrib@users.noreply.github.com>
Co-Authored-By: Abnormal <86753001+AbnormalPoof@users.noreply.github.com>
Co-Authored-By: Hyper_ <40342021+nothyper-474@users.noreply.github.com>
Co-Authored-By: Linus Torvalds <1024025+torvalds@users.noreply.github.com>
2025-09-03 18:44:24 -05:00
MAJigsaw77andHundrec b04b555b38 Intialize hxvlc asynchronously. 2025-09-04 04:28:02 +08:00
MoonDroid f349eedc72 fix: Re-add backbutton transitioning 2025-09-03 18:28:19 +07:00
VioletSnowLeopardandMoonDroid ee2f3c32eb Fixes for the recent update 2025-09-03 18:28:19 +07:00
LasercarandMoonDroid 0f99a1c7c2 Fix for https://github.com/FunkinCrew/Funkin/issues/3627 2025-09-03 18:28:19 +07:00
LasercarandMoonDroid 95aba09cd6 Fix target variation
Also no need to do instrumental stuff if not random
2025-09-03 18:28:19 +07:00
VioletSnowLeopardandMoonDroid 811b9e3343 don't freeze game if it fails to find target song/difficulty 2025-09-03 18:28:19 +07:00
VioletSnowLeopardandMoonDroid 37b9823b36 minor fix
Don't allow letter sort input while the instrumental selector is up
2025-09-03 18:28:19 +07:00
VioletSnowLeopardandMoonDroid 555684afda choose if you want the instrumental to be random 2025-09-03 18:28:19 +07:00
VioletSnowLeopardandMoonDroid 0d5c6ac963 selecting random can choose a random available instrumental 2025-09-03 18:28:19 +07:00
Cameron TaylorandMoonDroid db67a6928c ignroe VarTypeHint since it's a bit incompatible with final 2025-09-03 17:35:24 +07:00
Cameron TaylorandMoonDroid 9c34ab5f9c checkstyle fixins for AnsiTrace.hx 2025-09-03 17:35:24 +07:00
Cameron TaylorandMoonDroid f09074b79d add checkstyle regex to ignore long lines if they are strings (w/ whitespace) 2025-09-03 17:35:24 +07:00
Cameron TaylorandMoonDroid a5842fbe3f some checkstyle changes 2025-09-03 17:35:24 +07:00
8db654e63d Add support for 16K page sizes on Android.
Co-authored-by: MoonDroid <81515012+moondroidcoder@users.noreply.github.com>
2025-09-03 17:11:36 +07:00
MoonDroid 60144c0dc7 refactor: make time formatter extensible and easier to read 2025-09-03 16:12:59 +07:00
MAJigsaw77andMoonDroid 830b5dfd71 Properly formatted build time. 2025-09-03 16:12:59 +07:00
MAJigsaw77andHundrec 12d81f0f3b Fix setting the window fullscreen from preloader. 2025-09-03 16:56:23 +08:00
sector-aandMoonDroid 8c835447c8 Improved positioning of the Dialogue Box and the Portrait 2025-09-03 06:32:53 +07:00
111 changed files with 2407 additions and 998 deletions
+3 -3
View File
@@ -48,19 +48,19 @@ body:
attributes:
label: Mobile Device Model
description: (Mobile users only) What mobile device are you playing on?
placeholder: ex. iPhone 16, Galaxy S25, iPad 11th Gen
placeholder: ex. iPhone 17, Galaxy S25, iPad 11th Gen
- type: input
attributes:
label: Mobile OS Version
description: (Mobile users only) What version is your Operating System?
placeholder: ex. iOS 18.5, Android 15, iPadOS 18.5
placeholder: ex. iOS 26.0, Android 15, iPadOS 26.0
- type: input
attributes:
label: Version
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
placeholder: ex. 0.7.4
placeholder: ex. 0.7.5
validations:
required: true
+1 -1
View File
@@ -36,7 +36,7 @@ body:
attributes:
label: Version
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
placeholder: ex. 0.7.4
placeholder: ex. 0.7.5
validations:
required: true
+1 -1
View File
@@ -36,7 +36,7 @@ body:
attributes:
label: Version
description: Which version are you compiling? The game version is in the bottom left corner of the main menu or in the project.hxp file.
placeholder: ex. 0.7.4
placeholder: ex. 0.7.5
validations:
required: true
+4 -3
View File
@@ -15,6 +15,7 @@ body:
- label: I have read the [Contributing Guide](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md)
- label: I have checked the Issues/Discussions pages to see if my issue has already been reported
- label: I have properly titled my issue
- label: I did not have any mods installed when I encountered this issue
- type: dropdown
attributes:
@@ -48,19 +49,19 @@ body:
attributes:
label: Mobile Device Model
description: (Mobile users only) What mobile device are you playing on?
placeholder: ex. iPhone 16, Galaxy S25, iPad 11th Gen
placeholder: ex. iPhone 17, Galaxy S25, iPad 11th Gen
- type: input
attributes:
label: Mobile OS Version
description: (Mobile users only) What version is your Operating System?
placeholder: ex. iOS 18.5, Android 15, iPadOS 18.5
placeholder: ex. iOS 26.0, Android 15, iPadOS 26.0
- type: input
attributes:
label: Version
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
placeholder: ex. 0.7.4
placeholder: ex. 0.7.5
validations:
required: true
+1 -1
View File
@@ -16,7 +16,7 @@ NewgroundsCredentials.hx
# Mobile signing stuff
.apple/
.env
key.keystore
*.keystore
# Exclude JS stuff
node_modules/
+67 -1
View File
@@ -4,6 +4,72 @@ All notable changes will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.7.5] - 2025-09-19
### Added
- Added an `onStateCreate` event to the Module class.
### Changed
- Gave the debug display a huge upgrade!
- Choose from three settings in the Preferences Menu: Off/Simple/Advanced
- New “Task memory” counter shows how much RAM the game is using on your computer.
- Advanced mode displays graphs for FPS and RAM usage.
- Hot reloading now works in scripted states.
- Made a few more classes scriptable.
- FunkinVideoSprite
- FlxStrip
- FlxTypedGroup
- Strumline
- StageProp​​
- The Random capsule now brings up the option to select a random instrumental. ([0d5c6ac](https://github.com/FunkinCrew/Funkin/commit/0d5c6ac9632ee3bf3977b9bd369daab9f2d95fea)) - by @VioletSnowLeopard in [#5891](https://github.com/FunkinCrew/Funkin/pull/5891)
- The Character Select menu now returns to Freeplay after pressing a back button. ([250d218](https://github.com/FunkinCrew/Funkin/commit/250d2188b9cb2c92d283d8b12ed7de0286324592)) - by @MrMadera in [#5887](https://github.com/FunkinCrew/Funkin/pull/5887)
- Pressing the Shift key in the Credits menu now pauses scrolling. ([479036a](https://github.com/FunkinCrew/Funkin/commit/479036a4008fe1c54adc970c0a476fae6b1b3e94)) - by @JVNpixels in [#2924](https://github.com/FunkinCrew/Funkin/pull/2924)
- Chart Editor and Stage Editor backup files are now given relevant filenames. ([b31be10](https://github.com/FunkinCrew/Funkin/commit/b31be1043ccd1e906a656913b947bae160b451fe)) - by @Lasercar in [#4118](https://github.com/FunkinCrew/Funkin/pull/4118)
- The Animation Editor's onion skin now properly accounts for offsets and displays the idle animation. ([0fd6f5b](https://github.com/FunkinCrew/Funkin/commit/0fd6f5b3b7bd115a3b6821096daa929c220bd235)) - by @KoloInDaCrib in [#5810](https://github.com/FunkinCrew/Funkin/pull/5810)
### Fixed
- The Freeplay new rank animation now shows the new rank slamming onto the old one.
- Freeplay capsules with a Gold Perfect rank no longer become randomly offset when scrolling.
- The Pico Freeplay DJ’s new rank animation now properly renders its lighting.
- Freeplay song previews no longer play replaced or deleted audio files.
- The strumline no longer disappears after re-entering Lag Adjustment.
- Fixed various issues with Week 6 dialogue boxes, portraits, and audio.
- [MOBILE] The strumlines are no longer offset to the left in Blazin’.
- The preloader no longer disables fullscreen mode after completion.
- Attempting to log in to Newgrounds during an ongoing login attempt no longer crashes the game. ([4e572b1](https://github.com/FunkinCrew/Funkin/commit/4e572b171fe901a84f12b4566963fd3c1f4ae6fb)) - by @KoloInDaCrib in [#5922](https://github.com/FunkinCrew/Funkin/pull/5922)
- Added null-safety to Leaderboards and Medals to prevent a rare crash. ([88fc5f9](https://github.com/FunkinCrew/Funkin/commit/88fc5f9ed684e287d7411a59fb4fdf170acfed5a)) - by @NotHyper-474 in [#5926](https://github.com/FunkinCrew/Funkin/pull/5926)
- Freeplay ranks no longer pop out when scrolling through song capsules. ([747446e](https://github.com/FunkinCrew/Funkin/commit/747446e2e420449f422fbc69aafda6128ac8d16e)) - by @VioletSnowLeopard in [#5822](https://github.com/FunkinCrew/Funkin/pull/5822)
- The Freeplay intro no longer plays when returning from a sticker transition. ([63ec749](https://github.com/FunkinCrew/Funkin/commit/63ec749c7a3c90d529e9e8f2f3ed1c8913e8f9a1)) - by @PurSnake in [#5942](https://github.com/FunkinCrew/Funkin/pull/5942)
- Hot-reloading with F5 after earning a rank no longer crashes Freeplay. ([79e8b5d](https://github.com/FunkinCrew/Funkin/commit/79e8b5d71e052953b77b81b66e0a83a7bbeeeeed)) - by @KoloInDaCrib in [#5971](https://github.com/FunkinCrew/Funkin/pull/5971)
- The Main Menu music now fades in consistently when exiting from Freeplay to the Title Screen. ([a10bbf6](https://github.com/FunkinCrew/Funkin/commit/a10bbf60acbbeadfb5a91930745ef4fd338b1a24)) - by @VirtuGuy in [#5855](https://github.com/FunkinCrew/Funkin/pull/5855)
- The game now falls back to default audio files when files specified by the metadata don't exist. ([b5357ac](https://github.com/FunkinCrew/Funkin/commit/b5357acf98bde81a4121a3723061f7c559a0640f)) - by @VioletSnowLeopard in [#5954](https://github.com/FunkinCrew/Funkin/pull/5954)
- The cutscene pause menu no longer persists across songs after exiting a video cutscene. ([c2305f3](https://github.com/FunkinCrew/Funkin/commit/c2305f32f0147f6326e8665d21a6b8a793f45033)) - by @KoloInDaCrib in [#5880](https://github.com/FunkinCrew/Funkin/pull/5880)
- Mashing to skip the Eggnog Erect cutscene no longer crashes the game. ([5ca3c6a](https://github.com/FunkinCrew/funkin.assets/commit/5ca3c6a5745b61a457db149c289784c08e560ee9)) - by @VioletSnowLeopard in [funkin.assets#258](https://github.com/FunkinCrew/funkin.assets/pull/258)
- Stress (Pico Mix) no longer lag spikes when loading in the background tankmen. ([6701405](https://github.com/FunkinCrew/funkin.assets/commit/670140571239ca08c1ae1c5286773d99c2875c2d)) - by @NotHyper-474 and @KoloInDaCrib in [funkin.assets#242](https://github.com/FunkinCrew/funkin.assets/pull/242)
- Stress (Pico Mix) no longer lag spikes when Tankman changes sprites. ([aa5956a](https://github.com/FunkinCrew/Funkin/commit/aa5956a30bdc47d53ebebb4b61a7ebb3f04c7dc6)) - by @NotHyper-474 in [#5939](https://github.com/FunkinCrew/Funkin/pull/5939)
- The bricks in Stress (Pico Mix) no longer move after restarting the song. ([9cfee03](https://github.com/FunkinCrew/Funkin/commit/9cfee0398a057567f80ad68ff6052964da63af09)) - by @VirtuGuy in [#5875](https://github.com/FunkinCrew/Funkin/pull/5875)
- Pico now properly shoots after restarting Stress. ([b0ef0d3](https://github.com/FunkinCrew/funkin.assets/commit/b0ef0d352c199e93903652a95ffdf16a3dc2d569)) - by @7oltan in [funkin.assets#254](https://github.com/FunkinCrew/funkin.assets/pull/254)
- A-Bot's visualizer now zeroes out when restarting the song. ([ba3a8b0](https://github.com/FunkinCrew/funkin.assets/commit/ba3a8b0cbbf785a0729dcf85e25b6e59c0c12657)) - by @KoloInDaCrib in [funkin.assets#246](https://github.com/FunkinCrew/funkin.assets/pull/246)
- Notekinds now properly receive dispatched events last in PlayState. ([89b2b7f](https://github.com/FunkinCrew/Funkin/commit/89b2b7fcc3130db0af1f782594af72ae1f4ce320)) - by @Az989YT in [#5936](https://github.com/FunkinCrew/Funkin/pull/5936)
- Text from .json files is now properly sanitized before it is parsed. ([80d7ace](https://github.com/FunkinCrew/Funkin/commit/80d7ace714efb729cb8f1b23b61fdab4ac655275)) - by @KoloInDaCrib in [#5108](https://github.com/FunkinCrew/Funkin/pull/5108)
- Fixed a crash when exiting the Chart Editor after interacting with a text field. ([f85f2f0](https://github.com/FunkinCrew/Funkin/commit/f85f2f03d97fc1332275f088ddce2b4056f0ded2)) - by @NotHyper-474 in [#5992](https://github.com/FunkinCrew/Funkin/pull/5992)
- Fixed some issues with variation handling in the Chart Editor. ([970ccf2](https://github.com/FunkinCrew/Funkin/commit/970ccf2f4a9b5802b70d0b3c269c8b9e541d53f2)) - by @NotHyper-474 in [#4396](https://github.com/FunkinCrew/Funkin/pull/4396)
- Audio no longer plays when dragging a playhead in the Chart Editor. ([2d95754](https://github.com/FunkinCrew/Funkin/commit/2d95754d492da06920d56de2b2bcbb9b81182af0)) - by @Lasercar in [#4140](https://github.com/FunkinCrew/Funkin/pull/4140)
- The Chart Editor Offsets window song preview no longer continues playing during playtesting. ([11eb406](https://github.com/FunkinCrew/Funkin/commit/11eb4060d03b18a2f3604297eb9a24e54077fe3c)) - by @PurSnake and @Lasercar in [#5973](https://github.com/FunkinCrew/Funkin/pull/5973)
- The fullscreen keybind no longer triggers when typing in a field. ([4ebd00b](https://github.com/FunkinCrew/Funkin/commit/4ebd00bc06a5c94e935f557ba14ba9729faec104)) - by @lemz1 in [#4131](https://github.com/FunkinCrew/Funkin/pull/4131)
- The Stage Editor new stage menu can no longer be opened multiple times at once. ([47b1cee](https://github.com/FunkinCrew/Funkin/commit/47b1cee6d5ee37cd18146ca8098da85610c6b544)) - by @VioletSnowLeopard in [#5873](https://github.com/FunkinCrew/Funkin/pull/5873)
- Adjusted the save hotkeys in the Animation Editor to be usable on Windows. ([8ead5ee](https://github.com/FunkinCrew/Funkin/commit/8ead5eeb0840756a843fdcf0f3f347b087e7182d)) - by @VioletSnowLeopard in [#5974](https://github.com/FunkinCrew/Funkin/pull/5974)
- A few more tiny fixes.
## New Contributors for 0.7.5
* @Az989YT made their first contribution in [#5936](https://github.com/FunkinCrew/Funkin/pull/5936)
## [0.7.4] - 2025-09-01
### Added
@@ -47,7 +113,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Improved properties support, behaving more like regular Haxe. (Thanks NotHyper-474!)
- Fixed a null object reference when evaluating static variables with no explicit values. (Thanks NotHyper-474!)
- Fixed a null object reference when iterating over a null variable. (Thanks NotHyper-474!)
- Scripts are now automatically cleared when Polymod is initialized. (Thanks KoloInDaKrib!)
- Scripts are now automatically cleared when Polymod is initialized. (Thanks KoloInDaCrib!)
- Polished every chart in the game!
- The lengths of hold notes have been adjusted to match the vocals as closely as possible, across all variations and difficulties.
- Easy and Normal charts from Week 1 to Week 3 have been adjusted for more consistency between the player and opponent.
+2 -2
View File
@@ -1,4 +1,4 @@
<div align='center'><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/d2/Friday_Night_Funkin%27_logo.svg/2560px-Friday_Night_Funkin%27_logo.svg.png" width="800">
<div align='center'><img src="https://fridaynightfunkin.wiki.gg/images/FNF_logo.png" width="800">
<h2>Friday Night Funkin' is a rhythm game. Built using HaxeFlixel for <a href="https://ldjam.com/events/ludum-dare/47">Ludum Dare 47.</a></h2>
@@ -63,4 +63,4 @@ Full credits can be found in-game, or in the `credits.json` file which is locate
## Special Thanks
- [Tom Fulp](https://twitter.com/tomfulp) - For being a great guy and for Newgrounds
- [JohnnyUtah](https://twitter.com/JohnnyUtahNG/) - Voice of Tankman
- [L0Litsmonica](https://twitter.com/L0Litsmonica) - Voice of Mommy Mearest
- [L0Litsmonica](https://twitter.com/L0Litsmonica) - Voice of Mommy Mearest
+1 -1
Submodule assets updated: e6f463fb64...5ca3c6a574
+4
View File
@@ -17,6 +17,10 @@
"asset": "asset/shared/images/resultScreen/results-pico/resultsGOOD/spritemap1.png",
"blocksize": "4x4"
},
{
"asset": "assets/shared/images/resultScreen/results-bf/resultsEXCELLENT/spritemap1.png",
"blocksize": "4x4"
},
{
"asset": "asset/shared/images/resultScreen/results-pico/resultsSHIT/spritemap1.png",
"blocksize": "4x4"
+33 -20
View File
@@ -1,10 +1,9 @@
{
"extendsConfigPath": "",
"defineCombinations": [],
"defaultSeverity": "INFO",
"extendsConfigPath": "",
"numberOfCheckerThreads": 5,
"baseDefines": [],
"version": 1,
"exclude": {},
"defineCombinations": [],
"checks": [
{
"props": {},
@@ -40,12 +39,6 @@
"props": {},
"type": "AvoidStarImport"
},
{
"props": {
"severity": "IGNORE"
},
"type": "AvoidTernaryOperator"
},
{
"props": {},
"type": "BlockBreakingConditional"
@@ -71,7 +64,6 @@
{
"props": {
"severity": "IGNORE",
"policy": "aligned",
"allowSingleline": true
},
@@ -108,13 +100,12 @@
{
"props": {
"lineStyle": "onestar",
"startStyle": "twostars",
"severity": "IGNORE"
"startStyle": "ignore"
},
"type": "DocCommentStyle"
},
{
"props": {},
"props": { "severity": "IGNORE" },
"type": "Dynamic"
},
{
@@ -193,6 +184,14 @@
},
"type": "FileLength"
},
{
"type": "FileNameCase",
"props": {}
},
{
"type": "Final",
"props": {}
},
{
"props": {
"option": "upperCase"
@@ -245,7 +244,7 @@
{
"props": {
"max": 160,
"ignorePattern": "^$"
"ignorePattern": "^(\\s*['\"][^'\"]*['\"].*)$"
},
"type": "LineLength"
},
@@ -348,7 +347,7 @@
{
"props": {
"allowSingleLineStatement": true,
"tokens": ["FOR", "ELSE_IF", "WHILE", "DO_WHILE"]
"tokens": ["ELSE_IF", "WHILE", "DO_WHILE"]
},
"type": "NeedBraces"
},
@@ -580,6 +579,12 @@
"props": {},
"type": "TrailingWhitespace"
},
{
"type": "Type",
"props": {
"ignoreEnumAbstractValues": true
}
},
{
"props": {
"tokens": ["CLASS_DEF", "ENUM_DEF", "INTERFACE_DEF", "TYPEDEF_DEF"]
@@ -612,11 +617,18 @@
"type": "UnusedLocalVar"
},
{
"type": "VarTypeHint",
"props": {
"typeHintPolicy": "enforce_all",
"ignoreEnumAbstractValues": true
},
"type": "VarTypeHint"
"ignoreEnumAbstractValues": true,
"severity": "IGNORE"
}
},
{
"type": "VariableInitialisation",
"props": {
"allowFinal": false
}
},
{
"props": {
@@ -665,5 +677,6 @@
"type": "WhitespaceAround"
}
],
"numberOfCheckerThreads": 5
"exclude": {},
"version": 1
}
+1 -1
View File
@@ -60,7 +60,7 @@ This section provides guidelines to follow when [opening an issue](https://githu
## Requirements
Make sure you're playing:
- the latest version of the game (currently v0.7.4)
- the latest version of the game (currently v0.7.5)
- without any mods
- on [Newgrounds](https://www.newgrounds.com/portal/view/770371) or downloaded from [itch.io](https://ninja-muffin24.itch.io/funkin)
+10 -10
View File
@@ -4,8 +4,8 @@
"name": "FlxPartialSound",
"type": "git",
"dir": null,
"ref": "3c9f63e3501c20c0b60442089dc05306f5a87968",
"url": "https://github.com/FunkinDroidTeam/FlxPartialSound.git"
"ref": "4e86b724ca43489a59ea465a46ffc12598e5df15",
"url": "https://github.com/FunkinCrew/FlxPartialSound.git"
},
{
"name": "astc-compressor",
@@ -18,7 +18,7 @@
"name": "extension-admob",
"type": "git",
"dir": null,
"ref": "02334589ff9603a5f483077a44395009644f6274",
"ref": "96385202f6f951b2bde0cbe836c5da21dde1f36e",
"url": "https://github.com/FunkinCrew/extension-admob"
},
{
@@ -45,7 +45,7 @@
"name": "flixel",
"type": "git",
"dir": null,
"ref": "ac2a34fe4b3400bc04218f46320a65af0f337fc0",
"ref": "5cf3515e665812179a9b413629325b608b3a9815",
"url": "https://github.com/FunkinCrew/flixel"
},
{
@@ -111,14 +111,14 @@
"name": "hxcpp",
"type": "git",
"dir": null,
"ref": "5a0dc3f644dc676a4a092b7e6c8edc8be941f024",
"ref": "363d3d0a8d3ac450f5ba1b4c3357353656b71e82",
"url": "https://github.com/FunkinCrew/hxcpp"
},
{
"name": "hxcpp-debug-server",
"type": "git",
"dir": "hxcpp-debug-server",
"ref": "147294123f983e35f50a966741474438069a7a8f",
"ref": "7459934666a473a4cc4d066ba4a93ef92f1ce94c",
"url": "https://github.com/FunkinCrew/hxcpp-debugger"
},
{
@@ -143,7 +143,7 @@
{
"name": "hxvlc",
"type": "haxelib",
"version": "2.2.2"
"version": "2.2.4"
},
{
"name": "json2object",
@@ -170,7 +170,7 @@
"name": "lime",
"type": "git",
"dir": null,
"ref": "e5f8c27124598505917a001588b560244731adfb",
"ref": "f109a083023a9e5622342257993fc745d9bbb959",
"url": "https://github.com/FunkinCrew/lime"
},
{
@@ -184,14 +184,14 @@
"name": "openfl",
"type": "git",
"dir": null,
"ref": "a0df7c3afe360c9af59a76e45007dbf4e53b5131",
"ref": "e6296200292a76b5f7b8684a991ead6b05ae4507",
"url": "https://github.com/FunkinCrew/openfl"
},
{
"name": "polymod",
"type": "git",
"dir": null,
"ref": "55ee00236806330c89c3bff1e1563eb6425a9d96",
"ref": "47d9a2775e3fd8f6dd17d6f4b99b8798709da435",
"url": "https://github.com/larsiusprime/polymod"
},
{
+180 -38
View File
@@ -31,13 +31,13 @@ class Project extends HXProject
* REMEMBER TO CHANGE THIS WHEN THE GAME UPDATES!
* You only have to change it here, the rest of the game will query this value.
*/
static final VERSION:String = "0.7.4";
static final VERSION:String = "0.7.5";
/**
* The build's number and version code.
* Used when publishing the game to mobile app stores. Should increment with each patch.
*/
static final BUILD_NUMBER:Int = 53;
static final BUILD_NUMBER:Int = 56;
/**
* The game's name. Used as the default window title.
@@ -330,6 +330,13 @@ class Project extends HXProject
*/
static final FEATURE_LOG_TRACE:FeatureFlag = "FEATURE_LOG_TRACE";
/**
* `-DFEATURE_DEBUG_FILE_LOGGING`
* If this flag is enabled, the game will print debug traces to a log file near the game's executable.
* Disable to improve performance a bunch.
*/
static final FEATURE_DEBUG_FILE_LOGGING:FeatureFlag = "FEATURE_DEBUG_FILE_LOGGING";
/**
* `-DFEATURE_MOBILE_ADVERTISEMENTS`
* If this flag is enabled, Google AdMob will be enabled.
@@ -509,6 +516,11 @@ class Project extends HXProject
{
configureIOS();
}
if (!isDisplay())
{
checkLibraries();
}
}
/**
@@ -535,8 +547,7 @@ class Project extends HXProject
this.meta.title = TITLE;
// ios uses this character code instead of spaces...
if (isIOS())
this.meta.title = ~/ /ig.replace(TITLE, "\u2002");
if (isIOS()) this.meta.title = ~/ /ig.replace(TITLE, "\u2002");
this.meta.version = VERSION;
this.meta.packageName = PACKAGE_NAME;
@@ -782,8 +793,13 @@ class Project extends HXProject
// FEATURE FLAGS
//
// Should be true on debug builds or if GITHUB_BUILD is enabled.
FEATURE_DEBUG_FUNCTIONS.apply(this, isDebug() || GITHUB_BUILD.isEnabled(this));
FEATURE_RESULTS_DEBUG.apply(this, isDebug() || GITHUB_BUILD.isEnabled(this));
FEATURE_FUNKVIS.apply(this, true);
FEATURE_LOG_TRACE.apply(this, true);
FEATURE_DEBUG_FILE_LOGGING.apply(this, FEATURE_DEBUG_FUNCTIONS.isEnabled(this));
FEATURE_NAUGHTYNESS.apply(this, true);
FEATURE_OPEN_URL.apply(this, true);
FEATURE_PARTIAL_SOUNDS.apply(this, true);
@@ -793,10 +809,6 @@ class Project extends HXProject
// HXCPP has trouble with iOS simulator builds though...
FEATURE_VIDEO_PLAYBACK.apply(this, !isIOSSimulator());
// Should be true on debug builds or if GITHUB_BUILD is enabled.
FEATURE_DEBUG_FUNCTIONS.apply(this, isDebug() || GITHUB_BUILD.isEnabled(this));
FEATURE_RESULTS_DEBUG.apply(this, isDebug() || GITHUB_BUILD.isEnabled(this));
// Should be true on desktop, non-tester builds.
// We don't want testers to accidentally leak songs to their Discord friends!
FEATURE_DISCORD_RPC.apply(this, isDesktop() && !FEATURE_DEBUG_FUNCTIONS.isEnabled(this));
@@ -849,8 +861,6 @@ class Project extends HXProject
FEATURE_COMPRESSED_TEXTURES.apply(this, isMobile() && isRelease());
renderFlagsTable();
}
/**
@@ -893,7 +903,6 @@ class Project extends HXProject
enabledFlags.sort(function(a, b) return a > b ? 1 : -1);
disabledFlags.sort(function(a, b) return a > b ? 1 : -1);
var horizontalDivider:String = "".rpad("-", enabledWidth + disabledWidth);
info(horizontalDivider);
@@ -902,19 +911,16 @@ class Project extends HXProject
info('Enabled Flags $unicodeCheck'.rpad(" ", enabledWidth + 5) + 'Disabled Flags $unicodeCross');
info(horizontalDivider);
while (enabledFlags.length > 0 || disabledFlags.length > 0)
{
var outputLine:String = "";
if (enabledFlags.length > 0)
outputLine += enabledFlags.shift();
if (enabledFlags.length > 0) outputLine += enabledFlags.shift();
outputLine = outputLine.rpad(" ", enabledWidth + 5);
outputLine += "| ";
if (disabledFlags.length > 0)
outputLine += disabledFlags.shift() ?? "";
if (disabledFlags.length > 0) outputLine += disabledFlags.shift() ?? "";
info(outputLine);
}
@@ -1050,9 +1056,9 @@ class Project extends HXProject
if (envConfig != null)
{
keystore = new Keystore();
keystore.path = Std.string(envConfig.get('ANDROID_KEYSTORE_PATH'));
keystore.alias = Std.string(envConfig.get('ANDROID_KEYSTORE_ALIAS'));
keystore.password = Std.string(envConfig.get('ANDROID_KEYSTORE_PASSWORD'));
keystore.path = Std.string(envConfig.get('KEYSTORE_PATH'));
keystore.alias = Std.string(envConfig.get('KEYSTORE_ALIAS'));
keystore.password = Std.string(envConfig.get('KEYSTORE_PASSWORD'));
}
if (isBundle())
@@ -1339,7 +1345,6 @@ class Project extends HXProject
addIcon("art/icons/icon32.png", 32);
addIcon("art/icons/icon64.png", 64);
addIcon("art/icons/iconOG.png");
}
info('Done configuring icons.');
@@ -1350,7 +1355,7 @@ class Project extends HXProject
*/
function configureASTCTextures()
{
if (command != "display")
if (!(isDisplay() || isClean()))
{
readASTCExclusion();
@@ -1368,15 +1373,7 @@ class Project extends HXProject
{
if (envConfig == null) return;
if (isAndroid())
{
setenv("ADMOB_APPID", Std.string(envConfig.get('ANDROID_ADMOB_APP_ID')));
}
if (isIOS())
{
setenv("ADMOB_APPID", Std.string(envConfig.get('IOS_ADMOB_APP_ID')));
}
setenv("ADMOB_APPID", Std.string(envConfig.get('ADMOB_APP_ID')));
}
//
@@ -1489,6 +1486,11 @@ class Project extends HXProject
return this.command == "display";
}
public function isClean():Bool
{
return this.command == "clean";
}
public function isDebug():Bool
{
return this.debug;
@@ -1711,6 +1713,101 @@ class Project extends HXProject
return commitHashSplice;
}
@SuppressWarnings('checkstyle:Dynamic')
static function checkLibraries():Void
{
var outdatedLibraries:Map<String, Array<String>> = new Map<String, Array<String>>();
var hmmData:Dynamic = haxe.Json.parse(sys.io.File.getContent(#if ios '../../../../../' + #end 'hmm.json'));
var dependencies:Array<Dynamic> = cast hmmData.dependencies;
for (library in dependencies)
{
var libraryName:String = cast(library.name, String).replace(".", ",");
var libraryDev:String = readLibraryDev(libraryName);
var libraryCurrentVersion:String = readLibraryCurrentVersion(libraryName);
switch (library.type)
{
case 'haxelib':
if (libraryDev != "")
{
outdatedLibraries.set(libraryName, [libraryDev, library.version]);
}
else if (library.version != libraryCurrentVersion)
{
outdatedLibraries.set(libraryName, [libraryCurrentVersion, library.version]);
}
case 'git':
if (libraryDev != "")
{
outdatedLibraries.set(libraryName, [libraryDev, library.ref]);
}
else
{
var commitHash:String = readLibraryGitCommitHash(libraryName);
if (commitHash != library.ref && commitHash != "")
outdatedLibraries.set(libraryName, [commitHash, library.ref]);
}
}
}
if (Lambda.count(outdatedLibraries) > 0)
{
Sys.println("\x1b[1;33m\nWARNING: The following haxelibs diverge from the versions set in hmm.json."
+ "\n\nThey may be outdated, so it is recommended to abort compilation and run `hmm reinstall [library]` to update each library."
+ "\n\nYou may ignore this warning if your libraries are newer than the versions in hmm.json, or if you know what you're doing.\n\x1b[0m");
for (libraryName in outdatedLibraries.keys())
{
var versions:Null<Array<String>> = outdatedLibraries.get(libraryName);
if (versions == null) continue;
var outdatedVersion:String = versions[0];
var expectedVersion:String = versions[1];
var isDev:Bool = outdatedVersion.contains("/");
Sys.println("- " + libraryName.replace(",", ".") + (isDev ? "\x1b[1;34m (Development Build)\x1b[0m" : ""));
Sys.println(" \x1b[38;5;203mCurrent version: " + outdatedVersion + "\x1b[0m");
Sys.println(" \x1b[38;5;82mExpected version: " + expectedVersion + "\x1b[0m\n");
}
}
}
static function readLibraryCurrentVersion(libraryName:String):String
{
var path = haxe.io.Path.join([haxe.io.Path.addTrailingSlash(Sys.getCwd()), '.haxelib', libraryName, '.current']);
if (sys.FileSystem.exists(path))
return sys.io.File.getContent(path);
return '';
}
static function readLibraryDev(libraryName:String):String
{
var path = haxe.io.Path.join([haxe.io.Path.addTrailingSlash(Sys.getCwd()), '.haxelib', libraryName, '.dev']);
if (sys.FileSystem.exists(path))
return sys.io.File.getContent(path);
return '';
}
static function readLibraryGitCommitHash(libraryName:String):String
{
var gitProccess = new sys.io.Process("git", ["-C", ".haxelib/" + libraryName + "/git", "rev-parse", "HEAD"]);
gitProccess.exitCode(true);
return gitProccess.stdout.readAll().toString().trim();
}
public function getGitBranch():String
{
// Cannibalized from GitCommit.hx
@@ -1781,7 +1878,7 @@ class Project extends HXProject
*/
public function info(message:String):Void
{
if (command != "display")
if (!(isDisplay() || isClean()))
{
Sys.println('[INFO] ${message}');
}
@@ -1808,24 +1905,37 @@ class Project extends HXProject
}
var env = new Map<String, Dynamic>();
for (line in envFile.split('\n'))
{
if (line.length <= 0 || line.startsWith("#") || shouldExcludeEnvKey(line)) continue;
line = line.trim();
var index:Int = line.indexOf('=');
if (line.length <= 0 || line.startsWith('#') || shouldExcludeEnvKey(line))
{
continue;
}
if (index == -1) continue;
final index:Int = line.indexOf('=');
var field:String = line.substr(0, index);
var value:String = line.substr(index + 1);
if (index == -1)
{
continue;
}
env.set(field, value);
final value:String = line.substr(index + 1);
if (value.length == 0)
{
continue;
}
env.set(stripEnvTargetPrefix(line.substr(0, index)), value);
}
return env;
}
private function shouldExcludeEnvKey(key:String):Bool
public function shouldExcludeEnvKey(key:String):Bool
{
final android:Bool = key.startsWith('ANDROID_');
final ios:Bool = key.startsWith('IOS_');
@@ -1841,6 +1951,38 @@ class Project extends HXProject
return false;
}
public function stripEnvTargetPrefix(key:String):String
{
final index:Int = key.indexOf('_');
if (index == -1)
{
return key;
}
final prefix:String = key.substr(0, index);
final rest:String = key.substr(index + 1);
if (isAndroid() && (prefix == 'ANDROID' || prefix == 'MOBILE'))
{
return rest;
}
else if (isIOS() && (prefix == 'IOS' || prefix == 'MOBILE'))
{
return rest;
}
else if (isWeb() && prefix == 'WEB')
{
return rest;
}
else if (isDesktop() && prefix == 'DESKTOP')
{
return rest;
}
return key;
}
public function readASTCExclusion():Void
{
@:nullSafety(Off)
+22 -47
View File
@@ -6,13 +6,12 @@ import flixel.FlxState;
import funkin.ui.FullScreenScaleMode;
import funkin.Preferences;
import funkin.util.logging.CrashHandler;
import funkin.ui.debug.MemoryCounter;
import funkin.ui.debug.FunkinDebugDisplay;
import funkin.save.Save;
import haxe.ui.Toolkit;
#if hxvlc
import hxvlc.util.Handle;
#end
import openfl.display.FPS;
import openfl.display.Sprite;
import openfl.events.Event;
import openfl.Lib;
@@ -30,7 +29,6 @@ class Main extends Sprite
var initialState:Class<FlxState> = funkin.InitState; // The FlxState the game starts with.
var zoom:Float = -1; // If -1, zoom is automatically calculated to fit the window dimensions.
var skipSplash:Bool = true; // Whether to skip the flixel splash screen that appears in release mode.
var startFullscreen:Bool = false; // Whether to start the game in fullscreen on desktop targets
// You can pretty much ignore everything from here on - your code should go in your states.
@@ -84,32 +82,17 @@ class Main extends Sprite
setupGame();
}
var video:Video;
var netStream:NetStream;
var overlay:Sprite;
/**
* A frame counter displayed at the top left.
* The debug display at the top left.
*/
public static var fpsCounter:FPS;
/**
* A RAM counter displayed at the top left.
*/
public static var memoryCounter:MemoryCounter;
public static var debugDisplay:FunkinDebugDisplay;
function setupGame():Void
{
initHaxeUI();
// addChild gets called by the user settings code.
fpsCounter = new FPS(10, 3, 0xFFFFFF);
#if !html5
// addChild gets called by the user settings code.
// TODO: disabled on HTML5 (todo: find another method that works?)
memoryCounter = new MemoryCounter(10, 13, 0xFFFFFF);
#end
debugDisplay = new FunkinDebugDisplay(10, 10, 0xFFFFFF);
#if mobile
// Add this signal so we can reposition and resize the memory and fps counter.
@@ -121,7 +104,16 @@ class Main extends Sprite
#if hxvlc
// Initialize hxvlc's Handle here so the videos are loading faster.
Handle.init();
Handle.initAsync(function(success:Bool):Void {
if (success)
{
trace('[HXVLC] LibVLC instance initialized!');
}
else
{
trace('[HXVLC] LibVLC instance failed to initialize!');
}
});
#end
// Don't call anything from the preferences until the save is loaded!
@@ -133,7 +125,8 @@ class Main extends Sprite
WindowUtil.setVSyncMode(funkin.Preferences.vsyncMode);
var game:FlxGame = new FlxGame(gameWidth, gameHeight, initialState, Preferences.framerate, Preferences.framerate, skipSplash, startFullscreen);
var game:FlxGame = new FlxGame(gameWidth, gameHeight, initialState, Preferences.framerate, Preferences.framerate, skipSplash,
(FlxG.stage.window.fullscreen || Preferences.autoFullscreen));
// FlxG.game._customSoundTray wants just the class, it calls new from
// create() in there, which gets called when it's added to the stage
@@ -191,41 +184,23 @@ class Main extends Sprite
scale = Math.min(scale, 1);
#end
final thypos:Float = Math.max(FullScreenScaleMode.notchSize.x, 10);
if (fpsCounter != null)
if (debugDisplay != null)
{
fpsCounter.scaleX = fpsCounter.scaleY = scale;
debugDisplay.scaleX = debugDisplay.scaleY = scale;
if (FlxG.game != null)
{
if (lerp)
{
fpsCounter.x = flixel.math.FlxMath.lerp(fpsCounter.x, FlxG.game.x + thypos, FlxG.elapsed * 3);
debugDisplay.x = flixel.math.FlxMath.lerp(debugDisplay.x, FlxG.game.x + thypos, FlxG.elapsed * 3);
}
else
{
fpsCounter.x = FlxG.game.x + FullScreenScaleMode.notchSize.x + 10;
debugDisplay.x = FlxG.game.x + FullScreenScaleMode.notchSize.x + 10;
}
fpsCounter.y = FlxG.game.y + (3 * scale);
}
}
if (memoryCounter != null)
{
memoryCounter.scaleX = memoryCounter.scaleY = scale;
if (FlxG.game != null)
{
if (lerp)
{
memoryCounter.x = flixel.math.FlxMath.lerp(memoryCounter.x, FlxG.game.x + thypos, FlxG.elapsed * 3);
}
else
{
memoryCounter.x = FlxG.game.x + FullScreenScaleMode.notchSize.x + 10;
}
memoryCounter.y = FlxG.game.y + (13 * scale);
debugDisplay.y = FlxG.game.y + (3 * scale);
}
}
}
+36 -5
View File
@@ -3,6 +3,8 @@ package source; // Yeah, I know...
import sys.FileSystem;
import sys.io.File;
using StringTools;
/**
* A script which executes after the game is built.
*/
@@ -27,14 +29,43 @@ class Postbuild
sys.FileSystem.deleteFile(BUILD_TIME_FILE);
var buildTime:Float = roundToTwoDecimals(end - start);
Sys.println('[INFO] Build took: ${buildTime} seconds');
Sys.println('[INFO] Build took: ${format(end - start)}');
}
}
static function roundToTwoDecimals(value:Float):Float
static function format(time:Float, decimals:Int = 1):String
{
return Math.round(value * 100) / 100;
var units = [
{name: "day", secs: 86400},
{name: "hour", secs: 3600},
{name: "minute", secs: 60},
{name: "second", secs: 1}
];
var parts:Array<String> = [];
var remaining:Float = time;
for (unit in units)
{
var value:Float;
if (unit.name == "second")
{
// handle seconds with decimals
value = Math.round(remaining * Math.pow(10, decimals)) / Math.pow(10, decimals);
}
else
{
value = Math.floor(remaining / unit.secs);
remaining %= unit.secs;
}
//
if (value > 0 || (unit.name == "second" && parts.length == 0))
{
parts.push('${value} ${unit.name}${value == 1 ? "" : "s"}');
}
}
return parts.join(" ");
}
}
+1
View File
@@ -177,6 +177,7 @@ class FunkinMemory
var sprite = new flixel.FlxSprite();
sprite.loadGraphic(graphic);
sprite.draw(); // Draw sprite and load it into game's memory.
graphic.bitmap?.getTexture(FlxG.stage.context3D); // Just in case that didn't work...
sprite.destroy();
}
+2 -2
View File
@@ -24,7 +24,7 @@ import funkin.data.event.SongEventRegistry;
import funkin.data.stage.StageRegistry;
import funkin.data.story.level.LevelRegistry;
import funkin.modding.module.ModuleHandler;
import funkin.play.character.CharacterData.CharacterDataParser;
import funkin.data.character.CharacterData.CharacterDataParser;
import funkin.play.notes.notekind.NoteKindManager;
import funkin.play.PlayStatePlaylist;
import funkin.ui.debug.charting.ChartEditorState;
@@ -125,7 +125,7 @@ class InitState extends FlxState
#if ios
// Setup Audio session
funkin.external.ios.AudioSession.initialize();
funkin.external.apple.AudioSession.initialize();
#end
// This ain't a pixel art game! (most of the time)
+72 -32
View File
@@ -7,6 +7,7 @@ import funkin.mobile.util.InAppPurchasesUtil;
import funkin.save.Save;
import funkin.util.WindowUtil;
import funkin.util.HapticUtil.HapticsMode;
import funkin.ui.debug.FunkinDebugDisplay.DebugDisplayMode;
/**
* A core class which provides a store of user-configurable, globally relevant values.
@@ -137,27 +138,70 @@ class Preferences
/**
* If enabled, an FPS and memory counter will be displayed even if this is not a debug build.
* Always disabled on mobile.
* @default `false`
* @default `Off`
*/
public static var debugDisplay(get, set):Bool;
public static var debugDisplay(get, set):DebugDisplayMode;
static function get_debugDisplay():Bool
static function get_debugDisplay():DebugDisplayMode
{
#if mobile
return false;
return DebugDisplayMode.OFF;
#end
return Save?.instance?.options?.debugDisplay ?? false;
var value = Save?.instance?.options?.debugDisplay ?? 'Off';
return switch (value)
{
case "Simple":
DebugDisplayMode.SIMPLE;
case "Advanced":
DebugDisplayMode.ADVANCED;
default:
DebugDisplayMode.OFF;
};
}
static function set_debugDisplay(value:Bool):Bool
static function set_debugDisplay(value:DebugDisplayMode):DebugDisplayMode
{
if (value != Save.instance.options.debugDisplay)
var string;
switch (value)
{
toggleDebugDisplay(value);
case DebugDisplayMode.SIMPLE:
string = "Simple";
case DebugDisplayMode.ADVANCED:
string = "Advanced";
default:
string = "Off";
};
if (string != Save.instance.options.debugDisplay)
{
setDebugDisplayMode(value);
}
var save = Save.instance;
save.options.debugDisplay = value;
save.options.debugDisplay = string;
save.flush();
return value;
}
/**
* Opacity of the debug display's background.
* @default `50`
*/
public static var debugDisplayBGOpacity(get, set):Int;
static function get_debugDisplayBGOpacity():Int
{
return Save?.instance?.options?.debugDisplayBGOpacity ?? 50;
}
static function set_debugDisplayBGOpacity(value:Int):Int
{
setDebugDisplayBGOpacity(value / 100);
var save:Save = Save.instance;
save.options.debugDisplayBGOpacity = value;
save.flush();
return value;
}
@@ -459,18 +503,15 @@ class Preferences
{
// Apply the autoPause setting (enables automatic pausing on focus lost).
FlxG.autoPause = Preferences.autoPause;
// WindowUtil.setVSyncMode(Preferences.vsyncMode);
// Apply the debugDisplay setting (enables the FPS and RAM display).
toggleDebugDisplay(Preferences.debugDisplay);
setDebugDisplayMode(Preferences.debugDisplay);
setDebugDisplayBGOpacity(Preferences.debugDisplayBGOpacity / 100);
#if web
toggleFramerateCap(Preferences.unlockedFramerate);
#end
#if desktop
// Apply the autoFullscreen setting (launches the game in fullscreen automatically)
FlxG.fullscreen = Preferences.autoFullscreen;
#end
#if mobile
// Apply the allowScreenTimeout setting.
lime.system.System.allowScreenTimeout = Preferences.screenTimeout;
@@ -485,26 +526,25 @@ class Preferences
#end
}
static function toggleDebugDisplay(show:Bool):Void
static function setDebugDisplayMode(mode:DebugDisplayMode):Void
{
if (show)
if (FlxG.game.parent.contains(Main.debugDisplay))
{
// Enable the debug display.
FlxG.game.parent.addChild(Main.fpsCounter);
#if !html5
FlxG.game.parent.addChild(Main.memoryCounter);
#end
FlxG.game.parent.removeChild(Main.debugDisplay);
}
else
{
// Disable the debug display.
FlxG.game.parent.removeChild(Main.fpsCounter);
#if !html5
FlxG.game.parent.removeChild(Main.memoryCounter);
#end
}
if (mode == DebugDisplayMode.OFF) return;
Main.debugDisplay.isAdvanced = (mode == DebugDisplayMode.ADVANCED);
FlxG.game.parent.addChild(Main.debugDisplay);
}
static function setDebugDisplayBGOpacity(value:Float):Void
{
if (Main.debugDisplay == null) return;
Main.debugDisplay.backgroundOpacity = value;
}
#if mobile
+5 -4
View File
@@ -1,6 +1,5 @@
package funkin.api.discord;
import funkin.util.macro.EnvironmentConfigMacro;
#if FEATURE_DISCORD_RPC
import hxdiscord_rpc.Discord;
import hxdiscord_rpc.Types.DiscordButton;
@@ -9,10 +8,12 @@ import hxdiscord_rpc.Types.DiscordRichPresence;
import hxdiscord_rpc.Types.DiscordUser;
import sys.thread.Thread;
@:build(funkin.util.macro.EnvironmentMacro.build())
@:nullSafety
class DiscordClient
{
static final CLIENT_ID:Null<String> = EnvironmentConfigMacro.environmentConfig?.get("DESKTOP_DISCORD_CLIENT_ID");
@:envField
static final DISCORD_CLIENT_ID:Null<String>;
public static var instance(get, never):DiscordClient;
static var _instance:Null<DiscordClient> = null;
@@ -49,7 +50,7 @@ class DiscordClient
@:nullSafety(Off)
{
Discord.Initialize(CLIENT_ID, cpp.RawPointer.addressOf(handlers), 1, "");
Discord.Initialize(DISCORD_CLIENT_ID, cpp.RawPointer.addressOf(handlers), 1, "");
}
createDaemon();
@@ -60,7 +61,7 @@ class DiscordClient
*/
static function hasValidCredentials():Bool
{
return !(CLIENT_ID == null || CLIENT_ID == "" || (CLIENT_ID != null && CLIENT_ID.contains(" ")));
return !(DISCORD_CLIENT_ID == null || DISCORD_CLIENT_ID == "" || (DISCORD_CLIENT_ID != null && DISCORD_CLIENT_ID.contains(" ")));
}
var daemon:Null<Thread> = null;
+2 -8
View File
@@ -37,10 +37,7 @@ class Leaderboards
if (NewgroundsClient.instance.isLoggedIn())
{
var leaderboardList = NewgroundsClient.instance.leaderboards;
if (leaderboardList == null) return;
var leaderboardData:Null<LeaderboardData> = leaderboardList.get(leaderboard.getId());
var leaderboardData:Null<LeaderboardData> = listLeaderboardData().get(leaderboard.getId());
if (leaderboardData != null)
{
leaderboardData.postScore(score, function(outcome:Outcome<CallError>):Void {
@@ -67,10 +64,7 @@ class Leaderboards
// Silently reject retrieving scores from unknown leaderboards.
if (leaderboard == Leaderboard.Unknown) return;
var leaderboardList = NewgroundsClient.instance.leaderboards;
if (leaderboardList == null) return;
var leaderboardData:Null<LeaderboardData> = leaderboardList.get(leaderboard.getId());
var leaderboardData:Null<LeaderboardData> = listLeaderboardData().get(leaderboard.getId());
if (leaderboardData == null) return;
var user:Null<User> = null;
+2 -10
View File
@@ -30,11 +30,7 @@ class Medals
{
if (NewgroundsClient.instance.isLoggedIn())
{
var medalList = NewgroundsClient.instance.medals;
@:privateAccess
if (medalList == null || medalList._map == null) return;
var medalData:Null<MedalData> = medalList.get(medal.getId());
var medalData:Null<MedalData> = listMedalData().get(medal.getId());
@:privateAccess
if (medalData == null || medalData._data == null)
{
@@ -120,11 +116,7 @@ class Medals
public static function fetchMedalData(medal:Medal):Null<FetchedMedalData>
{
var medalList = NewgroundsClient.instance.medals;
@:privateAccess
if (medalList == null || medalList._map == null) return null;
var medalData:Null<MedalData> = medalList.get(medal.getId());
var medalData:Null<MedalData> = listMedalData().get(medal.getId());
@:privateAccess
if (medalData == null || medalData._data == null)
{
@@ -1,6 +1,5 @@
package funkin.api.newgrounds;
import funkin.util.macro.EnvironmentConfigMacro;
import funkin.save.Save;
import funkin.api.newgrounds.Medals.Medal;
#if FEATURE_NEWGROUNDS
@@ -15,11 +14,15 @@ import io.newgrounds.utils.SaveSlotList;
import io.newgrounds.utils.ScoreBoardList;
import io.newgrounds.objects.User;
@:build(funkin.util.macro.EnvironmentMacro.build())
@:nullSafety
class NewgroundsClient
{
static final APP_ID:Null<String> = EnvironmentConfigMacro.environmentConfig?.get("API_NG_APP_ID");
static final ENCRYPTION_KEY:Null<String> = EnvironmentConfigMacro.environmentConfig?.get("API_NG_ENC_KEY");
@:envField
static final API_NG_APP_ID:Null<String>;
@:envField
static final API_NG_ENC_KEY:Null<String>;
public static var instance(get, never):NewgroundsClient;
@@ -42,8 +45,8 @@ class NewgroundsClient
trace('[NEWGROUNDS] Initializing client...');
#if FEATURE_NEWGROUNDS_DEBUG
trace('[NEWGROUNDS] App ID: ${APP_ID}');
trace('[NEWGROUNDS] Encryption Key: ${ENCRYPTION_KEY}');
trace('[NEWGROUNDS] App ID: ${API_NG_APP_ID}');
trace('[NEWGROUNDS] Encryption Key: ${API_NG_ENC_KEY}');
#end
if (!hasValidCredentials())
@@ -54,9 +57,9 @@ class NewgroundsClient
@:nullSafety(Off)
{
NG.create(APP_ID, getSessionId(), #if FEATURE_NEWGROUNDS_DEBUG true #else false #end, onLoginResolved);
NG.create(API_NG_APP_ID, getSessionId(), #if FEATURE_NEWGROUNDS_DEBUG true #else false #end, onLoginResolved);
NG.core.setupEncryption(ENCRYPTION_KEY);
NG.core.setupEncryption(API_NG_ENC_KEY);
}
}
@@ -102,6 +105,12 @@ class NewgroundsClient
return;
}
if (NG.core.attemptingLogin)
{
trace("[NEWGROUNDS] Login attempt ongoing, will not login until finished.");
return;
}
if (onSuccess != null && onError != null)
{
NG.core.requestLogin(onLoginResolvedWithCallbacks.bind(_, onSuccess, onError));
@@ -176,12 +185,12 @@ class NewgroundsClient
*/
static function hasValidCredentials():Bool
{
return !(APP_ID == null
|| APP_ID == ""
|| (APP_ID != null && APP_ID.contains(" "))
|| ENCRYPTION_KEY == null
|| ENCRYPTION_KEY == ""
|| (ENCRYPTION_KEY != null && ENCRYPTION_KEY.contains(" ")));
return !(API_NG_APP_ID == null
|| API_NG_APP_ID == ""
|| (API_NG_APP_ID != null && API_NG_APP_ID.contains(" "))
|| API_NG_ENC_KEY == null
|| API_NG_ENC_KEY == ""
|| (API_NG_ENC_KEY != null && API_NG_ENC_KEY.contains(" ")));
}
function onLoginResolved(outcome:LoginOutcome):Void
@@ -1,4 +1,4 @@
package funkin.play.character;
package funkin.data.character;
import funkin.data.animation.AnimationData;
import funkin.modding.events.ScriptEvent;
@@ -8,6 +8,11 @@ import funkin.play.character.ScriptedCharacter.ScriptedBaseCharacter;
import funkin.play.character.ScriptedCharacter.ScriptedMultiSparrowCharacter;
import funkin.play.character.ScriptedCharacter.ScriptedPackerCharacter;
import funkin.play.character.ScriptedCharacter.ScriptedSparrowCharacter;
import funkin.play.character.AnimateAtlasCharacter;
import funkin.play.character.BaseCharacter;
import funkin.play.character.SparrowCharacter;
import funkin.play.character.MultiSparrowCharacter;
import funkin.play.character.PackerCharacter;
import funkin.util.assets.DataAssets;
import funkin.util.VersionUtil;
import haxe.Json;
+3 -2
View File
@@ -426,12 +426,13 @@ using funkin.data.song.migrator.SongDataMigrator;
}
}
public function parseEntryChartDataRawWithMigration(contents:String, ?fileName:String = 'raw', version:thx.semver.Version):Null<SongChartData>
public function parseEntryChartDataRawWithMigration(contents:String, ?fileName:String = 'raw', version:thx.semver.Version,
?variation:String):Null<SongChartData>
{
// If a version rule is not specified, do not check against it.
if (SONG_CHART_DATA_VERSION_RULE == null || VersionUtil.validateVersion(version, SONG_CHART_DATA_VERSION_RULE))
{
return parseEntryChartDataRaw(contents, fileName);
return parseEntryChartDataRaw(contents, fileName, variation);
}
else
{
+31
View File
@@ -0,0 +1,31 @@
package funkin.external.apple;
#if ((ios || macos) && cpp)
/**
* A utility class to manage the audio session on apple devices.
*/
@:build(funkin.util.macro.LinkerMacro.xml('project/Build.xml'))
@:include('AudioSession.hpp')
@:unreflective
extern class AudioSession
{
/**
* Initializes the audio session for playback.
*
* This function configures the shared AVAudioSession instance with the following settings:
* - Sets the audio session category to Playback, allowing Bluetooth A2DP and mixing with spoken audio interruptions.
* - For iOS 17.0 and above, disables interruption on route disconnect.
* - For iOS 14.5 and above, prefers no interruptions from system alerts.
*/
@:native('Apple_AudioSession_Initialize')
static function initialize():Void;
/**
* Sets the active state of the shared AVAudioSession.
*
* @param active Whether to activate or deactivate the audio session.
*/
@:native('Apple_AudioSession_SetActive')
static function setActive(active:Bool):Void;
}
#end
+20
View File
@@ -0,0 +1,20 @@
package funkin.external.apple;
#if ((ios || macos) && cpp)
/**
* A utility class to get information about the mem usage.
*/
@:build(funkin.util.macro.LinkerMacro.xml('project/Build.xml'))
@:include('MemoryUtil.hpp')
@:unreflective
extern class MemoryUtil
{
/**
* Retrieves the current process's resident set size (RSS) in bytes on Apple platforms.
*
* @return The resident set size (RSS) in bytes if successful; otherwise, returns 0 on failure.
*/
@:native('Apple_MemoryUtil_GetCurrentProcessRss')
static function getCurrentProcessRss():cpp.SizeT;
}
#end
+36
View File
@@ -0,0 +1,36 @@
package funkin.external.apple;
#if ((ios || macos) && cpp)
/**
* A utility class to get screen related informations on apple devices.
*/
@:build(funkin.util.macro.LinkerMacro.xml('project/Build.xml'))
@:include('ScreenUtil.hpp')
@:unreflective
extern class ScreenUtil
{
/**
* Retrieves the safe area insets for the current screen on iOS devices.
*
* This function populates the provided pointers with the safe area insets (in pixels) for the top, bottom, left, and right edges of the screen.
* On iOS 11 and later, it uses the main window's safeAreaInsets and converts them to pixel values using the screen scale.
* On earlier iOS versions, all insets are set to 0.0.
*
* @param top Pointer to a double to receive the top safe area inset (in pixels).
* @param bottom Pointer to a double to receive the bottom safe area inset (in pixels).
* @param left Pointer to a double to receive the left safe area inset (in pixels).
* @param right Pointer to a double to receive the right safe area inset (in pixels).
*/
@:native('Apple_ScreenUtil_GetSafeAreaInsets')
static function getSafeAreaInsets(top:cpp.RawPointer<Float>, bottom:cpp.RawPointer<Float>, left:cpp.RawPointer<Float>, right:cpp.RawPointer<Float>):Void;
/**
* Retrieves the size of the main screen in pixels.
*
* @param width Pointer to a double where the screen width (in pixels) will be stored.
* @param height Pointer to a double where the screen height (in pixels) will be stored.
*/
@:native('Apple_ScreenUtil_GetScreenSize')
static function getScreenSize(width:cpp.RawPointer<Float>, height:cpp.RawPointer<Float>):Void;
}
#end
+28
View File
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<xml>
<pragma once="true" />
<files id="haxe">
<compilerflag value="-I${this_dir}/include" />
</files>
<files id="__main__">
<compilerflag value="-I${this_dir}/include" />
</files>
<files id="external-apple">
<compilerflag value="-I${this_dir}/include" />
<file name="${this_dir}/src/AudioSession.mm" />
<file name="${this_dir}/src/MemoryUtil.mm" />
<file name="${this_dir}/src/ScreenUtil.mm" />
</files>
<target id="haxe">
<vflag name="-framework" value="UIKit" unless="macos" />
<vflag name="-framework" value="Foundation" />
<vflag name="-framework" value="AVFAudio" />
<files id="external-apple" />
</target>
</xml>
@@ -0,0 +1,18 @@
#pragma once
/**
* Initializes the audio session for playback.
*
* This function configures the shared AVAudioSession instance with the following settings:
* - Sets the audio session category to Playback, allowing Bluetooth A2DP and mixing with spoken audio interruptions.
* - For iOS 17.0 and above, disables interruption on route disconnect.
* - For iOS 14.5 and above, prefers no interruptions from system alerts.
*/
void Apple_AudioSession_Initialize();
/**
* Sets the active state of the shared AVAudioSession.
*
* @param active Whether to activate or deactivate the audio session.
*/
void Apple_AudioSession_SetActive(bool active);
@@ -0,0 +1,10 @@
#pragma once
#include <cstddef>
/**
* Retrieves the current process's resident set size (RSS) in bytes on Apple platforms.
*
* @return The resident set size (RSS) in bytes if successful; otherwise, returns 0 on failure.
*/
size_t Apple_MemoryUtil_GetCurrentProcessRss();
@@ -0,0 +1,23 @@
#pragma once
/**
* Retrieves the safe area insets for the current screen on iOS devices.
*
* This function populates the provided pointers with the safe area insets (in pixels) for the top, bottom, left, and right edges of the screen.
* On iOS 11 and later, it uses the main window's safeAreaInsets and converts them to pixel values using the screen scale.
* On earlier iOS versions, all insets are set to 0.0.
*
* @param top Pointer to a double to receive the top safe area inset (in pixels).
* @param bottom Pointer to a double to receive the bottom safe area inset (in pixels).
* @param left Pointer to a double to receive the left safe area inset (in pixels).
* @param right Pointer to a double to receive the right safe area inset (in pixels).
*/
void Apple_ScreenUtil_GetSafeAreaInsets(double* top, double* bottom, double* left, double* right);
/**
* Retrieves the size of the main screen in pixels.
*
* @param width Pointer to a double where the screen width (in pixels) will be stored.
* @param height Pointer to a double where the screen height (in pixels) will be stored.
*/
void Apple_ScreenUtil_GetScreenSize(double* width, double* height);
@@ -0,0 +1,43 @@
#import "AudioSession.hpp"
#import <TargetConditionals.h>
#import <Foundation/Foundation.h>
#import <AVFAudio/AVFAudio.h>
void Apple_AudioSession_Initialize()
{
#if TARGET_OS_IOS
AVAudioSession *session = [AVAudioSession sharedInstance];
NSError *error;
[session setCategory:AVAudioSessionCategoryPlayback mode:AVAudioSessionModeDefault options:AVAudioSessionCategoryOptionAllowBluetoothA2DP | AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers error:&error];
if (@available(iOS 17.0, *))
{
[session setPrefersInterruptionOnRouteDisconnect:false error:nil];
}
if (@available(iOS 14.5, *))
{
[session setPrefersNoInterruptionsFromSystemAlerts:true error:nil];
}
if (error)
NSLog(@"Unable to set category of audio session: %@", error);
#endif
}
void Apple_AudioSession_SetActive(bool active)
{
#if TARGET_OS_IOS
AVAudioSession *session = [AVAudioSession sharedInstance];
NSError *error;
[session setActive:active error:&error];
if (error)
NSLog(@"Unable to set active of audio session: %@", error);
#endif
}
+15
View File
@@ -0,0 +1,15 @@
#import "MemoryUtil.hpp"
#include <mach/mach.h>
size_t Apple_MemoryUtil_GetCurrentProcessRss()
{
struct task_basic_info info;
mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT;
if (task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &count) != KERN_SUCCESS)
return 0;
return info.resident_size;
}
+45
View File
@@ -0,0 +1,45 @@
#import "ScreenUtil.hpp"
#import <TargetConditionals.h>
#if TARGET_OS_IOS
#import <UIKit/UIKit.h>
#endif
#import <Foundation/Foundation.h>
void Apple_ScreenUtil_GetSafeAreaInsets(double* top, double* bottom, double* left, double* right)
{
#if TARGET_OS_IOS
if (@available(iOS 11, *))
{
UIWindow* window = [UIApplication sharedApplication].windows[0];
UIEdgeInsets safeAreaInsets = window.safeAreaInsets;
float scale = [UIScreen mainScreen].scale;
(*top) = safeAreaInsets.top * scale;
(*bottom) = safeAreaInsets.bottom * scale;
(*left) = safeAreaInsets.left * scale;
(*right) = safeAreaInsets.right * scale;
return;
}
#endif
(*top) = 0.0;
(*bottom) = 0.0;
(*left) = 0.0;
(*right) = 0.0;
}
void Apple_ScreenUtil_GetScreenSize(double* width, double* height)
{
#if TARGET_OS_IOS
CGRect screenRect = [[UIScreen mainScreen] bounds];
float scale = [UIScreen mainScreen].scale;
(*width) = (double)screenRect.size.width * scale;
(*height) = (double)screenRect.size.height * scale;
#else
(*width) = 0.0;
(*width) = 0.0;
#endif
}
-17
View File
@@ -1,17 +0,0 @@
package funkin.external.ios;
#if ios
/**
* A Utility class to manage iOS audio.
*/
@:build(funkin.util.macro.LinkerMacro.xml('project/Build.xml'))
@:include('AudioSession.hpp')
@:unreflective
extern class AudioSession
{
@:native('initialize')
static function initialize():Void;
@:native('setActive')
static function setActive(active:Bool):Void;
}
#end
-18
View File
@@ -1,18 +0,0 @@
package funkin.external.ios;
#if ios
/**
* A Utility class to get iOS screen related informations.
*/
@:build(funkin.util.macro.LinkerMacro.xml('project/Build.xml'))
@:include('ScreenUtil.hpp')
@:unreflective
extern class ScreenUtil
{
@:native('getSafeAreaInsets')
static function getSafeAreaInsets(top:cpp.RawPointer<Float>, bottom:cpp.RawPointer<Float>, left:cpp.RawPointer<Float>, right:cpp.RawPointer<Float>):Void;
@:native('getScreenSize')
static function getScreenSize(width:cpp.RawPointer<Float>, height:cpp.RawPointer<Float>):Void;
}
#end
-29
View File
@@ -1,29 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xml>
<pragma once="true" />
<files id="haxe">
<compilerflag value="-I${this_dir}/ios/include" if="iphoneos || iphonesim" />
</files>
<files id="__main__">
<compilerflag value="-I${this_dir}/ios/include" if="iphoneos || iphonesim" />
</files>
<files id="external-ios" dir="${this_dir}/ios" if="iphoneos || iphonesim">
<compilerflag value="-I${this_dir}/ios/include" />
<file name="src/ScreenUtil.mm" />
<file name="src/AudioSession.mm" />
</files>
<target id="haxe">
<section if="iphoneos || iphonesim">
<vflag name="-framework" value="UIKit" />
<vflag name="-framework" value="Foundation" />
<vflag name="-framework" value="AVFAudio" />
</section>
<files id="external-ios" if="iphoneos || iphonesim" />
</target>
</xml>
@@ -1,4 +0,0 @@
#pragma once
void initialize();
void setActive(bool active);
@@ -1,4 +0,0 @@
#pragma once
void getSafeAreaInsets(double* top, double* bottom, double* left, double* right);
void getScreenSize(double* width, double* height);
@@ -1,41 +0,0 @@
#import <Foundation/Foundation.h>
#import <AVFAudio/AVFAudio.h>
void initialize()
{
AVAudioSession *session = [AVAudioSession sharedInstance];
NSError *error;
[session setCategory:AVAudioSessionCategoryPlayback
mode:AVAudioSessionModeDefault
options:AVAudioSessionCategoryOptionAllowBluetoothA2DP|AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers
error:&error];
if (@available(iOS 17.0, *))
{
[session setPrefersInterruptionOnRouteDisconnect:false error:nil];
}
if (@available(iOS 14.5, *))
{
[session setPrefersNoInterruptionsFromSystemAlerts:true error:nil];
}
if (error)
{
NSLog(@"Unable to set category of audio session: %@", error);
}
}
void setActive(bool active)
{
AVAudioSession *session = [AVAudioSession sharedInstance];
NSError *error;
[session setActive:YES error:&error];
if (error)
{
NSLog(@"Unable to set active of audio session: %@", error);
}
}
@@ -1,36 +0,0 @@
#import "ScreenUtil.hpp"
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
void getSafeAreaInsets(double* top, double* bottom, double* left, double* right)
{
if (@available(iOS 11, *))
{
UIWindow* window = [UIApplication sharedApplication].windows[0];
UIEdgeInsets safeAreaInsets = window.safeAreaInsets;
float scale = [UIScreen mainScreen].scale;
(*top) = safeAreaInsets.top * scale;
(*bottom) = safeAreaInsets.bottom * scale;
(*left) = safeAreaInsets.left * scale;
(*right) = safeAreaInsets.right * scale;
return;
}
(*top) = 0.0;
(*bottom) = 0.0;
(*left) = 0.0;
(*right) = 0.0;
}
void getScreenSize(double* width, double* height)
{
CGRect screenRect = [[UIScreen mainScreen] bounds];
float scale = [UIScreen mainScreen].scale;
(*width) = (double)screenRect.size.width * scale;
(*height) = (double)screenRect.size.height * scale;
}
+9 -1
View File
@@ -56,10 +56,18 @@ extern class WinAPI
@:native('WINAPI_DisableWindowsGhosting')
static function disableWindowsGhosting():Void;
/**
* Retrieves the current working set size (in bytes) of the process.
*
* @return The size of the working set memory used by the process.
*/
@:native('WINAPI_GetProcessMemoryWorkingSetSize')
static function getProcessMemoryWorkingSetSize():cpp.SizeT;
/**
* Sets the dark mode for the active window.
*
* @param enable A boolean value indicating whether to enable (true) or disable (false) dark mode.
* @param enable Whether to enable or disable dark mode.
*/
@:native('WINAPI_SetDarkMode')
static function setDarkMode(enable:Bool):Void;
+3 -3
View File
@@ -10,7 +10,7 @@
<compilerflag value="-I${this_dir}/include" />
</files>
<files id="winapi">
<files id="external-windows">
<compilerflag value="-I${this_dir}/include" />
<file name="${this_dir}/src/winapi.cpp" />
@@ -24,13 +24,13 @@
<lib name="-ladvapi32" />
</section>
<section if="windows" unless="mingw">
<section unless="mingw">
<lib name="user32.lib" />
<lib name="kernel32.lib" />
<lib name="dwmapi.lib" />
<lib name="advapi32.lib" />
</section>
<files id="winapi" />
<files id="external-windows" />
</target>
</xml>
+23 -8
View File
@@ -1,51 +1,66 @@
#pragma once
/**
* @brief Shows an error message box
* Shows an error message box.
*
* @param message The error message to display
* @param title The title of the message box
*/
void WINAPI_ShowError(const char *message, const char *title);
/**
* @brief Shows a warning message box
* Shows a warning message box.
*
* @param message The warning message to display
* @param title The title of the message box
*/
void WINAPI_ShowWarning(const char *message, const char *title);
/**
* @brief Shows an information message box
* Shows an information message box.
*
* @param message The information message to display
* @param title The title of the message box
*/
void WINAPI_ShowInformation(const char *message, const char *title);
/**
* @brief Shows a question message box with OK/Cancel buttons
* Shows a question message box with OK/Cancel buttons.
*
* @param message The question message to display
* @param title The title of the message box
*/
void WINAPI_ShowQuestion(const char *message, const char *title);
/**
* @brief Disables Windows error reporting dialogs
* Disables Windows error reporting dialogs.
*/
void WINAPI_DisableErrorReporting();
/**
* @brief Disables Windows ghosting for the current process
* Disables Windows ghosting for the current process.
*/
void WINAPI_DisableWindowsGhosting();
/**
* @brief Sets dark mode for the active window
* Retrieves the current working set size (in bytes) of the calling process.
*
* This function queries the operating system for the amount of physical memory currently allocated to the process (its working set).
*
* @return The working set size in bytes. Returns 0 if the query fails.
*/
size_t WINAPI_GetProcessMemoryWorkingSetSize();
/**
* Sets dark mode for the active window.
*
* @param enable True to enable dark mode, false to disable
*/
void WINAPI_SetDarkMode(bool enable);
/**
* @brief Checks if the system is using dark mode
* Checks if the system is using dark mode.
*
* @return True if system is in dark mode, false otherwise
*/
bool WINAPI_IsSystemDarkMode();
+10
View File
@@ -41,6 +41,16 @@ void WINAPI_DisableWindowsGhosting()
DisableProcessWindowsGhosting();
}
size_t WINAPI_GetProcessMemoryWorkingSetSize()
{
PROCESS_MEMORY_COUNTERS_EX pmc;
if (GetProcessMemoryInfo(GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc)))
return pmc.WorkingSetSize;
return 0;
}
void WINAPI_SetDarkMode(bool enable)
{
HWND window = GetActiveWindow();
@@ -0,0 +1,10 @@
package funkin.graphics.video;
/**
* A script that can be tied to a FunkinVideoSprite.
* Create a scripted class that extends FunkinVideoSprite to use this.
*/
#if hxvlc
@:hscriptClass
class ScriptedFunkinVideoSprite extends funkin.graphics.video.FunkinVideoSprite implements polymod.hscript.HScriptedClass {}
#end
+22 -68
View File
@@ -7,11 +7,11 @@ import extension.admob.AdmobBannerSize;
import extension.admob.AdmobEvent;
import flixel.FlxG;
import funkin.play.cutscene.VideoCutscene;
import funkin.util.macro.EnvironmentConfigMacro;
/**
* Provides utility functions for working with admob advertisements.
*/
@:build(funkin.util.macro.EnvironmentMacro.build())
@:nullSafety
class AdMobUtil
{
@@ -29,27 +29,20 @@ class AdMobUtil
/**
* AdMob publisher ID used for the application.
*/
static final ADMOB_PUBLISHER:String = EnvironmentConfigMacro.environmentConfig.get("MOBILE_GLOBAL_ADMOB_PUBLISHER");
@:envField
static final ADMOB_PUBLISHER:Null<String>;
/**
* Test ad unit IDs for development and testing purposes.
* These IDs are provided by Google AdMob for testing ads without incurring costs.
* They should not be used in production applications.
*/
/**
* Ad unit ID for displaying banner ads.
*/
static final BANNER_AD_UNIT_ID:String = #if mobile EnvironmentConfigMacro.environmentConfig.get(#if android "ANDROID_ADMOB_BANNER_ID" #else "IOS_ADMOB_BANNER_ID" #end) #else "" #end;
@:envField
static final ADMOB_BANNER_AD_UNIT_ID:Null<String>;
/**
* Ad unit ID for displaying interstitial ads.
*/
static final INTERSTITIAL_AD_UNIT_ID:String = #if mobile EnvironmentConfigMacro.environmentConfig.get(#if android "ANDROID_ADMOB_INTERSTITIAL_ID" #else "IOS_ADMOB_INTERSTITIAL_ID" #end) #else "" #end;
/**
* Ad unit ID for displaying rewarded ads.
*/
static final REWARDED_AD_UNIT_ID:String = "";
@:envField
static final ADMOB_INTERSTITIAL_AD_UNIT_ID:Null<String>;
#else
/**
@@ -67,7 +60,7 @@ class AdMobUtil
* - Android: "9214589741" (test ad unit ID)
* - iOS: "2435281174" (test ad unit ID)
*/
static final BANNER_AD_UNIT_ID:String = #if android "9214589741" #elseif ios "2435281174" #else "" #end;
static final ADMOB_BANNER_AD_UNIT_ID:String = #if android "9214589741" #elseif ios "2435281174" #else "" #end;
/**
* Ad unit ID for displaying interstitial ads.
@@ -77,17 +70,7 @@ class AdMobUtil
* - Android: "1033173712" (test ad unit ID)
* - iOS: "4411468910" (test ad unit ID)
*/
static final INTERSTITIAL_AD_UNIT_ID:String = #if android "1033173712" #elseif ios "4411468910" #else "" #end;
/**
* Ad unit ID for displaying rewarded ads.
* Test IDs are used for Android and iOS platforms, while non-supported platforms default to an empty string.
* Replace with your actual interstitial video ad unit ID for production.
*
* - Android: "8691691433" (test ad unit ID)
* - iOS: "5135589807" (test ad unit ID)
*/
static final REWARDED_AD_UNIT_ID:String = #if android "8691691433" #elseif ios "5135589807" #else "" #end;
static final ADMOB_INTERSTITIAL_AD_UNIT_ID:String = #if android "1033173712" #elseif ios "4411468910" #else "" #end;
#end
/**
@@ -129,7 +112,7 @@ class AdMobUtil
}
#end
trace(event.toString());
Sys.println(event.toString());
});
Admob.configureConsentMetadata(Admob.getTCFConsentForPurpose(0) == 1, StringTools.startsWith(Admob.getUSPrivacy(), '1Y'));
@@ -145,9 +128,13 @@ class AdMobUtil
public static inline function addBanner(size:Int = AdmobBannerSize.BANNER, align:Int = AdmobBannerAlign.BOTTOM_CENTER):Void
{
#if FEATURE_MOBILE_IAP
if (InAppPurchasesUtil.isPurchased(InAppPurchasesUtil.UPGRADE_PRODUCT_ID)) return;
if (InAppPurchasesUtil.isPurchased(InAppPurchasesUtil.UPGRADE_PRODUCT_ID))
{
return;
}
#end
Admob.showBanner([AdMobUtil.ADMOB_PUBLISHER, AdMobUtil.BANNER_AD_UNIT_ID].join('/'), size, align);
Admob.showBanner([AdMobUtil.ADMOB_PUBLISHER, AdMobUtil.ADMOB_BANNER_AD_UNIT_ID].join('/'), size, align);
}
/**
@@ -156,8 +143,12 @@ class AdMobUtil
public static inline function removeBanner():Void
{
#if FEATURE_MOBILE_IAP
if (InAppPurchasesUtil.isPurchased(InAppPurchasesUtil.UPGRADE_PRODUCT_ID)) return;
if (InAppPurchasesUtil.isPurchased(InAppPurchasesUtil.UPGRADE_PRODUCT_ID))
{
return;
}
#end
Admob.hideBanner();
}
@@ -195,44 +186,7 @@ class AdMobUtil
Admob.onEvent.add(interstitialEvent);
Admob.loadInterstitial([AdMobUtil.ADMOB_PUBLISHER, AdMobUtil.INTERSTITIAL_AD_UNIT_ID].join('/'));
}
/**
* Loads a rewarded ad using Admob.
*
* @param onRewardedFinish Callback function to be called when the rewarded ad has been completed by the user.
*/
public static function loadRewarded(onRewardedFinish:Void->Void):Void
{
#if FEATURE_MOBILE_IAP
if (InAppPurchasesUtil.isPurchased(InAppPurchasesUtil.UPGRADE_PRODUCT_ID))
{
if (onRewardedFinish != null) onRewardedFinish();
return;
}
#end
function rewardedEvent(event:AdmobEvent):Void
{
if (event.name == AdmobEvent.REWARDED_LOADED)
{
Admob.showRewarded();
}
else if (event.name == AdmobEvent.REWARDED_DISMISSED
|| event.name == AdmobEvent.REWARDED_FAILED_TO_LOAD
|| event.name == AdmobEvent.REWARDED_FAILED_TO_SHOW)
{
if (onRewardedFinish != null) onRewardedFinish();
Admob.onEvent.remove(rewardedEvent);
}
}
Admob.onEvent.add(rewardedEvent);
Admob.loadRewarded([AdMobUtil.ADMOB_PUBLISHER, AdMobUtil.REWARDED_AD_UNIT_ID].join('/'));
Admob.loadInterstitial([AdMobUtil.ADMOB_PUBLISHER, AdMobUtil.ADMOB_INTERSTITIAL_AD_UNIT_ID].join('/'));
}
/**
+1 -1
View File
@@ -1,7 +1,7 @@
package funkin.mobile.util;
#if ios
import funkin.external.ios.ScreenUtil as NativeScreenUtil;
import funkin.external.apple.ScreenUtil as NativeScreenUtil;
#elseif android
import funkin.external.android.ScreenUtil as NativeScreenUtil;
#end
+3 -4
View File
@@ -15,7 +15,7 @@ import funkin.data.stage.StageRegistry;
import funkin.data.stickers.StickerRegistry;
import funkin.data.freeplay.album.AlbumRegistry;
import funkin.modding.module.ModuleHandler;
import funkin.play.character.CharacterData.CharacterDataParser;
import funkin.data.character.CharacterData.CharacterDataParser;
import funkin.save.Save;
import funkin.util.FileUtil;
import funkin.util.macro.ClassMacro;
@@ -271,6 +271,8 @@ class PolymodHandler
Polymod.addImportAlias('funkin.data.dialogue.conversation.ConversationRegistry', funkin.data.dialogue.ConversationRegistry);
Polymod.addImportAlias('funkin.data.dialogue.dialoguebox.DialogueBoxRegistry', funkin.data.dialogue.DialogueBoxRegistry);
Polymod.addImportAlias('funkin.data.dialogue.speaker.SpeakerRegistry', funkin.data.dialogue.SpeakerRegistry);
Polymod.addImportAlias('funkin.play.character.CharacterDataParser', funkin.data.character.CharacterData.CharacterDataParser);
Polymod.addImportAlias('funkin.play.character.CharacterData.CharacterDataParser', funkin.data.character.CharacterData.CharacterDataParser);
// `funkin.util.FileUtil` has unrestricted access to the file system.
Polymod.addImportAlias('funkin.util.FileUtil', funkin.util.FileUtilSandboxed);
@@ -379,9 +381,6 @@ class PolymodHandler
// Can load native processes on the host operating system.
Polymod.blacklistImport('openfl.desktop.NativeProcess');
// Contains critical private environment variables.
Polymod.blacklistImport('funkin.util.macro.EnvironmentConfigMacro');
// `funkin.api.*`
// Contains functions which may allow for cheating and such.
for (cls in ClassMacro.listClassesInPackage('funkin.api'))
@@ -0,0 +1,8 @@
package funkin.modding.base;
/**
* A script that can be tied to a FlxStrip.
* Create a scripted class that extends FlxStrip to use this.
*/
@:hscriptClass
class ScriptedFlxStrip extends flixel.FlxStrip implements HScriptedClass {}
@@ -0,0 +1,8 @@
package funkin.modding.base;
/**
* A script that can be tied to an FlxTypedGroup.
* Create a scripted class that extends FlxTypedGroup to use this.
*/
@:hscriptClass
class ScriptedFlxTypedGroup extends flixel.group.FlxGroup.FlxTypedGroup<Dynamic> implements HScriptedClass {}
@@ -2,6 +2,7 @@ package funkin.modding.events;
import funkin.modding.IScriptedClass.IPlayStateScriptedClass;
import funkin.modding.IScriptedClass;
import funkin.modding.module.Module;
/**
* Utility functions to assist with handling scripted classes.
@@ -32,6 +33,13 @@ class ScriptEventDispatcher
case CREATE:
target.onCreate(event);
return;
case STATE_CREATE:
if (Std.isOfType(target, Module))
{
var t:Module = cast(target, Module);
t.onStateCreate(event);
}
return;
case DESTROY:
target.onDestroy(event);
return;
@@ -12,6 +12,14 @@ enum abstract ScriptEventType(String) from String to String
*/
var CREATE = 'CREATE';
/**
* Called when the relevant object is fully created and ready to be used.
* This assumes all data is loaded and ready to go.
*
* This event is not cancelable.
*/
var STATE_CREATE = 'STATE_CREATE';
/**
* Called when the relevant object is destroyed.
* This should perform relevant cleanup to ensure good performance.
+4
View File
@@ -234,4 +234,8 @@ class Module implements IPlayStateScriptedClass implements IStateChangingScripte
* Called when the song has been restarted.
*/
public function onSongRetry(event:SongRetryEvent) {}
/**
* Called when any state is created.
*/
public function onStateCreate(event:ScriptEvent) {}
}
+10 -8
View File
@@ -35,7 +35,7 @@ import funkin.modding.events.ScriptEvent;
import funkin.api.newgrounds.Events;
import funkin.modding.events.ScriptEventDispatcher;
import funkin.play.character.BaseCharacter;
import funkin.play.character.CharacterData.CharacterDataParser;
import funkin.data.character.CharacterData.CharacterDataParser;
import funkin.play.components.HealthIcon;
import funkin.play.components.PopUpStuff;
import funkin.play.cutscene.dialogue.Conversation;
@@ -972,6 +972,8 @@ class PlayState extends MusicBeatSubState
previousDifficulty = currentDifficulty;
currentStage?.resetStage();
dispatchEvent(retryEvent);
resetCamera();
@@ -1016,8 +1018,6 @@ class PlayState extends MusicBeatSubState
vocals.opponentVolume = 1;
}
currentStage?.resetStage();
if (!fromDeathState)
{
playerStrumline.vwooshNotes();
@@ -1114,7 +1114,7 @@ class PlayState extends MusicBeatSubState
var pauseButtonCheck:Bool = false;
var androidPause:Bool = false;
// So the player wouldn't miss when pressing the pause utton
// So the player wouldn't miss when pressing the pause button
#if mobile
pauseButtonCheck = TouchUtil.pressAction(pauseButton);
#end
@@ -1424,9 +1424,6 @@ class PlayState extends MusicBeatSubState
// super.dispatchEvent(event) dispatches event to module scripts.
super.dispatchEvent(event);
// Dispatch event to note kind scripts
NoteKindManager.callEvent(event);
// Dispatch event to stage script.
ScriptEventDispatcher.callEvent(currentStage, event);
@@ -1438,6 +1435,9 @@ class PlayState extends MusicBeatSubState
// Dispatch event to conversation script.
ScriptEventDispatcher.callEvent(currentConversation, event);
// Dispatch event to note kind scripts
NoteKindManager.callEvent(event);
}
/**
@@ -3174,7 +3174,7 @@ class PlayState extends MusicBeatSubState
#end
#if mobile
pauseButtonCheck = TouchUtil.pressAction(pauseButton);
pauseButtonCheck = TouchUtil.overlapsComplex(pauseButton);
#end
if (currentConversation != null)
@@ -3534,6 +3534,8 @@ class PlayState extends MusicBeatSubState
forEachPausedSound((s) -> s.destroy());
if (VideoCutscene.isPlaying()) VideoCutscene.destroyVideo();
FlxTween.globalManager.clear();
FlxTimer.globalManager.clear();
+4 -4
View File
@@ -9,12 +9,12 @@ package funkin.play;
class PlayStatePlaylist
{
/**
* Whether the game is currently in Story Mode. If false, we are in Free Play Mode.
* Whether the game is currently in Story Mode. If false, we are in Freeplay Mode.
*/
public static var isStoryMode:Bool = false;
/**
* The loist of upcoming songs to be played.
* The list of upcoming songs to be played.
* When the user completes a song in Story Mode, the first entry in this list is played.
* When this list is empty, move to the Results screen instead.
*/
@@ -26,13 +26,13 @@ class PlayStatePlaylist
public static var campaignScore:Int = 0;
/**
* The title of this playlist, for example `Week 4` or `Weekend 1`
* The title of this playlist, for example `Week 4` or `Weekend 1`.
*/
public static var campaignTitle:String = 'UNKNOWN';
/**
* The internal ID of the current playlist, for example `week4` or `weekend-1`.
* @default `null`, used when no playlist is loaded
* @default `null`, used when no playlist is loaded.
*/
public static var campaignId:Null<String> = null;
@@ -15,7 +15,7 @@ import flixel.util.FlxColor;
import flixel.util.FlxDestroyUtil;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
import funkin.modding.events.ScriptEvent;
import funkin.play.character.CharacterData.CharacterRenderType;
import funkin.data.character.CharacterData.CharacterRenderType;
import flixel.util.FlxDirectionFlags;
import openfl.display.BitmapData;
import openfl.display.BlendMode;
@@ -2,8 +2,9 @@ package funkin.play.character;
import flixel.math.FlxPoint;
import funkin.modding.events.ScriptEvent;
import funkin.play.character.CharacterData.CharacterDataParser;
import funkin.play.character.CharacterData.CharacterRenderType;
import funkin.data.character.CharacterData;
import funkin.data.character.CharacterData.CharacterDataParser;
import funkin.data.character.CharacterData.CharacterRenderType;
import funkin.play.stage.Bopper;
import funkin.play.notes.NoteDirection;
@@ -4,7 +4,7 @@ import flixel.graphics.frames.FlxAtlasFrames;
import flixel.graphics.frames.FlxFramesCollection;
import funkin.modding.events.ScriptEvent;
import funkin.util.assets.FlxAnimationUtil;
import funkin.play.character.CharacterData.CharacterRenderType;
import funkin.data.character.CharacterData.CharacterRenderType;
/**
* For some characters which use Sparrow atlases, the spritesheets need to be split
@@ -2,7 +2,7 @@ package funkin.play.character;
import flixel.graphics.frames.FlxFramesCollection;
import funkin.modding.events.ScriptEvent;
import funkin.play.character.CharacterData.CharacterRenderType;
import funkin.data.character.CharacterData.CharacterRenderType;
import funkin.util.assets.FlxAnimationUtil;
/**
@@ -3,7 +3,7 @@ package funkin.play.character;
import funkin.modding.events.ScriptEvent;
import funkin.util.assets.FlxAnimationUtil;
import flixel.graphics.frames.FlxFramesCollection;
import funkin.play.character.CharacterData.CharacterRenderType;
import funkin.data.character.CharacterData.CharacterRenderType;
/**
* A SparrowCharacter is a Character which is rendered by
+2 -2
View File
@@ -1,10 +1,10 @@
package funkin.play.components;
import funkin.play.character.CharacterData;
import funkin.data.character.CharacterData;
import flixel.FlxSprite;
import flixel.math.FlxMath;
import flixel.math.FlxPoint;
import funkin.play.character.CharacterData.CharacterDataParser;
import funkin.data.character.CharacterData.CharacterDataParser;
import funkin.graphics.FunkinSprite;
import funkin.util.MathUtil;
@@ -347,6 +347,38 @@ class VideoCutscene
// throw "Not implemented!";
}
}
/**
* Destroy the active cutscene, if any. Separate from finishVideo() so that it doesn't trigger onCutsceneFinish().
*/
public static function destroyVideo()
{
#if html5
if (vid != null) PlayState.instance.remove(vid);
#end
#if hxvlc
if (vid != null)
{
vid.stop();
PlayState.instance.remove(vid);
}
#end
#if (html5 || hxvlc)
if (vid != null)
{
vid?.destroy();
vid = null;
}
#end
if (blackScreen != null)
{
PlayState.instance.remove(blackScreen);
blackScreen = null;
}
}
}
enum CutsceneType
@@ -10,7 +10,6 @@ import funkin.modding.events.ScriptEvent;
import funkin.audio.FunkinSound;
import funkin.modding.IScriptedClass.IDialogueScriptedClass;
import flixel.util.FlxColor;
import funkin.ui.FullScreenScaleMode;
import funkin.data.dialogue.DialogueBoxData;
import funkin.data.dialogue.DialogueBoxRegistry;
@@ -28,6 +27,11 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple
*/
var animationOffsets:Map<String, Array<Float>> = new Map<String, Array<Float>>();
/**
* The position without offsets applied.
*/
var originalPosition:Array<Float> = [0, 0];
/**
* The current animation offset being used.
*/
@@ -36,13 +40,10 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple
function set_animOffsets(value:Array<Float>):Array<Float>
{
if (animOffsets == null) animOffsets = [0, 0];
if ((animOffsets[0] == value[0]) && (animOffsets[1] == value[1])) return value;
if (animOffsets == value) return value;
var xDiff:Float = value[0] - animOffsets[0];
var yDiff:Float = value[1] - animOffsets[1];
this.x += xDiff;
this.y += yDiff;
this.x = globalOffsets[0] + originalPosition[0] + value[0];
this.y = globalOffsets[1] + originalPosition[1] + value[1];
return animOffsets = value;
}
@@ -57,17 +58,13 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple
if (globalOffsets == null) globalOffsets = [0, 0];
if (globalOffsets == value) return value;
var xDiff:Float = value[0] - globalOffsets[0];
var yDiff:Float = value[1] - globalOffsets[1];
this.screenCenter();
this.x += xDiff;
this.y += yDiff;
originalPosition[0] = this.x;
originalPosition[1] = this.y;
if (FullScreenScaleMode.wideScale.x != 1)
{
this.x *= fullscreenScale;
this.y = this.y * fullscreenScale + (-100 * fullscreenScale);
}
this.x = value[0] + originalPosition[0] + animOffsets[0];
this.y = value[1] + originalPosition[1] + animOffsets[1];
return globalOffsets = value;
}
@@ -96,16 +93,6 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple
return this.speed;
}
/**
* A value used for scaling object's parameters on mobile.
*/
var fullscreenScale(get, never):Float;
function get_fullscreenScale():Float
{
return FullScreenScaleMode.wideScale.x - 0.05;
}
public function new(id:String, ?params:Dynamic)
{
super();
@@ -120,9 +107,9 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple
public function onCreate(event:ScriptEvent):Void
{
this.globalOffsets = [0, 0];
this.x = 0;
this.y = 0;
this.globalOffsets = [0, 0];
this.alpha = 1;
loadSpritesheet();
@@ -207,11 +194,6 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple
{
if (scale == null) scale = 1.0;
if (FullScreenScaleMode.wideScale.x != 1)
{
scale *= fullscreenScale;
}
this.boxSprite.scale.x = scale;
this.boxSprite.scale.y = scale;
this.boxSprite.updateHitbox();
@@ -241,6 +223,9 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple
{
super.revive();
this.x = 0;
this.y = 0;
this.globalOffsets = [0, 0];
this.visible = true;
this.alpha = 1.0;
}
@@ -309,13 +294,6 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple
textDisplay.x += _data.text.offsets[0];
textDisplay.y += _data.text.offsets[1];
if (FullScreenScaleMode.wideScale.x != 1)
{
textDisplay.fieldWidth *= fullscreenScale;
textDisplay.x *= fullscreenScale;
textDisplay.y *= fullscreenScale;
}
add(textDisplay);
}
+19 -34
View File
@@ -8,7 +8,6 @@ import funkin.util.assets.FlxAnimationUtil;
import funkin.modding.IScriptedClass.IDialogueScriptedClass;
import funkin.data.dialogue.SpeakerData;
import funkin.data.dialogue.SpeakerRegistry;
import funkin.ui.FullScreenScaleMode;
/**
* The character sprite which displays during dialogue.
@@ -32,6 +31,11 @@ class Speaker extends FlxSprite implements IDialogueScriptedClass implements IRe
*/
var animationOffsets:Map<String, Array<Float>> = new Map<String, Array<Float>>();
/**
* The position without offsets applied.
*/
var originalPosition:Array<Float> = [0, 0];
/**
* The current animation offset being used.
*/
@@ -40,13 +44,10 @@ class Speaker extends FlxSprite implements IDialogueScriptedClass implements IRe
function set_animOffsets(value:Array<Float>):Array<Float>
{
if (animOffsets == null) animOffsets = [0, 0];
if ((animOffsets[0] == value[0]) && (animOffsets[1] == value[1])) return value;
if (animOffsets == value) return value;
var xDiff:Float = value[0] - animOffsets[0];
var yDiff:Float = value[1] - animOffsets[1];
this.x += xDiff;
this.y += yDiff;
this.x = globalOffsets[0] + originalPosition[0] + value[0];
this.y = globalOffsets[1] + originalPosition[1] + value[1];
return animOffsets = value;
}
@@ -61,31 +62,12 @@ class Speaker extends FlxSprite implements IDialogueScriptedClass implements IRe
if (globalOffsets == null) globalOffsets = [0, 0];
if (globalOffsets == value) return value;
var xDiff:Float = value[0] - globalOffsets[0];
var yDiff:Float = value[1] - globalOffsets[1];
this.x += xDiff;
this.y += yDiff;
if (FullScreenScaleMode.wideScale.x != 1)
{
this.x *= fullscreenScale;
this.y = this.y * fullscreenScale + (-100 * fullscreenScale);
}
this.x = value[0] + originalPosition[0] + animOffsets[0];
this.y = value[1] + originalPosition[1] + animOffsets[1];
return globalOffsets = value;
}
/**
* A value used for scaling object's parameters on mobile.
*/
var fullscreenScale(get, never):Float;
function get_fullscreenScale():Float
{
return FullScreenScaleMode.wideScale.x - 0.05;
}
public function new(id:String, ?params:Dynamic)
{
super();
@@ -105,9 +87,9 @@ class Speaker extends FlxSprite implements IDialogueScriptedClass implements IRe
*/
public function onCreate(event:ScriptEvent):Void
{
this.globalOffsets = [0, 0];
this.x = 0;
this.y = 0;
this.globalOffsets = [0, 0];
this.alpha = 1;
loadSpritesheet();
@@ -127,6 +109,9 @@ class Speaker extends FlxSprite implements IDialogueScriptedClass implements IRe
{
super.revive();
this.x = 0;
this.y = 0;
this.globalOffsets = [0, 0];
this.visible = true;
this.alpha = 1.0;
@@ -156,6 +141,11 @@ class Speaker extends FlxSprite implements IDialogueScriptedClass implements IRe
this.antialiasing = true;
}
this.screenCenter();
originalPosition[0] = this.x;
originalPosition[1] = this.y;
this.flipX = _data.flipX;
this.flipY = _data.flipY;
this.globalOffsets = _data.offsets;
@@ -170,11 +160,6 @@ class Speaker extends FlxSprite implements IDialogueScriptedClass implements IRe
{
if (scale == null) scale = 1.0;
if (FullScreenScaleMode.wideScale.x != 1)
{
scale *= fullscreenScale;
}
this.scale.x = scale;
this.scale.y = scale;
this.updateHitbox();
@@ -1,7 +1,7 @@
package funkin.play.event;
import funkin.data.event.SongEventSchema;
import funkin.play.character.CharacterData.HealthIconData;
import funkin.data.character.CharacterData.HealthIconData;
import funkin.data.song.SongData;
import funkin.data.song.SongData.SongEventData;
@@ -0,0 +1,8 @@
package funkin.play.notes;
/**
* A script that can be tied to a Strumline.
* Create a scripted class that extends Strumline to use this.
*/
@:hscriptClass
class ScriptedStrumline extends funkin.play.notes.Strumline implements polymod.hscript.HScriptedClass {}
+8 -2
View File
@@ -142,9 +142,15 @@ class Strumline extends FlxSpriteGroup
var background:FunkinSprite;
var strumlineNotes:FlxTypedSpriteGroup<StrumlineNote>;
/**
* The strumline notes (the receptors) themselves.
*/
public var strumlineNotes:FlxTypedSpriteGroup<StrumlineNote>;
var noteSplashes:FlxTypedSpriteGroup<NoteSplash>;
var noteHoldCovers:FlxTypedSpriteGroup<NoteHoldCover>;
/**
* Hold note covers.
*/
public var noteHoldCovers:FlxTypedSpriteGroup<NoteHoldCover>;
var notesVwoosh:FlxTypedSpriteGroup<NoteSprite>;
var holdNotesVwoosh:FlxTypedSpriteGroup<SustainTrail>;
+69 -60
View File
@@ -846,61 +846,91 @@ class SongDifficulty
{
var suffix:String = (variation != null && variation != '' && variation != 'default') ? '-$variation' : '';
// Automatically resolve voices by removing suffixes.
// For example, if `Voices-bf-car-erect.ogg` does not exist, check for `Voices-bf-erect.ogg`.
// Then, check for `Voices-bf-car.ogg`, then `Voices-bf.ogg`.
if (characters.playerVocals == null)
{
var playerId:String = characters.player;
var playerVoice:String = Paths.voices(this.song.id, '-${playerId}$suffix');
while (playerVoice != null && !Assets.exists(playerVoice))
{
// Remove the last suffix.
// For example, bf-car becomes bf.
playerId = playerId.split('-').slice(0, -1).join('-');
// Try again.
playerVoice = playerId == '' ? null : Paths.voices(this.song.id, '-${playerId}$suffix');
}
if (playerVoice == null)
{
// Try again without $suffix.
playerId = characters.player;
playerVoice = Paths.voices(this.song.id, '-${playerId}');
while (playerVoice != null && !Assets.exists(playerVoice))
{
// Remove the last suffix.
playerId = playerId.split('-').slice(0, -1).join('-');
// Try again.
playerVoice = playerId == '' ? null : Paths.voices(this.song.id, '-${playerId}$suffix');
}
}
return playerVoice != null ? [playerVoice] : [];
}
else
if (characters.playerVocals != null)
{
// The metadata explicitly defines the list of voices.
var playerIds:Array<String> = characters?.playerVocals ?? [characters.player];
var playerVoices:Array<String> = playerIds.map((id) -> Paths.voices(this.song.id, '-$id$suffix'));
var validVoices:Bool = true;
return playerVoices;
// Check if all voice paths exist before returning
// If not, fallback to the default method for resolving voices
for (voice in playerVoices)
{
if (voice == null || !Assets.exists(voice)) validVoices = false;
}
if (validVoices) return playerVoices;
}
// Automatically resolve voices by removing suffixes.
// For example, if `Voices-bf-car-erect.ogg` does not exist, check for `Voices-bf-erect.ogg`.
// Then, check for `Voices-bf-car.ogg`, then `Voices-bf.ogg`.
var playerId:String = characters.player;
var playerVoice:String = Paths.voices(this.song.id, '-${playerId}$suffix');
while (playerVoice != null && !Assets.exists(playerVoice))
{
// Remove the last suffix.
// For example, bf-car becomes bf.
playerId = playerId.split('-').slice(0, -1).join('-');
// Try again.
playerVoice = playerId == '' ? null : Paths.voices(this.song.id, '-${playerId}$suffix');
}
if (playerVoice == null)
{
// Try again without $suffix.
playerId = characters.player;
playerVoice = Paths.voices(this.song.id, '-${playerId}');
while (playerVoice != null && !Assets.exists(playerVoice))
{
// Remove the last suffix.
playerId = playerId.split('-').slice(0, -1).join('-');
// Try again.
playerVoice = playerId == '' ? null : Paths.voices(this.song.id, '-${playerId}$suffix');
}
}
return playerVoice != null ? [playerVoice] : [];
}
public function buildOpponentVoiceList():Array<String>
{
var suffix:String = (variation != null && variation != '' && variation != 'default') ? '-$variation' : '';
if (characters.opponentVocals != null)
{
// The metadata explicitly defines the list of voices.
var opponentIds:Array<String> = characters?.opponentVocals ?? [characters.opponent];
var opponentVoices:Array<String> = opponentIds.map((id) -> Paths.voices(this.song.id, '-$id$suffix'));
var validVoices:Bool = true;
// Check if all voice paths exist before returning
// If not, fallback to the default method for resolving voices
for (voice in opponentVoices)
{
if (voice == null || !Assets.exists(voice)) validVoices = false;
}
if (validVoices) return opponentVoices;
}
// Automatically resolve voices by removing suffixes.
// For example, if `Voices-bf-car-erect.ogg` does not exist, check for `Voices-bf-erect.ogg`.
// Then, check for `Voices-bf-car.ogg`, then `Voices-bf.ogg`.
if (characters.opponentVocals == null)
var opponentId:String = characters.opponent;
var opponentVoice:String = Paths.voices(this.song.id, '-${opponentId}$suffix');
while (opponentVoice != null && !Assets.exists(opponentVoice))
{
var opponentId:String = characters.opponent;
var opponentVoice:String = Paths.voices(this.song.id, '-${opponentId}$suffix');
// Remove the last suffix.
opponentId = opponentId.split('-').slice(0, -1).join('-');
// Try again.
opponentVoice = opponentId == '' ? null : Paths.voices(this.song.id, '-${opponentId}$suffix');
}
if (opponentVoice == null)
{
// Try again without $suffix.
opponentId = characters.opponent;
opponentVoice = Paths.voices(this.song.id, '-${opponentId}');
while (opponentVoice != null && !Assets.exists(opponentVoice))
{
// Remove the last suffix.
@@ -908,30 +938,9 @@ class SongDifficulty
// Try again.
opponentVoice = opponentId == '' ? null : Paths.voices(this.song.id, '-${opponentId}$suffix');
}
if (opponentVoice == null)
{
// Try again without $suffix.
opponentId = characters.opponent;
opponentVoice = Paths.voices(this.song.id, '-${opponentId}');
while (opponentVoice != null && !Assets.exists(opponentVoice))
{
// Remove the last suffix.
opponentId = opponentId.split('-').slice(0, -1).join('-');
// Try again.
opponentVoice = opponentId == '' ? null : Paths.voices(this.song.id, '-${opponentId}$suffix');
}
}
return opponentVoice != null ? [opponentVoice] : [];
}
else
{
// The metadata explicitly defines the list of voices.
var opponentIds:Array<String> = characters?.opponentVocals ?? [characters.opponent];
var opponentVoices:Array<String> = opponentIds.map((id) -> Paths.voices(this.song.id, '-$id$suffix'));
return opponentVoices;
}
return opponentVoice != null ? [opponentVoice] : [];
}
/**
@@ -0,0 +1,8 @@
package funkin.play.stage;
/**
* A script that can be tied to StageProp.
* Create a scripted class that extends StageProp to use this.
*/
@:hscriptClass
class ScriptedStageProp extends funkin.play.stage.StageProp implements polymod.hscript.HScriptedClass {}
+11 -4
View File
@@ -2,7 +2,7 @@ package funkin.save;
import flixel.util.FlxSave;
import funkin.input.Controls.Device;
import funkin.play.character.CharacterData.CharacterDataParser;
import funkin.data.character.CharacterData.CharacterDataParser;
import funkin.play.scoring.Scoring;
import funkin.play.scoring.Scoring.ScoringRank;
import funkin.save.migrator.RawSaveData_v1_0_0;
@@ -122,7 +122,8 @@ class Save implements ConsoleClass
downscroll: false,
flashingLights: true,
zoomCamera: true,
debugDisplay: false,
debugDisplay: 'Off',
debugDisplayBGOpacity: 50,
hapticsMode: 'All',
hapticsIntensityMultiplier: 1,
autoPause: true,
@@ -1606,9 +1607,15 @@ typedef SaveDataOptions =
/**
* If enabled, an FPS and memory counter will be displayed even if this is not a debug build.
* @default `false`
* @default `Off`
*/
var debugDisplay:Bool;
var debugDisplay:String;
/**
* Opacity of the debug display's background.
* @default `50`
*/
var debugDisplayBGOpacity:Int;
/**
* If enabled, haptic feedback will be enabled.
+1
View File
@@ -140,6 +140,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
Conductor.beatHit.add(this.beatHit);
Conductor.stepHit.add(this.stepHit);
dispatchEvent(new ScriptEvent(STATE_CREATE));
}
public override function destroy():Void
@@ -755,6 +755,11 @@ class CharSelectSubState extends MusicBeatSubState
FlxTween.tween(member, {y: member.y + 300}, 0.8, {ease: FlxEase.backIn});
}
FlxG.camera.follow(camFollow, LOCKON);
// going to freeplay so fast makes the fade effects and the camera to bug, that's why we cancel the tweens
FlxTween.cancelTweensOf(transitionGradient);
FlxTween.cancelTweensOf(fadeShader);
FlxTween.cancelTweensOf(camFollow);
FlxTween.tween(transitionGradient, {y: -150}, 0.8, {ease: FlxEase.backIn});
fadeShader.fade(1.0, 0, 0.8, {ease: FlxEase.quadIn});
FlxTween.tween(camFollow, {y: camFollow.y - 150}, 0.8,
@@ -764,7 +769,7 @@ class CharSelectSubState extends MusicBeatSubState
FlxG.switchState(() -> FreeplayState.build(
{
{
character: curChar,
character: wentBackToFreeplay ? rememberedChar : curChar,
fromCharSelect: true
}
}));
@@ -784,6 +789,8 @@ class CharSelectSubState extends MusicBeatSubState
var mobileDeny:Bool = false;
var mobileAccept:Bool = false;
var wentBackToFreeplay:Bool = false;
override public function update(elapsed:Float):Void
{
super.update(elapsed);
@@ -899,6 +906,14 @@ class CharSelectSubState extends MusicBeatSubState
holdTmrRight = 0;
selectSound.play(true);
}
if (controls.BACK)
{
wentBackToFreeplay = true;
FunkinSound.playOnce(Paths.sound('cancelMenu'));
FlxTween.tween(FlxG.sound.music, {volume: 0.0}, 0.7, {ease: FlxEase.quadInOut});
goToFreeplay();
}
}
if (cursorX < -1)
@@ -8,7 +8,7 @@ import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
import funkin.graphics.FunkinSprite;
import flixel.util.FlxColor;
import funkin.play.character.CharacterData.CharacterDataParser;
import funkin.data.character.CharacterData.CharacterDataParser;
import funkin.play.components.HealthIcon;
import funkin.ui.freeplay.charselect.PlayableCharacter;
import funkin.data.freeplay.player.PlayerRegistry;
+13 -8
View File
@@ -68,10 +68,15 @@ class CreditsState extends MusicBeatState
static final CREDITS_SCROLL_BASE_SPEED = 100.0;
/**
* The speed the credits scroll at while the button is held, in pixels per second.
* The speed the credits scroll at while accept keybind or spacebar is held, in pixels per second.
*/
static final CREDITS_SCROLL_FAST_SPEED = CREDITS_SCROLL_BASE_SPEED * 4.0;
/**
* The speed the credits scroll at while the pause keybind is held, in pixels per second.
*/
static final CREDITS_SCROLL_PAUSE_SPEED = 0.0;
/**
* The actual sprites and text used to display the credits.
*/
@@ -266,24 +271,24 @@ class CreditsState extends MusicBeatState
// TODO: Replace with whatever the special note button is.
if (FlxG.keys.pressed.ENTER || FlxG.keys.pressed.SPACE #if mobile || TouchUtil.pressed && !TouchUtil.overlaps(backButton) #end)
{
// Move the whole group.
// Move the whole group by the base scroll speed.
creditsGroup.y -= CREDITS_SCROLL_FAST_SPEED * elapsed;
}
else if (controls.PAUSE || FlxG.keys.pressed.SHIFT)
{
// Stop the whole group from moving.
creditsGroup.y -= CREDITS_SCROLL_PAUSE_SPEED * elapsed;
}
else
{
// Move the whole group.
// Move the whole group by the base scroll speed.
creditsGroup.y -= CREDITS_SCROLL_BASE_SPEED * elapsed;
}
}
if (controls.BACK || hasEnded())
{
exit();
}
else if (controls.PAUSE)
{
// scrollPaused = !scrollPaused;
}
}
function hasEnded():Bool
@@ -0,0 +1,300 @@
package funkin.ui.debug;
import flixel.util.FlxStringUtil;
import funkin.ui.debug.stats.FunkinStatsGraph;
import funkin.util.MemoryUtil;
import openfl.display.Shape;
import openfl.display.Sprite;
import openfl.text.TextField;
import openfl.text.TextFormat;
/**
* A debug overlay showing useful info.
*/
#if cpp
@:access(lime._internal.backend.native.NativeCFFI)
#end
class FunkinDebugDisplay extends Sprite
{
static final UPDATE_DELAY:Int = 100;
static final INNER_RECT_DIFF:Int = 3;
static final OUTER_RECT_DIMENSIONS:Array<Int> = [234, 201];
static final OTHERS_OFFSET:Int = 8;
/**
* Indicates whether the debug display is in advanced mode.
*/
public var isAdvanced(default, set):Bool = false;
/**
* The opacity of the debug display's background.
*/
public var backgroundOpacity(default, set):Float = 0.5;
var currentFPS:Int;
var deltaTimeout:Float;
var times:Array<Float>;
var color:Int;
#if !html5
var gcMem:Float;
var gcMemPeak:Float;
var taskMem:Float;
var taskMemPeak:Float;
#end
var background:Shape;
var fpsGraph:FunkinStatsGraph;
var gcMemGraph:FunkinStatsGraph;
var taskMemGraph:FunkinStatsGraph;
var infoDisplay:TextField;
public function new(x:Float = 10, y:Float = 10, color:Int = 0x000000):Void
{
super();
this.x = x;
this.y = y;
this.currentFPS = 0;
this.deltaTimeout = 0.0;
#if !html5
this.gcMem = 0.0;
this.gcMemPeak = 0.0;
this.taskMem = 0.0;
this.taskMemPeak = 0.0;
#end
this.times = [];
this.color = color;
this.backgroundOpacity = 0;
this.isAdvanced = false;
}
function buildDebugDisplay(advanced:Bool):Void
{
removeChildren(0, numChildren);
final BG_WIDTH_MULTIPLIER:Float = #if html5 advanced ? 1 : 0.3 #else 1 #end;
#if html5
final BG_HEIGHT_MULTIPLIER:Float = advanced ? 0.45 : 0.15;
#else
final BG_HEIGHT_MULTIPLIER:Float = advanced ? 1 : (MemoryUtil.supportsTaskMem()) ? 0.3 : 0.2;
#end
background = new Shape();
background.graphics.beginFill(0x3d3f41, 1);
background.graphics.drawRect(0, 0, (OUTER_RECT_DIMENSIONS[0] * BG_WIDTH_MULTIPLIER) + (INNER_RECT_DIFF * 2),
(OUTER_RECT_DIMENSIONS[1] * BG_HEIGHT_MULTIPLIER) + (INNER_RECT_DIFF * 2));
background.graphics.endFill();
background.graphics.beginFill(0x2c2f30, 1);
background.graphics.drawRect(INNER_RECT_DIFF, INNER_RECT_DIFF, OUTER_RECT_DIMENSIONS[0] * BG_WIDTH_MULTIPLIER,
OUTER_RECT_DIMENSIONS[1] * BG_HEIGHT_MULTIPLIER);
background.graphics.endFill();
background.alpha = backgroundOpacity;
addChild(background);
if (advanced)
{
createAdvancedElements();
updateAdvancedDisplay();
}
else
{
createSimpleElements();
updateSimpleDisplay();
}
}
function createAdvancedElements():Void
{
final graphsWidth:Int = OUTER_RECT_DIMENSIONS[0] + (INNER_RECT_DIFF * 2) - (OTHERS_OFFSET * 3);
final graphsHeight:Int = 25;
fpsGraph = new FunkinStatsGraph(OTHERS_OFFSET, OTHERS_OFFSET + 49, graphsWidth, graphsHeight, color);
fpsGraph.textDisplay.y = -49;
fpsGraph.minValue = 0;
addChild(fpsGraph);
#if !html5
gcMemGraph = new FunkinStatsGraph(OTHERS_OFFSET, Math.floor(OTHERS_OFFSET + (fpsGraph.y + fpsGraph.axisHeight) + 22), graphsWidth, graphsHeight, color);
gcMemGraph.minValue = 0;
addChild(gcMemGraph);
if (MemoryUtil.supportsTaskMem())
{
taskMemGraph = new FunkinStatsGraph(OTHERS_OFFSET, Math.floor(OTHERS_OFFSET + (gcMemGraph.y + gcMemGraph.axisHeight) + 22), graphsWidth, graphsHeight,
color);
taskMemGraph.minValue = 0;
addChild(taskMemGraph);
}
#end
}
function createSimpleElements():Void
{
infoDisplay = new TextField();
infoDisplay.x = OTHERS_OFFSET;
infoDisplay.y = OTHERS_OFFSET;
infoDisplay.width = 500;
infoDisplay.selectable = false;
infoDisplay.mouseEnabled = false;
infoDisplay.defaultTextFormat = new TextFormat('Monsterrat', 12, color, JUSTIFY);
infoDisplay.antiAliasType = NORMAL;
infoDisplay.sharpness = 100;
infoDisplay.multiline = true;
addChild(infoDisplay);
}
override function __enterFrame(deltaTime:Int):Void
{
#if cpp
final currentTime:Float = lime._internal.backend.native.NativeCFFI.lime_sdl_get_ticks();
#elseif html5
final currentTime:Float = js.Browser.window.performance.now();
#else
final currentTime:Float = haxe.Timer.stamp() * 1000;
#end
times.push(currentTime);
while (times[0] < currentTime - 1000)
{
times.shift();
}
if (deltaTimeout < UPDATE_DELAY)
{
deltaTimeout += deltaTime;
return;
}
currentFPS = times.length;
#if !html5
gcMem = MemoryUtil.getGCMemory();
if (gcMem > gcMemPeak) gcMemPeak = gcMem;
if (MemoryUtil.supportsTaskMem())
{
taskMem = MemoryUtil.getTaskMemory();
if (taskMem > taskMemPeak) taskMemPeak = taskMem;
}
#end
if (isAdvanced)
{
updateAdvancedDisplay();
}
else
{
updateSimpleDisplay();
}
deltaTimeout = 0.0;
}
function updateAdvancedDisplay():Void
{
updateFPSGraph();
#if !html5
updateGcMemGraph();
updateTaskMemGraph();
#end
final info:Array<String> = [];
info.push('FPS: $currentFPS');
info.push('AVG FPS: ${Math.floor(fpsGraph.average())}');
info.push('1% LOW FPS: ${Math.floor(fpsGraph.lowest())}');
fpsGraph.textDisplay.text = info.join('\n');
#if !html5
gcMemGraph.textDisplay.text = 'GC MEM: ${FlxStringUtil.formatBytes(gcMem).toLowerCase()} / ${FlxStringUtil.formatBytes(gcMemPeak).toLowerCase()}';
if (taskMemGraph != null)
{
taskMemGraph.textDisplay.text = 'TASK MEM: ${FlxStringUtil.formatBytes(taskMem).toLowerCase()} / ${FlxStringUtil.formatBytes(taskMemPeak).toLowerCase()}';
}
#end
}
function updateSimpleDisplay():Void
{
if (infoDisplay != null)
{
final info:Array<String> = [];
info.push('FPS: $currentFPS');
#if !html5
info.push('GC MEM: ${FlxStringUtil.formatBytes(gcMem).toLowerCase()} / ${FlxStringUtil.formatBytes(gcMemPeak).toLowerCase()}');
if (MemoryUtil.supportsTaskMem())
info.push('TASK MEM: ${FlxStringUtil.formatBytes(taskMem).toLowerCase()} / ${FlxStringUtil.formatBytes(taskMemPeak).toLowerCase()}');
#end
infoDisplay.text = info.join('\n');
}
}
function updateFPSGraph(?currentFPS:Int = 0):Void
{
fpsGraph.maxValue = FlxG.drawFramerate;
fpsGraph.update(times.length);
}
#if !html5
function updateGcMemGraph(?currentFPS:Int = 0):Void
{
gcMemGraph.maxValue = gcMemPeak;
gcMemGraph.update(gcMem);
}
function updateTaskMemGraph(?currentFPS:Int = 0):Void
{
if (taskMemGraph != null)
{
taskMemGraph.maxValue = taskMemPeak;
taskMemGraph.update(taskMem);
}
}
#end
function set_isAdvanced(value:Bool):Bool
{
buildDebugDisplay(value);
return isAdvanced = value;
}
function set_backgroundOpacity(value:Float):Float
{
if (background != null) background.alpha = value;
return backgroundOpacity = value;
}
}
enum abstract DebugDisplayMode(Int) from Int to Int
{
/**
* Debug display is disabled.
*/
var OFF = 0;
/**
* Simple debug display.
* FPS and Memory counters only.
*/
var SIMPLE = 1;
/**
* Advanced debug display.
* Full FPS and Memory info.
*/
var ADVANCED = 2;
}
-44
View File
@@ -1,44 +0,0 @@
package funkin.ui.debug;
import funkin.util.MemoryUtil;
import openfl.text.TextFormat;
import openfl.text.TextField;
class MemoryCounter extends TextField
{
var memPeak:Float = 0;
static final BYTES_PER_MEG:Float = 1024 * 1024;
static final ROUND_TO:Float = 1 / 100;
public function new(x:Float = 10, y:Float = 10, color:Int = 0x000000)
{
super();
this.x = x;
this.y = y;
this.width = 500;
this.selectable = false;
this.mouseEnabled = false;
defaultTextFormat = new TextFormat("_sans", 12, color);
text = "RAM: ";
#if flash
addEventListener(Event.ENTER_FRAME, function(e) {
var time = Lib.getTimer();
__enterFrame(time - currentTime);
});
#end
}
// Event Handlers
@:noCompletion
#if !flash override #end function __enterFrame(deltaTime:Float):Void
{
var mem:Float = Math.fround(MemoryUtil.getMemoryUsed() / BYTES_PER_MEG / ROUND_TO) * ROUND_TO;
if (mem > memPeak) memPeak = mem;
text = 'RAM: ${mem}mb / ${memPeak}mb';
}
}
@@ -12,8 +12,8 @@ import flixel.text.FlxText;
import flixel.util.FlxColor;
import funkin.input.Cursor;
import funkin.play.character.BaseCharacter;
import funkin.play.character.CharacterData;
import funkin.play.character.CharacterData.CharacterDataParser;
import funkin.data.character.CharacterData;
import funkin.data.character.CharacterData.CharacterDataParser;
import funkin.ui.mainmenu.MainMenuState;
import funkin.util.MouseUtil;
import funkin.util.SerializerUtil;
@@ -171,6 +171,21 @@ class DebugBoundingState extends FlxState
}
}
function updateOnionSkin():Void
{
if (swagChar == null) return;
if (swagChar.hasAnimation("idle")) swagChar.playAnimation("idle", true);
onionSkinChar.loadGraphicFromSprite(swagChar);
onionSkinChar.frame = swagChar.frame;
onionSkinChar.alpha = 0.6;
onionSkinChar.flipX = swagChar.flipX;
onionSkinChar.offset.x = swagChar.animOffsets[0];
onionSkinChar.offset.y = swagChar.animOffsets[1];
swagChar.playAnimation(currentAnimationName, true); // reset animation to the one it should be
}
function initOffsetView():Void
{
offsetView = new FlxGroup();
@@ -332,6 +347,16 @@ class DebugBoundingState extends FlxState
function offsetControls():Void
{
// CTRL + S = Save Character Data
// CTRL + SHIFT + S = Save Offsets
// "WINDOWS" key code is the same keycode as COMMAND on mac
if ((FlxG.keys.pressed.CONTROL || FlxG.keys.pressed.WINDOWS) && FlxG.keys.justPressed.S)
{
var outputString = FlxG.keys.pressed.SHIFT ? buildOutputStringOld() : buildOutputStringNew();
saveOffsets(outputString, FlxG.keys.pressed.SHIFT ? swagChar.characterId + "Offsets.txt" : swagChar.characterId + ".json");
return;
}
if (FlxG.keys.justPressed.RBRACKET || FlxG.keys.justPressed.E)
{
if (offsetAnimationDropdown.selectedIndex + 1 <= offsetAnimationDropdown.dataSource.size)
@@ -395,11 +420,13 @@ class DebugBoundingState extends FlxState
if (FlxG.keys.justPressed.F)
{
onionSkinChar.visible = !onionSkinChar.visible;
if (onionSkinChar.visible) updateOnionSkin();
}
if (FlxG.keys.justPressed.G)
{
swagChar.flipX = !swagChar.flipX;
if (onionSkinChar.visible) updateOnionSkin();
}
// Plays the idle animation
@@ -440,12 +467,6 @@ class DebugBoundingState extends FlxState
trace(animName);
}
if (FlxG.keys.justPressed.ESCAPE)
{
var outputString = FlxG.keys.pressed.CONTROL ? buildOutputStringOld() : buildOutputStringNew();
saveOffsets(outputString, FlxG.keys.pressed.CONTROL ? swagChar.characterId + "Offsets.txt" : swagChar.characterId + ".json");
}
}
function buildOutputStringOld():String
@@ -489,8 +510,8 @@ class DebugBoundingState extends FlxState
}
swagChar = CharacterDataParser.fetchCharacter(char);
swagChar.x = 100;
swagChar.y = 100;
swagChar.x = onionSkinChar.x = 100;
swagChar.y = onionSkinChar.y = 100;
swagChar.debug = true;
offsetView.add(swagChar);
@@ -545,14 +566,7 @@ class DebugBoundingState extends FlxState
function playCharacterAnimation(str:String, setOnionSkin:Bool = true)
{
if (setOnionSkin)
{
// clears the canvas
onionSkinChar.pixels.fillRect(new Rectangle(0, 0, FlxG.width * 2, FlxG.height * 2), 0x00000000);
onionSkinChar.stamp(swagChar, Std.int(swagChar.x), Std.int(swagChar.y));
onionSkinChar.alpha = 0.6;
}
if (setOnionSkin) updateOnionSkin();
// var animName = characterAnimNames[Std.parseInt(str)];
var animName = str;
@@ -41,7 +41,7 @@ import funkin.input.TurboKeyHandler;
import funkin.modding.events.ScriptEvent;
import funkin.play.notes.notekind.NoteKindManager;
import funkin.play.character.BaseCharacter.CharacterType;
import funkin.play.character.CharacterData.CharacterDataParser;
import funkin.data.character.CharacterData.CharacterDataParser;
import funkin.play.components.HealthIcon;
import funkin.play.notes.NoteSprite;
import funkin.play.PlayStatePlaylist;
@@ -78,6 +78,7 @@ import funkin.ui.debug.charting.components.ChartEditorSelectionSquareSprite;
import funkin.ui.debug.charting.toolboxes.ChartEditorDifficultyToolbox;
import funkin.ui.debug.charting.toolboxes.ChartEditorFreeplayToolbox;
import funkin.ui.debug.charting.toolboxes.ChartEditorOffsetsToolbox;
import funkin.ui.debug.FunkinDebugDisplay.DebugDisplayMode;
import funkin.ui.haxeui.components.CharacterPlayer;
import funkin.ui.mainmenu.MainMenuState;
import funkin.ui.transition.LoadingState;
@@ -1279,6 +1280,20 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
*/
var songMetadata:Map<String, SongMetadata> = [];
/**
* Updates the current song's play data variations list.
*/
function refreshPlayDataVariations():Void
{
var songVariations:Array<String> = songMetadata.get(Constants.DEFAULT_VARIATION).playData.songVariations;
songVariations.clear();
for (variation in availableVariations)
{
if (variation == Constants.DEFAULT_VARIATION) continue;
songVariations.push(variation);
}
}
/**
* Retrieves the list of variations for the current song.
*/
@@ -2172,6 +2187,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
*/
var notePreviewPlayhead:Null<FlxSprite> = null;
/**
* Whether the note preview playhead is currently being dragged with the mouse by the user.
*/
var notePreviewPlayHeadDragging:Bool = false;
/**
* The rectangular sprite used for rendering the selection box.
* Uses a 9-slice to stretch the selection box to the correct size without warping.
@@ -2845,7 +2865,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
txtCopyNotif.zIndex = 120;
add(txtCopyNotif);
if (!Preferences.debugDisplay) menubar.paddingLeft = null;
if (Preferences.debugDisplay == DebugDisplayMode.OFF) menubar.paddingLeft = null;
this.setupNotifications();
@@ -4350,6 +4370,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
else if (measureTicks != null && FlxG.mouse.overlaps(measureTicks) && !isCursorOverHaxeUI)
{
gridPlayheadScrollAreaPressed = true;
// Stop audio playback while dragging on the grid playhead.
if (audioInstTrack != null && audioInstTrack.isPlaying)
{
playbarHeadDraggingWasPlaying = true;
stopAudioPlayback();
}
}
else if (notePreview != null && FlxG.mouse.overlaps(notePreview) && !isCursorOverHaxeUI)
{
@@ -4372,11 +4398,24 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (gridPlayheadScrollAreaPressed && FlxG.mouse.released)
{
gridPlayheadScrollAreaPressed = false;
// If we were dragging the playhead while the song was playing, resume playing.
if (playbarHeadDraggingWasPlaying)
{
playbarHeadDraggingWasPlaying = false;
startAudioPlayback();
}
}
if (notePreviewScrollAreaStartPos != null && FlxG.mouse.released)
{
notePreviewScrollAreaStartPos = null;
notePreviewPlayHeadDragging = false;
if (playbarHeadDraggingWasPlaying)
{
playbarHeadDraggingWasPlaying = false;
startAudioPlayback();
}
}
if (gridPlayheadScrollAreaPressed)
@@ -4651,6 +4690,14 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
}
else if (notePreviewScrollAreaStartPos != null)
{
notePreviewPlayHeadDragging = true;
// Stop audio playback while dragging on the note preview playhead.
if (audioInstTrack != null && audioInstTrack.isPlaying)
{
playbarHeadDraggingWasPlaying = true;
stopAudioPlayback();
}
// Player is clicking and holding on note preview to scrub around.
targetCursorMode = Grabbing;
@@ -5677,6 +5724,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
return;
}
this.hideAllToolboxes();
stopWelcomeMusic();
// TODO: PR Flixel to make onComplete nullable.
if (audioInstTrack != null) audioInstTrack.onComplete = null;
@@ -6009,6 +6058,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
stopWelcomeMusic();
stopAudioPlayback();
// Force pauses audio preview from OffsetsToolbox, if it exists.
cast(this.getToolbox(CHART_EDITOR_TOOLBOX_OFFSETS_LAYOUT), ChartEditorOffsetsToolbox)?.pauseAudioPreview();
var startTimestamp:Float = 0;
if (playtestStartTime) startTimestamp = scrollPositionInMs + playheadPositionInMs;
@@ -6191,6 +6243,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
{
if (audioInstTrack != null)
{
// Don't allow the audio to be played while we're dragging any of the playheads
if (playbarHeadDragging || gridPlayheadScrollAreaPressed || notePreviewPlayHeadDragging) return;
cast(this.getToolbox(CHART_EDITOR_TOOLBOX_OFFSETS_LAYOUT), ChartEditorOffsetsToolbox)?.pauseAudioPreview();
audioInstTrack.play(false, audioInstTrack.time);
audioVocalTrackGroup.play(false, audioInstTrack.time);
}
@@ -6302,7 +6357,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (songMetadata.size() > 1)
{
if (variationMetadata.playData.difficulties.length == 0)
if (variation != Constants.DEFAULT_VARIATION && variationMetadata.playData.difficulties.length == 0)
{
songMetadata.remove(variation);
songChartData.remove(variation);
@@ -6319,6 +6374,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (selectedDifficulty == difficulty
|| !variationMetadata.playData.difficulties.contains(selectedDifficulty)) selectedDifficulty = variationMetadata.playData.difficulties[0];
refreshPlayDataVariations();
difficultySelectDirty = true; // Force the Difficulty toolbox to update.
}
@@ -6655,6 +6711,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
}
else
{
// Don't allow the audio to be played while we're dragging any of the playheads
if (playbarHeadDragging || gridPlayheadScrollAreaPressed || notePreviewPlayHeadDragging) return;
// Play
startAudioPlayback();
stopWelcomeMusic();
@@ -2,8 +2,8 @@ package funkin.ui.debug.charting.dialogs;
import flixel.math.FlxPoint;
import funkin.play.character.BaseCharacter.CharacterType;
import funkin.play.character.CharacterData;
import funkin.play.character.CharacterData.CharacterDataParser;
import funkin.data.character.CharacterData;
import funkin.data.character.CharacterData.CharacterDataParser;
import funkin.play.components.HealthIcon;
import funkin.util.SortUtil;
import haxe.ui.components.Label;
@@ -4,7 +4,7 @@ import funkin.input.Cursor;
import funkin.ui.debug.charting.dialogs.ChartEditorBaseDialog.DialogDropTarget;
import funkin.ui.debug.charting.dialogs.ChartEditorBaseDialog.DialogParams;
import funkin.util.FileUtil;
import funkin.play.character.CharacterData;
import funkin.data.character.CharacterData;
import haxe.io.Path;
import haxe.ui.components.Button;
import haxe.ui.components.Label;
@@ -168,11 +168,10 @@ class ChartEditorDialogHandler
var backupTimeLabel:Null<Label> = dialog.findComponent('backupTimeLabel', Label);
if (backupTimeLabel == null) throw 'Could not locate backupTimeLabel button in Backup Available dialog';
var latestBackupDate:Null<Date> = ChartEditorImportExportHandler.getLatestBackupDate();
var latestBackupDate:Null<String> = ChartEditorImportExportHandler.getLatestBackupDate();
if (latestBackupDate != null)
{
var latestBackupDateStr:String = DateUtil.generateCleanTimestamp(latestBackupDate);
backupTimeLabel.text = latestBackupDateStr;
backupTimeLabel.text = latestBackupDate;
}
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
@@ -1246,6 +1245,7 @@ class ChartEditorDialogHandler
pendingVariation.timeChanges[0].bpm = dialogBPM.value;
state.songMetadata.set(pendingVariation.variation, pendingVariation);
state.refreshPlayDataVariations();
state.difficultySelectDirty = true; // Force the Difficulty toolbox to update.
// Don't update conductor since we haven't switched to the new variation yet.
@@ -278,8 +278,7 @@ class ChartEditorImportExportHandler
var variChartDataString:String = mappedFileEntries.get(variChartDataPath)?.data?.toString() ?? throw 'Could not locate chart data ($variation).';
var variChartDataVersion:SemverVersion = VersionUtil.getVersionFromJSON(variChartDataString) ?? throw 'Could not read chart data version ($variation).';
var variChartData:SongChartData = SongRegistry.instance.parseEntryChartDataRawWithMigration(variChartDataString, variChartDataPath,
variChartDataVersion) ?? throw 'Could not read chart data ($variation).';
variChartDataVersion, variation) ?? throw 'Could not read chart data ($variation).';
songChartDatas.set(variation, variChartData);
}
loadSong(state, songMetadatas, songChartDatas, manifest);
@@ -368,7 +367,7 @@ class ChartEditorImportExportHandler
#end
}
public static function getLatestBackupDate():Null<Date>
public static function getLatestBackupDate():Null<String>
{
#if sys
var latestBackupPath:Null<String> = getLatestBackupPath();
@@ -377,19 +376,10 @@ class ChartEditorImportExportHandler
var latestBackupName:String = haxe.io.Path.withoutDirectory(latestBackupPath);
latestBackupName = haxe.io.Path.withoutExtension(latestBackupName);
var parts = latestBackupName.split('-');
var stat = sys.FileSystem.stat(latestBackupPath);
var sizeInMB = (stat.size / 1000000).round(3);
// var chart:String = parts[0];
// var editor:String = parts[1];
var year:Int = Std.parseInt(parts[2] ?? '0') ?? 0;
var month:Int = Std.parseInt(parts[3] ?? '1') ?? 1;
var day:Int = Std.parseInt(parts[4] ?? '0') ?? 0;
var hour:Int = Std.parseInt(parts[5] ?? '0') ?? 0;
var minute:Int = Std.parseInt(parts[6] ?? '0') ?? 0;
var second:Int = Std.parseInt(parts[7] ?? '0') ?? 0;
var date:Date = new Date(year, month - 1, day, hour, minute, second);
return date;
return "Full Name: " + latestBackupName + "\nLast Modified: " + stat.mtime.toString() + "\nSize: " + sizeInMB + " MB";
#else
return null;
#end
@@ -470,9 +460,10 @@ class ChartEditorImportExportHandler
{
// Force writing to a generic path (autosave or crash recovery)
targetMode = Skip;
if (state.currentSongId == '') state.currentSongName = 'New Chart'; // Hopefully no one notices this silliness
targetPath = Path.join([
BACKUPS_PATH,
'chart-editor-${DateUtil.generateTimestamp()}.${Constants.EXT_CHART}'
'chart-editor-${state.currentSongId}-${DateUtil.generateTimestamp()}.${Constants.EXT_CHART}'
]);
// We have to force write because the program will die before the save dialog is closed.
trace('Force exporting to $targetPath...');
@@ -1,7 +1,7 @@
package funkin.ui.debug.charting.toolboxes;
import funkin.play.character.BaseCharacter.CharacterType;
import funkin.play.character.CharacterData;
import funkin.data.character.CharacterData;
import funkin.data.song.importer.ChartManifestData;
import funkin.data.stage.StageRegistry;
import funkin.data.notestyle.NoteStyleRegistry;
@@ -103,6 +103,7 @@ class ChartEditorOffsetsToolbox extends ChartEditorBaseToolbox
function onClose(event:UIEvent)
{
stopAudioPreview(); // Pause it instead, maybe?
chartEditorState.menubarItemToggleToolboxOffsets.selected = false;
}
@@ -458,6 +459,7 @@ class ChartEditorOffsetsToolbox extends ChartEditorBaseToolbox
public function playAudioPreview():Void
{
chartEditorState.stopAudioPlayback();
audioPreviewTracks.play(false, audioPreviewTracks.time);
}
@@ -4,12 +4,12 @@ import funkin.data.notestyle.NoteStyleRegistry;
import funkin.play.notes.notestyle.NoteStyle;
import funkin.play.event.SongEvent;
import funkin.data.stage.StageRegistry;
import funkin.play.character.CharacterData;
import funkin.data.character.CharacterData;
import haxe.ui.components.DropDown;
import funkin.play.stage.Stage;
import funkin.play.character.BaseCharacter.CharacterType;
import funkin.data.event.SongEventRegistry;
import funkin.play.character.CharacterData.CharacterDataParser;
import funkin.data.character.CharacterData.CharacterDataParser;
/**
* Functions for populating dropdowns based on game data.
@@ -13,7 +13,7 @@ import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.addons.display.FlxGridOverlay;
import funkin.play.character.BaseCharacter;
import funkin.play.character.BaseCharacter.CharacterType;
import funkin.play.character.CharacterData.CharacterDataParser;
import funkin.data.character.CharacterData.CharacterDataParser;
import funkin.save.Save;
import funkin.input.Cursor;
import haxe.ui.backend.flixel.UIState;
@@ -206,7 +206,7 @@ class StageEditorState extends UIState
var data = this.packShitToZip();
var path = haxe.io.Path.join([
BACKUPS_PATH,
'stage-editor-${funkin.util.DateUtil.generateTimestamp()}.${FileUtil.FILE_EXTENSION_INFO_FNFS.extension}'
'stage-editor-${stageName}-${funkin.util.DateUtil.generateTimestamp()}.${FileUtil.FILE_EXTENSION_INFO_FNFS.extension}'
]);
FileUtil.writeBytesToPath(path, data);
@@ -638,7 +638,7 @@ class StageEditorState extends UIState
if (FlxG.keys.justPressed.S) FlxG.keys.pressed.SHIFT ? onMenuItemClick("save stage as") : onMenuItemClick("save stage");
if (FlxG.keys.justPressed.F) onMenuItemClick("find object");
if (FlxG.keys.justPressed.O) onMenuItemClick("open stage");
if (FlxG.keys.justPressed.N) onMenuItemClick("new stage");
if (FlxG.keys.justPressed.N && welcomeDialog == null && userGuideDialog == null) onMenuItemClick("new stage");
if (FlxG.keys.justPressed.Q) onMenuItemClick("exit");
}
@@ -1259,7 +1259,7 @@ class StageEditorState extends UIState
{
if (exitConfirmDialog == null)
{
exitConfirmDialog = Dialogs.messageBox("You are about to leave the Editor without Saving.\n\nAre you sure? ", "Leave Editor",
exitConfirmDialog = Dialogs.messageBox("You are about to leave the editor without saving.\n\nAre you sure? ", "Leave Editor",
MessageBoxType.TYPE_YESNO, true, function(btn:DialogButton) {
exitConfirmDialog = null;
if (btn == DialogButton.YES)
@@ -9,7 +9,7 @@ import funkin.util.DateUtil;
using StringTools;
@:xml('
<dialog id="backupAvailableDialog" width="475" height="150" title="Hey! Listen!">
<dialog id="backupAvailableDialog" width="475" height="200" title="Hey! Listen!">
<vbox width="100%" height="100%">
<label text="There is a stage backup available, would you like to open it?\n" width="100%" textAlign="center" />
<spacer height="6" />
@@ -34,20 +34,14 @@ class BackupAvailableDialog extends Dialog
if (!FileUtil.fileExists(filePath)) return;
// time text
var fileDate = Path.withoutExtension(Path.withoutDirectory(filePath));
var dateParts = fileDate.split("-");
var file = Path.withoutExtension(Path.withoutDirectory(filePath));
while (dateParts.length < 8)
dateParts.push("0");
#if sys
var stat = sys.FileSystem.stat(filePath);
var sizeInMB = (stat.size / 1000000).round(3);
var year:Int = Std.parseInt(dateParts[2]) ?? 0; // copied parts from ChartEditorImportExportHandler.hx
var month:Int = Std.parseInt(dateParts[3]) ?? 1;
var day:Int = Std.parseInt(dateParts[4]) ?? 0;
var hour:Int = Std.parseInt(dateParts[5]) ?? 0;
var minute:Int = Std.parseInt(dateParts[6]) ?? 0;
var second:Int = Std.parseInt(dateParts[7]) ?? 0;
backupTimeLabel.text = DateUtil.generateCleanTimestamp(new Date(year, month - 1, day, hour, minute, second));
backupTimeLabel.text = "Full Name: " + file + "\nLast Modified: " + stat.mtime.toString() + "\nSize: " + sizeInMB + " MB";
#end
// button callbacks
dialogCancel.onClick = function(_) hideDialog(DialogButton.CANCEL);
@@ -47,9 +47,9 @@ class WelcomeDialog extends Dialog
#if sys
var stat = sys.FileSystem.stat(file);
var sizeInMB = (stat.size / 1000000).round(2);
var sizeInMB = (stat.size / 1000000).round(3);
fileText.tooltip = "Full Name: " + file + "\nLast Modified: " + stat.mtime.toString() + "\nSize: " + sizeInMB + "MB";
fileText.tooltip = "Full Name: " + file + "\nLast Modified: " + stat.mtime.toString() + "\nSize: " + sizeInMB + " MB";
#end
contentRecent.addComponent(fileText);
@@ -2,8 +2,8 @@ package funkin.ui.debug.stageeditor.toolboxes;
import haxe.ui.components.NumberStepper;
import funkin.play.character.BaseCharacter.CharacterType;
import funkin.play.character.CharacterData.CharacterDataParser;
import funkin.play.character.CharacterData;
import funkin.data.character.CharacterData.CharacterDataParser;
import funkin.data.character.CharacterData;
import funkin.util.SortUtil;
import funkin.save.Save;
import haxe.ui.components.Button;
@@ -0,0 +1,162 @@
package funkin.ui.debug.stats;
import flixel.math.FlxMath;
import flixel.util.FlxColor;
import openfl.display.Shape;
import openfl.display.Sprite;
import openfl.text.TextField;
import openfl.text.TextFormat;
class FunkinStatsGraph extends Sprite
{
static inline var AXIS_COLOR:FlxColor = 0xffffff;
static inline var AXIS_ALPHA:Float = 0.5;
static inline var HISTORY_MAX:Int = 100;
public var minValue:Float = FlxMath.MAX_VALUE_FLOAT;
public var maxValue:Float = FlxMath.MIN_VALUE_FLOAT;
public var graphColor:FlxColor;
public var history:Array<Float> = [];
public var textDisplay:TextField;
public var axis:Shape;
public var axisWidth:Int;
public var axisHeight:Int;
public function new(x:Int, y:Int, width:Int, height:Int, graphColor:FlxColor):Void
{
super();
this.x = x;
this.y = y;
this.graphColor = graphColor;
this.axisWidth = width;
this.axisHeight = height;
textDisplay = new TextField();
textDisplay.width = 500;
textDisplay.y -= 22;
textDisplay.selectable = false;
textDisplay.mouseEnabled = false;
textDisplay.defaultTextFormat = new TextFormat('Monsterrat', 12, graphColor, JUSTIFY);
textDisplay.antiAliasType = NORMAL;
textDisplay.sharpness = 100;
textDisplay.multiline = true;
addChild(textDisplay);
axis = new Shape();
axis.x += 4;
addChild(axis);
drawAxes();
}
function drawAxes():Void
{
axis.graphics.clear();
axis.graphics.lineStyle(1, AXIS_COLOR, AXIS_ALPHA, false, null, null, MITER, 255);
axis.graphics.moveTo(0, 0);
axis.graphics.lineTo(0, axisHeight);
axis.graphics.moveTo(0, axisHeight);
axis.graphics.lineTo(axisWidth, axisHeight);
}
function drawGraph():Void
{
graphics.clear();
graphics.lineStyle(1, graphColor, 1, false, null, null, MITER, 255);
if (history.length == 0)
{
return;
}
var inc:Float = (axisWidth - 2) / (HISTORY_MAX - 1);
var range:Float = Math.max(maxValue - minValue, maxValue * 0.1);
var scale:Float = axisHeight / range;
for (i in 0...history.length)
{
final pointY:Float = axisHeight - ((history[i] - minValue) * scale) - 1;
if (i == 0) graphics.moveTo(axis.x, pointY);
graphics.lineTo(axis.x + 1 + (i * inc), pointY);
}
}
public function update(value:Float):Void
{
history.push(value);
if (history.length > HISTORY_MAX)
{
history.shift();
}
maxValue = Math.max(maxValue, value);
minValue = Math.min(minValue, value);
drawGraph();
}
public function average():Float
{
if (history.length == 0)
{
return 0;
}
var sum:Float = 0;
for (v in history)
{
sum += v;
}
return sum / history.length;
}
public function lowest():Float
{
if (history.length == 0)
{
return 0;
}
var val:Float = history[0];
for (v in history)
{
if (v < val)
{
val = v;
}
}
return val;
}
public function destroy():Void
{
if (axis != null)
{
removeChild(axis);
axis = null;
}
history = null;
}
}
+123 -35
View File
@@ -246,6 +246,7 @@ class FreeplayState extends MusicBeatSubState
var prepForNewRank:Bool = false;
var styleData:Null<FreeplayStyle> = null;
var fromCharSelect:Bool = false;
var forceSkipIntro:Bool = false;
public var freeplayArrow:Null<FlxText>;
@@ -277,7 +278,11 @@ class FreeplayState extends MusicBeatSubState
super(FlxColor.TRANSPARENT);
if (stickers?.members != null) stickerSubState = stickers;
if (stickers?.members != null)
{
stickerSubState = stickers;
forceSkipIntro = true;
}
var backingCardPrep:Null<BackingCard> = null;
@@ -348,6 +353,14 @@ class FreeplayState extends MusicBeatSubState
stickerSubState.degenStickers();
}
if (fromResultsParams != null)
{
@:privateAccess
this._parentState._constructor = () -> {
return FreeplayState.build(null, null);
}
}
#if FEATURE_DISCORD_RPC
// Updating Discord Rich Presence
DiscordClient.instance.setPresence({state: 'In the Menus', details: null});
@@ -498,7 +511,7 @@ class FreeplayState extends MusicBeatSubState
var overhangStuff:FlxSprite = new FlxSprite().makeGraphic(FlxG.width, 164, FlxColor.BLACK);
overhangStuff.y -= overhangStuff.height;
if (fromCharSelect)
if (fromCharSelect || forceSkipIntro)
{
blackOverlayBullshitLOLXD.visible = false;
overhangStuff.y = -100;
@@ -764,7 +777,7 @@ class FreeplayState extends MusicBeatSubState
albumRoll.skipIntro();
albumRoll.showStars();
}
else if (fromCharSelect)
else if (fromCharSelect || forceSkipIntro)
{
albumRoll.skipIntro();
albumRoll.showStars();
@@ -813,10 +826,11 @@ class FreeplayState extends MusicBeatSubState
rankCamera.fade(0xFF000000, 0, false, null, true);
}
if (fromCharSelect)
if (fromCharSelect || forceSkipIntro)
{
enterFromCharSel();
if (fromCharSelect) enterFromCharSel();
onDJIntroDone();
forceSkipIntro = false;
}
else
{
@@ -900,10 +914,10 @@ class FreeplayState extends MusicBeatSubState
var randomCapsule:SongMenuItem = grpCapsules.recycle(SongMenuItem);
randomCapsule.initRandom(styleData);
randomCapsule.onConfirm = function() {
capsuleOnConfirmRandom(randomCapsule);
capsuleOnOpenRandom(randomCapsule);
};
if (fromCharSelect || noJumpIn) randomCapsule.forcePosition();
if (fromCharSelect || forceSkipIntro || noJumpIn) randomCapsule.forcePosition();
else
{
randomCapsule.initJumpIn(0, force);
@@ -933,7 +947,7 @@ class FreeplayState extends MusicBeatSubState
funnyMenu.newText.animation.curAnim.curFrame = 45 - ((i * 4) % 45);
// Stop the bounce-in animation when returning to freeplay from the character selection screen, or if noJumpIn is set to true
if (fromCharSelect || noJumpIn) funnyMenu.forcePosition();
if (fromCharSelect || forceSkipIntro || noJumpIn) funnyMenu.forcePosition();
else
funnyMenu.initJumpIn(0, force);
@@ -1034,6 +1048,7 @@ class FreeplayState extends MusicBeatSubState
if (fromResults.oldRank != null)
{
capsuleToRank.fakeRanking.rank = fromResults.oldRank;
capsuleToRank.fakeBlurredRanking.rank = fromResults.oldRank;
sparks.frames = Paths.getSparrowAtlas('freeplay/sparks');
sparks.animation.addByPrefix('sparks', 'sparks', 24, false);
@@ -1067,6 +1082,7 @@ class FreeplayState extends MusicBeatSubState
trace(originalPos);
capsuleToRank.ranking.visible = false;
capsuleToRank.blurredRanking.visible = false;
// Rank animation vibrations.
HapticUtil.increasingVibrate(Constants.MIN_VIBRATION_AMPLITUDE, Constants.MAX_VIBRATION_AMPLITUDE, 0.6);
@@ -1090,23 +1106,29 @@ class FreeplayState extends MusicBeatSubState
function rankDisplayNew(fromResults:Null<FromResultsParams>, capsuleToRank:SongMenuItem):Void
{
capsuleToRank.ranking.visible = false;
capsuleToRank.fakeRanking.visible = false;
capsuleToRank.ranking.visible = true;
capsuleToRank.blurredRanking.visible = true;
capsuleToRank.ranking.scale.set(20, 20);
capsuleToRank.blurredRanking.scale.set(20, 20);
if (fromResults != null && fromResults.newRank != null)
{
capsuleToRank.ranking.playAnimationEach(fromResults.newRank.getFreeplayRankIconAsset(), true);
capsuleToRank.ranking.animation.play(fromResults.newRank.getFreeplayRankIconAsset(), true);
}
FlxTween.tween(capsuleToRank.ranking, {"scale.x": 0.9, "scale.y": 0.9}, 0.1);
new FlxTimer().start(0.1, _ -> {
capsuleToRank.ranking.visible = true;
if (fromResults != null && fromResults.newRank != null)
{
capsuleToRank.blurredRanking.animation.play(fromResults.newRank.getFreeplayRankIconAsset(), true);
}
FlxTween.tween(capsuleToRank.blurredRanking, {"scale.x": 0.9, "scale.y": 0.9}, 0.1);
new FlxTimer().start(0.1, _ -> {
if (fromResults?.oldRank != null)
{
capsuleToRank.fakeRanking.visible = false;
capsuleToRank.fakeBlurredRanking.visible = false;
sparks.visible = true;
sparksADD.visible = true;
@@ -1454,7 +1476,8 @@ class FreeplayState extends MusicBeatSubState
fadeDots(false);
#if FEATURE_TOUCH_CONTROLS
FlxTween.tween(backButton, {alpha: 0.0001}, 0.4, {ease: FlxEase.quadOut});
backTransitioning = true;
FlxTween.tween(backButton, {alpha: 0}, 0.4, {ease: FlxEase.quadOut});
#end
funnyCam.filtersEnabled = true;
@@ -1561,6 +1584,8 @@ class FreeplayState extends MusicBeatSubState
{
super.update(elapsed);
Conductor.instance.update(FlxG.sound?.music?.time ?? 0.0);
#if FEATURE_TOUCH_CONTROLS
if (backButton != null && !backTransitioning)
{
@@ -1569,7 +1594,7 @@ class FreeplayState extends MusicBeatSubState
backButton.animation.play("idle");
backButton.alpha = backButton.restingOpacity;
}
backButton.active = controls.active;
backButton.enabled = controls.active;
}
#end
@@ -2066,13 +2091,6 @@ class FreeplayState extends MusicBeatSubState
}
#end
override function beatHit():Bool
{
backingCard.beatHit();
return super.beatHit();
}
public override function destroy():Void
{
super.destroy();
@@ -2127,7 +2145,7 @@ class FreeplayState extends MusicBeatSubState
#if FEATURE_TOUCH_CONTROLS
FlxTween.tween(backButton, {x: FlxG.width + 300}, 0.45, {ease: FlxEase.expoIn});
FlxTween.tween(backButton, {alpha: 0.0001}, 0.3, {ease: FlxEase.quadOut, startDelay: 0.15});
FlxTween.tween(backButton, {alpha: 0}, 0.3, {ease: FlxEase.quadOut, startDelay: 0.15});
#end
for (caps in grpCapsules.members)
@@ -2303,7 +2321,7 @@ class FreeplayState extends MusicBeatSubState
intendedCompletion = Math.max(0, Scoring.tallyCompletion(songScore?.tallies));
rememberedDifficulty = currentDifficulty;
if (!capsuleAnim) generateSongList(currentFilter, false, true, true);
currentCapsule.refreshDisplay(!prepForNewRank);
if (change != 0) currentCapsule.refreshDisplay(!prepForNewRank);
}
else
{
@@ -2410,7 +2428,10 @@ class FreeplayState extends MusicBeatSubState
}
#end
function capsuleOnConfirmRandom(randomCapsule:SongMenuItem):Void
/**
* Called when hitting ENTER to open the instrumental choice for random capsule
*/
function capsuleOnOpenRandom(randomCapsule:SongMenuItem):Void
{
trace('RANDOM SELECTED');
@@ -2434,18 +2455,81 @@ class FreeplayState extends MusicBeatSubState
return;
}
var targetSong:SongMenuItem = FlxG.random.getObject(availableSongCapsules);
controls.active = false;
#if NO_FEATURE_TOUCH_CONTROLS
letterSort.inputEnabled = false;
#end
var instrumentalChoices:Array<String> = ['default', 'random'];
// Seeing if I can do an animation...
curSelected = grpCapsules.members.indexOf(targetSong);
changeSelection(0); // Trigger an update.
capsuleOptionsMenu = new CapsuleOptionsMenu(this, randomCapsule.targetPos.x + 175, randomCapsule.targetPos.y + 115, instrumentalChoices);
capsuleOptionsMenu.cameras = [funnyCam];
capsuleOptionsMenu.zIndex = 10000;
add(capsuleOptionsMenu);
capsuleOptionsMenu.onConfirm = function(instChoice:String) {
capsuleOnConfirmRandom(availableSongCapsules, instChoice);
}
}
/**
* Called when hitting ENTER on an instrumental choice for random capsule
*/
function capsuleOnConfirmRandom(availableSongCapsules:Array<SongMenuItem>, instChoice:String):Void
{
cleanupCapsuleOptionsMenu();
controls.active = false;
#if NO_FEATURE_TOUCH_CONTROLS
letterSort.inputEnabled = false;
#end
// Act like we hit Confirm on that song.
capsuleOnConfirmDefault(targetSong);
var targetSongCap:SongMenuItem = FlxG.random.getObject(availableSongCapsules);
// Seeing if I can do an animation...
curSelected = grpCapsules.members.indexOf(targetSongCap);
changeSelection(); // Trigger an update. This will also fix the target variation.
var targetSongId:String = targetSongCap?.freeplayData?.data.id ?? 'unknown';
var targetSongNullable:Null<Song> = SongRegistry.instance.fetchEntry(targetSongId);
if (targetSongNullable == null)
{
FlxG.log.warn('WARN: could not find song with id (${targetSongId})');
controls.active = true;
#if NO_FEATURE_TOUCH_CONTROLS
letterSort.inputEnabled = true;
#end
return;
}
var targetSong:Song = targetSongNullable;
var targetDifficultyId:String = currentDifficulty;
var targetVariation:Null<String> = currentVariation;
var targetDifficulty:Null<SongDifficulty> = targetSong.getDifficulty(targetDifficultyId, targetVariation);
if (targetDifficulty == null)
{
FlxG.log.warn('WARN: could not find difficulty with id (${targetDifficultyId})');
controls.active = true;
#if NO_FEATURE_TOUCH_CONTROLS
letterSort.inputEnabled = true;
#end
return;
}
if (instChoice == 'random')
{
var baseInstrumentalId:String = targetSong.getBaseInstrumentalId(targetDifficultyId, targetDifficulty?.variation ?? Constants.DEFAULT_VARIATION) ?? '';
var altInstrumentalIds:Array<String> = targetSong.listAltInstrumentalIds(targetDifficultyId,
targetDifficulty?.variation ?? Constants.DEFAULT_VARIATION) ?? [];
// Choose a random instrumental
var instrumentalIds:Array<String> = [baseInstrumentalId].concat(altInstrumentalIds);
var targetInstrumentalId:String = FlxG.random.getObject(instrumentalIds);
// Hit Confirm on that song with random instrumental
capsuleOnConfirmDefault(targetSongCap, targetInstrumentalId);
}
else
{
// Hit Confirm on that song with default instrumental
capsuleOnConfirmDefault(targetSongCap);
}
}
/**
@@ -2614,6 +2698,13 @@ class FreeplayState extends MusicBeatSubState
new FlxTimer().start(styleData?.getStartDelay(), function(tmr:FlxTimer) {
FunkinSound.emptyPartialQueue();
#if FEATURE_TOUCH_CONTROLS
if (backButton != null)
{
backTransitioning = true;
FlxTween.tween(backButton, {alpha: 0}, 0.2, {ease: FlxEase.quadOut});
}
#end
funnyCam.fade(FlxColor.BLACK, 0.2, false, function() {
Paths.setCurrentLevel(cap?.freeplayData?.levelId);
LoadingState.loadPlayState(
@@ -2732,9 +2823,7 @@ class FreeplayState extends MusicBeatSubState
if (currentCapsule.freeplayData == null) albumRoll.albumId = null;
changeDiff();
if (currentCapsule.freeplayData == null) currentCapsule.refreshDisplay();
else
currentCapsule.refreshDisplay(!prepForNewRank);
currentCapsule.refreshDisplay(currentCapsule.freeplayData == null);
for (index => capsule in grpCapsules.members)
{
@@ -2823,7 +2912,6 @@ class FreeplayState extends MusicBeatSubState
if (songDifficulty != null)
{
Conductor.instance.mapTimeChanges(songDifficulty.timeChanges);
Conductor.instance.update(FlxG.sound?.music?.time ?? 0.0);
}
}
}
+26 -45
View File
@@ -47,8 +47,10 @@ class SongMenuItem extends FlxSpriteGroup
public var favIcon:FlxSprite;
public var ranking:FreeplayRank;
public var blurredRanking:FreeplayRank;
public var fakeRanking:FreeplayRank;
public var fakeBlurredRanking:FreeplayRank;
public var targetPos:FlxPoint = new FlxPoint();
public var doLerp:Bool = false;
@@ -144,14 +146,25 @@ class SongMenuItem extends FlxSpriteGroup
// doesn't get added, simply is here to help with visibility of things for the pop in!
grpHide = new FlxGroup();
fakeRanking = new FreeplayRank(400, 15);
fakeRanking = new FreeplayRank(420, 41);
add(fakeRanking);
fakeBlurredRanking = new FreeplayRank(fakeRanking.x, fakeRanking.y);
fakeBlurredRanking.shader = new GaussianBlurShader(1);
add(fakeBlurredRanking);
fakeRanking.visible = false;
fakeBlurredRanking.visible = false;
fakeRanking.visible = false;
ranking = new FreeplayRank(400, 15);
ranking = new FreeplayRank(420, 41);
add(ranking);
blurredRanking = new FreeplayRank(ranking.x, ranking.y);
blurredRanking.shader = new GaussianBlurShader(1);
add(blurredRanking);
sparkle = new FlxSprite(ranking.x, ranking.y);
sparkle.frames = Paths.getSparrowAtlas('freeplay/sparkle');
sparkle.animation.addByPrefix('sparkle', 'sparkle Export0', 24, false);
@@ -464,6 +477,7 @@ class SongMenuItem extends FlxSpriteGroup
sparkle.visible = false;
this.ranking.rank = newRank;
this.blurredRanking.rank = newRank;
if (newRank == PERFECT_GOLD)
{
@@ -704,7 +718,7 @@ class SongMenuItem extends FlxSpriteGroup
public function intendedY(index:Float):Float
{
return (index * ((height * realScaled) + 10)) + 120;
return (index * ((capsule.height * realScaled) + 10)) + 120;
}
function set_selected(value:Bool):Bool
@@ -754,16 +768,10 @@ class SongMenuItem extends FlxSpriteGroup
}
}
/**
* Holds blurred and unblurred versions of the rank icon
*/
class FreeplayRank extends FlxSpriteGroup
class FreeplayRank extends FlxSprite
{
public var rank(default, set):Null<ScoringRank> = null;
var spr:FlxSprite;
var blur:FlxSprite;
function set_rank(val:Null<ScoringRank>):Null<ScoringRank>
{
rank = val;
@@ -776,8 +784,7 @@ class FreeplayRank extends FlxSpriteGroup
{
this.visible = true;
spr.animation.play(val.getFreeplayRankIconAsset(), true, false);
blur.animation.play(val.getFreeplayRankIconAsset(), true, false);
animation.play(val.getFreeplayRankIconAsset(), true, false);
centerOffsets(false);
@@ -814,24 +821,14 @@ class FreeplayRank extends FlxSpriteGroup
{
super(x, y);
blur = new FlxSprite();
blur.frames = Paths.getSparrowAtlas('freeplay/rankbadges');
blur.shader = new GaussianBlurShader(1);
add(blur);
frames = Paths.getSparrowAtlas('freeplay/rankbadges');
spr = new FlxSprite();
spr.frames = Paths.getSparrowAtlas('freeplay/rankbadges');
add(spr);
for (i in members)
{
i.animation.addByPrefix('PERFECT', 'PERFECT rank0', 24, false);
i.animation.addByPrefix('EXCELLENT', 'EXCELLENT rank0', 24, false);
i.animation.addByPrefix('GOOD', 'GOOD rank0', 24, false);
i.animation.addByPrefix('PERFECTSICK', 'PERFECT rank GOLD', 24, false);
i.animation.addByPrefix('GREAT', 'GREAT rank0', 24, false);
i.animation.addByPrefix('LOSS', 'LOSS rank0', 24, false);
}
animation.addByPrefix('PERFECT', 'PERFECT rank0', 24, false);
animation.addByPrefix('EXCELLENT', 'EXCELLENT rank0', 24, false);
animation.addByPrefix('GOOD', 'GOOD rank0', 24, false);
animation.addByPrefix('PERFECTSICK', 'PERFECT rank GOLD', 24, false);
animation.addByPrefix('GREAT', 'GREAT rank0', 24, false);
animation.addByPrefix('LOSS', 'LOSS rank0', 24, false);
blend = BlendMode.ADD;
@@ -841,22 +838,6 @@ class FreeplayRank extends FlxSpriteGroup
scale.set(0.9, 0.9);
updateHitbox();
}
/**
* Plays an animation for each member of the group
* Just passes the arguments to `animation.play`, since that's not available in FlxGroups
* @param animName The name of the animation to play
* @param force false
* @param reversed false
* @param frame 0
*/
public function playAnimationEach(animName:String, force = false, reversed = false, frame = 0):Void
{
for (i in members)
{
i.animation.play(animName, force, reversed, frame);
}
}
}
class CapsuleNumber extends FlxSprite
@@ -7,7 +7,7 @@ import funkin.modding.events.ScriptEvent.HitNoteScriptEvent;
import funkin.modding.events.ScriptEvent.SongTimeScriptEvent;
import funkin.modding.events.ScriptEvent.UpdateScriptEvent;
import funkin.play.character.BaseCharacter;
import funkin.play.character.CharacterData.CharacterDataParser;
import funkin.data.character.CharacterData.CharacterDataParser;
import haxe.ui.containers.Box;
import haxe.ui.core.Component;
import haxe.ui.events.AnimationEvent;
+2 -1
View File
@@ -394,7 +394,8 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
if (ControlsHandler.usingExternalInputDevice)
{
#end
testStrumline.y = Preferences.downscroll ? FlxG.height - (testStrumline.height + 45) - Constants.STRUMLINE_Y_OFFSET : (testStrumline.height / 2)
var height = testStrumline.strumlineNotes.members[0].height;
testStrumline.y = Preferences.downscroll ? FlxG.height - (height + 45) - Constants.STRUMLINE_Y_OFFSET : (height / 2)
- Constants.STRUMLINE_Y_OFFSET;
if (Preferences.downscroll) jumpInText.y = FlxG.height - 425;
testStrumline.isDownscroll = Preferences.downscroll;
+5
View File
@@ -290,6 +290,11 @@ class OptionsMenu extends Page<OptionsMenuPageName>
override function update(elapsed:Float):Void
{
if ((FlxG.sound.music?.volume ?? 1.0) < 0.8)
{
FlxG.sound.music.volume += 0.5 * elapsed;
}
#if FEATURE_TOUCH_CONTROLS
backButton.active = (!goingBack) ? !items.busy : true;
#end
+15 -2
View File
@@ -16,6 +16,7 @@ import funkin.ui.TextMenuList.TextMenuItem;
import funkin.ui.options.items.CheckboxPreferenceItem;
import funkin.ui.options.items.NumberPreferenceItem;
import funkin.ui.options.items.EnumPreferenceItem;
import funkin.ui.debug.FunkinDebugDisplay.DebugDisplayMode;
#if mobile
import funkin.mobile.ui.FunkinBackButton;
import funkin.mobile.input.ControlsHandler;
@@ -145,9 +146,21 @@ class PreferencesMenu extends Page<OptionsState.OptionsMenuPageName>
Preferences.zoomCamera = value;
}, Preferences.zoomCamera);
#if !mobile
createPrefItemCheckbox('Debug Display', 'If enabled, FPS and other debug stats will be displayed.', function(value:Bool):Void {
createPrefItemEnum('Debug Display', 'If enabled, FPS and other debug stats will be displayed.', [
"Advanced" => DebugDisplayMode.ADVANCED,
"Simple" => DebugDisplayMode.SIMPLE,
"Off" => DebugDisplayMode.OFF
], function(key:String, value:DebugDisplayMode):Void {
Preferences.debugDisplay = value;
}, Preferences.debugDisplay);
}, switch (Preferences.debugDisplay)
{
case DebugDisplayMode.SIMPLE: "Simple";
case DebugDisplayMode.ADVANCED: "Advanced";
default: "Off";
});
createPrefItemPercentage('Debug Display BG', "Change debug display's background opacity", function(value:Int):Void {
Preferences.debugDisplayBGOpacity = value;
}, Preferences.debugDisplayBGOpacity);
createPrefItemCheckbox('Pause on Unfocus', 'If enabled, game automatically pauses when it loses focus.', function(value:Bool):Void {
Preferences.autoPause = value;
}, Preferences.autoPause);
+5
View File
@@ -330,6 +330,11 @@ class StoryMenuState extends MusicBeatState
handleKeyPresses();
if ((FlxG.sound.music?.volume ?? 1.0) < 0.8)
{
FlxG.sound.music.volume += 0.5 * elapsed;
}
super.update(elapsed);
}
+5
View File
@@ -317,6 +317,11 @@ class TitleState extends MusicBeatState
}
if (pressedEnter && !skippedIntro && initialized) skipIntro();
if ((FlxG.sound.music?.volume ?? 1.0) < 0.8 && initialized)
{
FlxG.sound.music.volume += 0.5 * elapsed;
}
// TODO: Maybe use the dxdy method for swiping instead.
if (controls.UI_LEFT #if mobile || SwipeUtil.justSwipedLeft #end) swagShader.update(-elapsed * 0.1);
if (controls.UI_RIGHT #if mobile || SwipeUtil.justSwipedRight #end) swagShader.update(elapsed * 0.1);

Some files were not shown because too many files have changed in this diff Show More