1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2025-03-22 18:09:33 +00:00

Merge branch 'develop' into better-templates

This commit is contained in:
tposejank 2024-06-17 00:48:32 -04:00 committed by GitHub
commit 2cc41c23a8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 686 additions and 268 deletions

View file

@ -1 +1 @@
hi
hi

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/),
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
### Added
- 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!)
- Health icons now support a Winning frame without requiring a spritesheet, simply include a third frame in the icon file. (thanks gamerbross!)
- Remember that for more complex behaviors such as animations or transitions, you should use an XML file to define each frame.
- Improved the Event Toolbox in the Chart Editor; dropdowns are now bigger, include search field, and display elements in alphabetical order rather than a random order.
### Fixed
- Fixed an issue where Nene's visualizer would not play on Desktop builds
- Fixed a bug where the game would silently fail to load saves on HTML5
- Fixed some bugs with the props on the Story Menu not bopping properly
- Improved offsets for Pico and Tankman opponents so they don't slide around as much.
- Additional fixes to the Loading bar on HTML5 (thanks lemz1!)
- Fixed several bugs with the TitleState, including missing music when returning from the Main Menu (thanks gamerbross!)
- Fixed a camera bug in the Main Menu (thanks richTrash21!)
- 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!)
- Optimized animation handling for characters (thanks richTrash21!)
- 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"
xsi:schemaLocation="http://lime.openfl.org/project/1.0.4 http://lime.openfl.org/xsd/project-1.0.4.xsd">
<!-- _________________________ 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-->
<set name="APP_ID" value="0x0100f6c013bbc000" />

2
art

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

2
assets

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

View file

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

View file

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

View file

