Merge remote-tracking branch 'secret/rewrite/master' into bugfix/html5-save-data

This commit is contained in:
EliteMasterEric 2024-05-28 01:35:43 -04:00
commit 3b5e85d050
29 changed files with 415 additions and 143 deletions

View file

@ -4,6 +4,46 @@ 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.0] - 2024-05-??
### Added
- 2 new Erect remixes, Eggnog and Satin Panties. Check them out from
- Improvements to the Freeplay screen, with song difficulty ratings and player rank displays.
- Reworked the Results screen, with additional animations and audio based on your performance.
- Added a Charter field to the chart format, to allow for crediting the creator of a level's chart.
- You can see who charted a song from the Pause menu.
### Changed
- Tweaked the charts for several songs:
- Winter Horrorland
- Stress
- Lit Up
- 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.
### Fixed
- Fixed a bug where pressing the volume keys would stop the Toy commercial (thanks gamerbross!)
- Fixed a bug where the Chart Editor would crash when losing (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!)
## [0.3.3] - 2024-05-14
### Changed
- Cleaned up some code in `PlayAnimationSongEvent.hx` (thanks BurgerBalls!)
### Fixed
- Fix Web Loading Bar (thanks lemz1!)
- Don't allow any more inputs when exiting freeplay (thanks gamerbros!)
- Fixed using mouse wheel to scroll on freeplay (thanks JugieNoob!)
- Fixed the reset's of the health icons, score, and notes when re-entering gameplay from gameover (thanks ImCodist!)
- Fixed the chart editor character selector's hitbox width (thanks MadBear422!)
- Fixed camera stutter once a wipe transition to the Main Menu completes (thanks ImCodist!)
- Fixed an issue where hold note would be invisible for a single frame (thanks ImCodist!)
- Fix tween accumulation on title screen when pressing Y multiple times (thanks TheGaloXx!)
- Fix for a game over easter egg so you don't accidentally exit it when viewing
- Fix a crash when querying FlxG.state in the crash handler
- Fix an issue where the Freeplay menu never displays 100% clear
- Chart debug key now properly returns you to the previous chart editor session if you were playtesting a chart (thanks nebulazorua!)
- Hopefully fixed Freeplay crashes on AMD gpu's
## [0.3.2] - 2024-05-03 ## [0.3.2] - 2024-05-03
### Added ### Added
- Added `,` and `.` keybinds to the Chart Editor. These place Focus Camera events at the playhead, for the opponent and player respectively. - Added `,` and `.` keybinds to the Chart Editor. These place Focus Camera events at the playhead, for the opponent and player respectively.

View file

