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

Merge branch 'rewrite/master' into feature/freeplay-shit+santa-cutscene

This commit is contained in:
EliteMasterEric 2024-05-29 23:56:51 -04:00
commit ee70e85dec
63 changed files with 1277 additions and 659 deletions

9
.vscode/launch.json vendored
View file

@ -3,10 +3,17 @@
"configurations": [ "configurations": [
{ {
// Launch in native/CPP on Windows/OSX/Linux // Launch in native/CPP on Windows/OSX/Linux
"name": "Lime", "name": "Lime Build+Debug",
"type": "lime", "type": "lime",
"request": "launch" "request": "launch"
}, },
{
// Launch in native/CPP on Windows/OSX/Linux
"name": "Lime Debug (No Build)",
"type": "lime",
"request": "launch",
"preLaunchTask": null
},
{ {
// Launch in browser // Launch in browser
"name": "HTML5 Debug", "name": "HTML5 Debug",

View file

@ -155,6 +155,11 @@
"target": "hl", "target": "hl",
"args": ["-debug", "-DDIALOGUE"] "args": ["-debug", "-DDIALOGUE"]
}, },
{
"label": "Windows / Debug (Results Screen Test)",
"target": "windows",
"args": ["-debug", "-DRESULTS"]
},
{ {
"label": "Windows / Debug (Straight to Chart Editor)", "label": "Windows / Debug (Straight to Chart Editor)",
"target": "windows", "target": "windows",

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" />
@ -128,6 +128,7 @@
<haxelib name="json2object" /> <!-- JSON parsing --> <haxelib name="json2object" /> <!-- JSON parsing -->
<haxelib name="thx.core" /> <!-- General utility library, "the lodash of Haxe" -->
<haxelib name="thx.semver" /> <!-- Version string handling --> <haxelib name="thx.semver" /> <!-- Version string handling -->
<haxelib name="hxcpp-debug-server" if="desktop debug" /> <!-- VSCode debug support --> <haxelib name="hxcpp-debug-server" if="desktop debug" /> <!-- VSCode debug support -->

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,9 +19,11 @@ 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
- [MasterEric](https://twitter.com/EliteMasterEric) - Programmer - [EliteMasterEric](https://twitter.com/EliteMasterEric) - Programmer
- [MtH](https://twitter.com/emmnyaa) - Charting and Additional Programming - [MtH](https://twitter.com/emmnyaa) - Charting and Additional Programming
- [GeoKureli](https://twitter.com/Geokureli/) - Additional Programming - [GeoKureli](https://twitter.com/Geokureli/) - Additional Programming
- Our contributors on GitHub - Our contributors on GitHub

2
art

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

2
assets

@ -1 +1 @@
Subproject commit dabdf9b1d361afa0f65c87b9c0f12cf90b0eebdf Subproject commit 8fea0bf1fe07b6dd0efb8ecf46dc8091b0177007

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

@ -3,7 +3,7 @@
"description": "An introductory mod.", "description": "An introductory mod.",
"contributors": [ "contributors": [
{ {
"name": "MasterEric" "name": "EliteMasterEric"
} }
], ],
"api_version": "0.1.0", "api_version": "0.1.0",

View file

@ -3,7 +3,7 @@
"description": "Newgrounds? More like OLDGROUNDS lol.", "description": "Newgrounds? More like OLDGROUNDS lol.",
"contributors": [ "contributors": [
{ {
"name": "MasterEric" "name": "EliteMasterEric"
} }
], ],
"api_version": "0.1.0", "api_version": "0.1.0",

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"
}, },
{ {
@ -153,7 +153,7 @@
"name": "polymod", "name": "polymod",
"type": "git", "type": "git",
"dir": null, "dir": null,
"ref": "8553b800965f225bb14c7ab8f04bfa9cdec362ac", "ref": "bfbe30d81601b3543d80dce580108ad6b7e182c7",
"url": "https://github.com/larsiusprime/polymod" "url": "https://github.com/larsiusprime/polymod"
}, },
{ {

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

@ -214,6 +214,30 @@ class InitState extends FlxState
#elseif STAGEBUILD #elseif STAGEBUILD
// -DSTAGEBUILD // -DSTAGEBUILD
FlxG.switchState(() -> new funkin.ui.debug.stage.StageBuilderState()); FlxG.switchState(() -> new funkin.ui.debug.stage.StageBuilderState());
#elseif RESULTS
// -DRESULTS
FlxG.switchState(() -> new funkin.play.ResultState(
{
storyMode: false,
title: "CUM SONG",
isNewHighscore: true,
scoreData:
{
score: 1_234_567,
tallies:
{
sick: 130,
good: 25,
bad: 69,
shit: 69,
missed: 69,
combo: 69,
maxCombo: 69,
totalNotesHit: 140,
totalNotes: 200 // 0,
}
},
}));
#elseif ANIMDEBUG #elseif ANIMDEBUG
// -DANIMDEBUG // -DANIMDEBUG
FlxG.switchState(() -> new funkin.ui.debug.anim.DebugBoundingState()); FlxG.switchState(() -> new funkin.ui.debug.anim.DebugBoundingState());

View file

@ -1,9 +1,5 @@
package funkin.api.newgrounds; package funkin.api.newgrounds;
import flixel.util.FlxSignal;
import flixel.util.FlxTimer;
import lime.app.Application;
import openfl.display.Stage;
#if newgrounds #if newgrounds
import io.newgrounds.NG; import io.newgrounds.NG;
import io.newgrounds.NGLite; import io.newgrounds.NGLite;

View file

@ -2,19 +2,11 @@ package funkin.api.newgrounds;
#if newgrounds #if newgrounds
import flixel.util.FlxSignal; import flixel.util.FlxSignal;
import flixel.util.FlxTimer;
import io.newgrounds.NG; import io.newgrounds.NG;
import io.newgrounds.NGLite; import io.newgrounds.NGLite;
import io.newgrounds.components.ScoreBoardComponent.Period;
import io.newgrounds.objects.Error; import io.newgrounds.objects.Error;
import io.newgrounds.objects.Medal;
import io.newgrounds.objects.Score; import io.newgrounds.objects.Score;
import io.newgrounds.objects.ScoreBoard;
import io.newgrounds.objects.events.Response;
import io.newgrounds.objects.events.Result.GetCurrentVersionResult;
import io.newgrounds.objects.events.Result.GetVersionResult;
import lime.app.Application; import lime.app.Application;
import openfl.display.Stage;
#end #end
/** /**

View file

@ -11,10 +11,9 @@ import funkin.audio.waveform.WaveformDataParser;
import funkin.data.song.SongData.SongMusicData; import funkin.data.song.SongData.SongMusicData;
import funkin.data.song.SongRegistry; import funkin.data.song.SongRegistry;
import funkin.util.tools.ICloneable; import funkin.util.tools.ICloneable;
import openfl.Assets;
import openfl.media.SoundMixer; import openfl.media.SoundMixer;
#if (openfl >= "8.0.0") #if (openfl >= "8.0.0")
import openfl.utils.AssetType;
#end #end
/** /**

View file

@ -1,9 +1,7 @@
package funkin.audio; package funkin.audio;
import funkin.audio.FunkinSound;
import flixel.group.FlxGroup.FlxTypedGroup; import flixel.group.FlxGroup.FlxTypedGroup;
import funkin.audio.waveform.WaveformData; import funkin.audio.waveform.WaveformData;
import funkin.audio.waveform.WaveformDataParser;
class VoicesGroup extends SoundGroup class VoicesGroup extends SoundGroup
{ {

View file

@ -1,13 +1,9 @@
package funkin.audio.visualize; package funkin.audio.visualize;
import funkin.audio.visualize.dsp.FFT;
import flixel.FlxSprite; import flixel.FlxSprite;
import flixel.addons.plugin.taskManager.FlxTask;
import flixel.graphics.frames.FlxAtlasFrames; import flixel.graphics.frames.FlxAtlasFrames;
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup; import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
import flixel.math.FlxMath;
import flixel.sound.FlxSound; import flixel.sound.FlxSound;
import funkin.util.MathUtil;
import funkin.vis.dsp.SpectralAnalyzer; import funkin.vis.dsp.SpectralAnalyzer;
import funkin.vis.audioclip.frontends.LimeAudioClip; import funkin.vis.audioclip.frontends.LimeAudioClip;

View file

@ -1,6 +1,5 @@
package funkin.audio.visualize; package funkin.audio.visualize;
import funkin.audio.visualize.PolygonSpectogram;
import flixel.group.FlxGroup.FlxTypedGroup; import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.sound.FlxSound; import flixel.sound.FlxSound;

View file

@ -8,8 +8,6 @@ import flixel.sound.FlxSound;
import flixel.util.FlxColor; import flixel.util.FlxColor;
import funkin.audio.visualize.PolygonSpectogram.VISTYPE; import funkin.audio.visualize.PolygonSpectogram.VISTYPE;
import funkin.audio.visualize.VisShit.CurAudioInfo; import funkin.audio.visualize.VisShit.CurAudioInfo;
import funkin.audio.visualize.dsp.FFT;
import lime.system.ThreadPool;
import lime.utils.Int16Array; import lime.utils.Int16Array;
using Lambda; using Lambda;
@ -38,8 +36,6 @@ class SpectogramSprite extends FlxTypedSpriteGroup<FlxSprite>
lengthOfShit = amnt; lengthOfShit = amnt;
regenLineShit(); regenLineShit();
// makeGraphic(200, 200, FlxColor.BLACK);
} }
public function regenLineShit():Void public function regenLineShit():Void
@ -89,8 +85,6 @@ class SpectogramSprite extends FlxTypedSpriteGroup<FlxSprite>
{ {
checkAndSetBuffer(); checkAndSetBuffer();
// vis.checkAndSetBuffer();
if (setBuffer) if (setBuffer)
{ {
var samplesToGen:Int = Std.int(sampleRate * seconds); var samplesToGen:Int = Std.int(sampleRate * seconds);
@ -191,7 +185,6 @@ class SpectogramSprite extends FlxTypedSpriteGroup<FlxSprite>
// a value between 10hz and 100Khz // a value between 10hz and 100Khz
var hzPicker:Float = Math.pow(10, powedShit); var hzPicker:Float = Math.pow(10, powedShit);
// var sampleApprox:Int = Std.int(FlxMath.remapToRange(i, 0, group.members.length, startingSample, startingSample + samplesToGen));
var remappedFreq:Int = Std.int(FlxMath.remapToRange(hzPicker, 0, 10000, 0, freqShit[0].length - 1)); var remappedFreq:Int = Std.int(FlxMath.remapToRange(hzPicker, 0, 10000, 0, freqShit[0].length - 1));
group.members[i].x = prevLine.x; group.members[i].x = prevLine.x;
@ -211,8 +204,6 @@ class SpectogramSprite extends FlxTypedSpriteGroup<FlxSprite>
var line = FlxPoint.get(prevLine.x - group.members[i].x, prevLine.y - group.members[i].y); var line = FlxPoint.get(prevLine.x - group.members[i].x, prevLine.y - group.members[i].y);
// dont draw a line until i figure out a nicer way to view da spikes and shit idk lol! // dont draw a line until i figure out a nicer way to view da spikes and shit idk lol!
// group.members[i].setGraphicSize(Std.int(Math.max(line.length, 1)), Std.int(1));
// group.members[i].angle = line.degrees;
} }
} }
} }
@ -261,9 +252,6 @@ class SpectogramSprite extends FlxTypedSpriteGroup<FlxSprite>
group.members[Std.int(remappedSample)].x = prevLine.x; group.members[Std.int(remappedSample)].x = prevLine.x;
group.members[Std.int(remappedSample)].y = prevLine.y; group.members[Std.int(remappedSample)].y = prevLine.y;
// group.members[0].y = prevLine.y;
// FlxSpriteUtil.drawLine(this, prevLine.x, prevLine.y, width * remappedSample, left * height / 2 + height / 2);
prevLine.x = (curAud.balanced * swagheight / 2 + swagheight / 2) + x; prevLine.x = (curAud.balanced * swagheight / 2 + swagheight / 2) + x;
prevLine.y = (Std.int(remappedSample) / lengthOfShit * daHeight) + y; prevLine.y = (Std.int(remappedSample) / lengthOfShit * daHeight) + y;

View file

@ -3,7 +3,6 @@ package funkin.audio.visualize;
import flixel.math.FlxMath; import flixel.math.FlxMath;
import flixel.sound.FlxSound; import flixel.sound.FlxSound;
import funkin.audio.visualize.dsp.FFT; import funkin.audio.visualize.dsp.FFT;
import lime.system.ThreadPool;
import lime.utils.Int16Array; import lime.utils.Int16Array;
import funkin.util.MathUtil; import funkin.util.MathUtil;
@ -73,9 +72,6 @@ class VisShit
freqOutput.push([]); freqOutput.push([]);
// if (FlxG.keys.justPressed.M)
// trace(FFT.rfft(chunk).map(z -> z.scale(1 / fs).magnitude));
// find spectral peaks and their instantaneous frequencies // find spectral peaks and their instantaneous frequencies
for (k => s in freqs) for (k => s in freqs)
{ {
@ -91,7 +87,6 @@ class VisShit
if (freq < maxFreq) freqOutput[indexOfArray].push(power); if (freq < maxFreq) freqOutput[indexOfArray].push(power);
// //
} }
// haxe.Log.trace("", null);
indexOfArray++; indexOfArray++;
// move to next (overlapping) chunk // move to next (overlapping) chunk

View file

@ -1,7 +1,5 @@
package funkin.audio.visualize.dsp; package funkin.audio.visualize.dsp;
import funkin.audio.visualize.dsp.Complex;
using funkin.audio.visualize.dsp.OffsetArray; using funkin.audio.visualize.dsp.OffsetArray;
using funkin.audio.visualize.dsp.Signal; using funkin.audio.visualize.dsp.Signal;

View file

@ -1,7 +1,5 @@
package funkin.audio.waveform; package funkin.audio.waveform;
import funkin.util.MathUtil;
@:nullSafety @:nullSafety
class WaveformData class WaveformData
{ {

View file

@ -1,7 +1,5 @@
package funkin.audio.waveform; package funkin.audio.waveform;
import funkin.audio.waveform.WaveformData;
import funkin.audio.waveform.WaveformDataParser;
import funkin.graphics.rendering.MeshRender; import funkin.graphics.rendering.MeshRender;
import flixel.util.FlxColor; import flixel.util.FlxColor;

View file

@ -1,7 +1,5 @@
package funkin.data.dialogue.conversation; package funkin.data.dialogue.conversation;
import funkin.data.animation.AnimationData;
/** /**
* A type definition for the data for a specific conversation. * A type definition for the data for a specific conversation.
* It includes things like what dialogue boxes to use, what text to display, and what animations to play. * It includes things like what dialogue boxes to use, what text to display, and what animations to play.

View file

@ -1,7 +1,6 @@
package funkin.data.dialogue.conversation; package funkin.data.dialogue.conversation;
import funkin.play.cutscene.dialogue.Conversation; import funkin.play.cutscene.dialogue.Conversation;
import funkin.data.dialogue.conversation.ConversationData;
import funkin.play.cutscene.dialogue.ScriptedConversation; import funkin.play.cutscene.dialogue.ScriptedConversation;
class ConversationRegistry extends BaseRegistry<Conversation, ConversationData> class ConversationRegistry extends BaseRegistry<Conversation, ConversationData>

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

@ -11,6 +11,7 @@ import flixel.system.debug.watch.Tracker;
// These are great. // These are great.
using Lambda; using Lambda;
using StringTools; using StringTools;
using thx.Arrays;
using funkin.util.tools.ArraySortTools; using funkin.util.tools.ArraySortTools;
using funkin.util.tools.ArrayTools; using funkin.util.tools.ArrayTools;
using funkin.util.tools.FloatTools; using funkin.util.tools.FloatTools;

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.distinct();
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.distinct();
}
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();
playerStrumline.vwooshNotes(); if (!fromDeathState)
opponentStrumline.vwooshNotes(); {
playerStrumline.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,
@ -2549,12 +2573,20 @@ 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)
{ {
disableKeys = true; if (isChartingMode)
persistentUpdate = false; {
FlxG.switchState(() -> new ChartEditorState( if (FlxG.sound.music != null) FlxG.sound.music.pause(); // Don't reset song position!
{ this.close(); // This only works because PlayState is a substate!
targetSongId: currentSong.id, }
})); else
{
disableKeys = true;
persistentUpdate = false;
FlxG.switchState(() -> new ChartEditorState(
{
targetSongId: currentSong.id,
}));
}
} }
#end #end
@ -2777,6 +2809,7 @@ class PlayState extends MusicBeatSubState
deathCounter = 0; deathCounter = 0;
var isNewHighscore = false; var isNewHighscore = false;
var prevScoreData:Null<SaveScoreData> = Save.instance.getSongScore(currentSong.id, currentDifficulty);
if (currentSong != null && currentSong.validScore) if (currentSong != null && currentSong.validScore)
{ {
@ -2796,7 +2829,6 @@ class PlayState extends MusicBeatSubState
totalNotesHit: Highscore.tallies.totalNotesHit, totalNotesHit: Highscore.tallies.totalNotesHit,
totalNotes: Highscore.tallies.totalNotes, totalNotes: Highscore.tallies.totalNotes,
}, },
accuracy: Highscore.tallies.totalNotesHit / Highscore.tallies.totalNotes,
}; };
// adds current song data into the tallies for the level (story levels) // adds current song data into the tallies for the level (story levels)
@ -2833,7 +2865,7 @@ class PlayState extends MusicBeatSubState
score: PlayStatePlaylist.campaignScore, score: PlayStatePlaylist.campaignScore,
tallies: tallies:
{ {
// TODO: Sum up the values for the whole level! // TODO: Sum up the values for the whole week!
sick: 0, sick: 0,
good: 0, good: 0,
bad: 0, bad: 0,
@ -2844,7 +2876,6 @@ class PlayState extends MusicBeatSubState
totalNotesHit: 0, totalNotesHit: 0,
totalNotes: 0, totalNotes: 0,
}, },
accuracy: Highscore.tallies.totalNotesHit / Highscore.tallies.totalNotes,
}; };
if (Save.instance.isLevelHighScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data)) if (Save.instance.isLevelHighScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data))
@ -2930,11 +2961,11 @@ class PlayState extends MusicBeatSubState
{ {
if (rightGoddamnNow) if (rightGoddamnNow)
{ {
moveToResultsScreen(isNewHighscore); moveToResultsScreen(isNewHighscore, prevScoreData);
} }
else else
{ {
zoomIntoResultsScreen(isNewHighscore); zoomIntoResultsScreen(isNewHighscore, prevScoreData);
} }
} }
} }
@ -3008,7 +3039,7 @@ class PlayState extends MusicBeatSubState
/** /**
* Play the camera zoom animation and then move to the results screen once it's done. * Play the camera zoom animation and then move to the results screen once it's done.
*/ */
function zoomIntoResultsScreen(isNewHighscore:Bool):Void function zoomIntoResultsScreen(isNewHighscore:Bool, ?prevScoreData:SaveScoreData):Void
{ {
trace('WENT TO RESULTS SCREEN!'); trace('WENT TO RESULTS SCREEN!');
@ -3048,7 +3079,7 @@ class PlayState extends MusicBeatSubState
FlxTween.tween(camHUD, {alpha: 0}, 0.6, FlxTween.tween(camHUD, {alpha: 0}, 0.6,
{ {
onComplete: function(_) { onComplete: function(_) {
moveToResultsScreen(isNewHighscore); moveToResultsScreen(isNewHighscore, prevScoreData);
} }
}); });
@ -3081,7 +3112,7 @@ class PlayState extends MusicBeatSubState
/** /**
* Move to the results screen right goddamn now. * Move to the results screen right goddamn now.
*/ */
function moveToResultsScreen(isNewHighscore:Bool):Void function moveToResultsScreen(isNewHighscore:Bool, ?prevScoreData:SaveScoreData):Void
{ {
persistentUpdate = false; persistentUpdate = false;
vocals.stop(); vocals.stop();
@ -3093,6 +3124,8 @@ class PlayState extends MusicBeatSubState
{ {
storyMode: PlayStatePlaylist.isStoryMode, storyMode: PlayStatePlaylist.isStoryMode,
title: PlayStatePlaylist.isStoryMode ? ('${PlayStatePlaylist.campaignTitle}') : ('${currentChart.songName} by ${currentChart.songArtist}'), title: PlayStatePlaylist.isStoryMode ? ('${PlayStatePlaylist.campaignTitle}') : ('${currentChart.songName} by ${currentChart.songArtist}'),
prevScoreData: prevScoreData,
difficultyId: currentDifficulty,
scoreData: scoreData:
{ {
score: PlayStatePlaylist.isStoryMode ? PlayStatePlaylist.campaignScore : songScore, score: PlayStatePlaylist.isStoryMode ? PlayStatePlaylist.campaignScore : songScore,
@ -3108,7 +3141,6 @@ class PlayState extends MusicBeatSubState
totalNotesHit: talliesToUse.totalNotesHit, totalNotesHit: talliesToUse.totalNotesHit,
totalNotes: talliesToUse.totalNotes, totalNotes: talliesToUse.totalNotes,
}, },
accuracy: Highscore.tallies.totalNotesHit / Highscore.tallies.totalNotes,
}, },
isNewHighscore: isNewHighscore isNewHighscore: isNewHighscore
}); });

View file

@ -12,6 +12,8 @@ import funkin.ui.MusicBeatSubState;
import flixel.math.FlxRect; import flixel.math.FlxRect;
import flixel.text.FlxBitmapText; import flixel.text.FlxBitmapText;
import funkin.ui.freeplay.FreeplayScore; import funkin.ui.freeplay.FreeplayScore;
import flixel.text.FlxText;
import flixel.util.FlxColor;
import flixel.tweens.FlxEase; import flixel.tweens.FlxEase;
import funkin.ui.freeplay.FreeplayState; import funkin.ui.freeplay.FreeplayState;
import flixel.tweens.FlxTween; import flixel.tweens.FlxTween;
@ -22,153 +24,196 @@ import funkin.save.Save;
import funkin.save.Save.SaveScoreData; import funkin.save.Save.SaveScoreData;
import funkin.graphics.shaders.LeftMaskShader; import funkin.graphics.shaders.LeftMaskShader;
import funkin.play.components.TallyCounter; import funkin.play.components.TallyCounter;
import funkin.play.components.ClearPercentCounter;
/** /**
* The state for the results screen after a song or week is finished. * The state for the results screen after a song or week is finished.
*/ */
@:nullSafety
class ResultState extends MusicBeatSubState class ResultState extends MusicBeatSubState
{ {
final params:ResultsStateParams; final params:ResultsStateParams;
var resultsVariation:ResultVariations; final rank:ResultRank;
var songName:FlxBitmapText; final songName:FlxBitmapText;
var difficulty:FlxSprite; final difficulty:FlxSprite;
final clearPercentSmall:ClearPercentCounter;
var maskShaderSongName:LeftMaskShader = new LeftMaskShader(); final maskShaderSongName:LeftMaskShader = new LeftMaskShader();
var maskShaderDifficulty:LeftMaskShader = new LeftMaskShader(); final maskShaderDifficulty:LeftMaskShader = new LeftMaskShader();
final resultsAnim:FunkinSprite;
final ratingsPopin:FunkinSprite;
final scorePopin:FunkinSprite;
final bgFlash:FlxSprite;
final highscoreNew:FlxSprite;
final score:ResultScore;
var bfPerfect:Null<FlxAtlasSprite> = null;
var bfExcellent:Null<FlxAtlasSprite> = null;
var bfGreat:Null<FlxAtlasSprite> = null;
var bfGood:Null<FlxSprite> = null;
var gfGood:Null<FlxSprite> = null;
var bfShit:Null<FlxAtlasSprite> = null;
public function new(params:ResultsStateParams) public function new(params:ResultsStateParams)
{ {
super(); super();
this.params = params; this.params = params;
}
override function create():Void rank = calculateRank(params);
{ // rank = SHIT;
/*
if (params.scoreData.sick == params.scoreData.totalNotesHit
&& params.scoreData.maxCombo == params.scoreData.totalNotesHit) resultsVariation = PERFECT;
else if (params.scoreData.missed + params.scoreData.bad + params.scoreData.shit >= params.scoreData.totalNotes * 0.50)
resultsVariation = SHIT; // if more than half of your song was missed, bad, or shit notes, you get shit ending!
else
resultsVariation = NORMAL;
*/
resultsVariation = NORMAL;
FunkinSound.playMusic('results$resultsVariation', // We build a lot of this stuff in the constructor, then place it in create().
{ // This prevents having to do `null` checks everywhere.
startingVolume: 1.0,
overrideExisting: true,
restartTrack: true,
loop: resultsVariation != SHIT
});
// Reset the camera zoom on the results screen.
FlxG.camera.zoom = 1.0;
// TEMP-ish, just used to sorta "cache" the 3000x3000 image!
var cacheBullShit:FlxSprite = new FlxSprite().loadGraphic(Paths.image("resultScreen/soundSystem"));
add(cacheBullShit);
var dumb:FlxSprite = new FlxSprite().loadGraphic(Paths.image("resultScreen/scorePopin"));
add(dumb);
var bg:FlxSprite = FlxGradient.createGradientFlxSprite(FlxG.width, FlxG.height, [0xFFFECC5C, 0xFFFDC05C], 90);
bg.scrollFactor.set();
add(bg);
var bgFlash:FlxSprite = FlxGradient.createGradientFlxSprite(FlxG.width, FlxG.height, [0xFFFFEB69, 0xFFFFE66A], 90);
bgFlash.scrollFactor.set();
bgFlash.visible = false;
add(bgFlash);
// var bfGfExcellent:FlxAtlasSprite = new FlxAtlasSprite(380, -170, Paths.animateAtlas("resultScreen/resultsBoyfriendExcellent", "shared"));
// bfGfExcellent.visible = false;
// add(bfGfExcellent);
//
// var bfPerfect:FlxAtlasSprite = new FlxAtlasSprite(370, -180, Paths.animateAtlas("resultScreen/resultsBoyfriendPerfect", "shared"));
// bfPerfect.visible = false;
// add(bfPerfect);
//
// var bfSHIT:FlxAtlasSprite = new FlxAtlasSprite(0, 20, Paths.animateAtlas("resultScreen/resultsBoyfriendSHIT", "shared"));
// bfSHIT.visible = false;
// add(bfSHIT);
//
// bfGfExcellent.anim.onComplete = () -> {
// bfGfExcellent.anim.curFrame = 28;
// bfGfExcellent.anim.play(); // unpauses this anim, since it's on PlayOnce!
// };
//
// bfPerfect.anim.onComplete = () -> {
// bfPerfect.anim.curFrame = 136;
// bfPerfect.anim.play(); // unpauses this anim, since it's on PlayOnce!
// };
//
// bfSHIT.anim.onComplete = () -> {
// bfSHIT.anim.curFrame = 150;
// bfSHIT.anim.play(); // unpauses this anim, since it's on PlayOnce!
// };
var gf:FlxSprite = FunkinSprite.createSparrow(625, 325, 'resultScreen/resultGirlfriendGOOD');
gf.animation.addByPrefix("clap", "Girlfriend Good Anim", 24, false);
gf.visible = false;
gf.animation.finishCallback = _ -> {
gf.animation.play('clap', true, false, 9);
};
add(gf);
var boyfriend:FlxSprite = FunkinSprite.createSparrow(640, -200, 'resultScreen/resultBoyfriendGOOD');
boyfriend.animation.addByPrefix("fall", "Boyfriend Good Anim0", 24, false);
boyfriend.visible = false;
boyfriend.animation.finishCallback = function(_) {
boyfriend.animation.play('fall', true, false, 14);
};
add(boyfriend);
var soundSystem:FlxSprite = FunkinSprite.createSparrow(-15, -180, 'resultScreen/soundSystem');
soundSystem.animation.addByPrefix("idle", "sound system", 24, false);
soundSystem.visible = false;
new FlxTimer().start(0.4, _ -> {
soundSystem.animation.play("idle");
soundSystem.visible = true;
});
add(soundSystem);
difficulty = new FlxSprite(555);
var diffSpr:String = switch (PlayState.instance.currentDifficulty)
{
case 'easy':
'difEasy';
case 'normal':
'difNormal';
case 'hard':
'difHard';
case 'erect':
'difErect';
case 'nightmare':
'difNightmare';
case _:
'difNormal';
}
difficulty.loadGraphic(Paths.image("resultScreen/" + diffSpr));
add(difficulty);
var fontLetters:String = "AaBbCcDdEeFfGgHhiIJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz:1234567890"; var fontLetters:String = "AaBbCcDdEeFfGgHhiIJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz:1234567890";
songName = new FlxBitmapText(FlxBitmapFont.fromMonospace(Paths.image("resultScreen/tardlingSpritesheet"), fontLetters, FlxPoint.get(49, 62))); songName = new FlxBitmapText(FlxBitmapFont.fromMonospace(Paths.image("resultScreen/tardlingSpritesheet"), fontLetters, FlxPoint.get(49, 62)));
songName.text = params.title; songName.text = params.title;
songName.letterSpacing = -15; songName.letterSpacing = -15;
songName.angle = -4.4; songName.angle = -4.4;
songName.zIndex = 1000;
difficulty = new FlxSprite(555);
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);
resultsAnim = FunkinSprite.createSparrow(-200, -10, "resultScreen/results");
ratingsPopin = FunkinSprite.createSparrow(-150, 120, "resultScreen/ratingsPopin");
scorePopin = FunkinSprite.createSparrow(-180, 520, "resultScreen/scorePopin");
highscoreNew = new FlxSprite(310, 570);
score = new ResultScore(35, 305, 10, params.scoreData.score);
}
override function create():Void
{
// Reset the camera zoom on the results screen.
FlxG.camera.zoom = 1.0;
var bg:FlxSprite = FlxGradient.createGradientFlxSprite(FlxG.width, FlxG.height, [0xFFFECC5C, 0xFFFDC05C], 90);
bg.scrollFactor.set();
bg.zIndex = 10;
add(bg);
bgFlash.scrollFactor.set();
bgFlash.visible = false;
bgFlash.zIndex = 20;
add(bgFlash);
// The sound system which falls into place behind the score text. Plays every time!
var soundSystem:FlxSprite = FunkinSprite.createSparrow(-15, -180, 'resultScreen/soundSystem');
soundSystem.animation.addByPrefix("idle", "sound system", 24, false);
soundSystem.visible = false;
new FlxTimer().start(0.3, _ -> {
soundSystem.animation.play("idle");
soundSystem.visible = true;
});
soundSystem.zIndex = 1100;
add(soundSystem);
switch (rank)
{
case PERFECT | PERFECT_GOLD:
bfPerfect = new FlxAtlasSprite(370, -180, Paths.animateAtlas("resultScreen/results-bf/resultsPERFECT", "shared"));
bfPerfect.visible = false;
bfPerfect.zIndex = 500;
add(bfPerfect);
bfPerfect.anim.onComplete = () -> {
if (bfPerfect != null)
{
bfPerfect.anim.curFrame = 137;
bfPerfect.anim.play(); // unpauses this anim, since it's on PlayOnce!
}
};
case EXCELLENT:
bfExcellent = new FlxAtlasSprite(380, -170, Paths.animateAtlas("resultScreen/results-bf/resultsEXCELLENT", "shared"));
bfExcellent.visible = false;
bfExcellent.zIndex = 500;
add(bfExcellent);
bfExcellent.onAnimationFinish.add((animName) -> {
if (bfExcellent != null)
{
bfExcellent.playAnimation('Loop Start');
}
});
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.animation.addByPrefix("clap", "Girlfriend Good Anim", 24, false);
gfGood.visible = false;
gfGood.zIndex = 500;
gfGood.animation.finishCallback = _ -> {
if (gfGood != null)
{
gfGood.animation.play('clap', true, false, 9);
}
};
add(gfGood);
bfGood = FunkinSprite.createSparrow(640, -200, 'resultScreen/results-bf/resultsGOOD/resultBoyfriendGOOD');
bfGood.animation.addByPrefix("fall", "Boyfriend Good Anim0", 24, false);
bfGood.visible = false;
bfGood.zIndex = 501;
bfGood.animation.finishCallback = function(_) {
if (bfGood != null)
{
bfGood.animation.play('fall', true, false, 14);
}
};
add(bfGood);
case SHIT:
bfShit = new FlxAtlasSprite(0, 20, Paths.animateAtlas("resultScreen/results-bf/resultsSHIT", "shared"));
bfShit.visible = false;
bfShit.zIndex = 500;
add(bfShit);
bfShit.onAnimationFinish.add((animName) -> {
if (bfShit != null)
{
bfShit.playAnimation('Loop Start');
}
});
}
var diffSpr:String = 'dif${params?.difficultyId ?? 'Normal'}';
difficulty.loadGraphic(Paths.image("resultScreen/" + diffSpr));
add(difficulty);
add(songName); add(songName);
var angleRad = songName.angle * Math.PI / 180; var angleRad = songName.angle * Math.PI / 180;
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(); timerThenSongName(1.0, false);
songName.shader = maskShaderSongName; songName.shader = maskShaderSongName;
difficulty.shader = maskShaderDifficulty; difficulty.shader = maskShaderDifficulty;
@ -178,35 +223,53 @@ class ResultState extends MusicBeatSubState
var blackTopBar:FlxSprite = new FlxSprite().loadGraphic(Paths.image("resultScreen/topBarBlack")); var blackTopBar:FlxSprite = new FlxSprite().loadGraphic(Paths.image("resultScreen/topBarBlack"));
blackTopBar.y = -blackTopBar.height; blackTopBar.y = -blackTopBar.height;
FlxTween.tween(blackTopBar, {y: 0}, 0.4, {ease: FlxEase.quartOut, startDelay: 0.5}); FlxTween.tween(blackTopBar, {y: 0}, 0.4, {ease: FlxEase.quartOut});
blackTopBar.zIndex = 1010;
add(blackTopBar); add(blackTopBar);
var resultsAnim:FunkinSprite = FunkinSprite.createSparrow(-200, -10, "resultScreen/results");
resultsAnim.animation.addByPrefix("result", "results instance 1", 24, false); resultsAnim.animation.addByPrefix("result", "results instance 1", 24, false);
resultsAnim.animation.play("result"); resultsAnim.visible = false;
resultsAnim.zIndex = 1200;
add(resultsAnim); add(resultsAnim);
new FlxTimer().start(0.3, _ -> {
resultsAnim.visible = true;
resultsAnim.animation.play("result");
});
var ratingsPopin:FunkinSprite = FunkinSprite.createSparrow(-150, 120, "resultScreen/ratingsPopin");
ratingsPopin.animation.addByPrefix("idle", "Categories", 24, false); ratingsPopin.animation.addByPrefix("idle", "Categories", 24, false);
ratingsPopin.visible = false; ratingsPopin.visible = false;
ratingsPopin.zIndex = 1200;
add(ratingsPopin); add(ratingsPopin);
new FlxTimer().start(1.0, _ -> {
ratingsPopin.visible = true;
ratingsPopin.animation.play("idle");
});
var scorePopin:FunkinSprite = FunkinSprite.createSparrow(-180, 520, "resultScreen/scorePopin");
scorePopin.animation.addByPrefix("score", "tally score", 24, false); scorePopin.animation.addByPrefix("score", "tally score", 24, false);
scorePopin.visible = false; scorePopin.visible = false;
scorePopin.zIndex = 1200;
add(scorePopin); add(scorePopin);
new FlxTimer().start(1.0, _ -> {
scorePopin.visible = true;
scorePopin.animation.play("score");
scorePopin.animation.finishCallback = anim -> {
score.visible = true;
score.animateNumbers();
};
});
var highscoreNew:FlxSprite = new FlxSprite(310, 570);
highscoreNew.frames = Paths.getSparrowAtlas("resultScreen/highscoreNew"); highscoreNew.frames = Paths.getSparrowAtlas("resultScreen/highscoreNew");
highscoreNew.animation.addByPrefix("new", "NEW HIGHSCORE", 24); highscoreNew.animation.addByPrefix("new", "NEW HIGHSCORE", 24);
highscoreNew.visible = false; highscoreNew.visible = false;
highscoreNew.setGraphicSize(Std.int(highscoreNew.width * 0.8)); highscoreNew.setGraphicSize(Std.int(highscoreNew.width * 0.8));
highscoreNew.updateHitbox(); highscoreNew.updateHitbox();
highscoreNew.zIndex = 1200;
add(highscoreNew); add(highscoreNew);
var hStuf:Int = 50; var hStuf:Int = 50;
var ratingGrp:FlxTypedGroup<TallyCounter> = new FlxTypedGroup<TallyCounter>(); var ratingGrp:FlxTypedGroup<TallyCounter> = new FlxTypedGroup<TallyCounter>();
ratingGrp.zIndex = 1200;
add(ratingGrp); add(ratingGrp);
/** /**
@ -236,32 +299,115 @@ class ResultState extends MusicBeatSubState
var tallyMissed:TallyCounter = new TallyCounter(260, (hStuf * 9) + extraYOffset, params.scoreData.tallies.missed, 0xFFC68AE6); var tallyMissed:TallyCounter = new TallyCounter(260, (hStuf * 9) + extraYOffset, params.scoreData.tallies.missed, 0xFFC68AE6);
ratingGrp.add(tallyMissed); ratingGrp.add(tallyMissed);
var score:ResultScore = new ResultScore(35, 305, 10, params.scoreData.score);
score.visible = false; score.visible = false;
score.zIndex = 1200;
add(score); add(score);
for (ind => rating in ratingGrp.members) for (ind => rating in ratingGrp.members)
{ {
rating.visible = false; rating.visible = false;
new FlxTimer().start((0.3 * ind) + 0.55, _ -> { new FlxTimer().start((0.3 * ind) + 1.20, _ -> {
rating.visible = true; rating.visible = true;
FlxTween.tween(rating, {curNumber: rating.neededNumber}, 0.5, {ease: FlxEase.quartOut}); FlxTween.tween(rating, {curNumber: rating.neededNumber}, 0.5, {ease: FlxEase.quartOut});
}); });
} }
new FlxTimer().start(0.5, _ -> { ratingsPopin.animation.finishCallback = anim -> {
ratingsPopin.animation.play("idle"); startRankTallySequence();
ratingsPopin.visible = true;
if (params.isNewHighscore ?? false)
{
highscoreNew.visible = true;
highscoreNew.animation.play("new");
FlxTween.tween(highscoreNew, {y: highscoreNew.y + 10}, 0.8, {ease: FlxEase.quartOut});
}
else
{
highscoreNew.visible = false;
}
};
refresh();
super.create();
}
var rankTallyTimer:Null<FlxTimer> = null;
var clearPercentTarget:Int = 100;
var clearPercentLerp:Int = 0;
function startRankTallySequence():Void
{
var clearPercentFloat = (params.scoreData.tallies.sick + params.scoreData.tallies.good) / params.scoreData.tallies.totalNotes * 100;
clearPercentTarget = Math.floor(clearPercentFloat);
// Prevent off-by-one errors.
clearPercentLerp = Std.int(Math.max(0, clearPercentTarget - 36));
trace('Clear percent target: ' + clearPercentFloat + ', round: ' + clearPercentTarget);
var clearPercentCounter:ClearPercentCounter = new ClearPercentCounter(FlxG.width / 2 + 300, FlxG.height / 2 - 100, clearPercentLerp);
FlxTween.tween(clearPercentCounter, {curNumber: clearPercentTarget}, 1.5,
{
ease: FlxEase.quartOut,
onUpdate: _ -> {
// Only play the tick sound if the number increased.
if (clearPercentLerp != clearPercentCounter.curNumber)
{
clearPercentLerp = clearPercentCounter.curNumber;
FunkinSound.playOnce(Paths.sound('scrollMenu'));
}
},
onComplete: _ -> {
// Play confirm sound.
FunkinSound.playOnce(Paths.sound('confirmMenu'));
// Flash background.
bgFlash.visible = true;
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();
new FlxTimer().start(2.0, _ -> {
FlxTween.tween(clearPercentCounter, {alpha: 0}, 0.5,
{
startDelay: 0.5,
ease: FlxEase.quartOut,
onComplete: _ -> {
remove(clearPercentCounter);
}
});
afterRankTallySequence();
});
}
});
clearPercentCounter.zIndex = 450;
add(clearPercentCounter);
if (ratingsPopin == null)
{
trace("Could not build ratingsPopin!");
}
else
{
// ratingsPopin.animation.play("idle");
// ratingsPopin.visible = true;
ratingsPopin.animation.finishCallback = anim -> { ratingsPopin.animation.finishCallback = anim -> {
scorePopin.animation.play("score"); // scorePopin.animation.play("score");
scorePopin.animation.finishCallback = anim -> {
score.visible = true;
score.animateNumbers();
};
scorePopin.visible = true;
if (params.isNewHighscore) // scorePopin.visible = true;
if (params.isNewHighscore ?? false)
{ {
highscoreNew.visible = true; highscoreNew.visible = true;
highscoreNew.animation.play("new"); highscoreNew.animation.play("new");
@ -272,47 +418,128 @@ class ResultState extends MusicBeatSubState
highscoreNew.visible = false; highscoreNew.visible = false;
} }
}; };
}
switch (resultsVariation) refresh();
}
function displayRankText():Void
{
var rankTextVert:FunkinSprite = FunkinSprite.create(FlxG.width - 64, 100, rank.getVerTextAsset());
rankTextVert.zIndex = 2000;
add(rankTextVert);
for (i in 0...10)
{
var rankTextBack:FunkinSprite = FunkinSprite.create(FlxG.width / 2 - 80, 50, rank.getHorTextAsset());
rankTextBack.y += (rankTextBack.height * i / 2) + 10;
rankTextBack.zIndex = 100;
add(rankTextBack);
}
refresh();
}
function afterRankTallySequence():Void
{
showSmallClearPercent();
FunkinSound.playMusic(rank.getMusicPath(),
{ {
// case SHIT: startingVolume: 1.0,
// bfSHIT.visible = true; overrideExisting: true,
// bfSHIT.playAnimation(""); restartTrack: true,
loop: rank.shouldMusicLoop()
});
case NORMAL: FlxG.sound.music.onComplete = () -> {
boyfriend.animation.play('fall'); if (rank == SHIT)
boyfriend.visible = true; {
FunkinSound.playMusic('bluu',
new FlxTimer().start((1 / 24) * 12, _ -> { {
bgFlash.visible = true; startingVolume: 0.0,
FlxTween.tween(bgFlash, {alpha: 0}, 0.4); overrideExisting: true,
new FlxTimer().start((1 / 24) * 2, _ -> restartTrack: true,
{ loop: true
// bgFlash.alpha = 0.5;
// bgFlash.visible = false;
});
}); });
FlxG.sound.music.fadeIn(10.0, 0.0, 1.0);
}
}
switch (rank)
{
case PERFECT | PERFECT_GOLD:
if (bfPerfect == null)
{
trace("Could not build PERFECT animation!");
}
else
{
bfPerfect.visible = true;
bfPerfect.playAnimation('');
}
case EXCELLENT:
if (bfExcellent == null)
{
trace("Could not build EXCELLENT animation!");
}
else
{
bfExcellent.visible = true;
bfExcellent.playAnimation('Intro');
}
case GREAT:
if (bfGreat == null)
{
trace("Could not build GREAT animation!");
}
else
{
bfGreat.visible = true;
bfGreat.playAnimation('Intro');
}
case SHIT:
if (bfShit == null)
{
trace("Could not build SHIT animation!");
}
else
{
bfShit.visible = true;
bfShit.playAnimation('Intro');
}
case GOOD:
if (bfGood == null)
{
trace("Could not build GOOD animation!");
}
else
{
bfGood.animation.play('fall');
bfGood.visible = true;
new FlxTimer().start((1 / 24) * 22, _ -> { new FlxTimer().start((1 / 24) * 22, _ -> {
// plays about 22 frames (at 24fps timing) after bf spawns in // plays about 22 frames (at 24fps timing) after bf spawns in
gf.animation.play('clap', true); if (gfGood != null)
gf.visible = true; {
gfGood.animation.play('clap', true);
gfGood.visible = true;
}
else
{
trace("Could not build GOOD animation!");
}
}); });
// case PERFECT: }
// bfPerfect.visible = true; default:
// bfPerfect.playAnimation(""); }
// bfGfExcellent.visible = true;
// bfGfExcellent.playAnimation("");
default:
}
});
super.create();
} }
function timerThenSongName():Void function timerThenSongName(timerLength:Float = 3.0, autoScroll:Bool = true):Void
{ {
movingSongStuff = false; movingSongStuff = false;
@ -323,21 +550,47 @@ 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(3, _ -> { new FlxTimer().start(timerLength, _ -> {
var tempSpeed = FlxPoint.get(speedOfTween.x, speedOfTween.y); var tempSpeed = FlxPoint.get(speedOfTween.x, speedOfTween.y);
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);
@ -345,11 +598,9 @@ class ResultState extends MusicBeatSubState
{ {
super.draw(); super.draw();
if (songName != null) songName.clipRect = FlxRect.get(Math.max(0, 520 - songName.x), 0, FlxG.width, songName.height);
{
songName.clipRect = FlxRect.get(Math.max(0, 540 - 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)
// maskShaderSongName.frameUV = songName.frame.uv; // maskShaderSongName.frameUV = songName.frame.uv;
@ -364,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)
{ {
@ -401,14 +654,135 @@ class ResultState extends MusicBeatSubState
super.update(elapsed); super.update(elapsed);
} }
public static function calculateRank(params:ResultsStateParams):ResultRank
{
// Perfect (Platinum) is a Sick Full Clear
var isPerfectGold = params.scoreData.tallies.sick == params.scoreData.tallies.totalNotes;
if (isPerfectGold) return ResultRank.PERFECT_GOLD;
// Else, use the standard grades
// Grade % (only good and sick), 1.00 is a full combo
var grade = (params.scoreData.tallies.sick + params.scoreData.tallies.good) / params.scoreData.tallies.totalNotes;
// Clear % (including bad and shit). 1.00 is a full clear but not a full combo
var clear = (params.scoreData.tallies.totalNotesHit) / params.scoreData.tallies.totalNotes;
if (grade == Constants.RANK_PERFECT_THRESHOLD)
{
return ResultRank.PERFECT;
}
else if (grade >= Constants.RANK_EXCELLENT_THRESHOLD)
{
return ResultRank.EXCELLENT;
}
else if (grade >= Constants.RANK_GREAT_THRESHOLD)
{
return ResultRank.GREAT;
}
else if (grade >= Constants.RANK_GOOD_THRESHOLD)
{
return ResultRank.GOOD;
}
else
{
return ResultRank.SHIT;
}
}
} }
enum abstract ResultVariations(String) enum abstract ResultRank(String)
{ {
var PERFECT_GOLD;
var PERFECT; var PERFECT;
var EXCELLENT; var EXCELLENT;
var NORMAL; var GREAT;
var GOOD;
var SHIT; var SHIT;
public function getMusicPath():String
{
switch (abstract)
{
case PERFECT_GOLD:
return 'resultsPERFECT';
case PERFECT:
return 'resultsPERFECT';
case EXCELLENT:
return 'resultsNORMAL';
case GREAT:
return 'resultsNORMAL';
case GOOD:
return 'resultsNORMAL';
case SHIT:
return 'resultsSHIT';
default:
return 'resultsNORMAL';
}
}
public function shouldMusicLoop():Bool
{
switch (abstract)
{
case PERFECT_GOLD:
return true;
case PERFECT:
return true;
case EXCELLENT:
return true;
case GREAT:
return true;
case GOOD:
return true;
case SHIT:
return false;
default:
return false;
}
}
public function getHorTextAsset()
{
switch (abstract)
{
case PERFECT_GOLD:
return 'resultScreen/rankText/rankScrollPERFECT';
case PERFECT:
return 'resultScreen/rankText/rankScrollPERFECT';
case EXCELLENT:
return 'resultScreen/rankText/rankScrollEXCELLENT';
case GREAT:
return 'resultScreen/rankText/rankScrollGREAT';
case GOOD:
return 'resultScreen/rankText/rankScrollGOOD';
case SHIT:
return 'resultScreen/rankText/rankScrollLOSS';
default:
return 'resultScreen/rankText/rankScrollGOOD';
}
}
public function getVerTextAsset()
{
switch (abstract)
{
case PERFECT_GOLD:
return 'resultScreen/rankText/rankTextPERFECT';
case PERFECT:
return 'resultScreen/rankText/rankTextPERFECT';
case EXCELLENT:
return 'resultScreen/rankText/rankTextEXCELLENT';
case GREAT:
return 'resultScreen/rankText/rankTextGREAT';
case GOOD:
return 'resultScreen/rankText/rankTextGOOD';
case SHIT:
return 'resultScreen/rankText/rankTextLOSS';
default:
return 'resultScreen/rankText/rankTextGOOD';
}
}
} }
typedef ResultsStateParams = typedef ResultsStateParams =
@ -426,10 +800,21 @@ typedef ResultsStateParams =
/** /**
* Whether the displayed score is a new highscore * Whether the displayed score is a new highscore
*/ */
var isNewHighscore:Bool; var ?isNewHighscore:Bool;
/**
* The difficulty ID of the song/week we just played.
* @default Normal
*/
var ?difficultyId:String;
/** /**
* The score, accuracy, and judgements. * The score, accuracy, and judgements.
*/ */
var scoreData:SaveScoreData; var scoreData:SaveScoreData;
/**
* The previous score data, used for rank comparision.
*/
var ?prevScoreData:SaveScoreData;
}; };

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

@ -0,0 +1,137 @@
package funkin.play.components;
import funkin.graphics.FunkinSprite;
import funkin.graphics.shaders.PureColor;
import flixel.FlxSprite;
import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
import flixel.math.FlxMath;
import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
import flixel.text.FlxText.FlxTextAlign;
import funkin.util.MathUtil;
import flixel.util.FlxColor;
/**
* Numerical counters used to display the clear percent.
*/
class ClearPercentCounter extends FlxTypedSpriteGroup<FlxSprite>
{
public var curNumber(default, set):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);
flashShader = new PureColor(FlxColor.WHITE);
flashShader.colorSet = true;
curNumber = startingNumber;
this.small = small;
var clearPercentText:FunkinSprite = FunkinSprite.create(0, 0, 'resultScreen/clearPercent/clearPercentText${small ? 'Small' : ''}');
clearPercentText.x = small ? 40 : 0;
add(clearPercentText);
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;
override function update(elapsed:Float)
{
super.update(elapsed);
if (numberChanged) drawNumbers();
}
function drawNumbers()
{
var seperatedScore:Array<Int> = [];
var tempCombo:Int = Math.round(curNumber);
while (tempCombo != 0)
{
seperatedScore.push(tempCombo % 10);
tempCombo = Math.floor(tempCombo / 10);
}
if (seperatedScore.length == 0) seperatedScore.push(0);
seperatedScore.reverse();
for (ind => num in seperatedScore)
{
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)
{
// Three digits = LLR because the 1 and 0 won't be the same anyway.
var variant:Bool = (seperatedScore.length == 3) ? (digitIndex >= 2) : (digitIndex >= 1);
// var variant:Bool = (seperatedScore.length % 2 != 0) ? (digitIndex % 2 == 0) : (digitIndex % 2 == 1);
var numb:ClearPercentNumber = new ClearPercentNumber(xPos, yPos, num, variant, this.small);
numb.scale.set(this.scale.x, this.scale.y);
add(numb);
}
else
{
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;
}
}
}
}
class ClearPercentNumber extends FlxSprite
{
public function new(x:Float, y:Float, digit:Int, variant:Bool, small:Bool)
{
super(x, y);
frames = Paths.getSparrowAtlas('resultScreen/clearPercent/clearPercentNumber${small ? 'Small' : variant ? 'Right' : 'Left'}');
for (i in 0...10)
{
animation.addByPrefix('$i', 'number $i 0', 24, false);
}
animation.play('$digit');
updateHitbox();
}
}

View file

@ -24,7 +24,7 @@ import funkin.util.MathUtil;
* - i.e. `PlayState.instance.iconP1.playAnimation("losing")` * - i.e. `PlayState.instance.iconP1.playAnimation("losing")`
* - Scripts can also utilize all functionality that a normal FlxSprite would have access to, such as adding supplimental animations. * - Scripts can also utilize all functionality that a normal FlxSprite would have access to, such as adding supplimental animations.
* - i.e. `PlayState.instance.iconP1.animation.addByPrefix("jumpscare", "jumpscare", 24, false);` * - i.e. `PlayState.instance.iconP1.animation.addByPrefix("jumpscare", "jumpscare", 24, false);`
* @author MasterEric * @author EliteMasterEric
*/ */
@:nullSafety @:nullSafety
class HealthIcon extends FunkinSprite class HealthIcon extends FunkinSprite

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

@ -406,7 +406,7 @@ class Strumline extends FlxSpriteGroup
if (Preferences.downscroll) if (Preferences.downscroll)
{ {
holdNote.y = this.y + calculateNoteYPos(holdNote.strumTime, vwoosh) - holdNote.height + STRUMLINE_SIZE / 2; holdNote.y = this.y - INITIAL_OFFSET + calculateNoteYPos(holdNote.strumTime, vwoosh) - holdNote.height + STRUMLINE_SIZE / 2;
} }
else else
{ {
@ -435,7 +435,7 @@ class Strumline extends FlxSpriteGroup
if (Preferences.downscroll) if (Preferences.downscroll)
{ {
holdNote.y = this.y - holdNote.height + STRUMLINE_SIZE / 2; holdNote.y = this.y - INITIAL_OFFSET - holdNote.height + STRUMLINE_SIZE / 2;
} }
else else
{ {
@ -450,7 +450,7 @@ class Strumline extends FlxSpriteGroup
if (Preferences.downscroll) if (Preferences.downscroll)
{ {
holdNote.y = this.y + calculateNoteYPos(holdNote.strumTime, vwoosh) - holdNote.height + STRUMLINE_SIZE / 2; holdNote.y = this.y - INITIAL_OFFSET + calculateNoteYPos(holdNote.strumTime, vwoosh) - holdNote.height + STRUMLINE_SIZE / 2;
} }
else else
{ {
@ -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

@ -399,6 +399,27 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
return null; return null;
} }
/**
* Given that this character is selected in the Freeplay menu,
* which variations should be available?
* @param charId The character ID to query.
* @return An array of available variations.
*/
public function getVariationsByCharId(?charId:String):Array<String>
{
if (charId == null) charId = Constants.DEFAULT_CHARACTER;
if (variations.contains(charId))
{
return [charId];
}
else
{
// TODO: How to exclude character variations while keeping other custom variations?
return variations;
}
}
/** /**
* List all the difficulties in this song. * List all the difficulties in this song.
* *
@ -418,12 +439,16 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
// so we have to map it to the actual difficulty names. // so we have to map it to the actual difficulty names.
// We also filter out difficulties that don't match the variation or that don't exist. // We also filter out difficulties that don't match the variation or that don't exist.
var diffFiltered:Array<String> = difficulties.keys().array().map(function(diffId:String):Null<String> { var diffFiltered:Array<String> = difficulties.keys()
var difficulty:Null<SongDifficulty> = difficulties.get(diffId); .array()
if (difficulty == null) return null; .map(function(diffId:String):Null<String> {
if (variationIds.length > 0 && !variationIds.contains(difficulty.variation)) return null; var difficulty:Null<SongDifficulty> = difficulties.get(diffId);
return difficulty.difficulty; if (difficulty == null) return null;
}).nonNull().unique(); if (variationIds.length > 0 && !variationIds.contains(difficulty.variation)) return null;
return difficulty.difficulty;
})
.filterNull()
.distinct();
diffFiltered = diffFiltered.filter(function(diffId:String):Bool { diffFiltered = diffFiltered.filter(function(diffId:String):Bool {
if (showHidden) return true; if (showHidden) return true;

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.
@ -53,7 +52,8 @@ class Save
public function new(?data:RawSaveData) public function new(?data:RawSaveData)
{ {
if (data == null) this.data = Save.getDefault(); if (data == null) this.data = Save.getDefault();
else this.data = data; else
this.data = data;
} }
public static function getDefault():RawSaveData public static function getDefault():RawSaveData
@ -77,6 +77,9 @@ class Save
levels: [], levels: [],
songs: [], songs: [],
}, },
favoriteSongs: [],
options: options:
{ {
// Reasonable defaults. // Reasonable defaults.
@ -554,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)
@ -714,6 +746,7 @@ class Save
/** /**
* An anonymous structure containingg all the user's save data. * An anonymous structure containingg all the user's save data.
* Isn't stored with JSON, stored with some sort of Haxe built-in serialization?
*/ */
typedef RawSaveData = typedef RawSaveData =
{ {
@ -724,8 +757,6 @@ typedef RawSaveData =
/** /**
* A semantic versioning string for the save data format. * A semantic versioning string for the save data format.
*/ */
@:jcustomparse(funkin.data.DataParse.semverVersion)
@:jcustomwrite(funkin.data.DataWrite.semverVersion)
var version:Version; var version:Version;
var api:SaveApiData; var api:SaveApiData;
@ -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;
/** /**
@ -809,11 +846,6 @@ typedef SaveScoreData =
* The count of each judgement hit. * The count of each judgement hit.
*/ */
var tallies:SaveScoreTallyData; var tallies:SaveScoreTallyData;
/**
* The accuracy percentage.
*/
var accuracy:Float;
} }
typedef SaveScoreTallyData = typedef SaveScoreTallyData =

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

@ -3,7 +3,6 @@ package funkin.save.migrator;
import funkin.save.Save; import funkin.save.Save;
import funkin.save.migrator.RawSaveData_v1_0_0; import funkin.save.migrator.RawSaveData_v1_0_0;
import thx.semver.Version; import thx.semver.Version;
import funkin.util.StructureUtil;
import funkin.util.VersionUtil; import funkin.util.VersionUtil;
@:nullSafety @:nullSafety
@ -24,16 +23,20 @@ class SaveDataMigrator
} }
else else
{ {
// Sometimes the Haxe serializer has issues with the version so we fix it here.
version = VersionUtil.repairVersion(version);
if (VersionUtil.validateVersion(version, Save.SAVE_DATA_VERSION_RULE)) if (VersionUtil.validateVersion(version, Save.SAVE_DATA_VERSION_RULE))
{ {
// Simply import the structured data. // Import the structured data.
var save:Save = new Save(StructureUtil.deepMerge(Save.getDefault(), inputData)); var saveDataWithDefaults:RawSaveData = cast thx.Objects.deepCombine(Save.getDefault(), inputData);
var save:Save = new Save(saveDataWithDefaults);
return save; return save;
} }
else else
{ {
trace('[SAVE] Invalid save data version! Returning blank data.'); var message:String = 'Error migrating save data, expected ${Save.SAVE_DATA_VERSION}.';
trace(inputData); lime.app.Application.current.window.alert(message, "Save Data Failure");
trace('[SAVE] ' + message);
return new Save(Save.getDefault()); return new Save(Save.getDefault());
} }
} }
@ -118,7 +121,7 @@ class SaveDataMigrator
var scoreDataEasy:SaveScoreData = var scoreDataEasy:SaveScoreData =
{ {
score: inputSaveData.songScores.get('${levelId}-easy') ?? 0, score: inputSaveData.songScores.get('${levelId}-easy') ?? 0,
accuracy: inputSaveData.songCompletion.get('${levelId}-easy') ?? 0.0, // accuracy: inputSaveData.songCompletion.get('${levelId}-easy') ?? 0.0,
tallies: tallies:
{ {
sick: 0, sick: 0,
@ -137,7 +140,7 @@ class SaveDataMigrator
var scoreDataNormal:SaveScoreData = var scoreDataNormal:SaveScoreData =
{ {
score: inputSaveData.songScores.get('${levelId}') ?? 0, score: inputSaveData.songScores.get('${levelId}') ?? 0,
accuracy: inputSaveData.songCompletion.get('${levelId}') ?? 0.0, // accuracy: inputSaveData.songCompletion.get('${levelId}') ?? 0.0,
tallies: tallies:
{ {
sick: 0, sick: 0,
@ -156,7 +159,7 @@ class SaveDataMigrator
var scoreDataHard:SaveScoreData = var scoreDataHard:SaveScoreData =
{ {
score: inputSaveData.songScores.get('${levelId}-hard') ?? 0, score: inputSaveData.songScores.get('${levelId}-hard') ?? 0,
accuracy: inputSaveData.songCompletion.get('${levelId}-hard') ?? 0.0, // accuracy: inputSaveData.songCompletion.get('${levelId}-hard') ?? 0.0,
tallies: tallies:
{ {
sick: 0, sick: 0,
@ -178,7 +181,6 @@ class SaveDataMigrator
var scoreDataEasy:SaveScoreData = var scoreDataEasy:SaveScoreData =
{ {
score: 0, score: 0,
accuracy: 0,
tallies: tallies:
{ {
sick: 0, sick: 0,
@ -196,14 +198,13 @@ class SaveDataMigrator
for (songId in songIds) for (songId in songIds)
{ {
scoreDataEasy.score = Std.int(Math.max(scoreDataEasy.score, inputSaveData.songScores.get('${songId}-easy') ?? 0)); scoreDataEasy.score = Std.int(Math.max(scoreDataEasy.score, inputSaveData.songScores.get('${songId}-easy') ?? 0));
scoreDataEasy.accuracy = Math.max(scoreDataEasy.accuracy, inputSaveData.songCompletion.get('${songId}-easy') ?? 0.0); // scoreDataEasy.accuracy = Math.max(scoreDataEasy.accuracy, inputSaveData.songCompletion.get('${songId}-easy') ?? 0.0);
} }
result.setSongScore(songIds[0], 'easy', scoreDataEasy); result.setSongScore(songIds[0], 'easy', scoreDataEasy);
var scoreDataNormal:SaveScoreData = var scoreDataNormal:SaveScoreData =
{ {
score: 0, score: 0,
accuracy: 0,
tallies: tallies:
{ {
sick: 0, sick: 0,
@ -221,14 +222,13 @@ class SaveDataMigrator
for (songId in songIds) for (songId in songIds)
{ {
scoreDataNormal.score = Std.int(Math.max(scoreDataNormal.score, inputSaveData.songScores.get('${songId}') ?? 0)); scoreDataNormal.score = Std.int(Math.max(scoreDataNormal.score, inputSaveData.songScores.get('${songId}') ?? 0));
scoreDataNormal.accuracy = Math.max(scoreDataNormal.accuracy, inputSaveData.songCompletion.get('${songId}') ?? 0.0); // scoreDataNormal.accuracy = Math.max(scoreDataNormal.accuracy, inputSaveData.songCompletion.get('${songId}') ?? 0.0);
} }
result.setSongScore(songIds[0], 'normal', scoreDataNormal); result.setSongScore(songIds[0], 'normal', scoreDataNormal);
var scoreDataHard:SaveScoreData = var scoreDataHard:SaveScoreData =
{ {
score: 0, score: 0,
accuracy: 0,
tallies: tallies:
{ {
sick: 0, sick: 0,
@ -246,7 +246,7 @@ class SaveDataMigrator
for (songId in songIds) for (songId in songIds)
{ {
scoreDataHard.score = Std.int(Math.max(scoreDataHard.score, inputSaveData.songScores.get('${songId}-hard') ?? 0)); scoreDataHard.score = Std.int(Math.max(scoreDataHard.score, inputSaveData.songScores.get('${songId}-hard') ?? 0));
scoreDataHard.accuracy = Math.max(scoreDataHard.accuracy, inputSaveData.songCompletion.get('${songId}-hard') ?? 0.0); // scoreDataHard.accuracy = Math.max(scoreDataHard.accuracy, inputSaveData.songCompletion.get('${songId}-hard') ?? 0.0);
} }
result.setSongScore(songIds[0], 'hard', scoreDataHard); result.setSongScore(songIds[0], 'hard', scoreDataHard);
} }

View file

@ -137,7 +137,7 @@ using Lambda;
* *
* Some functionality is split into handler classes to help maintain my sanity. * Some functionality is split into handler classes to help maintain my sanity.
* *
* @author MasterEric * @author EliteMasterEric
*/ */
// @:nullSafety // @:nullSafety

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

@ -38,7 +38,7 @@ class AlbumRoll extends FlxSpriteGroup
var newAlbumArt:FlxAtlasSprite; var newAlbumArt:FlxAtlasSprite;
// var difficultyStars:DifficultyStars; var difficultyStars:DifficultyStars;
var _exitMovers:Null<FreeplayState.ExitMoverData>; var _exitMovers:Null<FreeplayState.ExitMoverData>;
var albumData:Album; var albumData:Album;
@ -65,9 +65,9 @@ class AlbumRoll extends FlxSpriteGroup
add(newAlbumArt); add(newAlbumArt);
// difficultyStars = new DifficultyStars(140, 39); difficultyStars = new DifficultyStars(140, 39);
// difficultyStars.stars.visible = false; difficultyStars.stars.visible = false;
// add(difficultyStars); add(difficultyStars);
} }
function onAlbumFinish(animName:String):Void function onAlbumFinish(animName:String):Void
@ -86,9 +86,14 @@ class AlbumRoll extends FlxSpriteGroup
{ {
if (albumId == null) if (albumId == null)
{ {
// difficultyStars.stars.visible = false; this.visible = false;
difficultyStars.stars.visible = false;
return; return;
} }
else
{
this.visible = true;
}
albumData = AlbumRegistry.instance.fetchEntry(albumId); albumData = AlbumRegistry.instance.fetchEntry(albumId);
@ -144,10 +149,10 @@ class AlbumRoll extends FlxSpriteGroup
newAlbumArt.visible = true; newAlbumArt.visible = true;
newAlbumArt.playAnimation(animNames.get('$albumId-active'), false, false, false); newAlbumArt.playAnimation(animNames.get('$albumId-active'), false, false, false);
// difficultyStars.stars.visible = false; difficultyStars.stars.visible = false;
new FlxTimer().start(0.75, function(_) { new FlxTimer().start(0.75, function(_) {
// showTitle(); // showTitle();
// showStars(); showStars();
}); });
} }
@ -156,16 +161,17 @@ class AlbumRoll extends FlxSpriteGroup
newAlbumArt.playAnimation(animNames.get('$albumId-trans'), false, false, false); newAlbumArt.playAnimation(animNames.get('$albumId-trans'), false, false, false);
} }
// public function setDifficultyStars(?difficulty:Int):Void public function setDifficultyStars(?difficulty:Int):Void
// { {
// if (difficulty == null) return; if (difficulty == null) return;
// difficultyStars.difficulty = difficulty; difficultyStars.difficulty = difficulty;
// } }
// /**
// * Make the album stars visible. /**
// */ * Make the album stars visible.
// public function showStars():Void */
// { public function showStars():Void
// difficultyStars.stars.visible = false; // true; {
// } difficultyStars.stars.visible = true; // true;
}
} }

View file

@ -0,0 +1,106 @@
package funkin.ui.freeplay;
import flixel.group.FlxSpriteGroup;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
import funkin.graphics.shaders.HSVShader;
class DifficultyStars extends FlxSpriteGroup
{
/**
* Internal handler var for difficulty... ranges from 0... to 15
* 0 is 1 star... 15 is 0 stars!
*/
var curDifficulty(default, set):Int = 0;
/**
* Range between 0 and 15
*/
public var difficulty(default, set):Int = 1;
public var stars:FlxAtlasSprite;
var flames:FreeplayFlames;
var hsvShader:HSVShader;
public function new(x:Float, y:Float)
{
super(x, y);
hsvShader = new HSVShader();
flames = new FreeplayFlames(0, 0);
add(flames);
stars = new FlxAtlasSprite(0, 0, Paths.animateAtlas("freeplay/freeplayStars"));
stars.anim.play("diff stars");
add(stars);
stars.shader = hsvShader;
for (memb in flames.members)
memb.shader = hsvShader;
}
override function update(elapsed:Float):Void
{
super.update(elapsed);
// "loops" the current animation
// for clarity, the animation file looks like
// frame : stars
// 0-99: 1 star
// 100-199: 2 stars
// ......
// 1300-1499: 15 stars
// 1500 : 0 stars
if (curDifficulty < 15 && stars.anim.curFrame >= (curDifficulty + 1) * 100)
{
stars.anim.play("diff stars", true, false, curDifficulty * 100);
}
}
function set_difficulty(value:Int):Int
{
difficulty = value;
if (difficulty <= 0)
{
difficulty = 0;
curDifficulty = 15;
}
else if (difficulty <= 15)
{
difficulty = value;
curDifficulty = difficulty - 1;
}
else
{
difficulty = 15;
curDifficulty = difficulty - 1;
}
if (difficulty > 10) flames.flameCount = difficulty - 10;
else
flames.flameCount = 0;
return difficulty;
}
function set_curDifficulty(value:Int):Int
{
curDifficulty = value;
if (curDifficulty == 15)
{
stars.anim.play("diff stars", true, false, 1500);
stars.anim.pause();
}
else
{
stars.anim.curFrame = Std.int(curDifficulty * 100);
stars.anim.play("diff stars", true, false, curDifficulty * 100);
}
return curDifficulty;
}
}

View file

@ -50,8 +50,19 @@ class FreeplayFlames extends FlxSpriteGroup
} }
} }
var timers:Array<FlxTimer> = [];
function set_flameCount(value:Int):Int function set_flameCount(value:Int):Int
{ {
// Stop all existing timers.
// This fixes a bug where quickly switching difficulties would show flames.
for (timer in timers)
{
timer.active = false;
timer.destroy();
timers.remove(timer);
}
this.flameCount = value; this.flameCount = value;
var visibleCount:Int = 0; var visibleCount:Int = 0;
for (i in 0...5) for (i in 0...5)
@ -62,10 +73,18 @@ class FreeplayFlames extends FlxSpriteGroup
{ {
if (!flame.visible) if (!flame.visible)
{ {
new FlxTimer().start(flameTimer * visibleCount, function(_) { var nextTimer:FlxTimer = new FlxTimer().start(flameTimer * visibleCount, function(currentTimer:FlxTimer) {
if (i >= this.flameCount)
{
trace('EARLY EXIT');
return;
}
timers.remove(currentTimer);
flame.animation.play("flame", true); flame.animation.play("flame", true);
flame.visible = true; flame.visible = true;
}); });
timers.push(nextTimer);
visibleCount++; visibleCount++;
} }
} }

View file

@ -124,8 +124,6 @@ class FreeplayState extends MusicBeatSubState
var curCapsule:SongMenuItem; var curCapsule:SongMenuItem;
var curPlaying:Bool = false; var curPlaying:Bool = false;
var displayedVariations:Array<String>;
var dj:DJBoyfriend; var dj:DJBoyfriend;
var ostName:FlxText; var ostName:FlxText;
@ -211,10 +209,6 @@ class FreeplayState extends MusicBeatSubState
// Add a null entry that represents the RANDOM option // Add a null entry that represents the RANDOM option
songs.push(null); songs.push(null);
// TODO: This makes custom variations disappear from Freeplay. Figure out a better solution later.
// Default character (BF) shows default and Erect variations. Pico shows only Pico variations.
displayedVariations = (currentCharacter == 'bf') ? [Constants.DEFAULT_VARIATION, 'erect'] : [currentCharacter];
// programmatically adds the songs via LevelRegistry and SongRegistry // programmatically adds the songs via LevelRegistry and SongRegistry
for (levelId in LevelRegistry.instance.listSortedLevelIds()) for (levelId in LevelRegistry.instance.listSortedLevelIds())
{ {
@ -222,7 +216,8 @@ class FreeplayState extends MusicBeatSubState
{ {
var song:Song = SongRegistry.instance.fetchEntry(songId); var song:Song = SongRegistry.instance.fetchEntry(songId);
// Only display songs which actually have available charts for the current character. // Only display songs which actually have available difficulties for the current character.
var displayedVariations = song.getVariationsByCharId(currentCharacter);
var availableDifficultiesForSong:Array<String> = song.listDifficulties(displayedVariations, false); var availableDifficultiesForSong:Array<String> = song.listDifficulties(displayedVariations, false);
if (availableDifficultiesForSong.length == 0) continue; if (availableDifficultiesForSong.length == 0) continue;
@ -554,10 +549,6 @@ class FreeplayState extends MusicBeatSubState
albumRoll.playIntro(); albumRoll.playIntro();
new FlxTimer().start(0.75, function(_) {
// albumRoll.showTitle();
});
FlxTween.tween(grpDifficulties, {x: 90}, 0.6, {ease: FlxEase.quartOut}); FlxTween.tween(grpDifficulties, {x: 90}, 0.6, {ease: FlxEase.quartOut});
diffSelLeft.visible = true; diffSelLeft.visible = true;
@ -1040,8 +1031,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,
{ {
@ -1065,8 +1056,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))
{ {
@ -1221,11 +1212,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)
{ {
@ -1242,6 +1246,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();
@ -1339,7 +1344,7 @@ class FreeplayState extends MusicBeatSubState
{ {
var songScore:SaveScoreData = Save.instance.getSongScore(grpCapsules.members[curSelected].songData.songId, currentDifficulty); var songScore:SaveScoreData = Save.instance.getSongScore(grpCapsules.members[curSelected].songData.songId, currentDifficulty);
intendedScore = songScore?.score ?? 0; intendedScore = songScore?.score ?? 0;
intendedCompletion = songScore?.accuracy ?? 0.0; intendedCompletion = songScore == null ? 0.0 : ((songScore.tallies.sick + songScore.tallies.good) / songScore.tallies.totalNotes);
rememberedDifficulty = currentDifficulty; rememberedDifficulty = currentDifficulty;
} }
else else
@ -1404,6 +1409,9 @@ class FreeplayState extends MusicBeatSubState
albumRoll.albumId = newAlbumId; albumRoll.albumId = newAlbumId;
albumRoll.skipIntro(); albumRoll.skipIntro();
} }
// Set difficulty star count.
albumRoll.setDifficultyStars(daSong?.difficultyRating);
} }
// Clears the cache of songs, frees up memory, they' ll have to be loaded in later tho function clearDaCache(actualSongTho:String) // Clears the cache of songs, frees up memory, they' ll have to be loaded in later tho function clearDaCache(actualSongTho:String)
@ -1570,7 +1578,7 @@ class FreeplayState extends MusicBeatSubState
{ {
var songScore:SaveScoreData = Save.instance.getSongScore(daSongCapsule.songData.songId, currentDifficulty); var songScore:SaveScoreData = Save.instance.getSongScore(daSongCapsule.songData.songId, currentDifficulty);
intendedScore = songScore?.score ?? 0; intendedScore = songScore?.score ?? 0;
intendedCompletion = songScore?.accuracy ?? 0.0; intendedCompletion = songScore == null ? 0.0 : ((songScore.tallies.sick + songScore.tallies.good) / songScore.tallies.totalNotes);
diffIdsCurrent = daSongCapsule.songData.songDifficulties; diffIdsCurrent = daSongCapsule.songData.songDifficulties;
rememberedSongId = daSongCapsule.songData.songId; rememberedSongId = daSongCapsule.songData.songId;
changeDiff(); changeDiff();
@ -1752,11 +1760,12 @@ class FreeplaySongData
public var songName(default, null):String = ''; public var songName(default, null):String = '';
public var songCharacter(default, null):String = ''; public var songCharacter(default, null):String = '';
public var songStartingBpm(default, null):Float = 0; public var songStartingBpm(default, null):Float = 0;
public var songRating(default, null):Int = 0; public var difficultyRating(default, null):Int = 0;
public var albumId(default, null):Null<String> = null; public var albumId(default, null):Null<String> = null;
public var currentDifficulty(default, set):String = Constants.DEFAULT_DIFFICULTY; public var currentDifficulty(default, set):String = Constants.DEFAULT_DIFFICULTY;
public var displayedVariations(default, null):Array<String> = [Constants.DEFAULT_VARIATION];
var displayedVariations:Array<String> = [Constants.DEFAULT_VARIATION];
function set_currentDifficulty(value:String):String function set_currentDifficulty(value:String):String
{ {
@ -1772,11 +1781,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);
@ -1787,7 +1817,7 @@ class FreeplaySongData
this.songStartingBpm = songDifficulty.getStartingBPM(); this.songStartingBpm = songDifficulty.getStartingBPM();
this.songName = songDifficulty.songName; this.songName = songDifficulty.songName;
this.songCharacter = songDifficulty.characters.opponent; this.songCharacter = songDifficulty.characters.opponent;
this.songRating = songDifficulty.difficultyRating; this.difficultyRating = songDifficulty.difficultyRating;
if (songDifficulty.album == null) if (songDifficulty.album == null)
{ {
FlxG.log.warn('No album for: ${songDifficulty.songName}'); FlxG.log.warn('No album for: ${songDifficulty.songName}');

View file

@ -439,8 +439,8 @@ class SongMenuItem extends FlxSpriteGroup
songText.text = songData?.songName ?? 'Random'; songText.text = songData?.songName ?? 'Random';
// Update capsule character. // Update capsule character.
if (songData?.songCharacter != null) setCharacter(songData.songCharacter); if (songData?.songCharacter != null) setCharacter(songData.songCharacter);
updateDifficultyRating(songData?.songRating ?? 0);
updateBPM(Std.int(songData?.songStartingBpm) ?? 0); updateBPM(Std.int(songData?.songStartingBpm) ?? 0);
updateDifficultyRating(songData?.difficultyRating ?? 0);
// Update opacity, offsets, etc. // Update opacity, offsets, etc.
updateSelected(); updateSelected();

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)
{ {
@ -355,8 +351,7 @@ class MainMenuState extends MusicBeatState
maxCombo: 0, maxCombo: 0,
totalNotesHit: 0, totalNotesHit: 0,
totalNotes: 0, totalNotes: 0,
}, }
accuracy: 0,
}); });
} }
#end #end

View file

@ -13,11 +13,10 @@ class LevelProp extends Bopper
// Only reset the prop if the asset path has changed. // Only reset the prop if the asset path has changed.
if (propData == null || value?.assetPath != propData?.assetPath) if (propData == null || value?.assetPath != propData?.assetPath)
{ {
this.visible = (value != null);
this.propData = value;
danceEvery = this.propData?.danceEvery ?? 0;
applyData(); applyData();
} }
this.visible = (value != null);
danceEvery = this.propData?.danceEvery ?? 0;
return this.propData; return this.propData;
} }

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

@ -57,8 +57,7 @@ class LoadingState extends MusicBeatSubState
funkay.scrollFactor.set(); funkay.scrollFactor.set();
funkay.screenCenter(); funkay.screenCenter();
loadBar = new FunkinSprite(0, FlxG.height - 20).makeSolidColor(FlxG.width, 10, 0xFFff16d2); loadBar = new FunkinSprite(0, FlxG.height - 20).makeSolidColor(0, 10, 0xFFff16d2);
loadBar.screenCenter(X);
add(loadBar); add(loadBar);
initSongsManifest().onComplete(function(lib) { initSongsManifest().onComplete(function(lib) {
@ -162,7 +161,16 @@ 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));
// this if-check prevents the setGraphicSize function
// from setting the width of the loadBar to the height of the loadBar
// this is a behaviour that is implemented in the setGraphicSize function
// if the width parameter is equal to 0
if (lerpWidth > 0)
{
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

@ -455,6 +455,17 @@ class Constants
public static final JUDGEMENT_BAD_COMBO_BREAK:Bool = true; public static final JUDGEMENT_BAD_COMBO_BREAK:Bool = true;
public static final JUDGEMENT_SHIT_COMBO_BREAK:Bool = true; public static final JUDGEMENT_SHIT_COMBO_BREAK:Bool = true;
// % Sick
public static final RANK_PERFECT_PLAT_THRESHOLD:Float = 1.0; // % Sick
public static final RANK_PERFECT_GOLD_THRESHOLD:Float = 0.85; // % Sick
// % Hit
public static final RANK_PERFECT_THRESHOLD:Float = 1.00;
public static final RANK_EXCELLENT_THRESHOLD:Float = 0.90;
public static final RANK_GREAT_THRESHOLD:Float = 0.75;
public static final RANK_GOOD_THRESHOLD:Float = 0.60;
// public static final RANK_SHIT_THRESHOLD:Float = 0.00;
/** /**
* FILE EXTENSIONS * FILE EXTENSIONS
*/ */

View file

@ -1,136 +0,0 @@
package funkin.util;
import funkin.util.tools.MapTools;
import haxe.DynamicAccess;
/**
* Utilities for working with anonymous structures.
*/
class StructureUtil
{
/**
* Merge two structures, with the second overwriting the first.
* Performs a SHALLOW clone, where child structures are not merged.
* @param a The base structure.
* @param b The new structure.
* @return The merged structure.
*/
public static function merge(a:Dynamic, b:Dynamic):Dynamic
{
var result:DynamicAccess<Dynamic> = Reflect.copy(a);
for (field in Reflect.fields(b))
{
result.set(field, Reflect.field(b, field));
}
return result;
}
public static function toMap(a:Dynamic):haxe.ds.Map<String, Dynamic>
{
var result:haxe.ds.Map<String, Dynamic> = [];
for (field in Reflect.fields(a))
{
result.set(field, Reflect.field(a, field));
}
return result;
}
public static function isMap(a:Dynamic):Bool
{
return Std.isOfType(a, haxe.Constraints.IMap);
}
public static function isObject(a:Dynamic):Bool
{
switch (Type.typeof(a))
{
case TObject:
return true;
default:
return false;
}
}
public static function isPrimitive(a:Dynamic):Bool
{
switch (Type.typeof(a))
{
case TInt | TFloat | TBool:
return true;
case TClass(c):
return false;
case TEnum(e):
return false;
case TObject:
return false;
case TFunction:
return false;
case TNull:
return true;
case TUnknown:
return false;
default:
return false;
}
}
/**
* Merge two structures, with the second overwriting the first.
* Performs a DEEP clone, where child structures are also merged recursively.
* @param a The base structure.
* @param b The new structure.
* @return The merged structure.
*/
public static function deepMerge(a:Dynamic, b:Dynamic):Dynamic
{
if (a == null) return b;
if (b == null) return null;
if (isPrimitive(a) && isPrimitive(b)) return b;
if (isMap(b))
{
if (isMap(a))
{
return MapTools.merge(a, b);
}
else
{
return StructureUtil.toMap(a).merge(b);
}
}
if (!Reflect.isObject(a) || !Reflect.isObject(b)) return b;
if (Std.isOfType(b, haxe.ds.StringMap))
{
if (Std.isOfType(a, haxe.ds.StringMap))
{
return MapTools.merge(a, b);
}
else
{
return StructureUtil.toMap(a).merge(b);
}
}
var result:DynamicAccess<Dynamic> = Reflect.copy(a);
for (field in Reflect.fields(b))
{
if (Reflect.isObject(b))
{
// Note that isObject also returns true for class instances,
// but we just assume that's not a problem here.
result.set(field, deepMerge(Reflect.field(result, field), Reflect.field(b, field)));
}
else
{
// If we're here, b[field] is a primitive.
result.set(field, Reflect.field(b, field));
}
}
return result;
}
}

View file

@ -32,6 +32,25 @@ class VersionUtil
} }
} }
public static function repairVersion(version:thx.semver.Version):thx.semver.Version
{
var versionData:thx.semver.Version.SemVer = version;
if (thx.Types.isAnonymousObject(versionData.version))
{
// This is bad! versionData.version should be an array!
versionData.version = [versionData.version[0], versionData.version[1], versionData.version[2]];
var fixedVersion:thx.semver.Version = versionData;
return fixedVersion;
}
else
{
// No need for repair.
return version;
}
}
/** /**
* Checks that a given verison number satisisfies a given version rule. * Checks that a given verison number satisisfies a given version rule.
* Version rule can be complex, e.g. "1.0.x" or ">=1.0.0,<1.1.0", or anything NPM supports. * Version rule can be complex, e.g. "1.0.x" or ">=1.0.0,<1.1.0", or anything NPM supports.

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

View file

@ -23,7 +23,7 @@ class InlineMacro
var fields:Array<haxe.macro.Expr.Field> = haxe.macro.Context.getBuildFields(); var fields:Array<haxe.macro.Expr.Field> = haxe.macro.Context.getBuildFields();
// Find the field with the given name. // Find the field with the given name.
var targetField:Null<haxe.macro.Expr.Field> = fields.find(function(f) return f.name == field var targetField:Null<haxe.macro.Expr.Field> = thx.Arrays.find(fields, function(f) return f.name == field
&& (MacroUtil.isFieldStatic(f) == isStatic)); && (MacroUtil.isFieldStatic(f) == isStatic));
// If the field was not found, throw an error. // If the field was not found, throw an error.

View file

@ -5,72 +5,6 @@ package funkin.util.tools;
*/ */
class ArrayTools class ArrayTools
{ {
/**
* Returns a copy of the array with all duplicate elements removed.
* @param array The array to remove duplicates from.
* @return A copy of the array with all duplicate elements removed.
*/
public static function unique<T>(array:Array<T>):Array<T>
{
var result:Array<T> = [];
for (element in array)
{
if (!result.contains(element))
{
result.push(element);
}
}
return result;
}
/**
* Returns a copy of the array with all `null` elements removed.
* @param array The array to remove `null` elements from.
* @return A copy of the array with all `null` elements removed.
*/
public static function nonNull<T>(array:Array<Null<T>>):Array<T>
{
var result:Array<T> = [];
for (element in array)
{
if (element != null)
{
result.push(element);
}
}
return result;
}
/**
* Return the first element of the array that satisfies the predicate, or null if none do.
* @param input The array to search
* @param predicate The predicate to call
* @return The result
*/
public static function find<T>(input:Array<T>, predicate:T->Bool):Null<T>
{
for (element in input)
{
if (predicate(element)) return element;
}
return null;
}
/**
* Return the index of the first element of the array that satisfies the predicate, or `-1` if none do.
* @param input The array to search
* @param predicate The predicate to call
* @return The index of the result
*/
public static function findIndex<T>(input:Array<T>, predicate:T->Bool):Int
{
for (index in 0...input.length)
{
if (predicate(input[index])) return index;
}
return -1;
}
/* /*
* Push an element to the array if it is not already present. * Push an element to the array if it is not already present.
* @param input The array to push to * @param input The array to push to

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

View file

@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<TextureAtlas imagePath="arrows.png">
<SubTexture name="staticLeft0001" x="0" y="0" width="17" height="17" />
<SubTexture name="staticDown0001" x="17" y="0" width="17" height="17" />
<SubTexture name="staticUp0001" x="34" y="0" width="17" height="17" />
<SubTexture name="staticRight0001" x="51" y="0" width="17" height="17" />
<SubTexture name="noteLeft0001" x="0" y="17" width="17" height="17" />
<SubTexture name="noteDown0001" x="17" y="17" width="17" height="17" />
<SubTexture name="noteUp0001" x="34" y="17" width="17" height="17" />
<SubTexture name="noteRight0001" x="51" y="17" width="17" height="17" />
<SubTexture name="pressedLeft0001" x="0" y="17" width="17" height="17" />
<SubTexture name="pressedDown0001" x="17" y="17" width="17" height="17" />
<SubTexture name="pressedUp0001" x="34" y="17" width="17" height="17" />
<SubTexture name="pressedRight0001" x="51" y="17" width="17" height="17" />
<SubTexture name="pressedLeft0002" x="0" y="34" width="17" height="17" />
<SubTexture name="pressedDown0002" x="17" y="34" width="17" height="17" />
<SubTexture name="pressedUp0002" x="34" y="34" width="17" height="17" />
<SubTexture name="pressedRight0002" x="51" y="34" width="17" height="17" />
<SubTexture name="confirmLeft0001" x="0" y="51" width="17" height="17" />
<SubTexture name="confirmDown0001" x="17" y="51" width="17" height="17" />
<SubTexture name="confirmUp0001" x="34" y="51" width="17" height="17" />
<SubTexture name="confirmRight0001" x="51" y="51" width="17" height="17" />
<SubTexture name="confirmLeft0002" x="0" y="68" width="17" height="17" />
<SubTexture name="confirmDown0002" x="17" y="68" width="17" height="17" />
<SubTexture name="confirmUp0002" x="34" y="68" width="17" height="17" />
<SubTexture name="confirmRight0002" x="51" y="68" width="17" height="17" />
</TextureAtlas>