Compare commits

...
120 Commits
Author SHA1 Message Date
HundrecandAbnormal ed86b326ab i forgot my milkshake
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-07-18 09:03:05 -05:00
Hundrec 596c5813d7 Someone get me a typewriter 2025-07-18 16:09:16 +08:00
Hundrec ae68925911 They say you can never have enough Changelog 2025-07-18 16:09:16 +08:00
Hundrec d7c3799562 Even more Changelog entries 2025-07-18 16:09:16 +08:00
Hundrec 47ab14a855 More Changelog adjustments 2025-07-18 16:09:16 +08:00
Hundrec 940e165612 Another change 2025-07-18 16:09:16 +08:00
Hundrec 5181023887 How can I play this? There's no ' 2025-07-18 16:09:16 +08:00
Hundrec 32e774b005 Additional entries for 0.7.2 2025-07-18 16:09:16 +08:00
Hundrec c353cc54a1 god effing damnit 2025-07-18 15:45:53 +08:00
AbnormalandHundrec 48d0798e5f fix bf perfect gold results anim 2025-07-18 14:03:31 +08:00
EliteMasterEricandHundrec 0463b4e26b Fix a bug where flames would show during the new rank animation 2025-07-18 12:24:30 +08:00
MAJigsaw77andMoonDroid 0c1842038f Update hmm.json 2025-07-18 04:49:03 +07:00
Karim AkraandEric aa0b8cbbbe Fixed the main menu options not working on Desktop 2025-07-17 17:19:17 -04:00
EliteMasterEricandHundrec 52cd4f6c36 one-line bug fix for freeplay flame positions :O 2025-07-18 05:09:17 +08:00
EliteMasterEric 6be7e89222 OK I tested it like 15 times in a row so if the bug still exists I'm going to blow a gasket 2025-07-17 14:50:28 -04:00
EliteMasterEric abea46939f Fix a softlock when tapping multiple buttons on the main menu at the same time 2025-07-17 14:50:28 -04:00
EliteMasterEric 3a5f1e14cf This made the flag always false? I blame the mobile people for this 2025-07-17 14:50:28 -04:00
Karim AkraandHundrec 849e413bb7 update assets 2025-07-18 02:47:05 +08:00
Mihai AlexandruandGitHub b4a7bd7462 Fix compile pref. (#1439) 2025-07-17 21:32:10 +03:00
MoonDroidandHundrec beb64528fb Properly manage swipe threshold on states. 2025-07-18 02:08:46 +08:00
luckydog7andKade eac940bccb temp credits font fix 2025-07-17 10:55:15 -07:00
MoonDroidandKade aa1c71cafd Properly get the store's IAP in main menu 2025-07-17 10:54:02 -07:00
EliteMasterEricandHundrec f4bedc359a Update Polymod version 2025-07-18 01:47:30 +08:00
Karim AkraandGitHub 01428dd5cf Fixed a tiny issue that caused cutscenes to softlock (#1432) 2025-07-17 12:51:06 +03:00
MoonDroidandGitHub c6e19c8512 Merge pull request #1423 from FunkinCrew/feature/haptics-intensity-pref 2025-07-17 04:11:16 +07:00
MoonDroid e30a4ebb7b Merge branch 'master' into feature/haptics-intensity-pref 2025-07-17 04:08:51 +07:00
Cameron TaylorandHundrec 949f409cff a few little FreeplayScore tidying perhaps 2025-07-17 04:28:52 +08:00
Cameron TaylorandHundrec 4c5aa9743e a few little FreeplayState tidying perhaps 2025-07-17 04:28:52 +08:00
Cameron TaylorandHundrec 3fa2b3574b potential fix for FUNK-2563 (incorrectly rounds to negative integer limit?) 2025-07-17 04:28:52 +08:00
TechnikTilandsector-a 1e41ac3052 make currentCharacterId the actual current character ID. also get the style from the style id instead of the character 2025-07-16 23:26:22 +03:00
MoonDroid 936812ae1e assets submod 2025-07-17 03:05:16 +07:00
MoonDroid ad3bf2d2d2 Update haptic values to compensate for default 1 multiplier 2025-07-17 03:04:54 +07:00
kade-githubandHundrec 1b1c2a124b STOP MAKING IT PRIVATE!!! 2025-07-17 03:58:03 +08:00
kade-githubandHundrec 2aff2fa201 downscroll... 2025-07-17 03:58:03 +08:00
kade-githubandHundrec 7ec602f6ec forgot dis 2025-07-17 03:58:03 +08:00
kade-githubandHundrec 4fbeffb57c extra sync logic fix 2025-07-17 03:58:03 +08:00
kade-githubandHundrec f029a27a3f external devices and what not 2025-07-17 03:58:03 +08:00
8a99fa4184 Add toggling the old icon by touching the player icon
Co-authored-by: MoonDroid <81515012+moondroidcoder@users.noreply.github.com>
2025-07-17 02:22:17 +07:00
MAJigsaw77andHundrec f21b015292 Update hmm.json 2025-07-17 03:14:09 +08:00
Karim AkraandHundrec bc8e35409b update assets submod to fix the stage vanishing when getting hit by can in practice mode 2025-07-17 03:01:25 +08:00
Karim AkraandHundrec 006f1dce0a Fixed the crash in 2hot and blazin 2025-07-17 03:01:25 +08:00
Karim AkraandHundrec 78186188d7 Revert "openalsoft audio fix"
This reverts commit 4029e92181.
2025-07-17 02:51:44 +08:00
Karim AkraandHundrec 762486639e Revert "run da formatter"
This reverts commit b5bb9721da.
2025-07-17 02:51:44 +08:00
AbnormalandKade b5bb9721da run da formatter 2025-07-16 10:29:48 -07:00
4029e92181 openalsoft audio fix
Co-authored-by: Smokey <78874128+Smokey555@users.noreply.github.com>
Co-authored-by: Cobalt Bar <79053181+CobaltBar@users.noreply.github.com>
2025-07-16 10:29:48 -07:00
sector-aandHundrec 9c7f87c225 Disable the nametag mosaic sequence on menu startup 2025-07-16 23:33:51 +08:00
sector-a 43420092f3 Change minimal intensity multiplier to 0.1 2025-07-16 17:34:41 +03:00
sector-a d8cd580535 Make haptics intensity on ios and android the same 2025-07-16 16:51:02 +03:00
sector-a 13c08b285d Add haptics intensity preference 2025-07-16 16:51:02 +03:00
AbnormalandHundrec 6238b72d8c bump polymod to fix a pico mix crash 2025-07-16 21:26:49 +08:00
EricandKade bf92bf3f60 Update hmm.json 2025-07-15 18:21:25 -07:00
Cameron TaylorandGitHub 6467cd75a8 IAP compiler conditional fix (#1416) 2025-07-16 01:39:12 +03:00
Karim AkraandCameron Taylor 32dc25bbf6 Fix the credits menu on iOS 2025-07-15 18:14:47 -04:00
Karim AkraandMihai Alexandru 11cb409bd2 Fixed the crash in game over 2025-07-16 00:41:43 +03:00
Mihai AlexandruandGitHub f245365ed3 Merge pull request #1413 from FunkinCrew/mob-320/controls-menu-back-button
[MOB-320] Add back button to controls menu.
2025-07-16 00:22:32 +03:00
Karim AkraandGitHub e0d0230b88 Dont show the toast if the theres no debug message (#1405) 2025-07-16 00:17:22 +03:00
Cameron TaylorandMoonDroid f8634e2b81 tidy: small preloader code removing 2025-07-16 00:54:35 +07:00
Cameron TaylorandMoonDroid 4f68f63e2d preloader lookin alright on iOS now 2025-07-16 00:54:35 +07:00
Cameron TaylorandMoonDroid e9e0567f8b preloader placement wips
more preloader placeholder wips
2025-07-16 00:54:35 +07:00
Cameron TaylorandMoonDroid 8db768fd2a tidy(preloader): some variable renaming 2025-07-16 00:54:35 +07:00
Cameron TaylorandMoonDroid cf814ee4e2 tidy(preloader): stop the preloader from spamming traces after completing 2025-07-16 00:54:35 +07:00
Mihai AlexandruandGitHub 43b7be4e71 Merge branch 'master' into bugfix/android-toast-iap 2025-07-15 20:50:35 +03:00
MoonDroidandMihai Alexandru 7d555b4155 Update credits 2025-07-15 20:47:11 +03:00
MAJigsaw77 88ad99c3ef Merge branch 'master' into bugfix/android-toast-iap 2025-07-15 20:33:44 +03:00
MAJigsaw77 d92b7ff3f5 Merge branch 'master' into bugfix/android-toast-iap 2025-07-15 20:33:19 +03:00
MoonDroid 66c6a78319 Properly set hasInitilaized on android 2025-07-16 00:32:52 +07:00
MoonDroid e6a2a09801 Make the IAP product value actually saved into the game save file. 2025-07-16 00:32:52 +07:00
MAJigsaw77andMoonDroid 8944e07fe1 Use a separated case here. 2025-07-16 00:32:52 +07:00
MAJigsaw77andMoonDroid 188260ea81 Correctly handle iap callbacks when purchasing. 2025-07-16 00:32:52 +07:00
Hundrec 22c6ce42ba Blazin' fix changelog entry 2025-07-16 00:53:17 +08:00
EliteMasterEricandHundrec d8445fe54a Update assets submodule 2025-07-16 00:53:17 +08:00
AbnormalandHundrec c3592ee6f5 bump versionnn to 0.7.2 FUUUUUCKKK 2025-07-15 19:47:10 +08:00
MAJigsaw77 e402328969 Merge branch 'master' into bugfix/android-toast-iap 2025-07-15 12:46:04 +03:00
AbnormalandHundrec 150a1d5b0a erm......lol? 2025-07-15 17:42:06 +08:00
MAJigsaw77 d468477663 Dont show the toast if the theres no debug message 2025-07-15 12:27:20 +03:00
AbnormalandHundrec ecaf51ef41 add 2 missing entires 2025-07-15 17:12:17 +08:00
AbnormalandHundrec 05627a50d5 0.7.2 changeloggyyyyyyyy 2025-07-15 17:12:17 +08:00
kade-githubandHundrec efe333da92 fix jump note GOD DAM NIT 2025-07-15 15:11:13 +08:00
kade-githubandHundrec e946b54249 sync fixies... can't hurt 2025-07-15 15:11:13 +08:00
AbnormalandKade 1d4a94041b enable traces only on debuggggg 2025-07-15 00:01:14 -07:00
AbnormalandKade 11da19ce1e zack got pissed off about this in vc so im making a pr for this 2025-07-15 00:01:14 -07:00
Cameron TaylorandHundrec 126ed3a67a use unicode in the iOS app title to force spacing 2025-07-15 14:54:35 +08:00
HundrecandKade 468197e74e 0.7.1 Changelog 2025-07-14 23:52:10 -07:00
Cameron TaylorandGitHub 7f17ad8bb8 Merge pull request #1400 from FunkinCrew/jni-conditionals
quick android conditionals
2025-07-14 15:34:17 -04:00
HundrecandAbnormal e9ef340c63 missed one 2025-07-14 13:49:24 -05:00
Cameron TaylorandGitHub 61bda6c2c2 Merge pull request #1394 from FunkinCrew/freeplay-sound-fix
Freeplay sound fix and other little freeplay cleanups and tidyings
2025-07-14 14:48:28 -04:00
Cameron Taylor 07f649ca37 LetterSort was causing that freeplay sound bug 2025-07-14 14:47:24 -04:00
Cameron Taylor 11ecd07606 various tiny lil tidyings in FreeplayState 2025-07-14 14:47:24 -04:00
Cameron Taylor 822c5ba405 use controls.active instead of busy for FreeplayState 2025-07-14 14:47:24 -04:00
Cameron Taylor d61f89b0e4 make quick and easy little currentCapsule getter 2025-07-14 14:47:24 -04:00
Cameron Taylor 9dfae07225 capsule song text no longer exposed 2025-07-14 14:47:24 -04:00
Cameron Taylor 01fad012f2 quick android conditionals 2025-07-14 14:45:06 -04:00
AbnormalandHundrec 9843f9549c final changelog for 0.7.0 2025-07-15 02:44:46 +08:00
Cameron TaylorandKade 044106e8a9 WaveformDataParser memory optimize + speed optimize perhaps + refactor mini 2025-07-14 10:38:27 -07:00
Cameron TaylorandKade d338be9270 removed some unused code in WaveformDataParser.hx 2025-07-14 10:38:27 -07:00
Cameron TaylorandKade e4d3e18e4f charteditor: player and opponent toolboxes are disabled, so we don't want to run their handler code (since it initalizes them, which takes about 0.5s extra to load!) 2025-07-14 10:38:27 -07:00
AbnormalandHundrec dd104365e7 bump flixel AGAIN 2025-07-14 08:54:56 +08:00
AbnormalandHundrec b0581f728a bump flixel 2025-07-13 16:06:31 +08:00
MAJigsaw77 5e50432b30 Add Intent.FLAG_GRANT_WRITE_URI_PERMISSION. 2025-07-13 08:49:32 +03:00
MAJigsaw77 314d8af9c8 Some adjustments 2025-07-13 08:49:32 +03:00
MAJigsaw77 f91261f748 Small adjustment to ReloadAssetsDebugPlugin. 2025-07-13 08:49:32 +03:00
Karim AkraandMihai Alexandru 0567849665 Added a button in options menu to open the data folder
The game also reloads mods after closing the folder!!

Co-Authored-By: Mihai Alexandru <77043862+MAJigsaw77@users.noreply.github.com>
2025-07-13 08:49:32 +03:00
MAJigsaw77 a701e6a4a9 Some fixes. 2025-07-13 08:49:32 +03:00
MAJigsaw77 abfc4ca79c Fix formatting and properly delete folders recursevely. 2025-07-13 08:49:32 +03:00
Karim AkraandMihai Alexandru 797a8bfcba fix the docy provider 2025-07-13 08:49:32 +03:00
MAJigsaw77 d70838cca9 Bruh what 2025-07-13 08:49:32 +03:00
MAJigsaw77 0d845154ef Add @:unreflective here. 2025-07-13 08:49:32 +03:00
MAJigsaw77 9e04db2c5c Adjustments 2025-07-13 08:49:32 +03:00
luckydog7andMihai Alexandru c0d3b9daa2 isChildDocument implementation 2025-07-13 08:49:32 +03:00
luckydog7andMihai Alexandru 0f336ae0e6 Add provider 2025-07-13 08:49:32 +03:00
kade-githubandHundrec 8c8eedf17a sector!!!! 2025-07-12 19:05:04 +08:00
EliteMasterEricandHundrec 793b533b88 Fix being able to modify final constants 2025-07-12 18:39:26 +08:00
EliteMasterEricandHundrec 852a74c33d Bump Polymod version 2025-07-12 18:39:26 +08:00
EliteMasterEricandHundrec bfdafd7f37 You can now access abstracts properly! 2025-07-12 18:39:26 +08:00
EliteMasterEricandHundrec 37d0d1b14e Latest experimental Polymod 2025-07-12 18:39:26 +08:00
EliteMasterEricandHundrec 2649e247fa Update Polymod and HScript to include the latest fixes. 2025-07-12 18:39:26 +08:00
EliteMasterEricandHundrec fafe157a70 Update the variation list when switching difficulties forces a song switch. 2025-07-12 16:51:08 +08:00
kade-githubandHundrec 39a4292dc5 fix jump note 2025-07-12 16:48:48 +08:00
EliteMasterEricandAbnormal aad0a0afb6 Fix a crash related to pausing stage sounds on HTML5 2025-07-11 17:22:52 -05:00
Karim AkraandKade f8516cfb4d Fix sustain trail notes being flipped on upscroll in keyboards on mobile 2025-07-09 13:36:43 -07:00
59 changed files with 1703 additions and 597 deletions
-1
View File
@@ -6,7 +6,6 @@ dump/
temp/
docs/temp/
export/
project/
astc-textures/
RECOVER_*.fla
shitAudio/
+252 -93
View File
@@ -4,119 +4,278 @@ 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.0] - 2025-06-??
## [0.7.2] - 2025-07-18
### Added
- Friday Night Funkin' now has an OFFICIAL mobile version, available on the [Google Play Store]() and [Apple App Store]()!
- This version is 100% feature complete with the desktop version of the game.
- [MOBILE] New touch input compatiblity for all menus.
- [MOBILE] Added banner and interstitial advertisements to the game. You can use an in-app purchase to upgrade to the full version to permanently disable advertisements.
- [MOBILE] Graphics are compressed using the ASTC algorithm, decreasing memory usage in exchange for a slightly bigger file size.
- [MOBILE] Added haptic feedback to several areas in the game.
- A whopping ZERO new songs! Woah!
- Added null-safety to a bunch of classes within the source code.
- Added a blank `Object` class for scripts to extend, and made `FlxObject` and `FlxBasic` scriptable. ([1c36548](https://github.com/FunkinCrew/Funkin/commit/1c36548861a54e2a8ae49f715056139ce31bbd83)) - by @cyn0x8 in [#3119](https://github.com/FunkinCrew/Funkin/pull/3119)
- The Chart Editor now highlights and deletes stacked notes and using a customizable threshold. ([8cae34e](https://github.com/FunkinCrew/Funkin/commit/8cae34eed711bff70e5348ffc6178a0fd69b5846)) - by @NotHyper-474 in [#3574](https://github.com/FunkinCrew/Funkin/pull/3574)
- Added the ability to press the Chart Editor keybind in Freeplay with a song capsule selected. ([066c2d3](https://github.com/FunkinCrew/Funkin/commit/066c2d3db04162d3df6af883066fc91e05988d58)) - by @Lasercar in [#4114](https://github.com/FunkinCrew/Funkin/pull/4114)
- Added more tween types to certain Chart Editor events. ([5177e12](https://github.com/FunkinCrew/Funkin/commit/5177e1275eb2fb2b016224c139e84debb421b895)) - by @Lasercar in [#4249](https://github.com/FunkinCrew/Funkin/pull/4249)
- Added default gamepad controls for two recently added Freeplay controls. ([8dad4f5](https://github.com/FunkinCrew/Funkin/commit/8dad4f55b660a01d4e1a3a903fde684e9876f555)) - by @MrMadera in [#4559](https://github.com/FunkinCrew/Funkin/pull/4559)
- Added Perfect (Gold) to the list of available ranks in Results Debug menu. ([0261355](https://github.com/FunkinCrew/Funkin/commit/02613557787581b9c5b7532ea6e69001f1989102)) - by @AbnormalPoof in [#4642](https://github.com/FunkinCrew/Funkin/pull/4642)
- Added a few sandboxed classes to give mods limited access to the Discord and Newgrounds APIs. ([6bd6066](https://github.com/FunkinCrew/Funkin/commit/6bd6066d169fd1611a706e984bb8d3ad7109e31f)) - by @KoloInDaCrib in [#5040](https://github.com/FunkinCrew/Funkin/pull/5040)
- Pressing Ctrl + N now creates a new stage in the Stage Editor. ([bc54634](https://github.com/FunkinCrew/Funkin/commit/bc546345377f9e9e7b69306b72d494cad02ebaba)) - by @Lasercar in [#5175](https://github.com/FunkinCrew/Funkin/pull/5175)
- Added script support for Freeplay Backing Cards. ([92e7588](https://github.com/FunkinCrew/Funkin/commit/92e7588370fcbdc0e959e01e7e52eed5d62323f2)) - by @KoloInDaCrib in [#5233](https://github.com/FunkinCrew/Funkin/pull/5233)
- Added a variation indicator next to the Chart Editor playbar difficulty. ([dc7fa97](https://github.com/FunkinCrew/Funkin/commit/dc7fa97d3ca3c00edbab0adc171135af0008735d)) - by @KoloInDaCrib in [#5236](https://github.com/FunkinCrew/Funkin/pull/5236)
- Added "Flip character horizontally" to the list of shortcuts in the Animation Editor. ([f73e925](https://github.com/FunkinCrew/funkin.assets/commit/f73e925cbc1e99223095e444f9217839c7d8148e)) - by @AbnormalPoof in [funkin.assets#60](https://github.com/FunkinCrew/funkin.assets/pull/60)
- [ANDROID] Added a button in the Options menu to access the mods folder.
- [MOBILE] Added a preference to adjust the intensity of haptic feedback, ranging from 0.1 to 5.
- [MOBILE] Added an easter egg when tapping the player's healthbar icon.
### Changed
- Modified several parts of the game to look better on phone screens with wider aspect ratios, up to 20:9.
- Fixed a ton of performance issues to help the game run better on mobile devices.
- Completely reformatted and optimized every single chart file in the game, along with fixing several chart issues.
- Completely reformatted every script file within the game's assets for better readability.
- Notes now scroll more smoothly by rendering based on delta timing. ([6ad9ffc](https://github.com/FunkinCrew/Funkin/commit/6ad9ffc7f9d66bbaf6ba343663de4c7268f4be3b)) - by @KutikiPlayz in [#3544](https://github.com/FunkinCrew/Funkin/pull/3544)
- Favorite songs in Freeplay are now sorted by Week order instead of alphabetically. ([da0964a](https://github.com/FunkinCrew/Funkin/commit/da0964a7b7bbe4ece1bdbd19233eb6dba0de3ac5)) - by @Hundrec in [#3609](https://github.com/FunkinCrew/Funkin/pull/3609)
- The Character Select screen now opens on the currently selected character. ([b233a53](https://github.com/FunkinCrew/Funkin/commit/b233a53710d8ef6b0151d83f5d78e1733fe356f5)) - by @Lasercar in [#4072](https://github.com/FunkinCrew/Funkin/pull/4072)
- The undo/redo history is now cleared when loading another song in the Chart Editor. ([4c4045e](https://github.com/FunkinCrew/Funkin/commit/4c4045e274f6a7fa0bc2574468abb3b70ca68f00)) - by @Lasercar in [#4308](https://github.com/FunkinCrew/Funkin/pull/4308)
- The Options Menu can now scroll to display more menu items. ([70f0a54](https://github.com/FunkinCrew/Funkin/commit/70f0a54191597bd72a6d30d4d12ef5ece6ba078c)) - by @AbnormalPoof in [#4706](https://github.com/FunkinCrew/Funkin/pull/4706)
- The Chart Editor will now only fall back to the first available difficulty if the selected difficulty cannot be found. ([19d75ff](https://github.com/FunkinCrew/Funkin/commit/19d75ffe72f08a6adb5b4ca00cae2f913767047a)) - by @Lasercar in [#4949](https://github.com/FunkinCrew/Funkin/pull/4949)
- Adjusted the size of the Beat/Step display in the Chart Editor. ([44b1a77](https://github.com/FunkinCrew/Funkin/commit/44b1a77b3b62146504166b392ea2dfbe6f65b302)) - by @NotHyper-474 in [#4994](https://github.com/FunkinCrew/Funkin/pull/4994)
- The Freeplay character select hint now always displays if you have more than one character unlocked. ([7ccf75c](https://github.com/FunkinCrew/Funkin/commit/7ccf75cd869ba4b6f18a5adc01e65e52ae7bb809)) - by @Hundrec in [#5023](https://github.com/FunkinCrew/Funkin/pull/5023)
- Raised the FPS cap preference from 300 to 500. ([71d7169](https://github.com/FunkinCrew/Funkin/commit/71d7169ba35c01471b4274671e238b0ac2cf04ed)) - by @Hundrec in [#5044](https://github.com/FunkinCrew/Funkin/pull/5044)
- Blacklisted more classes for security reasons. ([cadfa3b](https://github.com/FunkinCrew/Funkin/commit/cadfa3b7ceae2ecabe2d544ddc4c9f453b0dfd56)) - by @NotHyper-474 in [#5185](https://github.com/FunkinCrew/Funkin/pull/5185)
- Visualizers now zero out when the game audio is muted. ([3d205e9](https://github.com/FunkinCrew/Funkin/commit/3d205e935553096ab7bbd1879d8cb69bdb550ead)) - by @Lasercar in [#5266](https://github.com/FunkinCrew/Funkin/pull/5266)
- Shifted Mommy Mearest's pixel icon to the left in Freeplay. ([d9e8085](https://github.com/FunkinCrew/funkin.assets/commit/d9e8085020cfc516b7c38ec7773081619ab840a4)) - by @KoloInDaCrib in [funkin.assets#197](https://github.com/FunkinCrew/funkin.assets/pull/197)
- Changed default OpenAL configuration settings to improve audio quality. (Thanks Smokey555, cyn0x8, and CCobaltDev!)
- The difference may be more or less noticeable on different devices and hardware.
- Applies to Desktop and Android, but not iOS yet.
- Made several improvements to Polymod and HScript. These changes might break some mods, so please update them accordingly!
- Fixed an issue where scripted classes can define two or more fields with the same name.
- Fixed an issue causing some syntax errors (such as missing commas) to be ignored by the parser.
- Scripted classes can now create static fields and functions.
- Scripted classes can now create variables with the `final` keyword.
- Scripted classes can now access variables from another scripted class with `class.someVariable`, instead of `class.scriptGet("someVariable")`
- This applies to functions too: `class.someFunction()`
- Scripted classes that don't extend another class can now be created!
- This only works if you access the class in a static context. Creating an instance of said class doesn't work just yet!
- Added support for properties (`get_` and `set_` functions) (Thanks KoloInDaCrib!)
- Added support for abstracts in a static context. (Thanks lemz1!)
- You can now use classes like `FlxColor` properly!
- Added support for creating and using enums. (Thanks lemz1!)
- You can import them in another script as usual.
- Added support for renaming imported classes using the `as` keyword. (Thanks KoloInDaCrib!)
- Fixed `try`/`catch` blocks not working properly. (Thanks NotHyper-474!)
- Fixed null-safe field access not working properly for functions (ex. `class?.someFunction()). (Thanks KoloInDaCrib!)
### Fixed
- Songs no longer skip forward at the beginning with high visual offsets. ([02c64ae](https://github.com/FunkinCrew/Funkin/commit/02c64aea9c2e7fe06f9116312d5ef6c049dc8e9c)) - by @xenkap in [#3732](https://github.com/FunkinCrew/Funkin/pull/3732)
- Fixed various issues and added missing functionalities to the Stage Editor. ([15083a4](https://github.com/FunkinCrew/Funkin/commit/15083a477626f67125bfd60a844b42b85a82d8c3)) - by @KoloInDaCrib in [#3974](https://github.com/FunkinCrew/Funkin/pull/3974)
- Pressing the Chart Editor keybind during a song now opens to the variation and difficulty you were playing. ([aeb2dd5](https://github.com/FunkinCrew/Funkin/commit/aeb2dd53983b12fa0152861cc51a161115ea2729)) - by @Lasercar in [#4116](https://github.com/FunkinCrew/Funkin/pull/4116)
- Dragging a hold note in the Chart Editor now drags its trail along with its head. ([d3d8aaa](https://github.com/FunkinCrew/Funkin/commit/d3d8aaae7bfbfa8975a9573807b9a3ca68a1ff55)) - by @KoloInDaCrib in [#4127](https://github.com/FunkinCrew/Funkin/pull/4127)
- The help guide in the Stage Editor can no longer be opened multiple times. ([8c1fa3e](https://github.com/FunkinCrew/Funkin/commit/8c1fa3e630e595eac774e81f2c0f8f32ff97d927)) - by @Lasercar in [#4128](https://github.com/FunkinCrew/Funkin/pull/4128)
- The Chart Editor now properly saves audio levels when exiting. ([6da2718](https://github.com/FunkinCrew/Funkin/commit/6da2718f116b45dd969ee74c808b86f63631984c)) - by @Lasercar in [#4149](https://github.com/FunkinCrew/Funkin/pull/4149)
- Holding Ctrl and clicking on a hold note trail no longer crashes the Chart Editor. ([279bf2a](https://github.com/FunkinCrew/Funkin/commit/279bf2acd0e61b890a63b2dc4c82c6f6dc3c9ffd)) - by @Lasercar in [#4203](https://github.com/FunkinCrew/Funkin/pull/4203)
- Removed spammy console traces from `DiscordClient`. ([e89f9f5](https://github.com/FunkinCrew/Funkin/commit/e89f9f50dc6085e3550736459ce9e4fd02c1fc5b)) - by @AbnormalPoof in [#4207](https://github.com/FunkinCrew/Funkin/pull/4207)
- The Debug Menu can no longer be opened after selecting an item in the Main Menu. ([5695bc2](https://github.com/FunkinCrew/Funkin/commit/5695bc20e721f0afd5b97a36167f13e550c12b16)) - by @Lasercar in [#4211](https://github.com/FunkinCrew/Funkin/pull/4211)
- Fixed the Chart Editor timer occasionally displaying incorrect millisecond values. ([26dc895](https://github.com/FunkinCrew/Funkin/commit/26dc895a27e0d7e49469251cd68b83be66384e15)) - by @Hundrec in [#4257](https://github.com/FunkinCrew/Funkin/pull/4257)
- The Chart Editor "Load Metadata File" and "Load Chart File" buttons now function properly. ([5eecef1](https://github.com/FunkinCrew/Funkin/commit/5eecef17a143cbc8cf2307bc29a20c96b8eed6a8)) - by @Lasercar in [#4278](https://github.com/FunkinCrew/Funkin/pull/4278)
- The "New Highscore" text no longer appears more than once in the Results screen. ([c0d8e1b](https://github.com/FunkinCrew/Funkin/commit/c0d8e1b70356ec3492e57602a7d30fc2c595544b)) - by @Lasercar in [#4319](https://github.com/FunkinCrew/Funkin/pull/4319)
- The Story Mode Weekend 1 level title no longer clips into other level titles below it. ([b86efaa](https://github.com/FunkinCrew/Funkin/commit/b86efaaf7bc6b96fc3234b8f22420c2178fa4306)) - by @KoloInDaCrib in [#4348](https://github.com/FunkinCrew/Funkin/pull/4348)
- The debug cursor is now always hidden when the game starts. ([6222c38](https://github.com/FunkinCrew/Funkin/commit/6222c389e301fa2bb4939697376c4e51e29a9977)) - by @Hundrec in [#4520](https://github.com/FunkinCrew/Funkin/pull/4520)
- Fixed Newgrounds score submissions for Lit Up and Lit Up (BF Mix). ([183cec6](https://github.com/FunkinCrew/Funkin/commit/183cec62dc1fd3c7f3f634dd3e2400e6ee77b476)) - by @Raltyro in [#4577](https://github.com/FunkinCrew/Funkin/pull/4577)
- Freeplay song ranks no longer disappear after changing variations. ([c00f10a](https://github.com/FunkinCrew/Funkin/commit/c00f10adc8ebc7a6876bf20d6a4d15e44bb7d863)) - by @VioletSnowLeopard in [#4583](https://github.com/FunkinCrew/Funkin/pull/4583)
- Freeplay song capsules now cycle through long names consistently. ([ae086e7](https://github.com/FunkinCrew/Funkin/commit/ae086e7810437069efd3a4afbd55a5c303f473fb)) - by @VioletSnowLeopard in [#4677](https://github.com/FunkinCrew/Funkin/pull/4677)
- Properly blacklist a certain class from scripts. ([3dc7699](https://github.com/FunkinCrew/Funkin/commit/3dc7699aac737998b637ff9a9f16986a434424be)) - by @charlesisfeline in [#4773](https://github.com/FunkinCrew/Funkin/pull/4773)
- The song countdown no longer stacks when restarting or continues behind the Pause Menu. ([63eca96](https://github.com/FunkinCrew/Funkin/commit/63eca96c98a87e7155df8b2a1735f269ea83e1b5)) - by @NotHyper-474 in [#4875](https://github.com/FunkinCrew/Funkin/pull/4875)
- The charter field in the song metadata now properly displays the charter in the Chart Editor. ([4d24087](https://github.com/FunkinCrew/Funkin/commit/4d24087c2126f64f0bbdf32557e6f0ee7f2aee24)) - by @Lasercar in [#4879](https://github.com/FunkinCrew/Funkin/pull/4879)
- The Results Debug menu now shows the correct rank after recent scoring changes. ([66e36b9](https://github.com/FunkinCrew/Funkin/commit/66e36b99051298ddf306d981b3b0f29ddc88ffe3)) - by @NotHyper-474 in [#4905](https://github.com/FunkinCrew/Funkin/pull/4905)
- Opening the logs or backups folder before it's created no longer crashes the game. ([60e2b88](https://github.com/FunkinCrew/Funkin/commit/60e2b88703678776994d7a038687773a1c639019)) - by @NotHyper-474 in [#4940](https://github.com/FunkinCrew/Funkin/pull/4940)
- An easter egg now restarts the song using the correct instrumental. ([a499f46](https://github.com/FunkinCrew/Funkin/commit/a499f46e6dcec301ab35e62b9e494c7fac2e9c9b)) - by @KoloInDaCrib in [#4956](https://github.com/FunkinCrew/Funkin/pull/4956)
- Girlfriend's and Nene's combo drop animations now play consistently. ([34d5ed1](https://github.com/FunkinCrew/Funkin/commit/34d5ed11695cef7348c13505f13fb1da38b7988c)) - by @VioletSnowLeopard in [#4968](https://github.com/FunkinCrew/Funkin/pull/4968)
- Fixed a few visual issues with Freeplay's rank slam animation. ([ab817bb](https://github.com/FunkinCrew/Funkin/commit/ab817bb1eab74ae71c1b1fd74d7512a11e6d4339)) - by @Lasercar in [#4986](https://github.com/FunkinCrew/Funkin/pull/4986)
- Nonexistent characters no longer crash the Chart Editor. ([3bbb4b0](https://github.com/FunkinCrew/Funkin/commit/3bbb4b06c8c1a1885a18d4354fcfa4363a0c6c75)) - by @Lasercar in [#5008](https://github.com/FunkinCrew/Funkin/pull/5008)
- The Chart Editor playhead can no longer be scrolled to before the beginning of the song. ([03a0975](https://github.com/FunkinCrew/Funkin/commit/03a09752991c830be12befd5488fc8a102273c99)) - by @Hundrec in [#5024](https://github.com/FunkinCrew/Funkin/pull/5024)
- Fixed incorrect highlighting and squashed text on Freeplay song capsules. ([0c62428](https://github.com/FunkinCrew/Funkin/commit/0c62428fc883c0fa6d09cd403efb287ff3af8c53)) - by @VioletSnowLeopard in [#5036](https://github.com/FunkinCrew/Funkin/pull/5036)
- [MOBILE] Weekend 1 Story Mode no longer crashes before loading into Blazin'.
- [MOBILE] Beating 2hot from Freeplay no longer crashes in the Results screen.
- [MOBILE] Retrying and pressing the Back button at the same time no longer crashes the game.
- [MOBILE] Pressing the Options and Back buttons at the same time no longer softlocks the game.
- [HTML5] Pausing while the train passes by on the Week 3 Erect stage no longer crashes.
- [DESKTOP] Getting a Bad/Shit rating on Blazin' no longer breaks animations.
- The scroll sound no longer plays once after entering Freeplay.
- The Freeplay song preview and album cover now update properly when switching variations.
- The Freeplay clear percent counter now consistently displays the correct value on unranked songs.
- The Freeplay difficulty star flames no longer become offset from the stars.
- The Freeplay difficulty star flames no longer appear during a new rank animation.
- The Freeplay menu now correctly assigns the `currentCharacterId`. (Thanks TechnikTil!)
- Boyfriend's Perfect (Gold) Results animation now loops properly.
- [DESKTOP] The Input Offsets menu no longer activates the debug cursor.
- The Input Offsets Test menu no longer generates stacked notes.
- The Input Offsets Test menu drums no longer desync from the rest of the track.
- The Input Offsets Test menu no longer breaks when a keyboard or controller is connected.
- [MOBILE] Sustain trails now display properly with upscroll enabled.
- [MOBILE] Added a Back button to the keyboard/gamepad Controls menu.
- [iOS] Fixed app name spacing on the Home Screen.
- [iOS] Adjusted the preloader to accommodate for different screen sizes.
- [iOS] The Upgrade button no longer appears if you have already purchased it.
- [ANDROID] Fixed some issues with scrolling.
- [ANDROID] Toasts with blank messages no longer appear.
- Fixed a critical security vulnerability that could be exploited in mods.
- A few more bugfixes and optimizations here and there.
## New Contributors for 0.7.2
* @Smokey555 made their first contribution in [#3318](https://github.com/FunkinCrew/Funkin/pull/3318)
* @CCobaltDev made their first contribution in [#3318](https://github.com/FunkinCrew/Funkin/pull/3318)
## [0.7.1] - 2025-07-15
### Fixed
- Properly implemented ad playback on iOS devices.
## [0.7.0] - 2025-07-15
### Added
- Friday Night Funkin' now has OFFICIAL mobile versions for Android and iOS, available on the Google Play Store and Apple App Store!
- This version contains 100% of the songs from the desktop version of the game.
- [MOBILE] New touch input compatibility for all menus.
- [MOBILE] Added banner and interstitial advertisements to the game. You can upgrade to the full version through an in-app purchase to permanently disable advertisements.
- [MOBILE] Graphics are compressed using the ASTC algorithm, decreasing memory usage in exchange for a slightly larger file size.
- [MOBILE] Added haptic feedback to several areas of the game.
- Added a visual indicator that shows available difficulties for the currently selected song in Freeplay.
- Overhauled the input offsets system, including:
- One unified "offset" value.
- An "Offset Calibration" screen where the game determines your ideal offset.
- A "Test" screen where you can play a short note pattern to try out your offset.
- A brand new offset testing theme: Syncobation by Kawai Sprite!
- The ability to change your offsets in the Pause Menu, mid-song!
- The Input Offsets menu isn't yet available on HTML5, but offsets are still configurable through the Pause Menu.
- Added null-safety to a bunch of classes in the source code.
- Added the Changelog back to the game files, written by Hundrec and AbnormalPoof!
- Added a few sandboxed classes to give mods limited access to the Discord and Newgrounds APIs. ([50d9584](https://github.com/FunkinCrew/Funkin/commit/50d9584a388bd891aa2f8b68a5cde894a6e1ede6)) - by @KoloInDaCrib in [#5040](https://github.com/FunkinCrew/Funkin/pull/5040)
- Added script support for Freeplay Backing Cards. ([0001017](https://github.com/FunkinCrew/Funkin/commit/0001017c003be653236c6cc56487c7d0ee33633e)) - by @KoloInDaCrib in [#5233](https://github.com/FunkinCrew/Funkin/pull/5233)
- Sparrow results screen animations can now be scriptable. ([7bb2336](https://github.com/FunkinCrew/Funkin/commit/7bb23369727ca4955aa1fbe25e5798809e8169bd)) - by @KoloInDaCrib in [#5168](https://github.com/FunkinCrew/Funkin/pull/5168)
- Added a blank `Object` class for scripts to extend, and made `FlxObject` and `FlxBasic` scriptable. ([eb6becc](https://github.com/FunkinCrew/Funkin/commit/eb6becc03fff76117ee3fcbeb32fe254236ca232)) - by @cyn0x8 in [#3119](https://github.com/FunkinCrew/Funkin/pull/3119)
- Added default gamepad controls for two recently added Freeplay controls. ([a0d3f8e](https://github.com/FunkinCrew/Funkin/commit/a0d3f8ec553e06b625b463c7989658edbebbbdf5)) - by @MrMadera in [#4559](https://github.com/FunkinCrew/Funkin/pull/4559)
- Added the ability to press the Chart Editor keybind in Freeplay with a song capsule selected. ([2221594](https://github.com/FunkinCrew/Funkin/commit/2221594883afa7cd0e518fca7ea975d05626692a)) - by @Lasercar in [#4114](https://github.com/FunkinCrew/Funkin/pull/4114)
- The Chart Editor now highlights and deletes stacked notes using a customizable threshold. ([8cae34e](https://github.com/FunkinCrew/Funkin/commit/8cae34eed711bff70e5348ffc6178a0fd69b5846)) - by @NotHyper-474 in [#3574](https://github.com/FunkinCrew/Funkin/pull/3574)
- Added a variation indicator next to the Chart Editor playbar difficulty. ([ccd0148](https://github.com/FunkinCrew/Funkin/commit/ccd0148e9b46d512a22b4958d3f289cfc7854965)) - by @KoloInDaCrib in [#5236](https://github.com/FunkinCrew/Funkin/pull/5236)
- Added more tween types to certain Chart Editor events. ([5177e12](https://github.com/FunkinCrew/Funkin/commit/5177e1275eb2fb2b016224c139e84debb421b895)) - by @Lasercar in [#4249](https://github.com/FunkinCrew/Funkin/pull/4249)
- Pressing Ctrl + N now creates a new stage in the Stage Editor. ([576f8e5](https://github.com/FunkinCrew/Funkin/commit/576f8e54ff8ca8e205241fafa33d0256b62d11d5)) - by @Lasercar in [#5175](https://github.com/FunkinCrew/Funkin/pull/5175)
- Added "Flip character horizontally" to the list of shortcuts in the Animation Editor. ([c464cae](https://github.com/FunkinCrew/funkin.assets/commit/c464caec921dcefef7b0b74b2abf95e76ce64491)) - by @AbnormalPoof in [funkin.assets#60](https://github.com/FunkinCrew/funkin.assets/pull/60)
- Added Perfect (Gold) to the list of available ranks in Results Debug menu. ([c5308cc](https://github.com/FunkinCrew/Funkin/commit/c5308ccbb9d2b98c62fa4974b8ad7ac1e1ec7d19)) - by @AbnormalPoof in [#4642](https://github.com/FunkinCrew/Funkin/pull/4642)
- [MOBILE] Implemented Kevin and Michael.
### Changed
- The mod API version now supports v0.7.0, along with v0.6.3. Be sure to check that your mods still work!
- Updated the app icon for Desktop platforms.
- [MOBILE] Modified several parts of the game to look better on phone screens with wider aspect ratios, up to 20:9.
- [DESKTOP] The game now tries to match the window's aspect ratio when changing states, extending as wide as 20:9.
- [DESKTOP] Included Mobile stage expansions on Desktop as well. Now you'll have more room for camera events!
- Playable Pico and Weekend 1 songs are now always unlocked in Freeplay, even on new saves.
- The Freeplay difficulty graphic now scrolls smoothly when changing difficulties.
- The "Pause on Unfocus" preference now opens the Pause Menu when unfocusing during a song.
- Scripts can now make hold note trails semi-transparent.
- Completely reformatted every script file within the game's assets for better readability.
- Completely reformatted and optimized every single chart file in the game.
- Recharted pico-speaker's chart in Stress
- Tweaked charts for the following songs:
- Bopeebo [all difficulties] - Removed an extra hey animation event
- Bopeebo (Pico Mix) [Hard] - Added a missing note in Section 24
- Fresh Erect [Nightmare] - Added a missing grace note for BF in Section 24
- South Erect [Nightmare] - Added missing grace notes for BF in Sections 13, 17, and 53
- Philly Nice [Hard] - Added missing grace notes for Pico in Sections 30 and 62
- Philly Nice [all difficulties] - Added hey animations throughout the song
- Philly Nice Erect [Erect] - Added a grace note for BF in Section 33, removed a stacked note for opponent in Section 12
- Philly Nice (Pico Mix) [Normal] - Adjusted a left note by 1/96 in Section 60
- Blammed (Pico Mix) [Hard] - Added a missing jack in Section 46
- Satin Panties [Hard] - Added grace notes in Sections 7-10
- Satin Panties [Normal/Hard] - Made Mom sing a sustain rather than two notes in Section 30
- High Erect [Erect/Nightmare] - Added a missing note in Section 16
- Cocoa [Easy] - Added some notes to reduce sparseness, fixed Mom singing Dad's notes
- Cocoa Erect [Erect/Nightmare] - Reimplemented BF's censored notes for Nightmare, adjusted one note by 1/48 in Section 63
- Eggnog Erect [Erect/Nightmare] Added two grace notes in Sections 10 and 14 and a missing note for Dad in Section 44
- Eggnog (Pico Mix) [Hard] - Added a missing grace note for Pico that was present on Normal in Section 20
- Roses [Normal/Hard] - Made Senpai sing a sustain rather than two notes (sneaky)
- Roses Erect [Erect/Nightmare] - Mirrored the changes from normal Roses
- Guns [all difficulties] - Added a missing note in Sections 28 and 32 and adjusted a hold note's length in Section 73
- Stress [Hard] - Split whole notes in halves in Sections 57-60
- Darnell [Hard] - Added one missing note for Pico in Section 35
- Darnell [all difficulties] - Adjusted camera event timings for consistency
- Darnell (BF Mix) [all difficulties] - Removed 3 extra notes and fixed Darnell's pattern being offset
- Lit Up [all difficulties] - Added 4 sustains for Darnell throughout the song
- Lit Up (BF Mix) [all difficulties] - Added 4 sustains for Darnell throughout the song
- 2hot [Easy/Hard] - Fixed remaining offset rhythms (for real this time)
- Notes now scroll more smoothly by rendering based on delta timing. ([6ad9ffc](https://github.com/FunkinCrew/Funkin/commit/6ad9ffc7f9d66bbaf6ba343663de4c7268f4be3b)) - by @KutikiPlayz in [#3544](https://github.com/FunkinCrew/Funkin/pull/3544)
- The Freeplay character select hint now always displays if you have more than one character unlocked. ([7ccf75c](https://github.com/FunkinCrew/Funkin/commit/7ccf75cd869ba4b6f18a5adc01e65e52ae7bb809)) - by @Hundrec in [#5023](https://github.com/FunkinCrew/Funkin/pull/5023)
- Favorite songs in Freeplay are now sorted by Week order instead of alphabetically. ([da0964a](https://github.com/FunkinCrew/Funkin/commit/da0964a7b7bbe4ece1bdbd19233eb6dba0de3ac5)) - by @Hundrec in [#3609](https://github.com/FunkinCrew/Funkin/pull/3609)
- Shifted Mommy Mearest's pixel icon to the left in Freeplay. ([d861eba](https://github.com/FunkinCrew/funkin.assets/commit/d861ebac027dd07d0254c79d7c89b59ee04b38f1)) - by @KoloInDaCrib in [funkin.assets#197](https://github.com/FunkinCrew/funkin.assets/pull/197)
- The Character Select screen now opens on the currently selected character. ([4819a74](https://github.com/FunkinCrew/Funkin/commit/4819a74c2959cc9b32dfe2cb76c3a4c00e7c7f9a)) - by @Lasercar in [#4072](https://github.com/FunkinCrew/Funkin/pull/4072)
- Visualizers now zero out when the game audio is muted. ([6dcec59](https://github.com/FunkinCrew/Funkin/commit/6dcec592f467a0daeb8ff1e0ce122916e36ca869)) - by @Lasercar in [#5266](https://github.com/FunkinCrew/Funkin/pull/5266)
- The Options Menu can now scroll to display more menu items. ([70f0a54](https://github.com/FunkinCrew/Funkin/commit/70f0a54191597bd72a6d30d4d12ef5ece6ba078c)) - by @AbnormalPoof in [#4706](https://github.com/FunkinCrew/Funkin/pull/4706)
- Raised the FPS cap preference from 300 to 500. ([be73134](https://github.com/FunkinCrew/Funkin/commit/be7313453f70983fff55e69a8b52d741f0cc53b4)) - by @Hundrec in [#5044](https://github.com/FunkinCrew/Funkin/pull/5044)
- The Credits menu now uses less memory, especially with many entries. ([1b68c3a](https://github.com/FunkinCrew/Funkin/commit/1b68c3a8d6f66905a9a508a1cb692fe3beb7b4a2)) - by @lemz1 in [#2655](https://github.com/FunkinCrew/Funkin/pull/2655)
- Added a timer sequence class to queue up multiple timers in scripts with ease. ([9e182f7](https://github.com/FunkinCrew/Funkin/commit/9e182f70d2bcc92eb68d730d74af143c45f7dcf8)) - by @cyn0x8 in [#2391](https://github.com/FunkinCrew/Funkin/pull/2391)
- Replaced smoothLerp and coolLerp with smoothLerpPrecision to fix a few lerp-related bugs. ([94eae11](https://github.com/FunkinCrew/Funkin/commit/94eae116c7a5e6039683d6391208b169378b5ff1)) - by @cyn0x8 in [#3617](https://github.com/FunkinCrew/Funkin/pull/3617)
- Fixed empty text strings softlocking the dialogue box. ([88d0e8c](https://github.com/FunkinCrew/Funkin/commit/88d0e8c3b0529654fb7eee8aebb099f7fb346f66)) - by @xenkap in [#4671](https://github.com/FunkinCrew/Funkin/pull/4671)
- Adjusted the size of the Beat/Step display in the Chart Editor. ([905181c](https://github.com/FunkinCrew/Funkin/commit/905181c9af29bb11280bda33ef9343069678a762)) - by @NotHyper-474 in [#4994](https://github.com/FunkinCrew/Funkin/pull/4994)
- The Chart Editor will now only fall back to the first available difficulty if the selected difficulty cannot be found. ([1c25713](https://github.com/FunkinCrew/Funkin/commit/1c257134648ebd89acf6c9d07f5a0c088fb915c6)) - by @Lasercar in [#4949](https://github.com/FunkinCrew/Funkin/pull/4949)
- The undo/redo history is now cleared when loading another song in the Chart Editor. ([426a9c0](https://github.com/FunkinCrew/Funkin/commit/426a9c0c108ac65a042295194679d46444ec1ea5)) - by @Lasercar in [#4308](https://github.com/FunkinCrew/Funkin/pull/4308)
- Blacklisted more classes for security reasons. ([cadfa3b](https://github.com/FunkinCrew/Funkin/commit/cadfa3b7ceae2ecabe2d544ddc4c9f453b0dfd56)) - by @NotHyper-474 in [#5185](https://github.com/FunkinCrew/Funkin/pull/5185)
### Fixed
- Fixed a ton of performance issues to help the game run better on mobile devices.
- Exiting the Freeplay Menu no longer freezes the game for a really long time (thanks NotHyper-474!)
- Notestyle graphics are now preloaded before the song starts, fixing the stutter at the beginning of the song.
- Hitting many hold note trails in one song no longer leads to a lag spike.
- The first lightning strike in Week 2 Erect no longer creates a lag spike.
- [DESKTOP] The conductor and music no longer gradually drift out of sync to eventually trigger a resync.
- Pixel notestyle strumlines are now properly positioned when Downscroll is enabled.
- Added the missing graffiti to the wall in the Weekend 1 Blazin' stage.
- Accept keybinds now properly scroll faster through the Credits.
- Typing in most text fields in debug editors no longer triggers keyboard shortcuts.
- The Chart Editor playback speed feature now works properly.
- The Chart Editor metronome and hitsounds now play at exactly the right time.
- The Chart Editor notification box no longer covers playbar info.
- Selecting a Recent File too quickly in the Stage Editor no longer crashes the game.
- Blacklisted a few classes for security.
- Opening the logs or backups folder before it's created no longer crashes the game. ([d3490f8](https://github.com/FunkinCrew/Funkin/commit/d3490f8c9929eefb9879ad65ce43038c193642d6)) - by @NotHyper-474 in [#4940](https://github.com/FunkinCrew/Funkin/pull/4940)
- Fixed a crash when mashing D or I during startup. ([b52c73f](https://github.com/FunkinCrew/Funkin/commit/b52c73f2b0fa32fbc349804621cf947dff2d364e)) - by @CrusherNotDrip in [#5160](https://github.com/FunkinCrew/Funkin/pull/5160)
- Hot-reloading with F5 during gameplay no longer crashes the game. ([d2acb5d](https://github.com/FunkinCrew/Funkin/commit/d2acb5d167afd42299d7200ab8c67972044a09c6)) - by @AbnormalPoof in [#5065](https://github.com/FunkinCrew/Funkin/pull/5065)
- Hot-reloading with F5 in the Input Offsets menu no longer crashes the game. ([58257f6](https://github.com/FunkinCrew/Funkin/commit/58257f6ac187925f3b23d3f1eef2c812ae569a6b)) - by @NotHyper-474 in [#5085](https://github.com/FunkinCrew/Funkin/pull/5085)
- Songs no longer skip forward at the beginning with high offsets. ([1f75a64](https://github.com/FunkinCrew/Funkin/commit/1f75a641e0c80d15f1af10bae7ee71a6ffecf219)) - by @xenkap in [#3732](https://github.com/FunkinCrew/Funkin/pull/3732)
- The song countdown no longer stacks when restarting or continues behind the Pause Menu.
([63eca96](https://github.com/FunkinCrew/Funkin/commit/63eca96c98a87e7155df8b2a1735f269ea83e1b5)) - by @KoloInDaCrib and @NotHyper-474 in [#4875](https://github.com/FunkinCrew/Funkin/pull/4875)
- Fixed incorrect highlighting and squashed text on Freeplay song capsules. ([0c62428](https://github.com/FunkinCrew/Funkin/commit/0c62428fc883c0fa6d09cd403efb287ff3af8c53)) - by @VioletSnowLeopard in [#5036](https://github.com/FunkinCrew/Funkin/pull/5036)
- The Freeplay song preview no longer plays twice after returning from Character
Select. ([3d3e2bd](https://github.com/FunkinCrew/Funkin/commit/3d3e2bd3786b858143d214caf55be2ee3e9483fc)) - by @Lasercar in [#5248](https://github.com/FunkinCrew/Funkin/pull/5248)
- Freeplay song ranks no longer disappear after changing variations. ([7cc9464](https://github.com/FunkinCrew/Funkin/commit/7cc9464573d07996e4bd0d557f82847809d3a786)) - by @VioletSnowLeopard in [#4583](https://github.com/FunkinCrew/Funkin/pull/4583)
- Freeplay song capsules now cycle through long names consistently. ([e193f73](https://github.com/FunkinCrew/Funkin/commit/e193f7392a83a04e4aac85fba4f441a78f7b6668)) - by @VioletSnowLeopard in [#4677](https://github.com/FunkinCrew/Funkin/pull/4677)
- Fixed a few visual issues with Freeplay's rank slam animation. ([ab817bb](https://github.com/FunkinCrew/Funkin/commit/ab817bb1eab74ae71c1b1fd74d7512a11e6d4339)) - by @Lasercar in [#4986](https://github.com/FunkinCrew/Funkin/pull/4986)
- Fixed visual errors in Freeplay after exiting Character Select. ([56a18e1](https://github.com/FunkinCrew/Funkin/commit/56a18e1cf6a15971feebcc828be8818037948cef)) - by @KoloInDaCrib in [#5245](https://github.com/FunkinCrew/Funkin/pull/5245)
- Freeplay styles are now reloaded when hot-reloading with F5. ([f54e140](https://github.com/FunkinCrew/Funkin/commit/f54e140b65e36fcf810c52ce464799dbc0c73c6d)) - by @Keoiki in [#5286](https://github.com/FunkinCrew/Funkin/pull/5286)
- Adjusted offsets for Freeplay DJ Pico's fistPump animation. ([382e286](https://github.com/FunkinCrew/funkin.assets/commit/382e286a2478939d3d6aca1c7c90719f33815014)) - by @AbnormalPoof in [funkin.assets#91](https://github.com/FunkinCrew/funkin.assets/pull/91)
- The "New Highscore" text no longer appears more than once in the Results screen. ([4e31003](https://github.com/FunkinCrew/Funkin/commit/4e31003a0f60cd394edc2bd4586f9f385b3d07bc)) - by @Lasercar in [#4319](https://github.com/FunkinCrew/Funkin/pull/4319)
- The Results Debug menu now shows the correct rank after recent scoring changes. ([11d9998](https://github.com/FunkinCrew/Funkin/commit/11d9998e5c73a3839ea39e06b7f217cbaab69c6d)) - by @NotHyper-474 in [#4905](https://github.com/FunkinCrew/Funkin/pull/4905)
- The Input Offsets menu now exits to the Options menu instead of the Main menu. ([5361df2](https://github.com/FunkinCrew/Funkin/commit/5361df254470e68d7571b4534cf456f08d5ffd60)) - by @JackXson-Real in [#5076](https://github.com/FunkinCrew/Funkin/pull/5076)
- The Debug Menu can no longer be opened after selecting an item in the Main Menu.
([5695bc2](https://github.com/FunkinCrew/Funkin/commit/5695bc20e721f0afd5b97a36167f13e550c12b16)) - by @Lasercar in [#4211](https://github.com/FunkinCrew/Funkin/pull/4211)
- The debug cursor is now always hidden when the game starts. ([6222c38](https://github.com/FunkinCrew/Funkin/commit/6222c389e301fa2bb4939697376c4e51e29a9977)) - by @Hundrec in [#4520](https://github.com/FunkinCrew/Funkin/pull/4520)
- The Story Mode Weekend 1 level title no longer clips into other level titles below it. ([19d1a8c](https://github.com/FunkinCrew/Funkin/commit/19d1a8c59380009f0d0814c94fd0b4eccb0c80cd)) - by @KoloInDaCrib in [#4348](https://github.com/FunkinCrew/Funkin/pull/4348)
- Hold note covers now display properly if a hold note was previously dropped. ([96d1324](https://github.com/FunkinCrew/Funkin/commit/96d1324af140858cb93edd07dc36a969c8ae84c0)) - by @T5mpler in [#5275](https://github.com/FunkinCrew/Funkin/pull/5275)
- Girlfriend's and Nene's combo drop animations now play consistently. ([34d5ed1](https://github.com/FunkinCrew/Funkin/commit/34d5ed11695cef7348c13505f13fb1da38b7988c)) - by @VioletSnowLeopard in [#4968](https://github.com/FunkinCrew/Funkin/pull/4968)
- Girlfriend (Tankman Stickup) now plays her combo drop animation. ([e329601](https://github.com/FunkinCrew/funkin.assets/commit/e329601834f270910ce80ea5539e4487b9895a8a)) - by @qt2k4 in [funkin.assets#149](https://github.com/FunkinCrew/funkin.assets/pull/149)
- Darnell's idle animation now loops consistently. ([df64586](https://github.com/FunkinCrew/funkin.assets/commit/df64586771ea41c544913e049fd0f32bdc655417)) - by @qt2k4 in [funkin.assets#159](https://github.com/FunkinCrew/funkin.assets/pull/159)
- Darnell's kneeCan animation now plays properly in 2hot. ([a2e9931](https://github.com/FunkinCrew/funkin.assets/commit/a2e993167aaa2ff6bff8806940f912e444608645)) - by @biomseed in [funkin.assets#78](https://github.com/FunkinCrew/funkin.assets/pull/78)
- Otis and Pico (Speaker) no longer spaz out when playtesting Stress. ([3f6d75f](https://github.com/FunkinCrew/funkin.assets/commit/3f6d75f3b6f6c8deb660323e3fb9bf1974c06520)) - by @Lasercar in [funkin.assets#124](https://github.com/FunkinCrew/funkin.assets/pull/124)
- The gasp sound now only plays once in the Week 3 Pico Mix doppelganger cutscene. ([ab4598b](https://github.com/FunkinCrew/funkin.assets/commit/ab4598baf3c6790d30cfb727d04c8b57fa18dd0d)) - by @KoloInDaCrib in [funkin.assets#126](https://github.com/FunkinCrew/funkin.assets/pull/126)
- Fixed a few issues with the train in Week 3. ([8db2426](https://github.com/FunkinCrew/funkin.assets/commit/8db2426991caebde44d19c81b198e8e2ad86f700)) - by @ShadzXD in [funkin.assets#180](https://github.com/FunkinCrew/funkin.assets/pull/180)
- The cars in Week 4 and Weekend 1 no longer get stuck when the song is restarted. ([9c511e3](https://github.com/FunkinCrew/funkin.assets/commit/9c511e371fd08a43ddf834766a6d35667bdef4f7)) - by @MetaBreeze in [funkin.assets#186](https://github.com/FunkinCrew/funkin.assets/pull/186)
- A-Bot's visualizer no longer jumps to a random volume when the song ends. ([51cc118](https://github.com/FunkinCrew/funkin.assets/commit/51cc1186bc77a2ee45a47fdbde2d75d9ec69de3a)) - by @VioletSnowLeopard in [funkin.assets#183](https://github.com/FunkinCrew/funkin.assets/pull/183)
- Pico's burpShit animation now re-enables volume for player vocals. ([cefda0e](https://github.com/FunkinCrew/funkin.assets/commit/cefda0e52fabbbe04afe60b7aed560267e2cb01e)) - by @Hundrec in [funkin.assets#71](https://github.com/FunkinCrew/funkin.assets/pull/71)
- Removed vocals from Monster's instrumental on web builds. ([1c9473f](https://github.com/FunkinCrew/funkin.assets/commit/1c9473f3dfdfb97d97f6c8457001055322abf5ab)) - by @JVNpixels in [funkin.assets#182](https://github.com/FunkinCrew/funkin.assets/pull/182)
- Fixed the retry sound not playing after a Tankman death quote finishes. ([e7c4b1b](https://github.com/FunkinCrew/Funkin/commit/e7c4b1ba38ba0739cfe347f6c4763f9811fb95b0)) - by @VioletSnowLeopard in [#4726](https://github.com/FunkinCrew/Funkin/pull/4726)
- Darnell (BF Mix)'s alternate instrumental is now properly accessible. ([5abdabf](https://github.com/FunkinCrew/funkin.assets/commit/5abdabf69b39ca4eebd36ae6bbd77fab736d0b86)) - by @Hundrec in [funkin.assets#168](https://github.com/FunkinCrew/funkin.assets/pull/168)
- Fixed Newgrounds score submissions for Lit Up and Lit Up (BF Mix). ([183cec6](https://github.com/FunkinCrew/Funkin/commit/183cec62dc1fd3c7f3f634dd3e2400e6ee77b476)) - by @Raltyro in [#4577](https://github.com/FunkinCrew/Funkin/pull/4577)
- Inputs are now disabled before Senpai's dialogue appears. ([c43d906](https://github.com/FunkinCrew/funkin.assets/commit/c43d906d19d91d71b9096e65d5e0d3543af8cd31)) - by @anysad in [funkin.assets#165](https://github.com/FunkinCrew/funkin.assets/pull/165)
- An easter egg now restarts the song using the correct instrumental. ([e657bc9](https://github.com/FunkinCrew/Funkin/commit/e657bc900bc62cc220276dc171dd47f0a176ac66)) - by @KoloInDaCrib in [#4956](https://github.com/FunkinCrew/Funkin/pull/4956)
- Encountering an easter egg during a Chart Editor playtest no longer crashes the game. ([b53b5bd](https://github.com/FunkinCrew/funkin.assets/commit/b53b5bdaecf975555538725a4cdfe71d38565b08)) - by @NotHyper-474 in [funkin.assets#133](https://github.com/FunkinCrew/funkin.assets/pull/133)
- Nonexistent characters no longer crash the Chart Editor. ([3bbb4b0](https://github.com/FunkinCrew/Funkin/commit/3bbb4b06c8c1a1885a18d4354fcfa4363a0c6c75)) - by @Lasercar in [#5008](https://github.com/FunkinCrew/Funkin/pull/5008)
- Holding Ctrl and clicking on a hold note trail no longer crashes the Chart Editor. ([dc56cca](https://github.com/FunkinCrew/Funkin/commit/dc56ccada50e671996caf0557de1977b7ff8d236)) - by @Lasercar in [#4203](https://github.com/FunkinCrew/Funkin/pull/4203)
- Tweens and timers are now canceled when returning to the Chart Editor. ([7e76cf6](https://github.com/FunkinCrew/Funkin/commit/7e76cf66340c00ef6ec84358e6304d62815173b6)) - by @KoloInDaCrib in [#5278](https://github.com/FunkinCrew/Funkin/pull/5278)
- Reduced the severity of a memory leak in the Chart Editor. ([cce8c18](https://github.com/FunkinCrew/Funkin/commit/cce8c18822e083910200597f5db4d87b6e3b521f)) - by @NotHyper-474 in [#5247](https://github.com/FunkinCrew/Funkin/pull/5247)
- Pressing the Chart Editor keybind during a song now opens to the variation and difficulty you were playing. ([e3fca16](https://github.com/FunkinCrew/Funkin/commit/e3fca167938642bea85398fe57347c10439c1892)) - by @Lasercar in [#4116](https://github.com/FunkinCrew/Funkin/pull/4116)
- The Chart Editor now properly saves audio levels when exiting. ([f78ab4d](https://github.com/FunkinCrew/Funkin/commit/f78ab4da1db4f9527a2e1715d5cfb37670e11a74)) - by @Lasercar in [#4149](https://github.com/FunkinCrew/Funkin/pull/4149)
- The Chart Editor "Load Metadata File" and "Load Chart File" buttons now function properly. ([9df5395](https://github.com/FunkinCrew/Funkin/commit/9df5395ff888cb6740e21204c8e19116e0472db4)) - by @Lasercar in [#4278](https://github.com/FunkinCrew/Funkin/pull/4278)
- FNF Legacy files can now be opened in the Chart Editor on MacOS. ([d98628c](https://github.com/FunkinCrew/Funkin/commit/d98628ca0f9f60357715bd7f95fc686a83201209)) - by @AbnormalPoof in [#4580](https://github.com/FunkinCrew/Funkin/pull/4580)
- The Chart Editor now consistently displays the correct waveform for vocal tracks. ([c0e0523](https://github.com/FunkinCrew/Funkin/commit/c0e0523651e8aaaae2a0eed6d5fef6c5ef1b7315)) - by @NotHyper-474 in [#5231](https://github.com/FunkinCrew/Funkin/pull/5231)
- Fixed selection boxes duplicating in the Chart Editor. ([65ed583](https://github.com/FunkinCrew/Funkin/commit/65ed58350b798bca0044603510540cfe81b48611)) - by @NotHyper-474 in [#5073](https://github.com/FunkinCrew/Funkin/pull/5073)
- Hot-reloading with F5 in the Input Offsets menu no longer crashes the game. ([22372e8](https://github.com/FunkinCrew/Funkin/commit/22372e83886c5ef9821228daa3fb9fe8fbcf0e07)) - by @NotHyper-474 in [#5085](https://github.com/FunkinCrew/Funkin/pull/5085)
- The Chart Editor playbar no longer extends past the right of the grid. ([7edc99b](https://github.com/FunkinCrew/Funkin/commit/7edc99b8ba7d1c61eba737560512a07babf5dfe8)) - by @anysad in [#5090](https://github.com/FunkinCrew/Funkin/pull/5090)
- Removed a spammy console trace from some Chart Editor events. ([000491c](https://github.com/FunkinCrew/Funkin/commit/000491c3bd3db9de433a4b668b6ceebda2fc6eae)) - by @anysad in [#5097](https://github.com/FunkinCrew/Funkin/pull/5097)
- Fixed a crash when mashing D or I during startup. ([790af6d](https://github.com/FunkinCrew/Funkin/commit/790af6d9054a77248ecb7bf34d764186c4fa2243)) - by @CrusherNotDrip in [#5160](https://github.com/FunkinCrew/Funkin/pull/5160)
- The Chart Editor now consistently displays the correct waveform for vocal tracks. ([788d0fe](https://github.com/FunkinCrew/Funkin/commit/788d0fe9be5754e4e193de56e4437e8f409564a2)) - by @NotHyper-474 in [#5231](https://github.com/FunkinCrew/Funkin/pull/5231)
- Stage Editor windows are now able to be closed. ([990042e](https://github.com/FunkinCrew/Funkin/commit/990042e28f73ad0624dfd38179891163d0c2313a)) - by @Lasercar in [#5238](https://github.com/FunkinCrew/Funkin/pull/5238)
- Fixed duplicate exit prompts appearing in the Stage Editor. ([a599d83](https://github.com/FunkinCrew/Funkin/commit/a599d830f08ba697f2b526747280a53e16ebae06)) - by @Lasercar in [#5239](https://github.com/FunkinCrew/Funkin/pull/5239)
- Reduced the severity of a memory leak in the Chart Editor. ([6ec3191](https://github.com/FunkinCrew/Funkin/commit/6ec3191b2952533afeded514d582a56f0905afb8)) - by @NotHyper-474 in [#5247](https://github.com/FunkinCrew/Funkin/pull/5247)
- The Freeplay song preview no longer plays twice after returning from Character Select. ([f9387c1](https://github.com/FunkinCrew/Funkin/commit/f9387c144a62ecae2ea2b9da7b93ee16693bff02)) - by @Lasercar in [#5248](https://github.com/FunkinCrew/Funkin/pull/5248)
- Undoing and redoing hold note length changes now visually updates the trail in the Chart Editor. ([254be57](https://github.com/FunkinCrew/Funkin/commit/254be5798ec0b7b2e79612ae5ade438965e65249)) - by @NotHyper-474 in [#5265](https://github.com/FunkinCrew/Funkin/pull/5265)
- Pico's burpShit animation now re-enables volume for player vocals. ([2bf3846](https://github.com/FunkinCrew/funkin.assets/commit/2bf38463c524839e85faec19b7f31e90070e0b87)) - by @Hundrec in [funkin.assets#71](https://github.com/FunkinCrew/funkin.assets/pull/71)
- Darnell's kneeCan animation now plays properly in 2hot. ([f8030c4](https://github.com/FunkinCrew/funkin.assets/commit/f8030c441c73d203f7b92c243542f99402faf2a7)) - by @biomseed in [funkin.assets#78](https://github.com/FunkinCrew/funkin.assets/pull/78)
- Adjusted offsets for Freeplay DJ Pico's fistPump animation. ([17996db](https://github.com/FunkinCrew/funkin.assets/commit/17996db752c3077e005d4f05286a7795746acb05)) - by @AbnormalPoof in [funkin.assets#91](https://github.com/FunkinCrew/funkin.assets/pull/91)
- Chart Editor and Stage Editor windows now consistently show a close button. ([929f61f](https://github.com/FunkinCrew/funkin.assets/commit/929f61fe86bd49d61de27e09503085a04c2cddfb)) - by @Lasercar in [funkin.assets#121](https://github.com/FunkinCrew/funkin.assets/pull/121)
- Otis and Pico (Speaker) no longer spaz out when playtesting Stress. ([2fd62d8](https://github.com/FunkinCrew/funkin.assets/commit/2fd62d8f4fdefffc8d4a9cc96b9e993fd0130eb4)) - by @Lasercar in [funkin.assets#124](https://github.com/FunkinCrew/funkin.assets/pull/124)
- The gasp sound now only plays once in the Week 3 Pico Mix doppelganger cutscene. ([ecda30d](https://github.com/FunkinCrew/funkin.assets/commit/ecda30d7532df3c34a4b5ad25579cc960cb30c0f)) - by @KoloInDaCrib in [funkin.assets#126](https://github.com/FunkinCrew/funkin.assets/pull/126)
- Encountering an easter egg during a Chart Editor playtest no longer crashes the game. ([6cf9fb7](https://github.com/FunkinCrew/funkin.assets/commit/6cf9fb7ba48bb336f63eb940290fa051859330ec)) - by @NotHyper-474 in [funkin.assets#133](https://github.com/FunkinCrew/funkin.assets/pull/133)
- Girlfriend (Tankman Stickup) now plays her combo drop animation. ([cb0e506](https://github.com/FunkinCrew/funkin.assets/commit/cb0e506599d387b1d17faee2cf319914eb2dfc80)) - by @qt2k4 in [funkin.assets#149](https://github.com/FunkinCrew/funkin.assets/pull/149)
- Changed "Tankman Battlefield (Erect)" to "Tankman Battlefield [Erect]" in the Chart Editor for consistency. ([f9149eb](https://github.com/FunkinCrew/funkin.assets/commit/f9149eb72698ddd275f743080effed08c842644e)) - by @JVN-Pixels in [funkin.assets#155](https://github.com/FunkinCrew/funkin.assets/pull/155)
- Fixed Pico (Pixel) having the incorrect name in the Chart Editor. ([a668659](https://github.com/FunkinCrew/funkin.assets/commit/a668659a4bbd2094fde48041c73fd1ebef551288)) - by @ExtraCode75 in [funkin.assets#158](https://github.com/FunkinCrew/funkin.assets/pull/158)
- Darnell (BF Mix)'s alternate instrumental is now properly accessible. ([7bf4361](https://github.com/FunkinCrew/funkin.assets/commit/7bf436153081c27fdb354eddb05b72bd87fb18b7)) - by @Hundrec in [funkin.assets#168](https://github.com/FunkinCrew/funkin.assets/pull/168)
- Adjusted the font size for Starting BPM in the Chart Editor for consistency. ([801121a](https://github.com/FunkinCrew/funkin.assets/commit/801121a7d31ef3a1ac108bf16941d88009d1b12a)) - by @Lasercar in [funkin.assets#170](https://github.com/FunkinCrew/funkin.assets/pull/170)
- Removed vocals from Monster's instrumental on web builds. ([bf7b032](https://github.com/FunkinCrew/funkin.assets/commit/bf7b032154fdc9751fbe8fdf88dbc5fd24f361db)) - by @JVN-Pixels in [funkin.assets#182](https://github.com/FunkinCrew/funkin.assets/pull/182)
- A-Bot's visualizer no longer jumps to a random volume when the song ends. ([2904697](https://github.com/FunkinCrew/funkin.assets/commit/2904697b6fda66db4df2b47127f05520fdf831d1)) - by @VioletSnowLeopard in [funkin.assets#183](https://github.com/FunkinCrew/funkin.assets/pull/183)
- The cars in Week 4 and Weekend 1 no longer get stuck when the song is restarted. ([ecb1bbd](https://github.com/FunkinCrew/funkin.assets/commit/ecb1bbd8c8ced028b96b2c9b5572d2ea48f36cf4)) - by @MetaBreeze in [funkin.assets#186](https://github.com/FunkinCrew/funkin.assets/pull/186)
- Removed a spammy console trace from Spooky Kids (Dark). ([9ee98d6](https://github.com/FunkinCrew/funkin.assets/commit/9ee98d62aa336f1fccbefaf077b4ae3222e00ec4)) - by @NotHyper-474 in [funkin.assets#187](https://github.com/FunkinCrew/funkin.assets/pull/187)
- Fixed the Chart Editor timer occasionally displaying incorrect millisecond values. ([26dc895](https://github.com/FunkinCrew/Funkin/commit/26dc895a27e0d7e49469251cd68b83be66384e15)) - by @Hundrec in [#4257](https://github.com/FunkinCrew/Funkin/pull/4257)
- The Chart Editor playhead can no longer be scrolled to before the beginning of the song. ([7c7dc11](https://github.com/FunkinCrew/Funkin/commit/7c7dc11f18644882444df97ac927e11adaa4ce50)) - by @Hundrec in [#5024](https://github.com/FunkinCrew/Funkin/pull/5024)
- The Chart Editor playbar no longer extends past the right of the grid. ([c7abb19](https://github.com/FunkinCrew/Funkin/commit/c7abb196989476cfa4db6354cc6fac5bacb2e56a)) - by @anysad in [#5090](https://github.com/FunkinCrew/Funkin/pull/5090)
- Dragging a hold note in the Chart Editor now drags its trail along with its head. ([d3d8aaa](https://github.com/FunkinCrew/Funkin/commit/d3d8aaae7bfbfa8975a9573807b9a3ca68a1ff55)) - by @KoloInDaCrib in [#4127](https://github.com/FunkinCrew/Funkin/pull/4127)
- Hold note trails will no longer disappear when dragged too far in the Chart Editor. ([37dc66b](https://github.com/FunkinCrew/Funkin/commit/37dc66bc189bc1941cf60587e1c068770aeec872)) - by @NotHyper-474 in
[#5261](https://github.com/FunkinCrew/Funkin/pull/5261)
- Undoing and redoing hold note length changes now visually updates the trail in the Chart Editor. ([06a440f](https://github.com/FunkinCrew/Funkin/commit/06a440f21c285666ce2d2bdf17a91ee82ab01061)) - by @NotHyper-474 in [#5265](https://github.com/FunkinCrew/Funkin/pull/5265)
- The Chart Editor hold note context menu now displays the correct options. ([4801316](https://github.com/FunkinCrew/Funkin/commit/48013168ef09ddc09549268a2e5309520d3fcc18)) - by @Lasercar in [#4231](https://github.com/FunkinCrew/Funkin/pull/4231)
- The buttons in the Chart Editor context menu for selections now do the right thing. ([62d24fc](https://github.com/FunkinCrew/Funkin/commit/62d24fcf4cbe7be328a995bad04f3eeb265262c5)) - by @Lasercar in [#4233](https://github.com/FunkinCrew/Funkin/pull/4233)
- The charter field in the song metadata now properly displays the charter in the Chart Editor. ([894d8cb](https://github.com/FunkinCrew/Funkin/commit/894d8cb4637fd0a64359a5edc805a23403f3045c)) - by @Lasercar in [#4879](https://github.com/FunkinCrew/Funkin/pull/4879)
- Chart Editor difficulties are now sorted in a consistent order. ([7aa77a1](https://github.com/FunkinCrew/Funkin/commit/7aa77a11cf7508fc05758045a13b2220aca96dd5)) - by @Lasercar in [#4528](https://github.com/FunkinCrew/Funkin/pull/4528)
- Chart Editor and Stage Editor windows now consistently show a close button. ([b23b7b8](https://github.com/FunkinCrew/funkin.assets/commit/b23b7b81d843c8fa2334fbdbe1ef979633956bb9)) - by @Lasercar in [funkin.assets#121](https://github.com/FunkinCrew/funkin.assets/pull/121)
- The Chart Editor playbar's font size no longer becomes too small. ([f9c1f7a](https://github.com/FunkinCrew/Funkin/commit/f9c1f7a5f7d3906155c2004ad2ec1d02556b9730)) - by @KoloInDaCrib in [#5253](https://github.com/FunkinCrew/Funkin/pull/5253)
- The Chart Editor copy notification no longer chases the mouse cursor. ([0ea42e1](https://github.com/FunkinCrew/Funkin/commit/0ea42e18e93b0b57f1ae5499d70c4681f191dacb)) - by @KoloInDaCrib in [#4029](https://github.com/FunkinCrew/Funkin/pull/4029)
- Changed "Tankman Battlefield (Erect)" to "Tankman Battlefield [Erect]" in the Chart Editor for consistency. ([52852a0](https://github.com/FunkinCrew/funkin.assets/commit/52852a02f60df81c281bfd0e49fd8fa09e118409)) - by @JVNpixels in [funkin.assets#155](https://github.com/FunkinCrew/funkin.assets/pull/155)
- Fixed Pico (Pixel) having the incorrect name in the Chart Editor. ([3ff0e9c](https://github.com/FunkinCrew/funkin.assets/commit/3ff0e9ca4f8cdc5734c335f2d7d72fa310686d46)) - by @ExtraCode75 in [funkin.assets#158](https://github.com/FunkinCrew/funkin.assets/pull/158)
- Fixed various issues and added missing functionalities to the Stage Editor. ([a776ce1](https://github.com/FunkinCrew/Funkin/commit/a776ce1a81a539f75f8bd2220a9768c03d347058)) - by @KoloInDaCrib in [#3974](https://github.com/FunkinCrew/Funkin/pull/3974)
- Stage Editor windows are now able to be closed. ([65461d8](https://github.com/FunkinCrew/Funkin/commit/65461d839b3764659aab33f1a35edf64ee514952)) - by @Lasercar in [#5238](https://github.com/FunkinCrew/Funkin/pull/5238)
- Fixed duplicate exit prompts appearing in the Stage Editor. ([136a5df](https://github.com/FunkinCrew/Funkin/commit/136a5dfad430b461a909be3a7e89f46c8be1d3b3)) - by @Lasercar in [#5239](https://github.com/FunkinCrew/Funkin/pull/5239)
- The help guide in the Stage Editor can no longer be opened multiple times. ([564d679](https://github.com/FunkinCrew/Funkin/commit/564d679f969c500834426ce3cf50507091d69697)) - by @Lasercar in [#4128](https://github.com/FunkinCrew/Funkin/pull/4128)
- Removed a spammy console trace from Spooky Kids (Dark). ([5935a61](https://github.com/FunkinCrew/funkin.assets/commit/5935a61dd71f51e6264dcd039f1164cd1ff295ef)) - by @NotHyper-474 in [funkin.assets#187](https://github.com/FunkinCrew/funkin.assets/pull/187)
- Removed spammy console traces from `DiscordClient`. ([e89f9f5](https://github.com/FunkinCrew/Funkin/commit/e89f9f50dc6085e3550736459ce9e4fd02c1fc5b)) - by @AbnormalPoof in [#4207](https://github.com/FunkinCrew/Funkin/pull/4207)
- Removed a spammy console trace from some Chart Editor events. ([b883ad3](https://github.com/FunkinCrew/Funkin/commit/b883ad3d50b990e605d7b79d042292c7371db5f0)) - by @anysad in [#5097](https://github.com/FunkinCrew/Funkin/pull/5097)
- ANSI colors now display in the console on more computers. ([3747b94](https://github.com/FunkinCrew/Funkin/commit/3747b942461c3644fe31f25d2ce847fd74d1b1e0)) - by @AbnormalPoof in [#4676](https://github.com/FunkinCrew/Funkin/pull/4676)
- Properly blacklist a certain class from scripts. ([3dc7699](https://github.com/FunkinCrew/Funkin/commit/3dc7699aac737998b637ff9a9f16986a434424be)) - by @charlesisfeline in [#4773](https://github.com/FunkinCrew/Funkin/pull/4773)
### Removed
- [MOBILE] The Input Offsets menu has been temporarily removed pending future reworks.
- Removed a few non-functional screenshot preferences. ([8e40942](https://github.com/FunkinCrew/Funkin/commit/8e4094227f89ccc8ec7e58be49a5e1ad6911ed12)) - by @Lasercar in [#4895](https://github.com/FunkinCrew/Funkin/pull/4895)
- Removed the VSync preference from web builds, where it's non-functional. ([1338834](https://github.com/FunkinCrew/Funkin/commit/1338834519e13326fa21585a236218cd984ed614)) - by @NotHyper-474 in [#5062](https://github.com/FunkinCrew/Funkin/pull/5062)
- Removed the VSync preference from web builds, where it's non-functional. ([0b7a94b](https://github.com/FunkinCrew/Funkin/commit/0b7a94b1cc5c5d0dbac5a4c3d595349c4e6eb6e4)) - by @NotHyper-474 in [#5062](https://github.com/FunkinCrew/Funkin/pull/5062)
- Removed a few non-functional screenshot preferences. ([93e4f79](https://github.com/FunkinCrew/Funkin/commit/93e4f799f4f0ea435b3d77ca8ef2ab6beeb0a955)) - by @Lasercar in [#4895](https://github.com/FunkinCrew/Funkin/pull/4895)
## New Contributors for 0.7.0
* @KutikiPlayz made their first contribution in [#3544](https://github.com/FunkinCrew/Funkin/pull/3544)
* @xenkap made their first contribution in [#3732](https://github.com/FunkinCrew/Funkin/pull/3732)
* @Raltyro made their first contribution in [#4577](https://github.com/FunkinCrew/Funkin/pull/4577)
* @charlesisfeline made their first contribution in [#4773](https://github.com/FunkinCrew/Funkin/pull/4773)
* @T5mpler made their first contribution in [#5275](https://github.com/FunkinCrew/Funkin/pull/5275)
* @biomseed made their first contribution in [funkin.assets#78](https://github.com/FunkinCrew/funkin.assets/pull/78)
* @qt2k4 made their first contribution in [funkin.assets#149](https://github.com/FunkinCrew/funkin.assets/pull/149)
* @ExtraCode75 made their first contribution in [funkin.assets#158](https://github.com/FunkinCrew/funkin.assets/pull/158)
+1 -1
Submodule assets updated: 414e9a0248...2546f94b8d
+6 -6
View File
@@ -18,7 +18,7 @@
"name": "extension-admob",
"type": "git",
"dir": null,
"ref": "b0fe062c5eacd7cde80fd21544fe27616e5b4f9e",
"ref": "a53d5916bdcb2e48913f94d9ae1d949b049dcdc1",
"url": "https://github.com/FunkinCrew/extension-admob"
},
{
@@ -45,7 +45,7 @@
"name": "flixel",
"type": "git",
"dir": null,
"ref": "16962f601431d72ed104ca700d1e99ad4dcfffca",
"ref": "08fc955ca87f192a971719a675f1d3b21709725d",
"url": "https://github.com/FunkinCrew/flixel-mobile"
},
{
@@ -104,7 +104,7 @@
"name": "hscript",
"type": "git",
"dir": null,
"ref": "27c86f9a761c1d16d4433c4cf252eccb7b2e18de",
"ref": "d60bb2947fa609fdc875ccfae89666a6984eeaf2",
"url": "https://github.com/FunkinCrew/hscript"
},
{
@@ -170,7 +170,7 @@
"name": "lime",
"type": "git",
"dir": null,
"ref": "2d3784fe3fdbd81ce59d6ad035f52e12c59cf059",
"ref": "c750ebf6b48c4bc018abe9855fbae5ffdbc4771a",
"url": "https://github.com/FunkinCrew/lime-mobile"
},
{
@@ -212,14 +212,14 @@
"name": "openfl",
"type": "git",
"dir": null,
"ref": "db060809f0ac293fb873964d7a076762c85adb71",
"ref": "a0df7c3afe360c9af59a76e45007dbf4e53b5131",
"url": "https://github.com/FunkinCrew/openfl-mobile"
},
{
"name": "polymod",
"type": "git",
"dir": null,
"ref": "6a3f3793c29ec2922630854ba5186f6f1d73d25a",
"ref": "3e030c81de99ca84acde681431f806d8103bcf6e",
"url": "https://github.com/larsiusprime/polymod"
},
{
+76 -28
View File
@@ -29,7 +29,7 @@ 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.0";
static final VERSION:String = "0.7.2";
/**
* The build's number and version code.
@@ -101,6 +101,8 @@ class Project extends HXProject
*/
static final ANDROID_TARGET_SDK_VERSION:Int = 35;
static final ANDROID_EXTENSIONS:Array<String> = ["funkin.extensions.CallbackUtil"];
//
// ASTC COMPRESSION
//
@@ -488,6 +490,7 @@ class Project extends HXProject
configureApp();
displayTarget();
configureFeatureFlags();
configureCompileDefines();
configureIncludeMacros();
@@ -510,23 +513,14 @@ class Project extends HXProject
configureAdMobKeys();
}
// To sign in ios, use -xcode.
if (isAndroid())
{
if (isRelease())
{
configureAndroidRelease();
}
configureAndroid();
}
if (isIOS())
{
config.set("ios.non-exempt-encryption", "false");
config.set("ios.team-id", IOS_TEAM_ID);
config.set("ios.deployment", "16.0");
// launchStoryboard = new LaunchStoryboard();
// launchStoryboard.path = "./LaunchScreen.storyboard";
configureIOS();
}
info("Done configuring project.");
@@ -555,6 +549,11 @@ class Project extends HXProject
function configureApp()
{
this.meta.title = TITLE;
// ios uses this character code instead of spaces...
if (isIOS())
this.meta.title = ~/ /ig.replace(TITLE, "\u2002");
this.meta.version = VERSION;
this.meta.packageName = PACKAGE_NAME;
this.meta.company = COMPANY;
@@ -889,6 +888,11 @@ class Project extends HXProject
setHaxedef("FLX_NO_DEBUG");
}
if (FEATURE_LOG_TRACE.isDisabled(this))
{
addHaxeFlag("--no-traces");
}
// Disable the built in pause screen when unfocusing the game.
setHaxedef("FLX_NO_FOCUS_LOST_SCREEN");
@@ -977,40 +981,84 @@ class Project extends HXProject
app.path = buildDir;
}
function configureAndroidRelease()
function configureAndroid()
{
if (envConfig != null)
javaPaths.push(Path.join([SOURCE_DIR, 'funkin/mobile/external/android/java']));
if (isRelease())
{
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'));
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'));
}
Reflect.setField(config.get('android.manifest'), 'xmlns:tools', 'http://schemas.android.com/tools');
final permissionsConfig:Array<String> = [
'<uses-permission android:name="android.permission.READ_PHONE_STATE" tools:node="remove" />',
'<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:node="remove" />'
];
final xmlChildren:Null<Array<String>> = config.get('android.manifest').xmlChildren;
if (xmlChildren != null)
{
config.get('android.manifest').xmlChildren = xmlChildren.concat(permissionsConfig);
}
else
{
config.get('android.manifest').xmlChildren = permissionsConfig;
}
if (isBundle())
{
config.set("android.gradle-project-directory", "bin-bundle");
}
}
Reflect.setField(config.get('android.manifest'), 'xmlns:tools', 'http://schemas.android.com/tools');
final permissionsConfig:Array<String> = [
'<uses-permission android:name="android.permission.READ_PHONE_STATE" tools:node="remove" />',
'<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:node="remove" />'
final modsFolderProvider = [
'<provider android:authorities="$PACKAGE_NAME.docprovider" android:name="funkin.provider.DataFolderProvider" android:grantUriPermissions="true" android:exported="true" android:permission="android.permission.MANAGE_DOCUMENTS">',
' <intent-filter>',
' <action android:name="android.content.action.DOCUMENTS_PROVIDER" />',
' </intent-filter>',
'</provider>'
];
final xmlChildren:Null<Array<String>> = config.get('android.manifest').xmlChildren;
final xmlChildren:Null<Array<String>> = config.get('android.application').xmlChildren;
if (xmlChildren != null)
{
config.get('android.manifest').xmlChildren = xmlChildren.concat(permissionsConfig);
config.get('android.application').xmlChildren = xmlChildren.concat(modsFolderProvider);
}
else
{
config.get('android.manifest').xmlChildren = permissionsConfig;
config.get('android.application').xmlChildren = modsFolderProvider;
}
if (isBundle())
final extensions:Null<Array<String>> = config.getArrayString('android.extension');
for (extension in ANDROID_EXTENSIONS)
{
config.set("android.gradle-project-directory", "bin-bundle");
if (extensions == null || extensions != null && extensions.indexOf(extension) == -1)
{
config.push("android.extension", extension);
}
}
}
function configureIOS()
{
config.set("ios.non-exempt-encryption", "false");
config.set("ios.team-id", IOS_TEAM_ID);
config.set("ios.deployment", "16.0");
// launchStoryboard = new LaunchStoryboard();
// launchStoryboard.path = "./LaunchScreen.storyboard";
}
function configurePolymod()
{
// The file extension to use for script files.
+4 -5
View File
@@ -38,12 +38,11 @@ class Main extends Sprite
{
// Set the current working directory for Android and iOS devices
#if android
// For Android we determine the appropriate directory based on Android version
Sys.setCwd(haxe.io.Path.addTrailingSlash(extension.androidtools.os.Build.VERSION.SDK_INT > 30 ? extension.androidtools.content.Context.getObbDir() : // Use Obb directory for Android SDK version > 30
extension.androidtools.content.Context.getExternalFilesDir() // Use External Files directory for Android SDK version < 30
));
// On Android use External Files Dir.
Sys.setCwd(haxe.io.Path.addTrailingSlash(extension.androidtools.content.Context.getExternalFilesDir()));
#elseif ios
Sys.setCwd(haxe.io.Path.addTrailingSlash(lime.system.System.documentsDirectory)); // For iOS we use documents directory and this is only way we can do.
// On iOS use Documents Dir.
Sys.setCwd(haxe.io.Path.addTrailingSlash(lime.system.System.documentsDirectory));
#end
// We need to make the crash handler LITERALLY FIRST so nothing EVER gets past it.
+2 -1
View File
@@ -114,7 +114,7 @@ class InitState extends FlxState
#if ios
// Setup Audio session
funkin.mobile.external.AudioSession.initialize();
funkin.mobile.external.ios.AudioSession.initialize();
#end
// This ain't a pixel art game! (most of the time)
@@ -202,6 +202,7 @@ class InitState extends FlxState
//
#if android
FlxG.android.preventDefaultKeys = [flixel.input.android.FlxAndroidKey.BACK];
funkin.mobile.external.android.CallbackUtil.init();
#end
//
+46
View File
@@ -2,6 +2,7 @@ package funkin;
#if mobile
import funkin.mobile.ui.FunkinHitbox;
import funkin.mobile.util.InAppPurchasesUtil;
#end
import funkin.save.Save;
import funkin.util.WindowUtil;
@@ -202,6 +203,25 @@ class Preferences
return value;
}
/**
* Multiplier of intensity for all the haptic feedback effects.
* @default `2.5`
*/
public static var hapticsIntensityMultiplier(get, set):Float;
static function get_hapticsIntensityMultiplier():Float
{
return Save?.instance?.options?.hapticsIntensityMultiplier ?? 1;
}
static function set_hapticsIntensityMultiplier(value:Float):Float
{
var save:Save = Save.instance;
save.options.hapticsIntensityMultiplier = value;
save.flush();
return value;
}
/**
* If enabled, the game will automatically pause when tabbing out.
* Always enabled on mobile.
@@ -527,5 +547,31 @@ class Preferences
save.flush();
return value;
}
#if FEATURE_MOBILE_IAP
/**
* If bought, the game will not show any ads.
* @default `false`
*/
@:unreflective
public static var noAds(get, set):Bool;
@:unreflective
static function get_noAds():Bool
{
if (InAppPurchasesUtil.hasInitialized) noAds = InAppPurchasesUtil.isPurchased(InAppPurchasesUtil.UPGRADE_PRODUCT_ID);
var returnedValue = Save?.instance?.mobileOptions?.noAds ?? false;
return returnedValue;
}
@:unreflective
static function set_noAds(value:Bool):Bool
{
var save:Save = Save.instance;
save.mobileOptions.noAds = value;
save.flush();
return value;
}
#end
#end
}
+3 -2
View File
@@ -243,9 +243,10 @@ class WaveformDataChannel
}
/**
* Retrieve a given minimum point at an index.
* @param i Index
* @return minimum point at an index.
*/
public function minSample(i:Int)
public function minSample(i:Int):Int
{
var offset = (i * parent.channels + this.channelId) * 2;
return inline parent.get(offset);
@@ -43,69 +43,58 @@ class WaveformDataParser
public static function interpretAudioBuffer(soundBuffer:lime.media.AudioBuffer):Null<WaveformData>
{
var sampleRate = soundBuffer.sampleRate;
var channels = soundBuffer.channels;
var bitsPerSample = soundBuffer.bitsPerSample;
var samplesPerPoint:Int = 256; // I don't think we need to configure this.
var pointsPerSecond:Float = sampleRate / samplesPerPoint; // 172 samples per second for most songs is plenty precise while still being performant..
// TODO: Make this work better on HTML5.
var soundData:lime.utils.Int16Array = cast soundBuffer.data;
var soundDataRawLength:Int = soundData.length;
var soundDataSampleCount:Int = Std.int(Math.ceil(soundDataRawLength / channels / (bitsPerSample == 16 ? 2 : 1)));
var soundDataSampleCount:Int = Std.int(Math.ceil(soundData.length / channels / (bitsPerSample == 16 ? 2 : 1)));
var outputPointCount:Int = Std.int(Math.ceil(soundDataSampleCount / samplesPerPoint));
// trace('Interpreting audio buffer:');
// trace(' sampleRate: ${sampleRate}');
// trace(' channels: ${channels}');
// trace(' bitsPerSample: ${bitsPerSample}');
// trace(' samplesPerPoint: ${samplesPerPoint}');
// trace(' pointsPerSecond: ${pointsPerSecond}');
// trace(' soundDataRawLength: ${soundDataRawLength}');
// trace(' soundDataSampleCount: ${soundDataSampleCount}');
// trace(' soundDataRawLength/4: ${soundDataRawLength / 4}');
// trace(' outputPointCount: ${outputPointCount}');
// Pre-allocate Vector with exact final size for better performance and memory efficiency
var outputDataLength:Int = outputPointCount * channels * 2;
var outputData = new haxe.ds.Vector<Int>(outputDataLength);
var minSampleValue:Int = bitsPerSample == 16 ? INT16_MIN : INT8_MIN;
var maxSampleValue:Int = bitsPerSample == 16 ? INT16_MAX : INT8_MAX;
var outputData:Array<Int> = [];
// Reusable min/max tracking arrays to avoid allocation in the loop
var minValues = new haxe.ds.Vector<Int>(channels);
var maxValues = new haxe.ds.Vector<Int>(channels);
for (pointIndex in 0...outputPointCount)
{
// minChannel1, maxChannel1, minChannel2, maxChannel2, ...
var values:Array<Int> = [];
var rangeStart:Int = pointIndex * samplesPerPoint;
var rangeEnd:Int = Std.int(Math.min(rangeStart + samplesPerPoint, soundDataSampleCount));
// Reset min/max values for this range
for (i in 0...channels)
{
values.push(bitsPerSample == 16 ? INT16_MAX : INT8_MAX);
values.push(bitsPerSample == 16 ? INT16_MIN : INT8_MIN);
minValues[i] = bitsPerSample == 16 ? INT16_MAX : INT8_MAX;
maxValues[i] = bitsPerSample == 16 ? INT16_MIN : INT8_MIN;
}
var rangeStart = pointIndex * samplesPerPoint;
var rangeEnd = rangeStart + samplesPerPoint;
if (rangeEnd > soundDataSampleCount) rangeEnd = soundDataSampleCount;
// Process all samples in this range
for (sampleIndex in rangeStart...rangeEnd)
{
for (channelIndex in 0...channels)
{
var sampleIndex:Int = sampleIndex * channels + channelIndex;
var sampleValue = soundData[sampleIndex];
var sampleValue:Int = soundData[sampleIndex * channels + channelIndex];
if (sampleValue < values[channelIndex * 2]) values[(channelIndex * 2)] = sampleValue;
if (sampleValue > values[channelIndex * 2 + 1]) values[(channelIndex * 2) + 1] = sampleValue;
if (sampleValue < minValues[channelIndex]) minValues[channelIndex] = sampleValue;
if (sampleValue > maxValues[channelIndex]) maxValues[channelIndex] = sampleValue;
}
}
// We now have the min and max values for the range.
for (value in values)
outputData.push(value);
// Write directly to final positions in output Vector
var baseIndex:Int = pointIndex * channels * 2;
for (channelIndex in 0...channels)
{
outputData[baseIndex + channelIndex * 2] = minValues[channelIndex];
outputData[baseIndex + channelIndex * 2 + 1] = maxValues[channelIndex];
}
}
var outputDataLength:Int = Std.int(outputData.length / channels / 2);
var result = new WaveformData(null, channels, sampleRate, samplesPerPoint, bitsPerSample, outputPointCount, outputData);
var result = new WaveformData(null, channels, soundBuffer.sampleRate, samplesPerPoint, bitsPerSample, outputPointCount, outputData.toArray());
return result;
}
+2 -2
View File
@@ -324,7 +324,7 @@ class Controls extends FlxActionSet
inline function get_VOLUME_MUTE()
return _volume_mute.check();
public function new(name, scheme:KeyboardScheme = null)
public function new(name, ?scheme:KeyboardScheme)
{
super(name);
@@ -372,7 +372,7 @@ class Controls extends FlxActionSet
setKeyboardScheme(scheme, false);
}
override function update()
override function update():Void
{
super.update();
}
+72
View File
@@ -0,0 +1,72 @@
package funkin.mobile.external.android;
import lime.system.JNI;
import flixel.util.FlxSignal;
/**
* A Utility class to handle Android API level callbacks and events.
*/
@:unreflective
class CallbackUtil
{
#if android
/**
* The result code for `DATA_FOLDER_CLOSED` activity.
*/
public static var DATA_FOLDER_CLOSED(get, never):Int;
@:noCompletion
static function get_DATA_FOLDER_CLOSED():Int
{
final field:Null<Dynamic> = JNIUtil.createStaticField('funkin/extensions/CallbackUtil', 'DATA_FOLDER_CLOSED', 'I');
return field != null ? field.get() : 0;
}
/**
* Signal triggered when an activity result is received.
*
* First argument is the request code, second is the result code.
*/
public static var onActivityResult:FlxTypedSignal<Int->Int->Void> = new FlxTypedSignal<Int->Int->Void>();
/**
* Initializes the callback utility.
*/
public static function init():Void
{
final initCallBackJNI:Null<Dynamic> = JNIUtil.createStaticMethod('funkin/extensions/CallbackUtil', 'initCallBack', '(Lorg/haxe/lime/HaxeObject;)V');
if (initCallBackJNI != null)
{
initCallBackJNI(new CallbackHandler());
}
}
#end
}
/**
* Internal class to handle native callback events.
*/
class CallbackHandler #if (lime >= "8.0.0") implements JNISafety #end
{
#if android
@:allow(funkin.mobile.external.android.CallbackUtil)
function new():Void {}
/**
* Handles the activity result callback from native code.
*
* @param requestCode The request code of the acitivty.
* @param resultCode The result code of the acitivty.
*/
@:keep
#if (lime >= "8.0.0")
@:runOnMainThread
#end
public function onActivityResult(requestCode:Int, resultCode:Int):Void
{
if (CallbackUtil.onActivityResult != null) CallbackUtil.onActivityResult.dispatch(requestCode, resultCode);
}
#end
}
+23
View File
@@ -0,0 +1,23 @@
package funkin.mobile.external.android;
/**
* A Utility class to manage the Application's Data folder on Android.
*/
@:unreflective
class DataFolderUtil
{
#if android
/**
* Opens the data folder on an Android device using JNI.
*/
public static function openDataFolder():Void
{
final openDataFolderJNI:Null<Dynamic> = JNIUtil.createStaticMethod('funkin/util/DataFolderUtil', 'openDataFolder', '(I)V');
if (openDataFolderJNI != null)
{
openDataFolderJNI(CallbackUtil.DATA_FOLDER_CLOSED);
}
}
#end
}
+111
View File
@@ -0,0 +1,111 @@
package funkin.mobile.external.android;
#if android
import lime.system.JNI;
/**
* A utility class for caching JNI method and field references.
*/
class JNIUtil
{
@:noCompletion
private static var staticMethodCache:Map<String, Dynamic> = [];
@:noCompletion
private static var memberMethodCache:Map<String, Dynamic> = [];
@:noCompletion
private static var staticFieldCache:Map<String, JNIStaticField> = [];
@:noCompletion
private static var memberFieldCache:Map<String, JNIMemberField> = [];
/**
* Retrieves or creates a cached static method reference.
*
* @param className The name of the Java class containing the method.
* @param methodName The name of the method.
* @param signature The method signature in JNI format.
* @param cache Whether to cache the result (default true).
* @return A dynamic reference to the static method.
*/
public static function createStaticMethod(className:String, methodName:String, signature:String, cache:Bool = true):Null<Dynamic>
{
@:privateAccess
className = JNI.transformClassName(className);
final key:String = '$className::$methodName::$signature';
if (cache && !staticMethodCache.exists(key)) staticMethodCache.set(key, JNI.createStaticMethod(className, methodName, signature));
else if (!cache) return JNI.createStaticMethod(className, methodName, signature);
return staticMethodCache.get(key);
}
/**
* Retrieves or creates a cached member method reference.
*
* @param className The name of the Java class containing the method.
* @param methodName The name of the method.
* @param signature The method signature in JNI format.
* @param cache Whether to cache the result (default true).
* @return A dynamic reference to the member method.
*/
public static function createMemberMethod(className:String, methodName:String, signature:String, cache:Bool = true):Null<Dynamic>
{
@:privateAccess
className = JNI.transformClassName(className);
final key:String = '$className::$methodName::$signature';
if (cache && !memberMethodCache.exists(key)) memberMethodCache.set(key, JNI.createMemberMethod(className, methodName, signature));
else if (!cache) return JNI.createMemberMethod(className, methodName, signature);
return memberMethodCache.get(key);
}
/**
* Retrieves or creates a cached static field reference.
*
* @param className The name of the Java class containing the field.
* @param fieldName The name of the field.
* @param signature The field signature in JNI format.
* @param cache Whether to cache the result (default true).
* @return A reference to the static field.
*/
public static function createStaticField(className:String, fieldName:String, signature:String, cache:Bool = true):Null<JNIStaticField>
{
@:privateAccess
className = JNI.transformClassName(className);
final key:String = '$className::$fieldName::$signature';
if (cache && !staticFieldCache.exists(key)) staticFieldCache.set(key, JNI.createStaticField(className, fieldName, signature));
else if (!cache) return JNI.createStaticField(className, fieldName, signature);
return staticFieldCache.get(key);
}
/**
* Retrieves or creates a cached member field reference.
*
* @param className The name of the Java class containing the field.
* @param fieldName The name of the field.
* @param signature The field signature in JNI format.
* @param cache Whether to cache the result (default true).
* @return A reference to the member field.
*/
public static function createMemberField(className:String, fieldName:String, signature:String, cache:Bool = true):Null<JNIMemberField>
{
@:privateAccess
className = JNI.transformClassName(className);
final key:String = '$className::$fieldName::$signature';
if (cache && !memberFieldCache.exists(key)) memberFieldCache.set(key, JNI.createMemberField(className, fieldName, signature));
else if (!cache) return JNI.createMemberField(className, fieldName, signature);
return memberFieldCache.get(key);
}
}
#end
+52
View File
@@ -0,0 +1,52 @@
package funkin.mobile.external.android;
import lime.math.Rectangle;
import lime.system.JNI;
/**
* A Utility class to get Android screen related informations.
*/
@:unreflective
class ScreenUtil
{
#if android
/**
* Retrieves the dimensions of display cutouts (such as notches) on Android devices.
*
* @return An array of `Rectangle` objects, each representing a display cutout's position and size.
*/
public static function getCutoutDimensions():Array<Rectangle>
{
final getCutoutDimensionsJNI:Null<Dynamic> = JNIUtil.createStaticMethod('funkin/util/ScreenUtil', 'getCutoutDimensions', '()[Landroid/graphics/Rect;');
if (getCutoutDimensionsJNI != null)
{
final rectangles:Array<Rectangle> = [];
for (rectangle in cast(getCutoutDimensionsJNI(), Array<Dynamic>))
{
if (rectangle == null) continue;
final topJNI:Null<JNIMemberField> = JNIUtil.createMemberField('android/graphics/Rect', 'top', 'I');
final leftJNI:Null<JNIMemberField> = JNIUtil.createMemberField('android/graphics/Rect', 'left', 'I');
final rightJNI:Null<JNIMemberField> = JNIUtil.createMemberField('android/graphics/Rect', 'right', 'I');
final bottomJNI:Null<JNIMemberField> = JNIUtil.createMemberField('android/graphics/Rect', 'bottom', 'I');
if (topJNI != null && leftJNI != null && rightJNI != null && bottomJNI != null)
{
final top:Int = topJNI.get(rectangle);
final left:Int = leftJNI.get(rectangle);
final right:Int = rightJNI.get(rectangle);
final bottom:Int = bottomJNI.get(rectangle);
rectangles.push(new Rectangle(left, top, right - left, bottom - top));
}
}
return rectangles;
}
return [];
}
#end
}
@@ -0,0 +1,36 @@
package funkin.extensions;
import android.content.Intent;
import org.haxe.extension.Extension;
import org.haxe.lime.HaxeObject;
public class CallbackUtil extends Extension
{
/**
* Constant representing the event when the data folder is closed.
*/
public static int DATA_FOLDER_CLOSED = 0x01;
private static HaxeObject haxeObject;
/**
* Initializes the callback object for handling Haxe callbacks.
*
* @param haxeObject The HaxeObject instance to handle callbacks.
*/
public static void initCallBack(final HaxeObject haxeObject)
{
CallbackUtil.haxeObject = haxeObject;
}
@Override
public boolean onActivityResult(int requestCode, int resultCode, Intent data)
{
if (haxeObject != null)
haxeObject.call2("onActivityResult", requestCode, resultCode);
return true;
}
}
@@ -0,0 +1,346 @@
package funkin.provider;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.graphics.Point;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
import android.provider.DocumentsProvider;
import android.webkit.MimeTypeMap;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedList;
/**
* @see https://github.com/termux/termux-app/blob/7bceab88e2272f961d1b94ef736f1a9e20173247/app/src/main/java/com/termux/filepicker/TermuxDocumentsProvider.java
*/
public class DataFolderProvider extends DocumentsProvider
{
private static File BASE_DIR;
private static String BASE_DIR_PATH;
private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
Root.COLUMN_ROOT_ID,
Root.COLUMN_MIME_TYPES,
Root.COLUMN_FLAGS,
Root.COLUMN_ICON,
Root.COLUMN_TITLE,
Root.COLUMN_SUMMARY,
Root.COLUMN_DOCUMENT_ID,
Root.COLUMN_AVAILABLE_BYTES
};
private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
Document.COLUMN_DOCUMENT_ID,
Document.COLUMN_MIME_TYPE,
Document.COLUMN_DISPLAY_NAME,
Document.COLUMN_LAST_MODIFIED,
Document.COLUMN_FLAGS,
Document.COLUMN_SIZE
};
@Override
public Cursor queryRoots(String[] projection)
{
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_ROOT_PROJECTION);
if (BASE_DIR == null)
return result;
final MatrixCursor.RowBuilder row = result.newRow();
row.add(Root.COLUMN_ROOT_ID, getDocIdForFile(BASE_DIR));
row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(BASE_DIR));
row.add(Root.COLUMN_SUMMARY, "Data Folder");
row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD);
row.add(Root.COLUMN_TITLE, "::APP_TITLE::");
row.add(Root.COLUMN_MIME_TYPES, "*/*");
row.add(Root.COLUMN_AVAILABLE_BYTES, BASE_DIR.getFreeSpace());
::if (APP_PACKAGE != "")::
row.add(Root.COLUMN_ICON, ::APP_PACKAGE::.R.mipmap.ic_launcher);
::end::
return result;
}
@Override
public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException
{
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
includeFile(result, documentId, null);
return result;
}
@Override
public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException
{
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
File parent = getFileForDocId(parentDocumentId);
if (parent != null)
{
File[] children = null;
try {
children = parent.listFiles();
} catch (SecurityException e) {
children = new File[0];
}
if (children != null)
{
for (File file : children)
includeFile(result, null, file);
}
}
return result;
}
@Override
public ParcelFileDescriptor openDocument(String documentId, String mode, CancellationSignal signal) throws FileNotFoundException
{
return ParcelFileDescriptor.open(getFileForDocId(documentId), ParcelFileDescriptor.parseMode(mode));
}
@Override
public AssetFileDescriptor openDocumentThumbnail(String documentId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException
{
final File file = getFileForDocId(documentId);
final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
return new AssetFileDescriptor(pfd, 0, file.length());
}
@Override
public boolean onCreate()
{
BASE_DIR = getContext().getExternalFilesDir(null);
if (BASE_DIR == null)
return false;
try
{
BASE_DIR_PATH = BASE_DIR.getCanonicalPath();
}
catch (IOException e)
{
BASE_DIR_PATH = BASE_DIR.getAbsolutePath();
}
return true;
}
@Override
public String createDocument(String parentDocumentId, String mimeType, String displayName) throws FileNotFoundException
{
File parentFile = getFileForDocId(parentDocumentId);
File newFile = new File(parentFile, displayName);
int noConflictId = 2;
while (newFile.exists())
{
newFile = new File(parentFile, displayName + " (" + noConflictId++ + ")");
}
try
{
boolean succeeded;
if (Document.MIME_TYPE_DIR.equals(mimeType))
succeeded = newFile.mkdir();
else
succeeded = newFile.createNewFile();
if (!succeeded)
throw new FileNotFoundException("Failed to create document with id " + newFile.getPath());
}
catch (IOException e)
{
throw new FileNotFoundException("Failed to create document with id " + newFile.getPath());
}
return newFile.getPath();
}
@Override
public void deleteDocument(String documentId) throws FileNotFoundException
{
if (!deleteRecursive(getFileForDocId(documentId)))
throw new FileNotFoundException("Failed to delete document with id " + documentId);
}
@Override
public String getDocumentType(String documentId) throws FileNotFoundException
{
return getMimeType(getFileForDocId(documentId));
}
@Override
public Cursor querySearchDocuments(String rootId, String query, String[] projection) throws FileNotFoundException
{
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
final LinkedList<File> pending = new LinkedList<>();
pending.add(getFileForDocId(rootId));
final int MAX_SEARCH_RESULTS = 50;
while (!pending.isEmpty() && result.getCount() < MAX_SEARCH_RESULTS)
{
final File file = pending.removeFirst();
boolean isInsideHome;
try
{
isInsideHome = file.getCanonicalPath().startsWith(BASE_DIR_PATH);
}
catch (IOException e)
{
isInsideHome = true;
}
if (isInsideHome)
{
if (file.isDirectory())
{
try {
Collections.addAll(pending, file.listFiles());
} catch (SecurityException e) {
// ignore
}
}
else if (file.getName().toLowerCase().contains(query))
{
includeFile(result, null, file);
}
}
}
return result;
}
@Override
public boolean isChildDocument(String parentDocumentId, String documentId)
{
try
{
File parent = getFileForDocId(parentDocumentId).getCanonicalFile();
File child = getFileForDocId(documentId).getCanonicalFile();
return child.getPath().startsWith(parent.getPath() + "/");
}
catch (IOException e)
{
return false;
}
}
private boolean deleteRecursive(File file)
{
if (file.isDirectory())
{
File[] children = file.listFiles();
if (children != null)
{
for (File child : children)
{
if (!deleteRecursive(child))
return false;
}
}
}
return file.delete();
}
private static String getDocIdForFile(File file)
{
return file.getAbsolutePath();
}
private static File getFileForDocId(String docId) throws FileNotFoundException
{
if (BASE_DIR == null)
throw new FileNotFoundException("Base directory not available");
final File f = (docId == null || docId.isEmpty()) ? BASE_DIR : new File(docId);
if (!f.exists())
throw new FileNotFoundException(f.getAbsolutePath() + " not found");
return f;
}
private static String getMimeType(File file)
{
if (file == null || file.isDirectory())
return Document.MIME_TYPE_DIR;
String name = file.getName();
int lastDot = name.lastIndexOf('.');
if (lastDot >= 0)
{
String extension = name.substring(lastDot + 1).toLowerCase();
String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if (mime != null)
return mime;
}
return "application/octet-stream";
}
private void includeFile(MatrixCursor result, String docId, File file) throws FileNotFoundException
{
if (docId == null)
docId = getDocIdForFile(file);
else
file = getFileForDocId(docId);
int flags = 0;
if (file.isDirectory())
{
if (file.canWrite())
flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
}
else if (file.canWrite())
{
flags |= Document.FLAG_SUPPORTS_WRITE;
}
if (file.getParentFile() != null && file.getParentFile().canWrite())
flags |= Document.FLAG_SUPPORTS_DELETE;
final String mimeType = getMimeType(file);
if (mimeType.startsWith("image/"))
flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
final MatrixCursor.RowBuilder row = result.newRow();
row.add(Document.COLUMN_DOCUMENT_ID, docId);
row.add(Document.COLUMN_DISPLAY_NAME, file.getName());
row.add(Document.COLUMN_SIZE, file.length());
row.add(Document.COLUMN_MIME_TYPE, mimeType);
row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
row.add(Document.COLUMN_FLAGS, flags);
::if (APP_PACKAGE != "")::
row.add(Document.COLUMN_ICON, ::APP_PACKAGE::.R.mipmap.ic_launcher);
::end::
}
}
@@ -0,0 +1,34 @@
package funkin.util;
import android.content.Intent;
import android.content.ContentResolver;
import android.net.Uri;
import android.os.Build;
import android.provider.DocumentsContract;
import android.content.pm.PackageInfo;
import java.util.List;
import org.haxe.extension.Extension;
public class DataFolderUtil
{
/**
* A method that opens the Application's data folder for browsing through the Storage Access Framework.
* It's highly based on some code borrowed from Mterial Files
* https://github.com/zhanghai/MaterialFiles
*/
public static void openDataFolder(int requestCode)
{
::if (APP_PACKAGE != "")::
if (Extension.mainActivity != null)
{
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(DocumentsContract.buildRootUri("::APP_PACKAGE::.docprovider", ""), "vnd.android.document/directory");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
Extension.mainActivity.startActivityForResult(intent, requestCode);
}
::end::
}
}
@@ -0,0 +1,39 @@
package funkin.util;
import android.graphics.Rect;
import android.os.Build;
import android.view.DisplayCutout;
import android.view.WindowInsets;
import java.util.List;
import org.haxe.extension.Extension;
public class ScreenUtil
{
public static Rect[] getCutoutDimensions()
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
{
if (Extension.mainActivity != null)
{
WindowInsets insets = Extension.mainActivity.getWindow().getDecorView().getRootWindowInsets();
if (insets != null)
{
DisplayCutout cutout = insets.getDisplayCutout();
if (cutout != null)
{
List<Rect> boundingRects = cutout.getBoundingRects();
if (boundingRects != null && !boundingRects.isEmpty())
return boundingRects.toArray(new Rect[0]);
}
}
}
}
return new Rect[0];
}
}
@@ -1,4 +1,4 @@
package funkin.mobile.external;
package funkin.mobile.external.ios;
/**
* A Utility class to manage iOS audio.
@@ -1,15 +1,16 @@
package funkin.mobile.external;
package funkin.mobile.external.ios;
/**
* A Utility class to get iOS screen related informations.
*/
@:build(funkin.mobile.macros.LinkerMacro.xml('project/Build.xml'))
@:include('ScreenUtils.hpp')
@:include('ScreenUtil.hpp')
@:unreflective
extern class ScreenUtils
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;
}
@@ -13,7 +13,7 @@
<files id="external-ios" dir="${this_dir}/ios" if="iphoneos || iphonesim">
<compilerflag value="-I${this_dir}/ios/include" />
<file name="src/ScreenUtils.mm" />
<file name="src/ScreenUtil.mm" />
<file name="src/AudioSession.mm" />
</files>
@@ -1,4 +1,4 @@
#import "ScreenUtils.hpp"
#import "ScreenUtil.hpp"
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
+1 -1
View File
@@ -66,7 +66,7 @@ class FunkinBackButton extends FunkinButton
held = true;
FlxTween.cancelTweensOf(this);
HapticUtil.vibrate(0, 0.01, 0.2);
HapticUtil.vibrate(0, 0.01, 0.5);
animation.play('hold');
alpha = 1;
+36 -11
View File
@@ -28,6 +28,8 @@ class InAppPurchasesUtil
*/
public static final UPGRADE_PRODUCT_ID:String = 'no_ads';
public static var hasInitialized:Bool = false;
/**
* A static variable that holds an array of currently loaded product details for in-app purchases.
*/
@@ -65,9 +67,14 @@ class InAppPurchasesUtil
});
IAPAndroid.onProductDetailsResponse.add(function(result:IAPResult, productDetails:Array<IAPProductDetails>):Void {
if (result.getResponseCode() == IAPResponseCode.OK) currentProductDetails = productDetails;
if (result.getResponseCode() == IAPResponseCode.OK)
{
hasInitialized = true;
currentProductDetails = productDetails;
}
else
{
hasInitialized = false;
trace('Failed to fetch product details: "$result"');
}
});
@@ -102,10 +109,12 @@ class InAppPurchasesUtil
#else
IAPIOS.onProductDetailsReceived.add(function(productDetails:Array<IAPProductDetails>):Void {
if (productDetails != null) currentProductDetails = productDetails;
hasInitialized = true;
});
IAPIOS.onProductDetailsFailed.add(function(error:IAPError):Void {
trace('Failed to request product details: "$error"');
hasInitialized = false;
});
IAPIOS.onPurchasesUpdated.add(function(purchases:Array<IAPPurchase>):Void {
@@ -147,20 +156,32 @@ class InAppPurchasesUtil
{
function purchasesUpdatedEvent(result:IAPResult, purchases:Array<IAPPurchase>):Void
{
for (purchase in purchases)
if (result.getResponseCode() == IAPResponseCode.OK)
{
if (purchase.getProducts().contains(id))
for (purchase in purchases)
{
if (purchase.getPurchaseState() == IAPPurchaseState.PURCHASED)
if (purchase.getProducts().contains(id))
{
if (onPurchased != null) onPurchased();
if (purchase.getPurchaseState() == IAPPurchaseState.PURCHASED)
{
if (onPurchased != null) onPurchased();
IAPAndroid.onPurchasesUpdated.remove(purchasesUpdatedEvent);
}
}
}
}
else
{
IAPAndroid.onPurchasesUpdated.remove(purchasesUpdatedEvent);
}
IAPAndroid.onPurchasesUpdated.remove(purchasesUpdatedEvent);
final debugMessage:Null<String> = result.getDebugMessage();
Toast.makeText(result.getDebugMessage(), Toast.LENGTH_SHORT);
if (debugMessage != null && debugMessage.length > 0)
{
Toast.makeText(debugMessage, Toast.LENGTH_SHORT);
}
}
if (!IAPAndroid.onPurchasesUpdated.has(purchasesUpdatedEvent))
@@ -180,14 +201,18 @@ class InAppPurchasesUtil
{
if (purchase.getPaymentProductIdentifier() == id)
{
if (purchase.getTransactionState() == IAPPurchaseState.PURCHASED)
switch (purchase.getTransactionState())
{
if (onPurchased != null) onPurchased();
case IAPPurchaseState.PURCHASED:
if (onPurchased != null) onPurchased();
IAPIOS.onPurchasesUpdated.remove(purchasesUpdatedEvent);
case IAPPurchaseState.FAILED:
IAPIOS.onPurchasesUpdated.remove(purchasesUpdatedEvent);
default:
}
}
}
IAPIOS.onPurchasesUpdated.remove(purchasesUpdatedEvent);
}
if (!IAPIOS.onPurchasesUpdated.has(purchasesUpdatedEvent))
+6 -7
View File
@@ -1,9 +1,9 @@
package funkin.mobile.util;
#if ios
import funkin.mobile.external.ScreenUtils;
import funkin.mobile.external.ios.ScreenUtil as NativeScreenUtil;
#elseif android
import extension.androidtools.Tools;
import funkin.mobile.external.android.ScreenUtil as NativeScreenUtil;
#end
import lime.math.Rectangle;
import lime.system.System;
@@ -26,7 +26,7 @@ class ScreenUtil
final rectDimensions:Array<Array<Float>> = [[], [], [], []];
// Push all the dimensions of the cutouts into an array
for (rect in Tools.getCutoutDimensions())
for (rect in NativeScreenUtil.getCutoutDimensions())
{
rectDimensions[0].push(rect.x);
rectDimensions[1].push(rect.y);
@@ -59,13 +59,12 @@ class ScreenUtil
var bottomInset:Float = -1;
var deviceWidth:Float = -1;
var deviceHeight:Float = -1;
var displayOrientation:DisplayOrientation = System.getDisplayOrientation(0);
ScreenUtils.getSafeAreaInsets(cpp.RawPointer.addressOf(topInset), cpp.RawPointer.addressOf(bottomInset), cpp.RawPointer.addressOf(leftInset),
NativeScreenUtil.getSafeAreaInsets(cpp.RawPointer.addressOf(topInset), cpp.RawPointer.addressOf(bottomInset), cpp.RawPointer.addressOf(leftInset),
cpp.RawPointer.addressOf(rightInset));
ScreenUtils.getScreenSize(cpp.RawPointer.addressOf(deviceWidth), cpp.RawPointer.addressOf(deviceHeight));
var displayOrientation = System.getDisplayOrientation(0);
NativeScreenUtil.getScreenSize(cpp.RawPointer.addressOf(deviceWidth), cpp.RawPointer.addressOf(deviceHeight));
notchRect.x = 0;
notchRect.y = 0.0;
+1
View File
@@ -545,6 +545,7 @@ class GameOverSubState extends MusicBeatSubState
*/
public function goBack():Void
{
if (blueballed == false) return;
isEnding = true;
blueballed = false;
if (parentPlayState != null) parentPlayState.deathCounter = 0;
+3 -6
View File
@@ -536,7 +536,7 @@ class PauseSubState extends MusicBeatSubState
FlxTween.tween(pauseCircle, {alpha: 0}, 0.6, {ease: FlxEase.quartOut});
hapticTimer.start(0.2, function(_) {
HapticUtil.vibrate(0, 0.01, 0.2);
HapticUtil.vibrate(0, 0.01, 0.5);
});
dataFadeTimer.start(0.3, function(_) {
@@ -640,12 +640,12 @@ class PauseSubState extends MusicBeatSubState
if (i == currentEntry)
{
currentMenuEntries[currentEntry].callback(this);
HapticUtil.vibrate(0, 0.05, 0.5);
HapticUtil.vibrate(0, 0.05, 1);
break;
}
changeSelection(i - currentEntry);
HapticUtil.vibrate(0, 0.01, 0.2);
HapticUtil.vibrate(0, 0.01, 0.5);
break;
}
@@ -745,9 +745,6 @@ class PauseSubState extends MusicBeatSubState
clearAndAddMenuEntries();
updateMetadataText();
changeSelection();
#if FEATURE_TOUCH_CONTROLS
SwipeUtil.calculateSwipeThreshold(menuEntryText.members, Y);
#end
}
/**
+12 -5
View File
@@ -545,6 +545,8 @@ class PlayState extends MusicBeatSubState
*/
public var comboPopUps:PopUpStuff;
public var isSongEnd:Bool = false;
#if mobile
/**
* The pause button for the game, only appears in Mobile targets.
@@ -1053,7 +1055,7 @@ class PlayState extends MusicBeatSubState
}
#if mobile
// note scrolling is shit on mobile without the arguments!!!
// Note scrolling is less smooth on mobile without these arguments!!!
Conductor.instance.update(Conductor.instance.songPosition + elapsed * 1000 * playbackRate, false);
#else
Conductor.instance.update(); // Normal conductor update.
@@ -1063,7 +1065,7 @@ class PlayState extends MusicBeatSubState
// This helps prevent a major bug where the level suddenly loops back to the start or middle.
if (Conductor.instance.songPosition >= (FlxG.sound.music.endTime ?? FlxG.sound.music.length))
{
if (mayPauseGame) endSong(skipEndingTransition);
if (mayPauseGame && !isSongEnd) endSong(skipEndingTransition);
}
}
@@ -1425,8 +1427,11 @@ class PlayState extends MusicBeatSubState
FlxG.sound.list.forEachAlive(function(sound:FlxSound) {
if (!sound.active || sound == FlxG.sound.music) return;
// In case it's a scheduled sound
var funkinSound:FunkinSound = cast sound;
if (funkinSound != null && !funkinSound.isPlaying) return;
if (Std.isOfType(sound, FunkinSound))
{
var funkinSound:FunkinSound = cast sound;
if (funkinSound != null && !funkinSound.isPlaying) return;
}
if (!sound.playing && sound.time >= 0) return;
sound.pause();
soundsPausedBySubState.add(sound);
@@ -2960,7 +2965,8 @@ class PlayState extends MusicBeatSubState
#end
// 9: Toggle the old icon.
if (FlxG.keys.justPressed.NINE && iconP1 != null) iconP1.toggleOldIcon();
if ((FlxG.keys.justPressed.NINE #if FEATURE_TOUCH_CONTROLS || (TouchUtil.justPressed && TouchUtil.overlapsComplex(iconP1)) #end)
&& iconP1 != null) iconP1.toggleOldIcon();
#if FEATURE_DEBUG_FUNCTIONS
// PAGEUP: Skip forward two sections.
@@ -3106,6 +3112,7 @@ class PlayState extends MusicBeatSubState
if (FlxG.sound.music != null) FlxG.sound.music.volume = 0;
vocals.volume = 0;
mayPauseGame = false;
isSongEnd = true;
// Prevent ghost misses while the song is ending.
disableKeys = true;
+2 -2
View File
@@ -773,7 +773,7 @@ class ResultState extends MusicBeatSubState
// GF slams into the wall.
if (atlas.sprite.anim.curFrame == 51)
{
HapticUtil.vibrate(0, 0.01, Constants.MAX_VIBRATION_AMPLITUDE / 3);
HapticUtil.vibrate(0, 0.01, (Constants.MAX_VIBRATION_AMPLITUDE / 3) * 2.5);
break;
}
@@ -797,7 +797,7 @@ class ResultState extends MusicBeatSubState
// Pico shoots.
if (atlas.sprite.anim.curFrame == 45)
{
HapticUtil.vibrate(0, 0.01, Constants.MAX_VIBRATION_AMPLITUDE / 3);
HapticUtil.vibrate(0, 0.01, (Constants.MAX_VIBRATION_AMPLITUDE / 3) * 2.5);
break;
}
@@ -61,7 +61,7 @@ class NoteVibrationsHandler
if (stackingAmplitude > Constants.MAX_VIBRATION_AMPLITUDE) stackingAmplitude = Constants.MAX_VIBRATION_AMPLITUDE;
if (stackingAmplitude > 0) HapticUtil.vibrate(0, 0.01, stackingAmplitude, 1, [HapticsMode.ALL, HapticsMode.NOTES_ONLY]);
if (stackingAmplitude > 0) HapticUtil.vibrate(0, 0.01, stackingAmplitude * 2.5, 1, [HapticsMode.ALL, HapticsMode.NOTES_ONLY]);
}
/**
@@ -84,7 +84,7 @@ class NoteVibrationsHandler
if (stackingAmplitude > Constants.MAX_VIBRATION_AMPLITUDE) stackingAmplitude = Constants.MAX_VIBRATION_AMPLITUDE;
if (stackingAmplitude > 0) HapticUtil.vibrate(0, 0.01, stackingAmplitude, 1, [HapticsMode.ALL, HapticsMode.NOTES_ONLY]);
if (stackingAmplitude > 0) HapticUtil.vibrate(0, 0.01, stackingAmplitude * 2.5, 1, [HapticsMode.ALL, HapticsMode.NOTES_ONLY]);
for (currentNoteStatus in noteStatuses)
{
+1 -1
View File
@@ -162,7 +162,7 @@ class Strumline extends FlxSpriteGroup
final inArrowContorlSchemeMode:Bool = #if mobile (Preferences.controlsScheme == FunkinHitboxControlSchemes.Arrows
&& !ControlsHandler.usingExternalInputDevice) #else false #end;
var isDownscroll:Bool = #if mobile (Preferences.controlsScheme == FunkinHitboxControlSchemes.Arrows
public var isDownscroll:Bool = #if mobile (Preferences.controlsScheme == FunkinHitboxControlSchemes.Arrows
&& !ControlsHandler.usingExternalInputDevice)
|| #end Preferences.downscroll;
+3 -1
View File
@@ -208,7 +208,9 @@ class SustainTrail extends FlxSprite
graphicHeight = sustainHeight(sustainLength, parentStrumline?.scrollSpeed ?? 1.0);
// instead of scrollSpeed, PlayState.SONG.speed
flipY = Preferences.downscroll #if mobile || (Preferences.controlsScheme == FunkinHitboxControlSchemes.Arrows) #end;
flipY = Preferences.downscroll #if mobile
|| (Preferences.controlsScheme == FunkinHitboxControlSchemes.Arrows
&& !funkin.mobile.input.ControlsHandler.usingExternalInputDevice) #end;
// alpha = 0.6;
alpha = 1.0;
+15 -1
View File
@@ -123,6 +123,7 @@ class Save
zoomCamera: true,
debugDisplay: false,
hapticsMode: 'All',
hapticsIntensityMultiplier: 1,
autoPause: true,
vsyncMode: 'Off',
strumlineBackgroundOpacity: 0,
@@ -158,7 +159,8 @@ class Save
{
// Reasonable defaults.
screenTimeout: false,
controlsScheme: FunkinHitboxControlSchemes.Arrows
controlsScheme: FunkinHitboxControlSchemes.Arrows,
noAds: false
},
#end
@@ -1553,6 +1555,12 @@ typedef SaveDataOptions =
*/
var hapticsMode:String;
/**
* Multiplier of intensity for all the haptic feedback effects.
* @default `1`
*/
var hapticsIntensityMultiplier:Float;
/**
* If enabled, the game will automatically pause when tabbing out.
* @default `true`
@@ -1639,6 +1647,12 @@ typedef SaveDataMobileOptions =
* @default `Arrows`
*/
var controlsScheme:String;
/**
* If bought, the game will not show any ads.
* @default `false`
*/
var noAds:Bool;
};
#end
+3 -2
View File
@@ -18,6 +18,7 @@ class MenuTypedList<T:MenuListItem> extends FlxTypedGroup<T>
{
// Pause input variable
public static var pauseInput:Bool = false;
public var selectedIndex(default, null):Int = 0;
public var selectedItem(get, never):T;
@@ -180,7 +181,7 @@ class MenuTypedList<T:MenuListItem> extends FlxTypedGroup<T>
item.scale.set(1.1, 1.1);
FlxTween.tween(item.scale, {x: 1, y: 1}, 0.3, {ease: FlxEase.backOut});
HapticUtil.vibrate(0, 0.05, 0.5);
HapticUtil.vibrate(0, 0.05, 1);
accept();
}
else
@@ -189,7 +190,7 @@ class MenuTypedList<T:MenuListItem> extends FlxTypedGroup<T>
item.scale.set(0.94, 0.94);
FlxTween.tween(item.scale, {x: 1, y: 1}, 0.3, {ease: FlxEase.backOut});
HapticUtil.vibrate(0, 0.01, 0.2);
HapticUtil.vibrate(0, 0.01, 0.5);
}
}
else
+7 -5
View File
@@ -23,9 +23,10 @@ class Nametag extends FlxSprite
shader = mosaicShader;
// So that's why there was that cursed sight (originally defaulted to bf)
if (character != null) switchChar(character);
// Made it not play the shader effect to prevent its being stuck, can't see it anyway.
if (character != null) switchChar(character, false);
else
switchChar(Constants.DEFAULT_CHARACTER);
switchChar(Constants.DEFAULT_CHARACTER, false);
}
public function updatePosition():Void
@@ -37,9 +38,9 @@ class Nametag extends FlxSprite
y -= offsetY;
}
public function switchChar(str:String):Void
public function switchChar(str:String, playMosaicSequence:Bool = true):Void
{
shaderEffect();
if (playMosaicSequence) shaderEffect();
new FlxTimer().start(4 / 30, _ -> {
var path:String = str;
@@ -54,7 +55,8 @@ class Nametag extends FlxSprite
scale.x = scale.y = 0.77;
updatePosition();
shaderEffect(true);
if (playMosaicSequence) shaderEffect(true);
});
}
+6
View File
@@ -91,6 +91,12 @@ class CreditsState extends MusicBeatState
{
super.create();
#if ios
var fix = new FlxText();
fix.font = CREDITS_FONT;
fix.draw();
#end
backersToBuild = CreditsDataHandler.fetchBackerEntries();
entriesToBuild = [];
@@ -5151,8 +5151,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
function handleToolboxes():Void
{
handleDifficultyToolbox();
handlePlayerPreviewToolbox();
handleOpponentPreviewToolbox();
// handlePlayerPreviewToolbox();
// handleOpponentPreviewToolbox();
}
function handleDifficultyToolbox():Void
+2
View File
@@ -175,6 +175,8 @@ class AlbumRoll extends FlxSpriteGroup
newAlbumArt.playAnimation('intro', true);
difficultyStars.visible = false;
difficultyStars.flameCheck();
new FlxTimer().start(0.75, function(_) {
showTitle();
showStars();
@@ -64,6 +64,7 @@ class FreeplayFlames extends FlxSpriteGroup
timers.remove(timer);
}
this.properPositions = false;
this.flameCount = value;
var visibleCount:Int = 0;
for (i in 0...5)
+18 -26
View File
@@ -11,28 +11,16 @@ class FreeplayScore extends FlxTypedSpriteGroup<ScoreNum>
function set_scoreShit(val):Int
{
if (group == null || group.members == null) return val;
var loopNum:Int = group.members.length - 1;
var dumbNumb = Std.parseInt(Std.string(val));
if (dumbNumb == null) dumbNumb = 0;
var prevNum:ScoreNum;
var dumbNumb:Int = Std.parseInt(Std.string(val)) ?? 0;
dumbNumb = Std.int(Math.min(dumbNumb, Math.pow(10, group.members.length) - 1));
var loopNum:Int = group.members.length - 1;
while (dumbNumb > 0)
{
group.members[loopNum].digit = dumbNumb % 10;
// var funnyNum = group.members[loopNum];
// prevNum = group.members[loopNum + 1];
// if (prevNum != null)
// {
// funnyNum.x = prevNum.x - (funnyNum.width * 0.7);
// }
// funnyNum.y = (funnyNum.baseY - (funnyNum.height / 2)) + 73;
// funnyNum.x = (funnyNum.baseX - (funnyNum.width / 2)) + 450; // this plus value is hand picked lol!
dumbNumb = Math.floor(dumbNumb / 10);
loopNum--;
}
@@ -71,6 +59,11 @@ class FreeplayScore extends FlxTypedSpriteGroup<ScoreNum>
}
}
/**
* ScoreNum is the number graphic that is used for the completion percentage.
* It handles offsetting / positioning of the numbers so they look a bit nicer placed
* NOTE: this is actually a bit similar to the ResultScore class, should perhaps tidy the logic up?
*/
@:nullSafety
class ScoreNum extends FlxSprite
{
@@ -106,18 +99,10 @@ class ScoreNum extends FlxSprite
return val;
}
public var baseY:Float = 0;
public var baseX:Float = 0;
var numToString:Array<String> = ["ZERO", "ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN", "EIGHT", "NINE"];
public function new(x:Float, y:Float, ?initDigit:Int = 0, ?styleData:FreeplayStyle)
{
super(x, y);
baseY = y;
baseX = x;
if (styleData == null)
{
frames = Paths.getSparrowAtlas('digital_numbers');
@@ -129,15 +114,22 @@ class ScoreNum extends FlxSprite
for (i in 0...10)
{
var stringNum:String = numToString[i];
animation.addByPrefix(stringNum, '$stringNum DIGITAL', 24, false);
animation.addByPrefix(getIntToString(i), '${getIntToString(i)} DIGITAL', 24, false);
}
this.digit = initDigit ?? 0;
animation.play(numToString[digit], true);
animation.play(getIntToString(digit), true);
setGraphicSize(Std.int(width * 0.4));
updateHitbox();
}
final numToString:Array<String> = ["ZERO", "ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN", "EIGHT", "NINE"];
function getIntToString(number:Int):String
{
if (numToString[number] == null) return numToString[0];
return numToString[number];
}
}
+164 -186
View File
@@ -122,6 +122,7 @@ class FreeplayState extends MusicBeatSubState
var songs:Array<Null<FreeplaySongData>> = [];
var curSelected:Int = 0;
// curSelectedFloat is used for mobile to get "inbetween" selections for swipe/scrolling/momentum stuff
var curSelectedFloat:Float = 0;
/**
@@ -158,6 +159,16 @@ class FreeplayState extends MusicBeatSubState
return grpDifficulties.members.filter(d -> d.difficultyId == currentDifficulty)[0];
}
/**
* Another utility var, this one gets our current selected capsule easily
*/
var currentCapsule(get, never):SongMenuItem;
function get_currentCapsule():SongMenuItem
{
return grpCapsules.members[curSelected];
}
var coolColors:Array<Int> = [
0xFF9271FD,
0xFF9271FD,
@@ -170,7 +181,6 @@ class FreeplayState extends MusicBeatSubState
];
var grpCapsules:FlxTypedGroup<SongMenuItem>;
var curPlaying:Bool = false;
var dj:Null<FreeplayDJ> = null;
#if FEATURE_TOUCH_CONTROLS
@@ -187,8 +197,8 @@ class FreeplayState extends MusicBeatSubState
var letterSort:LetterSort;
var exitMovers:ExitMoverData = new Map();
var diffSelLeft:Null<DifficultySelector> = null;
var diffSelRight:Null<DifficultySelector> = null;
var diffSelLeft:DifficultySelector;
var diffSelRight:DifficultySelector;
var exitMoversCharSel:ExitMoverData = new Map();
@@ -215,8 +225,6 @@ class FreeplayState extends MusicBeatSubState
*/
public static var rememberedVariation:String = Constants.DEFAULT_VARIATION;
var allDifficulties:Array<String> = Constants.DEFAULT_DIFFICULTY_LIST_FULL;
public var funnyCam:FunkinCamera;
var rankCamera:FunkinCamera;
@@ -261,11 +269,11 @@ class FreeplayState extends MusicBeatSubState
};
currentCharacter = fetchPlayableCharacter();
currentCharacterId = currentCharacter.getFreeplayStyleID();
currentCharacterId = currentCharacter.id;
currentVariation = rememberedVariation;
currentDifficulty = rememberedDifficulty;
styleData = FreeplayStyleRegistry.instance.fetchEntry(currentCharacterId);
styleData = FreeplayStyleRegistry.instance.fetchEntry(currentCharacter.getFreeplayStyleID());
rememberedCharacterId = currentCharacter?.id ?? Constants.DEFAULT_CHARACTER;
fromCharSelect = params?.fromCharSelect ?? false;
@@ -306,6 +314,7 @@ class FreeplayState extends MusicBeatSubState
funnyCam = new FunkinCamera('freeplayFunny', 0, 0, FlxG.width, FlxG.height);
grpCapsules = new FlxTypedGroup<SongMenuItem>();
grpDifficulties = new FlxTypedSpriteGroup<DifficultySprite>(-300, 80);
difficultyDots = new FlxTypedSpriteGroup<DifficultyDot>(203, 170);
letterSort = new LetterSort((CUTOUT_WIDTH * SONGS_POS_MULTI) + 400, 75);
rankBg = new FunkinSprite(0, 0);
@@ -318,6 +327,10 @@ class FreeplayState extends MusicBeatSubState
charSelectHint = new FlxText(-40, 18, FlxG.width - 8 - 8, 'Press [ LOL ] to change characters', 32);
backingImage = FunkinSprite.create(backingCard.pinkBack.width * 0.74, 0, styleData == null ? 'freeplay/freeplayBGweek1-bf' : styleData.getBgAssetKey());
// TODO: refactor DifficultySelector to *not* use `this` as input? Handle it's animations and style data in different manner
diffSelLeft = new DifficultySelector((CUTOUT_WIDTH * DJ_POS_MULTI) + 20, grpDifficulties.y - 10, false, controls, styleData);
diffSelRight = new DifficultySelector((CUTOUT_WIDTH * DJ_POS_MULTI) + 325, grpDifficulties.y - 10, true, controls, styleData);
}
override function create():Void
@@ -345,7 +358,7 @@ class FreeplayState extends MusicBeatSubState
#end
// Block input until the intro finishes.
busy = true;
controls.active = false;
// Add a null entry that represents the RANDOM option
songs.push(null);
@@ -610,8 +623,8 @@ class FreeplayState extends MusicBeatSubState
// Reminder, this is a callback function being set, rather than these being called here in create()
letterSort.changeSelectionCallback = (str) -> {
var curSong:Null<FreeplaySongData> = grpCapsules.members[curSelected]?.freeplayData;
grpCapsules.members[curSelected].selected = false;
var curSong:Null<FreeplaySongData> = currentCapsule?.freeplayData;
currentCapsule.selected = false;
switch (str)
{
@@ -652,20 +665,11 @@ class FreeplayState extends MusicBeatSubState
wait: 0.1
});
diffSelLeft = new DifficultySelector(this, (CUTOUT_WIDTH * DJ_POS_MULTI) + 20, grpDifficulties.y - 10, false, controls, styleData);
diffSelRight = new DifficultySelector(this, (CUTOUT_WIDTH * DJ_POS_MULTI) + 325, grpDifficulties.y - 10, true, controls, styleData);
diffSelLeft.visible = false;
add(diffSelLeft);
if (diffSelLeft != null)
{
diffSelLeft.visible = false;
add(diffSelLeft);
}
if (diffSelRight != null)
{
diffSelRight.visible = false;
add(diffSelRight);
}
diffSelRight.visible = false;
add(diffSelRight);
// putting these here to fix the layering
add(overhangStuff);
@@ -681,14 +685,13 @@ class FreeplayState extends MusicBeatSubState
// be careful not to "add()" things in here unless it's to a group that's already added to the state
// otherwise it won't be properly attatched to funnyCamera (relavent code should be at the bottom of create())
var onDJIntroDone = function() {
busy = false;
var onDJIntroDone:Void->Void = function() {
controls.active = true;
// when boyfriend hits dat shiii
albumRoll.playIntro();
var daSong = grpCapsules.members[curSelected].freeplayData;
albumRoll.albumId = daSong?.data.getAlbumId(currentDifficulty, currentVariation);
albumRoll.albumId = currentCapsule.freeplayData?.data.getAlbumId(currentDifficulty, currentVariation);
if (!fromCharSelect)
{
@@ -711,30 +714,26 @@ class FreeplayState extends MusicBeatSubState
FlxTween.cancelTweensOf(diff);
FlxTween.tween(diff, {x: (CUTOUT_WIDTH * DJ_POS_MULTI) + 90}, 0.6, {ease: FlxEase.quartOut});
diff.y = 80;
final isCurrentDiff:Bool = diff == currentDifficultySprite;
diff.visible = isCurrentDiff;
diff.visible = diff == currentDifficultySprite;
}
FlxTween.tween(grpDifficulties, {x: (CUTOUT_WIDTH * DJ_POS_MULTI) + 90}, 0.6, {ease: FlxEase.quartOut});
if (diffSelLeft != null) diffSelLeft.visible = true;
if (diffSelRight != null) diffSelRight.visible = true;
diffSelLeft.visible = true;
diffSelRight.visible = true;
letterSort.visible = true;
if (diffSelLeft != null && diffSelRight != null)
{
exitMovers.set([diffSelLeft, diffSelRight],
{
x: -diffSelLeft.width * 2,
speed: 0.26
});
exitMovers.set([diffSelLeft, diffSelRight],
{
x: -diffSelLeft.width * 2,
speed: 0.26
});
exitMoversCharSel.set([diffSelLeft, diffSelRight],
{
y: -270,
speed: 0.8,
wait: 0.1
});
}
exitMoversCharSel.set([diffSelLeft, diffSelRight],
{
y: -270,
speed: 0.8,
wait: 0.1
});
new FlxTimer().start(1 / 24, function(handShit) {
fnfHighscoreSpr.visible = true;
@@ -761,20 +760,19 @@ class FreeplayState extends MusicBeatSubState
if (prepForNewRank && fromResultsParams != null)
{
rankAnimStart(fromResultsParams, grpCapsules.members[curSelected]);
rankAnimStart(fromResultsParams, currentCapsule);
albumRoll.skipIntro();
albumRoll.showStars();
}
refreshDots(5, Constants.DEFAULT_DIFFICULTY_LIST_FULL.indexOf(currentDifficulty), Constants.DEFAULT_DIFFICULTY_LIST_FULL.indexOf(currentDifficulty));
fadeDots(true);
#if FEATURE_TOUCH_CONTROLS
SwipeUtil.calculateSwipeThreshold(grpCapsules.members, Y);
FlxG.touches.swipeThreshold.x = 60;
#end
};
allDifficulties = SongRegistry.instance.listAllDifficulties(currentCharacterId);
// Generates song list with the starter params (who our current character is, last remembered difficulty, etc.)
// Set this to false if you prefer the 50% transparency on the capsules when they first appear.
generateSongList(null, true);
@@ -895,15 +893,7 @@ class FreeplayState extends MusicBeatSubState
// Initialize the random capsule, with empty/blank info (which we display once bf/pico does his hand)
var randomCapsule:SongMenuItem = grpCapsules.recycle(SongMenuItem);
randomCapsule.initPosition(FlxG.width, 0);
randomCapsule.initData(null, styleData, 1);
randomCapsule.y = randomCapsule.intendedY(0) + 10;
randomCapsule.targetPos.x = randomCapsule.x;
randomCapsule.alpha = 0.5;
randomCapsule.songText.visible = false;
randomCapsule.favIcon.visible = false;
randomCapsule.favIconBlurred.visible = false;
randomCapsule.ranking.visible = false;
randomCapsule.initRandom(styleData);
randomCapsule.onConfirm = function() {
capsuleOnConfirmRandom(randomCapsule);
};
@@ -1009,7 +999,7 @@ class FreeplayState extends MusicBeatSubState
function rankAnimStart(fromResults:FromResultsParams, capsuleToRank:SongMenuItem):Void
{
busy = true;
controls.active = false;
// We get the current selected capsule, in-case someone changes the song selection during a timer
capsuleToRank.sparkle.alpha = 0;
// capsuleToRank.forcePosition();
@@ -1230,7 +1220,7 @@ class FreeplayState extends MusicBeatSubState
capsule.cameras = [funnyCam];
// NOW we can interact with the menu
busy = false;
controls.active = true;
capsule.sparkle.alpha = 0.7;
playCurSongPreview(capsule);
}, null);
@@ -1306,7 +1296,7 @@ class FreeplayState extends MusicBeatSubState
{
var distance:Int = 30;
var shiftAmt:Float = (distance * amount) / 2;
var daSong:Null<FreeplaySongData> = grpCapsules.members[curSelected].freeplayData;
var daSong:Null<FreeplaySongData> = currentCapsule.freeplayData;
for (i in 0...difficultyDots.group.members.length)
{
@@ -1389,7 +1379,7 @@ class FreeplayState extends MusicBeatSubState
return;
}
busy = true;
controls.active = false;
FunkinSound.playOnce(Paths.sound('confirmMenu'));
@@ -1408,8 +1398,8 @@ class FreeplayState extends MusicBeatSubState
function transitionToCharSelect():Void
{
busy = true;
var transitionGradient = new FlxSprite(0, 720).loadGraphic(Paths.image('freeplay/transitionGradient'));
controls.active = false;
var transitionGradient:FlxSprite = new FlxSprite(0, 720).loadGraphic(Paths.image('freeplay/transitionGradient'));
transitionGradient.scale.set(1280, 1);
transitionGradient.updateHitbox();
transitionGradient.cameras = [rankCamera];
@@ -1443,25 +1433,22 @@ class FreeplayState extends MusicBeatSubState
fadeShader.fade(1.0, 0.0, 0.8, {ease: FlxEase.quadIn});
FlxG.sound.music?.fadeOut(0.9, 0);
// Passing the currrent Freeplay character to the CharSelect so we can start it with that character selected
new FlxTimer().start(0.9, _ -> {
FlxG.switchState(() ->
new funkin.ui.charSelect.CharSelectSubState({character: currentCharacterId} // Passing the currrent Freeplay character to the CharSelect so we can start it with that character selected
));
FlxG.switchState(() -> new funkin.ui.charSelect.CharSelectSubState({character: currentCharacterId}));
});
for (grpSpr in exitMoversCharSel.keys())
{
var moveData:Null<MoveData> = exitMoversCharSel.get(grpSpr);
if (moveData == null) continue;
if (exitMoversCharSel.get(grpSpr) == null) continue;
for (spr in grpSpr)
{
if (spr == null) continue;
var funnyMoveShit:MoveData = moveData;
var moveDataY = funnyMoveShit.y ?? spr.y;
var moveDataSpeed = funnyMoveShit.speed ?? 0.2;
var moveDataWait = funnyMoveShit.wait ?? 0.0;
var moveDataY = exitMoversCharSel.get(grpSpr)?.y ?? spr.y;
var moveDataSpeed = exitMoversCharSel.get(grpSpr)?.speed ?? 0.2;
FlxTween.tween(spr, {y: moveDataY + spr.y}, moveDataSpeed, {ease: FlxEase.backIn});
}
@@ -1471,7 +1458,7 @@ class FreeplayState extends MusicBeatSubState
function enterFromCharSel():Void
{
busy = true;
controls.active = false;
if (_parentState != null) _parentState.persistentDraw = false;
var transitionGradient = new FlxSprite(0, 720).loadGraphic(Paths.image('freeplay/transitionGradient'));
@@ -1503,20 +1490,17 @@ class FreeplayState extends MusicBeatSubState
fadeShader.fade(0.0, 1.0, 0.8, {ease: FlxEase.quadIn});
for (grpSpr in exitMoversCharSel.keys())
{
var moveData:Null<MoveData> = exitMoversCharSel.get(grpSpr);
if (moveData == null) continue;
if (exitMoversCharSel.get(grpSpr) == null) continue;
for (spr in grpSpr)
{
if (spr == null) continue;
var funnyMoveShit:MoveData = moveData;
var moveDataY = funnyMoveShit.y ?? spr.y;
var moveDataSpeed = funnyMoveShit.speed ?? 0.2;
var moveDataWait = funnyMoveShit.wait ?? 0.0;
var moveDataY = exitMoversCharSel.get(grpSpr)?.y ?? spr.y;
var moveDataSpeed = exitMoversCharSel.get(grpSpr)?.speed ?? 0.2;
spr.y += moveDataY;
FlxTween.tween(spr, {y: spr.y - moveDataY}, moveDataSpeed * 1.2,
{
ease: FlxEase.expoOut,
@@ -1525,7 +1509,7 @@ class FreeplayState extends MusicBeatSubState
{
capsule.doLerp = true;
fromCharSelect = false;
busy = false;
controls.active = true;
}
}
});
@@ -1536,11 +1520,6 @@ class FreeplayState extends MusicBeatSubState
var spamTimer:Float = 0;
var spamming:Bool = false;
/**
* If true, disable interaction with the interface.
*/
public var busy:Bool = false;
var originalPos:FlxPoint = new FlxPoint();
var hintTimer:Float = 0;
@@ -1556,12 +1535,12 @@ class FreeplayState extends MusicBeatSubState
#if FEATURE_TOUCH_CONTROLS
if (backButton != null && !backTransitioning)
{
if (busy)
if (!controls.active)
{
backButton.animation.play("idle");
backButton.alpha = backButton.restingOpacity;
}
backButton.active = !busy;
backButton.active = controls.active;
}
#end
@@ -1591,34 +1570,37 @@ class FreeplayState extends MusicBeatSubState
newRank: PERFECT_GOLD,
songId: "tutorial",
difficultyId: "hard"
}, grpCapsules.members[curSelected]);
}, currentCapsule);
}
#end // ^<-- FEATURE_DEBUG_FUNCTIONS
if ((controls.FREEPLAY_CHAR_SELECT #if FEATURE_TOUCH_CONTROLS
|| (TouchUtil.pressAction(djHitbox, funnyCam, false) && !SwipeUtil.swipeAny) #end)
&& !busy)
&& controls.active)
{
tryOpenCharSelect();
}
if (controls.FREEPLAY_FAVORITE && !busy)
{
favoriteSong();
}
if (controls.FREEPLAY_FAVORITE && controls.active) favoriteSong();
if (controls.FREEPLAY_JUMP_TO_TOP && !busy)
{
changeSelection(-curSelected);
}
if (controls.FREEPLAY_JUMP_TO_TOP && controls.active) changeSelection(-curSelected);
if (controls.FREEPLAY_JUMP_TO_BOTTOM && !busy)
{
changeSelection(grpCapsules.countLiving() - curSelected - 1);
}
if (controls.FREEPLAY_JUMP_TO_BOTTOM && controls.active) changeSelection(grpCapsules.countLiving() - curSelected - 1);
lerpScore = MathUtil.snap(MathUtil.smoothLerpPrecision(lerpScore, intendedScore, elapsed, 0.2), intendedScore, 1);
lerpCompletion = MathUtil.snap(MathUtil.smoothLerpPrecision(lerpCompletion, intendedCompletion, elapsed, 0.5), intendedCompletion, 1 / 100);
calculateCompletion();
handleInputs(elapsed);
if (dj != null) FlxG.watch.addQuick('dj-anim', dj.getCurrentAnimation());
// If the allowPicoBulletsVibration is true, trigger vibration each update (for pico shooting bullets animation).
if (allowPicoBulletsVibration) HapticUtil.vibrate(0, 0.01, (Constants.MAX_VIBRATION_AMPLITUDE / 3) * 2.5);
}
function calculateCompletion():Void
{
lerpScore = MathUtil.snap(MathUtil.smoothLerpPrecision(lerpScore, intendedScore, FlxG.elapsed, 0.2), intendedScore, 1);
lerpCompletion = MathUtil.snap(MathUtil.smoothLerpPrecision(lerpCompletion, intendedCompletion, FlxG.elapsed, 0.5), intendedCompletion, 1 / 100);
if (Math.isNaN(lerpScore))
{
@@ -1632,7 +1614,10 @@ class FreeplayState extends MusicBeatSubState
fp.updateScore(Std.int(lerpScore));
txtCompletion.text = '${Math.floor(lerpCompletion * 100)}';
// sets the text of the completion percentage. Perhaps eventually we may want to generalize this,
// but for now we can just clamp the values between 0 and 100.
// Fixes issue where it rounds to negative integer overflow on Windows? Which occurs when switching to an unranked song?
txtCompletion.text = '${FlxMath.clamp(Math.floor(lerpCompletion * 100), 0, 100)}';
// Right align the completion percentage
switch (txtCompletion.text.length)
@@ -1646,13 +1631,6 @@ class FreeplayState extends MusicBeatSubState
default:
txtCompletion.offset.x = 0;
}
handleInputs(elapsed);
if (dj != null) FlxG.watch.addQuick('dj-anim', dj.getCurrentAnimation());
// If the allowPicoBulletsVibration is true, trigger vibration each update (for pico shooting bullets animation).
if (allowPicoBulletsVibration) HapticUtil.vibrate(0, 0.01, Constants.MAX_VIBRATION_AMPLITUDE / 3);
}
var _dragOffset:Float = 0;
@@ -1665,7 +1643,7 @@ class FreeplayState extends MusicBeatSubState
function handleInputs(elapsed:Float):Void
{
if (busy) return;
if (!controls.active) return;
final upP:Bool = controls.UI_UP_P;
final downP:Bool = controls.UI_DOWN_P;
@@ -1709,9 +1687,9 @@ class FreeplayState extends MusicBeatSubState
goBack();
}
if (accepted && !busy)
if (accepted && controls.active)
{
grpCapsules.members[curSelected].onConfirm();
currentCapsule.onConfirm();
}
}
@@ -1752,8 +1730,8 @@ class FreeplayState extends MusicBeatSubState
function handleDifficultySwitch():Void
{
#if FEATURE_TOUCH_CONTROLS
final leftPressed:Bool = controls.UI_LEFT_P || (diffSelLeft != null && TouchUtil.pressAction(diffSelLeft, funnyCam, false));
final rightPressed:Bool = controls.UI_RIGHT_P || (diffSelRight != null && TouchUtil.pressAction(diffSelRight, funnyCam, false));
final leftPressed:Bool = controls.UI_LEFT_P || TouchUtil.pressAction(diffSelLeft, funnyCam, false);
final rightPressed:Bool = controls.UI_RIGHT_P || TouchUtil.pressAction(diffSelRight, funnyCam, false);
#else
final leftPressed:Bool = controls.UI_LEFT_P;
final rightPressed:Bool = controls.UI_RIGHT_P;
@@ -1777,12 +1755,12 @@ class FreeplayState extends MusicBeatSubState
function handleDebugKeys():Void
{
#if FEATURE_CHART_EDITOR
if (busy) return;
if (!controls.active) return;
if (!controls.DEBUG_CHART) return;
busy = true;
controls.active = false;
var targetSongID = grpCapsules.members[curSelected]?.freeplayData?.data.id ?? 'unknown';
var targetSongID = currentCapsule?.freeplayData?.data.id ?? 'unknown';
if (targetSongID == 'unknown')
{
letterSort.inputEnabled = false;
@@ -1799,7 +1777,7 @@ class FreeplayState extends MusicBeatSubState
if (availableSongCapsules.length == 0)
{
trace('No songs available!');
busy = false;
controls.active = true;
letterSort.inputEnabled = true;
FunkinSound.playOnce(Paths.sound('cancelMenu'));
return;
@@ -1810,7 +1788,7 @@ class FreeplayState extends MusicBeatSubState
// Seeing if I can do an animation...
curSelected = grpCapsules.members.indexOf(targetSong);
changeSelection(0);
targetSongID = grpCapsules.members[curSelected]?.freeplayData?.data.id ?? 'unknown';
targetSongID = currentCapsule?.freeplayData?.data.id ?? 'unknown';
}
// Play the confirm animation so the user knows they actually did something.
FunkinSound.playOnce(Paths.sound('confirmMenu'));
@@ -1852,7 +1830,7 @@ class FreeplayState extends MusicBeatSubState
curSelected = i;
changeSelection(0);
FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4);
HapticUtil.vibrate(0, 0.01, 0.2);
HapticUtil.vibrate(0, 0.01, 0.5);
}
break;
}
@@ -1860,7 +1838,7 @@ class FreeplayState extends MusicBeatSubState
if (TouchUtil.justPressed)
{
final selected = grpCapsules.members[curSelected].theActualHitbox;
final selected = currentCapsule.theActualHitbox;
_pressedOnSelected = selected != null && TouchUtil.overlaps(selected, funnyCam);
}
}
@@ -1868,7 +1846,7 @@ class FreeplayState extends MusicBeatSubState
function handleTouchSelectionScroll(elapsed:Float):Void
{
if (draggingDifficulty || ControlsHandler.usingExternalInputDevice) return;
if (TouchUtil.pressAction(grpCapsules.members[curSelected].theActualHitbox, funnyCam)) return;
if (TouchUtil.pressAction(currentCapsule.theActualHitbox, funnyCam)) return;
if (TouchUtil.justPressed && TouchUtil.overlaps(capsuleHitbox, funnyCam))
{
@@ -1966,7 +1944,7 @@ class FreeplayState extends MusicBeatSubState
_flickEnded = true;
new FlxTimer().start(0.21, (afteranim) -> {
grpCapsules.members[curSelected].doLerp = true;
currentCapsule.doLerp = true;
generateSongList(currentFilter, true, false, true);
FlxG.touches.flickManager.destroy();
});
@@ -1985,7 +1963,7 @@ class FreeplayState extends MusicBeatSubState
_flickEnded = true;
new FlxTimer().start(0.21, (afteranim) -> {
grpCapsules.members[curSelected].doLerp = true;
currentCapsule.doLerp = true;
generateSongList(currentFilter, true, false, true);
FlxG.touches.flickManager.destroy();
});
@@ -2004,16 +1982,16 @@ class FreeplayState extends MusicBeatSubState
}
else
{
grpCapsules.members[curSelected].doLerp = true;
currentCapsule.doLerp = true;
}
if (busy) return;
if (!controls.active) return;
if (currentDifficultySprite == null) return;
// If we press onto our difficulty, we want to "grab" it rather than simply check if we are overlapping
if (TouchUtil.overlapsComplex(currentDifficultySprite, funnyCam) && TouchUtil.justPressed && !draggingDifficulty)
{
HapticUtil.vibrate(0, 0.01, 0.15, 0.4);
HapticUtil.vibrate(0, 0.01, 0.375, 0.4);
draggingDifficulty = true;
}
@@ -2026,7 +2004,7 @@ class FreeplayState extends MusicBeatSubState
var vibDist:Float = 5; // essentially how far the touch needs to be before it will trigger a tiny haptic feel
if (Std.int((TouchUtil.touch.x - _dragOffset) / vibDist) * vibDist != _prevRoundedDragOffset)
{
HapticUtil.vibrate(0, 0.01, 0.08, 0.8);
HapticUtil.vibrate(0, 0.01, 0.2, 0.8);
}
_prevRoundedDragOffset = Std.int((TouchUtil.touch.x - _dragOffset) / vibDist) * vibDist;
@@ -2056,8 +2034,8 @@ class FreeplayState extends MusicBeatSubState
currentDifficultySprite.offset.x = MathUtil.smoothLerpPrecision(currentDifficultySprite.offset.x, 0, FlxG.elapsed, 0.4);
}
if (diffSelRight != null) diffSelRight.setPress(TouchUtil.overlaps(diffSelRight, funnyCam) && TouchUtil.justPressed);
if (diffSelLeft != null) diffSelLeft.setPress(TouchUtil.overlaps(diffSelLeft, funnyCam) && TouchUtil.justPressed);
diffSelRight.setPress(TouchUtil.overlaps(diffSelRight, funnyCam) && TouchUtil.justPressed);
diffSelLeft.setPress(TouchUtil.overlaps(diffSelLeft, funnyCam) && TouchUtil.justPressed);
}
#end
@@ -2077,7 +2055,7 @@ class FreeplayState extends MusicBeatSubState
function goBack():Void
{
if (busy) return;
if (!controls.active) return;
backTransitioning = true;
#if FEATURE_TOUCH_CONTROLS
if (backButton != null)
@@ -2086,7 +2064,7 @@ class FreeplayState extends MusicBeatSubState
backButton.animation.play("confirm");
}
#end
busy = true;
controls.active = false;
FlxTween.globalManager.clear();
FlxTimer.globalManager.clear();
if (dj != null) dj.onIntroDone.removeAll();
@@ -2201,18 +2179,18 @@ class FreeplayState extends MusicBeatSubState
*/
function changeDiff(change:Int = 0, force:Bool = false, capsuleAnim:Bool = false):Void
{
if (busy) return;
if (!controls.active) return;
if (capsuleAnim)
{
if (grpCapsules.members[curSelected] != null)
if (currentCapsule != null)
{
busy = true;
grpCapsules.members[curSelected].doLerp = false;
controls.active = false;
currentCapsule.doLerp = false;
var movement:Float = (change > 0) ? 15 : -15;
FlxTween.tween(grpCapsules.members[curSelected], {x: grpCapsules.members[curSelected].x - movement}, 0.1, {ease: FlxEase.expoOut});
FlxTween.tween(grpCapsules.members[curSelected], {x: grpCapsules.members[curSelected].x + movement}, 0.1, {ease: FlxEase.expoIn, startDelay: 0.1});
FlxTween.tween(currentCapsule, {x: currentCapsule.x - movement}, 0.1, {ease: FlxEase.expoOut});
FlxTween.tween(currentCapsule, {x: currentCapsule.x + movement}, 0.1, {ease: FlxEase.expoIn, startDelay: 0.1});
}
}
@@ -2224,12 +2202,12 @@ class FreeplayState extends MusicBeatSubState
diff.visible = true;
final newX:Int = (change > 0) ? -320 : 500;
busy = true;
controls.active = false;
FlxTween.tween(diff, {x: newX + (CUTOUT_WIDTH * DJ_POS_MULTI)}, 0.2,
{
ease: FlxEase.circInOut,
onComplete: function(_) {
busy = false;
controls.active = true;
diff.x = 90 + (CUTOUT_WIDTH * DJ_POS_MULTI);
diff.visible = false;
}
@@ -2238,18 +2216,18 @@ class FreeplayState extends MusicBeatSubState
}
if (change != 0)
{
HapticUtil.vibrate(0, 0.01, 0.2, 0.1);
HapticUtil.vibrate(0, 0.01, 0.5, 0.1);
FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4);
}
var previousVariation:String = currentVariation;
var daSong:Null<FreeplaySongData> = grpCapsules.members[curSelected].freeplayData;
grpCapsules.members[curSelected].selected = false;
var daSong:Null<FreeplaySongData> = currentCapsule.freeplayData;
currentCapsule.selected = false;
// Available variations for current character. We get this since bf is usually `default` variation, and `pico` is `pico`
// but sometimes pico can be the default variation (weekend 1 songs), and bf can be `bf` variation (darnell)
var characterVariations:Array<String> = daSong?.data.getVariationsByCharacter(currentCharacter) ?? Constants.DEFAULT_VARIATION_LIST;
var difficultiesAvailable:Array<String> = allDifficulties;
var difficultiesAvailable:Array<String> = SongRegistry.instance.listAllDifficulties(currentCharacterId) ?? Constants.DEFAULT_DIFFICULTY_LIST_FULL;
// Gets all available difficulties for our character, via our available variations
var songDifficulties:Array<String> = daSong?.data.listDifficulties(null, characterVariations) ?? Constants.DEFAULT_DIFFICULTY_LIST;
@@ -2267,9 +2245,13 @@ class FreeplayState extends MusicBeatSubState
// For when we change the difficulty, but the song doesn't have that difficulty!
if (daSong != null && !songDifficulties.contains(difficultiesAvailable[currentDifficultyIndex]))
{
// Switch to the closest song with that difficulty.
curSelected = findClosestDiff(characterVariations, difficultiesAvailable[currentDifficultyIndex]);
daSong = grpCapsules.members[curSelected].freeplayData;
daSong = currentCapsule.freeplayData;
rememberedSongId = daSong?.data.id;
// Update the variation list for the new song.
characterVariations = daSong?.data.getVariationsByCharacter(currentCharacter) ?? Constants.DEFAULT_VARIATION_LIST;
}
for (variation in characterVariations)
@@ -2297,7 +2279,7 @@ class FreeplayState extends MusicBeatSubState
((songScore.tallies.sick + songScore.tallies.good - songScore.tallies.missed) / songScore.tallies.totalNotes));
rememberedDifficulty = currentDifficulty;
if (!capsuleAnim) generateSongList(currentFilter, false, true, true);
grpCapsules.members[curSelected].refreshDisplay((prepForNewRank == true) ? false : true);
currentCapsule.refreshDisplay((prepForNewRank == true) ? false : true);
}
else
{
@@ -2378,7 +2360,7 @@ class FreeplayState extends MusicBeatSubState
// Set difficulty star count.
albumRoll.setDifficultyStars(daSong?.data.getDifficulty(currentDifficulty, currentVariation)?.difficultyRating ?? 0);
grpCapsules.members[curSelected].selected = true; // set selected again, so it can run its getter function to initialize movement
currentCapsule.selected = true; // set selected again, so it can run its getter function to initialize movement
}
#if FEATURE_TOUCH_CONTROLS
@@ -2393,7 +2375,7 @@ class FreeplayState extends MusicBeatSubState
function handleDiffBoundaryChange(change:Int):Void
{
if (busy) return;
if (!controls.active) return;
dj?.resetAFKTimer();
changeDiff(change);
generateSongList(currentFilter, true, false);
@@ -2408,7 +2390,7 @@ class FreeplayState extends MusicBeatSubState
{
trace('RANDOM SELECTED');
busy = true;
controls.active = false;
#if NO_FEATURE_TOUCH_CONTROLS
letterSort.inputEnabled = false;
#end
@@ -2425,7 +2407,7 @@ class FreeplayState extends MusicBeatSubState
if (availableSongCapsules.length == 0)
{
trace('No songs available!');
busy = false;
controls.active = true;
#if NO_FEATURE_TOUCH_CONTROLS
letterSort.inputEnabled = true;
#end
@@ -2448,14 +2430,14 @@ class FreeplayState extends MusicBeatSubState
*/
function capsuleOnOpenDefault(cap:SongMenuItem):Void
{
busy = true;
controls.active = false;
letterSort.inputEnabled = false;
var targetSongId:String = cap?.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})');
busy = false;
controls.active = true;
letterSort.inputEnabled = true;
return;
}
@@ -2470,7 +2452,7 @@ class FreeplayState extends MusicBeatSubState
if (targetDifficulty == null)
{
FlxG.log.warn('WARN: could not find difficulty with id (${targetDifficultyId})');
busy = false;
controls.active = true;
letterSort.inputEnabled = true;
return;
}
@@ -2521,7 +2503,7 @@ class FreeplayState extends MusicBeatSubState
public function cleanupCapsuleOptionsMenu():Void
{
this.busy = false;
this.controls.active = true;
letterSort.inputEnabled = true;
if (capsuleOptionsMenu != null)
@@ -2536,7 +2518,7 @@ class FreeplayState extends MusicBeatSubState
*/
function capsuleOnConfirmDefault(cap:SongMenuItem, ?targetInstId:String):Void
{
busy = true;
controls.active = false;
#if NO_FEATURE_TOUCH_CONTROLS
letterSort.inputEnabled = false;
#end
@@ -2548,7 +2530,7 @@ class FreeplayState extends MusicBeatSubState
if (targetSongNullable == null)
{
FlxG.log.warn('WARN: could not find song with id (${targetSongId})');
busy = false;
controls.active = true;
letterSort.inputEnabled = true;
return;
}
@@ -2561,7 +2543,7 @@ class FreeplayState extends MusicBeatSubState
if (targetDifficulty == null)
{
FlxG.log.warn('WARN: could not find difficulty with id (${currentDifficulty})');
busy = false;
controls.active = true;
letterSort.inputEnabled = true;
return;
}
@@ -2576,8 +2558,8 @@ class FreeplayState extends MusicBeatSubState
FunkinSound.playOnce(Paths.sound('confirmMenu'));
if (dj != null) dj.confirm();
grpCapsules.members[curSelected].forcePosition();
grpCapsules.members[curSelected].confirm();
currentCapsule.forcePosition();
currentCapsule.confirm();
backingCard.confirm();
fadeDots(false);
@@ -2597,7 +2579,7 @@ class FreeplayState extends MusicBeatSubState
// A single vibration.
default:
HapticUtil.vibrate(Constants.DEFAULT_VIBRATION_PERIOD, Constants.DEFAULT_VIBRATION_DURATION * 5, Constants.MAX_VIBRATION_AMPLITUDE / 3);
HapticUtil.vibrate(Constants.DEFAULT_VIBRATION_PERIOD, Constants.DEFAULT_VIBRATION_DURATION * 5, (Constants.MAX_VIBRATION_AMPLITUDE / 3) * 2.5);
}
});
}
@@ -2677,7 +2659,7 @@ class FreeplayState extends MusicBeatSubState
if (curSelected != prevSelected)
{
FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4);
HapticUtil.vibrate(0, 0.01, 0.2);
HapticUtil.vibrate(0, 0.01, 0.5);
dj?.resetAFKTimer();
_pressedOnSelected = false;
}
@@ -2711,7 +2693,7 @@ class FreeplayState extends MusicBeatSubState
if (!prepForNewRank && curSelected != prevSelected) FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4);
var daSongCapsule:SongMenuItem = grpCapsules.members[curSelected];
var daSongCapsule:SongMenuItem = currentCapsule;
if (daSongCapsule.freeplayData != null)
{
var songScore:Null<SaveScoreData> = Save.instance.getSongScore(daSongCapsule.freeplayData.data.id, currentDifficulty, currentVariation);
@@ -2747,21 +2729,21 @@ class FreeplayState extends MusicBeatSubState
&& ControlsHandler.usingExternalInputDevice #end) capsule.targetPos.y -= 100; // another 100 for good measure
}
if (grpCapsules.countLiving() > 0 && !prepForNewRank && !busy)
if (grpCapsules.countLiving() > 0 && !prepForNewRank && controls.active)
{
playCurSongPreview(daSongCapsule);
grpCapsules.members[curSelected].selected = true;
currentCapsule.selected = true;
// switchBackingImage(daSongCapsule.freeplayData);
}
// Small vibrations every selection change.
if (change != 0) HapticUtil.vibrate(0, 0.01, 0.2);
if (change != 0) HapticUtil.vibrate(0, 0.01, 0.5);
}
public function playCurSongPreview(?daSongCapsule:SongMenuItem):Void
{
if (daSongCapsule == null) daSongCapsule = grpCapsules.members[curSelected];
if (daSongCapsule == null) daSongCapsule = currentCapsule;
if (curSelected == 0)
{
FunkinSound.playMusic('freeplayRandom',
@@ -2843,7 +2825,7 @@ class FreeplayState extends MusicBeatSubState
function favoriteSong():Void
{
var targetSong = grpCapsules.members[curSelected]?.freeplayData;
var targetSong = currentCapsule?.freeplayData;
if (targetSong != null)
{
var realShit:Int = curSelected;
@@ -2857,7 +2839,7 @@ class FreeplayState extends MusicBeatSubState
FunkinSound.playOnce(Paths.sound('fav'), 1);
grpCapsules.members[realShit].checkClip();
grpCapsules.members[realShit].selected = true; // set selected again, so it can run its getter function to initialize movement
busy = true;
controls.active = false;
grpCapsules.members[realShit].doLerp = false;
FlxTween.tween(grpCapsules.members[realShit], {y: grpCapsules.members[realShit].y - 5}, 0.1, {ease: FlxEase.expoOut});
@@ -2868,7 +2850,7 @@ class FreeplayState extends MusicBeatSubState
startDelay: 0.1,
onComplete: function(_) {
grpCapsules.members[realShit].doLerp = true;
busy = false;
controls.active = true;
}
});
}
@@ -2884,7 +2866,7 @@ class FreeplayState extends MusicBeatSubState
grpCapsules.members[realShit].selected = true; // set selected again, so it can run its getter function to initialize movement
});
busy = true;
controls.active = false;
grpCapsules.members[realShit].doLerp = false;
FlxTween.tween(grpCapsules.members[realShit], {y: grpCapsules.members[realShit].y + 5}, 0.1, {ease: FlxEase.expoOut});
FlxTween.tween(grpCapsules.members[realShit], {y: grpCapsules.members[realShit].y - 5}, 0.1,
@@ -2893,7 +2875,7 @@ class FreeplayState extends MusicBeatSubState
startDelay: 0.1,
onComplete: function(_) {
grpCapsules.members[realShit].doLerp = true;
busy = false;
controls.active = true;
}
});
}
@@ -2910,34 +2892,30 @@ class DifficultySelector extends FlxSprite
var controls:Controls;
var whiteShader:PureColor;
var parent:FreeplayState;
#if FEATURE_TOUCH_CONTROLS
public var pressed:Bool = false;
var pressed:Bool = false;
#end
public function new(parent:FreeplayState, x:Float, y:Float, flipped:Bool, controls:Controls, ?styleData:FreeplayStyle = null)
public function new(x:Float, y:Float, flipped:Bool, controls:Controls, ?styleData:FreeplayStyle)
{
super(x, y);
this.parent = parent;
this.controls = controls;
whiteShader = new PureColor(FlxColor.WHITE);
this.whiteShader = new PureColor(FlxColor.WHITE);
this.frames = Paths.getSparrowAtlas(styleData?.getSelectorAssetKey() ?? "freeplay/freeplaySelector");
animation.addByPrefix('shine', 'arrow pointer loop', 24);
animation.play('shine');
this.shader = whiteShader;
this.flipX = flipped;
}
override function update(elapsed:Float):Void
{
if (flipX && controls.UI_RIGHT_P && !parent.busy) moveShitDown();
if (!flipX && controls.UI_LEFT_P && !parent.busy) moveShitDown();
if (!controls.active) return;
if (flipX && controls.UI_RIGHT_P) moveShitDown();
if (!flipX && controls.UI_LEFT_P) moveShitDown();
super.update(elapsed);
}
+1 -1
View File
@@ -159,7 +159,7 @@ class LetterSort extends FlxSpriteGroup
new FlxTimer().start(2 / 24, function(_) {
arrowToMove.offset.x = 0;
});
if (playSound) FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4);
if (playSound && diff != 0) FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4);
}
/**
+15 -1
View File
@@ -40,7 +40,8 @@ class SongMenuItem extends FlxSpriteGroup
public var selected(default, set):Bool;
public var forceHighlight(default, set):Bool;
public var songText:CapsuleText;
var songText:CapsuleText;
public var favIconBlurred:FlxSprite;
public var favIcon:FlxSprite;
@@ -541,6 +542,19 @@ class SongMenuItem extends FlxSpriteGroup
checkWeek(freeplayData?.data.id);
}
public function initRandom(?styleData:FreeplayStyle = null):Void
{
initPosition(FlxG.width, 0);
initData(null, styleData, 1);
y = intendedY(0) + 10;
targetPos.x = x;
alpha = 0.5;
songText.visible = false;
favIcon.visible = false;
favIconBlurred.visible = false;
ranking.visible = false;
}
var frameInTicker:Float = 0;
var frameInTypeBeat:Int = 0;
+24 -14
View File
@@ -88,7 +88,9 @@ class MainMenuState extends MusicBeatState
transOut = FlxTransitionableState.defaultTransOut;
#if FEATURE_MOBILE_IAP
hasUpgraded = InAppPurchasesUtil.isPurchased(InAppPurchasesUtil.UPGRADE_PRODUCT_ID);
if (InAppPurchasesUtil.hasInitialized) Preferences.noAds = InAppPurchasesUtil.isPurchased(InAppPurchasesUtil.UPGRADE_PRODUCT_ID);
// If the user is faster than their shit wifi, it gets the saved noAds instead.
hasUpgraded = Preferences.noAds;
#else
// just to make sure its never accidentally turned off
hasUpgraded = true;
@@ -194,7 +196,8 @@ class MainMenuState extends MusicBeatState
});
}
if (#if mobile ControlsHandler.usingExternalInputDevice #else true #end) {
if (#if mobile ControlsHandler.usingExternalInputDevice #else true #end)
{
createMenuItem('options', 'mainmenu/options', function() {
startExitState(() -> new funkin.ui.options.OptionsState());
});
@@ -276,7 +279,8 @@ class MainMenuState extends MusicBeatState
{
addOptionsButton(35, FlxG.height - 210, function() {
if (!canInteract) return;
canInteract = false;
trace("OPTIONS: Interact complete.");
startExitState(() -> new funkin.ui.options.OptionsState());
});
}
@@ -285,9 +289,9 @@ class MainMenuState extends MusicBeatState
{
backButton.onConfirmStart.add(function():Void {
if (backButton == null) return;
backButton.active = true;
goingBack = true;
if (menuItems != null) menuItems.enabled = false;
trace('BACK: Interact Start');
});
}
@@ -295,9 +299,9 @@ class MainMenuState extends MusicBeatState
{
optionsButton.onConfirmStart.add(function():Void {
if (optionsButton == null) return;
optionsButton.active = true;
goingToOptions = true;
if (menuItems != null) menuItems.enabled = false;
trace('OPTIONS: Interact Start');
});
}
#end
@@ -390,8 +394,8 @@ class MainMenuState extends MusicBeatState
function onMenuItemChange(selected:MenuListItem)
{
if (#if mobile ControlsHandler.usingExternalInputDevice #else true #end)
camFollow.setPosition(selected.getGraphicMidpoint().x, selected.getGraphicMidpoint().y);
if (#if mobile ControlsHandler.usingExternalInputDevice #else true #end) camFollow.setPosition(selected.getGraphicMidpoint().x,
selected.getGraphicMidpoint().y);
}
#if FEATURE_OPEN_URL
@@ -422,6 +426,10 @@ class MainMenuState extends MusicBeatState
function startExitState(state:NextState):Void
{
#if mobile
if (!canInteract && !ControlsHandler.usingExternalInputDevice) return;
#end
if (menuItems != null)
{
menuItems.enabled = false; // disable for exit
@@ -441,13 +449,14 @@ class MainMenuState extends MusicBeatState
});
#if mobile
if (optionsButton != null)
FlxTween.tween(optionsButton, {alpha: 0}, duration, {ease: FlxEase.quadOut});
if (backButton != null)
FlxTween.tween(backButton, {alpha: 0}, duration, {ease: FlxEase.quadOut});
if (optionsButton != null) FlxTween.tween(optionsButton, {alpha: 0}, duration, {ease: FlxEase.quadOut});
if (backButton != null) FlxTween.tween(backButton, {alpha: 0}, duration, {ease: FlxEase.quadOut});
#end
new FlxTimer().start(duration, function(_) FlxG.switchState(state));
new FlxTimer().start(duration, function(_) {
trace('Exiting MainMenuState...');
FlxG.switchState(state);
});
}
}
@@ -480,8 +489,8 @@ class MainMenuState extends MusicBeatState
if (menuItems != null)
{
#if mobile
if (optionsButton != null) optionsButton.active = canInteract && (!menuItems.busy && !goingBack);
if (backButton != null) backButton.active = canInteract && (!menuItems.busy && !goingToOptions);
// if (optionsButton != null) optionsButton.active = canInteract && (!menuItems.busy && !goingBack);
// if (backButton != null) backButton.active = canInteract && (!menuItems.busy && !goingToOptions);
#end
if (_exiting) menuItems.enabled = false;
}
@@ -612,6 +621,7 @@ class MainMenuState extends MusicBeatState
{
if (canInteract)
{
trace("BACK: Interact complete.");
canInteract = false;
if (menuItems != null)
{
+17
View File
@@ -14,6 +14,9 @@ import funkin.ui.AtlasText;
import funkin.ui.MenuList.MenuTypedList;
import funkin.ui.TextMenuList;
import funkin.ui.Page;
#if FEATURE_TOUCH_CONTROLS
import funkin.mobile.ui.FunkinBackButton;
#end
class ControlsMenu extends Page<OptionsState.OptionsMenuPageName>
{
@@ -191,6 +194,20 @@ class ControlsMenu extends Page<OptionsState.OptionsMenuPageName>
popup.back.scrollFactor.set(0, 0);
popup.exists = false;
add(popup);
#if FEATURE_TOUCH_CONTROLS
var backButton:FunkinBackButton = new FunkinBackButton(FlxG.width - 230, FlxG.height - 200, function():Void {
if (controlGrid.enabled && deviceList != null && deviceListSelected == false)
{
goToDeviceList();
}
else if (canExit)
{
exit();
}
}, 1.0);
add(backButton);
#end
}
function createItem(x = 0.0, y = 0.0, control:Control, index:Int)
+64 -27
View File
@@ -7,6 +7,7 @@ import funkin.mobile.ui.FunkinBackButton;
#if mobile
import funkin.mobile.ui.FunkinHitbox;
import funkin.mobile.ui.FunkinHitbox.FunkinHitboxControlSchemes;
import funkin.mobile.input.ControlsHandler;
import funkin.util.TouchUtil;
#end
import funkin.input.PreciseInputManager;
@@ -241,23 +242,6 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
testStrumline.cameras = [menuCamera];
#if mobile
if (Preferences.controlsScheme == FunkinHitboxControlSchemes.Arrows)
{
final amplification:Float = (FlxG.width / FlxG.height) / (FlxG.initialWidth / FlxG.initialHeight);
final playerStrumlineScale:Float = ((FlxG.height / FlxG.width) * 1.95) * amplification;
final playerNoteSpacing:Float = ((FlxG.height / FlxG.width) * 2.8) * amplification;
testStrumline.strumlineScale.set(playerStrumlineScale, playerStrumlineScale);
testStrumline.setNoteSpacing(playerNoteSpacing);
testStrumline.width *= 2;
testStrumline.x = (FlxG.width - testStrumline.width) / 2 + Constants.STRUMLINE_X_OFFSET;
testStrumline.y = (FlxG.height - testStrumline.height) * 0.95 - Constants.STRUMLINE_Y_OFFSET;
testStrumline.y -= 10;
}
#end
testStrumline.conductorInUse = localConductor;
testStrumline.zIndex = 1001;
for (strum in testStrumline)
@@ -321,6 +305,9 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
calibrating = true;
MenuTypedList.pauseInput = true;
OptionsState.instance.drumsBG.pause();
OptionsState.instance.drumsBG.time = FlxG.sound.music.time;
OptionsState.instance.drumsBG.resume();
OptionsState.instance.drumsBG.fadeIn(1, 0, 1);
canExit = false;
differences = [];
@@ -345,30 +332,75 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
testStrumline.noteData = [];
testStrumline.nextNoteIndex = 0;
OptionsState.instance.drumsBG.pause();
OptionsState.instance.drumsBG.time = FlxG.sound.music.time;
OptionsState.instance.drumsBG.resume();
localConductor.update(FlxG.sound.music.time, true);
var floored = Math.floor(localConductor.currentBeatTime);
arrowBeat = floored - (floored % 4);
arrowBeat += 4;
_lastDirection = 0;
if (arrowBeat - localConductor.currentBeatTime < 4) arrowBeat += 4;
var diffBeats = Math.floor(arrowBeat - localConductor.currentBeatTime);
trace('Testing strumline at beat: ' + arrowBeat);
trace('Prestart arrowBeat: ' + arrowBeat);
if (diffBeats < 4) arrowBeat += (arrowBeat % 4) + 4; // Ensure we have at least 4 beats to test.
trace('Testing strumline at beat: ' + arrowBeat + ' diff: ' + diffBeats);
jumpInText.text = 'Hit the notes as they come in!';
#if mobile
if (OptionsState.instance.hitbox != null) OptionsState.instance.hitbox.visible = true;
if (Preferences.controlsScheme == FunkinHitboxControlSchemes.Arrows && !ControlsHandler.usingExternalInputDevice)
{
final amplification:Float = (FlxG.width / FlxG.height) / (FlxG.initialWidth / FlxG.initialHeight);
final playerStrumlineScale:Float = ((FlxG.height / FlxG.width) * 1.95) * amplification;
final playerNoteSpacing:Float = ((FlxG.height / FlxG.width) * 2.8) * amplification;
testStrumline.strumlineScale.set(playerStrumlineScale, playerStrumlineScale);
testStrumline.setNoteSpacing(playerNoteSpacing);
testStrumline.width *= 2;
testStrumline.x = (FlxG.width - testStrumline.width) / 2 + Constants.STRUMLINE_X_OFFSET;
testStrumline.y = (FlxG.height - testStrumline.height) * 0.95 - Constants.STRUMLINE_Y_OFFSET;
testStrumline.y -= 10;
}
else
{
if (testStrumline != null)
{
testStrumline.destroy();
remove(testStrumline);
}
testStrumline = new Strumline(noteStyle, true);
// center
testStrumline.setPosition(FlxG.width / 2, FlxG.height / 2);
testStrumline.x -= testStrumline.width / 2;
testStrumline.scrollFactor.set(0, 0);
add(testStrumline);
}
#end
MenuTypedList.pauseInput = true;
OptionsState.instance.drumsBG.fadeIn(1, 0, 1);
canExit = false;
differences = [];
#if !mobile
testStrumline.y = Preferences.downscroll ? FlxG.height - (testStrumline.height + 45) - Constants.STRUMLINE_Y_OFFSET : (testStrumline.height / 2)
- Constants.STRUMLINE_Y_OFFSET;
#end
jumpInText.y = testStrumline.y + 175;
#if !mobile if (Preferences.downscroll) #end jumpInText.y = testStrumline.y - 175;
#if mobile
if (Preferences.controlsScheme != FunkinHitboxControlSchemes.Arrows || ControlsHandler.usingExternalInputDevice)
{
#end
testStrumline.y = Preferences.downscroll ? FlxG.height - (testStrumline.height + 45) - Constants.STRUMLINE_Y_OFFSET : (testStrumline.height / 2)
- Constants.STRUMLINE_Y_OFFSET;
if (Preferences.downscroll) jumpInText.y = testStrumline.y - 175;
testStrumline.isDownscroll = Preferences.downscroll;
#if mobile
}
#end
});
PreciseInputManager.instance.onInputPressed.add(onKeyPress);
PreciseInputManager.instance.onInputReleased.add(onKeyRelease);
@@ -536,12 +568,16 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
// Resync logic
var diff:Float = Math.abs((FlxG.sound.music.time + localConductor.combinedOffset) - localConductor.songPosition);
if (diff > 50)
var diffBg:Float = Math.abs(FlxG.sound.music.time - OptionsState.instance.drumsBG.time);
if (diff > 50 || diffBg > 50)
{
trace('Resyncing conductor: ' + diff + 'ms difference');
trace('Resyncing conductor: ' + (diff > diffBg ? diff : diffBg) + 'ms difference');
// If the difference is greater than 50ms, we resync the conductor.
localConductor.update(FlxG.sound.music.time, true);
OptionsState.instance.drumsBG.pause();
OptionsState.instance.drumsBG.time = FlxG.sound.music.time;
OptionsState.instance.drumsBG.resume();
b = localConductor.currentBeatTime;
_lastBeat = b;
}
@@ -690,7 +726,7 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
var data:SongNoteData = new SongNoteData(arrowBeat * msPerBeat, _lastDirection, 0, null, null);
testStrumline.addNoteData(data, false);
if (arrowBeat % 8 == 0)
if (Math.floor(arrowBeat % 8) == 0)
{
var data:SongNoteData = new SongNoteData(arrowBeat * msPerBeat, 2, 0, null, null);
testStrumline.addNoteData(data, false);
@@ -698,6 +734,7 @@ class OffsetMenu extends Page<OptionsState.OptionsMenuPageName>
_lastDirection = (_lastDirection + 1) % 4; // Cycle through directions 0-3
}
if (b >= 124 && _lastDirection != 0) _lastDirection = 0; // reset direction on loop
}
// Remove arrows and what not for when we are exiting calibration/testing
else
+8
View File
@@ -194,6 +194,11 @@ class OptionsMenu extends Page<OptionsMenuPageName>
InAppPurchasesUtil.restorePurchases();
});
#end
#if android
createItem("OPEN DATA FOLDER", function() {
funkin.mobile.external.android.DataFolderUtil.openDataFolder();
});
#end
#if FEATURE_NEWGROUNDS
if (NewgroundsClient.instance.isLoggedIn())
{
@@ -252,6 +257,9 @@ class OptionsMenu extends Page<OptionsMenuPageName>
onMenuChange(items.members[0]);
items.selectItem(OptionsState.rememberedSelectedIndex);
#if FEATURE_TOUCH_CONTROLS
FlxG.touches.swipeThreshold.y = 100;
#end
}
function onMenuChange(selected:TextMenuItem):Void
+5 -2
View File
@@ -135,6 +135,9 @@ class PreferencesMenu extends Page<OptionsState.OptionsMenuPageName>
case HapticsMode.NONE: "None";
default: "All";
});
createPrefItemNumber('Haptics Intensity', 'Multiplier of intensity for all the haptic feedback effects.', function(value:Float) {
Preferences.hapticsIntensityMultiplier = value;
}, null, Preferences.hapticsIntensityMultiplier, 0.1, 5, 0.1, 1);
#end
createPrefItemCheckbox('Flashing Lights', 'If disabled, it will dampen flashing effects. Useful for people with photosensitive epilepsy.',
function(value:Bool):Void {
@@ -267,8 +270,8 @@ class PreferencesMenu extends Page<OptionsState.OptionsMenuPageName>
* @param step The value to increment/decrement by (default = 0.1)
* @param precision Rounds decimals up to a `precision` amount of digits (ex: 4 -> 0.1234, 2 -> 0.12)
*/
function createPrefItemNumber(prefName:String, prefDesc:String, onChange:Float->Void, ?valueFormatter:Float->String, defaultValue:Int, min:Int, max:Int,
step:Float = 0.1, precision:Int):Void
function createPrefItemNumber(prefName:String, prefDesc:String, onChange:Float->Void, ?valueFormatter:Float->String, defaultValue:Float, min:Float,
max:Float, step:Float = 0.1, precision:Int):Void
{
var item = new NumberPreferenceItem(funkin.ui.FullScreenScaleMode.gameNotchSize.x, (120 * items.length) + 30, prefName, defaultValue, min, max, step,
precision, onChange, valueFormatter);
@@ -80,8 +80,8 @@ class NumberPreferenceItem extends TextMenuItem
changeRateTimer -= elapsed;
}
var jpLeft:Bool = controls().UI_LEFT_P #if mobile || SwipeUtil.justSwipedLeft #end;
var jpRight:Bool = controls().UI_RIGHT_P #if mobile || SwipeUtil.justSwipedRight #end;
var jpLeft:Bool = controls().UI_LEFT_P #if FEATURE_TOUCH_CONTROLS || SwipeUtil.justSwipedLeft #end;
var jpRight:Bool = controls().UI_RIGHT_P #if FEATURE_TOUCH_CONTROLS || SwipeUtil.justSwipedRight #end;
if (jpLeft || jpRight)
{
@@ -93,21 +93,23 @@ class NumberPreferenceItem extends TextMenuItem
var shouldIncrease:Bool = jpRight;
var valueChangeMultiplier:Float = 1;
#if FEATURE_TOUCH_CONTROLS
final dragThreshold:Float = 24 / elapsed / 100;
if (TouchUtil.touch != null && (TouchUtil.touch.deltaViewX <= -dragThreshold || TouchUtil.touch.deltaViewX >= dragThreshold))
{
valueChangeMultiplier = dragStepMultiplier;
}
#end
if (holdDelayTimer <= 0.0 && changeRateTimer <= 0.0)
{
if (controls().UI_LEFT || (TouchUtil.touch != null && TouchUtil.touch.deltaX <= -dragThreshold))
if (controls().UI_LEFT #if FEATURE_TOUCH_CONTROLS || (TouchUtil.touch != null && TouchUtil.touch.deltaX <= -dragThreshold) #end)
{
shouldDecrease = true;
changeRateTimer = CHANGE_RATE;
}
else if (controls().UI_RIGHT || (TouchUtil.touch != null && TouchUtil.touch.deltaX >= dragThreshold))
else if (controls().UI_RIGHT #if FEATURE_TOUCH_CONTROLS || (TouchUtil.touch != null && TouchUtil.touch.deltaX >= dragThreshold) #end)
{
shouldIncrease = true;
changeRateTimer = CHANGE_RATE;
@@ -40,6 +40,8 @@ class FunkinPreloader extends FlxBasePreloader
static final BAR_HEIGHT:Int = 12;
static final PIECES_COUNT:Int = 16;
/**
* Display takes this long (in seconds) to fade in.
*/
@@ -111,7 +113,7 @@ class FunkinPreloader extends FlxBasePreloader
var vfdShader:VFDOverlay;
var vfdBitmap:Bitmap;
var box:Sprite;
var rTextGroup:Sprite;
var progressLines:Sprite;
public function new()
@@ -131,29 +133,41 @@ class FunkinPreloader extends FlxBasePreloader
Lib.current.stage.color = Constants.COLOR_PRELOADER_BG;
// Width and height of the preloader.
// Reference: Mobile resolution actually spits out smaller number
// this._width is 893 on iPhone 14 Pro
// and this._height is 393
// so a few lines lower
// ratio = 893 / 1280 / 2.0 = ~0.3 on iPhone
// ratio = 1280 / 1280 / 2.0 = 0.5 on desktop
// However on Android, this._width/_height are the devices actual resolution.
this._width = Lib.current.stage.stageWidth;
this._height = Lib.current.stage.stageHeight;
trace('Preloader size: ' + this._width + 'x' + this._height);
// Scale assets to the screen size.
ratio = this._width / BASE_WIDTH / 2.0;
// Desktop is always 1:1 scale, mobile needs DPI normalization for consistent positioning
#if mobile
var display = lime.system.System.getDisplay(0);
var dpiScale = display.dpi / 160.0; // 160 is Android's baseline DPI
var normalizedWidth = this._width / dpiScale;
ratio = normalizedWidth / BASE_WIDTH;
#else
ratio = 1.0; // Desktop is always 1:1 scale
#end
var amountOfPieces:Int = 16;
progressBarPieces = [];
var maxBarWidth:Float = this._width - BAR_PADDING * 2;
var pieceWidth:Float = maxBarWidth / amountOfPieces;
var pieceWidth:Float = maxBarWidth / PIECES_COUNT;
var pieceGap:Int = 8;
progressLines = new openfl.display.Sprite();
progressLines.graphics.lineStyle(2, Constants.COLOR_PRELOADER_BAR);
progressLines.graphics.drawRect(-2, this._height - BAR_PADDING - BAR_HEIGHT - 208, this._width + 4, 30);
progressLines.graphics.drawRect(-2, 0, this._width + 4, 30);
progressLines.y = this._height * 0.67;
addChild(progressLines);
var progressBarPiece:Sprite = new Sprite();
progressBarPiece.graphics.beginFill(Constants.COLOR_PRELOADER_BAR);
progressBarPiece.graphics.drawRoundRect(0, 0, pieceWidth - pieceGap, BAR_HEIGHT, 4, 4);
progressBarPiece.graphics.endFill();
for (i in 0...amountOfPieces)
for (i in 0...PIECES_COUNT)
{
var piece:Sprite = new Sprite();
piece.graphics.beginFill(Constants.COLOR_PRELOADER_BAR);
@@ -161,91 +175,70 @@ class FunkinPreloader extends FlxBasePreloader
piece.graphics.endFill();
piece.x = i * (piece.width + pieceGap);
piece.y = this._height - BAR_PADDING - BAR_HEIGHT - 200;
piece.y = progressLines.y + 8;
addChild(piece);
progressBarPieces.push(piece);
}
// Create the progress message.
progressLeftText = new TextField();
dspText = new TextField();
fnfText = new TextField();
enhancedText = new TextField();
stereoText = new TextField();
var progressLeftTextFormat = new TextFormat("DS-Digital", 32, Constants.COLOR_PRELOADER_BAR, true);
var progressLeftTextFormat:TextFormat = new TextFormat("DS-Digital", Std.int(32 * ratio), Constants.COLOR_PRELOADER_BAR, true);
progressLeftTextFormat.align = TextFormatAlign.LEFT;
progressLeftText.defaultTextFormat = progressLeftTextFormat;
progressLeftText.selectable = false;
progressLeftText.width = this._width - BAR_PADDING * 2;
progressLeftText.text = 'Downloading assets...';
progressLeftText.x = BAR_PADDING;
progressLeftText.y = this._height - BAR_PADDING - BAR_HEIGHT - 290;
// progressLeftText.shader = new VFDOverlay();
addChild(progressLeftText);
// Create the progress %.
progressRightText = new TextField();
var progressRightTextFormat = new TextFormat("DS-Digital", 16, Constants.COLOR_PRELOADER_BAR, true);
var progressRightTextFormat:TextFormat = new TextFormat("DS-Digital", 16, Constants.COLOR_PRELOADER_BAR, true);
progressRightTextFormat.align = TextFormatAlign.RIGHT;
progressRightText.defaultTextFormat = progressRightTextFormat;
progressRightText.selectable = false;
progressRightText.width = this._width - BAR_PADDING * 2;
progressRightText.text = '0%';
progressRightText.x = BAR_PADDING;
progressRightText.y = this._height - BAR_PADDING - BAR_HEIGHT - 16 - 4;
progressLeftText = makeText(BAR_PADDING * ratio, progressLines.y, 'Downloading assets...', Constants.COLOR_PRELOADER_BAR);
progressLeftText.defaultTextFormat = progressLeftTextFormat;
progressLeftText.width = this._width - BAR_PADDING * 2;
addChild(progressLeftText);
progressLeftText.y -= (progressLeftText.textHeight / ratio) * 2.5;
if (!isLandscapeFlipped()) progressLeftText.x += ScreenUtil.getNotchRect().width * ratio;
// Create the progress % in the bottom right
// This displays in the bottom right corner, so it's generally safe from notches...
// but we should do a sweep online to make sure that there's no hole-punch style cameras on android that may block this
progressRightText = makeText(BAR_PADDING, this._height - BAR_PADDING - BAR_HEIGHT - 16 - 4, '0%', Constants.COLOR_PRELOADER_BAR);
progressRightText.defaultTextFormat = progressRightTextFormat;
addChild(progressRightText);
box = new Sprite();
box.graphics.beginFill(Constants.COLOR_PRELOADER_BAR, 1);
box.graphics.drawRoundRect(0, 0, 64, 20, 5, 5);
box.graphics.drawRoundRect(70, 0, 58, 20, 5, 5);
box.graphics.endFill();
box.graphics.beginFill(Constants.COLOR_PRELOADER_BAR, 0.1);
box.graphics.drawRoundRect(0, 0, 128, 20, 5, 5);
box.graphics.endFill();
box.x = this._width - BAR_PADDING - BAR_HEIGHT - 432;
box.y = this._height - BAR_PADDING - BAR_HEIGHT - 244;
addChild(box);
// note: on mobile we generally dont want to scale these texts down
// however should test on android + iPad to see how it fits!
rTextGroup = new Sprite();
rTextGroup.graphics.beginFill(Constants.COLOR_PRELOADER_BAR, 1);
rTextGroup.graphics.drawRoundRect(0, 40, 64, 20, 5, 5);
rTextGroup.graphics.drawRoundRect(70, 40, 58, 20, 5, 5);
rTextGroup.graphics.endFill();
rTextGroup.graphics.beginFill(Constants.COLOR_PRELOADER_BAR, 0.1);
rTextGroup.graphics.drawRoundRect(0, 40, 128, 20, 5, 5);
rTextGroup.graphics.endFill();
rTextGroup.x = this._width * 0.64;
rTextGroup.y = progressLeftText.y;
addChild(rTextGroup);
dspText.selectable = false;
dspText.textColor = 0x000000;
dspText = makeText(10, 33, 'DSP', 0x000000);
dspText.width = this._width;
dspText.height = 30;
dspText.text = 'DSP';
dspText.x = 10;
dspText.y = -7;
box.addChild(dspText);
rTextGroup.addChild(dspText);
fnfText.selectable = false;
fnfText.textColor = 0x000000;
fnfText = makeText(78, 33, 'FNF', 0x000000);
fnfText.width = this._width;
fnfText.height = 30;
fnfText.x = 78;
fnfText.y = -7;
fnfText.text = 'FNF';
box.addChild(fnfText);
rTextGroup.addChild(fnfText);
enhancedText.selectable = false;
enhancedText.textColor = Constants.COLOR_PRELOADER_BAR;
enhancedText = makeText(-100, 40, 'ENHANCED', Constants.COLOR_PRELOADER_BAR);
enhancedText.width = this._width;
enhancedText.height = 100;
enhancedText.text = 'ENHANCED';
enhancedText.x = -100;
enhancedText.y = 0;
box.addChild(enhancedText);
rTextGroup.addChild(enhancedText);
stereoText.selectable = false;
stereoText.textColor = Constants.COLOR_PRELOADER_BAR;
stereoText = makeText(0, 0, 'STEREO', Constants.COLOR_PRELOADER_BAR);
stereoText.width = this._width;
stereoText.height = 100;
stereoText.text = 'STEREO';
stereoText.x = 0;
stereoText.y = -40;
box.addChild(stereoText);
rTextGroup.addChild(stereoText);
// todo: check if these actually overlap the notch with some rect check thing
// im making more sweeping assumptions rn because i only have iOS
if (isLandscapeFlipped()) rTextGroup.x -= ScreenUtil.getNotchRect().width * ratio;
vfdBitmap = new Bitmap(new BitmapData(this._width, this._height, true, 0xFFFFFFFF));
addChild(vfdBitmap);
@@ -270,6 +263,18 @@ class FunkinPreloader extends FlxBasePreloader
#end
}
function makeText(txtX:Float, txtY:Float, txt:String, color:Int):TextField
{
var text:TextField = new TextField();
text.selectable = false;
text.width = this._width - BAR_PADDING * 2;
text.x = txtX;
text.y = txtY;
text.text = txt;
text.textColor = color;
return text;
}
var lastElapsed:Float = 0.0;
override function update(percent:Float):Void
@@ -830,7 +835,7 @@ class FunkinPreloader extends FlxBasePreloader
}
#end
static final TOTAL_STEPS:Int = 11;
public static final TOTAL_STEPS:Int = 11;
static final ELLIPSIS_TIME:Float = 0.5;
function updateGraphics(percent:Float, elapsed:Float):Void
@@ -853,14 +858,10 @@ class FunkinPreloader extends FlxBasePreloader
else
{
// Render progress bar
var maxWidth = this._width - BAR_PADDING * 2;
var barWidth = maxWidth * percent;
var piecesToRender:Int = Std.int(percent * progressBarPieces.length);
for (i => piece in progressBarPieces)
{
piece.alpha = i <= piecesToRender ? 0.9 : 0.1;
}
}
// Cycle ellipsis count to show loading
@@ -876,7 +877,7 @@ class FunkinPreloader extends FlxBasePreloader
var percentage:Int = Math.floor(percent * 100);
progressRightText.text = '$percentage%';
trace('Preloader state: ' + currentState + ' (' + percentage + '%, ' + elapsed + 's)');
if (currentState.getProgressLeftText() != null) trace('Preloader state: ' + currentState + ' (' + percentage + '%, ' + elapsed + 's)');
super.update(percent);
}
@@ -889,9 +890,8 @@ class FunkinPreloader extends FlxBasePreloader
else if (progressLeftText.text != text)
{
// We have to keep updating the text format, because the font can take a frame or two to load.
var progressLeftTextFormat:TextFormat = new TextFormat("DS-Digital", 32, Constants.COLOR_PRELOADER_BAR, true);
progressLeftTextFormat.align = TextFormatAlign.LEFT;
progressLeftText.defaultTextFormat = progressLeftTextFormat;
progressLeftText.defaultTextFormat = new TextFormat("DS-Digital", 32, Constants.COLOR_PRELOADER_BAR, true);
progressLeftText.defaultTextFormat.align = TextFormatAlign.LEFT;
progressLeftText.text = text;
dspText.defaultTextFormat = new TextFormat("Quantico", 20, 0x000000, false);
@@ -911,6 +911,16 @@ class FunkinPreloader extends FlxBasePreloader
}
}
/**
* Whether or not we are in flipped landscape device rotation,
* generally for mobile to accomodate the device notch!
* @return Bool
*/
function isLandscapeFlipped():Bool
{
return lime.system.System.getDisplayOrientation(0) == DISPLAY_ORIENTATION_LANDSCAPE_FLIPPED;
}
function immediatelyStartGame():Void
{
_loaded = true;
@@ -930,11 +940,7 @@ class FunkinPreloader extends FlxBasePreloader
// Fade out progress bar too.
progressLeftText.alpha = alphaToFade;
progressRightText.alpha = alphaToFade;
box.alpha = alphaToFade;
dspText.alpha = alphaToFade;
fnfText.alpha = alphaToFade;
enhancedText.alpha = alphaToFade;
stereoText.alpha = alphaToFade;
rTextGroup.alpha = alphaToFade;
progressLines.alpha = alphaToFade;
for (piece in progressBarPieces)
@@ -960,7 +966,7 @@ class FunkinPreloader extends FlxBasePreloader
}
}
enum abstract FunkinPreloaderState(Int) to Int
enum abstract FunkinPreloaderState(String) to String
{
/**
* The state before downloading has begun.
@@ -1037,12 +1043,14 @@ enum abstract FunkinPreloaderState(Int) to Int
/**
* Formats the status text for progress bar display.
* @param steps The total number of steps
* @param suffix What to append to the end of the text, usually those dynamic ellipsis
* @param steps The total number of steps. Defaults to `FunkinPreloader.TOTAL_STEPS`.
* @param suffix What to append to the end of the text, usually those dynamic ellipsis. Defaults to an empty string.
* @return String 'Loading \n0/$steps $suffix' for example
*/
public function getProgressLeftText(steps:Int, suffix:String):String
public function getProgressLeftText(?steps:Int, ?suffix:String):String
{
steps = steps ?? FunkinPreloader.TOTAL_STEPS;
suffix = suffix ?? '';
switch (this)
{
case NotStarted:
+1 -1
View File
@@ -18,7 +18,7 @@ class CLIUtil
var cwd:String = Path.addTrailingSlash(Sys.getCwd());
var gameDir:String = '';
#if android
gameDir = Path.addTrailingSlash(extension.androidtools.os.Build.VERSION.SDK_INT > 30 ? extension.androidtools.content.Context.getObbDir() : extension.androidtools.content.Context.getExternalFilesDir());
gameDir = Path.addTrailingSlash(extension.androidtools.content.Context.getExternalFilesDir());
#elseif ios
// Why? Because for some reason lime.system.System.documentsDirectory is returning a directory that's different and we're unable to read or write from, so it's disabled and no solution is found...
trace('[WARN]: Reseting the Current Working Directory is unavailable on iOS targets');
+1 -1
View File
@@ -614,7 +614,7 @@ class Constants
/**
* Min vibration amplitude.
*/
public inline static final MIN_VIBRATION_AMPLITUDE:Float = 0.1;
public inline static final MIN_VIBRATION_AMPLITUDE:Float = 0.25;
/**
* Default vibration amplitude.
+1 -5
View File
@@ -44,11 +44,7 @@ class HapticUtil
final hapticsModes:Array<HapticsMode> = targetHapticsModes ?? [HapticsMode.ALL];
if (!hapticsModes.contains(Preferences.hapticsMode)) return;
#if ios
final amplitudeValue = FlxMath.bound(amplitude * 2.5, 0, Constants.MAX_VIBRATION_AMPLITUDE);
#else
final amplitudeValue = amplitude;
#end
final amplitudeValue = FlxMath.bound(amplitude * Preferences.hapticsIntensityMultiplier, 0, Constants.MAX_VIBRATION_AMPLITUDE);
if (period > 0)
{
-3
View File
@@ -10,9 +10,6 @@ class AnsiTrace
*/
public static function trace(v:Dynamic, ?info:haxe.PosInfos)
{
#if (NO_FEATURE_LOG_TRACE && !FEATURE_DEBUG_FUNCTIONS)
return;
#end
var str = formatOutput(v, info);
#if FEATURE_DEBUG_TRACY
cpp.vm.tracy.TracyProfiler.message(str, flixel.util.FlxColor.WHITE);
@@ -4,6 +4,9 @@ import flixel.FlxG;
import flixel.FlxBasic;
import funkin.ui.MusicBeatState;
import funkin.ui.MusicBeatSubState;
#if android
import funkin.mobile.external.android.CallbackUtil;
#end
/**
* A plugin which adds functionality to press `F5` to reload all game assets, then reload the current state.
@@ -15,6 +18,10 @@ class ReloadAssetsDebugPlugin extends FlxBasic
public function new()
{
super();
#if android
CallbackUtil.onActivityResult.add(onActivityResult);
#end
}
public static function initialize():Void
@@ -32,20 +39,44 @@ class ReloadAssetsDebugPlugin extends FlxBasic
if (FlxG.keys.justPressed.F5)
#end
{
var state:Dynamic = FlxG.state;
if (state is MusicBeatState || state is MusicBeatSubState) state.reloadAssets();
else
{
funkin.modding.PolymodHandler.forceReloadAssets();
// Create a new instance of the current state, so old data is cleared.
FlxG.resetState();
}
reload();
}
}
public override function destroy():Void
{
super.destroy();
#if android
if (CallbackUtil.onActivityResult.has(onActivityResult))
{
CallbackUtil.onActivityResult.remove(onActivityResult);
}
#end
}
@:noCompletion
function reload():Void
{
var state:Dynamic = FlxG.state;
if (state is MusicBeatState || state is MusicBeatSubState) state.reloadAssets();
else
{
funkin.modding.PolymodHandler.forceReloadAssets();
// Create a new instance of the current state, so old data is cleared.
FlxG.resetState();
}
}
#if android
@:noCompletion
function onActivityResult(resultCode:Int, requestCode:Int):Void
{
if (resultCode == CallbackUtil.DATA_FOLDER_CLOSED)
{
reload();
}
}
#end
}