@ -5,7 +5,7 @@ The Friday Night Funkin' source code is licensed under the Apache 2.0 license: (
Friday Night Funkin' Copyright 2020-2024 The Funkin' Crew Inc. Friday Night Funkin' Copyright 2020-2024 The Funkin' Crew Inc.
All Rights Reserved. "Friday Night Funkin'" and the "Friday Night Funkin'" logo are trademarks of The Funkin' Crew Inc. All Rights Reserved. "Friday Night Funkin'" and the "Friday Night Funkin'" logo are trademarks of The Funkin' Crew Inc.
You can view the `funkin-assets` license here: (https://github.com/FunkinCrew/funkin-assets/blob/main/LICENSE.md) You can view the `funkin-assets` license here: (https://github.com/FunkinCrew/funkin.assets/blob/main/LICENSE.md)
## Apache 2.0 License ## Apache 2.0 License

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<project> <project>
<!-- _________________________ Application Settings _________________________ --> <!-- _________________________ Application Settings _________________________ -->
<app title="Friday Night Funkin'" file="Funkin" packageName="com.funkin.fnf" package="com.funkin.fnf" main="Main" version="0.3.2" company="ninjamuffin99" /> <app title="Friday Night Funkin'" file="Funkin" packageName="com.funkin.fnf" package="com.funkin.fnf" main="Main" version="0.3.3" 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

@ -1,19 +1,17 @@
# Friday Night Funkin' &middot; [![GitHub license](https://img.shields.io/badge/license-Modified%20Apache%20V2-blue.svg)](https://github.com/ninjamuffin99/Funkin/blob/master/LICENSE.md) ![Repo size](https://img.shields.io/github/repo-size/ninjamuffin99/Funkin) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/ninjamuffin99/Funkin/pulls) # Friday Night Funkin'
Friday Night Funkin' is a rhythm game that doubles as a playable cartoon. Built using HaxeFlixel for Ludem 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 it's community. Extra love to Tom Fulp.
## [Play for free on Newgrounds!](https://www.newgrounds.com/portal/view/770371) - [Playable web demo on Newgrounds!](https://www.newgrounds.com/portal/view/770371)
## [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)
![Friday Night Funkin' Logo](./art/thumbnailNewer.png)
# Getting Started # Getting Started
**PLEASE USE THE LINKS ABOVE IF YOU JUST WANT TO PLAY THE GAME** **PLEASE USE THE LINKS ABOVE IF YOU JUST WANT TO PLAY THE GAME**
To learn how to install the necessary dependencies and compile the game from source, please check out our [building the game](/docs/compiling.md) guide. To learn how to install the necessary dependencies and compile the game from source, please check out our [building the game](/docs/COMPILING.md) guide.
# Contributing # Contributing
@ -21,6 +19,8 @@ Please check out our [Contributor's guide](./CONTRIBUTORS.md) on how you can act
# Credits and Special Thanks # Credits and Special Thanks
Full credits can be found in-game, or wherever the credits.json file is.
## Programming ## Programming
- [ninjamuffin99](https://twitter.com/ninja_muffin99) - Lead Programmer - [ninjamuffin99](https://twitter.com/ninja_muffin99) - Lead Programmer
- [EliteMasterEric](https://twitter.com/EliteMasterEric) - Programmer - [EliteMasterEric](https://twitter.com/EliteMasterEric) - Programmer

2
art

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

2
assets

@ -1 +1 @@
Subproject commit ce7dabffbebc154c9dda1f01e92dbef83e3405ab Subproject commit 8a8239cb50b5277fb0cfce041b3d8a9dfc780c35

View file

@ -3,7 +3,7 @@
0. Setup 0. Setup
- Download Haxe from [Haxe.org](https://haxe.org) - Download Haxe from [Haxe.org](https://haxe.org)
1. Cloning the Repository: Make sure when you clone, you clone the submodules to get the assets repo: 1. Cloning the Repository: Make sure when you clone, you clone the submodules to get the assets repo:
- `git clone --recurse-submodules https://github.com/FunkinCrew/funkin-secret.git` - `git clone --recurse-submodules https://github.com/FunkinCrew/funkin.git`
- If you accidentally cloned without the `assets` submodule (aka didn't follow the step above), you can run `git submodule update --init --recursive` to get the assets in a foolproof way. - If you accidentally cloned without the `assets` submodule (aka didn't follow the step above), you can run `git submodule update --init --recursive` to get the assets in a foolproof way.
2. Install `hmm` (run `haxelib --global install hmm` and then `haxelib --global run hmm setup`) 2. Install `hmm` (run `haxelib --global install hmm` and then `haxelib --global run hmm setup`)
3. Install all haxelibs of the current branch by running `hmm install` 3. Install all haxelibs of the current branch by running `hmm install`
@ -18,3 +18,7 @@
- HTML5: Compiles without any extra setup - HTML5: Compiles without any extra setup
6. If you are targeting for native, you may need to run `lime rebuild PLATFORM` and `lime rebuild PLATFORM -debug` 6. If you are targeting for native, you may need to run `lime rebuild PLATFORM` and `lime rebuild PLATFORM -debug`
7. `lime test PLATFORM` ! Add `-debug` to enable several debug features such as time travel (`PgUp`/`PgDn` in Play State). 7. `lime test PLATFORM` ! Add `-debug` to enable several debug features such as time travel (`PgUp`/`PgDn` in Play State).
# Troubleshooting
- During the cloning process, you may experience an error along the lines of `error: RPC failed; curl 92 HTTP/2 stream 0 was not closed cleanly: PROTOCOL_ERROR (err 1)` due to poor connectivity. A common fix is to run ` git config --global http.postBuffer 4096M`.

View file

@ -1,31 +1,31 @@
# Funkin' Debug Hotkeys # Funkin' Debug Hotkeys
`F4` (EVERYWHERE) - Leave Current State and move to Main Menu Most of this functionality is only available on debug builds of the game!
`F5` (EVERYWHERE) - Hot Reload Data Files
`Y` (Title Screen) - WOAH ## Any State
- `F2`: ***OVERLAY***: Enables the Flixel debug overlay, which has partial support for scripting.
- `F3`: ***SCREENSHOT***: Takes a screenshot of the game and saves it to the local `screenshots` directory. Works outside of debug builds too!
- `F4`: ***EJECT***: Forcibly switch state to the Main Menu (with no extra transition). Useful if you're stuck in a level and you need to get out!
- `F5`: ***HOT RELOAD***: Forcibly reload the game's scripts and data files, then restart the current state. If any files in the `assets` folder have been modified, the game should process the changes for you! NOTE: Known bug, this does not reset song charts or song scripts, but it should reset everything else (such as stage layout data and character animation data).
- `CTRL-SHIFT-L`: ***FORCE CRASH***: Immediately crash the game with a detailed crash log and a stack trace.
`~` (Main Menu) - Access Debug Menu ## **Play State**
- `H`: ***HIDE UI***: Makes the user interface invisible. Works in Pause Menu, great for screenshots.
- `1`: ***END SONG***: Immediately ends the song and moves to Results Screen on Freeplay, or next song on Story Mode.
- `2`: ***GAIN HEALTH***: Debug function, add 10% to the player's health.
- `3`: ***LOSE HEALTH***: Debug function, subtract 5% to the player's health.
- `9`: NEATO!
- `PAGEUP` (MacOS: `Fn-Up`): ***FORWARDS TIME TRAVEL****: Move forward by 2 sections. Hold SHIFT to move forward by 20 sections instead.
- `PAGEDOWN` (MacOS: `Fn-Down`): ***BACKWARDS TIME TRAVEL****: Move backward by 2 sections. Hold SHIFT to move backward by 20 sections instead.
`U` (Play) - Open Stage Editor State ## **Freeplay State**
`H` (Play) - Show/Hide HUD - `F` (Freeplay Menu) - Move to Favorites
`1` (Play) - End Song - `Q` (Freeplay Menu) - Back one category
`2` (Play) - Add 10% Health - `E` (Freeplay Menu) - Forward one category
`3` (Play) - Subtract 5% Health
`7` (Play) - (NOT WORKING) Open Chart Editor
`8` (Play) - Open Animation Editor
`9` (Play) - (Easter Egg) Classic Health Icon
`PGUP`/`Fn+Up` (Play) - Skip Forward In Time
`PGDN`/`Fn+Down` (Play) - 🦃 That's right, we're going to go BACK IN TIME
`F` (Freeplay Menu) - Move to Favorites ## **Title State**
`P` (Freeplay Menu) - Switch to Pico (probably doesn't work) - `Y` - WOAH
`T` (Freeplay Menu) - Start typing in search bar
`Q` (Freeplay Menu) - Back one letter
`E` (Freeplay Menu) - Forward one letter
`Arrows` (Stage Editor) - Move Prop ## **Main Menu**
`Ctrl-Z` (Stage Editor) - Undo - `~`: ***DEBUG****: Opens a menu to access the Chart Editor and other work-in-progress editors. Rebindable in the options menu.
`Y` (Stage Editor) - Leave Stage Editor - `CTRL-ALT-SHIFT-W`: ***ALL ACCESS***: Unlocks all songs in Freeplay. Only available on debug builds.
`H` (Pause Menu) - Hide the Pause Menu UI (good for screenshots!)

View file

@ -49,7 +49,7 @@
"name": "funkin.vis", "name": "funkin.vis",
"type": "git", "type": "git",
"dir": null, "dir": null,
"ref": "98c9db09f0bbfedfe67a84538a5814aaef80bdea", "ref": "2aa654b974507ab51ab1724d2d97e75726fd7d78",
"url": "https://github.com/FunkinCrew/funkVis" "url": "https://github.com/FunkinCrew/funkVis"
}, },
{ {
@ -80,7 +80,7 @@
"name": "hxCodec", "name": "hxCodec",
"type": "git", "type": "git",
"dir": null, "dir": null,
"ref": "c0c7f2680cc190c932a549c2e2fdd9b0ba2bd10e", "ref": "61b98a7a353b7f529a8fec84ed9afc919a2dffdd",
"url": "https://github.com/FunkinCrew/hxCodec" "url": "https://github.com/FunkinCrew/hxCodec"
}, },
{ {

View file

@ -430,7 +430,7 @@ class Conductor
else if (currentTimeChange != null && this.songPosition > 0.0) else if (currentTimeChange != null && this.songPosition > 0.0)
{ {
// roundDecimal prevents representing 8 as 7.9999999 // roundDecimal prevents representing 8 as 7.9999999
this.currentStepTime = FlxMath.roundDecimal((currentTimeChange.beatTime * 4) + (this.songPosition - currentTimeChange.timeStamp) / stepLengthMs, 6); this.currentStepTime = FlxMath.roundDecimal((currentTimeChange.beatTime * Constants.STEPS_PER_BEAT) + (this.songPosition - currentTimeChange.timeStamp) / stepLengthMs, 6);
this.currentBeatTime = currentStepTime / Constants.STEPS_PER_BEAT; this.currentBeatTime = currentStepTime / Constants.STEPS_PER_BEAT;
this.currentMeasureTime = currentStepTime / stepsPerMeasure; this.currentMeasureTime = currentStepTime / stepsPerMeasure;
this.currentStep = Math.floor(currentStepTime); this.currentStep = Math.floor(currentStepTime);
@ -564,7 +564,7 @@ class Conductor
if (ms >= timeChange.timeStamp) if (ms >= timeChange.timeStamp)
{ {
lastTimeChange = timeChange; lastTimeChange = timeChange;
resultStep = lastTimeChange.beatTime * 4; resultStep = lastTimeChange.beatTime * Constants.STEPS_PER_BEAT;
} }
else else
{ {
@ -600,7 +600,7 @@ class Conductor
var lastTimeChange:SongTimeChange = timeChanges[0]; var lastTimeChange:SongTimeChange = timeChanges[0];
for (timeChange in timeChanges) for (timeChange in timeChanges)
{ {
if (stepTime >= timeChange.beatTime * 4) if (stepTime >= timeChange.beatTime * Constants.STEPS_PER_BEAT)
{ {
lastTimeChange = timeChange; lastTimeChange = timeChange;
resultMs = lastTimeChange.timeStamp; resultMs = lastTimeChange.timeStamp;
@ -613,7 +613,7 @@ class Conductor
} }
var lastStepLengthMs:Float = ((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) / timeSignatureNumerator; var lastStepLengthMs:Float = ((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) / timeSignatureNumerator;
resultMs += (stepTime - lastTimeChange.beatTime * 4) * lastStepLengthMs; resultMs += (stepTime - lastTimeChange.beatTime * Constants.STEPS_PER_BEAT) * lastStepLengthMs;
return resultMs; return resultMs;
} }

View file

@ -227,14 +227,14 @@ class InitState extends FlxState
tallies: tallies:
{ {
sick: 130, sick: 130,
good: 69, good: 25,
bad: 69, bad: 69,
shit: 69, shit: 69,
missed: 69, missed: 69,
combo: 69, combo: 69,
maxCombo: 69, maxCombo: 69,
totalNotesHit: 140, totalNotesHit: 140,
totalNotes: 2000, totalNotes: 200 // 0,
} }
}, },
})); }));

View file

@ -40,8 +40,8 @@ class StrokeShader extends FlxShader
void main() void main()
{ {
vec4 sample = flixel_texture2D(bitmap, openfl_TextureCoordv); vec4 gay = flixel_texture2D(bitmap, openfl_TextureCoordv);
if (sample.a == 0.) { if (gay.a == 0.) {
float w = size.x / openfl_TextureSize.x; float w = size.x / openfl_TextureSize.x;
float h = size.y / openfl_TextureSize.y; float h = size.y / openfl_TextureSize.y;
@ -49,9 +49,9 @@ class StrokeShader extends FlxShader
|| flixel_texture2D(bitmap, vec2(openfl_TextureCoordv.x - w, openfl_TextureCoordv.y)).a != 0. || flixel_texture2D(bitmap, vec2(openfl_TextureCoordv.x - w, openfl_TextureCoordv.y)).a != 0.
|| flixel_texture2D(bitmap, vec2(openfl_TextureCoordv.x, openfl_TextureCoordv.y + h)).a != 0. || flixel_texture2D(bitmap, vec2(openfl_TextureCoordv.x, openfl_TextureCoordv.y + h)).a != 0.
|| flixel_texture2D(bitmap, vec2(openfl_TextureCoordv.x, openfl_TextureCoordv.y - h)).a != 0.) || flixel_texture2D(bitmap, vec2(openfl_TextureCoordv.x, openfl_TextureCoordv.y - h)).a != 0.)
sample = color; gay = color;
} }
gl_FragColor = sample; gl_FragColor = gay;
} }
') ')
public function new(color:FlxColor = 0xFFFFFFFF, width:Float = 1, height:Float = 1) public function new(color:FlxColor = 0xFFFFFFFF, width:Float = 1, height:Float = 1)

View file

@ -527,6 +527,14 @@ class Controls extends FlxActionSet
action.inputs[i].inputID = toAdd; action.inputs[i].inputID = toAdd;
} }
hasReplaced = true; hasReplaced = true;
} else if (input.device == KEYBOARD && input.inputID == toAdd) {
// This key is already bound!
if (hasReplaced) {
// Remove the duplicate keybind, don't replace.
action.inputs.remove(input);
} else {
hasReplaced = true;
}
} }
} }
@ -707,7 +715,7 @@ 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.F]; case Control.FULLSCREEN: return [FlxKey.F11]; // We use F for other things LOL.
} }
case Duo(true): case Duo(true):
@ -989,6 +997,7 @@ class Controls extends FlxActionSet
for (control in Control.createAll()) for (control in Control.createAll())
{ {
var inputs:Array<Int> = Reflect.field(data, control.getName()); var inputs:Array<Int> = Reflect.field(data, control.getName());
inputs = inputs.unique();
if (inputs != null) if (inputs != null)
{ {
if (inputs.length == 0) { if (inputs.length == 0) {
@ -1038,7 +1047,11 @@ class Controls extends FlxActionSet
var inputs = getInputsFor(control, device); var inputs = getInputsFor(control, device);
isEmpty = isEmpty && inputs.length == 0; isEmpty = isEmpty && inputs.length == 0;
if (inputs.length == 0) inputs = [FlxKey.NONE]; if (inputs.length == 0) {
inputs = [FlxKey.NONE];
} else {
inputs = inputs.unique();
}
Reflect.setField(data, control.getName(), inputs); Reflect.setField(data, control.getName(), inputs);
} }

View file

@ -83,6 +83,8 @@ class GameOverSubState extends MusicBeatSubState
var isChartingMode:Bool = false; var isChartingMode:Bool = false;
var mustNotExit:Bool = false;
var transparent:Bool; var transparent:Bool;
static final CAMERA_ZOOM_DURATION:Float = 0.5; static final CAMERA_ZOOM_DURATION:Float = 0.5;
@ -160,6 +162,8 @@ class GameOverSubState extends MusicBeatSubState
@:nullSafety(Off) @:nullSafety(Off)
function setCameraTarget():Void function setCameraTarget():Void
{ {
if (PlayState.instance.isMinimalMode || boyfriend == null) return;
// Assign a camera follow point to the boyfriend's position. // Assign a camera follow point to the boyfriend's position.
cameraFollowPoint = new FlxObject(PlayState.instance.cameraFollowPoint.x, PlayState.instance.cameraFollowPoint.y, 1, 1); cameraFollowPoint = new FlxObject(PlayState.instance.cameraFollowPoint.x, PlayState.instance.cameraFollowPoint.y, 1, 1);
cameraFollowPoint.x = boyfriend.getGraphicMidpoint().x; cameraFollowPoint.x = boyfriend.getGraphicMidpoint().x;
@ -240,7 +244,7 @@ class GameOverSubState extends MusicBeatSubState
} }
// 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) if (controls.BACK && !mustNotExit)
{ {
blueballed = false; blueballed = false;
PlayState.instance.deathCounter = 0; PlayState.instance.deathCounter = 0;
@ -252,6 +256,7 @@ class GameOverSubState extends MusicBeatSubState
this.close(); this.close();
if (FlxG.sound.music != null) FlxG.sound.music.pause(); // Don't reset song position! if (FlxG.sound.music != null) FlxG.sound.music.pause(); // Don't reset song position!
PlayState.instance.close(); // This only works because PlayState is a substate! PlayState.instance.close(); // This only works because PlayState is a substate!
return;
} }
else if (PlayStatePlaylist.isStoryMode) else if (PlayStatePlaylist.isStoryMode)
{ {

View file

@ -826,6 +826,8 @@ class PlayState extends MusicBeatSubState
resetCamera(); resetCamera();
var fromDeathState = isPlayerDying;
persistentUpdate = true; persistentUpdate = true;
persistentDraw = true; persistentDraw = true;
@ -863,8 +865,11 @@ class PlayState extends MusicBeatSubState
if (currentStage != null) currentStage.resetStage(); if (currentStage != null) currentStage.resetStage();
if (!fromDeathState)
{
playerStrumline.vwooshNotes(); playerStrumline.vwooshNotes();
opponentStrumline.vwooshNotes(); opponentStrumline.vwooshNotes();
}
playerStrumline.clean(); playerStrumline.clean();
opponentStrumline.clean(); opponentStrumline.clean();
@ -1075,6 +1080,25 @@ class PlayState extends MusicBeatSubState
function moveToGameOver():Void function moveToGameOver():Void
{ {
// Reset and update a bunch of values in advance for the transition back from the game over substate.
playerStrumline.clean();
opponentStrumline.clean();
songScore = 0;
updateScoreText();
health = Constants.HEALTH_STARTING;
healthLerp = health;
healthBar.value = healthLerp;
if (!isMinimalMode)
{
iconP1.updatePosition();
iconP2.updatePosition();
}
// Transition to the game over substate.
var gameOverSubState = new GameOverSubState( var gameOverSubState = new GameOverSubState(
{ {
isChartingMode: isChartingMode, isChartingMode: isChartingMode,
@ -2548,6 +2572,13 @@ 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;
@ -2556,6 +2587,7 @@ class PlayState extends MusicBeatSubState
targetSongId: currentSong.id, targetSongId: currentSong.id,
})); }));
} }
}
#end #end
#if (debug || FORCE_DEBUG_VERSION) #if (debug || FORCE_DEBUG_VERSION)

View file

@ -37,6 +37,7 @@ class ResultState extends MusicBeatSubState
final rank:ResultRank; final rank:ResultRank;
final songName:FlxBitmapText; final songName:FlxBitmapText;
final difficulty:FlxSprite; final difficulty:FlxSprite;
final clearPercentSmall:ClearPercentCounter;
final maskShaderSongName:LeftMaskShader = new LeftMaskShader(); final maskShaderSongName:LeftMaskShader = new LeftMaskShader();
final maskShaderDifficulty:LeftMaskShader = new LeftMaskShader(); final maskShaderDifficulty:LeftMaskShader = new LeftMaskShader();
@ -52,6 +53,7 @@ class ResultState extends MusicBeatSubState
var bfPerfect:Null<FlxAtlasSprite> = null; var bfPerfect:Null<FlxAtlasSprite> = null;
var bfExcellent:Null<FlxAtlasSprite> = null; var bfExcellent:Null<FlxAtlasSprite> = null;
var bfGreat:Null<FlxAtlasSprite> = null;
var bfGood:Null<FlxSprite> = null; var bfGood:Null<FlxSprite> = null;
var gfGood:Null<FlxSprite> = null; var gfGood:Null<FlxSprite> = null;
var bfShit:Null<FlxAtlasSprite> = null; var bfShit:Null<FlxAtlasSprite> = null;
@ -78,6 +80,10 @@ class ResultState extends MusicBeatSubState
difficulty = new FlxSprite(555); difficulty = new FlxSprite(555);
difficulty.zIndex = 1000; difficulty.zIndex = 1000;
clearPercentSmall = new ClearPercentCounter(FlxG.width / 2 + 300, FlxG.height / 2 - 100, 100, true);
clearPercentSmall.zIndex = 1000;
clearPercentSmall.visible = false;
bgFlash = FlxGradient.createGradientFlxSprite(FlxG.width, FlxG.height, [0xFFFFEB69, 0xFFFFE66A], 90); bgFlash = FlxGradient.createGradientFlxSprite(FlxG.width, FlxG.height, [0xFFFFEB69, 0xFFFFE66A], 90);
resultsAnim = FunkinSprite.createSparrow(-200, -10, "resultScreen/results"); resultsAnim = FunkinSprite.createSparrow(-200, -10, "resultScreen/results");
@ -146,7 +152,20 @@ class ResultState extends MusicBeatSubState
} }
}); });
case GOOD | GREAT: case GREAT:
bfGreat = new FlxAtlasSprite(640, 200, Paths.animateAtlas("resultScreen/results-bf/resultsGREAT", "shared"));
bfGreat.visible = false;
bfGreat.zIndex = 500;
add(bfGreat);
bfGreat.onAnimationFinish.add((animName) -> {
if (bfGreat != null)
{
bfGreat.playAnimation('Loop Start');
}
});
case GOOD:
gfGood = FunkinSprite.createSparrow(625, 325, 'resultScreen/results-bf/resultsGOOD/resultGirlfriendGOOD'); gfGood = FunkinSprite.createSparrow(625, 325, 'resultScreen/results-bf/resultsGOOD/resultGirlfriendGOOD');
gfGood.animation.addByPrefix("clap", "Girlfriend Good Anim", 24, false); gfGood.animation.addByPrefix("clap", "Girlfriend Good Anim", 24, false);
gfGood.visible = false; gfGood.visible = false;
@ -194,7 +213,7 @@ class ResultState extends MusicBeatSubState
speedOfTween.x = -1.0 * Math.cos(angleRad); speedOfTween.x = -1.0 * Math.cos(angleRad);
speedOfTween.y = -1.0 * Math.sin(angleRad); speedOfTween.y = -1.0 * Math.sin(angleRad);
timerThenSongName(1.0); timerThenSongName(1.0, false);
songName.shader = maskShaderSongName; songName.shader = maskShaderSongName;
difficulty.shader = maskShaderDifficulty; difficulty.shader = maskShaderDifficulty;
@ -319,13 +338,15 @@ class ResultState extends MusicBeatSubState
function startRankTallySequence():Void function startRankTallySequence():Void
{ {
clearPercentTarget = Math.floor((params.scoreData.tallies.sick + params.scoreData.tallies.good) / params.scoreData.tallies.totalNotes * 100); var clearPercentFloat = (params.scoreData.tallies.sick + params.scoreData.tallies.good) / params.scoreData.tallies.totalNotes * 100;
clearPercentTarget = 100; clearPercentTarget = Math.floor(clearPercentFloat);
// Prevent off-by-one errors.
clearPercentLerp = Std.int(Math.max(0, clearPercentTarget - 36)); clearPercentLerp = Std.int(Math.max(0, clearPercentTarget - 36));
var clearPercentCounter:ClearPercentCounter = new ClearPercentCounter(FlxG.width / 2 + 300, FlxG.height / 2 - 100, clearPercentTarget); trace('Clear percent target: ' + clearPercentFloat + ', round: ' + clearPercentTarget);
clearPercentCounter.curNumber = clearPercentLerp;
var clearPercentCounter:ClearPercentCounter = new ClearPercentCounter(FlxG.width / 2 + 300, FlxG.height / 2 - 100, clearPercentLerp);
FlxTween.tween(clearPercentCounter, {curNumber: clearPercentTarget}, 1.5, FlxTween.tween(clearPercentCounter, {curNumber: clearPercentTarget}, 1.5,
{ {
ease: FlxEase.quartOut, ease: FlxEase.quartOut,
@ -345,10 +366,25 @@ class ResultState extends MusicBeatSubState
bgFlash.visible = true; bgFlash.visible = true;
FlxTween.tween(bgFlash, {alpha: 0}, 0.4); FlxTween.tween(bgFlash, {alpha: 0}, 0.4);
// Just to be sure that the lerp didn't mess things up.
clearPercentCounter.curNumber = clearPercentTarget;
clearPercentCounter.flash(true);
new FlxTimer().start(0.4, _ -> {
clearPercentCounter.flash(false);
});
displayRankText(); displayRankText();
new FlxTimer().start(2.0, _ -> { new FlxTimer().start(2.0, _ -> {
// remove(clearPercentCounter); FlxTween.tween(clearPercentCounter, {alpha: 0}, 0.5,
{
startDelay: 0.5,
ease: FlxEase.quartOut,
onComplete: _ -> {
remove(clearPercentCounter);
}
});
afterRankTallySequence(); afterRankTallySequence();
}); });
@ -406,6 +442,8 @@ class ResultState extends MusicBeatSubState
function afterRankTallySequence():Void function afterRankTallySequence():Void
{ {
showSmallClearPercent();
FunkinSound.playMusic(rank.getMusicPath(), FunkinSound.playMusic(rank.getMusicPath(),
{ {
startingVolume: 1.0, startingVolume: 1.0,
@ -452,6 +490,17 @@ class ResultState extends MusicBeatSubState
bfExcellent.playAnimation('Intro'); bfExcellent.playAnimation('Intro');
} }
case GREAT:
if (bfGreat == null)
{
trace("Could not build GREAT animation!");
}
else
{
bfGreat.visible = true;
bfGreat.playAnimation('Intro');
}
case SHIT: case SHIT:
if (bfShit == null) if (bfShit == null)
{ {
@ -463,7 +512,7 @@ class ResultState extends MusicBeatSubState
bfShit.playAnimation('Intro'); bfShit.playAnimation('Intro');
} }
case GREAT | GOOD: case GOOD:
if (bfGood == null) if (bfGood == null)
{ {
trace("Could not build GOOD animation!"); trace("Could not build GOOD animation!");
@ -490,7 +539,7 @@ class ResultState extends MusicBeatSubState
} }
} }
function timerThenSongName(timerLength:Float = 3.0):Void function timerThenSongName(timerLength:Float = 3.0, autoScroll:Bool = true):Void
{ {
movingSongStuff = false; movingSongStuff = false;
@ -501,10 +550,17 @@ class ResultState extends MusicBeatSubState
difficulty.y = -difficulty.height; difficulty.y = -difficulty.height;
FlxTween.tween(difficulty, {y: diffYTween}, 0.5, {ease: FlxEase.expoOut, startDelay: 0.8}); FlxTween.tween(difficulty, {y: diffYTween}, 0.5, {ease: FlxEase.expoOut, startDelay: 0.8});
if (clearPercentSmall != null)
{
clearPercentSmall.x = (difficulty.x + difficulty.width) + 60;
clearPercentSmall.y = -clearPercentSmall.height;
FlxTween.tween(clearPercentSmall, {y: 122 - 5}, 0.5, {ease: FlxEase.expoOut, startDelay: 0.8});
}
songName.y = -songName.height; songName.y = -songName.height;
var fuckedupnumber = (10) * (songName.text.length / 15); var fuckedupnumber = (10) * (songName.text.length / 15);
FlxTween.tween(songName, {y: diffYTween - 35 - fuckedupnumber}, 0.5, {ease: FlxEase.expoOut, startDelay: 0.9}); FlxTween.tween(songName, {y: diffYTween - 25 - fuckedupnumber}, 0.5, {ease: FlxEase.expoOut, startDelay: 0.9});
songName.x = (difficulty.x + difficulty.width) + 20; songName.x = clearPercentSmall.x + clearPercentSmall.width - 30;
new FlxTimer().start(timerLength, _ -> { new FlxTimer().start(timerLength, _ -> {
var tempSpeed = FlxPoint.get(speedOfTween.x, speedOfTween.y); var tempSpeed = FlxPoint.get(speedOfTween.x, speedOfTween.y);
@ -512,10 +568,29 @@ class ResultState extends MusicBeatSubState
speedOfTween.set(0, 0); speedOfTween.set(0, 0);
FlxTween.tween(speedOfTween, {x: tempSpeed.x, y: tempSpeed.y}, 0.7, {ease: FlxEase.quadIn}); FlxTween.tween(speedOfTween, {x: tempSpeed.x, y: tempSpeed.y}, 0.7, {ease: FlxEase.quadIn});
movingSongStuff = true; movingSongStuff = (autoScroll);
}); });
} }
function showSmallClearPercent():Void
{
if (clearPercentSmall != null)
{
add(clearPercentSmall);
clearPercentSmall.visible = true;
clearPercentSmall.flash(true);
new FlxTimer().start(0.4, _ -> {
clearPercentSmall.flash(false);
});
clearPercentSmall.curNumber = clearPercentTarget;
clearPercentSmall.zIndex = 1000;
refresh();
}
movingSongStuff = true;
}
var movingSongStuff:Bool = false; var movingSongStuff:Bool = false;
var speedOfTween:FlxPoint = FlxPoint.get(-1, 1); var speedOfTween:FlxPoint = FlxPoint.get(-1, 1);
@ -523,7 +598,8 @@ class ResultState extends MusicBeatSubState
{ {
super.draw(); super.draw();
songName.clipRect = FlxRect.get(Math.max(0, 540 - songName.x), 0, FlxG.width, songName.height); songName.clipRect = FlxRect.get(Math.max(0, 520 - songName.x), 0, FlxG.width, songName.height);
// PROBABLY SHOULD FIX MEMORY FREE OR WHATEVER THE PUT() FUNCTION DOES !!!! FEELS LIKE IT STUTTERS!!! // PROBABLY SHOULD FIX MEMORY FREE OR WHATEVER THE PUT() FUNCTION DOES !!!! FEELS LIKE IT STUTTERS!!!
// if (songName != null && songName.frame != null) // if (songName != null && songName.frame != null)
@ -539,8 +615,10 @@ class ResultState extends MusicBeatSubState
{ {
songName.x += speedOfTween.x; songName.x += speedOfTween.x;
difficulty.x += speedOfTween.x; difficulty.x += speedOfTween.x;
clearPercentSmall.x += speedOfTween.x;
songName.y += speedOfTween.y; songName.y += speedOfTween.y;
difficulty.y += speedOfTween.y; difficulty.y += speedOfTween.y;
clearPercentSmall.y += speedOfTween.y;
if (songName.x + songName.width < 100) if (songName.x + songName.width < 100)
{ {

View file

@ -420,7 +420,8 @@ class BaseCharacter extends Bopper
{ {
if (isSinging()) return; if (isSinging()) return;
if (['hey', 'cheer'].contains(getCurrentAnimation()) && !isAnimationFinished()) return; var currentAnimation:String = getCurrentAnimation();
if ((currentAnimation == 'hey' || currentAnimation == 'cheer') && !isAnimationFinished()) return;
} }
// Prevent dancing while another animation is playing. // Prevent dancing while another animation is playing.
@ -441,19 +442,15 @@ class BaseCharacter extends Bopper
switch (player) switch (player)
{ {
case 1: case 1:
return [ return PlayerSettings.player1.controls.NOTE_LEFT_P
PlayerSettings.player1.controls.NOTE_LEFT_P, || PlayerSettings.player1.controls.NOTE_DOWN_P
PlayerSettings.player1.controls.NOTE_DOWN_P, || PlayerSettings.player1.controls.NOTE_UP_P
PlayerSettings.player1.controls.NOTE_UP_P, || PlayerSettings.player1.controls.NOTE_RIGHT_P;
PlayerSettings.player1.controls.NOTE_RIGHT_P,
].contains(true);
case 2: case 2:
return [ return PlayerSettings.player2.controls.NOTE_LEFT_P
PlayerSettings.player2.controls.NOTE_LEFT_P, || PlayerSettings.player2.controls.NOTE_DOWN_P
PlayerSettings.player2.controls.NOTE_DOWN_P, || PlayerSettings.player2.controls.NOTE_UP_P
PlayerSettings.player2.controls.NOTE_UP_P, || PlayerSettings.player2.controls.NOTE_RIGHT_P;
PlayerSettings.player2.controls.NOTE_RIGHT_P,
].contains(true);
} }
return false; return false;
} }
@ -469,19 +466,15 @@ class BaseCharacter extends Bopper
switch (player) switch (player)
{ {
case 1: case 1:
return [ return PlayerSettings.player1.controls.NOTE_LEFT
PlayerSettings.player1.controls.NOTE_LEFT, || PlayerSettings.player1.controls.NOTE_DOWN
PlayerSettings.player1.controls.NOTE_DOWN, || PlayerSettings.player1.controls.NOTE_UP
PlayerSettings.player1.controls.NOTE_UP, || PlayerSettings.player1.controls.NOTE_RIGHT;
PlayerSettings.player1.controls.NOTE_RIGHT,
].contains(true);
case 2: case 2:
return [ return PlayerSettings.player2.controls.NOTE_LEFT
PlayerSettings.player2.controls.NOTE_LEFT, || PlayerSettings.player2.controls.NOTE_DOWN
PlayerSettings.player2.controls.NOTE_DOWN, || PlayerSettings.player2.controls.NOTE_UP
PlayerSettings.player2.controls.NOTE_UP, || PlayerSettings.player2.controls.NOTE_RIGHT;
PlayerSettings.player2.controls.NOTE_RIGHT,
].contains(true);
} }
return false; return false;
} }

View file

@ -1,6 +1,7 @@
package funkin.play.components; package funkin.play.components;
import funkin.graphics.FunkinSprite; import funkin.graphics.FunkinSprite;
import funkin.graphics.shaders.PureColor;
import flixel.FlxSprite; import flixel.FlxSprite;
import flixel.group.FlxGroup.FlxTypedGroup; import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup; import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
@ -9,25 +10,54 @@ import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween; import flixel.tweens.FlxTween;
import flixel.text.FlxText.FlxTextAlign; import flixel.text.FlxText.FlxTextAlign;
import funkin.util.MathUtil; import funkin.util.MathUtil;
import flixel.util.FlxColor;
/** /**
* Numerical counters used to display the clear percent. * Numerical counters used to display the clear percent.
*/ */
class ClearPercentCounter extends FlxTypedSpriteGroup<FlxSprite> class ClearPercentCounter extends FlxTypedSpriteGroup<FlxSprite>
{ {
public var curNumber:Int = 0; public var curNumber(default, set):Int = 0;
public var neededNumber:Int = 0;
public function new(x:Float, y:Float, neededNumber:Int = 0) var numberChanged:Bool = false;
function set_curNumber(val:Int):Int
{
numberChanged = true;
return curNumber = val;
}
var small:Bool = false;
var flashShader:PureColor;
public function new(x:Float, y:Float, startingNumber:Int = 0, small:Bool = false)
{ {
super(x, y); super(x, y);
this.neededNumber = neededNumber; flashShader = new PureColor(FlxColor.WHITE);
flashShader.colorSet = true;
var clearPercentText:FunkinSprite = FunkinSprite.create(0, 0, 'resultScreen/clearPercent/clearPercentText'); curNumber = startingNumber;
this.small = small;
var clearPercentText:FunkinSprite = FunkinSprite.create(0, 0, 'resultScreen/clearPercent/clearPercentText${small ? 'Small' : ''}');
clearPercentText.x = small ? 40 : 0;
add(clearPercentText); add(clearPercentText);
if (curNumber == neededNumber) drawNumbers(); drawNumbers();
}
/**
* Make the counter flash turn white or stop being all white.
* @param enabled Whether the counter should be white.
*/
public function flash(enabled:Bool):Void
{
for (member in members)
{
member.shader = enabled ? flashShader : null;
}
} }
var tmr:Float = 0; var tmr:Float = 0;
@ -36,7 +66,7 @@ class ClearPercentCounter extends FlxTypedSpriteGroup<FlxSprite>
{ {
super.update(elapsed); super.update(elapsed);
if (curNumber < neededNumber) drawNumbers(); if (numberChanged) drawNumbers();
} }
function drawNumbers() function drawNumbers()
@ -44,8 +74,6 @@ class ClearPercentCounter extends FlxTypedSpriteGroup<FlxSprite>
var seperatedScore:Array<Int> = []; var seperatedScore:Array<Int> = [];
var tempCombo:Int = Math.round(curNumber); var tempCombo:Int = Math.round(curNumber);
var fullNumberDigits:Int = Std.int(Math.max(1, Math.ceil(MathUtil.logBase(10, neededNumber))));
while (tempCombo != 0) while (tempCombo != 0)
{ {
seperatedScore.push(tempCombo % 10); seperatedScore.push(tempCombo % 10);
@ -59,19 +87,32 @@ class ClearPercentCounter extends FlxTypedSpriteGroup<FlxSprite>
for (ind => num in seperatedScore) for (ind => num in seperatedScore)
{ {
var digitIndex = ind + 1; var digitIndex = ind + 1;
// If there's only one digit, move it to the right
// If there's three digits, move them all to the left
var digitOffset = (seperatedScore.length == 1) ? 1 : (seperatedScore.length == 3) ? -1 : 0;
var digitSize = small ? 32 : 72;
var digitHeightOffset = small ? -4 : 0;
var xPos = (digitIndex - 1 + digitOffset) * (digitSize * this.scale.x);
xPos += small ? -24 : 0;
var yPos = (digitIndex - 1 + digitOffset) * (digitHeightOffset * this.scale.y);
yPos += small ? 0 : 72;
if (digitIndex >= members.length) if (digitIndex >= members.length)
{ {
var xPos = (digitIndex - 1) * (72 * this.scale.x); // Three digits = LLR because the 1 and 0 won't be the same anyway.
var yPos = 72; var variant:Bool = (seperatedScore.length == 3) ? (digitIndex >= 2) : (digitIndex >= 1);
// Three digits = LRL so two different numbers aren't adjacent to each other. // var variant:Bool = (seperatedScore.length % 2 != 0) ? (digitIndex % 2 == 0) : (digitIndex % 2 == 1);
var variant:Bool = (fullNumberDigits % 2 != 0) ? (digitIndex % 2 == 0) : (digitIndex % 2 == 1); var numb:ClearPercentNumber = new ClearPercentNumber(xPos, yPos, num, variant, this.small);
var numb:ClearPercentNumber = new ClearPercentNumber(xPos, yPos, num);
numb.scale.set(this.scale.x, this.scale.y); numb.scale.set(this.scale.x, this.scale.y);
add(numb); add(numb);
} }
else else
{ {
members[digitIndex].animation.play(Std.string(num)); members[digitIndex].animation.play(Std.string(num));
// Reset the position of the number
members[digitIndex].x = xPos + this.x;
members[digitIndex].y = yPos + this.y;
} }
} }
} }
@ -79,11 +120,11 @@ class ClearPercentCounter extends FlxTypedSpriteGroup<FlxSprite>
class ClearPercentNumber extends FlxSprite class ClearPercentNumber extends FlxSprite
{ {
public function new(x:Float, y:Float, digit:Int, variant:Bool = false) public function new(x:Float, y:Float, digit:Int, variant:Bool, small:Bool)
{ {
super(x, y); super(x, y);
frames = Paths.getSparrowAtlas('resultScreen/clearPercent/clearPercentNumber${variant ? 'Right' : 'Left'}'); frames = Paths.getSparrowAtlas('resultScreen/clearPercent/clearPercentNumber${small ? 'Small' : variant ? 'Right' : 'Left'}');
for (i in 0...10) for (i in 0...10)
{ {

View file

@ -31,25 +31,13 @@ class PlayAnimationSongEvent extends SongEvent
switch (targetName) switch (targetName)
{ {
case 'boyfriend': case 'boyfriend' | 'bf' | 'player':
trace('Playing animation $anim on boyfriend.'); trace('Playing animation $anim on boyfriend.');
target = PlayState.instance.currentStage.getBoyfriend(); target = PlayState.instance.currentStage.getBoyfriend();
case 'bf': case 'dad' | 'opponent':
trace('Playing animation $anim on boyfriend.');
target = PlayState.instance.currentStage.getBoyfriend();
case 'player':
trace('Playing animation $anim on boyfriend.');
target = PlayState.instance.currentStage.getBoyfriend();
case 'dad':
trace('Playing animation $anim on dad.'); trace('Playing animation $anim on dad.');
target = PlayState.instance.currentStage.getDad(); target = PlayState.instance.currentStage.getDad();
case 'opponent': case 'girlfriend' | 'gf':
trace('Playing animation $anim on dad.');
target = PlayState.instance.currentStage.getDad();
case 'girlfriend':
trace('Playing animation $anim on girlfriend.');
target = PlayState.instance.currentStage.getGirlfriend();
case 'gf':
trace('Playing animation $anim on girlfriend.'); trace('Playing animation $anim on girlfriend.');
target = PlayState.instance.currentStage.getGirlfriend(); target = PlayState.instance.currentStage.getGirlfriend();
default: default:

View file

@ -576,6 +576,8 @@ class Strumline extends FlxSpriteGroup
note.holdNoteSprite.hitNote = true; note.holdNoteSprite.hitNote = true;
note.holdNoteSprite.missedNote = false; note.holdNoteSprite.missedNote = false;
note.holdNoteSprite.alpha = 1.0; note.holdNoteSprite.alpha = 1.0;
note.holdNoteSprite.sustainLength = (note.holdNoteSprite.strumTime + note.holdNoteSprite.fullSustainLength) - conductorInUse.songPosition;
} }
} }

View file

@ -14,8 +14,7 @@ import funkin.util.SerializerUtil;
@:nullSafety @:nullSafety
class Save class Save
{ {
// Version 2.0.2 adds attributes to `optionsChartEditor`, that should return default values if they are null. public static final SAVE_DATA_VERSION:thx.semver.Version = "2.0.4";
public static final SAVE_DATA_VERSION:thx.semver.Version = "2.0.3";
public static final SAVE_DATA_VERSION_RULE:thx.semver.VersionRule = "2.0.x"; public static final SAVE_DATA_VERSION_RULE:thx.semver.VersionRule = "2.0.x";
// We load this version's saves from a new save path, to maintain SOME level of backwards compatibility. // We load this version's saves from a new save path, to maintain SOME level of backwards compatibility.
@ -78,6 +77,9 @@ class Save
levels: [], levels: [],
songs: [], songs: [],
}, },
favoriteSongs: [],
options: options:
{ {
// Reasonable defaults. // Reasonable defaults.
@ -555,6 +557,35 @@ class Save
return false; return false;
} }
public function isSongFavorited(id:String):Bool
{
if (data.favoriteSongs == null)
{
data.favoriteSongs = [];
flush();
};
return data.favoriteSongs.contains(id);
}
public function favoriteSong(id:String):Void
{
if (!isSongFavorited(id))
{
data.favoriteSongs.push(id);
flush();
}
}
public function unfavoriteSong(id:String):Void
{
if (isSongFavorited(id))
{
data.favoriteSongs.remove(id);
flush();
}
}
public function getControls(playerId:Int, inputType:Device):Null<SaveControlsData> public function getControls(playerId:Int, inputType:Device):Null<SaveControlsData>
{ {
switch (inputType) switch (inputType)
@ -740,6 +771,12 @@ typedef RawSaveData =
*/ */
var options:SaveDataOptions; var options:SaveDataOptions;
/**
* The user's favorited songs in the Freeplay menu,
* as a list of song IDs.
*/
var favoriteSongs:Array<String>;
var mods:SaveDataMods; var mods:SaveDataMods;
/** /**

View file

@ -5,6 +5,9 @@ All notable changes to this project 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).
## [2.0.4] - 2024-05-21
### Added
- `favoriteSongs:Array<String>` to `Save`
## [2.0.3] - 2024-01-09 ## [2.0.3] - 2024-01-09
### Added ### Added

View file

@ -67,7 +67,7 @@ class ChartEditorCharacterIconSelectorMenu extends ChartEditorBaseMenu
var charGrid = new Grid(); var charGrid = new Grid();
charGrid.columns = 5; charGrid.columns = 5;
charGrid.width = 100; charGrid.width = this.width;
charSelectScroll.addComponent(charGrid); charSelectScroll.addComponent(charGrid);
var charIds:Array<String> = CharacterDataParser.listCharacterIds(); var charIds:Array<String> = CharacterDataParser.listCharacterIds();

View file

@ -699,8 +699,8 @@ class FreeplayState extends MusicBeatSubState
if (targetSong != null) if (targetSong != null)
{ {
var realShit:Int = curSelected; var realShit:Int = curSelected;
targetSong.isFav = !targetSong.isFav; var isFav = targetSong.toggleFavorite();
if (targetSong.isFav) if (isFav)
{ {
FlxTween.tween(grpCapsules.members[realShit], {angle: 360}, 0.4, FlxTween.tween(grpCapsules.members[realShit], {angle: 360}, 0.4,
{ {
@ -724,8 +724,8 @@ class FreeplayState extends MusicBeatSubState
} }
} }
lerpScore = MathUtil.coolLerp(lerpScore, intendedScore, 0.2); lerpScore = MathUtil.smoothLerp(lerpScore, intendedScore, elapsed, 0.5);
lerpCompletion = MathUtil.coolLerp(lerpCompletion, intendedCompletion, 0.9); lerpCompletion = MathUtil.smoothLerp(lerpCompletion, intendedCompletion, elapsed, 0.5);
if (Math.isNaN(lerpScore)) if (Math.isNaN(lerpScore))
{ {
@ -880,11 +880,24 @@ class FreeplayState extends MusicBeatSubState
spamTimer = 0; spamTimer = 0;
} }
#if !html5
if (FlxG.mouse.wheel != 0) if (FlxG.mouse.wheel != 0)
{ {
dj.resetAFKTimer(); dj.resetAFKTimer();
changeSelection(-Math.round(FlxG.mouse.wheel / 4)); changeSelection(-Math.round(FlxG.mouse.wheel));
} }
#else
if (FlxG.mouse.wheel < 0)
{
dj.resetAFKTimer();
changeSelection(-Math.round(FlxG.mouse.wheel / 8));
}
else if (FlxG.mouse.wheel > 0)
{
dj.resetAFKTimer();
changeSelection(-Math.round(FlxG.mouse.wheel / 8));
}
#end
if (controls.UI_LEFT_P && !FlxG.keys.pressed.CONTROL) if (controls.UI_LEFT_P && !FlxG.keys.pressed.CONTROL)
{ {
@ -901,6 +914,7 @@ class FreeplayState extends MusicBeatSubState
if (controls.BACK) if (controls.BACK)
{ {
busy = true;
FlxTween.globalManager.clear(); FlxTween.globalManager.clear();
FlxTimer.globalManager.clear(); FlxTimer.globalManager.clear();
dj.onIntroDone.removeAll(); dj.onIntroDone.removeAll();
@ -1398,11 +1412,32 @@ class FreeplaySongData
this.levelId = levelId; this.levelId = levelId;
this.songId = songId; this.songId = songId;
this.song = song; this.song = song;
this.isFav = Save.instance.isSongFavorited(songId);
if (displayedVariations != null) this.displayedVariations = displayedVariations; if (displayedVariations != null) this.displayedVariations = displayedVariations;
updateValues(displayedVariations); updateValues(displayedVariations);
} }
/**
* Toggle whether or not the song is favorited, then flush to save data.
* @return Whether or not the song is now favorited.
*/
public function toggleFavorite():Bool
{
isFav = !isFav;
if (isFav)
{
Save.instance.favoriteSong(this.songId);
}
else
{
Save.instance.unfavoriteSong(this.songId);
}
return isFav;
}
function updateValues(variations:Array<String>):Void function updateValues(variations:Array<String>):Void
{ {
this.songDifficulties = song.listDifficulties(variations, false, false); this.songDifficulties = song.listDifficulties(variations, false, false);

View file

@ -139,10 +139,6 @@ class MainMenuState extends MusicBeatState
resetCamStuff(); resetCamStuff();
subStateClosed.add(_ -> {
resetCamStuff();
});
subStateOpened.add(sub -> { subStateOpened.add(sub -> {
if (Type.getClass(sub) == FreeplayState) if (Type.getClass(sub) == FreeplayState)
{ {

View file

@ -89,7 +89,7 @@ class AttractState extends MusicBeatState
super.update(elapsed); super.update(elapsed);
// If the user presses any button, skip the video. // If the user presses any button, skip the video.
if (FlxG.keys.justPressed.ANY) if (FlxG.keys.justPressed.ANY && !controls.VOLUME_MUTE && !controls.VOLUME_UP && !controls.VOLUME_DOWN)
{ {
onAttractEnd(); onAttractEnd();
} }

View file

@ -283,6 +283,7 @@ class TitleState extends MusicBeatState
if (FlxG.keys.justPressed.Y) if (FlxG.keys.justPressed.Y)
{ {
FlxTween.cancelTweensOf(FlxG.stage.window, ['x', 'y']);
FlxTween.tween(FlxG.stage.window, {x: FlxG.stage.window.x + 300}, 1.4, {ease: FlxEase.quadInOut, type: PINGPONG, startDelay: 0.35}); FlxTween.tween(FlxG.stage.window, {x: FlxG.stage.window.x + 300}, 1.4, {ease: FlxEase.quadInOut, type: PINGPONG, startDelay: 0.35});
FlxTween.tween(FlxG.stage.window, {y: FlxG.stage.window.y + 100}, 0.7, {ease: FlxEase.quadInOut, type: PINGPONG}); FlxTween.tween(FlxG.stage.window, {y: FlxG.stage.window.y + 100}, 0.7, {ease: FlxEase.quadInOut, type: PINGPONG});
} }

View file

@ -162,7 +162,9 @@ class LoadingState extends MusicBeatSubState
{ {
targetShit = FlxMath.remapToRange(callbacks.numRemaining / callbacks.length, 1, 0, 0, 1); targetShit = FlxMath.remapToRange(callbacks.numRemaining / callbacks.length, 1, 0, 0, 1);
loadBar.scale.x = FlxMath.lerp(loadBar.scale.x, targetShit, 0.50); var lerpWidth:Int = Std.int(FlxMath.lerp(loadBar.width, FlxG.width * targetShit, 0.2));
loadBar.setGraphicSize(lerpWidth, loadBar.height);
loadBar.updateHitbox();
FlxG.watch.addQuick('percentage?', callbacks.numRemaining / callbacks.length); FlxG.watch.addQuick('percentage?', callbacks.numRemaining / callbacks.length);
} }

View file

@ -141,7 +141,9 @@ class CrashHandler
fullContents += '\n'; fullContents += '\n';
fullContents += 'Flixel Current State: ${Type.getClassName(Type.getClass(FlxG.state))}\n'; var currentState = FlxG.state != null ? Type.getClassName(Type.getClass(FlxG.state)) : 'No state loaded';
fullContents += 'Flixel Current State: ${currentState}\n';
fullContents += '\n'; fullContents += '\n';