1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2025-11-26 06:09:02 +00:00

Merge branch 'rewrite/master'

This commit is contained in:
Cameron Taylor 2024-06-12 15:26:40 -04:00
commit ead37894cd
37 changed files with 797 additions and 360 deletions

4
.gitmodules vendored
View file

@ -1,6 +1,6 @@
[submodule "assets"] [submodule "assets"]
path = assets path = assets
url = https://github.com/FunkinCrew/funkin.assets url = https://github.com/FunkinCrew/Funkin-Assets-secret
[submodule "art"] [submodule "art"]
path = art path = art
url = https://github.com/FunkinCrew/funkin.art url = https://github.com/FunkinCrew/Funkin-Art-secret

View file

@ -4,6 +4,35 @@ All notable changes will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.4.1] - 2024-06-12
### Added
- Pressing ESCAPE on the title screen on desktop now exits the game, allowing you to exit the game while in fullscreen on desktop
- Freeplay menu controls (favoriting and switching categories) are now rebindable from the Options menu, and now have default binds on controllers.
### Changed
- Highscores and ranks are now saved separately, which fixes the issue where people would overwrite their saves with higher scores,
which would remove their rank if they had a lower one.
- A-Bot speaker now reacts to the user's volume preference on desktop (thanks to [M7theguy for the issue report/suggestion](https://github.com/FunkinCrew/Funkin/issues/2744)!)
- On Freeplay, heart icons are shifted to the right when you favorite a song that has no rank on it.
- Only play `scrollMenu` sound effect when there's a real change on the freeplay menu ([thanks gamerbross for the PR!](https://github.com/FunkinCrew/Funkin/pull/2741))
- Gave antialiasing to the edge of the dad graphic on Freeplay
- Rearranged some controls in the controls menu
- Made several chart revisions
- Re-enabled custom camera events in Roses (Erect/Nightmare)
- Tweaked the chart for Lit Up (Hard)
- Corrected the difficulty ratings for M.I.L.F. (Easy/Normal/Hard)
### Fixed
- Fixed an issue in the controls menu where some control binds would overlap their names
- Fixed crash when attempting to exit the gameover screen when also attempting to retry the song ([thanks DMMaster636 for the PR!](https://github.com/FunkinCrew/Funkin/pull/2709))
- Fix botplay sustain release bug ([thanks Hundrec!](Fix botplay sustain release bug #2683))
- Fix for the camera not pausing during a gameplay pause ([thanks gamerbross!](https://github.com/FunkinCrew/Funkin/pull/2684))
- Fixed issue where Pico's gameplay sprite would unintentionally appear on the gameover screen when dying on 2Hot from an explosion
- Freeplay previews properly fade volume during the BF idle animation
- Fixed bug where Dadbattle incorrectly appeared as Dadbattle Erect when returning to freeplay on Hard
- 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 the black "temp" graphic on freeplay from being incorrectly sized / masked, now it's identical to the dad freeplay graphic
## [0.4.0] - 2024-06-06 ## [0.4.0] - 2024-06-06
### Added ### Added
- 2 new Erect remixes, Eggnog and Satin Panties. Check them out from the Freeplay menu! - 2 new Erect remixes, Eggnog and Satin Panties. Check them out from the Freeplay menu!
@ -32,11 +61,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- 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!) - 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!) - 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. - 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
- Fixed an issue where Nene's visualizer would not play on Desktop builds - 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 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 - 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!)
- Fixed a bug where changing difficulties in Story mode wouldn't update the score (thanks sectorA!)
- Fixed a crash in Freeplay caused by a level referencing an invalid song (thanks gamerbross!)
- 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!)
- 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 hold notes would be positioned wrong on downscroll (thanks MaybeMaru!)
- Removed a large number of unused imports to optimize builds (thanks Ethan-makes-music!)
- Improved debug logging for unscripted stages (thanks gamerbross!)
- Made improvements to compiling documentation (thanks gedehari!)
- Fixed a crash on Linux caused by an old version of hxCodec (thanks Noobz4Life!) - Fixed a crash on Linux caused by an old version of hxCodec (thanks Noobz4Life!)
- Optimized animation handling for characters (thanks richTrash21!) - Optimized animation handling for characters (thanks richTrash21!)
- Made improvements to compiling documentation (thanks gedehari!) - Made improvements to compiling documentation (thanks gedehari!)

View file

@ -2,7 +2,7 @@
<project xmlns="http://lime.openfl.org/project/1.0.4" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <project xmlns="http://lime.openfl.org/project/1.0.4" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://lime.openfl.org/project/1.0.4 http://lime.openfl.org/xsd/project-1.0.4.xsd"> xsi:schemaLocation="http://lime.openfl.org/project/1.0.4 http://lime.openfl.org/xsd/project-1.0.4.xsd">
<!-- _________________________ Application Settings _________________________ --> <!-- _________________________ Application Settings _________________________ -->
<app title="Friday Night Funkin'" file="Funkin" packageName="com.funkin.fnf" package="com.funkin.fnf" main="Main" version="0.4.0" company="ninjamuffin99" /> <app title="Friday Night Funkin'" file="Funkin" packageName="com.funkin.fnf" package="com.funkin.fnf" main="Main" version="0.4.1" company="ninjamuffin99" />
<!--Switch Export with Unique ApplicationID and Icon--> <!--Switch Export with Unique ApplicationID and Icon-->
<set name="APP_ID" value="0x0100f6c013bbc000" /> <set name="APP_ID" value="0x0100f6c013bbc000" />

View file

@ -2,7 +2,7 @@
Friday Night Funkin' is a rhythm game. Built using HaxeFlixel for Ludum Dare 47. 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) - [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) - [Demo download builds for Windows, Mac, and Linux from Itch.io!](https://ninja-muffin24.itch.io/funkin)

2
art

@ -1 +1 @@
Subproject commit 66572f85d826ce2ec1d45468c12733b161237ffa Subproject commit faeba700c5526bd4fd57ccc927d875c82b9d3553

2
assets

@ -1 +1 @@
Subproject commit 3b8235e953505a6fe7f4ff253f5a99b9a7b9857a Subproject commit 2e1594ee4c04c7148628bae471bdd061c9deb6b7

View file

@ -23,3 +23,4 @@
# Troubleshooting # 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`. - 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. // already paused before we lost focus.
if (_lostFocus && !_alreadyPaused) if (_lostFocus && !_alreadyPaused)
{ {
trace('Resuming audio (${this._label}) on focus!'); // trace('Resuming audio (${this._label}) on focus!');
resume(); resume();
} }
else else
{ {
trace('Not resuming audio (${this._label}) on focus!'); // trace('Not resuming audio (${this._label}) on focus!');
} }
_lostFocus = false; _lostFocus = false;
} }
@ -242,7 +242,7 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
*/ */
override function onFocusLost():Void override function onFocusLost():Void
{ {
trace('Focus lost, pausing audio!'); // trace('Focus lost, pausing audio!');
_lostFocus = true; _lostFocus = true;
_alreadyPaused = _paused; _alreadyPaused = _paused;
pause(); pause();

View file

@ -54,12 +54,12 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
public function initAnalyzer() public function initAnalyzer()
{ {
@:privateAccess @:privateAccess
analyzer = new SpectralAnalyzer(snd._channel.__source, 7, 0.1, 30); analyzer = new SpectralAnalyzer(snd._channel.__source, 7, 0.1, 40);
#if desktop #if desktop
// On desktop it uses FFT stuff that isn't as optimized as the direct browser stuff we use on HTML5 // 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! // So we want to manually change it!
analyzer.fftN = 512; analyzer.fftN = 256;
#end #end
// analyzer.maxDb = -35; // analyzer.maxDb = -35;
@ -101,6 +101,10 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
{ {
var animFrame:Int = Math.round(levels[i].value * 5); var animFrame:Int = Math.round(levels[i].value * 5);
#if desktop
animFrame = Math.round(animFrame * FlxG.sound.volume);
#end
animFrame = Math.floor(Math.min(5, animFrame)); animFrame = Math.floor(Math.min(5, animFrame));
animFrame = Math.floor(Math.max(0, animFrame)); animFrame = Math.floor(Math.max(0, animFrame));

View file

@ -6,15 +6,28 @@ class AngleMask extends FlxShader
{ {
@:glFragmentSource(' @:glFragmentSource('
#pragma header #pragma header
uniform vec2 endPosition; uniform vec2 endPosition;
void main() vec2 hash22(vec2 p) {
{ vec3 p3 = fract(vec3(p.xyx) * vec3(.1031, .1030, .0973));
vec4 base = texture2D(bitmap, openfl_TextureCoordv); p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.xx + p3.yz) * p3.zy);
vec2 uv = openfl_TextureCoordv.xy; }
// ====== GAMMA CORRECTION ====== //
// Helps with color mixing -- good to have by default in almost any shader
// See https://www.shadertoy.com/view/lscSzl
vec3 gamma(in vec3 color) {
return pow(color, vec3(1.0 / 2.2));
}
vec4 mainPass(vec2 fragCoord) {
vec4 base = texture2D(bitmap, fragCoord);
vec2 uv = fragCoord.xy;
vec2 start = vec2(0.0, 0.0); vec2 start = vec2(0.0, 0.0);
vec2 end = vec2(endPosition.x / openfl_TextureSize.x, 1.0); vec2 end = vec2(endPosition.x / openfl_TextureSize.x, 1.0);
@ -29,10 +42,35 @@ class AngleMask extends FlxShader
float uvA = atan(uv.y, uv.x); float uvA = atan(uv.y, uv.x);
if (uvA < angle) if (uvA < angle)
gl_FragColor = base; return base;
else else
gl_FragColor = vec4(0.0); return vec4(0.0);
}
vec4 antialias(vec2 fragCoord) {
const float AA_STAGES = 2.0;
const float AA_TOTAL_PASSES = AA_STAGES * AA_STAGES + 1.0;
const float AA_JITTER = 0.5;
// Run the shader multiple times with a random subpixel offset each time and average the results
vec4 color = mainPass(fragCoord);
for (float x = 0.0; x < AA_STAGES; x++)
{
for (float y = 0.0; y < AA_STAGES; y++)
{
vec2 offset = AA_JITTER * (2.0 * hash22(vec2(x, y)) - 1.0) / openfl_TextureSize.xy;
color += mainPass(fragCoord + offset);
}
}
return color / AA_TOTAL_PASSES;
}
void main() {
vec4 col = antialias(openfl_TextureCoordv);
// col.xyz = gamma(col.xyz);
gl_FragColor = col;
}') }')
public function new() public function new()
{ {

View file

@ -58,7 +58,11 @@ class Controls extends FlxActionSet
var _back = new FunkinAction(Action.BACK); var _back = new FunkinAction(Action.BACK);
var _pause = new FunkinAction(Action.PAUSE); var _pause = new FunkinAction(Action.PAUSE);
var _reset = new FunkinAction(Action.RESET); 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 _cutscene_advance = new FunkinAction(Action.CUTSCENE_ADVANCE);
var _debug_menu = new FunkinAction(Action.DEBUG_MENU); var _debug_menu = new FunkinAction(Action.DEBUG_MENU);
var _debug_chart = new FunkinAction(Action.DEBUG_CHART); 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_up = new FunkinAction(Action.VOLUME_UP);
var _volume_down = new FunkinAction(Action.VOLUME_DOWN); var _volume_down = new FunkinAction(Action.VOLUME_DOWN);
var _volume_mute = new FunkinAction(Action.VOLUME_MUTE); var _volume_mute = new FunkinAction(Action.VOLUME_MUTE);
var _fullscreen = new FunkinAction(Action.FULLSCREEN);
var byName:Map<String, FunkinAction> = new Map<String, FunkinAction>(); var byName:Map<String, FunkinAction> = new Map<String, FunkinAction>();
@ -233,10 +236,30 @@ class Controls extends FlxActionSet
inline function get_RESET() inline function get_RESET()
return _reset.check(); return _reset.check();
public var SCREENSHOT(get, never):Bool; public var WINDOW_FULLSCREEN(get, never):Bool;
inline function get_SCREENSHOT() inline function get_WINDOW_FULLSCREEN()
return _screenshot.check(); 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; public var CUTSCENE_ADVANCE(get, never):Bool;
@ -273,11 +296,6 @@ class Controls extends FlxActionSet
inline function get_VOLUME_MUTE() inline function get_VOLUME_MUTE()
return _volume_mute.check(); return _volume_mute.check();
public var FULLSCREEN(get, never):Bool;
inline function get_FULLSCREEN()
return _fullscreen.check();
public function new(name, scheme:KeyboardScheme = null) public function new(name, scheme:KeyboardScheme = null)
{ {
super(name); super(name);
@ -294,7 +312,11 @@ class Controls extends FlxActionSet
add(_back); add(_back);
add(_pause); add(_pause);
add(_reset); add(_reset);
add(_screenshot); add(_window_screenshot);
add(_window_fullscreen);
add(_freeplay_favorite);
add(_freeplay_left);
add(_freeplay_right);
add(_cutscene_advance); add(_cutscene_advance);
add(_debug_menu); add(_debug_menu);
add(_debug_chart); add(_debug_chart);
@ -302,7 +324,6 @@ class Controls extends FlxActionSet
add(_volume_up); add(_volume_up);
add(_volume_down); add(_volume_down);
add(_volume_mute); add(_volume_mute);
add(_fullscreen);
for (action in digitalActions) { for (action in digitalActions) {
if (Std.isOfType(action, FunkinAction)) { if (Std.isOfType(action, FunkinAction)) {
@ -398,7 +419,11 @@ class Controls extends FlxActionSet
case BACK: _back; case BACK: _back;
case PAUSE: _pause; case PAUSE: _pause;
case RESET: _reset; 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 CUTSCENE_ADVANCE: _cutscene_advance;
case DEBUG_MENU: _debug_menu; case DEBUG_MENU: _debug_menu;
case DEBUG_CHART: _debug_chart; case DEBUG_CHART: _debug_chart;
@ -406,7 +431,6 @@ class Controls extends FlxActionSet
case VOLUME_UP: _volume_up; case VOLUME_UP: _volume_up;
case VOLUME_DOWN: _volume_down; case VOLUME_DOWN: _volume_down;
case VOLUME_MUTE: _volume_mute; case VOLUME_MUTE: _volume_mute;
case FULLSCREEN: _fullscreen;
} }
} }
@ -466,8 +490,16 @@ class Controls extends FlxActionSet
func(_pause, JUST_PRESSED); func(_pause, JUST_PRESSED);
case RESET: case RESET:
func(_reset, JUST_PRESSED); func(_reset, JUST_PRESSED);
case SCREENSHOT: case WINDOW_SCREENSHOT:
func(_screenshot, JUST_PRESSED); 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: case CUTSCENE_ADVANCE:
func(_cutscene_advance, JUST_PRESSED); func(_cutscene_advance, JUST_PRESSED);
case DEBUG_MENU: case DEBUG_MENU:
@ -482,8 +514,6 @@ class Controls extends FlxActionSet
func(_volume_down, JUST_PRESSED); func(_volume_down, JUST_PRESSED);
case VOLUME_MUTE: case VOLUME_MUTE:
func(_volume_mute, JUST_PRESSED); 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.BACK, getDefaultKeybinds(scheme, Control.BACK));
bindKeys(Control.PAUSE, getDefaultKeybinds(scheme, Control.PAUSE)); bindKeys(Control.PAUSE, getDefaultKeybinds(scheme, Control.PAUSE));
bindKeys(Control.RESET, getDefaultKeybinds(scheme, Control.RESET)); 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.CUTSCENE_ADVANCE, getDefaultKeybinds(scheme, Control.CUTSCENE_ADVANCE));
bindKeys(Control.DEBUG_MENU, getDefaultKeybinds(scheme, Control.DEBUG_MENU)); bindKeys(Control.DEBUG_MENU, getDefaultKeybinds(scheme, Control.DEBUG_MENU));
bindKeys(Control.DEBUG_CHART, getDefaultKeybinds(scheme, Control.DEBUG_CHART)); 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_UP, getDefaultKeybinds(scheme, Control.VOLUME_UP));
bindKeys(Control.VOLUME_DOWN, getDefaultKeybinds(scheme, Control.VOLUME_DOWN)); bindKeys(Control.VOLUME_DOWN, getDefaultKeybinds(scheme, Control.VOLUME_DOWN));
bindKeys(Control.VOLUME_MUTE, getDefaultKeybinds(scheme, Control.VOLUME_MUTE)); bindKeys(Control.VOLUME_MUTE, getDefaultKeybinds(scheme, Control.VOLUME_MUTE));
bindKeys(Control.FULLSCREEN, getDefaultKeybinds(scheme, Control.FULLSCREEN));
bindMobileLol(); bindMobileLol();
} }
@ -707,7 +740,11 @@ class Controls extends FlxActionSet
case Control.BACK: return [X, BACKSPACE, ESCAPE]; case Control.BACK: return [X, BACKSPACE, ESCAPE];
case Control.PAUSE: return [P, ENTER, ESCAPE]; case Control.PAUSE: return [P, ENTER, ESCAPE];
case Control.RESET: return [R]; 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.CUTSCENE_ADVANCE: return [Z, ENTER];
case Control.DEBUG_MENU: return [GRAVEACCENT]; case Control.DEBUG_MENU: return [GRAVEACCENT];
case Control.DEBUG_CHART: return []; case Control.DEBUG_CHART: return [];
@ -715,8 +752,6 @@ class Controls extends FlxActionSet
case Control.VOLUME_UP: return [PLUS, NUMPADPLUS]; case Control.VOLUME_UP: return [PLUS, NUMPADPLUS];
case Control.VOLUME_DOWN: return [MINUS, NUMPADMINUS]; case Control.VOLUME_DOWN: return [MINUS, NUMPADMINUS];
case Control.VOLUME_MUTE: return [ZERO, NUMPADZERO]; case Control.VOLUME_MUTE: return [ZERO, NUMPADZERO];
case Control.FULLSCREEN: return [FlxKey.F11]; // We use F for other things LOL.
} }
case Duo(true): case Duo(true):
switch (control) { switch (control) {
@ -732,7 +767,11 @@ class Controls extends FlxActionSet
case Control.BACK: return [H, X]; case Control.BACK: return [H, X];
case Control.PAUSE: return [ONE]; case Control.PAUSE: return [ONE];
case Control.RESET: return [R]; 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.CUTSCENE_ADVANCE: return [G, Z];
case Control.DEBUG_MENU: return [GRAVEACCENT]; case Control.DEBUG_MENU: return [GRAVEACCENT];
case Control.DEBUG_CHART: return []; case Control.DEBUG_CHART: return [];
@ -740,7 +779,6 @@ class Controls extends FlxActionSet
case Control.VOLUME_UP: return [PLUS]; case Control.VOLUME_UP: return [PLUS];
case Control.VOLUME_DOWN: return [MINUS]; case Control.VOLUME_DOWN: return [MINUS];
case Control.VOLUME_MUTE: return [ZERO]; case Control.VOLUME_MUTE: return [ZERO];
case Control.FULLSCREEN: return [FlxKey.F];
} }
case Duo(false): case Duo(false):
@ -757,15 +795,18 @@ class Controls extends FlxActionSet
case Control.BACK: return [ESCAPE]; case Control.BACK: return [ESCAPE];
case Control.PAUSE: return [ONE]; case Control.PAUSE: return [ONE];
case Control.RESET: return [R]; 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.CUTSCENE_ADVANCE: return [ENTER];
case Control.DEBUG_MENU: return [GRAVEACCENT]; case Control.DEBUG_MENU: return [];
case Control.DEBUG_CHART: return []; case Control.DEBUG_CHART: return [];
case Control.DEBUG_STAGE: return []; case Control.DEBUG_STAGE: return [];
case Control.VOLUME_UP: return [NUMPADPLUS]; case Control.VOLUME_UP: return [NUMPADPLUS];
case Control.VOLUME_DOWN: return [NUMPADMINUS]; case Control.VOLUME_DOWN: return [NUMPADMINUS];
case Control.VOLUME_MUTE: return [NUMPADZERO]; case Control.VOLUME_MUTE: return [NUMPADZERO];
case Control.FULLSCREEN: return [];
} }
default: default:
@ -856,34 +897,37 @@ class Controls extends FlxActionSet
public function addDefaultGamepad(id):Void public function addDefaultGamepad(id):Void
{ {
addGamepadLiteral(id, [ addGamepadLiteral(id, [
Control.ACCEPT => getDefaultGamepadBinds(Control.ACCEPT), Control.ACCEPT => getDefaultGamepadBinds(Control.ACCEPT),
Control.BACK => getDefaultGamepadBinds(Control.BACK), Control.BACK => getDefaultGamepadBinds(Control.BACK),
Control.UI_UP => getDefaultGamepadBinds(Control.UI_UP), Control.UI_UP => getDefaultGamepadBinds(Control.UI_UP),
Control.UI_DOWN => getDefaultGamepadBinds(Control.UI_DOWN), Control.UI_DOWN => getDefaultGamepadBinds(Control.UI_DOWN),
Control.UI_LEFT => getDefaultGamepadBinds(Control.UI_LEFT), Control.UI_LEFT => getDefaultGamepadBinds(Control.UI_LEFT),
Control.UI_RIGHT => getDefaultGamepadBinds(Control.UI_RIGHT), 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_UP => getDefaultGamepadBinds(Control.NOTE_UP),
Control.NOTE_DOWN => getDefaultGamepadBinds(Control.NOTE_DOWN), Control.NOTE_DOWN => getDefaultGamepadBinds(Control.NOTE_DOWN),
Control.NOTE_LEFT => getDefaultGamepadBinds(Control.NOTE_LEFT), Control.NOTE_LEFT => getDefaultGamepadBinds(Control.NOTE_LEFT),
Control.NOTE_RIGHT => getDefaultGamepadBinds(Control.NOTE_RIGHT), Control.NOTE_RIGHT => getDefaultGamepadBinds(Control.NOTE_RIGHT),
Control.PAUSE => getDefaultGamepadBinds(Control.PAUSE), Control.PAUSE => getDefaultGamepadBinds(Control.PAUSE),
Control.RESET => getDefaultGamepadBinds(Control.RESET), Control.RESET => getDefaultGamepadBinds(Control.RESET),
// Control.SCREENSHOT => [], Control.WINDOW_FULLSCREEN => getDefaultGamepadBinds(Control.WINDOW_FULLSCREEN),
// Control.VOLUME_UP => [RIGHT_SHOULDER], Control.WINDOW_SCREENSHOT => getDefaultGamepadBinds(Control.WINDOW_SCREENSHOT),
// Control.VOLUME_DOWN => [LEFT_SHOULDER],
// Control.VOLUME_MUTE => [RIGHT_TRIGGER],
Control.CUTSCENE_ADVANCE => getDefaultGamepadBinds(Control.CUTSCENE_ADVANCE), Control.CUTSCENE_ADVANCE => getDefaultGamepadBinds(Control.CUTSCENE_ADVANCE),
// Control.DEBUG_MENU Control.FREEPLAY_FAVORITE => getDefaultGamepadBinds(Control.FREEPLAY_FAVORITE),
// Control.DEBUG_CHART 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> { function getDefaultGamepadBinds(control:Control):Array<FlxGamepadInputID> {
switch(control) { switch(control) {
case Control.ACCEPT: return [#if switch B #else A #end]; 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_UP: return [DPAD_UP, LEFT_STICK_DIGITAL_UP];
case Control.UI_DOWN: return [DPAD_DOWN, LEFT_STICK_DIGITAL_DOWN]; case Control.UI_DOWN: return [DPAD_DOWN, LEFT_STICK_DIGITAL_DOWN];
case Control.UI_LEFT: return [DPAD_LEFT, LEFT_STICK_DIGITAL_LEFT]; 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_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.NOTE_RIGHT: return [DPAD_RIGHT, B, LEFT_STICK_DIGITAL_RIGHT, RIGHT_STICK_DIGITAL_RIGHT];
case Control.PAUSE: return [START]; case Control.PAUSE: return [START];
case Control.RESET: return [RIGHT_SHOULDER]; case Control.RESET: return [FlxGamepadInputID.BACK]; // Back (i.e. Select)
case Control.SCREENSHOT: return []; case Control.WINDOW_FULLSCREEN: [];
case Control.VOLUME_UP: return []; case Control.WINDOW_SCREENSHOT: [];
case Control.VOLUME_DOWN: return [];
case Control.VOLUME_MUTE: return [];
case Control.CUTSCENE_ADVANCE: return [A]; case Control.CUTSCENE_ADVANCE: return [A];
case Control.DEBUG_MENU: return []; case Control.FREEPLAY_FAVORITE: [FlxGamepadInputID.BACK]; // Back (i.e. Select)
case Control.DEBUG_CHART: return []; case Control.FREEPLAY_LEFT: [LEFT_SHOULDER];
case Control.FULLSCREEN: return []; 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: default:
// Fallthrough. // Fallthrough.
} }
@ -1425,14 +1473,18 @@ enum Control
UI_RIGHT; UI_RIGHT;
UI_DOWN; UI_DOWN;
RESET; RESET;
SCREENSHOT;
ACCEPT; ACCEPT;
BACK; BACK;
PAUSE; PAUSE;
FULLSCREEN;
// CUTSCENE // CUTSCENE
CUTSCENE_ADVANCE; CUTSCENE_ADVANCE;
// SCREENSHOT // FREEPLAY
FREEPLAY_FAVORITE;
FREEPLAY_LEFT;
FREEPLAY_RIGHT;
// WINDOW
WINDOW_SCREENSHOT;
WINDOW_FULLSCREEN;
// VOLUME // VOLUME
VOLUME_UP; VOLUME_UP;
VOLUME_DOWN; VOLUME_DOWN;
@ -1475,11 +1527,15 @@ enum abstract Action(String) to String from String
var BACK = "back"; var BACK = "back";
var PAUSE = "pause"; var PAUSE = "pause";
var RESET = "reset"; var RESET = "reset";
var FULLSCREEN = "fullscreen"; // WINDOW
// SCREENSHOT var WINDOW_FULLSCREEN = "window_fullscreen";
var SCREENSHOT = "screenshot"; var WINDOW_SCREENSHOT = "window_screenshot";
// CUTSCENE // CUTSCENE
var CUTSCENE_ADVANCE = "cutscene_advance"; var CUTSCENE_ADVANCE = "cutscene_advance";
// FREEPLAY
var FREEPLAY_FAVORITE = "freeplay_favorite";
var FREEPLAY_LEFT = "freeplay_left";
var FREEPLAY_RIGHT = "freeplay_right";
// VOLUME // VOLUME
var VOLUME_UP = "volume_up"; var VOLUME_UP = "volume_up";
var VOLUME_DOWN = "volume_down"; var VOLUME_DOWN = "volume_down";

View file

@ -140,16 +140,36 @@ class HitNoteScriptEvent extends NoteScriptEvent
*/ */
public var score:Int; 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); super(NOTE_HIT, note, healthChange, comboCount, true);
this.score = score; this.score = score;
this.judgement = judgement; this.judgement = judgement;
this.isComboBreak = isComboBreak;
this.doesNotesplash = doesNotesplash;
this.hitDiff = hitDiff;
} }
public override function toString():String 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; 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. * This means the animation and transition have already started.
*/ */
var isEnding:Bool = false; var isEnding:Bool = false;
@ -237,15 +237,16 @@ class GameOverSubState extends MusicBeatSubState
} }
// KEYBOARD ONLY: Restart the level when pressing the assigned key. // KEYBOARD ONLY: Restart the level when pressing the assigned key.
if (controls.ACCEPT && blueballed) if (controls.ACCEPT && blueballed && !mustNotExit)
{ {
blueballed = false; blueballed = false;
confirmDeath(); confirmDeath();
} }
// KEYBOARD ONLY: Return to the menu when pressing the assigned key. // KEYBOARD ONLY: Return to the menu when pressing the assigned key.
if (controls.BACK && !mustNotExit) if (controls.BACK && !mustNotExit && !isEnding)
{ {
isEnding = true;
blueballed = false; blueballed = false;
PlayState.instance.deathCounter = 0; PlayState.instance.deathCounter = 0;
// PlayState.seenCutscene = false; // old thing... // PlayState.seenCutscene = false; // old thing...

View file

@ -449,13 +449,14 @@ class PauseSubState extends MusicBeatSubState
*/ */
function changeSelection(change:Int = 0):Void function changeSelection(change:Int = 0):Void
{ {
FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4); var prevEntry:Int = currentEntry;
currentEntry += change; currentEntry += change;
if (currentEntry < 0) currentEntry = currentMenuEntries.length - 1; if (currentEntry < 0) currentEntry = currentMenuEntries.length - 1;
if (currentEntry >= currentMenuEntries.length) currentEntry = 0; if (currentEntry >= currentMenuEntries.length) currentEntry = 0;
if (currentEntry != prevEntry) FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4);
for (entryIndex in 0...currentMenuEntries.length) for (entryIndex in 0...currentMenuEntries.length)
{ {
var isCurrent:Bool = entryIndex == currentEntry; var isCurrent:Bool = entryIndex == currentEntry;

View file

@ -175,6 +175,12 @@ class PlayState extends MusicBeatSubState
*/ */
public var currentVariation:String = Constants.DEFAULT_VARIATION; public var currentVariation:String = Constants.DEFAULT_VARIATION;
/**
* The currently selected instrumental ID.
* @default `''`
*/
public var currentInstrumental:String = '';
/** /**
* The currently active Stage. This is the object containing all the props. * The currently active Stage. This is the object containing all the props.
*/ */
@ -603,6 +609,7 @@ class PlayState extends MusicBeatSubState
currentSong = params.targetSong; currentSong = params.targetSong;
if (params.targetDifficulty != null) currentDifficulty = params.targetDifficulty; if (params.targetDifficulty != null) currentDifficulty = params.targetDifficulty;
if (params.targetVariation != null) currentVariation = params.targetVariation; if (params.targetVariation != null) currentVariation = params.targetVariation;
if (params.targetInstrumental != null) currentInstrumental = params.targetInstrumental;
isPracticeMode = params.practiceMode ?? false; isPracticeMode = params.practiceMode ?? false;
isBotPlayMode = params.botPlayMode ?? false; isBotPlayMode = params.botPlayMode ?? false;
isMinimalMode = params.minimalMode ?? false; isMinimalMode = params.minimalMode ?? false;
@ -1211,6 +1218,9 @@ class PlayState extends MusicBeatSubState
cameraTweensPausedBySubState.add(cameraZoomTween); cameraTweensPausedBySubState.add(cameraZoomTween);
} }
// Pause camera follow
FlxG.camera.followLerp = 0;
for (tween in scrollSpeedTweens) for (tween in scrollSpeedTweens)
{ {
if (tween != null && tween.active) if (tween != null && tween.active)
@ -1255,6 +1265,9 @@ class PlayState extends MusicBeatSubState
} }
cameraTweensPausedBySubState.clear(); cameraTweensPausedBySubState.clear();
// Resume camera follow
FlxG.camera.followLerp = Constants.DEFAULT_CAMERA_FOLLOW_RATE;
if (currentConversation != null) if (currentConversation != null)
{ {
currentConversation.resumeMusic(); currentConversation.resumeMusic();
@ -1968,7 +1981,7 @@ class PlayState extends MusicBeatSubState
if (!overrideMusic && !isGamePaused && currentChart != null) if (!overrideMusic && !isGamePaused && currentChart != null)
{ {
currentChart.playInst(1.0, false); currentChart.playInst(1.0, currentInstrumental, false);
} }
if (FlxG.sound.music == null) if (FlxG.sound.music == null)
@ -2115,7 +2128,8 @@ class PlayState extends MusicBeatSubState
// Call an event to allow canceling the note hit. // Call an event to allow canceling the note hit.
// NOTE: This is what handles the character animations! // 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); dispatchEvent(event);
// Calling event.cancelEvent() skips all the other logic! Neat! // Calling event.cancelEvent() skips all the other logic! Neat!
@ -2211,7 +2225,7 @@ class PlayState extends MusicBeatSubState
// Call an event to allow canceling the note hit. // Call an event to allow canceling the note hit.
// NOTE: This is what handles the character animations! // 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); dispatchEvent(event);
// Calling event.cancelEvent() skips all the other logic! Neat! // Calling event.cancelEvent() skips all the other logic! Neat!
@ -2269,13 +2283,22 @@ class PlayState extends MusicBeatSubState
if (holdNote == null || !holdNote.alive) continue; if (holdNote == null || !holdNote.alive) continue;
// While the hold note is being hit, and there is length on the hold note... // 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. // Grant the player health.
if (!isBotPlayMode)
{
health += Constants.HEALTH_HOLD_BONUS_PER_SECOND * elapsed; health += Constants.HEALTH_HOLD_BONUS_PER_SECOND * elapsed;
songScore += Std.int(Constants.SCORE_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) if (holdNote.missedNote && !holdNote.handledMiss)
{ {
// The player dropped a hold note. // The player dropped a hold note.
@ -2420,27 +2443,41 @@ class PlayState extends MusicBeatSubState
var daRating = Scoring.judgeNote(noteDiff, PBOT1); var daRating = Scoring.judgeNote(noteDiff, PBOT1);
var healthChange = 0.0; var healthChange = 0.0;
var isComboBreak = false;
switch (daRating) switch (daRating)
{ {
case 'sick': case 'sick':
healthChange = Constants.HEALTH_SICK_BONUS; healthChange = Constants.HEALTH_SICK_BONUS;
isComboBreak = Constants.JUDGEMENT_SICK_COMBO_BREAK;
case 'good': case 'good':
healthChange = Constants.HEALTH_GOOD_BONUS; healthChange = Constants.HEALTH_GOOD_BONUS;
isComboBreak = Constants.JUDGEMENT_GOOD_COMBO_BREAK;
case 'bad': case 'bad':
healthChange = Constants.HEALTH_BAD_BONUS; healthChange = Constants.HEALTH_BAD_BONUS;
isComboBreak = Constants.JUDGEMENT_BAD_COMBO_BREAK;
case 'shit': case 'shit':
isComboBreak = Constants.JUDGEMENT_SHIT_COMBO_BREAK;
healthChange = Constants.HEALTH_SHIT_BONUS; healthChange = Constants.HEALTH_SHIT_BONUS;
} }
// Send the note hit event. // 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); dispatchEvent(event);
// Calling event.cancelEvent() skips all the other logic! Neat! // Calling event.cancelEvent() skips all the other logic! Neat!
if (event.eventCanceled) return; 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. // 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);
} }
/** /**
@ -2451,9 +2488,6 @@ class PlayState extends MusicBeatSubState
{ {
// If we are here, we already CALLED the onNoteMiss script hook! // If we are here, we already CALLED the onNoteMiss script hook!
health += healthChange;
songScore -= 10;
if (!isPracticeMode) if (!isPracticeMode)
{ {
// messy copy paste rn lol // messy copy paste rn lol
@ -2493,14 +2527,9 @@ class PlayState extends MusicBeatSubState
} }
vocals.playerVolume = 0; vocals.playerVolume = 0;
Highscore.tallies.missed++; if (Highscore.tallies.combo != 0) if (Highscore.tallies.combo >= 10) comboPopUps.displayCombo(0);
if (Highscore.tallies.combo != 0) applyScore(-10, 'miss', healthChange, true);
{
// Break the combo.
if (Highscore.tallies.combo >= 10) comboPopUps.displayCombo(0);
Highscore.tallies.combo = 0;
}
if (playSound) if (playSound)
{ {
@ -2587,13 +2616,6 @@ class PlayState extends MusicBeatSubState
// Redirect to the chart editor playing the current song. // Redirect to the chart editor playing the current song.
if (controls.DEBUG_CHART) 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; disableKeys = true;
persistentUpdate = false; persistentUpdate = false;
@ -2602,7 +2624,6 @@ class PlayState extends MusicBeatSubState
targetSongId: currentSong.id, targetSongId: currentSong.id,
})); }));
} }
}
#end #end
#if (debug || FORCE_DEBUG_VERSION) #if (debug || FORCE_DEBUG_VERSION)
@ -2632,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) switch (daRating)
{ {
case 'sick': case 'sick':
Highscore.tallies.sick += 1; Highscore.tallies.sick += 1;
Highscore.tallies.totalNotesHit++;
isComboBreak = Constants.JUDGEMENT_SICK_COMBO_BREAK;
case 'good': case 'good':
Highscore.tallies.good += 1; Highscore.tallies.good += 1;
Highscore.tallies.totalNotesHit++;
isComboBreak = Constants.JUDGEMENT_GOOD_COMBO_BREAK;
case 'bad': case 'bad':
Highscore.tallies.bad += 1; Highscore.tallies.bad += 1;
Highscore.tallies.totalNotesHit++;
isComboBreak = Constants.JUDGEMENT_BAD_COMBO_BREAK;
case 'shit': case 'shit':
Highscore.tallies.shit += 1; Highscore.tallies.shit += 1;
Highscore.tallies.totalNotesHit++; case 'miss':
isComboBreak = Constants.JUDGEMENT_SHIT_COMBO_BREAK; Highscore.tallies.missed += 1;
default:
FlxG.log.error('Wuh? Buh? Guh? Note hit judgement was $daRating!');
} }
health += healthChange; health += healthChange;
if (isComboBreak) if (isComboBreak)
{ {
// Break the combo, but don't increment tallies.misses. // Break the combo, but don't increment tallies.misses.
@ -2683,15 +2682,23 @@ class PlayState extends MusicBeatSubState
Highscore.tallies.combo++; Highscore.tallies.combo++;
if (Highscore.tallies.combo > Highscore.tallies.maxCombo) Highscore.tallies.maxCombo = Highscore.tallies.combo; if (Highscore.tallies.combo > Highscore.tallies.maxCombo) Highscore.tallies.maxCombo = Highscore.tallies.combo;
} }
songScore += score;
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) if (!isPracticeMode)
{ {
@ -2731,12 +2738,7 @@ class PlayState extends MusicBeatSubState
} }
} }
comboPopUps.displayRating(daRating); comboPopUps.displayRating(daRating);
if (Highscore.tallies.combo >= 10 || Highscore.tallies.combo == 0) comboPopUps.displayCombo(Highscore.tallies.combo); if (combo >= 10 || combo == 0) comboPopUps.displayCombo(combo);
if (daNote.isHoldNote && daNote.holdNoteSprite != null)
{
playerStrumline.playNoteHoldCover(daNote.holdNoteSprite);
}
vocals.playerVolume = 1; vocals.playerVolume = 1;
} }
@ -2823,8 +2825,13 @@ class PlayState extends MusicBeatSubState
deathCounter = 0; 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 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) if (currentSong != null && currentSong.validScore)
{ {
@ -2849,13 +2856,21 @@ class PlayState extends MusicBeatSubState
// adds current song data into the tallies for the level (story levels) // adds current song data into the tallies for the level (story levels)
Highscore.talliesLevel = Highscore.combineTallies(Highscore.tallies, Highscore.talliesLevel); Highscore.talliesLevel = Highscore.combineTallies(Highscore.tallies, Highscore.talliesLevel);
if (!isPracticeMode && !isBotPlayMode && Save.instance.isSongHighScore(currentSong.id, currentDifficulty, data)) if (!isPracticeMode && !isBotPlayMode)
{
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)
{ {
Save.instance.setSongScore(currentSong.id, currentDifficulty, data);
#if newgrounds #if newgrounds
NGio.postScore(score, currentSong.id); NGio.postScore(score, currentSong.id);
#end #end
isNewHighscore = true; }
} }
} }
@ -3090,7 +3105,7 @@ class PlayState extends MusicBeatSubState
FlxG.camera.targetOffset.x += 20; FlxG.camera.targetOffset.x += 20;
// Replace zoom animation with a fade out for now. // 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, FlxTween.tween(camHUD, {alpha: 0}, 0.6,
{ {
@ -3185,7 +3200,7 @@ class PlayState extends MusicBeatSubState
cancelAllCameraTweens(); cancelAllCameraTweens();
} }
FlxG.camera.follow(cameraFollowPoint, LOCKON, 0.04); FlxG.camera.follow(cameraFollowPoint, LOCKON, Constants.DEFAULT_CAMERA_FOLLOW_RATE);
FlxG.camera.targetOffset.set(); FlxG.camera.targetOffset.set();
if (resetZoom) if (resetZoom)

View file

@ -53,8 +53,9 @@ class HealthIcon extends FunkinSprite
/** /**
* Apply the "bop" animation once every X steps. * 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. * 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 // Perfect (Platinum) is a Sick Full Clear
var isPerfectGold = scoreData.tallies.sick == scoreData.tallies.totalNotes; 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 // Else, use the standard grades
@ -397,62 +400,79 @@ enum abstract ScoringRank(String)
var GOOD; var GOOD;
var SHIT; 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 true;
if (a == null || b == null) return false; if (a == null || b == null) return false;
var temp1:Int = 0; var temp1:Int = getValue(a);
var temp2:Int = 0; var temp2:Int = getValue(b);
// temp 1 return temp1 > temp2;
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 @:op(A >= B) static function compareGTEQ(a:Null<ScoringRank>, b:Null<ScoringRank>):Bool
switch (b)
{ {
case PERFECT_GOLD: if (a != null && b == null) return true;
temp2 = 5; if (a == null || b == null) return false;
case PERFECT:
temp2 = 4; var temp1:Int = getValue(a);
case EXCELLENT: var temp2:Int = getValue(b);
temp2 = 3;
case GREAT: return temp1 >= temp2;
temp2 = 2;
case GOOD:
temp2 = 1;
case SHIT:
temp2 = 0;
default:
temp2 = -1;
} }
if (temp1 > temp2) @:op(A < B) static function compareLT(a:Null<ScoringRank>, b:Null<ScoringRank>):Bool
{ {
return true; 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;
} }
else
@:op(A <= B) static function compareLTEQ(a:Null<ScoringRank>, b:Null<ScoringRank>):Bool
{ {
return false; 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 * Delay in seconds
*/ */

View file

@ -682,9 +682,9 @@ class SongDifficulty
FlxG.sound.cache(getInstPath(instrumental)); FlxG.sound.cache(getInstPath(instrumental));
} }
public function playInst(volume:Float = 1.0, looped:Bool = false):Void public function playInst(volume:Float = 1.0, instId:String = '', looped:Bool = false):Void
{ {
var suffix:String = (variation != null && variation != '' && variation != 'default') ? '-$variation' : ''; var suffix:String = (instId != '') ? '-$instId' : '';
FlxG.sound.music = FunkinSound.load(Paths.inst(this.song.id, suffix), volume, looped, false, true); FlxG.sound.music = FunkinSound.load(Paths.inst(this.song.id, suffix), volume, looped, false, true);

View file

@ -1,6 +1,7 @@
package funkin.save; package funkin.save;
import flixel.util.FlxSave; import flixel.util.FlxSave;
import funkin.util.FileUtil;
import funkin.input.Controls.Device; import funkin.input.Controls.Device;
import funkin.play.scoring.Scoring; import funkin.play.scoring.Scoring;
import funkin.play.scoring.Scoring.ScoringRank; import funkin.play.scoring.Scoring.ScoringRank;
@ -58,7 +59,7 @@ class Save
this.data = data; this.data = data;
// Make sure the verison number is up to date before we flush. // 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 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 public function setSongScore(songId:String, difficultyId:String, score:SaveScoreData):Void
{ {
@ -518,6 +519,44 @@ class Save
flush(); 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? * Is the provided score data better than the current high score for the given song?
* @param songId The song ID to check. * @param songId The song ID to check.
@ -543,6 +582,39 @@ class Save
return score.score > currentScore.score; 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? * Has the provided song been beaten on one of the listed difficulties?
* @param songId The song ID to check. * @param songId The song ID to check.
@ -832,6 +904,29 @@ class Save
return cast legacySave.data; 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 = typedef SaveDataMods =
{ {
var enabledMods:Array<String>; var enabledMods:Array<String>;
// TODO: Make this not trip up the serializer when debugging.
@:jignored
var modOptions:Map<String, Dynamic>; var modOptions:Map<String, Dynamic>;
} }

View file

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

View file

@ -904,7 +904,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
function set_notePreviewDirty(value:Bool):Bool function set_notePreviewDirty(value:Bool):Bool
{ {
trace('Note preview dirtied!'); // trace('Note preview dirtied!');
return notePreviewDirty = value; return notePreviewDirty = value;
} }
@ -6304,7 +6304,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
var tempNote:NoteSprite = new NoteSprite(NoteStyleRegistry.instance.fetchDefault()); var tempNote:NoteSprite = new NoteSprite(NoteStyleRegistry.instance.fetchDefault());
tempNote.noteData = noteData; tempNote.noteData = noteData;
tempNote.scrollFactor.set(0, 0); 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); dispatchEvent(event);
// Calling event.cancelEvent() skips all the other logic! Neat! // Calling event.cancelEvent() skips all the other logic! Neat!

View file

@ -35,7 +35,15 @@ class SetItemSelectionCommand implements ChartEditorCommand
{ {
var eventSelected = this.events[0]; var eventSelected = this.events[0];
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; state.eventKindToPlace = eventSelected.eventKind;
}
// This code is here to parse event data that's not built as a struct for some reason. // 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. // TODO: Clean this up or get rid of it.

View file

@ -201,7 +201,8 @@ class ChartEditorThemeHandler
// Selection borders horizontally in the middle. // Selection borders horizontally in the middle.
for (i in 1...(Conductor.instance.stepsPerMeasure)) 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, state.gridBitmap.fillRect(new Rectangle(0, (ChartEditorState.GRID_SIZE * i) - (GRID_BEAT_DIVIDER_WIDTH / 2), state.gridBitmap.width,
GRID_BEAT_DIVIDER_WIDTH), GRID_BEAT_DIVIDER_WIDTH),

View file

@ -58,17 +58,8 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
function initialize():Void 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) { toolboxEventsEventKind.onChange = function(event:UIEvent) {
var eventType:String = event.data.value; var eventType:String = event.data.id;
trace('ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Event type changed: $eventType'); trace('ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Event type changed: $eventType');
@ -83,7 +74,7 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
return; return;
} }
buildEventDataFormFromSchema(toolboxEventsDataGrid, schema); buildEventDataFormFromSchema(toolboxEventsDataGrid, schema, chartEditorState.eventKindToPlace);
if (!_initializing && chartEditorState.currentEventSelection.length > 0) if (!_initializing && chartEditorState.currentEventSelection.length > 0)
{ {
@ -98,14 +89,40 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
chartEditorState.notePreviewDirty = true; 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 public override function refresh():Void
{ {
super.refresh(); 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()) for (pair in chartEditorState.eventDataToPlace.keyValueIterator())
{ {
@ -116,7 +133,7 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
if (field == null) 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 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. // Clear the frame.
target.removeAllComponents(); target.removeAllComponents();
@ -188,6 +211,9 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
var dropDown:DropDown = new DropDown(); var dropDown:DropDown = new DropDown();
dropDown.id = field.name; dropDown.id = field.name;
dropDown.width = 200.0; dropDown.width = 200.0;
dropDown.dropdownSize = 10;
dropDown.dropdownWidth = 300;
dropDown.searchable = true;
dropDown.dataSource = new ArrayDataSource(); dropDown.dataSource = new ArrayDataSource();
if (field.keys == null) throw 'Field "${field.name}" is of Enum type but has no keys.'; 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()) for (optionName in field.keys.keys())
{ {
var optionValue:Null<Dynamic> = field.keys.get(optionName); var optionValue:Null<Dynamic> = field.keys.get(optionName);
trace('$optionName : $optionValue'); // trace('$optionName : $optionValue');
dropDown.dataSource.add({value: optionValue, text: optionName}); dropDown.dataSource.add({value: optionValue, text: optionName});
} }
dropDown.value = field.defaultValue; dropDown.value = field.defaultValue;
// TODO: Add an option to customize sort.
dropDown.dataSource.sort('text', ASCENDING);
input = dropDown; input = dropDown;
case STRING: case STRING:
input = new TextField(); input = new TextField();

View file

@ -3,11 +3,13 @@ package funkin.ui.debug.charting.util;
import funkin.data.notestyle.NoteStyleRegistry; import funkin.data.notestyle.NoteStyleRegistry;
import funkin.play.notes.notestyle.NoteStyle; import funkin.play.notes.notestyle.NoteStyle;
import funkin.data.stage.StageData; import funkin.data.stage.StageData;
import funkin.play.event.SongEvent;
import funkin.data.stage.StageRegistry; import funkin.data.stage.StageRegistry;
import funkin.play.character.CharacterData; import funkin.play.character.CharacterData;
import haxe.ui.components.DropDown; import haxe.ui.components.DropDown;
import funkin.play.stage.Stage; import funkin.play.stage.Stage;
import funkin.play.character.BaseCharacter.CharacterType; import funkin.play.character.BaseCharacter.CharacterType;
import funkin.data.event.SongEventRegistry;
import funkin.play.character.CharacterData.CharacterDataParser; import funkin.play.character.CharacterData.CharacterDataParser;
/** /**
@ -81,6 +83,42 @@ class ChartEditorDropdowns
return returnValue; 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. * 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. // Fade out music to 40% volume over 1 second.
// This helps make the TV a bit more audible. // 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. // 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)); 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; 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) if (stickerSubState != null)
{ {
this.persistentUpdate = true; this.persistentUpdate = true;
@ -285,7 +291,10 @@ class FreeplayState extends MusicBeatSubState
// Only display songs which actually have available difficulties for the current character. // Only display songs which actually have available difficulties for the current character.
var displayedVariations = song.getVariationsByCharId(currentCharacter); var displayedVariations = song.getVariationsByCharId(currentCharacter);
trace(songId);
trace(displayedVariations);
var availableDifficultiesForSong:Array<String> = song.listDifficulties(displayedVariations, false); var availableDifficultiesForSong:Array<String> = song.listDifficulties(displayedVariations, false);
trace(availableDifficultiesForSong);
if (availableDifficultiesForSong.length == 0) continue; if (availableDifficultiesForSong.length == 0) continue;
songs.push(new FreeplaySongData(levelId, songId, song, displayedVariations)); songs.push(new FreeplaySongData(levelId, songId, song, displayedVariations));
@ -449,15 +458,20 @@ class FreeplayState extends MusicBeatSubState
add(dj); add(dj);
bgDad = new FlxSprite(pinkBack.width * 0.75, 0).loadGraphic(Paths.image('freeplay/freeplayBGdad')); bgDad = new FlxSprite(pinkBack.width * 0.74, 0).loadGraphic(Paths.image('freeplay/freeplayBGdad'));
bgDad.setGraphicSize(0, FlxG.height);
bgDad.updateHitbox();
bgDad.shader = new AngleMask(); bgDad.shader = new AngleMask();
bgDad.visible = false; bgDad.visible = false;
var blackOverlayBullshitLOLXD:FlxSprite = new FlxSprite(FlxG.width).makeGraphic(Std.int(bgDad.width), Std.int(bgDad.height), FlxColor.BLACK); var blackOverlayBullshitLOLXD:FlxSprite = new FlxSprite(FlxG.width).makeGraphic(Std.int(bgDad.width), Std.int(bgDad.height), FlxColor.BLACK);
add(blackOverlayBullshitLOLXD); // used to mask the text lol! add(blackOverlayBullshitLOLXD); // used to mask the text lol!
// this makes the texture sizes consistent, for the angle shader
bgDad.setGraphicSize(0, FlxG.height);
blackOverlayBullshitLOLXD.setGraphicSize(0, FlxG.height);
bgDad.updateHitbox();
blackOverlayBullshitLOLXD.updateHitbox();
exitMovers.set([blackOverlayBullshitLOLXD, bgDad], exitMovers.set([blackOverlayBullshitLOLXD, bgDad],
{ {
x: FlxG.width * 1.5, x: FlxG.width * 1.5,
@ -466,7 +480,7 @@ class FreeplayState extends MusicBeatSubState
}); });
add(bgDad); add(bgDad);
FlxTween.tween(blackOverlayBullshitLOLXD, {x: pinkBack.width * 0.76}, 0.7, {ease: FlxEase.quintOut}); FlxTween.tween(blackOverlayBullshitLOLXD, {x: pinkBack.width * 0.74}, 0.7, {ease: FlxEase.quintOut});
blackOverlayBullshitLOLXD.shader = bgDad.shader; blackOverlayBullshitLOLXD.shader = bgDad.shader;
@ -583,6 +597,8 @@ class FreeplayState extends MusicBeatSubState
generateSongList({filterType: FAVORITE}, true); generateSongList({filterType: FAVORITE}, true);
case 'ALL': case 'ALL':
generateSongList(null, true); generateSongList(null, true);
case '#':
generateSongList({filterType: REGEXP, filterData: '0-9'}, true);
default: default:
generateSongList({filterType: REGEXP, filterData: str}, true); generateSongList({filterType: REGEXP, filterData: str}, true);
} }
@ -591,6 +607,7 @@ class FreeplayState extends MusicBeatSubState
// that is, only if there's more than one song in the group! // that is, only if there's more than one song in the group!
if (grpCapsules.members.length > 0) if (grpCapsules.members.length > 0)
{ {
FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4);
curSelected = 1; curSelected = 1;
changeSelection(); changeSelection();
} }
@ -652,6 +669,9 @@ class FreeplayState extends MusicBeatSubState
alsoOrangeLOL.visible = true; alsoOrangeLOL.visible = true;
grpTxtScrolls.visible = true; grpTxtScrolls.visible = true;
// render optimisation
if (_parentState != null) _parentState.persistentDraw = false;
cardGlow.visible = true; cardGlow.visible = true;
FlxTween.tween(cardGlow, {alpha: 0, "scale.x": 1.2, "scale.y": 1.2}, 0.45, {ease: FlxEase.sineOut}); FlxTween.tween(cardGlow, {alpha: 0, "scale.x": 1.2, "scale.y": 1.2}, 0.45, {ease: FlxEase.sineOut});
@ -956,16 +976,20 @@ class FreeplayState extends MusicBeatSubState
grpCapsules.members[curSelected].ranking.scale.set(20, 20); grpCapsules.members[curSelected].ranking.scale.set(20, 20);
grpCapsules.members[curSelected].blurredRanking.scale.set(20, 20); grpCapsules.members[curSelected].blurredRanking.scale.set(20, 20);
if (fromResults?.newRank != null)
{
grpCapsules.members[curSelected].ranking.animation.play(fromResults.newRank.getFreeplayRankIconAsset(), true); grpCapsules.members[curSelected].ranking.animation.play(fromResults.newRank.getFreeplayRankIconAsset(), true);
// grpCapsules.members[curSelected].ranking.animation.curAnim.name, true); }
FlxTween.tween(grpCapsules.members[curSelected].ranking, {"scale.x": 1, "scale.y": 1}, 0.1); FlxTween.tween(grpCapsules.members[curSelected].ranking, {"scale.x": 1, "scale.y": 1}, 0.1);
if (fromResults?.newRank != null)
{
grpCapsules.members[curSelected].blurredRanking.animation.play(fromResults.newRank.getFreeplayRankIconAsset(), true); grpCapsules.members[curSelected].blurredRanking.animation.play(fromResults.newRank.getFreeplayRankIconAsset(), true);
}
FlxTween.tween(grpCapsules.members[curSelected].blurredRanking, {"scale.x": 1, "scale.y": 1}, 0.1); FlxTween.tween(grpCapsules.members[curSelected].blurredRanking, {"scale.x": 1, "scale.y": 1}, 0.1);
new FlxTimer().start(0.1, _ -> { new FlxTimer().start(0.1, _ -> {
// trace(grpCapsules.members[curSelected].ranking.rank);
if (fromResults?.oldRank != null) if (fromResults?.oldRank != null)
{ {
grpCapsules.members[curSelected].fakeRanking.visible = false; grpCapsules.members[curSelected].fakeRanking.visible = false;
@ -994,7 +1018,6 @@ class FreeplayState extends MusicBeatSubState
FunkinSound.playOnce(Paths.sound('ranks/rankinnormal')); FunkinSound.playOnce(Paths.sound('ranks/rankinnormal'));
} }
rankCamera.zoom = 1.3; 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}); FlxTween.tween(rankCamera, {"zoom": 1.5}, 0.3, {ease: FlxEase.backInOut});
@ -1012,13 +1035,11 @@ class FreeplayState extends MusicBeatSubState
new FlxTimer().start(0.4, _ -> { new FlxTimer().start(0.4, _ -> {
FlxTween.tween(funnyCam, {"zoom": 1}, 0.8, {ease: FlxEase.sineIn}); FlxTween.tween(funnyCam, {"zoom": 1}, 0.8, {ease: FlxEase.sineIn});
FlxTween.tween(rankCamera, {"zoom": 1.2}, 0.8, {ease: FlxEase.backIn}); 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}); 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, _ -> { new FlxTimer().start(0.6, _ -> {
rankAnimSlam(fromResults); rankAnimSlam(fromResults);
// IntervalShake.shake(grpCapsules.members[curSelected].capsule, 0.3, 1 / 30, 0, 0.3, FlxEase.quartIn);
}); });
} }
@ -1183,51 +1204,9 @@ class FreeplayState extends MusicBeatSubState
// { // {
// rankAnimSlam(fromResultsParams); // 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 #end
if (FlxG.keys.justPressed.F && !busy) if (controls.FREEPLAY_FAVORITE && !busy)
{ {
var targetSong = grpCapsules.members[curSelected]?.songData; var targetSong = grpCapsules.members[curSelected]?.songData;
if (targetSong != null) if (targetSong != null)
@ -1571,6 +1550,8 @@ class FreeplayState extends MusicBeatSubState
{ {
clearDaCache(daSong.songName); clearDaCache(daSong.songName);
} }
// remove and destroy freeplay camera
FlxG.cameras.remove(funnyCam);
} }
function changeDiff(change:Int = 0, force:Bool = false):Void function changeDiff(change:Int = 0, force:Bool = false):Void
@ -1591,7 +1572,19 @@ class FreeplayState extends MusicBeatSubState
var daSong:Null<FreeplaySongData> = grpCapsules.members[curSelected].songData; var daSong:Null<FreeplaySongData> = grpCapsules.members[curSelected].songData;
if (daSong != null) 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; intendedScore = songScore?.score ?? 0;
intendedCompletion = songScore == null ? 0.0 : ((songScore.tallies.sick + songScore.tallies.good) / songScore.tallies.totalNotes); intendedCompletion = songScore == null ? 0.0 : ((songScore.tallies.sick + songScore.tallies.good) / songScore.tallies.totalNotes);
rememberedDifficulty = currentDifficulty; rememberedDifficulty = currentDifficulty;
@ -1726,11 +1719,20 @@ class FreeplayState extends MusicBeatSubState
FlxG.log.warn('WARN: could not find song with id (${cap.songData.songId})'); FlxG.log.warn('WARN: could not find song with id (${cap.songData.songId})');
return; return;
} }
var targetDifficulty:String = currentDifficulty; var targetDifficultyId:String = currentDifficulty;
var targetVariation:String = targetSong.getFirstValidVariation(targetDifficulty); var targetVariation:String = targetSong.getFirstValidVariation(targetDifficultyId);
PlayStatePlaylist.campaignId = cap.songData.levelId; PlayStatePlaylist.campaignId = cap.songData.levelId;
var targetDifficulty:SongDifficulty = targetSong.getDifficulty(targetDifficultyId, targetVariation);
if (targetDifficulty == null)
{
FlxG.log.warn('WARN: could not find difficulty with id (${targetDifficultyId})');
return;
}
// TODO: Change this with alternate instrumentals
var targetInstId:String = targetDifficulty.characters.instrumental;
// Visual and audio effects. // Visual and audio effects.
FunkinSound.playOnce(Paths.sound('confirmMenu')); FunkinSound.playOnce(Paths.sound('confirmMenu'));
dj.confirm(); dj.confirm();
@ -1779,8 +1781,9 @@ class FreeplayState extends MusicBeatSubState
LoadingState.loadPlayState( LoadingState.loadPlayState(
{ {
targetSong: targetSong, targetSong: targetSong,
targetDifficulty: targetDifficulty, targetDifficulty: targetDifficultyId,
targetVariation: targetVariation, targetVariation: targetVariation,
targetInstrumental: targetInstId,
practiceMode: false, practiceMode: false,
minimalMode: false, minimalMode: false,
@ -1817,12 +1820,12 @@ class FreeplayState extends MusicBeatSubState
function changeSelection(change:Int = 0):Void function changeSelection(change:Int = 0):Void
{ {
if (!prepForNewRank) FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4);
var prevSelected:Int = curSelected; var prevSelected:Int = curSelected;
curSelected += change; curSelected += change;
if (!prepForNewRank && curSelected != prevSelected) FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4);
if (curSelected < 0) curSelected = grpCapsules.countLiving() - 1; if (curSelected < 0) curSelected = grpCapsules.countLiving() - 1;
if (curSelected >= grpCapsules.countLiving()) curSelected = 0; if (curSelected >= grpCapsules.countLiving()) curSelected = 0;
@ -2076,7 +2079,7 @@ class FreeplaySongData
this.songDifficulties = song.listDifficulties(null, variations, false, false); this.songDifficulties = song.listDifficulties(null, variations, false, false);
if (!this.songDifficulties.contains(currentDifficulty)) currentDifficulty = Constants.DEFAULT_DIFFICULTY; 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; if (songDifficulty == null) return;
this.songStartingBpm = songDifficulty.getStartingBPM(); this.songStartingBpm = songDifficulty.getStartingBPM();
this.songName = songDifficulty.songName; this.songName = songDifficulty.songName;

View file

@ -8,6 +8,7 @@ import flixel.tweens.FlxTween;
import flixel.tweens.FlxEase; import flixel.tweens.FlxEase;
import flixel.util.FlxColor; import flixel.util.FlxColor;
import flixel.util.FlxTimer; import flixel.util.FlxTimer;
import funkin.input.Controls;
import funkin.graphics.adobeanimate.FlxAtlasSprite; import funkin.graphics.adobeanimate.FlxAtlasSprite;
class LetterSort extends FlxTypedSpriteGroup<FlxSprite> class LetterSort extends FlxTypedSpriteGroup<FlxSprite>
@ -69,14 +70,19 @@ class LetterSort extends FlxTypedSpriteGroup<FlxSprite>
changeSelection(0); changeSelection(0);
} }
var controls(get, never):Controls;
inline function get_controls():Controls
return PlayerSettings.player1.controls;
override function update(elapsed:Float):Void override function update(elapsed:Float):Void
{ {
super.update(elapsed); super.update(elapsed);
if (inputEnabled) if (inputEnabled)
{ {
if (FlxG.keys.justPressed.E) changeSelection(1); if (controls.FREEPLAY_LEFT) changeSelection(-1);
if (FlxG.keys.justPressed.Q) changeSelection(-1); if (controls.FREEPLAY_RIGHT) changeSelection(1);
} }
} }

View file

@ -219,7 +219,7 @@ class SongMenuItem extends FlxSpriteGroup
favIconBlurred.visible = false; favIconBlurred.visible = false;
add(favIconBlurred); add(favIconBlurred);
favIcon = new FlxSprite(380, 40); favIcon = new FlxSprite(favIconBlurred.x, favIconBlurred.y);
favIcon.frames = Paths.getSparrowAtlas('freeplay/favHeart'); favIcon.frames = Paths.getSparrowAtlas('freeplay/favHeart');
favIcon.animation.addByPrefix('fav', 'favorite heart', 24, false); favIcon.animation.addByPrefix('fav', 'favorite heart', 24, false);
favIcon.animation.play('fav'); 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 public function checkClip():Void
{ {
var clipSize:Int = 290; var clipSize:Int = 290;
var clipType:Int = 0; var clipType:Int = 0;
if (ranking.visible == true) clipType += 1; if (ranking.visible)
if (favIcon.visible == true) clipType = 2; {
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) switch (clipType)
{ {
case 2: case 2:
clipSize = 220; clipSize = 210;
case 1: case 1:
clipSize = 255; clipSize = 245;
} }
songText.clipWidth = clipSize; 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 #end
if (FlxG.sound.music != null && FlxG.sound.music.volume < 0.8) 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], [NOTE_UP, NOTE_DOWN, NOTE_LEFT, NOTE_RIGHT],
[UI_UP, UI_DOWN, UI_LEFT, UI_RIGHT, ACCEPT, BACK], [UI_UP, UI_DOWN, UI_LEFT, UI_RIGHT, ACCEPT, BACK],
[CUTSCENE_ADVANCE], [CUTSCENE_ADVANCE],
[FREEPLAY_FAVORITE, FREEPLAY_LEFT, FREEPLAY_RIGHT],
[WINDOW_FULLSCREEN, WINDOW_SCREENSHOT],
[VOLUME_UP, VOLUME_DOWN, VOLUME_MUTE], [VOLUME_UP, VOLUME_DOWN, VOLUME_MUTE],
[DEBUG_MENU, DEBUG_CHART] [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); headers.add(new AtlasText(0, y, "CUTSCENE", AtlasFont.BOLD)).screenCenter(X);
y += spacer; 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) else if (currentHeader != "VOLUME_" && name.indexOf("VOLUME_") == 0)
{ {
currentHeader = "VOLUME_"; 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); 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; label.alpha = 0.6;
for (i in 0...COLUMNS) 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; y += spacer;
} }

View file

@ -25,6 +25,8 @@ class OptionsState extends MusicBeatState
override function create():Void override function create():Void
{ {
persistentUpdate = true;
var menuBG = new FlxSprite().loadGraphic(Paths.image('menuBG')); var menuBG = new FlxSprite().loadGraphic(Paths.image('menuBG'));
var hsv = new HSVShader(); var hsv = new HSVShader();
hsv.hue = -0.6; hsv.hue = -0.6;
@ -55,8 +57,6 @@ class OptionsState extends MusicBeatState
setPage(Controls); setPage(Controls);
} }
// disable for intro transition
currentPage.enabled = false;
super.create(); super.create();
} }
@ -86,13 +86,6 @@ class OptionsState extends MusicBeatState
} }
} }
override function finishTransIn()
{
super.finishTransIn();
currentPage.enabled = true;
}
function switchPage(name:PageName) function switchPage(name:PageName)
{ {
// TODO: Animate this transition? // TODO: Animate this transition?
@ -266,11 +259,11 @@ class OptionsMenu extends Page
#end #end
} }
enum PageName enum abstract PageName(String)
{ {
Options; var Options = "options";
Controls; var Controls = "controls";
Colors; var Colors = "colors";
Mods; var Mods = "mods";
Preferences; var Preferences = "preferences";
} }

View file

@ -387,6 +387,7 @@ class StoryMenuState extends MusicBeatState
function changeLevel(change:Int = 0):Void function changeLevel(change:Int = 0):Void
{ {
var currentIndex:Int = levelList.indexOf(currentLevelId); var currentIndex:Int = levelList.indexOf(currentLevelId);
var prevIndex:Int = currentIndex;
currentIndex += change; 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(); updateText();
updateBackground(previousLevelId); updateBackground(previousLevelId);

View file

@ -265,6 +265,13 @@ class TitleState extends MusicBeatState
if (FlxG.keys.pressed.DOWN) FlxG.sound.music.pitch -= 0.5 * elapsed; if (FlxG.keys.pressed.DOWN) FlxG.sound.music.pitch -= 0.5 * elapsed;
#end #end
#if desktop
if (FlxG.keys.justPressed.ESCAPE)
{
Sys.exit(0);
}
#end
Conductor.instance.update(); Conductor.instance.update();
/* if (FlxG.onMobile) /* if (FlxG.onMobile)

View file

@ -19,6 +19,7 @@ import haxe.ui.containers.dialogs.Dialogs.FileDialogExtensionInfo;
class FileUtil class FileUtil
{ {
public static final FILE_FILTER_FNFC:FileFilter = new FileFilter("Friday Night Funkin' Chart (.fnfc)", "*.fnfc"); 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_ZIP:FileFilter = new FileFilter("ZIP Archive (.zip)", "*.zip");
public static final FILE_FILTER_PNG:FileFilter = new FileFilter("PNG Image (.png)", "*.png"); 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) -> { 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) if (e.keyCode == key)
{ {

View file

@ -103,7 +103,7 @@ class ScreenshotPlugin extends FlxBasic
public function hasPressedScreenshot():Bool public function hasPressedScreenshot():Bool
{ {
return PlayerSettings.player1.controls.SCREENSHOT; return PlayerSettings.player1.controls.WINDOW_SCREENSHOT;
} }
public function updatePreferences():Void public function updatePreferences():Void