Merge branch 'rewrite/master' into bugfix/instrumental-id

This commit is contained in:
Cameron Taylor 2024-06-11 16:12:39 -04:00
commit c05f053fe3
35 changed files with 724 additions and 395 deletions

View file

@ -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!)
<!-- FILL THIS ISSUE THING OUT AS MUCH AS POSSIBLE
OR ELSE YOUR ISSUE WILL BE LESS LIKELY TO BE SOLVED!
[weed]: <> (ALSO MAKE SURE THAT YOU USE PROPER LABELS, IF YOU'RE RUNNING INTO COMPILER ISSUES, USE THE compiler issue LABEL!!!)
Do not post about issues from other FNF mod engines!
We cannot and probably won't solve those!
You can hopefully go to their respective GitHub issues and report them there, thank you :)
#### Please check for duplicates or similar issues, as well performing simple troubleshooting steps (such as clearing cookies, clearing AppData, trying another browser) before submitting an issue.
### If you are playing the game in a browser, what site are you playing it from?
Please check for duplicates or similar issues, as well performing simple troubleshooting steps (such as clearing cookies, clearing AppData, trying another browser) before submitting an issue.
[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)
From Joel On Software:
- [ ] [Newgrounds](https://www.newgrounds.com/portal/view/770371)
- [ ] [Itch.io](https://ninja-muffin24.itch.io/funkin)? Specify below
- - [ ] Windows
- - [ ] Mac
- - [ ] Linux
"Its pretty easy to remember the rule for a good bug report. Every good bug report needs exactly three things.
### If you are playing the game in a browser, what browser are you using?
1. Steps to reproduce,
2. What you expected to see, and
3. What you saw instead."
[weed]: <> (Again, put an x in the [ ] box!)
-->
- [ ] Google Chrome (or chomium based like Brave, vivaldi, MS Edge)
- [ ] Firefox
- [ ] Safari
## Describe the bug
<!-- A clear and concise description of what the bug is. -->
## 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
<!-- Describe in DETAIL how to reproduce the bug/issue you are running into. -->
## Expected behavior
<!-- A clear and concise description of what you expected to happen. -->
## Screenshots/Video
<!-- If applicable, add screenshots/video to help explain your problem.
Remember to mark the area in the application thats impacted. -->
## 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:
<!-- [e.g. Windows 10, 11, Mac, Linux Mint, Ubuntu, Arch (btw)] -->
- Browser
<!-- [e.g. chrome, safari, firefox, edge, operaGX] -->
- Version:
<!-- [e.g. 0.4.0, 0.3.3, this can be found in the bottom left corner of the main menu!] -->
## Please describe your issue. Provide extensive detail and images if possible.
## Additional context
<!-- Add any other context about the problem here. -->
## 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
<!-- 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 -->

View file

@ -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.

View file

@ -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?

View file

@ -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

View file

@ -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)

2
assets

@ -1 +1 @@
Subproject commit 4427687c487d643c6051f8c4610470326c87ea4d
Subproject commit c22ed15e83182f628d7830773021e3e48c596174

View file

@ -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`.

View file

@ -227,12 +227,12 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
// 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<FunkinSound>
*/
override function onFocusLost():Void
{
trace('Focus lost, pausing audio!');
// trace('Focus lost, pausing audio!');
_lostFocus = true;
_alreadyPaused = _paused;
pause();

View file

@ -54,12 +54,12 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
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;

View file

@ -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<String, FunkinAction> = new Map<String, FunkinAction>();
@ -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<FlxGamepadInputID> {
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";

View file

@ -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 + ')';
}
}

View file

@ -71,7 +71,7 @@ class GameOverSubState extends MusicBeatSubState
var gameOverMusic:Null<FunkinSound> = 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...

View file

@ -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;

View file

@ -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<SaveScoreData> = Save.instance.getSongScore(currentSong.id, currentDifficulty);
var prevScoreData:Null<SaveScoreData> = 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)

View file

@ -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.

View file

@ -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<ScoringRank>, b:Null<ScoringRank>):Bool
/**
* Converts ScoringRank to an integer value for comparison.
* Better ranks should be tied to a higher value.
*/
static function getValue(rank:Null<ScoringRank>):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<ScoringRank>, b:Null<ScoringRank>):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<ScoringRank>, b:Null<ScoringRank>):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<ScoringRank>, b:Null<ScoringRank>):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<ScoringRank>, b:Null<ScoringRank>):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;
}

View file

@ -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<RawSaveData>(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<String>;
// TODO: Make this not trip up the serializer when debugging.
@:jignored
var modOptions:Map<String, Dynamic>;
}

View file

@ -94,7 +94,7 @@ class MenuTypedList<T:MenuListItem> extends FlxTypedGroup<T>
if (newIndex != selectedIndex)
{
FunkinSound.playOnce(Paths.sound('scrollMenu'));
FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4);
selectItem(newIndex);
}
@ -142,7 +142,7 @@ class MenuTypedList<T:MenuListItem> extends FlxTypedGroup<T>
*/
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);

View file

@ -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!

View file

@ -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.

View file

@ -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),

View file

@ -58,17 +58,8 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
function initialize():Void
{
toolboxEventsEventKind.dataSource = new ArrayDataSource();
var songEvents:Array<SongEvent> = 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<Dynamic> = 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();

View file

@ -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<SongEvent> = 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<DropDownEntry>
{
// 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.
*/

View file

@ -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));

View file

@ -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<String> = 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<FreeplaySongData> = 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;

View file

@ -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<FlxSprite>
@ -69,14 +70,19 @@ class LetterSort extends FlxTypedSpriteGroup<FlxSprite>
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);
}
}

View file

@ -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;
}

View file

@ -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)

View file

@ -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;
}

View file

@ -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";
}

View file

@ -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);

View file

@ -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)

View file

@ -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");

View file

@ -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)
{

View file

@ -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