diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md index efa81b56d..82c121b5a 100644 --- a/.github/ISSUE_TEMPLATE/bug.md +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -5,40 +5,46 @@ title: 'Bug Report: [DESCRIBE YOUR BUG IN DETAIL HERE]' labels: bug --- -[weed]: <> (FILL THIS ISSUE THING OUT AS MUCH AS POSSIBLE) -[weed]: <> (OR ELSE YOUR ISSUE WILL BE LESS LIKELY TO BE SOLVED!) -[weed]: <> (DO NOT POST ABOUT ISSUES FROM OTHER FNF MOD ENGINES! I CANNOT AND PROBABLY WON'T SOLVE THOSE!) -[weed]: <> (GO TO THEIR RESPECTIVE GITHUB ISSUES AND REPORT THEM THERE LOL!) + -- [ ] Google Chrome (or chomium based like Brave, vivaldi, MS Edge) -- [ ] Firefox -- [ ] Safari +## Describe the bug + -## What version of the game are you using? Look in the bottom left corner of the main menu. (ex: 0.2.7, 0.2.1, shit like that) +## To Reproduce + +## Expected behavior + +## Screenshots/Video + -## Have you identified any steps to reproduce the bug? If so, please describe them below in as much detail as possible. Use images if possible. +## Desktop + - OS: + + - Browser + + - Version: + -## Please describe your issue. Provide extensive detail and images if possible. +## Additional context + - - -## If you're game is FROZEN and you're playing a web version, press F12 to open up browser dev window, and go to console, and copy-paste whatever red error you're getting + diff --git a/.github/ISSUE_TEMPLATE/compiling.md b/.github/ISSUE_TEMPLATE/compiling.md deleted file mode 100644 index 14aea44a7..000000000 --- a/.github/ISSUE_TEMPLATE/compiling.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -name: Compiling help -about: If you need help compiling the game, and you're running into issues. (Look through the 'compiling help' label in case it's been solved!) -title: 'Compiling help: [BRIEF DESCRIPTION / ERROR MESSAGE OUTPUT]' -labels: compiling help ---- - -[weed]: <> (FILL THIS ISSUE THING OUT AS MUCH AS POSSIBLE) -[weed]: <> (OR ELSE YOUR ISSUE WILL BE LESS LIKELY TO BE SOLVED!) -[weed]: <> (DO NOT POST ABOUT ISSUES FROM OTHER FNF MOD ENGINES! I CANNOT AND PROBABLY WON'T SOLVE THOSE!) -[weed]: <> (GO TO THEIR RESPECTIVE GITHUB ISSUES AND REPORT THEM THERE LOL!) - -#### Please check for duplicates or similar compiler issues by filtering for 'compiler help' - -[weed]: <> (Put an X in the [ ] thingies to fill out checkbox!) -[weed]: <> (something like [x] pretty much, don't screw up or you will look stupid) - - -- [ ] Windows -- [ ] Mac -- [ ] Linux -- [ ] HTML5 - -## Please describe your issue. Provide extensive detail and images if possible. - diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md deleted file mode 100644 index a257c217a..000000000 --- a/.github/ISSUE_TEMPLATE/question.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -name: Question -about: Ask a general question -title: 'Question: ' -labels: question ---- - -[weed]: <> (This isn't a place for AMA type questions, if you want to ask any of the devs something, reach out to them on twitter prob ) -[weed]: <> (any biz bullshit can go to cameron.taylor.ninja@gmail.com) - -#### Please check for duplicates or similar issues before asking your question. -## What is your question? diff --git a/CHANGELOG.md b/CHANGELOG.md index e44005ffa..78391c275 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,19 @@ 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.4.0] - 2024-05-?? +## [0.4.1] +### Changed +- Tweaked the chart for Lit Up some more to fix some offset notes. +### Fixed +- Bug where Dadbattle shows up as Dadbattle Erect when returning to freeplay +- Fixed 2Hot not appearing under the "#" category in Freeplay menu +- Fixed a bug where the Chart Editor would crash when attempting to select an event with the Event toolbox open +- Improved offsets for Pico and Tankman opponents so they don't slide around as much. +- Fixed an issue where M.I.L.F. displayed the wrong difficulty rating in the Freeplay menu. + + + +## [0.4.0] - 2024-06-06 ### Added - 2 new Erect remixes, Eggnog and Satin Panties. Check them out from the Freeplay menu! - Major visual improvements to the Results screen, with additional animations and audio based on your performance. @@ -12,7 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Freeplay now plays a preview of songs when you hover over them. - Added a Charter field to the chart format, to allow for crediting the creator of a level's chart. - You can see who charted a song from the Pause menu. -- Added a new Scroll Speed chart event to change the note speed mid-song (thanks ) +- Added a new Scroll Speed chart event to change the note speed mid-song (thanks burgerballs!) ### Changed - Tweaked the charts for several songs: - Tutorial (increased the note speed slightly) @@ -27,14 +39,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Stress - Lit Up - Favorite songs marked in Freeplay are now stored between sessions. +- The Freeplay easter eggs are now easier to see. - In the event that the game cannot load your save data, it will now perform a backup before clearing it, so that we can try to repair it in the future. - Custom note styles are now properly supported for songs; add new notestyles via JSON, then select it for use from the Chart Editor Metadata toolbox. (thanks Keoiki!) - Health icons now support a Winning frame without requiring a spritesheet, simply include a third frame in the icon file. (thanks gamerbross!) - Remember that for more complex behaviors such as animations or transitions, you should use an XML file to define each frame. +- Improved the Event Toolbox in the Chart Editor; dropdowns are now bigger, include search field, and display elements in alphabetical order rather than a random order. ### Fixed +- Fixed an issue where Nene's visualizer would not play on Desktop builds - Fixed a bug where the game would silently fail to load saves on HTML5 - Fixed some bugs with the props on the Story Menu not bopping properly -- Improved offsets for Pico and Tankman opponents so they don't slide around as much. - Additional fixes to the Loading bar on HTML5 (thanks lemz1!) - Fixed several bugs with the TitleState, including missing music when returning from the Main Menu (thanks gamerbross!) - Fixed a camera bug in the Main Menu (thanks richTrash21!) @@ -49,6 +63,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Made improvements to compiling documentation (thanks gedehari!) - Fixed a crash on Linux caused by an old version of hxCodec (thanks Noobz4Life!) - Optimized animation handling for characters (thanks richTrash21!) +- Made improvements to compiling documentation (thanks gedehari!) +- Fixed a bug where pressing the volume keys would stop the Toy commercial (thanks gamerbross!) +- Fixed a bug where the Chart Editor Playtest would crash when losing (thanks gamerbross!) +- Removed a large number of unused imports to optimize builds (thanks Ethan-makes-music!) +- Fixed a bug where hold notes would be positioned wrong on downscroll (thanks MaybeMaru!) +- Additional fixes to the Loading bar on HTML5 (thanks lemz1!) +- Fixed a crash in Freeplay caused by a level referencing an invalid song (thanks gamerbross!) +- Improved debug logging for unscripted stages (thanks gamerbross!) +- Fixed a bug where changing difficulties in Story mode wouldn't update the score (thanks sectorA!) +- Fixed an issue where the Chart Editor would use an incorrect instrumental on imported Legacy songs (thanks gamerbross!) +- Fixed a camera bug in the Main Menu (thanks richTrash21!) +- Fixed several bugs with the TitleState, including missing music when returning from the Main Menu (thanks gamerbross!) +- Fixed a bug where opening the game from the command line would crash the preloader (thanks NotHyper474!) +- Fixed a bug where hold notes would display improperly in the Chart Editor when downscroll was enabled for gameplay (thanks gamerbross!) +- Fixed a bug where characters would sometimes use the wrong scale value (thanks PurSnake!) - Additional bug fixes and optimizations. ## [0.3.3] - 2024-05-14 diff --git a/README.md b/README.md index 7b7032a20..b1e16f6de 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Friday Night Funkin' is a rhythm game. Built using HaxeFlixel for Ludum Dare 47. -This game was made with love to Newgrounds and it's community. Extra love to Tom Fulp. +This game was made with love to Newgrounds and its community. Extra love to Tom Fulp. - [Playable web demo on Newgrounds!](https://www.newgrounds.com/portal/view/770371) - [Demo download builds for Windows, Mac, and Linux from Itch.io!](https://ninja-muffin24.itch.io/funkin) diff --git a/assets b/assets index 4427687c4..c22ed15e8 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 4427687c487d643c6051f8c4610470326c87ea4d +Subproject commit c22ed15e83182f628d7830773021e3e48c596174 diff --git a/docs/COMPILING.md b/docs/COMPILING.md index 07df6367f..e7c19875a 100644 --- a/docs/COMPILING.md +++ b/docs/COMPILING.md @@ -6,9 +6,10 @@ - `git clone --recurse-submodules https://github.com/FunkinCrew/funkin.git` - If you accidentally cloned without the `assets` submodule (aka didn't follow the step above), you can run `git submodule update --init --recursive` to get the assets in a foolproof way. 2. Install `hmm` (run `haxelib --global install hmm` and then `haxelib --global run hmm setup`) -3. Install all haxelibs of the current branch by running `hmm install` -4. Setup lime: `haxelib run lime setup` -5. Platform setup +3. Download Git from [git-scm.com](https://www.git-scm.com) +4. Install all haxelibs of the current branch by running `hmm install` +5. Setup lime: `haxelib run lime setup` +6. Platform setup - For Windows, download the [Visual Studio Build Tools](https://aka.ms/vs/17/release/vs_BuildTools.exe) - When prompted, select "Individual Components" and make sure to download the following: - MSVC v143 VS 2022 C++ x64/x86 build tools @@ -16,9 +17,10 @@ - Mac: [`lime setup mac` Documentation](https://lime.openfl.org/docs/advanced-setup/macos/) - Linux: [`lime setup linux` Documentation](https://lime.openfl.org/docs/advanced-setup/linux/) - HTML5: Compiles without any extra setup -6. If you are targeting for native, you may need to run `lime rebuild PLATFORM` and `lime rebuild PLATFORM -debug` -7. `lime test PLATFORM` ! Add `-debug` to enable several debug features such as time travel (`PgUp`/`PgDn` in Play State). +7. If you are targeting for native, you may need to run `lime rebuild PLATFORM` and `lime rebuild PLATFORM -debug` +8. `lime test PLATFORM` ! Add `-debug` to enable several debug features such as time travel (`PgUp`/`PgDn` in Play State). # Troubleshooting - During the cloning process, you may experience an error along the lines of `error: RPC failed; curl 92 HTTP/2 stream 0 was not closed cleanly: PROTOCOL_ERROR (err 1)` due to poor connectivity. A common fix is to run ` git config --global http.postBuffer 4096M`. + diff --git a/source/funkin/audio/FunkinSound.hx b/source/funkin/audio/FunkinSound.hx index 4f61e70c2..c70f195d2 100644 --- a/source/funkin/audio/FunkinSound.hx +++ b/source/funkin/audio/FunkinSound.hx @@ -227,12 +227,12 @@ class FunkinSound extends FlxSound implements ICloneable // already paused before we lost focus. if (_lostFocus && !_alreadyPaused) { - trace('Resuming audio (${this._label}) on focus!'); + // trace('Resuming audio (${this._label}) on focus!'); resume(); } else { - trace('Not resuming audio (${this._label}) on focus!'); + // trace('Not resuming audio (${this._label}) on focus!'); } _lostFocus = false; } @@ -242,7 +242,7 @@ class FunkinSound extends FlxSound implements ICloneable */ override function onFocusLost():Void { - trace('Focus lost, pausing audio!'); + // trace('Focus lost, pausing audio!'); _lostFocus = true; _alreadyPaused = _paused; pause(); diff --git a/source/funkin/audio/visualize/ABotVis.hx b/source/funkin/audio/visualize/ABotVis.hx index 1b0463144..a455cb8a2 100644 --- a/source/funkin/audio/visualize/ABotVis.hx +++ b/source/funkin/audio/visualize/ABotVis.hx @@ -54,12 +54,12 @@ class ABotVis extends FlxTypedSpriteGroup public function initAnalyzer() { @:privateAccess - analyzer = new SpectralAnalyzer(snd._channel.__source, 7, 0.1, 30); + analyzer = new SpectralAnalyzer(snd._channel.__source, 7, 0.1, 40); #if desktop // On desktop it uses FFT stuff that isn't as optimized as the direct browser stuff we use on HTML5 // So we want to manually change it! - analyzer.fftN = 512; + analyzer.fftN = 256; #end // analyzer.maxDb = -35; diff --git a/source/funkin/input/Controls.hx b/source/funkin/input/Controls.hx index 345791eef..e2cae5613 100644 --- a/source/funkin/input/Controls.hx +++ b/source/funkin/input/Controls.hx @@ -58,7 +58,11 @@ class Controls extends FlxActionSet var _back = new FunkinAction(Action.BACK); var _pause = new FunkinAction(Action.PAUSE); var _reset = new FunkinAction(Action.RESET); - var _screenshot = new FunkinAction(Action.SCREENSHOT); + var _window_screenshot = new FunkinAction(Action.WINDOW_SCREENSHOT); + var _window_fullscreen = new FunkinAction(Action.WINDOW_FULLSCREEN); + var _freeplay_favorite = new FunkinAction(Action.FREEPLAY_FAVORITE); + var _freeplay_left = new FunkinAction(Action.FREEPLAY_LEFT); + var _freeplay_right = new FunkinAction(Action.FREEPLAY_RIGHT); var _cutscene_advance = new FunkinAction(Action.CUTSCENE_ADVANCE); var _debug_menu = new FunkinAction(Action.DEBUG_MENU); var _debug_chart = new FunkinAction(Action.DEBUG_CHART); @@ -66,7 +70,6 @@ class Controls extends FlxActionSet var _volume_up = new FunkinAction(Action.VOLUME_UP); var _volume_down = new FunkinAction(Action.VOLUME_DOWN); var _volume_mute = new FunkinAction(Action.VOLUME_MUTE); - var _fullscreen = new FunkinAction(Action.FULLSCREEN); var byName:Map = new Map(); @@ -233,10 +236,30 @@ class Controls extends FlxActionSet inline function get_RESET() return _reset.check(); - public var SCREENSHOT(get, never):Bool; + public var WINDOW_FULLSCREEN(get, never):Bool; - inline function get_SCREENSHOT() - return _screenshot.check(); + inline function get_WINDOW_FULLSCREEN() + return _window_fullscreen.check(); + + public var WINDOW_SCREENSHOT(get, never):Bool; + + inline function get_WINDOW_SCREENSHOT() + return _window_screenshot.check(); + + public var FREEPLAY_FAVORITE(get, never):Bool; + + inline function get_FREEPLAY_FAVORITE() + return _freeplay_favorite.check(); + + public var FREEPLAY_LEFT(get, never):Bool; + + inline function get_FREEPLAY_LEFT() + return _freeplay_left.check(); + + public var FREEPLAY_RIGHT(get, never):Bool; + + inline function get_FREEPLAY_RIGHT() + return _freeplay_right.check(); public var CUTSCENE_ADVANCE(get, never):Bool; @@ -273,11 +296,6 @@ class Controls extends FlxActionSet inline function get_VOLUME_MUTE() return _volume_mute.check(); - public var FULLSCREEN(get, never):Bool; - - inline function get_FULLSCREEN() - return _fullscreen.check(); - public function new(name, scheme:KeyboardScheme = null) { super(name); @@ -294,7 +312,11 @@ class Controls extends FlxActionSet add(_back); add(_pause); add(_reset); - add(_screenshot); + add(_window_screenshot); + add(_window_fullscreen); + add(_freeplay_favorite); + add(_freeplay_left); + add(_freeplay_right); add(_cutscene_advance); add(_debug_menu); add(_debug_chart); @@ -302,7 +324,6 @@ class Controls extends FlxActionSet add(_volume_up); add(_volume_down); add(_volume_mute); - add(_fullscreen); for (action in digitalActions) { if (Std.isOfType(action, FunkinAction)) { @@ -398,7 +419,11 @@ class Controls extends FlxActionSet case BACK: _back; case PAUSE: _pause; case RESET: _reset; - case SCREENSHOT: _screenshot; + case WINDOW_SCREENSHOT: _window_screenshot; + case WINDOW_FULLSCREEN: _window_fullscreen; + case FREEPLAY_FAVORITE: _freeplay_favorite; + case FREEPLAY_LEFT: _freeplay_left; + case FREEPLAY_RIGHT: _freeplay_right; case CUTSCENE_ADVANCE: _cutscene_advance; case DEBUG_MENU: _debug_menu; case DEBUG_CHART: _debug_chart; @@ -406,7 +431,6 @@ class Controls extends FlxActionSet case VOLUME_UP: _volume_up; case VOLUME_DOWN: _volume_down; case VOLUME_MUTE: _volume_mute; - case FULLSCREEN: _fullscreen; } } @@ -466,8 +490,16 @@ class Controls extends FlxActionSet func(_pause, JUST_PRESSED); case RESET: func(_reset, JUST_PRESSED); - case SCREENSHOT: - func(_screenshot, JUST_PRESSED); + case WINDOW_SCREENSHOT: + func(_window_screenshot, JUST_PRESSED); + case WINDOW_FULLSCREEN: + func(_window_fullscreen, JUST_PRESSED); + case FREEPLAY_FAVORITE: + func(_freeplay_favorite, JUST_PRESSED); + case FREEPLAY_LEFT: + func(_freeplay_left, JUST_PRESSED); + case FREEPLAY_RIGHT: + func(_freeplay_right, JUST_PRESSED); case CUTSCENE_ADVANCE: func(_cutscene_advance, JUST_PRESSED); case DEBUG_MENU: @@ -482,8 +514,6 @@ class Controls extends FlxActionSet func(_volume_down, JUST_PRESSED); case VOLUME_MUTE: func(_volume_mute, JUST_PRESSED); - case FULLSCREEN: - func(_fullscreen, JUST_PRESSED); } } @@ -678,7 +708,11 @@ class Controls extends FlxActionSet bindKeys(Control.BACK, getDefaultKeybinds(scheme, Control.BACK)); bindKeys(Control.PAUSE, getDefaultKeybinds(scheme, Control.PAUSE)); bindKeys(Control.RESET, getDefaultKeybinds(scheme, Control.RESET)); - bindKeys(Control.SCREENSHOT, getDefaultKeybinds(scheme, Control.SCREENSHOT)); + bindKeys(Control.WINDOW_SCREENSHOT, getDefaultKeybinds(scheme, Control.WINDOW_SCREENSHOT)); + bindKeys(Control.WINDOW_FULLSCREEN, getDefaultKeybinds(scheme, Control.WINDOW_FULLSCREEN)); + bindKeys(Control.FREEPLAY_FAVORITE, getDefaultKeybinds(scheme, Control.FREEPLAY_FAVORITE)); + bindKeys(Control.FREEPLAY_LEFT, getDefaultKeybinds(scheme, Control.FREEPLAY_LEFT)); + bindKeys(Control.FREEPLAY_RIGHT, getDefaultKeybinds(scheme, Control.FREEPLAY_RIGHT)); bindKeys(Control.CUTSCENE_ADVANCE, getDefaultKeybinds(scheme, Control.CUTSCENE_ADVANCE)); bindKeys(Control.DEBUG_MENU, getDefaultKeybinds(scheme, Control.DEBUG_MENU)); bindKeys(Control.DEBUG_CHART, getDefaultKeybinds(scheme, Control.DEBUG_CHART)); @@ -686,7 +720,6 @@ class Controls extends FlxActionSet bindKeys(Control.VOLUME_UP, getDefaultKeybinds(scheme, Control.VOLUME_UP)); bindKeys(Control.VOLUME_DOWN, getDefaultKeybinds(scheme, Control.VOLUME_DOWN)); bindKeys(Control.VOLUME_MUTE, getDefaultKeybinds(scheme, Control.VOLUME_MUTE)); - bindKeys(Control.FULLSCREEN, getDefaultKeybinds(scheme, Control.FULLSCREEN)); bindMobileLol(); } @@ -707,7 +740,11 @@ class Controls extends FlxActionSet case Control.BACK: return [X, BACKSPACE, ESCAPE]; case Control.PAUSE: return [P, ENTER, ESCAPE]; case Control.RESET: return [R]; - case Control.SCREENSHOT: return [F3]; // TODO: Change this back to PrintScreen + case Control.WINDOW_FULLSCREEN: return [F11]; // We use F for other things LOL. + case Control.WINDOW_SCREENSHOT: return [F3]; + case Control.FREEPLAY_FAVORITE: return [F]; // Favorite a song on the menu + case Control.FREEPLAY_LEFT: return [Q]; // Switch tabs on the menu + case Control.FREEPLAY_RIGHT: return [E]; // Switch tabs on the menu case Control.CUTSCENE_ADVANCE: return [Z, ENTER]; case Control.DEBUG_MENU: return [GRAVEACCENT]; case Control.DEBUG_CHART: return []; @@ -715,8 +752,6 @@ class Controls extends FlxActionSet case Control.VOLUME_UP: return [PLUS, NUMPADPLUS]; case Control.VOLUME_DOWN: return [MINUS, NUMPADMINUS]; case Control.VOLUME_MUTE: return [ZERO, NUMPADZERO]; - case Control.FULLSCREEN: return [FlxKey.F11]; // We use F for other things LOL. - } case Duo(true): switch (control) { @@ -732,7 +767,11 @@ class Controls extends FlxActionSet case Control.BACK: return [H, X]; case Control.PAUSE: return [ONE]; case Control.RESET: return [R]; - case Control.SCREENSHOT: return [PRINTSCREEN]; + case Control.WINDOW_SCREENSHOT: return [F3]; + case Control.WINDOW_FULLSCREEN: return [F11]; + case Control.FREEPLAY_FAVORITE: return [F]; // Favorite a song on the menu + case Control.FREEPLAY_LEFT: return [Q]; // Switch tabs on the menu + case Control.FREEPLAY_RIGHT: return [E]; // Switch tabs on the menu case Control.CUTSCENE_ADVANCE: return [G, Z]; case Control.DEBUG_MENU: return [GRAVEACCENT]; case Control.DEBUG_CHART: return []; @@ -740,7 +779,6 @@ class Controls extends FlxActionSet case Control.VOLUME_UP: return [PLUS]; case Control.VOLUME_DOWN: return [MINUS]; case Control.VOLUME_MUTE: return [ZERO]; - case Control.FULLSCREEN: return [FlxKey.F]; } case Duo(false): @@ -757,15 +795,18 @@ class Controls extends FlxActionSet case Control.BACK: return [ESCAPE]; case Control.PAUSE: return [ONE]; case Control.RESET: return [R]; - case Control.SCREENSHOT: return [PRINTSCREEN]; + case Control.WINDOW_SCREENSHOT: return []; + case Control.WINDOW_FULLSCREEN: return []; + case Control.FREEPLAY_FAVORITE: return []; + case Control.FREEPLAY_LEFT: return []; + case Control.FREEPLAY_RIGHT: return []; case Control.CUTSCENE_ADVANCE: return [ENTER]; - case Control.DEBUG_MENU: return [GRAVEACCENT]; + case Control.DEBUG_MENU: return []; case Control.DEBUG_CHART: return []; case Control.DEBUG_STAGE: return []; case Control.VOLUME_UP: return [NUMPADPLUS]; case Control.VOLUME_DOWN: return [NUMPADMINUS]; case Control.VOLUME_MUTE: return [NUMPADZERO]; - case Control.FULLSCREEN: return []; } default: @@ -856,34 +897,37 @@ class Controls extends FlxActionSet public function addDefaultGamepad(id):Void { addGamepadLiteral(id, [ - Control.ACCEPT => getDefaultGamepadBinds(Control.ACCEPT), Control.BACK => getDefaultGamepadBinds(Control.BACK), Control.UI_UP => getDefaultGamepadBinds(Control.UI_UP), Control.UI_DOWN => getDefaultGamepadBinds(Control.UI_DOWN), Control.UI_LEFT => getDefaultGamepadBinds(Control.UI_LEFT), Control.UI_RIGHT => getDefaultGamepadBinds(Control.UI_RIGHT), - // don't swap A/B or X/Y for switch on these. A is always the bottom face button Control.NOTE_UP => getDefaultGamepadBinds(Control.NOTE_UP), Control.NOTE_DOWN => getDefaultGamepadBinds(Control.NOTE_DOWN), Control.NOTE_LEFT => getDefaultGamepadBinds(Control.NOTE_LEFT), Control.NOTE_RIGHT => getDefaultGamepadBinds(Control.NOTE_RIGHT), Control.PAUSE => getDefaultGamepadBinds(Control.PAUSE), Control.RESET => getDefaultGamepadBinds(Control.RESET), - // Control.SCREENSHOT => [], - // Control.VOLUME_UP => [RIGHT_SHOULDER], - // Control.VOLUME_DOWN => [LEFT_SHOULDER], - // Control.VOLUME_MUTE => [RIGHT_TRIGGER], + Control.WINDOW_FULLSCREEN => getDefaultGamepadBinds(Control.WINDOW_FULLSCREEN), + Control.WINDOW_SCREENSHOT => getDefaultGamepadBinds(Control.WINDOW_SCREENSHOT), Control.CUTSCENE_ADVANCE => getDefaultGamepadBinds(Control.CUTSCENE_ADVANCE), - // Control.DEBUG_MENU - // Control.DEBUG_CHART + Control.FREEPLAY_FAVORITE => getDefaultGamepadBinds(Control.FREEPLAY_FAVORITE), + Control.FREEPLAY_LEFT => getDefaultGamepadBinds(Control.FREEPLAY_LEFT), + Control.FREEPLAY_RIGHT => getDefaultGamepadBinds(Control.FREEPLAY_RIGHT), + Control.VOLUME_UP => getDefaultGamepadBinds(Control.VOLUME_UP), + Control.VOLUME_DOWN => getDefaultGamepadBinds(Control.VOLUME_DOWN), + Control.VOLUME_MUTE => getDefaultGamepadBinds(Control.VOLUME_MUTE), + Control.DEBUG_MENU => getDefaultGamepadBinds(Control.DEBUG_MENU), + Control.DEBUG_CHART => getDefaultGamepadBinds(Control.DEBUG_CHART), + Control.DEBUG_STAGE => getDefaultGamepadBinds(Control.DEBUG_STAGE), ]); } function getDefaultGamepadBinds(control:Control):Array { switch(control) { case Control.ACCEPT: return [#if switch B #else A #end]; - case Control.BACK: return [#if switch A #else B #end, FlxGamepadInputID.BACK]; + case Control.BACK: return [#if switch A #else B #end]; case Control.UI_UP: return [DPAD_UP, LEFT_STICK_DIGITAL_UP]; case Control.UI_DOWN: return [DPAD_DOWN, LEFT_STICK_DIGITAL_DOWN]; case Control.UI_LEFT: return [DPAD_LEFT, LEFT_STICK_DIGITAL_LEFT]; @@ -893,15 +937,19 @@ class Controls extends FlxActionSet case Control.NOTE_LEFT: return [DPAD_LEFT, X, LEFT_STICK_DIGITAL_LEFT, RIGHT_STICK_DIGITAL_LEFT]; case Control.NOTE_RIGHT: return [DPAD_RIGHT, B, LEFT_STICK_DIGITAL_RIGHT, RIGHT_STICK_DIGITAL_RIGHT]; case Control.PAUSE: return [START]; - case Control.RESET: return [RIGHT_SHOULDER]; - case Control.SCREENSHOT: return []; - case Control.VOLUME_UP: return []; - case Control.VOLUME_DOWN: return []; - case Control.VOLUME_MUTE: return []; + case Control.RESET: return [FlxGamepadInputID.BACK]; // Back (i.e. Select) + case Control.WINDOW_FULLSCREEN: []; + case Control.WINDOW_SCREENSHOT: []; case Control.CUTSCENE_ADVANCE: return [A]; - case Control.DEBUG_MENU: return []; - case Control.DEBUG_CHART: return []; - case Control.FULLSCREEN: return []; + case Control.FREEPLAY_FAVORITE: [FlxGamepadInputID.BACK]; // Back (i.e. Select) + case Control.FREEPLAY_LEFT: [LEFT_SHOULDER]; + case Control.FREEPLAY_RIGHT: [RIGHT_SHOULDER]; + case Control.VOLUME_UP: []; + case Control.VOLUME_DOWN: []; + case Control.VOLUME_MUTE: []; + case Control.DEBUG_MENU: []; + case Control.DEBUG_CHART: []; + case Control.DEBUG_STAGE: []; default: // Fallthrough. } @@ -1392,7 +1440,7 @@ class FlxActionInputDigitalAndroid extends FlxActionInputDigital override public function check(Action:FlxAction):Bool { - returnswitch(trigger) + return switch(trigger) { #if android case PRESSED: FlxG.android.checkStatus(inputID, PRESSED) || FlxG.android.checkStatus(inputID, PRESSED); @@ -1425,14 +1473,18 @@ enum Control UI_RIGHT; UI_DOWN; RESET; - SCREENSHOT; ACCEPT; BACK; PAUSE; - FULLSCREEN; // CUTSCENE CUTSCENE_ADVANCE; - // SCREENSHOT + // FREEPLAY + FREEPLAY_FAVORITE; + FREEPLAY_LEFT; + FREEPLAY_RIGHT; + // WINDOW + WINDOW_SCREENSHOT; + WINDOW_FULLSCREEN; // VOLUME VOLUME_UP; VOLUME_DOWN; @@ -1475,11 +1527,15 @@ enum abstract Action(String) to String from String var BACK = "back"; var PAUSE = "pause"; var RESET = "reset"; - var FULLSCREEN = "fullscreen"; - // SCREENSHOT - var SCREENSHOT = "screenshot"; + // WINDOW + var WINDOW_FULLSCREEN = "window_fullscreen"; + var WINDOW_SCREENSHOT = "window_screenshot"; // CUTSCENE var CUTSCENE_ADVANCE = "cutscene_advance"; + // FREEPLAY + var FREEPLAY_FAVORITE = "freeplay_favorite"; + var FREEPLAY_LEFT = "freeplay_left"; + var FREEPLAY_RIGHT = "freeplay_right"; // VOLUME var VOLUME_UP = "volume_up"; var VOLUME_DOWN = "volume_down"; diff --git a/source/funkin/modding/events/ScriptEvent.hx b/source/funkin/modding/events/ScriptEvent.hx index 8d625290d..dd55de23b 100644 --- a/source/funkin/modding/events/ScriptEvent.hx +++ b/source/funkin/modding/events/ScriptEvent.hx @@ -140,16 +140,36 @@ class HitNoteScriptEvent extends NoteScriptEvent */ public var score:Int; - public function new(note:NoteSprite, healthChange:Float, score:Int, judgement:String, comboCount:Int = 0):Void + /** + * If the hit causes a combo break. + */ + public var isComboBreak:Bool = false; + + /** + * The time difference when the player hit the note + */ + public var hitDiff:Float = 0; + + /** + * If the hit causes a notesplash + */ + public var doesNotesplash:Bool = false; + + public function new(note:NoteSprite, healthChange:Float, score:Int, judgement:String, isComboBreak:Bool, comboCount:Int = 0, hitDiff:Float = 0, + doesNotesplash:Bool = false):Void { super(NOTE_HIT, note, healthChange, comboCount, true); this.score = score; this.judgement = judgement; + this.isComboBreak = isComboBreak; + this.doesNotesplash = doesNotesplash; + this.hitDiff = hitDiff; } public override function toString():String { - return 'HitNoteScriptEvent(note=' + note + ', comboCount=' + comboCount + ', judgement=' + judgement + ', score=' + score + ')'; + return 'HitNoteScriptEvent(note=' + note + ', comboCount=' + comboCount + ', judgement=' + judgement + ', score=' + score + ', isComboBreak=' + + isComboBreak + ', hitDiff=' + hitDiff + ', doesNotesplash=' + doesNotesplash + ')'; } } diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index 4d50d75cc..c84d5b154 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -71,7 +71,7 @@ class GameOverSubState extends MusicBeatSubState var gameOverMusic:Null = null; /** - * Whether the player has confirmed and prepared to restart the level. + * Whether the player has confirmed and prepared to restart the level or to go back to the freeplay menu. * This means the animation and transition have already started. */ var isEnding:Bool = false; @@ -237,15 +237,16 @@ class GameOverSubState extends MusicBeatSubState } // KEYBOARD ONLY: Restart the level when pressing the assigned key. - if (controls.ACCEPT && blueballed) + if (controls.ACCEPT && blueballed && !mustNotExit) { blueballed = false; confirmDeath(); } // KEYBOARD ONLY: Return to the menu when pressing the assigned key. - if (controls.BACK && !mustNotExit) + if (controls.BACK && !mustNotExit && !isEnding) { + isEnding = true; blueballed = false; PlayState.instance.deathCounter = 0; // PlayState.seenCutscene = false; // old thing... diff --git a/source/funkin/play/PauseSubState.hx b/source/funkin/play/PauseSubState.hx index 8c45fac65..d0c759b16 100644 --- a/source/funkin/play/PauseSubState.hx +++ b/source/funkin/play/PauseSubState.hx @@ -449,13 +449,14 @@ class PauseSubState extends MusicBeatSubState */ function changeSelection(change:Int = 0):Void { - FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4); - + var prevEntry:Int = currentEntry; currentEntry += change; if (currentEntry < 0) currentEntry = currentMenuEntries.length - 1; if (currentEntry >= currentMenuEntries.length) currentEntry = 0; + if (currentEntry != prevEntry) FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4); + for (entryIndex in 0...currentMenuEntries.length) { var isCurrent:Bool = entryIndex == currentEntry; diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 6bc4b2d89..f55cef388 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -1218,6 +1218,9 @@ class PlayState extends MusicBeatSubState cameraTweensPausedBySubState.add(cameraZoomTween); } + // Pause camera follow + FlxG.camera.followLerp = 0; + for (tween in scrollSpeedTweens) { if (tween != null && tween.active) @@ -1262,6 +1265,9 @@ class PlayState extends MusicBeatSubState } cameraTweensPausedBySubState.clear(); + // Resume camera follow + FlxG.camera.followLerp = Constants.DEFAULT_CAMERA_FOLLOW_RATE; + if (currentConversation != null) { currentConversation.resumeMusic(); @@ -2122,7 +2128,8 @@ class PlayState extends MusicBeatSubState // Call an event to allow canceling the note hit. // NOTE: This is what handles the character animations! - var event:NoteScriptEvent = new HitNoteScriptEvent(note, 0.0, 0, 'perfect', 0); + + var event:NoteScriptEvent = new HitNoteScriptEvent(note, 0.0, 0, 'perfect', false, 0); dispatchEvent(event); // Calling event.cancelEvent() skips all the other logic! Neat! @@ -2218,7 +2225,7 @@ class PlayState extends MusicBeatSubState // Call an event to allow canceling the note hit. // NOTE: This is what handles the character animations! - var event:NoteScriptEvent = new HitNoteScriptEvent(note, 0.0, 0, 'perfect', 0); + var event:NoteScriptEvent = new HitNoteScriptEvent(note, 0.0, 0, 'perfect', false, 0); dispatchEvent(event); // Calling event.cancelEvent() skips all the other logic! Neat! @@ -2276,11 +2283,20 @@ class PlayState extends MusicBeatSubState if (holdNote == null || !holdNote.alive) continue; // While the hold note is being hit, and there is length on the hold note... - if (!isBotPlayMode && holdNote.hitNote && !holdNote.missedNote && holdNote.sustainLength > 0) + if (holdNote.hitNote && !holdNote.missedNote && holdNote.sustainLength > 0) { // Grant the player health. - health += Constants.HEALTH_HOLD_BONUS_PER_SECOND * elapsed; - songScore += Std.int(Constants.SCORE_HOLD_BONUS_PER_SECOND * elapsed); + if (!isBotPlayMode) + { + health += Constants.HEALTH_HOLD_BONUS_PER_SECOND * elapsed; + songScore += Std.int(Constants.SCORE_HOLD_BONUS_PER_SECOND * elapsed); + } + + // Make sure the player keeps singing while the note is held by the bot. + if (isBotPlayMode && currentStage != null && currentStage.getBoyfriend() != null && currentStage.getBoyfriend().isSinging()) + { + currentStage.getBoyfriend().holdTimer = 0; + } } if (holdNote.missedNote && !holdNote.handledMiss) @@ -2427,27 +2443,41 @@ class PlayState extends MusicBeatSubState var daRating = Scoring.judgeNote(noteDiff, PBOT1); var healthChange = 0.0; + var isComboBreak = false; switch (daRating) { case 'sick': healthChange = Constants.HEALTH_SICK_BONUS; + isComboBreak = Constants.JUDGEMENT_SICK_COMBO_BREAK; case 'good': healthChange = Constants.HEALTH_GOOD_BONUS; + isComboBreak = Constants.JUDGEMENT_GOOD_COMBO_BREAK; case 'bad': healthChange = Constants.HEALTH_BAD_BONUS; + isComboBreak = Constants.JUDGEMENT_BAD_COMBO_BREAK; case 'shit': + isComboBreak = Constants.JUDGEMENT_SHIT_COMBO_BREAK; healthChange = Constants.HEALTH_SHIT_BONUS; } // Send the note hit event. - var event:HitNoteScriptEvent = new HitNoteScriptEvent(note, healthChange, score, daRating, Highscore.tallies.combo + 1); + var event:HitNoteScriptEvent = new HitNoteScriptEvent(note, healthChange, score, daRating, isComboBreak, Highscore.tallies.combo + 1, noteDiff, + daRating == 'sick'); dispatchEvent(event); // Calling event.cancelEvent() skips all the other logic! Neat! if (event.eventCanceled) return; + Highscore.tallies.totalNotesHit++; + // Display the hit on the strums + playerStrumline.hitNote(note, !isComboBreak); + if (event.doesNotesplash) playerStrumline.playNoteSplash(note.noteData.getDirection()); + if (note.isHoldNote && note.holdNoteSprite != null) playerStrumline.playNoteHoldCover(note.holdNoteSprite); + vocals.playerVolume = 1; + // Display the combo meter and add the calculation to the score. - popUpScore(note, event.score, event.judgement, event.healthChange); + applyScore(event.score, event.judgement, event.healthChange, event.isComboBreak); + popUpScore(event.judgement); } /** @@ -2458,9 +2488,6 @@ class PlayState extends MusicBeatSubState { // If we are here, we already CALLED the onNoteMiss script hook! - health += healthChange; - songScore -= 10; - if (!isPracticeMode) { // messy copy paste rn lol @@ -2500,14 +2527,9 @@ class PlayState extends MusicBeatSubState } vocals.playerVolume = 0; - Highscore.tallies.missed++; + if (Highscore.tallies.combo != 0) if (Highscore.tallies.combo >= 10) comboPopUps.displayCombo(0); - if (Highscore.tallies.combo != 0) - { - // Break the combo. - if (Highscore.tallies.combo >= 10) comboPopUps.displayCombo(0); - Highscore.tallies.combo = 0; - } + applyScore(-10, 'miss', healthChange, true); if (playSound) { @@ -2595,20 +2617,12 @@ class PlayState extends MusicBeatSubState // Redirect to the chart editor playing the current song. if (controls.DEBUG_CHART) { - if (isChartingMode) - { - if (FlxG.sound.music != null) FlxG.sound.music.pause(); // Don't reset song position! - this.close(); // This only works because PlayState is a substate! - } - else - { - disableKeys = true; - persistentUpdate = false; - FlxG.switchState(() -> new ChartEditorState( - { - targetSongId: currentSong.id, - })); - } + disableKeys = true; + persistentUpdate = false; + FlxG.switchState(() -> new ChartEditorState( + { + targetSongId: currentSong.id, + })); } #end @@ -2639,46 +2653,24 @@ class PlayState extends MusicBeatSubState } /** - * Handles health, score, and rating popups when a note is hit. + * Handles applying health, score, and ratings. */ - function popUpScore(daNote:NoteSprite, score:Int, daRating:String, healthChange:Float):Void + function applyScore(score:Int, daRating:String, healthChange:Float, isComboBreak:Bool) { - if (daRating == 'miss') - { - // If daRating is 'miss', that means we made a mistake and should not continue. - FlxG.log.warn('popUpScore judged a note as a miss!'); - // TODO: Remove this. - // comboPopUps.displayRating('miss'); - return; - } - - vocals.playerVolume = 1; - - var isComboBreak = false; switch (daRating) { case 'sick': Highscore.tallies.sick += 1; - Highscore.tallies.totalNotesHit++; - isComboBreak = Constants.JUDGEMENT_SICK_COMBO_BREAK; case 'good': Highscore.tallies.good += 1; - Highscore.tallies.totalNotesHit++; - isComboBreak = Constants.JUDGEMENT_GOOD_COMBO_BREAK; case 'bad': Highscore.tallies.bad += 1; - Highscore.tallies.totalNotesHit++; - isComboBreak = Constants.JUDGEMENT_BAD_COMBO_BREAK; case 'shit': Highscore.tallies.shit += 1; - Highscore.tallies.totalNotesHit++; - isComboBreak = Constants.JUDGEMENT_SHIT_COMBO_BREAK; - default: - FlxG.log.error('Wuh? Buh? Guh? Note hit judgement was $daRating!'); + case 'miss': + Highscore.tallies.missed += 1; } - health += healthChange; - if (isComboBreak) { // Break the combo, but don't increment tallies.misses. @@ -2690,15 +2682,23 @@ class PlayState extends MusicBeatSubState Highscore.tallies.combo++; if (Highscore.tallies.combo > Highscore.tallies.maxCombo) Highscore.tallies.maxCombo = Highscore.tallies.combo; } - - playerStrumline.hitNote(daNote, !isComboBreak); - - if (daRating == 'sick') - { - playerStrumline.playNoteSplash(daNote.noteData.getDirection()); - } - songScore += score; + } + + /** + * Handles rating popups when a note is hit. + */ + function popUpScore(daRating:String, ?combo:Int):Void + { + if (daRating == 'miss') + { + // If daRating is 'miss', that means we made a mistake and should not continue. + FlxG.log.warn('popUpScore judged a note as a miss!'); + // TODO: Remove this. + // comboPopUps.displayRating('miss'); + return; + } + if (combo == null) combo = Highscore.tallies.combo; if (!isPracticeMode) { @@ -2738,12 +2738,7 @@ class PlayState extends MusicBeatSubState } } comboPopUps.displayRating(daRating); - if (Highscore.tallies.combo >= 10 || Highscore.tallies.combo == 0) comboPopUps.displayCombo(Highscore.tallies.combo); - - if (daNote.isHoldNote && daNote.holdNoteSprite != null) - { - playerStrumline.playNoteHoldCover(daNote.holdNoteSprite); - } + if (combo >= 10 || combo == 0) comboPopUps.displayCombo(combo); vocals.playerVolume = 1; } @@ -2830,8 +2825,13 @@ class PlayState extends MusicBeatSubState deathCounter = 0; + // TODO: This line of code makes me sad, but you can't really fix it without a breaking migration. + // `easy`, `erect`, `normal-pico`, etc. + var suffixedDifficulty = (currentVariation != Constants.DEFAULT_VARIATION + && currentVariation != 'erect') ? '$currentDifficulty-${currentVariation}' : currentDifficulty; + var isNewHighscore = false; - var prevScoreData:Null = Save.instance.getSongScore(currentSong.id, currentDifficulty); + var prevScoreData:Null = Save.instance.getSongScore(currentSong.id, suffixedDifficulty); if (currentSong != null && currentSong.validScore) { @@ -2856,13 +2856,21 @@ class PlayState extends MusicBeatSubState // adds current song data into the tallies for the level (story levels) Highscore.talliesLevel = Highscore.combineTallies(Highscore.tallies, Highscore.talliesLevel); - if (!isPracticeMode && !isBotPlayMode && Save.instance.isSongHighScore(currentSong.id, currentDifficulty, data)) + if (!isPracticeMode && !isBotPlayMode) { - Save.instance.setSongScore(currentSong.id, currentDifficulty, data); - #if newgrounds - NGio.postScore(score, currentSong.id); - #end - isNewHighscore = true; + isNewHighscore = Save.instance.isSongHighScore(currentSong.id, suffixedDifficulty, data); + + // If no high score is present, save both score and rank. + // If score or rank are better, save the highest one. + // If neither are higher, nothing will change. + Save.instance.applySongRank(currentSong.id, suffixedDifficulty, data); + + if (isNewHighscore) + { + #if newgrounds + NGio.postScore(score, currentSong.id); + #end + } } } @@ -3097,7 +3105,7 @@ class PlayState extends MusicBeatSubState FlxG.camera.targetOffset.x += 20; // Replace zoom animation with a fade out for now. - camGame.fade(FlxColor.BLACK, 0.6); + FlxG.camera.fade(FlxColor.BLACK, 0.6); FlxTween.tween(camHUD, {alpha: 0}, 0.6, { @@ -3192,7 +3200,7 @@ class PlayState extends MusicBeatSubState cancelAllCameraTweens(); } - FlxG.camera.follow(cameraFollowPoint, LOCKON, 0.04); + FlxG.camera.follow(cameraFollowPoint, LOCKON, Constants.DEFAULT_CAMERA_FOLLOW_RATE); FlxG.camera.targetOffset.set(); if (resetZoom) diff --git a/source/funkin/play/components/HealthIcon.hx b/source/funkin/play/components/HealthIcon.hx index a3204329a..2442b0dc5 100644 --- a/source/funkin/play/components/HealthIcon.hx +++ b/source/funkin/play/components/HealthIcon.hx @@ -53,8 +53,9 @@ class HealthIcon extends FunkinSprite /** * Apply the "bop" animation once every X steps. + * Defaults to once per beat. */ - public var bopEvery:Int = 4; + public var bopEvery:Int = Constants.STEPS_PER_BEAT; /** * The amount, in degrees, to rotate the icon by when boping. diff --git a/source/funkin/play/scoring/Scoring.hx b/source/funkin/play/scoring/Scoring.hx index d6f71fc7e..dc2c40647 100644 --- a/source/funkin/play/scoring/Scoring.hx +++ b/source/funkin/play/scoring/Scoring.hx @@ -356,7 +356,10 @@ class Scoring // Perfect (Platinum) is a Sick Full Clear var isPerfectGold = scoreData.tallies.sick == scoreData.tallies.totalNotes; - if (isPerfectGold) return ScoringRank.PERFECT_GOLD; + if (isPerfectGold) + { + return ScoringRank.PERFECT_GOLD; + } // Else, use the standard grades @@ -397,62 +400,79 @@ enum abstract ScoringRank(String) var GOOD; var SHIT; - @:op(A > B) static function compare(a:Null, b:Null):Bool + /** + * Converts ScoringRank to an integer value for comparison. + * Better ranks should be tied to a higher value. + */ + static function getValue(rank:Null):Int + { + if (rank == null) return -1; + switch (rank) + { + case PERFECT_GOLD: + return 5; + case PERFECT: + return 4; + case EXCELLENT: + return 3; + case GREAT: + return 2; + case GOOD: + return 1; + case SHIT: + return 0; + default: + return -1; + } + } + + // Yes, we really need a different function for each comparison operator. + @:op(A > B) static function compareGT(a:Null, b:Null):Bool { if (a != null && b == null) return true; if (a == null || b == null) return false; - var temp1:Int = 0; - var temp2:Int = 0; + var temp1:Int = getValue(a); + var temp2:Int = getValue(b); - // temp 1 - switch (a) - { - case PERFECT_GOLD: - temp1 = 5; - case PERFECT: - temp1 = 4; - case EXCELLENT: - temp1 = 3; - case GREAT: - temp1 = 2; - case GOOD: - temp1 = 1; - case SHIT: - temp1 = 0; - default: - temp1 = -1; - } - - // temp 2 - switch (b) - { - case PERFECT_GOLD: - temp2 = 5; - case PERFECT: - temp2 = 4; - case EXCELLENT: - temp2 = 3; - case GREAT: - temp2 = 2; - case GOOD: - temp2 = 1; - case SHIT: - temp2 = 0; - default: - temp2 = -1; - } - - if (temp1 > temp2) - { - return true; - } - else - { - return false; - } + return temp1 > temp2; } + @:op(A >= B) static function compareGTEQ(a:Null, b:Null):Bool + { + if (a != null && b == null) return true; + if (a == null || b == null) return false; + + var temp1:Int = getValue(a); + var temp2:Int = getValue(b); + + return temp1 >= temp2; + } + + @:op(A < B) static function compareLT(a:Null, b:Null):Bool + { + if (a != null && b == null) return true; + if (a == null || b == null) return false; + + var temp1:Int = getValue(a); + var temp2:Int = getValue(b); + + return temp1 < temp2; + } + + @:op(A <= B) static function compareLTEQ(a:Null, b:Null):Bool + { + if (a != null && b == null) return true; + if (a == null || b == null) return false; + + var temp1:Int = getValue(a); + var temp2:Int = getValue(b); + + return temp1 <= temp2; + } + + // @:op(A == B) isn't necessary! + /** * Delay in seconds */ @@ -462,15 +482,15 @@ enum abstract ScoringRank(String) { case PERFECT_GOLD | PERFECT: // return 2.5; - return 95/24; + return 95 / 24; case EXCELLENT: return 0; case GREAT: - return 5/24; + return 5 / 24; case GOOD: - return 3/24; + return 3 / 24; case SHIT: - return 2/24; + return 2 / 24; default: return 3.5; } @@ -482,15 +502,15 @@ enum abstract ScoringRank(String) { case PERFECT_GOLD | PERFECT: // return 2.5; - return 95/24; + return 95 / 24; case EXCELLENT: - return 97/24; + return 97 / 24; case GREAT: - return 95/24; + return 95 / 24; case GOOD: - return 95/24; + return 95 / 24; case SHIT: - return 95/24; + return 95 / 24; default: return 3.5; } @@ -502,15 +522,15 @@ enum abstract ScoringRank(String) { case PERFECT_GOLD | PERFECT: // return 2.5; - return 129/24; + return 129 / 24; case EXCELLENT: - return 122/24; + return 122 / 24; case GREAT: - return 109/24; + return 109 / 24; case GOOD: - return 107/24; + return 107 / 24; case SHIT: - return 186/24; + return 186 / 24; default: return 3.5; } @@ -522,15 +542,15 @@ enum abstract ScoringRank(String) { case PERFECT_GOLD | PERFECT: // return 2.5; - return 140/24; + return 140 / 24; case EXCELLENT: - return 140/24; + return 140 / 24; case GREAT: - return 129/24; + return 129 / 24; case GOOD: - return 127/24; + return 127 / 24; case SHIT: - return 207/24; + return 207 / 24; default: return 3.5; } diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx index 2ff6b96cc..2900ce2be 100644 --- a/source/funkin/save/Save.hx +++ b/source/funkin/save/Save.hx @@ -1,6 +1,7 @@ package funkin.save; import flixel.util.FlxSave; +import funkin.util.FileUtil; import funkin.input.Controls.Device; import funkin.play.scoring.Scoring; import funkin.play.scoring.Scoring.ScoringRank; @@ -58,7 +59,7 @@ class Save this.data = data; // Make sure the verison number is up to date before we flush. - this.data.version = Save.SAVE_DATA_VERSION; + updateVersionToLatest(); } public static function getDefault():RawSaveData @@ -503,7 +504,7 @@ class Save } /** - * Apply the score the user achieved for a given song on a given difficulty. + * Directly set the score the user achieved for a given song on a given difficulty. */ public function setSongScore(songId:String, difficultyId:String, score:SaveScoreData):Void { @@ -518,6 +519,44 @@ class Save flush(); } + /** + * Only replace the ranking data for the song, because the old score is still better. + */ + public function applySongRank(songId:String, difficultyId:String, newScoreData:SaveScoreData):Void + { + var newRank = Scoring.calculateRank(newScoreData); + if (newScoreData == null || newRank == null) return; + + var song = data.scores.songs.get(songId); + if (song == null) + { + song = []; + data.scores.songs.set(songId, song); + } + + var previousScoreData = song.get(difficultyId); + + var previousRank = Scoring.calculateRank(previousScoreData); + + if (previousScoreData == null || previousRank == null) + { + // Directly set the highscore. + setSongScore(songId, difficultyId, newScoreData); + return; + } + + // Set the high score and the high rank separately. + var newScore:SaveScoreData = + { + score: (previousScoreData.score > newScoreData.score) ? previousScoreData.score : newScoreData.score, + tallies: (previousRank > newRank) ? previousScoreData.tallies : newScoreData.tallies + }; + + song.set(difficultyId, newScore); + + flush(); + } + /** * Is the provided score data better than the current high score for the given song? * @param songId The song ID to check. @@ -543,6 +582,39 @@ class Save return score.score > currentScore.score; } + /** + * Is the provided score data better than the current rank for the given song? + * @param songId The song ID to check. + * @param difficultyId The difficulty to check. + * @param score The score to check the rank for. + * @return Whether the score's rank is better than the current rank. + */ + public function isSongHighRank(songId:String, difficultyId:String = 'normal', score:SaveScoreData):Bool + { + var newScoreRank = Scoring.calculateRank(score); + if (newScoreRank == null) + { + // The provided score is invalid. + return false; + } + + var song = data.scores.songs.get(songId); + if (song == null) + { + song = []; + data.scores.songs.set(songId, song); + } + var currentScore = song.get(difficultyId); + var currentScoreRank = Scoring.calculateRank(currentScore); + if (currentScoreRank == null) + { + // There is no primary highscore for this song. + return true; + } + + return newScoreRank > currentScoreRank; + } + /** * Has the provided song been beaten on one of the listed difficulties? * @param songId The song ID to check. @@ -832,6 +904,29 @@ class Save return cast legacySave.data; } } + + /** + * Serialize this Save into a JSON string. + * @param pretty Whether the JSON should be big ol string (false), + * or formatted with tabs (true) + * @return The JSON string. + */ + public function serialize(pretty:Bool = true):String + { + var ignoreNullOptionals = true; + var writer = new json2object.JsonWriter(ignoreNullOptionals); + return writer.write(data, pretty ? ' ' : null); + } + + public function updateVersionToLatest():Void + { + this.data.version = Save.SAVE_DATA_VERSION; + } + + public function debug_dumpSave():Void + { + FileUtil.saveFile(haxe.io.Bytes.ofString(this.serialize()), [FileUtil.FILE_FILTER_JSON], null, null, './save.json', 'Write save data as JSON...'); + } } /** @@ -904,6 +999,9 @@ typedef SaveHighScoresData = typedef SaveDataMods = { var enabledMods:Array; + + // TODO: Make this not trip up the serializer when debugging. + @:jignored var modOptions:Map; } diff --git a/source/funkin/ui/MenuList.hx b/source/funkin/ui/MenuList.hx index 63a688778..d7319abd6 100644 --- a/source/funkin/ui/MenuList.hx +++ b/source/funkin/ui/MenuList.hx @@ -94,7 +94,7 @@ class MenuTypedList extends FlxTypedGroup if (newIndex != selectedIndex) { - FunkinSound.playOnce(Paths.sound('scrollMenu')); + FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4); selectItem(newIndex); } @@ -142,7 +142,7 @@ class MenuTypedList extends FlxTypedGroup */ function navGrid(latSize:Int, latPrev:Bool, latNext:Bool, latAllowWrap:Bool, prev:Bool, next:Bool, allowWrap:Bool):Int { - // The grid lenth along the variable-length axis + // The grid length along the variable-length axis var size = Math.ceil(length / latSize); // The selected position along the variable-length axis var index = Math.floor(selectedIndex / latSize); diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 260393fac..f72cca77f 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -904,7 +904,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState function set_notePreviewDirty(value:Bool):Bool { - trace('Note preview dirtied!'); + // trace('Note preview dirtied!'); return notePreviewDirty = value; } @@ -6304,7 +6304,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState var tempNote:NoteSprite = new NoteSprite(NoteStyleRegistry.instance.fetchDefault()); tempNote.noteData = noteData; tempNote.scrollFactor.set(0, 0); - var event:NoteScriptEvent = new HitNoteScriptEvent(tempNote, 0.0, 0, 'perfect', 0); + var event:NoteScriptEvent = new HitNoteScriptEvent(tempNote, 0.0, 0, 'perfect', false, 0); dispatchEvent(event); // Calling event.cancelEvent() skips all the other logic! Neat! diff --git a/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx b/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx index 73cf80fa0..661c44d85 100644 --- a/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx +++ b/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx @@ -35,7 +35,15 @@ class SetItemSelectionCommand implements ChartEditorCommand { var eventSelected = this.events[0]; - state.eventKindToPlace = eventSelected.eventKind; + if (state.eventKindToPlace == eventSelected.eventKind) + { + trace('Target event kind matches selection: ${eventSelected.eventKind}'); + } + else + { + trace('Switching target event kind to match selection: ${state.eventKindToPlace} != ${eventSelected.eventKind}'); + state.eventKindToPlace = eventSelected.eventKind; + } // This code is here to parse event data that's not built as a struct for some reason. // TODO: Clean this up or get rid of it. diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx index b1af0ce4c..e42102a52 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorThemeHandler.hx @@ -201,7 +201,8 @@ class ChartEditorThemeHandler // Selection borders horizontally in the middle. for (i in 1...(Conductor.instance.stepsPerMeasure)) { - if ((i % Conductor.instance.beatsPerMeasure) == 0) + // There may be a different number of beats per measure, but there's always 4 steps per beat. + if ((i % Constants.STEPS_PER_BEAT) == 0) { state.gridBitmap.fillRect(new Rectangle(0, (ChartEditorState.GRID_SIZE * i) - (GRID_BEAT_DIVIDER_WIDTH / 2), state.gridBitmap.width, GRID_BEAT_DIVIDER_WIDTH), diff --git a/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx b/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx index f0949846d..8f021840a 100644 --- a/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx +++ b/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx @@ -58,17 +58,8 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox function initialize():Void { - toolboxEventsEventKind.dataSource = new ArrayDataSource(); - - var songEvents:Array = SongEventRegistry.listEvents(); - - for (event in songEvents) - { - toolboxEventsEventKind.dataSource.add({text: event.getTitle(), value: event.id}); - } - toolboxEventsEventKind.onChange = function(event:UIEvent) { - var eventType:String = event.data.value; + var eventType:String = event.data.id; trace('ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Event type changed: $eventType'); @@ -83,7 +74,7 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox return; } - buildEventDataFormFromSchema(toolboxEventsDataGrid, schema); + buildEventDataFormFromSchema(toolboxEventsDataGrid, schema, chartEditorState.eventKindToPlace); if (!_initializing && chartEditorState.currentEventSelection.length > 0) { @@ -98,14 +89,40 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox chartEditorState.notePreviewDirty = true; } } - toolboxEventsEventKind.value = chartEditorState.eventKindToPlace; + var startingEventValue = ChartEditorDropdowns.populateDropdownWithSongEvents(toolboxEventsEventKind, chartEditorState.eventKindToPlace); + trace('ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Starting event kind: ${startingEventValue}'); + toolboxEventsEventKind.value = startingEventValue; } public override function refresh():Void { super.refresh(); - toolboxEventsEventKind.value = chartEditorState.eventKindToPlace; + var newDropdownElement = ChartEditorDropdowns.findDropdownElement(chartEditorState.eventKindToPlace, toolboxEventsEventKind); + + if (newDropdownElement == null) + { + throw 'ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Event kind not in dropdown: ${chartEditorState.eventKindToPlace}'; + } + else if (toolboxEventsEventKind.value != newDropdownElement || lastEventKind != toolboxEventsEventKind.value.id) + { + toolboxEventsEventKind.value = newDropdownElement; + + var schema:SongEventSchema = SongEventRegistry.getEventSchema(chartEditorState.eventKindToPlace); + if (schema == null) + { + trace('ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Unknown event kind: ${chartEditorState.eventKindToPlace}'); + } + else + { + trace('ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Event kind changed: ${toolboxEventsEventKind.value.id} != ${newDropdownElement.id} != ${lastEventKind}, rebuilding form'); + buildEventDataFormFromSchema(toolboxEventsDataGrid, schema, chartEditorState.eventKindToPlace); + } + } + else + { + trace('ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Event kind not changed: ${toolboxEventsEventKind.value} == ${newDropdownElement} == ${lastEventKind}'); + } for (pair in chartEditorState.eventDataToPlace.keyValueIterator()) { @@ -116,7 +133,7 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox if (field == null) { - throw 'ChartEditorToolboxHandler.refresh() - Field "${fieldId}" does not exist in the event data form.'; + throw 'ChartEditorToolboxHandler.refresh() - Field "${fieldId}" does not exist in the event data form for kind ${lastEventKind}.'; } else { @@ -141,9 +158,15 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox } } - function buildEventDataFormFromSchema(target:Box, schema:SongEventSchema):Void + var lastEventKind:String = 'unknown'; + + function buildEventDataFormFromSchema(target:Box, schema:SongEventSchema, eventKind:String):Void { - trace(schema); + trace('Building event data form from schema for event kind: ${eventKind}'); + // trace(schema); + + lastEventKind = eventKind ?? 'unknown'; + // Clear the frame. target.removeAllComponents(); @@ -188,6 +211,9 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox var dropDown:DropDown = new DropDown(); dropDown.id = field.name; dropDown.width = 200.0; + dropDown.dropdownSize = 10; + dropDown.dropdownWidth = 300; + dropDown.searchable = true; dropDown.dataSource = new ArrayDataSource(); if (field.keys == null) throw 'Field "${field.name}" is of Enum type but has no keys.'; @@ -197,12 +223,15 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox for (optionName in field.keys.keys()) { var optionValue:Null = field.keys.get(optionName); - trace('$optionName : $optionValue'); + // trace('$optionName : $optionValue'); dropDown.dataSource.add({value: optionValue, text: optionName}); } dropDown.value = field.defaultValue; + // TODO: Add an option to customize sort. + dropDown.dataSource.sort('text', ASCENDING); + input = dropDown; case STRING: input = new TextField(); diff --git a/source/funkin/ui/debug/charting/util/ChartEditorDropdowns.hx b/source/funkin/ui/debug/charting/util/ChartEditorDropdowns.hx index d2a0a053e..55aab0ab0 100644 --- a/source/funkin/ui/debug/charting/util/ChartEditorDropdowns.hx +++ b/source/funkin/ui/debug/charting/util/ChartEditorDropdowns.hx @@ -3,11 +3,13 @@ package funkin.ui.debug.charting.util; import funkin.data.notestyle.NoteStyleRegistry; import funkin.play.notes.notestyle.NoteStyle; import funkin.data.stage.StageData; +import funkin.play.event.SongEvent; import funkin.data.stage.StageRegistry; import funkin.play.character.CharacterData; import haxe.ui.components.DropDown; import funkin.play.stage.Stage; import funkin.play.character.BaseCharacter.CharacterType; +import funkin.data.event.SongEventRegistry; import funkin.play.character.CharacterData.CharacterDataParser; /** @@ -81,6 +83,42 @@ class ChartEditorDropdowns return returnValue; } + public static function populateDropdownWithSongEvents(dropDown:DropDown, startingEventId:String):DropDownEntry + { + dropDown.dataSource.clear(); + + var returnValue:DropDownEntry = {id: "FocusCamera", text: "Focus Camera"}; + + var songEvents:Array = SongEventRegistry.listEvents(); + + for (event in songEvents) + { + var value = {id: event.id, text: event.getTitle()}; + if (startingEventId == event.id) returnValue = value; + dropDown.dataSource.add(value); + } + + dropDown.dataSource.sort('text', ASCENDING); + + return returnValue; + } + + /** + * Given the ID of a dropdown element, find the corresponding entry in the dropdown's dataSource. + */ + public static function findDropdownElement(id:String, dropDown:DropDown):Null + { + // Attempt to find the entry. + for (entryIndex in 0...dropDown.dataSource.size) + { + var entry = dropDown.dataSource.get(entryIndex); + if (entry.id == id) return entry; + } + + // Not found. + return null; + } + /** * Populate a dropdown with a list of note styles. */ diff --git a/source/funkin/ui/freeplay/DJBoyfriend.hx b/source/funkin/ui/freeplay/DJBoyfriend.hx index bd2f73e42..bbf043dd4 100644 --- a/source/funkin/ui/freeplay/DJBoyfriend.hx +++ b/source/funkin/ui/freeplay/DJBoyfriend.hx @@ -266,7 +266,7 @@ class DJBoyfriend extends FlxAtlasSprite // Fade out music to 40% volume over 1 second. // This helps make the TV a bit more audible. - FlxG.sound.music.fadeOut(1.0, 0.4); + FlxG.sound.music.fadeOut(1.0, 0.1); // Play the cartoon at a random time between the start and 5 seconds from the end. cartoonSnd.time = FlxG.random.float(0, Math.max(cartoonSnd.length - (5 * Constants.MS_PER_SEC), 0.0)); diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index c47933fd7..acf0306c1 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -230,6 +230,12 @@ class FreeplayState extends MusicBeatSubState FlxTransitionableState.skipNextTransIn = true; + // dedicated camera for the state so we don't need to fuk around with camera scrolls from the mainmenu / elsewhere + funnyCam = new FunkinCamera('freeplayFunny', 0, 0, FlxG.width, FlxG.height); + funnyCam.bgColor = FlxColor.TRANSPARENT; + FlxG.cameras.add(funnyCam, false); + this.cameras = [funnyCam]; + if (stickerSubState != null) { this.persistentUpdate = true; @@ -285,7 +291,10 @@ class FreeplayState extends MusicBeatSubState // Only display songs which actually have available difficulties for the current character. var displayedVariations = song.getVariationsByCharId(currentCharacter); + trace(songId); + trace(displayedVariations); var availableDifficultiesForSong:Array = song.listDifficulties(displayedVariations, false); + trace(availableDifficultiesForSong); if (availableDifficultiesForSong.length == 0) continue; songs.push(new FreeplaySongData(levelId, songId, song, displayedVariations)); @@ -583,6 +592,8 @@ class FreeplayState extends MusicBeatSubState generateSongList({filterType: FAVORITE}, true); case 'ALL': generateSongList(null, true); + case '#': + generateSongList({filterType: REGEXP, filterData: '0-9'}, true); default: generateSongList({filterType: REGEXP, filterData: str}, true); } @@ -591,6 +602,7 @@ class FreeplayState extends MusicBeatSubState // that is, only if there's more than one song in the group! if (grpCapsules.members.length > 0) { + FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4); curSelected = 1; changeSelection(); } @@ -652,6 +664,9 @@ class FreeplayState extends MusicBeatSubState alsoOrangeLOL.visible = true; grpTxtScrolls.visible = true; + // render optimisation + if (_parentState != null) _parentState.persistentDraw = false; + cardGlow.visible = true; FlxTween.tween(cardGlow, {alpha: 0, "scale.x": 1.2, "scale.y": 1.2}, 0.45, {ease: FlxEase.sineOut}); @@ -956,16 +971,18 @@ class FreeplayState extends MusicBeatSubState grpCapsules.members[curSelected].ranking.scale.set(20, 20); grpCapsules.members[curSelected].blurredRanking.scale.set(20, 20); - grpCapsules.members[curSelected].ranking.animation.play(fromResults.newRank.getFreeplayRankIconAsset(), true); - // grpCapsules.members[curSelected].ranking.animation.curAnim.name, true); + if (fromResults?.newRank != null) { + grpCapsules.members[curSelected].ranking.animation.play(fromResults.newRank.getFreeplayRankIconAsset(), true); + } FlxTween.tween(grpCapsules.members[curSelected].ranking, {"scale.x": 1, "scale.y": 1}, 0.1); - grpCapsules.members[curSelected].blurredRanking.animation.play(fromResults.newRank.getFreeplayRankIconAsset(), true); + if (fromResults?.newRank != null) { + grpCapsules.members[curSelected].blurredRanking.animation.play(fromResults.newRank.getFreeplayRankIconAsset(), true); + } FlxTween.tween(grpCapsules.members[curSelected].blurredRanking, {"scale.x": 1, "scale.y": 1}, 0.1); new FlxTimer().start(0.1, _ -> { - // trace(grpCapsules.members[curSelected].ranking.rank); if (fromResults?.oldRank != null) { grpCapsules.members[curSelected].fakeRanking.visible = false; @@ -994,7 +1011,6 @@ class FreeplayState extends MusicBeatSubState FunkinSound.playOnce(Paths.sound('ranks/rankinnormal')); } rankCamera.zoom = 1.3; - // FlxTween.tween(rankCamera, {"zoom": 1.4}, 0.3, {ease: FlxEase.elasticOut}); FlxTween.tween(rankCamera, {"zoom": 1.5}, 0.3, {ease: FlxEase.backInOut}); @@ -1012,13 +1028,11 @@ class FreeplayState extends MusicBeatSubState new FlxTimer().start(0.4, _ -> { FlxTween.tween(funnyCam, {"zoom": 1}, 0.8, {ease: FlxEase.sineIn}); FlxTween.tween(rankCamera, {"zoom": 1.2}, 0.8, {ease: FlxEase.backIn}); - // IntervalShake.shake(grpCapsules.members[curSelected], 0.8 + 0.5, 1 / 24, 0, 2, FlxEase.quadIn); FlxTween.tween(grpCapsules.members[curSelected], {x: originalPos.x - 7, y: originalPos.y - 80}, 0.8 + 0.5, {ease: FlxEase.quartIn}); }); new FlxTimer().start(0.6, _ -> { rankAnimSlam(fromResults); - // IntervalShake.shake(grpCapsules.members[curSelected].capsule, 0.3, 1 / 30, 0, 0.3, FlxEase.quartIn); }); } @@ -1183,51 +1197,9 @@ class FreeplayState extends MusicBeatSubState // { // rankAnimSlam(fromResultsParams); // } - - if (FlxG.keys.justPressed.G) - { - sparks.y -= 2; - trace(sparks.x, sparks.y); - } - if (FlxG.keys.justPressed.V) - { - sparks.x -= 2; - trace(sparks.x, sparks.y); - } - if (FlxG.keys.justPressed.N) - { - sparks.x += 2; - trace(sparks.x, sparks.y); - } - if (FlxG.keys.justPressed.B) - { - sparks.y += 2; - trace(sparks.x, sparks.y); - } - - if (FlxG.keys.justPressed.I) - { - sparksADD.y -= 2; - trace(sparksADD.x, sparksADD.y); - } - if (FlxG.keys.justPressed.J) - { - sparksADD.x -= 2; - trace(sparksADD.x, sparksADD.y); - } - if (FlxG.keys.justPressed.L) - { - sparksADD.x += 2; - trace(sparksADD.x, sparksADD.y); - } - if (FlxG.keys.justPressed.K) - { - sparksADD.y += 2; - trace(sparksADD.x, sparksADD.y); - } #end - if (FlxG.keys.justPressed.F && !busy) + if (controls.FREEPLAY_FAVORITE && !busy) { var targetSong = grpCapsules.members[curSelected]?.songData; if (targetSong != null) @@ -1571,6 +1543,8 @@ class FreeplayState extends MusicBeatSubState { clearDaCache(daSong.songName); } + // remove and destroy freeplay camera + FlxG.cameras.remove(funnyCam); } function changeDiff(change:Int = 0, force:Bool = false):Void @@ -1591,7 +1565,19 @@ class FreeplayState extends MusicBeatSubState var daSong:Null = grpCapsules.members[curSelected].songData; if (daSong != null) { - var songScore:SaveScoreData = Save.instance.getSongScore(grpCapsules.members[curSelected].songData.songId, currentDifficulty); + // TODO: Make this actually be the variation you're focused on. We don't need to fetch the song metadata just to calculate it. + var targetSong:Song = SongRegistry.instance.fetchEntry(grpCapsules.members[curSelected].songData.songId); + if (targetSong == null) + { + FlxG.log.warn('WARN: could not find song with id (${grpCapsules.members[curSelected].songData.songId})'); + return; + } + var targetVariation:String = targetSong.getFirstValidVariation(currentDifficulty); + + // TODO: This line of code makes me sad, but you can't really fix it without a breaking migration. + var suffixedDifficulty = (targetVariation != Constants.DEFAULT_VARIATION + && targetVariation != 'erect') ? '$currentDifficulty-${targetVariation}' : currentDifficulty; + var songScore:SaveScoreData = Save.instance.getSongScore(grpCapsules.members[curSelected].songData.songId, suffixedDifficulty); intendedScore = songScore?.score ?? 0; intendedCompletion = songScore == null ? 0.0 : ((songScore.tallies.sick + songScore.tallies.good) / songScore.tallies.totalNotes); rememberedDifficulty = currentDifficulty; @@ -1827,12 +1813,12 @@ class FreeplayState extends MusicBeatSubState function changeSelection(change:Int = 0):Void { - if (!prepForNewRank) FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4); - var prevSelected:Int = curSelected; curSelected += change; + if (!prepForNewRank && curSelected != prevSelected) FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4); + if (curSelected < 0) curSelected = grpCapsules.countLiving() - 1; if (curSelected >= grpCapsules.countLiving()) curSelected = 0; @@ -2086,7 +2072,7 @@ class FreeplaySongData this.songDifficulties = song.listDifficulties(null, variations, false, false); if (!this.songDifficulties.contains(currentDifficulty)) currentDifficulty = Constants.DEFAULT_DIFFICULTY; - var songDifficulty:SongDifficulty = song.getDifficulty(currentDifficulty, variations); + var songDifficulty:SongDifficulty = song.getDifficulty(currentDifficulty, null, variations); if (songDifficulty == null) return; this.songStartingBpm = songDifficulty.getStartingBPM(); this.songName = songDifficulty.songName; diff --git a/source/funkin/ui/freeplay/LetterSort.hx b/source/funkin/ui/freeplay/LetterSort.hx index e813c9198..049e9194a 100644 --- a/source/funkin/ui/freeplay/LetterSort.hx +++ b/source/funkin/ui/freeplay/LetterSort.hx @@ -8,6 +8,7 @@ import flixel.tweens.FlxTween; import flixel.tweens.FlxEase; import flixel.util.FlxColor; import flixel.util.FlxTimer; +import funkin.input.Controls; import funkin.graphics.adobeanimate.FlxAtlasSprite; class LetterSort extends FlxTypedSpriteGroup @@ -69,14 +70,19 @@ class LetterSort extends FlxTypedSpriteGroup changeSelection(0); } + var controls(get, never):Controls; + + inline function get_controls():Controls + return PlayerSettings.player1.controls; + override function update(elapsed:Float):Void { super.update(elapsed); if (inputEnabled) { - if (FlxG.keys.justPressed.E) changeSelection(1); - if (FlxG.keys.justPressed.Q) changeSelection(-1); + if (controls.FREEPLAY_LEFT) changeSelection(-1); + if (controls.FREEPLAY_RIGHT) changeSelection(1); } } diff --git a/source/funkin/ui/freeplay/SongMenuItem.hx b/source/funkin/ui/freeplay/SongMenuItem.hx index dc30b4345..7708b3bcf 100644 --- a/source/funkin/ui/freeplay/SongMenuItem.hx +++ b/source/funkin/ui/freeplay/SongMenuItem.hx @@ -219,7 +219,7 @@ class SongMenuItem extends FlxSpriteGroup favIconBlurred.visible = false; add(favIconBlurred); - favIcon = new FlxSprite(380, 40); + favIcon = new FlxSprite(favIconBlurred.x, favIconBlurred.y); favIcon.frames = Paths.getSparrowAtlas('freeplay/favHeart'); favIcon.animation.addByPrefix('fav', 'favorite heart', 24, false); favIcon.animation.play('fav'); @@ -294,21 +294,34 @@ class SongMenuItem extends FlxSpriteGroup } } - // 255, 27 normal - // 220, 27 favourited + /** + * Checks whether the song is favorited, and/or has a rank, and adjusts the clipping + * for the scenario when the text could be too long + */ public function checkClip():Void { var clipSize:Int = 290; var clipType:Int = 0; - if (ranking.visible == true) clipType += 1; - if (favIcon.visible == true) clipType = 2; + if (ranking.visible) + { + favIconBlurred.x = this.x + 370; + favIcon.x = favIconBlurred.x; + clipType += 1; + } + else + { + favIconBlurred.x = favIcon.x = this.x + 405; + } + + if (favIcon.visible) clipType += 1; + switch (clipType) { case 2: - clipSize = 220; + clipSize = 210; case 1: - clipSize = 255; + clipSize = 245; } songText.clipWidth = clipSize; } diff --git a/source/funkin/ui/mainmenu/MainMenuState.hx b/source/funkin/ui/mainmenu/MainMenuState.hx index b6ec25e61..d09536eea 100644 --- a/source/funkin/ui/mainmenu/MainMenuState.hx +++ b/source/funkin/ui/mainmenu/MainMenuState.hx @@ -371,6 +371,33 @@ class MainMenuState extends MusicBeatState } }); } + + if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.R) + { + // Give the user a hypothetical overridden score, + // and see if we can maintain that golden P rank. + funkin.save.Save.instance.setSongScore('tutorial', 'easy', + { + score: 1234567, + tallies: + { + sick: 0, + good: 0, + bad: 0, + shit: 1, + missed: 0, + combo: 0, + maxCombo: 0, + totalNotesHit: 1, + totalNotes: 10, + } + }); + } + + if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.E) + { + funkin.save.Save.instance.debug_dumpSave(); + } #end if (FlxG.sound.music != null && FlxG.sound.music.volume < 0.8) diff --git a/source/funkin/ui/options/ControlsMenu.hx b/source/funkin/ui/options/ControlsMenu.hx index dd7d5ff38..1f40a8455 100644 --- a/source/funkin/ui/options/ControlsMenu.hx +++ b/source/funkin/ui/options/ControlsMenu.hx @@ -28,6 +28,8 @@ class ControlsMenu extends funkin.ui.options.OptionsState.Page [NOTE_UP, NOTE_DOWN, NOTE_LEFT, NOTE_RIGHT], [UI_UP, UI_DOWN, UI_LEFT, UI_RIGHT, ACCEPT, BACK], [CUTSCENE_ADVANCE], + [FREEPLAY_FAVORITE, FREEPLAY_LEFT, FREEPLAY_RIGHT], + [WINDOW_FULLSCREEN, WINDOW_SCREENSHOT], [VOLUME_UP, VOLUME_DOWN, VOLUME_MUTE], [DEBUG_MENU, DEBUG_CHART] ]; @@ -108,6 +110,18 @@ class ControlsMenu extends funkin.ui.options.OptionsState.Page headers.add(new AtlasText(0, y, "CUTSCENE", AtlasFont.BOLD)).screenCenter(X); y += spacer; } + else if (currentHeader != "FREEPLAY_" && name.indexOf("FREEPLAY_") == 0) + { + currentHeader = "FREEPLAY_"; + headers.add(new AtlasText(0, y, "FREEPLAY", AtlasFont.BOLD)).screenCenter(X); + y += spacer; + } + else if (currentHeader != "WINDOW_" && name.indexOf("WINDOW_") == 0) + { + currentHeader = "WINDOW_"; + headers.add(new AtlasText(0, y, "WINDOW", AtlasFont.BOLD)).screenCenter(X); + y += spacer; + } else if (currentHeader != "VOLUME_" && name.indexOf("VOLUME_") == 0) { currentHeader = "VOLUME_"; @@ -123,10 +137,10 @@ class ControlsMenu extends funkin.ui.options.OptionsState.Page if (currentHeader != null && name.indexOf(currentHeader) == 0) name = name.substr(currentHeader.length); - var label = labels.add(new AtlasText(150, y, name, AtlasFont.BOLD)); + var label = labels.add(new AtlasText(100, y, name, AtlasFont.BOLD)); label.alpha = 0.6; for (i in 0...COLUMNS) - createItem(label.x + 400 + i * 300, y, control, i); + createItem(label.x + 550 + i * 400, y, control, i); y += spacer; } diff --git a/source/funkin/ui/options/OptionsState.hx b/source/funkin/ui/options/OptionsState.hx index 81331b266..40308d96b 100644 --- a/source/funkin/ui/options/OptionsState.hx +++ b/source/funkin/ui/options/OptionsState.hx @@ -25,6 +25,8 @@ class OptionsState extends MusicBeatState override function create():Void { + persistentUpdate = true; + var menuBG = new FlxSprite().loadGraphic(Paths.image('menuBG')); var hsv = new HSVShader(); hsv.hue = -0.6; @@ -55,8 +57,6 @@ class OptionsState extends MusicBeatState setPage(Controls); } - // disable for intro transition - currentPage.enabled = false; super.create(); } @@ -86,13 +86,6 @@ class OptionsState extends MusicBeatState } } - override function finishTransIn() - { - super.finishTransIn(); - - currentPage.enabled = true; - } - function switchPage(name:PageName) { // TODO: Animate this transition? @@ -266,11 +259,11 @@ class OptionsMenu extends Page #end } -enum PageName +enum abstract PageName(String) { - Options; - Controls; - Colors; - Mods; - Preferences; + var Options = "options"; + var Controls = "controls"; + var Colors = "colors"; + var Mods = "mods"; + var Preferences = "preferences"; } diff --git a/source/funkin/ui/story/StoryMenuState.hx b/source/funkin/ui/story/StoryMenuState.hx index c1a001e5d..06a83ab4d 100644 --- a/source/funkin/ui/story/StoryMenuState.hx +++ b/source/funkin/ui/story/StoryMenuState.hx @@ -387,6 +387,7 @@ class StoryMenuState extends MusicBeatState function changeLevel(change:Int = 0):Void { var currentIndex:Int = levelList.indexOf(currentLevelId); + var prevIndex:Int = currentIndex; currentIndex += change; @@ -417,7 +418,7 @@ class StoryMenuState extends MusicBeatState } } - FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4); + if (currentIndex != prevIndex) FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4); updateText(); updateBackground(previousLevelId); diff --git a/source/funkin/ui/title/TitleState.hx b/source/funkin/ui/title/TitleState.hx index c6dbcd505..8087916cb 100644 --- a/source/funkin/ui/title/TitleState.hx +++ b/source/funkin/ui/title/TitleState.hx @@ -265,6 +265,13 @@ class TitleState extends MusicBeatState if (FlxG.keys.pressed.DOWN) FlxG.sound.music.pitch -= 0.5 * elapsed; #end + #if desktop + if (FlxG.keys.justPressed.ESCAPE) + { + Sys.exit(0); + } + #end + Conductor.instance.update(); /* if (FlxG.onMobile) diff --git a/source/funkin/util/FileUtil.hx b/source/funkin/util/FileUtil.hx index 7a7b1422c..00a0a14b7 100644 --- a/source/funkin/util/FileUtil.hx +++ b/source/funkin/util/FileUtil.hx @@ -19,6 +19,7 @@ import haxe.ui.containers.dialogs.Dialogs.FileDialogExtensionInfo; class FileUtil { public static final FILE_FILTER_FNFC:FileFilter = new FileFilter("Friday Night Funkin' Chart (.fnfc)", "*.fnfc"); + public static final FILE_FILTER_JSON:FileFilter = new FileFilter("JSON Data File (.json)", "*.json"); public static final FILE_FILTER_ZIP:FileFilter = new FileFilter("ZIP Archive (.zip)", "*.zip"); public static final FILE_FILTER_PNG:FileFilter = new FileFilter("PNG Image (.png)", "*.png"); diff --git a/source/funkin/util/WindowUtil.hx b/source/funkin/util/WindowUtil.hx index 07f6bc13a..0fe63fe32 100644 --- a/source/funkin/util/WindowUtil.hx +++ b/source/funkin/util/WindowUtil.hx @@ -92,7 +92,7 @@ class WindowUtil }); openfl.Lib.current.stage.addEventListener(openfl.events.KeyboardEvent.KEY_DOWN, (e:openfl.events.KeyboardEvent) -> { - for (key in PlayerSettings.player1.controls.getKeysForAction(FULLSCREEN)) + for (key in PlayerSettings.player1.controls.getKeysForAction(WINDOW_FULLSCREEN)) { if (e.keyCode == key) { diff --git a/source/funkin/util/plugins/ScreenshotPlugin.hx b/source/funkin/util/plugins/ScreenshotPlugin.hx index 9ac21d4b8..c859710de 100644 --- a/source/funkin/util/plugins/ScreenshotPlugin.hx +++ b/source/funkin/util/plugins/ScreenshotPlugin.hx @@ -103,7 +103,7 @@ class ScreenshotPlugin extends FlxBasic public function hasPressedScreenshot():Bool { - return PlayerSettings.player1.controls.SCREENSHOT; + return PlayerSettings.player1.controls.WINDOW_SCREENSHOT; } public function updatePreferences():Void