@ -5,35 +5,73 @@ import flixel.system.FlxAssets.FlxShader;
class AngleMask extends FlxShader
{
@:glFragmentSource('
#pragma header
uniform vec2 endPosition;
void main()
{
vec4 base = texture2D(bitmap, openfl_TextureCoordv);
#pragma header
vec2 uv = openfl_TextureCoordv.xy;
uniform vec2 endPosition;
vec2 hash22(vec2 p) {
vec3 p3 = fract(vec3(p.xyx) * vec3(.1031, .1030, .0973));
p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.xx + p3.yz) * p3.zy);
}
vec2 start = vec2(0.0, 0.0);
vec2 end = vec2(endPosition.x / openfl_TextureSize.x, 1.0);
// ====== 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));
}
float dx = end.x - start.x;
float dy = end.y - start.y;
vec4 mainPass(vec2 fragCoord) {
vec4 base = texture2D(bitmap, fragCoord);
float angle = atan(dy, dx);
vec2 uv = fragCoord.xy;
uv.x -= start.x;
uv.y -= start.y;
vec2 start = vec2(0.0, 0.0);
vec2 end = vec2(endPosition.x / openfl_TextureSize.x, 1.0);
float uvA = atan(uv.y, uv.x);
float dx = end.x - start.x;
float dy = end.y - start.y;
if (uvA < angle)
gl_FragColor = base;
else
gl_FragColor = vec4(0.0);
float angle = atan(dy, dx);
}')
uv.x -= start.x;
uv.y -= start.y;
float uvA = atan(uv.y, uv.x);
if (uvA < angle)
return base;
else
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()
{
super();

View file

@ -58,7 +58,11 @@ class Controls extends FlxActionSet
var _back = new FunkinAction(Action.BACK);
var _pause = new FunkinAction(Action.PAUSE);
var _reset = new FunkinAction(Action.RESET);
var _screenshot = new FunkinAction(Action.SCREENSHOT);
var _window_screenshot = new FunkinAction(Action.WINDOW_SCREENSHOT);
var _window_fullscreen = new FunkinAction(Action.WINDOW_FULLSCREEN);
var _freeplay_favorite = new FunkinAction(Action.FREEPLAY_FAVORITE);
var _freeplay_left = new FunkinAction(Action.FREEPLAY_LEFT);
var _freeplay_right = new FunkinAction(Action.FREEPLAY_RIGHT);
var _cutscene_advance = new FunkinAction(Action.CUTSCENE_ADVANCE);
var _debug_menu = new FunkinAction(Action.DEBUG_MENU);
var _debug_chart = new FunkinAction(Action.DEBUG_CHART);
@ -66,7 +70,6 @@ class Controls extends FlxActionSet
var _volume_up = new FunkinAction(Action.VOLUME_UP);
var _volume_down = new FunkinAction(Action.VOLUME_DOWN);
var _volume_mute = new FunkinAction(Action.VOLUME_MUTE);
var _fullscreen = new FunkinAction(Action.FULLSCREEN);
var byName:Map<String, FunkinAction> = new Map<String, FunkinAction>();
@ -233,10 +236,30 @@ class Controls extends FlxActionSet
inline function get_RESET()
return _reset.check();
public var SCREENSHOT(get, never):Bool;
public var WINDOW_FULLSCREEN(get, never):Bool;
inline function get_SCREENSHOT()
return _screenshot.check();
inline function get_WINDOW_FULLSCREEN()
return _window_fullscreen.check();
public var WINDOW_SCREENSHOT(get, never):Bool;
inline function get_WINDOW_SCREENSHOT()
return _window_screenshot.check();
public var FREEPLAY_FAVORITE(get, never):Bool;
inline function get_FREEPLAY_FAVORITE()
return _freeplay_favorite.check();
public var FREEPLAY_LEFT(get, never):Bool;
inline function get_FREEPLAY_LEFT()
return _freeplay_left.check();
public var FREEPLAY_RIGHT(get, never):Bool;
inline function get_FREEPLAY_RIGHT()
return _freeplay_right.check();
public var CUTSCENE_ADVANCE(get, never):Bool;
@ -273,11 +296,6 @@ class Controls extends FlxActionSet
inline function get_VOLUME_MUTE()
return _volume_mute.check();
public var FULLSCREEN(get, never):Bool;
inline function get_FULLSCREEN()
return _fullscreen.check();
public function new(name, scheme:KeyboardScheme = null)
{
super(name);
@ -294,7 +312,11 @@ class Controls extends FlxActionSet
add(_back);
add(_pause);
add(_reset);
add(_screenshot);
add(_window_screenshot);
add(_window_fullscreen);
add(_freeplay_favorite);
add(_freeplay_left);
add(_freeplay_right);
add(_cutscene_advance);
add(_debug_menu);
add(_debug_chart);
@ -302,7 +324,6 @@ class Controls extends FlxActionSet
add(_volume_up);
add(_volume_down);
add(_volume_mute);
add(_fullscreen);
for (action in digitalActions) {
if (Std.isOfType(action, FunkinAction)) {
@ -398,7 +419,11 @@ class Controls extends FlxActionSet
case BACK: _back;
case PAUSE: _pause;
case RESET: _reset;
case SCREENSHOT: _screenshot;
case WINDOW_SCREENSHOT: _window_screenshot;
case WINDOW_FULLSCREEN: _window_fullscreen;
case FREEPLAY_FAVORITE: _freeplay_favorite;
case FREEPLAY_LEFT: _freeplay_left;
case FREEPLAY_RIGHT: _freeplay_right;
case CUTSCENE_ADVANCE: _cutscene_advance;
case DEBUG_MENU: _debug_menu;
case DEBUG_CHART: _debug_chart;
@ -406,7 +431,6 @@ class Controls extends FlxActionSet
case VOLUME_UP: _volume_up;
case VOLUME_DOWN: _volume_down;
case VOLUME_MUTE: _volume_mute;
case FULLSCREEN: _fullscreen;
}
}
@ -466,8 +490,16 @@ class Controls extends FlxActionSet
func(_pause, JUST_PRESSED);
case RESET:
func(_reset, JUST_PRESSED);
case SCREENSHOT:
func(_screenshot, JUST_PRESSED);
case WINDOW_SCREENSHOT:
func(_window_screenshot, JUST_PRESSED);
case WINDOW_FULLSCREEN:
func(_window_fullscreen, JUST_PRESSED);
case FREEPLAY_FAVORITE:
func(_freeplay_favorite, JUST_PRESSED);
case FREEPLAY_LEFT:
func(_freeplay_left, JUST_PRESSED);
case FREEPLAY_RIGHT:
func(_freeplay_right, JUST_PRESSED);
case CUTSCENE_ADVANCE:
func(_cutscene_advance, JUST_PRESSED);
case DEBUG_MENU:
@ -482,8 +514,6 @@ class Controls extends FlxActionSet
func(_volume_down, JUST_PRESSED);
case VOLUME_MUTE:
func(_volume_mute, JUST_PRESSED);
case FULLSCREEN:
func(_fullscreen, JUST_PRESSED);
}
}
@ -678,7 +708,11 @@ class Controls extends FlxActionSet
bindKeys(Control.BACK, getDefaultKeybinds(scheme, Control.BACK));
bindKeys(Control.PAUSE, getDefaultKeybinds(scheme, Control.PAUSE));
bindKeys(Control.RESET, getDefaultKeybinds(scheme, Control.RESET));
bindKeys(Control.SCREENSHOT, getDefaultKeybinds(scheme, Control.SCREENSHOT));
bindKeys(Control.WINDOW_SCREENSHOT, getDefaultKeybinds(scheme, Control.WINDOW_SCREENSHOT));
bindKeys(Control.WINDOW_FULLSCREEN, getDefaultKeybinds(scheme, Control.WINDOW_FULLSCREEN));
bindKeys(Control.FREEPLAY_FAVORITE, getDefaultKeybinds(scheme, Control.FREEPLAY_FAVORITE));
bindKeys(Control.FREEPLAY_LEFT, getDefaultKeybinds(scheme, Control.FREEPLAY_LEFT));
bindKeys(Control.FREEPLAY_RIGHT, getDefaultKeybinds(scheme, Control.FREEPLAY_RIGHT));
bindKeys(Control.CUTSCENE_ADVANCE, getDefaultKeybinds(scheme, Control.CUTSCENE_ADVANCE));
bindKeys(Control.DEBUG_MENU, getDefaultKeybinds(scheme, Control.DEBUG_MENU));
bindKeys(Control.DEBUG_CHART, getDefaultKeybinds(scheme, Control.DEBUG_CHART));
@ -686,7 +720,6 @@ class Controls extends FlxActionSet
bindKeys(Control.VOLUME_UP, getDefaultKeybinds(scheme, Control.VOLUME_UP));
bindKeys(Control.VOLUME_DOWN, getDefaultKeybinds(scheme, Control.VOLUME_DOWN));
bindKeys(Control.VOLUME_MUTE, getDefaultKeybinds(scheme, Control.VOLUME_MUTE));
bindKeys(Control.FULLSCREEN, getDefaultKeybinds(scheme, Control.FULLSCREEN));
bindMobileLol();
}
@ -707,7 +740,11 @@ class Controls extends FlxActionSet
case Control.BACK: return [X, BACKSPACE, ESCAPE];
case Control.PAUSE: return [P, ENTER, ESCAPE];
case Control.RESET: return [R];
case Control.SCREENSHOT: return [F3]; // TODO: Change this back to PrintScreen
case Control.WINDOW_FULLSCREEN: return [F11]; // We use F for other things LOL.
case Control.WINDOW_SCREENSHOT: return [F3];
case Control.FREEPLAY_FAVORITE: return [F]; // Favorite a song on the menu
case Control.FREEPLAY_LEFT: return [Q]; // Switch tabs on the menu
case Control.FREEPLAY_RIGHT: return [E]; // Switch tabs on the menu
case Control.CUTSCENE_ADVANCE: return [Z, ENTER];
case Control.DEBUG_MENU: return [GRAVEACCENT];
case Control.DEBUG_CHART: return [];
@ -715,8 +752,6 @@ class Controls extends FlxActionSet
case Control.VOLUME_UP: return [PLUS, NUMPADPLUS];
case Control.VOLUME_DOWN: return [MINUS, NUMPADMINUS];
case Control.VOLUME_MUTE: return [ZERO, NUMPADZERO];
case Control.FULLSCREEN: return [FlxKey.F11]; // We use F for other things LOL.
}
case Duo(true):
switch (control) {
@ -732,7 +767,11 @@ class Controls extends FlxActionSet
case Control.BACK: return [H, X];
case Control.PAUSE: return [ONE];
case Control.RESET: return [R];
case Control.SCREENSHOT: return [PRINTSCREEN];
case Control.WINDOW_SCREENSHOT: return [F3];
case Control.WINDOW_FULLSCREEN: return [F11];
case Control.FREEPLAY_FAVORITE: return [F]; // Favorite a song on the menu
case Control.FREEPLAY_LEFT: return [Q]; // Switch tabs on the menu
case Control.FREEPLAY_RIGHT: return [E]; // Switch tabs on the menu
case Control.CUTSCENE_ADVANCE: return [G, Z];
case Control.DEBUG_MENU: return [GRAVEACCENT];
case Control.DEBUG_CHART: return [];
@ -740,7 +779,6 @@ class Controls extends FlxActionSet
case Control.VOLUME_UP: return [PLUS];
case Control.VOLUME_DOWN: return [MINUS];
case Control.VOLUME_MUTE: return [ZERO];
case Control.FULLSCREEN: return [FlxKey.F];
}
case Duo(false):
@ -757,15 +795,18 @@ class Controls extends FlxActionSet
case Control.BACK: return [ESCAPE];
case Control.PAUSE: return [ONE];
case Control.RESET: return [R];
case Control.SCREENSHOT: return [PRINTSCREEN];
case Control.WINDOW_SCREENSHOT: return [];
case Control.WINDOW_FULLSCREEN: return [];
case Control.FREEPLAY_FAVORITE: return [];
case Control.FREEPLAY_LEFT: return [];
case Control.FREEPLAY_RIGHT: return [];
case Control.CUTSCENE_ADVANCE: return [ENTER];
case Control.DEBUG_MENU: return [GRAVEACCENT];
case Control.DEBUG_MENU: return [];
case Control.DEBUG_CHART: return [];
case Control.DEBUG_STAGE: return [];
case Control.VOLUME_UP: return [NUMPADPLUS];
case Control.VOLUME_DOWN: return [NUMPADMINUS];
case Control.VOLUME_MUTE: return [NUMPADZERO];
case Control.FULLSCREEN: return [];
}
default:
@ -856,34 +897,37 @@ class Controls extends FlxActionSet
public function addDefaultGamepad(id):Void
{
addGamepadLiteral(id, [
Control.ACCEPT => getDefaultGamepadBinds(Control.ACCEPT),
Control.BACK => getDefaultGamepadBinds(Control.BACK),
Control.UI_UP => getDefaultGamepadBinds(Control.UI_UP),
Control.UI_DOWN => getDefaultGamepadBinds(Control.UI_DOWN),
Control.UI_LEFT => getDefaultGamepadBinds(Control.UI_LEFT),
Control.UI_RIGHT => getDefaultGamepadBinds(Control.UI_RIGHT),
// don't swap A/B or X/Y for switch on these. A is always the bottom face button
Control.NOTE_UP => getDefaultGamepadBinds(Control.NOTE_UP),
Control.NOTE_DOWN => getDefaultGamepadBinds(Control.NOTE_DOWN),
Control.NOTE_LEFT => getDefaultGamepadBinds(Control.NOTE_LEFT),
Control.NOTE_RIGHT => getDefaultGamepadBinds(Control.NOTE_RIGHT),
Control.PAUSE => getDefaultGamepadBinds(Control.PAUSE),
Control.RESET => getDefaultGamepadBinds(Control.RESET),
// Control.SCREENSHOT => [],
// Control.VOLUME_UP => [RIGHT_SHOULDER],
// Control.VOLUME_DOWN => [LEFT_SHOULDER],
// Control.VOLUME_MUTE => [RIGHT_TRIGGER],
Control.WINDOW_FULLSCREEN => getDefaultGamepadBinds(Control.WINDOW_FULLSCREEN),
Control.WINDOW_SCREENSHOT => getDefaultGamepadBinds(Control.WINDOW_SCREENSHOT),
Control.CUTSCENE_ADVANCE => getDefaultGamepadBinds(Control.CUTSCENE_ADVANCE),
// Control.DEBUG_MENU
// Control.DEBUG_CHART
Control.FREEPLAY_FAVORITE => getDefaultGamepadBinds(Control.FREEPLAY_FAVORITE),
Control.FREEPLAY_LEFT => getDefaultGamepadBinds(Control.FREEPLAY_LEFT),
Control.FREEPLAY_RIGHT => getDefaultGamepadBinds(Control.FREEPLAY_RIGHT),
Control.VOLUME_UP => getDefaultGamepadBinds(Control.VOLUME_UP),
Control.VOLUME_DOWN => getDefaultGamepadBinds(Control.VOLUME_DOWN),
Control.VOLUME_MUTE => getDefaultGamepadBinds(Control.VOLUME_MUTE),
Control.DEBUG_MENU => getDefaultGamepadBinds(Control.DEBUG_MENU),
Control.DEBUG_CHART => getDefaultGamepadBinds(Control.DEBUG_CHART),
Control.DEBUG_STAGE => getDefaultGamepadBinds(Control.DEBUG_STAGE),
]);
}
function getDefaultGamepadBinds(control:Control):Array<FlxGamepadInputID> {
switch(control) {
case Control.ACCEPT: return [#if switch B #else A #end];
case Control.BACK: return [#if switch A #else B #end, FlxGamepadInputID.BACK];
case Control.BACK: return [#if switch A #else B #end];
case Control.UI_UP: return [DPAD_UP, LEFT_STICK_DIGITAL_UP];
case Control.UI_DOWN: return [DPAD_DOWN, LEFT_STICK_DIGITAL_DOWN];
case Control.UI_LEFT: return [DPAD_LEFT, LEFT_STICK_DIGITAL_LEFT];
@ -893,15 +937,19 @@ class Controls extends FlxActionSet
case Control.NOTE_LEFT: return [DPAD_LEFT, X, LEFT_STICK_DIGITAL_LEFT, RIGHT_STICK_DIGITAL_LEFT];
case Control.NOTE_RIGHT: return [DPAD_RIGHT, B, LEFT_STICK_DIGITAL_RIGHT, RIGHT_STICK_DIGITAL_RIGHT];
case Control.PAUSE: return [START];
case Control.RESET: return [RIGHT_SHOULDER];
case Control.SCREENSHOT: return [];
case Control.VOLUME_UP: return [];
case Control.VOLUME_DOWN: return [];
case Control.VOLUME_MUTE: return [];
case Control.RESET: return [FlxGamepadInputID.BACK]; // Back (i.e. Select)
case Control.WINDOW_FULLSCREEN: [];
case Control.WINDOW_SCREENSHOT: [];
case Control.CUTSCENE_ADVANCE: return [A];
case Control.DEBUG_MENU: return [];
case Control.DEBUG_CHART: return [];
case Control.FULLSCREEN: return [];
case Control.FREEPLAY_FAVORITE: [FlxGamepadInputID.BACK]; // Back (i.e. Select)
case Control.FREEPLAY_LEFT: [LEFT_SHOULDER];
case Control.FREEPLAY_RIGHT: [RIGHT_SHOULDER];
case Control.VOLUME_UP: [];
case Control.VOLUME_DOWN: [];
case Control.VOLUME_MUTE: [];
case Control.DEBUG_MENU: [];
case Control.DEBUG_CHART: [];
case Control.DEBUG_STAGE: [];
default:
// Fallthrough.
}
@ -1392,7 +1440,7 @@ class FlxActionInputDigitalAndroid extends FlxActionInputDigital
override public function check(Action:FlxAction):Bool
{
returnswitch(trigger)
return switch(trigger)
{
#if android
case PRESSED: FlxG.android.checkStatus(inputID, PRESSED) || FlxG.android.checkStatus(inputID, PRESSED);
@ -1425,14 +1473,18 @@ enum Control
UI_RIGHT;
UI_DOWN;
RESET;
SCREENSHOT;
ACCEPT;
BACK;
PAUSE;
FULLSCREEN;
// CUTSCENE
CUTSCENE_ADVANCE;
// SCREENSHOT
// FREEPLAY
FREEPLAY_FAVORITE;
FREEPLAY_LEFT;
FREEPLAY_RIGHT;
// WINDOW
WINDOW_SCREENSHOT;
WINDOW_FULLSCREEN;
// VOLUME
VOLUME_UP;
VOLUME_DOWN;
@ -1475,11 +1527,15 @@ enum abstract Action(String) to String from String
var BACK = "back";
var PAUSE = "pause";
var RESET = "reset";
var FULLSCREEN = "fullscreen";
// SCREENSHOT
var SCREENSHOT = "screenshot";
// WINDOW
var WINDOW_FULLSCREEN = "window_fullscreen";
var WINDOW_SCREENSHOT = "window_screenshot";
// CUTSCENE
var CUTSCENE_ADVANCE = "cutscene_advance";
// FREEPLAY
var FREEPLAY_FAVORITE = "freeplay_favorite";
var FREEPLAY_LEFT = "freeplay_left";
var FREEPLAY_RIGHT = "freeplay_right";
// VOLUME
var VOLUME_UP = "volume_up";
var VOLUME_DOWN = "volume_down";

View file

@ -71,7 +71,7 @@ class GameOverSubState extends MusicBeatSubState
var gameOverMusic:Null<FunkinSound> = null;
/**
* Whether the player has confirmed and prepared to restart the level.
* Whether the player has confirmed and prepared to restart the level or to go back to the freeplay menu.
* This means the animation and transition have already started.
*/
var isEnding:Bool = false;
@ -237,15 +237,16 @@ class GameOverSubState extends MusicBeatSubState
}
// KEYBOARD ONLY: Restart the level when pressing the assigned key.
if (controls.ACCEPT && blueballed)
if (controls.ACCEPT && blueballed && !mustNotExit)
{
blueballed = false;
confirmDeath();
}
// KEYBOARD ONLY: Return to the menu when pressing the assigned key.
if (controls.BACK && !mustNotExit)
if (controls.BACK && !mustNotExit && !isEnding)
{
isEnding = true;
blueballed = false;
PlayState.instance.deathCounter = 0;
// PlayState.seenCutscene = false; // old thing...

View file

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

View file

@ -175,6 +175,12 @@ class PlayState extends MusicBeatSubState
*/
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.
*/
@ -603,6 +609,7 @@ class PlayState extends MusicBeatSubState
currentSong = params.targetSong;
if (params.targetDifficulty != null) currentDifficulty = params.targetDifficulty;
if (params.targetVariation != null) currentVariation = params.targetVariation;
if (params.targetInstrumental != null) currentInstrumental = params.targetInstrumental;
isPracticeMode = params.practiceMode ?? false;
isBotPlayMode = params.botPlayMode ?? false;
isMinimalMode = params.minimalMode ?? false;
@ -1974,7 +1981,7 @@ class PlayState extends MusicBeatSubState
if (!overrideMusic && !isGamePaused && currentChart != null)
{
currentChart.playInst(1.0, false);
currentChart.playInst(1.0, currentInstrumental, false);
}
if (FlxG.sound.music == null)
@ -2284,7 +2291,7 @@ class PlayState extends MusicBeatSubState
health += Constants.HEALTH_HOLD_BONUS_PER_SECOND * elapsed;
songScore += Std.int(Constants.SCORE_HOLD_BONUS_PER_SECOND * elapsed);
}
// Make sure the player keeps singing while the note is held by the bot.
if (isBotPlayMode && currentStage != null && currentStage.getBoyfriend() != null && currentStage.getBoyfriend().isSinging())
{
@ -2818,8 +2825,13 @@ class PlayState extends MusicBeatSubState
deathCounter = 0;
// TODO: This line of code makes me sad, but you can't really fix it without a breaking migration.
// `easy`, `erect`, `normal-pico`, etc.
var suffixedDifficulty = (currentVariation != Constants.DEFAULT_VARIATION
&& currentVariation != 'erect') ? '$currentDifficulty-${currentVariation}' : currentDifficulty;
var isNewHighscore = false;
var prevScoreData:Null<SaveScoreData> = Save.instance.getSongScore(currentSong.id, currentDifficulty);
var prevScoreData:Null<SaveScoreData> = Save.instance.getSongScore(currentSong.id, suffixedDifficulty);
if (currentSong != null && currentSong.validScore)
{
@ -2844,13 +2856,21 @@ class PlayState extends MusicBeatSubState
// adds current song data into the tallies for the level (story levels)
Highscore.talliesLevel = Highscore.combineTallies(Highscore.tallies, Highscore.talliesLevel);
if (!isPracticeMode && !isBotPlayMode && Save.instance.isSongHighScore(currentSong.id, currentDifficulty, data))
if (!isPracticeMode && !isBotPlayMode)
{
Save.instance.setSongScore(currentSong.id, currentDifficulty, data);
#if newgrounds
NGio.postScore(score, currentSong.id);
#end
isNewHighscore = true;
isNewHighscore = Save.instance.isSongHighScore(currentSong.id, suffixedDifficulty, data);
// If no high score is present, save both score and rank.
// If score or rank are better, save the highest one.
// If neither are higher, nothing will change.
Save.instance.applySongRank(currentSong.id, suffixedDifficulty, data);
if (isNewHighscore)
{
#if newgrounds
NGio.postScore(score, currentSong.id);
#end
}
}
}

View file

@ -53,8 +53,9 @@ class HealthIcon extends FunkinSprite
/**
* Apply the "bop" animation once every X steps.
* Defaults to once per beat.
*/
public var bopEvery:Int = 4;
public var bopEvery:Int = Constants.STEPS_PER_BEAT;
/**
* The amount, in degrees, to rotate the icon by when boping.

View file

@ -356,7 +356,10 @@ class Scoring
// Perfect (Platinum) is a Sick Full Clear
var isPerfectGold = scoreData.tallies.sick == scoreData.tallies.totalNotes;
if (isPerfectGold) return ScoringRank.PERFECT_GOLD;
if (isPerfectGold)
{
return ScoringRank.PERFECT_GOLD;
}
// Else, use the standard grades
@ -397,62 +400,79 @@ enum abstract ScoringRank(String)
var GOOD;
var SHIT;
@:op(A > B) static function compare(a:Null<ScoringRank>, b:Null<ScoringRank>):Bool
/**
* Converts ScoringRank to an integer value for comparison.
* Better ranks should be tied to a higher value.
*/
static function getValue(rank:Null<ScoringRank>):Int
{
if (rank == null) return -1;
switch (rank)
{
case PERFECT_GOLD:
return 5;
case PERFECT:
return 4;
case EXCELLENT:
return 3;
case GREAT:
return 2;
case GOOD:
return 1;
case SHIT:
return 0;
default:
return -1;
}
}
// Yes, we really need a different function for each comparison operator.
@:op(A > B) static function compareGT(a:Null<ScoringRank>, b:Null<ScoringRank>):Bool
{
if (a != null && b == null) return true;
if (a == null || b == null) return false;
var temp1:Int = 0;
var temp2:Int = 0;
var temp1:Int = getValue(a);
var temp2:Int = getValue(b);
// temp 1
switch (a)
{
case PERFECT_GOLD:
temp1 = 5;
case PERFECT:
temp1 = 4;
case EXCELLENT:
temp1 = 3;
case GREAT:
temp1 = 2;
case GOOD:
temp1 = 1;
case SHIT:
temp1 = 0;
default:
temp1 = -1;
}
// temp 2
switch (b)
{
case PERFECT_GOLD:
temp2 = 5;
case PERFECT:
temp2 = 4;
case EXCELLENT:
temp2 = 3;
case GREAT:
temp2 = 2;
case GOOD:
temp2 = 1;
case SHIT:
temp2 = 0;
default:
temp2 = -1;
}
if (temp1 > temp2)
{
return true;
}
else
{
return false;
}
return temp1 > temp2;
}
@:op(A >= B) static function compareGTEQ(a:Null<ScoringRank>, b:Null<ScoringRank>):Bool
{
if (a != null && b == null) return true;
if (a == null || b == null) return false;
var temp1:Int = getValue(a);
var temp2:Int = getValue(b);
return temp1 >= temp2;
}
@:op(A < B) static function compareLT(a:Null<ScoringRank>, b:Null<ScoringRank>):Bool
{
if (a != null && b == null) return true;
if (a == null || b == null) return false;
var temp1:Int = getValue(a);
var temp2:Int = getValue(b);
return temp1 < temp2;
}
@:op(A <= B) static function compareLTEQ(a:Null<ScoringRank>, b:Null<ScoringRank>):Bool
{
if (a != null && b == null) return true;
if (a == null || b == null) return false;
var temp1:Int = getValue(a);
var temp2:Int = getValue(b);
return temp1 <= temp2;
}
// @:op(A == B) isn't necessary!
/**
* Delay in seconds
*/
@ -462,15 +482,15 @@ enum abstract ScoringRank(String)
{
case PERFECT_GOLD | PERFECT:
// return 2.5;
return 95/24;
return 95 / 24;
case EXCELLENT:
return 0;
case GREAT:
return 5/24;
return 5 / 24;
case GOOD:
return 3/24;
return 3 / 24;
case SHIT:
return 2/24;
return 2 / 24;
default:
return 3.5;
}
@ -482,15 +502,15 @@ enum abstract ScoringRank(String)
{
case PERFECT_GOLD | PERFECT:
// return 2.5;
return 95/24;
return 95 / 24;
case EXCELLENT:
return 97/24;
return 97 / 24;
case GREAT:
return 95/24;
return 95 / 24;
case GOOD:
return 95/24;
return 95 / 24;
case SHIT:
return 95/24;
return 95 / 24;
default:
return 3.5;
}
@ -502,15 +522,15 @@ enum abstract ScoringRank(String)
{
case PERFECT_GOLD | PERFECT:
// return 2.5;
return 129/24;
return 129 / 24;
case EXCELLENT:
return 122/24;
return 122 / 24;
case GREAT:
return 109/24;
return 109 / 24;
case GOOD:
return 107/24;
return 107 / 24;
case SHIT:
return 186/24;
return 186 / 24;
default:
return 3.5;
}
@ -522,15 +542,15 @@ enum abstract ScoringRank(String)
{
case PERFECT_GOLD | PERFECT:
// return 2.5;
return 140/24;
return 140 / 24;
case EXCELLENT:
return 140/24;
return 140 / 24;
case GREAT:
return 129/24;
return 129 / 24;
case GOOD:
return 127/24;
return 127 / 24;
case SHIT:
return 207/24;
return 207 / 24;
default:
return 3.5;
}

View file

@ -682,9 +682,9 @@ class SongDifficulty
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);

View file

@ -1,6 +1,7 @@
package funkin.save;
import flixel.util.FlxSave;
import funkin.util.FileUtil;
import funkin.input.Controls.Device;
import funkin.play.scoring.Scoring;
import funkin.play.scoring.Scoring.ScoringRank;
@ -58,7 +59,7 @@ class Save
this.data = data;
// Make sure the verison number is up to date before we flush.
this.data.version = Save.SAVE_DATA_VERSION;
updateVersionToLatest();
}
public static function getDefault():RawSaveData
@ -503,7 +504,7 @@ class Save
}
/**
* Apply the score the user achieved for a given song on a given difficulty.
* Directly set the score the user achieved for a given song on a given difficulty.
*/
public function setSongScore(songId:String, difficultyId:String, score:SaveScoreData):Void
{
@ -518,6 +519,44 @@ class Save
flush();
}
/**
* Only replace the ranking data for the song, because the old score is still better.
*/
public function applySongRank(songId:String, difficultyId:String, newScoreData:SaveScoreData):Void
{
var newRank = Scoring.calculateRank(newScoreData);
if (newScoreData == null || newRank == null) return;
var song = data.scores.songs.get(songId);
if (song == null)
{
song = [];
data.scores.songs.set(songId, song);
}
var previousScoreData = song.get(difficultyId);
var previousRank = Scoring.calculateRank(previousScoreData);
if (previousScoreData == null || previousRank == null)
{
// Directly set the highscore.
setSongScore(songId, difficultyId, newScoreData);
return;
}
// Set the high score and the high rank separately.
var newScore:SaveScoreData =
{
score: (previousScoreData.score > newScoreData.score) ? previousScoreData.score : newScoreData.score,
tallies: (previousRank > newRank) ? previousScoreData.tallies : newScoreData.tallies
};
song.set(difficultyId, newScore);
flush();
}
/**
* Is the provided score data better than the current high score for the given song?
* @param songId The song ID to check.
@ -543,6 +582,39 @@ class Save
return score.score > currentScore.score;
}
/**
* Is the provided score data better than the current rank for the given song?
* @param songId The song ID to check.
* @param difficultyId The difficulty to check.
* @param score The score to check the rank for.
* @return Whether the score's rank is better than the current rank.
*/
public function isSongHighRank(songId:String, difficultyId:String = 'normal', score:SaveScoreData):Bool
{
var newScoreRank = Scoring.calculateRank(score);
if (newScoreRank == null)
{
// The provided score is invalid.
return false;
}
var song = data.scores.songs.get(songId);
if (song == null)
{
song = [];
data.scores.songs.set(songId, song);
}
var currentScore = song.get(difficultyId);
var currentScoreRank = Scoring.calculateRank(currentScore);
if (currentScoreRank == null)
{
// There is no primary highscore for this song.
return true;
}
return newScoreRank > currentScoreRank;
}
/**
* Has the provided song been beaten on one of the listed difficulties?
* @param songId The song ID to check.
@ -832,6 +904,29 @@ class Save
return cast legacySave.data;
}
}
/**
* Serialize this Save into a JSON string.
* @param pretty Whether the JSON should be big ol string (false),
* or formatted with tabs (true)
* @return The JSON string.
*/
public function serialize(pretty:Bool = true):String
{
var ignoreNullOptionals = true;
var writer = new json2object.JsonWriter<RawSaveData>(ignoreNullOptionals);
return writer.write(data, pretty ? ' ' : null);
}
public function updateVersionToLatest():Void
{
this.data.version = Save.SAVE_DATA_VERSION;
}
public function debug_dumpSave():Void
{
FileUtil.saveFile(haxe.io.Bytes.ofString(this.serialize()), [FileUtil.FILE_FILTER_JSON], null, null, './save.json', 'Write save data as JSON...');
}
}
/**
@ -904,6 +999,9 @@ typedef SaveHighScoresData =
typedef SaveDataMods =
{
var enabledMods:Array<String>;
// TODO: Make this not trip up the serializer when debugging.
@:jignored
var modOptions:Map<String, Dynamic>;
}

View file

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

View file

@ -904,7 +904,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
function set_notePreviewDirty(value:Bool):Bool
{
trace('Note preview dirtied!');
// trace('Note preview dirtied!');
return notePreviewDirty = value;
}

View file

@ -35,7 +35,15 @@ class SetItemSelectionCommand implements ChartEditorCommand
{
var eventSelected = this.events[0];
state.eventKindToPlace = eventSelected.eventKind;
if (state.eventKindToPlace == eventSelected.eventKind)
{
trace('Target event kind matches selection: ${eventSelected.eventKind}');
}
else
{
trace('Switching target event kind to match selection: ${state.eventKindToPlace} != ${eventSelected.eventKind}');
state.eventKindToPlace = eventSelected.eventKind;
}
// This code is here to parse event data that's not built as a struct for some reason.
// TODO: Clean this up or get rid of it.

View file

@ -201,7 +201,8 @@ class ChartEditorThemeHandler
// Selection borders horizontally in the middle.
for (i in 1...(Conductor.instance.stepsPerMeasure))
{
if ((i % Conductor.instance.beatsPerMeasure) == 0)
// There may be a different number of beats per measure, but there's always 4 steps per beat.
if ((i % Constants.STEPS_PER_BEAT) == 0)
{
state.gridBitmap.fillRect(new Rectangle(0, (ChartEditorState.GRID_SIZE * i) - (GRID_BEAT_DIVIDER_WIDTH / 2), state.gridBitmap.width,
GRID_BEAT_DIVIDER_WIDTH),

View file

@ -58,17 +58,8 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
function initialize():Void
{
toolboxEventsEventKind.dataSource = new ArrayDataSource();
var songEvents:Array<SongEvent> = SongEventRegistry.listEvents();
for (event in songEvents)
{
toolboxEventsEventKind.dataSource.add({text: event.getTitle(), value: event.id});
}
toolboxEventsEventKind.onChange = function(event:UIEvent) {
var eventType:String = event.data.value;
var eventType:String = event.data.id;
trace('ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Event type changed: $eventType');
@ -83,7 +74,7 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
return;
}
buildEventDataFormFromSchema(toolboxEventsDataGrid, schema);
buildEventDataFormFromSchema(toolboxEventsDataGrid, schema, chartEditorState.eventKindToPlace);
if (!_initializing && chartEditorState.currentEventSelection.length > 0)
{
@ -98,14 +89,40 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
chartEditorState.notePreviewDirty = true;
}
}
toolboxEventsEventKind.value = chartEditorState.eventKindToPlace;
var startingEventValue = ChartEditorDropdowns.populateDropdownWithSongEvents(toolboxEventsEventKind, chartEditorState.eventKindToPlace);
trace('ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Starting event kind: ${startingEventValue}');
toolboxEventsEventKind.value = startingEventValue;
}
public override function refresh():Void
{
super.refresh();
toolboxEventsEventKind.value = chartEditorState.eventKindToPlace;
var newDropdownElement = ChartEditorDropdowns.findDropdownElement(chartEditorState.eventKindToPlace, toolboxEventsEventKind);
if (newDropdownElement == null)
{
throw 'ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Event kind not in dropdown: ${chartEditorState.eventKindToPlace}';
}
else if (toolboxEventsEventKind.value != newDropdownElement || lastEventKind != toolboxEventsEventKind.value.id)
{
toolboxEventsEventKind.value = newDropdownElement;
var schema:SongEventSchema = SongEventRegistry.getEventSchema(chartEditorState.eventKindToPlace);
if (schema == null)
{
trace('ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Unknown event kind: ${chartEditorState.eventKindToPlace}');
}
else
{
trace('ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Event kind changed: ${toolboxEventsEventKind.value.id} != ${newDropdownElement.id} != ${lastEventKind}, rebuilding form');
buildEventDataFormFromSchema(toolboxEventsDataGrid, schema, chartEditorState.eventKindToPlace);
}
}
else
{
trace('ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Event kind not changed: ${toolboxEventsEventKind.value} == ${newDropdownElement} == ${lastEventKind}');
}
for (pair in chartEditorState.eventDataToPlace.keyValueIterator())
{
@ -116,7 +133,7 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
if (field == null)
{
throw 'ChartEditorToolboxHandler.refresh() - Field "${fieldId}" does not exist in the event data form.';
throw 'ChartEditorToolboxHandler.refresh() - Field "${fieldId}" does not exist in the event data form for kind ${lastEventKind}.';
}
else
{
@ -141,9 +158,15 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
}
}
function buildEventDataFormFromSchema(target:Box, schema:SongEventSchema):Void
var lastEventKind:String = 'unknown';
function buildEventDataFormFromSchema(target:Box, schema:SongEventSchema, eventKind:String):Void
{
trace(schema);
trace('Building event data form from schema for event kind: ${eventKind}');
// trace(schema);
lastEventKind = eventKind ?? 'unknown';
// Clear the frame.
target.removeAllComponents();
@ -188,6 +211,9 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
var dropDown:DropDown = new DropDown();
dropDown.id = field.name;
dropDown.width = 200.0;
dropDown.dropdownSize = 10;
dropDown.dropdownWidth = 300;
dropDown.searchable = true;
dropDown.dataSource = new ArrayDataSource();
if (field.keys == null) throw 'Field "${field.name}" is of Enum type but has no keys.';
@ -197,12 +223,15 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
for (optionName in field.keys.keys())
{
var optionValue:Null<Dynamic> = field.keys.get(optionName);
trace('$optionName : $optionValue');
// trace('$optionName : $optionValue');
dropDown.dataSource.add({value: optionValue, text: optionName});
}
dropDown.value = field.defaultValue;
// TODO: Add an option to customize sort.
dropDown.dataSource.sort('text', ASCENDING);
input = dropDown;
case STRING:
input = new TextField();

View file

@ -3,11 +3,13 @@ package funkin.ui.debug.charting.util;
import funkin.data.notestyle.NoteStyleRegistry;
import funkin.play.notes.notestyle.NoteStyle;
import funkin.data.stage.StageData;
import funkin.play.event.SongEvent;
import funkin.data.stage.StageRegistry;
import funkin.play.character.CharacterData;
import haxe.ui.components.DropDown;
import funkin.play.stage.Stage;
import funkin.play.character.BaseCharacter.CharacterType;
import funkin.data.event.SongEventRegistry;
import funkin.play.character.CharacterData.CharacterDataParser;
/**
@ -81,6 +83,42 @@ class ChartEditorDropdowns
return returnValue;
}
public static function populateDropdownWithSongEvents(dropDown:DropDown, startingEventId:String):DropDownEntry
{
dropDown.dataSource.clear();
var returnValue:DropDownEntry = {id: "FocusCamera", text: "Focus Camera"};
var songEvents:Array<SongEvent> = SongEventRegistry.listEvents();
for (event in songEvents)
{
var value = {id: event.id, text: event.getTitle()};
if (startingEventId == event.id) returnValue = value;
dropDown.dataSource.add(value);
}
dropDown.dataSource.sort('text', ASCENDING);
return returnValue;
}
/**
* Given the ID of a dropdown element, find the corresponding entry in the dropdown's dataSource.
*/
public static function findDropdownElement(id:String, dropDown:DropDown):Null<DropDownEntry>
{
// Attempt to find the entry.
for (entryIndex in 0...dropDown.dataSource.size)
{
var entry = dropDown.dataSource.get(entryIndex);
if (entry.id == id) return entry;
}
// Not found.
return null;
}
/**
* Populate a dropdown with a list of note styles.
*/

View file

@ -266,7 +266,7 @@ class DJBoyfriend extends FlxAtlasSprite
// Fade out music to 40% volume over 1 second.
// This helps make the TV a bit more audible.
FlxG.sound.music.fadeOut(1.0, 0.4);
FlxG.sound.music.fadeOut(1.0, 0.1);
// Play the cartoon at a random time between the start and 5 seconds from the end.
cartoonSnd.time = FlxG.random.float(0, Math.max(cartoonSnd.length - (5 * Constants.MS_PER_SEC), 0.0));

View file

@ -291,7 +291,10 @@ class FreeplayState extends MusicBeatSubState
// Only display songs which actually have available difficulties for the current character.
var displayedVariations = song.getVariationsByCharId(currentCharacter);
trace(songId);
trace(displayedVariations);
var availableDifficultiesForSong:Array<String> = song.listDifficulties(displayedVariations, false);
trace(availableDifficultiesForSong);
if (availableDifficultiesForSong.length == 0) continue;
songs.push(new FreeplaySongData(levelId, songId, song, displayedVariations));
@ -455,15 +458,20 @@ class FreeplayState extends MusicBeatSubState
add(dj);
bgDad = new FlxSprite(pinkBack.width * 0.75, 0).loadGraphic(Paths.image('freeplay/freeplayBGdad'));
bgDad.setGraphicSize(0, FlxG.height);
bgDad.updateHitbox();
bgDad = new FlxSprite(pinkBack.width * 0.74, 0).loadGraphic(Paths.image('freeplay/freeplayBGdad'));
bgDad.shader = new AngleMask();
bgDad.visible = false;
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!
// 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],
{
x: FlxG.width * 1.5,
@ -472,7 +480,7 @@ class FreeplayState extends MusicBeatSubState
});
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;
@ -589,6 +597,8 @@ class FreeplayState extends MusicBeatSubState
generateSongList({filterType: FAVORITE}, true);
case 'ALL':
generateSongList(null, true);
case '#':
generateSongList({filterType: REGEXP, filterData: '0-9'}, true);
default:
generateSongList({filterType: REGEXP, filterData: str}, true);
}
@ -597,6 +607,7 @@ class FreeplayState extends MusicBeatSubState
// that is, only if there's more than one song in the group!
if (grpCapsules.members.length > 0)
{
FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4);
curSelected = 1;
changeSelection();
}
@ -965,16 +976,20 @@ class FreeplayState extends MusicBeatSubState
grpCapsules.members[curSelected].ranking.scale.set(20, 20);
grpCapsules.members[curSelected].blurredRanking.scale.set(20, 20);
grpCapsules.members[curSelected].ranking.animation.play(fromResults.newRank.getFreeplayRankIconAsset(), true);
// grpCapsules.members[curSelected].ranking.animation.curAnim.name, true);
if (fromResults?.newRank != null)
{
grpCapsules.members[curSelected].ranking.animation.play(fromResults.newRank.getFreeplayRankIconAsset(), true);
}
FlxTween.tween(grpCapsules.members[curSelected].ranking, {"scale.x": 1, "scale.y": 1}, 0.1);
grpCapsules.members[curSelected].blurredRanking.animation.play(fromResults.newRank.getFreeplayRankIconAsset(), true);
if (fromResults?.newRank != null)
{
grpCapsules.members[curSelected].blurredRanking.animation.play(fromResults.newRank.getFreeplayRankIconAsset(), true);
}
FlxTween.tween(grpCapsules.members[curSelected].blurredRanking, {"scale.x": 1, "scale.y": 1}, 0.1);
new FlxTimer().start(0.1, _ -> {
// trace(grpCapsules.members[curSelected].ranking.rank);
if (fromResults?.oldRank != null)
{
grpCapsules.members[curSelected].fakeRanking.visible = false;
@ -1003,7 +1018,6 @@ class FreeplayState extends MusicBeatSubState
FunkinSound.playOnce(Paths.sound('ranks/rankinnormal'));
}
rankCamera.zoom = 1.3;
// FlxTween.tween(rankCamera, {"zoom": 1.4}, 0.3, {ease: FlxEase.elasticOut});
FlxTween.tween(rankCamera, {"zoom": 1.5}, 0.3, {ease: FlxEase.backInOut});
@ -1021,13 +1035,11 @@ class FreeplayState extends MusicBeatSubState
new FlxTimer().start(0.4, _ -> {
FlxTween.tween(funnyCam, {"zoom": 1}, 0.8, {ease: FlxEase.sineIn});
FlxTween.tween(rankCamera, {"zoom": 1.2}, 0.8, {ease: FlxEase.backIn});
// IntervalShake.shake(grpCapsules.members[curSelected], 0.8 + 0.5, 1 / 24, 0, 2, FlxEase.quadIn);
FlxTween.tween(grpCapsules.members[curSelected], {x: originalPos.x - 7, y: originalPos.y - 80}, 0.8 + 0.5, {ease: FlxEase.quartIn});
});
new FlxTimer().start(0.6, _ -> {
rankAnimSlam(fromResults);
// IntervalShake.shake(grpCapsules.members[curSelected].capsule, 0.3, 1 / 30, 0, 0.3, FlxEase.quartIn);
});
}
@ -1192,51 +1204,9 @@ class FreeplayState extends MusicBeatSubState
// {
// rankAnimSlam(fromResultsParams);
// }
if (FlxG.keys.justPressed.G)
{
sparks.y -= 2;
trace(sparks.x, sparks.y);
}
if (FlxG.keys.justPressed.V)
{
sparks.x -= 2;
trace(sparks.x, sparks.y);
}
if (FlxG.keys.justPressed.N)
{
sparks.x += 2;
trace(sparks.x, sparks.y);
}
if (FlxG.keys.justPressed.B)
{
sparks.y += 2;
trace(sparks.x, sparks.y);
}
if (FlxG.keys.justPressed.I)
{
sparksADD.y -= 2;
trace(sparksADD.x, sparksADD.y);
}
if (FlxG.keys.justPressed.J)
{
sparksADD.x -= 2;
trace(sparksADD.x, sparksADD.y);
}
if (FlxG.keys.justPressed.L)
{
sparksADD.x += 2;
trace(sparksADD.x, sparksADD.y);
}
if (FlxG.keys.justPressed.K)
{
sparksADD.y += 2;
trace(sparksADD.x, sparksADD.y);
}
#end
if (FlxG.keys.justPressed.F && !busy)
if (controls.FREEPLAY_FAVORITE && !busy)
{
var targetSong = grpCapsules.members[curSelected]?.songData;
if (targetSong != null)
@ -1602,7 +1572,19 @@ class FreeplayState extends MusicBeatSubState
var daSong:Null<FreeplaySongData> = grpCapsules.members[curSelected].songData;
if (daSong != null)
{
var songScore:SaveScoreData = Save.instance.getSongScore(grpCapsules.members[curSelected].songData.songId, currentDifficulty);
// TODO: Make this actually be the variation you're focused on. We don't need to fetch the song metadata just to calculate it.
var targetSong:Song = SongRegistry.instance.fetchEntry(grpCapsules.members[curSelected].songData.songId);
if (targetSong == null)
{
FlxG.log.warn('WARN: could not find song with id (${grpCapsules.members[curSelected].songData.songId})');
return;
}
var targetVariation:String = targetSong.getFirstValidVariation(currentDifficulty);
// TODO: This line of code makes me sad, but you can't really fix it without a breaking migration.
var suffixedDifficulty = (targetVariation != Constants.DEFAULT_VARIATION
&& targetVariation != 'erect') ? '$currentDifficulty-${targetVariation}' : currentDifficulty;
var songScore:SaveScoreData = Save.instance.getSongScore(grpCapsules.members[curSelected].songData.songId, suffixedDifficulty);
intendedScore = songScore?.score ?? 0;
intendedCompletion = songScore == null ? 0.0 : ((songScore.tallies.sick + songScore.tallies.good) / songScore.tallies.totalNotes);
rememberedDifficulty = currentDifficulty;
@ -1737,11 +1719,20 @@ class FreeplayState extends MusicBeatSubState
FlxG.log.warn('WARN: could not find song with id (${cap.songData.songId})');
return;
}
var targetDifficulty:String = currentDifficulty;
var targetVariation:String = targetSong.getFirstValidVariation(targetDifficulty);
var targetDifficultyId:String = currentDifficulty;
var targetVariation:String = targetSong.getFirstValidVariation(targetDifficultyId);
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.
FunkinSound.playOnce(Paths.sound('confirmMenu'));
dj.confirm();
@ -1790,8 +1781,9 @@ class FreeplayState extends MusicBeatSubState
LoadingState.loadPlayState(
{
targetSong: targetSong,
targetDifficulty: targetDifficulty,
targetDifficulty: targetDifficultyId,
targetVariation: targetVariation,
targetInstrumental: targetInstId,
practiceMode: false,
minimalMode: false,
@ -1828,12 +1820,12 @@ class FreeplayState extends MusicBeatSubState
function changeSelection(change:Int = 0):Void
{
if (!prepForNewRank) FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4);
var prevSelected:Int = curSelected;
curSelected += change;
if (!prepForNewRank && curSelected != prevSelected) FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4);
if (curSelected < 0) curSelected = grpCapsules.countLiving() - 1;
if (curSelected >= grpCapsules.countLiving()) curSelected = 0;
@ -2087,7 +2079,7 @@ class FreeplaySongData
this.songDifficulties = song.listDifficulties(null, variations, false, false);
if (!this.songDifficulties.contains(currentDifficulty)) currentDifficulty = Constants.DEFAULT_DIFFICULTY;
var songDifficulty:SongDifficulty = song.getDifficulty(currentDifficulty, variations);
var songDifficulty:SongDifficulty = song.getDifficulty(currentDifficulty, null, variations);
if (songDifficulty == null) return;
this.songStartingBpm = songDifficulty.getStartingBPM();
this.songName = songDifficulty.songName;

View file

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

View file

@ -219,7 +219,7 @@ class SongMenuItem extends FlxSpriteGroup
favIconBlurred.visible = false;
add(favIconBlurred);
favIcon = new FlxSprite(380, 40);
favIcon = new FlxSprite(favIconBlurred.x, favIconBlurred.y);
favIcon.frames = Paths.getSparrowAtlas('freeplay/favHeart');
favIcon.animation.addByPrefix('fav', 'favorite heart', 24, false);
favIcon.animation.play('fav');
@ -294,21 +294,34 @@ class SongMenuItem extends FlxSpriteGroup
}
}
// 255, 27 normal
// 220, 27 favourited
/**
* Checks whether the song is favorited, and/or has a rank, and adjusts the clipping
* for the scenario when the text could be too long
*/
public function checkClip():Void
{
var clipSize:Int = 290;
var clipType:Int = 0;
if (ranking.visible == true) clipType += 1;
if (favIcon.visible == true) clipType = 2;
if (ranking.visible)
{
favIconBlurred.x = this.x + 370;
favIcon.x = favIconBlurred.x;
clipType += 1;
}
else
{
favIconBlurred.x = favIcon.x = this.x + 405;
}
if (favIcon.visible) clipType += 1;
switch (clipType)
{
case 2:
clipSize = 220;
clipSize = 210;
case 1:
clipSize = 255;
clipSize = 245;
}
songText.clipWidth = clipSize;
}

View file

@ -371,6 +371,33 @@ class MainMenuState extends MusicBeatState
}
});
}
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.R)
{
// Give the user a hypothetical overridden score,
// and see if we can maintain that golden P rank.
funkin.save.Save.instance.setSongScore('tutorial', 'easy',
{
score: 1234567,
tallies:
{
sick: 0,
good: 0,
bad: 0,
shit: 1,
missed: 0,
combo: 0,
maxCombo: 0,
totalNotesHit: 1,
totalNotes: 10,
}
});
}
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.E)
{
funkin.save.Save.instance.debug_dumpSave();
}
#end
if (FlxG.sound.music != null && FlxG.sound.music.volume < 0.8)

View file

@ -28,6 +28,8 @@ class ControlsMenu extends funkin.ui.options.OptionsState.Page
[NOTE_UP, NOTE_DOWN, NOTE_LEFT, NOTE_RIGHT],
[UI_UP, UI_DOWN, UI_LEFT, UI_RIGHT, ACCEPT, BACK],
[CUTSCENE_ADVANCE],
[FREEPLAY_FAVORITE, FREEPLAY_LEFT, FREEPLAY_RIGHT],
[WINDOW_FULLSCREEN, WINDOW_SCREENSHOT],
[VOLUME_UP, VOLUME_DOWN, VOLUME_MUTE],
[DEBUG_MENU, DEBUG_CHART]
];
@ -108,6 +110,18 @@ class ControlsMenu extends funkin.ui.options.OptionsState.Page
headers.add(new AtlasText(0, y, "CUTSCENE", AtlasFont.BOLD)).screenCenter(X);
y += spacer;
}
else if (currentHeader != "FREEPLAY_" && name.indexOf("FREEPLAY_") == 0)
{
currentHeader = "FREEPLAY_";
headers.add(new AtlasText(0, y, "FREEPLAY", AtlasFont.BOLD)).screenCenter(X);
y += spacer;
}
else if (currentHeader != "WINDOW_" && name.indexOf("WINDOW_") == 0)
{
currentHeader = "WINDOW_";
headers.add(new AtlasText(0, y, "WINDOW", AtlasFont.BOLD)).screenCenter(X);
y += spacer;
}
else if (currentHeader != "VOLUME_" && name.indexOf("VOLUME_") == 0)
{
currentHeader = "VOLUME_";
@ -123,10 +137,10 @@ class ControlsMenu extends funkin.ui.options.OptionsState.Page
if (currentHeader != null && name.indexOf(currentHeader) == 0) name = name.substr(currentHeader.length);
var label = labels.add(new AtlasText(150, y, name, AtlasFont.BOLD));
var label = labels.add(new AtlasText(100, y, name, AtlasFont.BOLD));
label.alpha = 0.6;
for (i in 0...COLUMNS)
createItem(label.x + 400 + i * 300, y, control, i);
createItem(label.x + 550 + i * 400, y, control, i);
y += spacer;
}

View file

@ -387,6 +387,7 @@ class StoryMenuState extends MusicBeatState
function changeLevel(change:Int = 0):Void
{
var currentIndex:Int = levelList.indexOf(currentLevelId);
var prevIndex:Int = currentIndex;
currentIndex += change;
@ -417,7 +418,7 @@ class StoryMenuState extends MusicBeatState
}
}
FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4);
if (currentIndex != prevIndex) FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4);
updateText();
updateBackground(previousLevelId);

View file

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

View file

@ -19,6 +19,7 @@ import haxe.ui.containers.dialogs.Dialogs.FileDialogExtensionInfo;
class FileUtil
{
public static final FILE_FILTER_FNFC:FileFilter = new FileFilter("Friday Night Funkin' Chart (.fnfc)", "*.fnfc");
public static final FILE_FILTER_JSON:FileFilter = new FileFilter("JSON Data File (.json)", "*.json");
public static final FILE_FILTER_ZIP:FileFilter = new FileFilter("ZIP Archive (.zip)", "*.zip");
public static final FILE_FILTER_PNG:FileFilter = new FileFilter("PNG Image (.png)", "*.png");

View file

@ -92,7 +92,7 @@ class WindowUtil
});
openfl.Lib.current.stage.addEventListener(openfl.events.KeyboardEvent.KEY_DOWN, (e:openfl.events.KeyboardEvent) -> {
for (key in PlayerSettings.player1.controls.getKeysForAction(FULLSCREEN))
for (key in PlayerSettings.player1.controls.getKeysForAction(WINDOW_FULLSCREEN))
{
if (e.keyCode == key)
{

View file

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