1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2025-03-21 17:39:20 +00:00

Merge branch 'rewrite/master' into pursnake/character-scale

This commit is contained in:
Cameron Taylor 2024-06-06 19:47:48 -04:00 committed by GitHub
commit 02be65c383
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
48 changed files with 1868 additions and 484 deletions

4
.gitmodules vendored
View file

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

View file

@ -6,31 +6,52 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [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.
- 2 new Erect remixes, Eggnog and Satin Panties. Check them out from the Freeplay menu!
- Major visual improvements to the Results screen, with additional animations and audio based on your performance.
- Major visual improvements to the Freeplay screen, with song difficulty ratings and player rank displays.
- Freeplay now plays a preview of songs when you hover over them.
- Added a Charter field to the chart format, to allow for crediting the creator of a level's chart.
- You can see who charted a song from the Pause menu.
- Added a new Scroll Speed chart event to change the note speed mid-song (thanks )
### Changed
- Tweaked the charts for several songs:
- Monster
- Winter Horrorland
- Stress
- Lit Up
- Tutorial (increased the note speed slightly)
- Senpai (increased the note speed)
- Thorns (increased the note speed slightly)
- Favorite songs marked in Freeplay are now stored between sessions.
- In the event that the game cannot load your save data, it will now perform a backup before clearing it, so that we can try to repair it in the future.
- Custom note styles are now properly supported for songs; add new notestyles via JSON, then select it for use from the Chart Editor Metadata toolbox. (thanks Keoiki!)
- Improved logic for NoteHitScriptEvents, allowing you to view the hit diff and modify whether a note hit is a combo break (thanks nebulazorua!)
- 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 the game would silently fail to load saves on HTML5
- Fixed some bugs with the props on the Story Menu not bopping properly
- Additional fixes to the Loading bar on HTML5 (thanks lemz1!)
- Fixed several bugs with the TitleState, including missing music when returning from the Main Menu (thanks gamerbross!)
- Fixed a camera bug in the Main Menu (thanks richTrash21!)
- Fixed a bug where changing difficulties in Story mode wouldn't update the score (thanks sectorA!)
- Fixed a crash in Freeplay caused by a level referencing an invalid song (thanks gamerbross!)
- Fixed a bug where pressing the volume keys would stop the Toy commercial (thanks gamerbross!)
- Fixed a bug where the Chart Editor would crash when losing (thanks gamerbross!)
- Fixed a bug where the Chart Editor Playtest would crash when losing (thanks gamerbross!)
- Fixed a bug where hold notes would display improperly in the Chart Editor when downscroll was enabled for gameplay (thanks gamerbross!)
- Fixed a bug where hold notes would be positioned wrong on downscroll (thanks MaybeMaru!)
- Removed a large number of unused imports to optimize builds (thanks Ethan-makes-music!)
- Improved debug logging for unscripted stages (thanks gamerbross!)
- Made improvements to compiling documentation (thanks gedehari!)
- Fixed a crash on Linux caused by an old version of hxCodec (thanks Noobz4Life!)
- Optimized animation handling for characters (thanks richTrash21!)
- Additional bug fixes and optimizations.
## [0.3.3] - 2024-05-14
### Changed
- Cleaned up some code in `PlayAnimationSongEvent.hx` (thanks BurgerBalls!)
### Fixed
- Fix Web Loading Bar (thanks lemz1!)
- Fixes to the Loading bar on HTML5 (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!)
@ -38,11 +59,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- 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 for a game over easter egg so you don't accidentally exit it when viewing
- Fix an issue where the Freeplay menu never displays 100% clear
- Fix an issue where Weekend 1 Pico attempted to retrieve a missing asset.
- Fix an issue where duplicate keybinds would be stoed, potentially causing a crash
- 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
- Fix a crash on Freeplay found on AMD graphics cards
## [0.3.2] - 2024-05-03
### Added

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<project>
<project xmlns="http://lime.openfl.org/project/1.0.4" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://lime.openfl.org/project/1.0.4 http://lime.openfl.org/xsd/project-1.0.4.xsd">
<!-- _________________________ Application Settings _________________________ -->
<app title="Friday Night Funkin'" file="Funkin" packageName="com.funkin.fnf" package="com.funkin.fnf" main="Main" version="0.3.3" company="ninjamuffin99" />
<!--Switch Export with Unique ApplicationID and Icon-->
@ -28,7 +29,7 @@
<set name="BUILD_DIR" value="export/debug" if="debug" />
<set name="BUILD_DIR" value="export/release" unless="debug" />
<set name="BUILD_DIR" value="export/32bit" if="32bit" />
<classpath name="source" />
<source path="source" />
<assets path="assets/preload" rename="assets" exclude="*.ogg|*.wav" if="web" />
<assets path="assets/preload" rename="assets" exclude="*.mp3|*.wav" unless="web" />
<define name="PRELOAD_ALL" unless="web" />
@ -126,6 +127,7 @@
<haxelib name="hxCodec" if="desktop" unless="hl" /> <!-- Video playback -->
<haxelib name="funkin.vis"/>
<haxelib name="FlxPartialSound" /> <!-- Loading partial sound data -->
<haxelib name="json2object" /> <!-- JSON parsing -->
<haxelib name="thx.core" /> <!-- General utility library, "the lodash of Haxe" -->

2
art

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

2
assets

@ -1 +1 @@
Subproject commit 8fea0bf1fe07b6dd0efb8ecf46dc8091b0177007
Subproject commit 0062c05d559ae281ce39f8df3da6efb1f92ca808

View file

@ -79,7 +79,7 @@
{
"props": {
"ignoreExtern": true,
"format": "^[a-z][A-Z][A-Z0-9]*(_[A-Z0-9_]+)*$",
"format": "^[a-zA-Z0-9]+(?:_[a-zA-Z0-9]+)*$",
"tokens": ["INLINE", "NOTINLINE"]
},
"type": "ConstantName"

View file

@ -40,6 +40,13 @@
"ref": "17e0d59fdbc2b6283a5c0e4df41f1c7f27b71c49",
"url": "https://github.com/FunkinCrew/flxanimate"
},
{
"name": "FlxPartialSound",
"type": "git",
"dir": null,
"ref": "f986332ba5ab02abd386ce662578baf04904604a",
"url": "https://github.com/FunkinCrew/FlxPartialSound.git"
},
{
"name": "format",
"type": "haxelib",

View file

@ -219,7 +219,9 @@ class InitState extends FlxState
FlxG.switchState(() -> new funkin.play.ResultState(
{
storyMode: false,
title: "CUM SONG",
title: "Cum Song Erect by Kawai Sprite",
songId: "cum",
difficultyId: "nightmare",
isNewHighscore: true,
scoreData:
{
@ -227,7 +229,7 @@ class InitState extends FlxState
tallies:
{
sick: 130,
good: 25,
good: 60,
bad: 69,
shit: 69,
missed: 69,

View file

@ -123,9 +123,17 @@ class Paths
return 'songs:assets/songs/${song.toLowerCase()}/Voices$suffix.${Constants.EXT_SOUND}';
}
public static function inst(song:String, ?suffix:String = ''):String
/**
* Gets the path to an `Inst.mp3/ogg` song instrumental from songs:assets/songs/`song`/
* @param song name of the song to get instrumental for
* @param suffix any suffix to add to end of song name, used for `-erect` variants usually
* @param withExtension if it should return with the audio file extension `.mp3` or `.ogg`.
* @return String
*/
public static function inst(song:String, ?suffix:String = '', ?withExtension:Bool = true):String
{
return 'songs:assets/songs/${song.toLowerCase()}/Inst$suffix.${Constants.EXT_SOUND}';
var ext:String = withExtension ? '.${Constants.EXT_SOUND}' : '';
return 'songs:assets/songs/${song.toLowerCase()}/Inst$suffix$ext';
}
public static function image(key:String, ?library:String):String
@ -153,3 +161,11 @@ class Paths
return FlxAtlasFrames.fromSpriteSheetPacker(image(key, library), file('images/$key.txt', library));
}
}
enum abstract PathsFunction(String)
{
var MUSIC;
var INST;
var VOICES;
var SOUND;
}

View file

@ -11,6 +11,11 @@ import funkin.audio.waveform.WaveformDataParser;
import funkin.data.song.SongData.SongMusicData;
import funkin.data.song.SongRegistry;
import funkin.util.tools.ICloneable;
import funkin.util.flixel.sound.FlxPartialSound;
import funkin.Paths.PathsFunction;
import openfl.Assets;
import lime.app.Future;
import lime.app.Promise;
import openfl.media.SoundMixer;
#if (openfl >= "8.0.0")
@ -341,23 +346,76 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
FlxG.log.warn('Tried and failed to find music metadata for $key');
}
}
var music = FunkinSound.load(Paths.music('$key/$key'), params?.startingVolume ?? 1.0, params.loop ?? true, false, true);
if (music != null)
var pathsFunction = params.pathsFunction ?? MUSIC;
var suffix = params.suffix ?? '';
var pathToUse = switch (pathsFunction)
{
FlxG.sound.music = music;
case MUSIC: Paths.music('$key/$key');
case INST: Paths.inst('$key', suffix);
default: Paths.music('$key/$key');
}
// Prevent repeat update() and onFocus() calls.
FlxG.sound.list.remove(FlxG.sound.music);
var shouldLoadPartial = params.partialParams?.loadPartial ?? false;
return true;
// even if we arent' trying to partial load a song, we want to error out any songs in progress,
// so we don't get overlapping music if someone were to load a new song while a partial one is loading!
emptyPartialQueue();
if (shouldLoadPartial)
{
var music = FunkinSound.loadPartial(pathToUse, params.partialParams?.start ?? 0.0, params.partialParams?.end ?? 1.0, params?.startingVolume ?? 1.0,
params.loop ?? true, false, false, params.onComplete);
if (music != null)
{
partialQueue.push(music);
@:nullSafety(Off)
music.future.onComplete(function(partialMusic:Null<FunkinSound>) {
FlxG.sound.music = partialMusic;
FlxG.sound.list.remove(FlxG.sound.music);
if (FlxG.sound.music != null && params.onLoad != null) params.onLoad();
});
return true;
}
else
{
return false;
}
}
else
{
return false;
var music = FunkinSound.load(pathToUse, params?.startingVolume ?? 1.0, params.loop ?? true, false, true);
if (music != null)
{
FlxG.sound.music = music;
// Prevent repeat update() and onFocus() calls.
FlxG.sound.list.remove(FlxG.sound.music);
return true;
}
else
{
return false;
}
}
}
public static function emptyPartialQueue():Void
{
while (partialQueue.length > 0)
{
@:nullSafety(Off)
partialQueue.pop().error("Cancel loading partial sound");
}
}
static var partialQueue:Array<Promise<Null<FunkinSound>>> = [];
/**
* Creates a new `FunkinSound` object synchronously.
*
@ -414,6 +472,49 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
return sound;
}
/**
* Will load a section of a sound file, useful for Freeplay where we don't want to load all the bytes of a song
* @param path The path to the sound file
* @param start The start time of the sound file
* @param end The end time of the sound file
* @param volume Volume to start at
* @param looped Whether the sound file should loop
* @param autoDestroy Whether the sound file should be destroyed after it finishes playing
* @param autoPlay Whether the sound file should play immediately
* @param onComplete Callback when the sound finishes playing
* @param onLoad Callback when the sound finishes loading
* @return A FunkinSound object
*/
public static function loadPartial(path:String, start:Float = 0, end:Float = 1, volume:Float = 1.0, looped:Bool = false, autoDestroy:Bool = false,
autoPlay:Bool = true, ?onComplete:Void->Void, ?onLoad:Void->Void):Promise<Null<FunkinSound>>
{
var promise:lime.app.Promise<Null<FunkinSound>> = new lime.app.Promise<Null<FunkinSound>>();
// split the path and get only after first :
// we are bypassing the openfl/lime asset library fuss
path = Paths.stripLibrary(path);
var soundRequest = FlxPartialSound.partialLoadFromFile(path, start, end);
if (soundRequest == null)
{
promise.complete(null);
}
else
{
promise.future.onError(function(e) {
soundRequest.error("Sound loading was errored or cancelled");
});
soundRequest.future.onComplete(function(partialSound) {
var snd = FunkinSound.load(partialSound, volume, looped, autoDestroy, autoPlay, onComplete, onLoad);
promise.complete(snd);
});
}
return promise;
}
@:nullSafety(Off)
public override function destroy():Void
{
@ -474,6 +575,12 @@ typedef FunkinSoundPlayMusicParams =
*/
var ?startingVolume:Float;
/**
* The suffix of the music file to play. Usually for "-erect" tracks when loading an INST file
* @default ``
*/
var ?suffix:String;
/**
* Whether to override music if a different track is already playing.
* @default `false`
@ -497,4 +604,22 @@ typedef FunkinSoundPlayMusicParams =
* @default `true`
*/
var ?mapTimeChanges:Bool;
/**
* Which Paths function to use to load a song
* @default `MUSIC`
*/
var ?pathsFunction:PathsFunction;
var ?partialParams:PartialSoundParams;
var ?onComplete:Void->Void;
var ?onLoad:Void->Void;
}
typedef PartialSoundParams =
{
var loadPartial:Bool;
var start:Float;
var end:Float;
}

View file

@ -5,6 +5,10 @@ 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/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.2.3]
### Added
- Added `charter` field to denote authorship of a chart.
## [2.2.2]
### Added
- Added `playData.previewStart` and `playData.previewEnd` fields to specify when in the song should the song's audio should be played as a preview in Freeplay.

View file

@ -30,6 +30,9 @@ class SongMetadata implements ICloneable<SongMetadata>
@:default("Unknown")
public var artist:String;
@:optional
public var charter:Null<String> = null;
@:optional
@:default(96)
public var divisions:Null<Int>; // Optional field
@ -53,6 +56,8 @@ class SongMetadata implements ICloneable<SongMetadata>
@:default(funkin.data.song.SongRegistry.DEFAULT_GENERATEDBY)
public var generatedBy:String;
@:optional
@:default(funkin.data.song.SongData.SongTimeFormat.MILLISECONDS)
public var timeFormat:SongTimeFormat;
public var timeChanges:Array<SongTimeChange>;
@ -112,14 +117,23 @@ class SongMetadata implements ICloneable<SongMetadata>
*/
public function serialize(pretty:Bool = true):String
{
// Update generatedBy and version before writing.
updateVersionToLatest();
var ignoreNullOptionals = true;
var writer = new json2object.JsonWriter<SongMetadata>(ignoreNullOptionals);
// I believe @:jignored should be iggnored by the writer?
// I believe @:jignored should be ignored by the writer?
// var output = this.clone();
// output.variation = null; // Not sure how to make a field optional on the reader and ignored on the writer.
return writer.write(this, pretty ? ' ' : null);
}
public function updateVersionToLatest():Void
{
this.version = SongRegistry.SONG_METADATA_VERSION;
this.generatedBy = SongRegistry.DEFAULT_GENERATEDBY;
}
/**
* Produces a string representation suitable for debugging.
*/
@ -368,6 +382,12 @@ class SongMusicData implements ICloneable<SongMusicData>
this.variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
}
public function updateVersionToLatest():Void
{
this.version = SongRegistry.SONG_MUSIC_DATA_VERSION;
this.generatedBy = SongRegistry.DEFAULT_GENERATEDBY;
}
public function clone():SongMusicData
{
var result:SongMusicData = new SongMusicData(this.songName, this.artist, this.variation);
@ -600,11 +620,20 @@ class SongChartData implements ICloneable<SongChartData>
*/
public function serialize(pretty:Bool = true):String
{
// Update generatedBy and version before writing.
updateVersionToLatest();
var ignoreNullOptionals = true;
var writer = new json2object.JsonWriter<SongChartData>(ignoreNullOptionals);
return writer.write(this, pretty ? ' ' : null);
}
public function updateVersionToLatest():Void
{
this.version = SongRegistry.SONG_CHART_DATA_VERSION;
this.generatedBy = SongRegistry.DEFAULT_GENERATEDBY;
}
public function clone():SongChartData
{
// We have to manually perform the deep clone here because Map.deepClone() doesn't work.

View file

@ -20,7 +20,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
* Handle breaking changes by incrementing this value
* and adding migration to the `migrateStageData()` function.
*/
public static final SONG_METADATA_VERSION:thx.semver.Version = "2.2.2";
public static final SONG_METADATA_VERSION:thx.semver.Version = "2.2.3";
public static final SONG_METADATA_VERSION_RULE:thx.semver.VersionRule = "2.2.x";

View file

@ -61,10 +61,18 @@ class ChartManifestData
*/
public function serialize(pretty:Bool = true):String
{
// Update generatedBy and version before writing.
updateVersionToLatest();
var writer = new json2object.JsonWriter<ChartManifestData>();
return writer.write(this, pretty ? ' ' : null);
}
public function updateVersionToLatest():Void
{
this.version = CHART_MANIFEST_DATA_VERSION;
}
public static function deserialize(contents:String):Null<ChartManifestData>
{
var parser = new json2object.JsonParser<ChartManifestData>();

View file

@ -36,7 +36,7 @@ class FNFLegacyImporter
{
trace('Migrating song metadata from FNF Legacy.');
var songMetadata:SongMetadata = new SongMetadata('Import', 'Kawai Sprite', 'default');
var songMetadata:SongMetadata = new SongMetadata('Import', Constants.DEFAULT_ARTIST, 'default');
var hadError:Bool = false;
@ -65,7 +65,7 @@ class FNFLegacyImporter
songMetadata.timeChanges = rebuildTimeChanges(songData);
songMetadata.playData.characters = new SongCharacterData(songData?.song?.player1 ?? 'bf', 'gf', songData?.song?.player2 ?? 'dad', 'mom');
songMetadata.playData.characters = new SongCharacterData(songData?.song?.player1 ?? 'bf', 'gf', songData?.song?.player2 ?? 'dad');
return songMetadata;
}

View file

@ -58,9 +58,17 @@ class StageData
*/
public function serialize(pretty:Bool = true):String
{
// Update generatedBy and version before writing.
updateVersionToLatest();
var writer = new json2object.JsonWriter<StageData>();
return writer.write(this, pretty ? ' ' : null);
}
public function updateVersionToLatest():Void
{
this.version = StageRegistry.STAGE_DATA_VERSION;
}
}
typedef StageDataCharacters =

View file

@ -101,6 +101,10 @@ class PauseSubState extends MusicBeatSubState
*/
static final MUSIC_FINAL_VOLUME:Float = 0.75;
static final CHARTER_FADE_DELAY:Float = 15.0;
static final CHARTER_FADE_DURATION:Float = 0.75;
/**
* Defines which pause music to use.
*/
@ -163,6 +167,12 @@ class PauseSubState extends MusicBeatSubState
*/
var metadataDeaths:FlxText;
/**
* A text object which displays the current song's artist.
* Fades to the charter after a period before fading back.
*/
var metadataArtist:FlxText;
/**
* The actual text objects for the menu entries.
*/
@ -203,6 +213,8 @@ class PauseSubState extends MusicBeatSubState
regenerateMenu();
transitionIn();
startCharterTimer();
}
/**
@ -222,6 +234,8 @@ class PauseSubState extends MusicBeatSubState
public override function destroy():Void
{
super.destroy();
charterFadeTween.cancel();
charterFadeTween = null;
pauseMusic.stop();
}
@ -270,16 +284,25 @@ class PauseSubState extends MusicBeatSubState
metadata.scrollFactor.set(0, 0);
add(metadata);
var metadataSong:FlxText = new FlxText(20, 15, FlxG.width - 40, 'Song Name - Artist');
var metadataSong:FlxText = new FlxText(20, 15, FlxG.width - 40, 'Song Name');
metadataSong.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT);
if (PlayState.instance?.currentChart != null)
{
metadataSong.text = '${PlayState.instance.currentChart.songName} - ${PlayState.instance.currentChart.songArtist}';
metadataSong.text = '${PlayState.instance.currentChart.songName}';
}
metadataSong.scrollFactor.set(0, 0);
metadata.add(metadataSong);
var metadataDifficulty:FlxText = new FlxText(20, 15 + 32, FlxG.width - 40, 'Difficulty: ');
metadataArtist = new FlxText(20, metadataSong.y + 32, FlxG.width - 40, 'Artist: ${Constants.DEFAULT_ARTIST}');
metadataArtist.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT);
if (PlayState.instance?.currentChart != null)
{
metadataArtist.text = 'Artist: ${PlayState.instance.currentChart.songArtist}';
}
metadataArtist.scrollFactor.set(0, 0);
metadata.add(metadataArtist);
var metadataDifficulty:FlxText = new FlxText(20, metadataArtist.y + 32, FlxG.width - 40, 'Difficulty: ');
metadataDifficulty.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT);
if (PlayState.instance?.currentDifficulty != null)
{
@ -288,12 +311,12 @@ class PauseSubState extends MusicBeatSubState
metadataDifficulty.scrollFactor.set(0, 0);
metadata.add(metadataDifficulty);
metadataDeaths = new FlxText(20, 15 + 64, FlxG.width - 40, '${PlayState.instance?.deathCounter} Blue Balls');
metadataDeaths = new FlxText(20, metadataDifficulty.y + 32, FlxG.width - 40, '${PlayState.instance?.deathCounter} Blue Balls');
metadataDeaths.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT);
metadataDeaths.scrollFactor.set(0, 0);
metadata.add(metadataDeaths);
metadataPractice = new FlxText(20, 15 + 96, FlxG.width - 40, 'PRACTICE MODE');
metadataPractice = new FlxText(20, metadataDeaths.y + 32, FlxG.width - 40, 'PRACTICE MODE');
metadataPractice.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT);
metadataPractice.visible = PlayState.instance?.isPracticeMode ?? false;
metadataPractice.scrollFactor.set(0, 0);
@ -302,6 +325,62 @@ class PauseSubState extends MusicBeatSubState
updateMetadataText();
}
var charterFadeTween:Null<FlxTween> = null;
function startCharterTimer():Void
{
charterFadeTween = FlxTween.tween(metadataArtist, {alpha: 0.0}, CHARTER_FADE_DURATION,
{
startDelay: CHARTER_FADE_DELAY,
ease: FlxEase.quartOut,
onComplete: (_) -> {
if (PlayState.instance?.currentChart != null)
{
metadataArtist.text = 'Charter: ${PlayState.instance.currentChart.charter ?? 'Unknown'}';
}
else
{
metadataArtist.text = 'Charter: ${Constants.DEFAULT_CHARTER}';
}
FlxTween.tween(metadataArtist, {alpha: 1.0}, CHARTER_FADE_DURATION,
{
ease: FlxEase.quartOut,
onComplete: (_) -> {
startArtistTimer();
}
});
}
});
}
function startArtistTimer():Void
{
charterFadeTween = FlxTween.tween(metadataArtist, {alpha: 0.0}, CHARTER_FADE_DURATION,
{
startDelay: CHARTER_FADE_DELAY,
ease: FlxEase.quartOut,
onComplete: (_) -> {
if (PlayState.instance?.currentChart != null)
{
metadataArtist.text = 'Artist: ${PlayState.instance.currentChart.songArtist}';
}
else
{
metadataArtist.text = 'Artist: ${Constants.DEFAULT_ARTIST}';
}
FlxTween.tween(metadataArtist, {alpha: 1.0}, CHARTER_FADE_DURATION,
{
ease: FlxEase.quartOut,
onComplete: (_) -> {
startCharterTimer();
}
});
}
});
}
/**
* Perform additional animations to transition the pause menu in when it is first displayed.
*/

View file

@ -2317,8 +2317,6 @@ class PlayState extends MusicBeatSubState
var notesInRange:Array<NoteSprite> = playerStrumline.getNotesMayHit();
var holdNotesInRange:Array<SustainTrail> = playerStrumline.getHoldNotesHitOrMissed();
// If there are notes in range, pressing a key will cause a ghost miss.
var notesByDirection:Array<Array<NoteSprite>> = [[], [], [], []];
for (note in notesInRange)
@ -2340,17 +2338,27 @@ class PlayState extends MusicBeatSubState
// Play the strumline animation.
playerStrumline.playPress(input.noteDirection);
trace('PENALTY Score: ${songScore}');
}
else if (Constants.GHOST_TAPPING && (holdNotesInRange.length + notesInRange.length > 0) && notesInDirection.length == 0)
else if (Constants.GHOST_TAPPING && (!playerStrumline.mayGhostTap()) && notesInDirection.length == 0)
{
// Pressed a wrong key with no notes nearby AND with notes in a different direction available.
// Pressed a wrong key with notes visible on-screen.
// Perform a ghost miss (anti-spam).
ghostNoteMiss(input.noteDirection, notesInRange.length > 0);
// Play the strumline animation.
playerStrumline.playPress(input.noteDirection);
trace('PENALTY Score: ${songScore}');
}
else if (notesInDirection.length > 0)
else if (notesInDirection.length == 0)
{
// Press a key with no penalty.
// Play the strumline animation.
playerStrumline.playPress(input.noteDirection);
trace('NO PENALTY Score: ${songScore}');
}
else
{
// Choose the first note, deprioritizing low priority notes.
var targetNote:Null<NoteSprite> = notesInDirection.find((note) -> !note.lowPriority);
@ -2360,17 +2368,13 @@ class PlayState extends MusicBeatSubState
// Judge and hit the note.
trace('Hit note! ${targetNote.noteData}');
goodNoteHit(targetNote, input);
trace('Score: ${songScore}');
notesInDirection.remove(targetNote);
// Play the strumline animation.
playerStrumline.playConfirm(input.noteDirection);
}
else
{
// Play the strumline animation.
playerStrumline.playPress(input.noteDirection);
}
}
while (inputReleaseQueue.length > 0)
@ -3118,9 +3122,10 @@ class PlayState extends MusicBeatSubState
var res:ResultState = new ResultState(
{
storyMode: PlayStatePlaylist.isStoryMode,
songId: currentChart.song.id,
difficultyId: currentDifficulty,
title: PlayStatePlaylist.isStoryMode ? ('${PlayStatePlaylist.campaignTitle}') : ('${currentChart.songName} by ${currentChart.songArtist}'),
prevScoreData: prevScoreData,
difficultyId: currentDifficulty,
scoreData:
{
score: PlayStatePlaylist.isStoryMode ? PlayStatePlaylist.campaignScore : songScore,
@ -3139,7 +3144,7 @@ class PlayState extends MusicBeatSubState
},
isNewHighscore: isNewHighscore
});
res.camera = camHUD;
this.persistentDraw = false;
openSubState(res);
}

View file

@ -2,11 +2,16 @@ package funkin.play;
import flixel.FlxSprite;
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
import flixel.tweens.FlxTween;
import flixel.util.FlxTimer;
import flixel.tweens.FlxEase;
class ResultScore extends FlxTypedSpriteGroup<ScoreNum>
{
public var scoreShit(default, set):Int = 0;
public var scoreStart:Int = 0;
function set_scoreShit(val):Int
{
if (group == null || group.members == null) return val;
@ -16,7 +21,8 @@ class ResultScore extends FlxTypedSpriteGroup<ScoreNum>
while (dumbNumb > 0)
{
group.members[loopNum].digit = dumbNumb % 10;
scoreStart += 1;
group.members[loopNum].finalDigit = dumbNumb % 10;
// var funnyNum = group.members[loopNum];
// prevNum = group.members[loopNum + 1];
@ -44,9 +50,15 @@ class ResultScore extends FlxTypedSpriteGroup<ScoreNum>
public function animateNumbers():Void
{
for (i in group.members)
for (i in group.members.length-scoreStart...group.members.length)
{
i.playAnim();
// if(i.finalDigit == 10) continue;
new FlxTimer().start((i-1)/24, _ -> {
group.members[i].finalDelay = scoreStart - (i-1);
group.members[i].playAnim();
group.members[i].shuffle();
});
}
}
@ -71,12 +83,26 @@ class ResultScore extends FlxTypedSpriteGroup<ScoreNum>
class ScoreNum extends FlxSprite
{
public var digit(default, set):Int = 10;
public var finalDigit(default, set):Int = 10;
public var glow:Bool = true;
function set_finalDigit(val):Int
{
animation.play('GONE', true, false, 0);
return finalDigit = val;
}
function set_digit(val):Int
{
if (val >= 0 && animation.curAnim != null && animation.curAnim.name != numToString[val])
{
animation.play(numToString[val], true, false, 0);
if(glow){
animation.play(numToString[val], true, false, 0);
glow = false;
}else{
animation.play(numToString[val], true, false, 4);
}
updateHitbox();
switch (val)
@ -107,6 +133,10 @@ class ScoreNum extends FlxSprite
animation.play(numToString[digit], true, false, 0);
}
public var shuffleTimer:FlxTimer;
public var finalTween:FlxTween;
public var finalDelay:Float = 0;
public var baseY:Float = 0;
public var baseX:Float = 0;
@ -114,6 +144,47 @@ class ScoreNum extends FlxSprite
"ZERO", "ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN", "EIGHT", "NINE", "DISABLED"
];
function finishShuffleTween():Void{
var tweenFunction = function(x) {
var digitRounded = Math.floor(x);
//if(digitRounded == finalDigit) glow = true;
digit = digitRounded;
};
finalTween = FlxTween.num(0.0, finalDigit, 23/24, {
ease: FlxEase.quadOut,
onComplete: function (input) {
new FlxTimer().start((finalDelay)/24, _ -> {
animation.play(animation.curAnim.name, true, false, 0);
});
// fuck
}
}, tweenFunction);
}
function shuffleProgress(shuffleTimer:FlxTimer):Void
{
var tempDigit:Int = digit;
tempDigit += 1;
if(tempDigit > 9) tempDigit = 0;
if(tempDigit < 0) tempDigit = 0;
digit = tempDigit;
if (shuffleTimer.loops > 0 && shuffleTimer.loopsLeft == 0)
{
//digit = finalDigit;
finishShuffleTween();
}
}
public function shuffle():Void{
var duration:Float = 41/24;
var interval:Float = 1/24;
shuffleTimer = new FlxTimer().start(interval, shuffleProgress, Std.int(duration / interval));
}
public function new(x:Float, y:Float)
{
super(x, y);
@ -130,6 +201,7 @@ class ScoreNum extends FlxSprite
}
animation.addByPrefix('DISABLED', 'DISABLED', 24, false);
animation.addByPrefix('GONE', 'GONE', 24, false);
this.digit = 10;

View file

@ -5,6 +5,7 @@ import funkin.ui.story.StoryMenuState;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
import flixel.FlxSprite;
import funkin.graphics.FunkinSprite;
import flixel.effects.FlxFlicker;
import flixel.graphics.frames.FlxBitmapFont;
import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.math.FlxPoint;
@ -15,12 +16,15 @@ import funkin.ui.freeplay.FreeplayScore;
import flixel.text.FlxText;
import flixel.util.FlxColor;
import flixel.tweens.FlxEase;
import funkin.graphics.FunkinCamera;
import funkin.ui.freeplay.FreeplayState;
import flixel.tweens.FlxTween;
import flixel.addons.display.FlxBackdrop;
import funkin.audio.FunkinSound;
import flixel.util.FlxGradient;
import flixel.util.FlxTimer;
import funkin.save.Save;
import funkin.play.scoring.Scoring;
import funkin.save.Save.SaveScoreData;
import funkin.graphics.shaders.LeftMaskShader;
import funkin.play.components.TallyCounter;
@ -34,7 +38,7 @@ class ResultState extends MusicBeatSubState
{
final params:ResultsStateParams;
final rank:ResultRank;
final rank:ScoringRank;
final songName:FlxBitmapText;
final difficulty:FlxSprite;
final clearPercentSmall:ClearPercentCounter;
@ -52,20 +56,30 @@ class ResultState extends MusicBeatSubState
final score:ResultScore;
var bfPerfect:Null<FlxAtlasSprite> = null;
var heartsPerfect:Null<FlxAtlasSprite> = null;
var bfExcellent:Null<FlxAtlasSprite> = null;
var bfGreat:Null<FlxAtlasSprite> = null;
var gfGreat:Null<FlxAtlasSprite> = null;
var bfGood:Null<FlxSprite> = null;
var gfGood:Null<FlxSprite> = null;
var bfShit:Null<FlxAtlasSprite> = null;
var rankBg:FunkinSprite;
final cameraBG:FunkinCamera;
final cameraScroll:FunkinCamera;
final cameraEverything:FunkinCamera;
public function new(params:ResultsStateParams)
{
super();
this.params = params;
rank = calculateRank(params);
// rank = SHIT;
rank = Scoring.calculateRank(params.scoreData) ?? SHIT;
cameraBG = new FunkinCamera('resultsBG', 0, 0, FlxG.width, FlxG.height);
cameraScroll = new FunkinCamera('resultsScroll', 0, 0, FlxG.width, FlxG.height);
cameraEverything = new FunkinCamera('resultsEverything', 0, 0, FlxG.width, FlxG.height);
// We build a lot of this stuff in the constructor, then place it in create().
// This prevents having to do `null` checks everywhere.
@ -84,39 +98,59 @@ class ResultState extends MusicBeatSubState
clearPercentSmall.zIndex = 1000;
clearPercentSmall.visible = false;
bgFlash = FlxGradient.createGradientFlxSprite(FlxG.width, FlxG.height, [0xFFFFEB69, 0xFFFFE66A], 90);
bgFlash = FlxGradient.createGradientFlxSprite(FlxG.width, FlxG.height, [0xFFFFF1A6, 0xFFFFF1BE], 90);
resultsAnim = FunkinSprite.createSparrow(-200, -10, "resultScreen/results");
ratingsPopin = FunkinSprite.createSparrow(-150, 120, "resultScreen/ratingsPopin");
ratingsPopin = FunkinSprite.createSparrow(-135, 135, "resultScreen/ratingsPopin");
scorePopin = FunkinSprite.createSparrow(-180, 520, "resultScreen/scorePopin");
scorePopin = FunkinSprite.createSparrow(-180, 515, "resultScreen/scorePopin");
highscoreNew = new FlxSprite(310, 570);
highscoreNew = new FlxSprite(44, 557);
score = new ResultScore(35, 305, 10, params.scoreData.score);
rankBg = new FunkinSprite(0, 0);
}
override function create():Void
{
if (FlxG.sound.music != null) FlxG.sound.music.stop();
// We need multiple cameras so we can put one at an angle.
cameraScroll.angle = -3.8;
cameraBG.bgColor = FlxColor.MAGENTA;
cameraScroll.bgColor = FlxColor.TRANSPARENT;
cameraEverything.bgColor = FlxColor.TRANSPARENT;
FlxG.cameras.add(cameraBG, false);
FlxG.cameras.add(cameraScroll, false);
FlxG.cameras.add(cameraEverything, false);
FlxG.cameras.setDefaultDrawTarget(cameraEverything, true);
this.camera = cameraEverything;
// 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;
bg.cameras = [cameraBG];
add(bg);
bgFlash.scrollFactor.set();
bgFlash.visible = false;
bgFlash.zIndex = 20;
// bgFlash.cameras = [cameraBG];
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, _ -> {
new FlxTimer().start(8 / 24, _ -> {
soundSystem.animation.play("idle");
soundSystem.visible = true;
});
@ -126,7 +160,21 @@ class ResultState extends MusicBeatSubState
switch (rank)
{
case PERFECT | PERFECT_GOLD:
bfPerfect = new FlxAtlasSprite(370, -180, Paths.animateAtlas("resultScreen/results-bf/resultsPERFECT", "shared"));
heartsPerfect = new FlxAtlasSprite(1342, 370, Paths.animateAtlas("resultScreen/results-bf/resultsPERFECT/hearts", "shared"));
heartsPerfect.visible = false;
heartsPerfect.zIndex = 501;
add(heartsPerfect);
heartsPerfect.anim.onComplete = () -> {
if (heartsPerfect != null)
{
// bfPerfect.anim.curFrame = 137;
heartsPerfect.anim.curFrame = 43;
heartsPerfect.anim.play(); // unpauses this anim, since it's on PlayOnce!
}
};
bfPerfect = new FlxAtlasSprite(1342, 370, Paths.animateAtlas("resultScreen/results-bf/resultsPERFECT", "shared"));
bfPerfect.visible = false;
bfPerfect.zIndex = 500;
add(bfPerfect);
@ -134,36 +182,56 @@ class ResultState extends MusicBeatSubState
bfPerfect.anim.onComplete = () -> {
if (bfPerfect != null)
{
// bfPerfect.anim.curFrame = 137;
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 = new FlxAtlasSprite(1329, 429, Paths.animateAtlas("resultScreen/results-bf/resultsEXCELLENT", "shared"));
bfExcellent.visible = false;
bfExcellent.zIndex = 500;
add(bfExcellent);
bfExcellent.onAnimationFinish.add((animName) -> {
bfExcellent.anim.onComplete = () -> {
if (bfExcellent != null)
{
bfExcellent.playAnimation('Loop Start');
bfExcellent.anim.curFrame = 28;
bfExcellent.anim.play(); // unpauses this anim, since it's on PlayOnce!
}
});
};
case GREAT:
bfGreat = new FlxAtlasSprite(640, 200, Paths.animateAtlas("resultScreen/results-bf/resultsGREAT", "shared"));
gfGreat = new FlxAtlasSprite(802, 331, Paths.animateAtlas("resultScreen/results-bf/resultsGREAT/gf", "shared"));
gfGreat.visible = false;
gfGreat.zIndex = 499;
add(gfGreat);
gfGreat.scale.set(0.93, 0.93);
gfGreat.anim.onComplete = () -> {
if (gfGreat != null)
{
gfGreat.anim.curFrame = 9;
gfGreat.anim.play(); // unpauses this anim, since it's on PlayOnce!
}
};
bfGreat = new FlxAtlasSprite(929, 363, Paths.animateAtlas("resultScreen/results-bf/resultsGREAT/bf", "shared"));
bfGreat.visible = false;
bfGreat.zIndex = 500;
add(bfGreat);
bfGreat.onAnimationFinish.add((animName) -> {
bfGreat.scale.set(0.93, 0.93);
bfGreat.anim.onComplete = () -> {
if (bfGreat != null)
{
bfGreat.playAnimation('Loop Start');
bfGreat.anim.curFrame = 15;
bfGreat.anim.play(); // unpauses this anim, since it's on PlayOnce!
}
});
};
case GOOD:
gfGood = FunkinSprite.createSparrow(625, 325, 'resultScreen/results-bf/resultsGOOD/resultGirlfriendGOOD');
@ -203,7 +271,7 @@ class ResultState extends MusicBeatSubState
});
}
var diffSpr:String = 'dif${params?.difficultyId ?? 'Normal'}';
var diffSpr:String = 'diff_${params?.difficultyId ?? 'Normal'}';
difficulty.loadGraphic(Paths.image("resultScreen/" + diffSpr));
add(difficulty);
@ -223,7 +291,7 @@ class ResultState extends MusicBeatSubState
var blackTopBar:FlxSprite = new FlxSprite().loadGraphic(Paths.image("resultScreen/topBarBlack"));
blackTopBar.y = -blackTopBar.height;
FlxTween.tween(blackTopBar, {y: 0}, 0.4, {ease: FlxEase.quartOut});
FlxTween.tween(blackTopBar, {y: 0}, 7 / 24, {ease: FlxEase.quartOut, startDelay: 3 / 24});
blackTopBar.zIndex = 1010;
add(blackTopBar);
@ -231,7 +299,7 @@ class ResultState extends MusicBeatSubState
resultsAnim.visible = false;
resultsAnim.zIndex = 1200;
add(resultsAnim);
new FlxTimer().start(0.3, _ -> {
new FlxTimer().start(6 / 24, _ -> {
resultsAnim.visible = true;
resultsAnim.animation.play("result");
});
@ -240,7 +308,7 @@ class ResultState extends MusicBeatSubState
ratingsPopin.visible = false;
ratingsPopin.zIndex = 1200;
add(ratingsPopin);
new FlxTimer().start(1.0, _ -> {
new FlxTimer().start(21 / 24, _ -> {
ratingsPopin.visible = true;
ratingsPopin.animation.play("idle");
});
@ -249,23 +317,47 @@ class ResultState extends MusicBeatSubState
scorePopin.visible = false;
scorePopin.zIndex = 1200;
add(scorePopin);
new FlxTimer().start(1.0, _ -> {
new FlxTimer().start(36 / 24, _ -> {
scorePopin.visible = true;
scorePopin.animation.play("score");
scorePopin.animation.finishCallback = anim -> {
score.visible = true;
score.animateNumbers();
};
scorePopin.animation.finishCallback = anim -> {};
});
new FlxTimer().start(37 / 24, _ -> {
score.visible = true;
score.animateNumbers();
startRankTallySequence();
});
new FlxTimer().start(rank.getBFDelay(), _ -> {
afterRankTallySequence();
});
new FlxTimer().start(rank.getFlashDelay(), _ -> {
displayRankText();
});
highscoreNew.frames = Paths.getSparrowAtlas("resultScreen/highscoreNew");
highscoreNew.animation.addByPrefix("new", "NEW HIGHSCORE", 24);
highscoreNew.animation.addByPrefix("new", "highscoreAnim0", 24, false);
highscoreNew.visible = false;
highscoreNew.setGraphicSize(Std.int(highscoreNew.width * 0.8));
// highscoreNew.setGraphicSize(Std.int(highscoreNew.width * 0.8));
highscoreNew.updateHitbox();
highscoreNew.zIndex = 1200;
add(highscoreNew);
new FlxTimer().start(rank.getHighscoreDelay(), _ -> {
if (params.isNewHighscore ?? false)
{
highscoreNew.visible = true;
highscoreNew.animation.play("new");
highscoreNew.animation.finishCallback = _ -> highscoreNew.animation.play("new", true, false, 16);
}
else
{
highscoreNew.visible = false;
}
});
var hStuf:Int = 50;
var ratingGrp:FlxTypedGroup<TallyCounter> = new FlxTypedGroup<TallyCounter>();
@ -283,7 +375,10 @@ class ResultState extends MusicBeatSubState
ratingGrp.add(maxCombo);
hStuf += 2;
var extraYOffset:Float = 5;
var extraYOffset:Float = 7;
hStuf += 2;
var tallySick:TallyCounter = new TallyCounter(230, (hStuf * 5) + extraYOffset, params.scoreData.tallies.sick, 0xFF89E59E);
ratingGrp.add(tallySick);
@ -312,20 +407,49 @@ class ResultState extends MusicBeatSubState
});
}
ratingsPopin.animation.finishCallback = anim -> {
startRankTallySequence();
// 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;
// }
if (params.isNewHighscore ?? false)
new FlxTimer().start(rank.getMusicDelay(), _ -> {
if (rank.hasMusicIntro())
{
highscoreNew.visible = true;
highscoreNew.animation.play("new");
FlxTween.tween(highscoreNew, {y: highscoreNew.y + 10}, 0.8, {ease: FlxEase.quartOut});
// Play the intro music.
var introMusic:String = Paths.music(rank.getMusicPath() + '/' + rank.getMusicPath() + '-intro');
FunkinSound.load(introMusic, 1.0, false, true, true, () -> {
FunkinSound.playMusic(rank.getMusicPath(),
{
startingVolume: 1.0,
overrideExisting: true,
restartTrack: true,
loop: rank.shouldMusicLoop()
});
});
}
else
{
highscoreNew.visible = false;
FunkinSound.playMusic(rank.getMusicPath(),
{
startingVolume: 1.0,
overrideExisting: true,
restartTrack: true,
loop: rank.shouldMusicLoop()
});
}
};
});
rankBg.makeSolidColor(FlxG.width, FlxG.height, 0xFF000000);
rankBg.zIndex = 99999;
add(rankBg);
rankBg.alpha = 0;
refresh();
@ -338,6 +462,8 @@ class ResultState extends MusicBeatSubState
function startRankTallySequence():Void
{
bgFlash.visible = true;
FlxTween.tween(bgFlash, {alpha: 0}, 5 / 24);
var clearPercentFloat = (params.scoreData.tallies.sick + params.scoreData.tallies.good) / params.scoreData.tallies.totalNotes * 100;
clearPercentTarget = Math.floor(clearPercentFloat);
// Prevent off-by-one errors.
@ -346,8 +472,8 @@ class ResultState extends MusicBeatSubState
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,
var clearPercentCounter:ClearPercentCounter = new ClearPercentCounter(FlxG.width / 2 + 190, FlxG.height / 2 - 70, clearPercentLerp);
FlxTween.tween(clearPercentCounter, {curNumber: clearPercentTarget}, 58 / 24,
{
ease: FlxEase.quartOut,
onUpdate: _ -> {
@ -362,10 +488,6 @@ class ResultState extends MusicBeatSubState
// 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;
@ -374,9 +496,10 @@ class ResultState extends MusicBeatSubState
clearPercentCounter.flash(false);
});
displayRankText();
// displayRankText();
new FlxTimer().start(2.0, _ -> {
// previously 2.0 seconds
new FlxTimer().start(0.25, _ -> {
FlxTween.tween(clearPercentCounter, {alpha: 0}, 0.5,
{
startDelay: 0.5,
@ -386,7 +509,7 @@ class ResultState extends MusicBeatSubState
}
});
afterRankTallySequence();
// afterRankTallySequence();
});
}
});
@ -411,7 +534,6 @@ class ResultState extends MusicBeatSubState
{
highscoreNew.visible = true;
highscoreNew.animation.play("new");
FlxTween.tween(highscoreNew, {y: highscoreNew.y + 10}, 0.8, {ease: FlxEase.quartOut});
}
else
{
@ -425,16 +547,35 @@ class ResultState extends MusicBeatSubState
function displayRankText():Void
{
var rankTextVert:FunkinSprite = FunkinSprite.create(FlxG.width - 64, 100, rank.getVerTextAsset());
rankTextVert.zIndex = 2000;
bgFlash.visible = true;
bgFlash.alpha = 1;
FlxTween.tween(bgFlash, {alpha: 0}, 14 / 24);
var rankTextVert:FlxBackdrop = new FlxBackdrop(Paths.image(rank.getVerTextAsset()), Y, 0, 30);
rankTextVert.x = FlxG.width - 44;
rankTextVert.y = 100;
rankTextVert.zIndex = 990;
add(rankTextVert);
for (i in 0...10)
FlxFlicker.flicker(rankTextVert, 2 / 24 * 3, 2 / 24, true);
// Scrolling.
new FlxTimer().start(30 / 24, _ -> {
rankTextVert.velocity.y = -80;
});
for (i in 0...12)
{
var rankTextBack:FunkinSprite = FunkinSprite.create(FlxG.width / 2 - 80, 50, rank.getHorTextAsset());
rankTextBack.y += (rankTextBack.height * i / 2) + 10;
var rankTextBack:FlxBackdrop = new FlxBackdrop(Paths.image(rank.getHorTextAsset()), X, 10, 0);
rankTextBack.x = FlxG.width / 2 - 320;
rankTextBack.y = 50 + (135 * i / 2) + 10;
// rankTextBack.angle = -3.8;
rankTextBack.zIndex = 100;
rankTextBack.cameras = [cameraScroll];
add(rankTextBack);
// Scrolling.
rankTextBack.velocity.x = (i % 2 == 0) ? -7.0 : 7.0;
}
refresh();
@ -444,28 +585,6 @@ class ResultState extends MusicBeatSubState
{
showSmallClearPercent();
FunkinSound.playMusic(rank.getMusicPath(),
{
startingVolume: 1.0,
overrideExisting: true,
restartTrack: true,
loop: rank.shouldMusicLoop()
});
FlxG.sound.music.onComplete = () -> {
if (rank == SHIT)
{
FunkinSound.playMusic('bluu',
{
startingVolume: 0.0,
overrideExisting: true,
restartTrack: true,
loop: true
});
FlxG.sound.music.fadeIn(10.0, 0.0, 1.0);
}
}
switch (rank)
{
case PERFECT | PERFECT_GOLD:
@ -478,7 +597,17 @@ class ResultState extends MusicBeatSubState
bfPerfect.visible = true;
bfPerfect.playAnimation('');
}
new FlxTimer().start(106 / 24, _ -> {
if (heartsPerfect == null)
{
trace("Could not build heartsPerfect animation!");
}
else
{
heartsPerfect.visible = true;
heartsPerfect.playAnimation('');
}
});
case EXCELLENT:
if (bfExcellent == null)
{
@ -487,9 +616,8 @@ class ResultState extends MusicBeatSubState
else
{
bfExcellent.visible = true;
bfExcellent.playAnimation('Intro');
bfExcellent.playAnimation('');
}
case GREAT:
if (bfGreat == null)
{
@ -498,9 +626,20 @@ class ResultState extends MusicBeatSubState
else
{
bfGreat.visible = true;
bfGreat.playAnimation('Intro');
bfGreat.playAnimation('');
}
new FlxTimer().start(6 / 24, _ -> {
if (gfGreat == null)
{
trace("Could not build GREAT animation for gf!");
}
else
{
gfGreat.visible = true;
gfGreat.playAnimation('');
}
});
case SHIT:
if (bfShit == null)
{
@ -511,7 +650,6 @@ class ResultState extends MusicBeatSubState
bfShit.visible = true;
bfShit.playAnimation('Intro');
}
case GOOD:
if (bfGood == null)
{
@ -521,7 +659,6 @@ class ResultState extends MusicBeatSubState
{
bfGood.animation.play('fall');
bfGood.visible = true;
new FlxTimer().start((1 / 24) * 22, _ -> {
// plays about 22 frames (at 24fps timing) after bf spawns in
if (gfGood != null)
@ -554,13 +691,13 @@ class ResultState extends MusicBeatSubState
{
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});
FlxTween.tween(clearPercentSmall, {y: 122 - 5}, 0.5, {ease: FlxEase.expoOut, startDelay: 0.85});
}
songName.y = -songName.height;
var fuckedupnumber = (10) * (songName.text.length / 15);
FlxTween.tween(songName, {y: diffYTween - 25 - fuckedupnumber}, 0.5, {ease: FlxEase.expoOut, startDelay: 0.9});
songName.x = clearPercentSmall.x + clearPercentSmall.width - 30;
songName.x = clearPercentSmall.x + 94;
new FlxTimer().start(timerLength, _ -> {
var tempSpeed = FlxPoint.get(speedOfTween.x, speedOfTween.y);
@ -588,7 +725,9 @@ class ResultState extends MusicBeatSubState
refresh();
}
movingSongStuff = true;
new FlxTimer().start(2.5, _ -> {
movingSongStuff = true;
});
}
var movingSongStuff:Bool = false;
@ -608,6 +747,79 @@ class ResultState extends MusicBeatSubState
override function update(elapsed:Float):Void
{
// if(FlxG.keys.justPressed.R){
// FlxG.switchState(() -> new funkin.play.ResultState(
// {
// storyMode: false,
// title: "Cum Song Erect by Kawai Sprite",
// songId: "cum",
// difficultyId: "nightmare",
// isNewHighscore: true,
// scoreData:
// {
// score: 1_234_567,
// tallies:
// {
// sick: 200,
// good: 0,
// bad: 0,
// shit: 0,
// missed: 0,
// combo: 0,
// maxCombo: 69,
// totalNotesHit: 200,
// totalNotes: 200 // 0,
// }
// },
// }));
// }
// if(heartsPerfect != null){
// if (FlxG.keys.justPressed.I)
// {
// heartsPerfect.y -= 1;
// trace(heartsPerfect.x, heartsPerfect.y);
// }
// if (FlxG.keys.justPressed.J)
// {
// heartsPerfect.x -= 1;
// trace(heartsPerfect.x, heartsPerfect.y);
// }
// if (FlxG.keys.justPressed.L)
// {
// heartsPerfect.x += 1;
// trace(heartsPerfect.x, heartsPerfect.y);
// }
// if (FlxG.keys.justPressed.K)
// {
// heartsPerfect.y += 1;
// trace(heartsPerfect.x, heartsPerfect.y);
// }
// }
// if(bfGreat != null){
// if (FlxG.keys.justPressed.W)
// {
// bfGreat.y -= 1;
// trace(bfGreat.x, bfGreat.y);
// }
// if (FlxG.keys.justPressed.A)
// {
// bfGreat.x -= 1;
// trace(bfGreat.x, bfGreat.y);
// }
// if (FlxG.keys.justPressed.D)
// {
// bfGreat.x += 1;
// trace(bfGreat.x, bfGreat.y);
// }
// if (FlxG.keys.justPressed.S)
// {
// bfGreat.y += 1;
// trace(bfGreat.x, bfGreat.y);
// }
// }
// maskShaderSongName.swagSprX = songName.x;
maskShaderDifficulty.swagSprX = difficulty.x;
@ -635,154 +847,69 @@ class ResultState extends MusicBeatSubState
if (controls.PAUSE)
{
FlxTween.tween(FlxG.sound.music, {volume: 0}, 0.8);
FlxTween.tween(FlxG.sound.music, {pitch: 3}, 0.1,
{
onComplete: _ -> {
FlxTween.tween(FlxG.sound.music, {pitch: 0.5}, 0.4);
}
});
if (FlxG.sound.music != null)
{
FlxTween.tween(FlxG.sound.music, {volume: 0}, 0.8);
FlxTween.tween(FlxG.sound.music, {pitch: 3}, 0.1,
{
onComplete: _ -> {
FlxTween.tween(FlxG.sound.music, {pitch: 0.5}, 0.4);
}
});
}
if (params.storyMode)
{
openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new StoryMenuState(sticker)));
}
else
{
openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> FreeplayState.build(null, sticker)));
var rigged:Bool = true;
if (rank > Scoring.calculateRank(params?.prevScoreData)) // if (rigged)
{
trace('THE RANK IS Higher.....');
FlxTween.tween(rankBg, {alpha: 1}, 0.5,
{
ease: FlxEase.expoOut,
onComplete: function(_) {
FlxG.switchState(FreeplayState.build(
{
{
fromResults:
{
oldRank: Scoring.calculateRank(params?.prevScoreData),
newRank: rank,
songId: params.songId,
difficultyId: params.difficultyId,
playRankAnim: true
}
}
}));
}
});
}
else
{
trace('rank is lower...... and/or equal');
openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> FreeplayState.build(
{
{
fromResults:
{
oldRank: null,
playRankAnim: false,
newRank: rank,
songId: params.songId,
difficultyId: params.difficultyId
}
}
}, sticker)));
}
}
}
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 ResultRank(String)
{
var PERFECT_GOLD;
var PERFECT;
var EXCELLENT;
var GREAT;
var GOOD;
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 =
@ -797,6 +924,8 @@ typedef ResultsStateParams =
*/
var title:String;
var songId:String;
/**
* Whether the displayed score is a new highscore
*/

View file

@ -35,7 +35,7 @@ class ClearPercentCounter extends FlxTypedSpriteGroup<FlxSprite>
super(x, y);
flashShader = new PureColor(FlxColor.WHITE);
flashShader.colorSet = true;
flashShader.colorSet = false;
curNumber = startingNumber;
@ -54,22 +54,19 @@ class ClearPercentCounter extends FlxTypedSpriteGroup<FlxSprite>
*/
public function flash(enabled:Bool):Void
{
for (member in members)
{
member.shader = enabled ? flashShader : null;
}
flashShader.colorSet = enabled;
}
var tmr:Float = 0;
override function update(elapsed:Float)
override function update(elapsed:Float):Void
{
super.update(elapsed);
if (numberChanged) drawNumbers();
}
function drawNumbers()
function drawNumbers():Void
{
var seperatedScore:Array<Int> = [];
var tempCombo:Int = Math.round(curNumber);
@ -86,7 +83,7 @@ class ClearPercentCounter extends FlxTypedSpriteGroup<FlxSprite>
for (ind => num in seperatedScore)
{
var digitIndex = ind + 1;
var digitIndex:Int = 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;
@ -105,6 +102,8 @@ class ClearPercentCounter extends FlxTypedSpriteGroup<FlxSprite>
// 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);
numb.shader = flashShader;
numb.visible = true;
add(numb);
}
else
@ -113,8 +112,13 @@ class ClearPercentCounter extends FlxTypedSpriteGroup<FlxSprite>
// Reset the position of the number
members[digitIndex].x = xPos + this.x;
members[digitIndex].y = yPos + this.y;
members[digitIndex].visible = true;
}
}
for (ind in (seperatedScore.length + 1)...(members.length))
{
members[ind].visible = false;
}
}
}

View file

@ -171,6 +171,20 @@ class Strumline extends FlxSpriteGroup
updateNotes();
}
/**
* Returns `true` if no notes are in range of the strumline and the player can spam without penalty.
*/
public function mayGhostTap():Bool
{
// TODO: Refine this. Only querying "can be hit" is too tight but "is being rendered" is too loose.
// Also, if you just hit a note, there should be a (short) period where this is off so you can't spam.
// If there are any notes on screen, we can't ghost tap.
return notes.members.filter(function(note:NoteSprite) {
return note != null && note.alive && !note.hasBeenHit;
}).length == 0;
}
/**
* Return notes that are within `Constants.HIT_WINDOW` ms of the strumline.
* @return An array of `NoteSprite` objects.

View file

@ -1,5 +1,7 @@
package funkin.play.scoring;
import funkin.save.Save.SaveScoreData;
/**
* Which system to use when scoring and judging notes.
*/
@ -344,4 +346,303 @@ class Scoring
return 'miss';
}
}
public static function calculateRank(scoreData:Null<SaveScoreData>):Null<ScoringRank>
{
if (scoreData?.tallies.totalNotes == 0 || scoreData == null) return null;
// we can return null here, meaning that the player hasn't actually played and finished the song (thus has no data)
if (scoreData.tallies.totalNotes == 0) return null;
// Perfect (Platinum) is a Sick Full Clear
var isPerfectGold = scoreData.tallies.sick == scoreData.tallies.totalNotes;
if (isPerfectGold) return ScoringRank.PERFECT_GOLD;
// Else, use the standard grades
// Grade % (only good and sick), 1.00 is a full combo
var grade = (scoreData.tallies.sick + scoreData.tallies.good) / scoreData.tallies.totalNotes;
// Clear % (including bad and shit). 1.00 is a full clear but not a full combo
var clear = (scoreData.tallies.totalNotesHit) / scoreData.tallies.totalNotes;
if (grade == Constants.RANK_PERFECT_THRESHOLD)
{
return ScoringRank.PERFECT;
}
else if (grade >= Constants.RANK_EXCELLENT_THRESHOLD)
{
return ScoringRank.EXCELLENT;
}
else if (grade >= Constants.RANK_GREAT_THRESHOLD)
{
return ScoringRank.GREAT;
}
else if (grade >= Constants.RANK_GOOD_THRESHOLD)
{
return ScoringRank.GOOD;
}
else
{
return ScoringRank.SHIT;
}
}
}
enum abstract ScoringRank(String)
{
var PERFECT_GOLD;
var PERFECT;
var EXCELLENT;
var GREAT;
var GOOD;
var SHIT;
@:op(A > B) static function compare(a:Null<ScoringRank>, b:Null<ScoringRank>):Bool
{
if (a != null && b == null) return true;
if (a == null || b == null) return false;
var temp1:Int = 0;
var temp2:Int = 0;
// temp 1
switch (a)
{
case PERFECT_GOLD:
temp1 = 5;
case PERFECT:
temp1 = 4;
case EXCELLENT:
temp1 = 3;
case GREAT:
temp1 = 2;
case GOOD:
temp1 = 1;
case SHIT:
temp1 = 0;
default:
temp1 = -1;
}
// temp 2
switch (b)
{
case PERFECT_GOLD:
temp2 = 5;
case PERFECT:
temp2 = 4;
case EXCELLENT:
temp2 = 3;
case GREAT:
temp2 = 2;
case GOOD:
temp2 = 1;
case SHIT:
temp2 = 0;
default:
temp2 = -1;
}
if (temp1 > temp2)
{
return true;
}
else
{
return false;
}
}
/**
* Delay in seconds
*/
public function getMusicDelay():Float
{
switch (abstract)
{
case PERFECT_GOLD | PERFECT:
// return 2.5;
return 95/24;
case EXCELLENT:
return 0;
case GREAT:
return 5/24;
case GOOD:
return 3/24;
case SHIT:
return 2/24;
default:
return 3.5;
}
}
public function getBFDelay():Float
{
switch (abstract)
{
case PERFECT_GOLD | PERFECT:
// return 2.5;
return 95/24;
case EXCELLENT:
return 97/24;
case GREAT:
return 95/24;
case GOOD:
return 95/24;
case SHIT:
return 95/24;
default:
return 3.5;
}
}
public function getFlashDelay():Float
{
switch (abstract)
{
case PERFECT_GOLD | PERFECT:
// return 2.5;
return 129/24;
case EXCELLENT:
return 122/24;
case GREAT:
return 109/24;
case GOOD:
return 107/24;
case SHIT:
return 186/24;
default:
return 3.5;
}
}
public function getHighscoreDelay():Float
{
switch (abstract)
{
case PERFECT_GOLD | PERFECT:
// return 2.5;
return 140/24;
case EXCELLENT:
return 140/24;
case GREAT:
return 129/24;
case GOOD:
return 127/24;
case SHIT:
return 207/24;
default:
return 3.5;
}
}
public function getMusicPath():String
{
switch (abstract)
{
case PERFECT_GOLD:
return 'resultsPERFECT';
case PERFECT:
return 'resultsPERFECT';
case EXCELLENT:
return 'resultsEXCELLENT';
case GREAT:
return 'resultsNORMAL';
case GOOD:
return 'resultsNORMAL';
case SHIT:
return 'resultsSHIT';
default:
return 'resultsNORMAL';
}
}
public function hasMusicIntro():Bool
{
switch (abstract)
{
case EXCELLENT:
return true;
case SHIT:
return true;
default:
return false;
}
}
public function getFreeplayRankIconAsset():Null<String>
{
switch (abstract)
{
case PERFECT_GOLD:
return 'PERFECTSICK';
case PERFECT:
return 'PERFECT';
case EXCELLENT:
return 'EXCELLENT';
case GREAT:
return 'GREAT';
case GOOD:
return 'GOOD';
case SHIT:
return 'LOSS';
default:
return null;
}
}
public function shouldMusicLoop():Bool
{
switch (abstract)
{
case PERFECT_GOLD | PERFECT | EXCELLENT | GREAT | 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';
}
}
}

View file

@ -91,6 +91,12 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
return _metadata.keys().array();
}
// this returns false so that any new song can override this and return true when needed
public function isSongNew(currentDifficulty:String):Bool
{
return false;
}
/**
* Set to false if the song was edited in the charter and should not be saved as a high score.
*/
@ -120,6 +126,18 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
return DEFAULT_ARTIST;
}
/**
* The artist of the song.
*/
public var charter(get, never):String;
function get_charter():String
{
if (_data != null) return _data?.charter ?? 'Unknown';
if (_metadata.size() > 0) return _metadata.get(Constants.DEFAULT_VARIATION)?.charter ?? 'Unknown';
return Constants.DEFAULT_CHARTER;
}
/**
* @param id The ID of the song to load.
* @param ignoreErrors If false, an exception will be thrown if the song data could not be loaded.
@ -270,6 +288,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
difficulty.songName = metadata.songName;
difficulty.songArtist = metadata.artist;
difficulty.charter = metadata.charter ?? Constants.DEFAULT_CHARTER;
difficulty.timeFormat = metadata.timeFormat;
difficulty.divisions = metadata.divisions;
difficulty.timeChanges = metadata.timeChanges;
@ -334,6 +353,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
{
difficulty.songName = metadata.songName;
difficulty.songArtist = metadata.artist;
difficulty.charter = metadata.charter ?? Constants.DEFAULT_CHARTER;
difficulty.timeFormat = metadata.timeFormat;
difficulty.divisions = metadata.divisions;
difficulty.timeChanges = metadata.timeChanges;
@ -364,7 +384,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
*/
public function getDifficulty(?diffId:String, ?variation:String, ?variations:Array<String>):Null<SongDifficulty>
{
if (diffId == null) diffId = listDifficulties(variation)[0];
if (diffId == null) diffId = listDifficulties(variation, variations)[0];
if (variation == null) variation = Constants.DEFAULT_VARIATION;
if (variations == null) variations = [variation];
@ -590,6 +610,7 @@ class SongDifficulty
public var songName:String = Constants.DEFAULT_SONGNAME;
public var songArtist:String = Constants.DEFAULT_ARTIST;
public var charter:String = Constants.DEFAULT_CHARTER;
public var timeFormat:SongTimeFormat = Constants.DEFAULT_TIMEFORMAT;
public var divisions:Null<Int> = null;
public var looped:Bool = false;

View file

@ -852,6 +852,11 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements
}
}
public override function toString():String
{
return 'Stage($id)';
}
static function _fetchData(id:String):Null<StageData>
{
return StageRegistry.instance.parseEntryDataWithMigration(id, StageRegistry.instance.fetchEntryVersion(id));

View file

@ -1,20 +1,22 @@
package funkin.save;
import flixel.util.FlxSave;
import funkin.save.migrator.SaveDataMigrator;
import thx.semver.Version;
import funkin.input.Controls.Device;
import funkin.play.scoring.Scoring;
import funkin.play.scoring.Scoring.ScoringRank;
import funkin.save.migrator.RawSaveData_v1_0_0;
import funkin.save.migrator.SaveDataMigrator;
import funkin.save.migrator.SaveDataMigrator;
import funkin.ui.debug.charting.ChartEditorState.ChartEditorLiveInputStyle;
import funkin.ui.debug.charting.ChartEditorState.ChartEditorTheme;
import thx.semver.Version;
import funkin.util.SerializerUtil;
import thx.semver.Version;
import thx.semver.Version;
@:nullSafety
class Save
{
public static final SAVE_DATA_VERSION:thx.semver.Version = "2.0.4";
public static final SAVE_DATA_VERSION:thx.semver.Version = "2.0.5";
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.
@ -54,6 +56,9 @@ class Save
if (data == null) this.data = Save.getDefault();
else
this.data = data;
// Make sure the verison number is up to date before we flush.
this.data.version = Save.SAVE_DATA_VERSION;
}
public static function getDefault():RawSaveData
@ -492,6 +497,11 @@ class Save
return song.get(difficultyId);
}
public function getSongRank(songId:String, difficultyId:String = 'normal'):Null<ScoringRank>
{
return Scoring.calculateRank(getSongScore(songId, difficultyId));
}
/**
* Apply the score the user achieved for a given song on a given difficulty.
*/
@ -706,7 +716,6 @@ class Save
{
trace('[SAVE] Found legacy save data, converting...');
var gameSave = SaveDataMigrator.migrateFromLegacy(legacySaveData);
@:privateAccess
FlxG.save.mergeData(gameSave.data, true);
}
else
@ -718,13 +727,94 @@ class Save
}
else
{
trace('[SAVE] Loaded save data.');
@:privateAccess
trace('[SAVE] Found existing save data.');
var gameSave = SaveDataMigrator.migrate(FlxG.save.data);
FlxG.save.mergeData(gameSave.data, true);
}
}
public static function archiveBadSaveData(data:Dynamic):Int
{
// We want to save this somewhere so we can try to recover it for the user in the future!
final RECOVERY_SLOT_START = 1000;
return writeToAvailableSlot(RECOVERY_SLOT_START, data);
}
public static function debug_queryBadSaveData():Void
{
final RECOVERY_SLOT_START = 1000;
final RECOVERY_SLOT_END = 1100;
var firstBadSaveData = querySlotRange(RECOVERY_SLOT_START, RECOVERY_SLOT_END);
if (firstBadSaveData > 0)
{
trace('[SAVE] Found bad save data in slot ${firstBadSaveData}!');
trace('We should look into recovery...');
trace(haxe.Json.stringify(fetchFromSlotRaw(firstBadSaveData)));
}
}
static function fetchFromSlotRaw(slot:Int):Null<Dynamic>
{
var targetSaveData = new FlxSave();
targetSaveData.bind('$SAVE_NAME${slot}', SAVE_PATH);
if (targetSaveData.isEmpty()) return null;
return targetSaveData.data;
}
static function writeToAvailableSlot(slot:Int, data:Dynamic):Int
{
trace('[SAVE] Finding slot to write data to (starting with ${slot})...');
var targetSaveData = new FlxSave();
targetSaveData.bind('$SAVE_NAME${slot}', SAVE_PATH);
while (!targetSaveData.isEmpty())
{
// Keep trying to bind to slots until we find an empty slot.
trace('[SAVE] Slot ${slot} is taken, continuing...');
slot++;
targetSaveData.bind('$SAVE_NAME${slot}', SAVE_PATH);
}
trace('[SAVE] Writing data to slot ${slot}...');
targetSaveData.mergeData(data, true);
trace('[SAVE] Data written to slot ${slot}!');
return slot;
}
/**
* Return true if the given save slot is not empty.
* @param slot The slot number to check.
* @return Whether the slot is not empty.
*/
static function querySlot(slot:Int):Bool
{
var targetSaveData = new FlxSave();
targetSaveData.bind('$SAVE_NAME${slot}', SAVE_PATH);
return !targetSaveData.isEmpty();
}
/**
* Return true if any of the slots in the given range is not empty.
* @param start The starting slot number to check.
* @param end The ending slot number to check.
* @return The first slot in the range that is not empty, or `-1` if none are.
*/
static function querySlotRange(start:Int, end:Int):Int
{
for (i in start...end)
{
if (querySlot(i))
{
return i;
}
}
return -1;
}
static function fetchLegacySaveData():Null<RawSaveData_v1_0_0>
{
trace("[SAVE] Checking for legacy save data...");

View file

@ -5,6 +5,10 @@ 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/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.0.5] - 2024-05-21
### Fixed
- Resolved an issue where HTML5 wouldn't store the semantic version properly, causing the game to fail to load the save.
## [2.0.4] - 2024-05-21
### Added
- `favoriteSongs:Array<String>` to `Save`

View file

@ -35,8 +35,9 @@ class SaveDataMigrator
else
{
var message:String = 'Error migrating save data, expected ${Save.SAVE_DATA_VERSION}.';
lime.app.Application.current.window.alert(message, "Save Data Failure");
trace('[SAVE] ' + message);
var slot:Int = Save.archiveBadSaveData(inputData);
var fullMessage:String = 'An error occurred migrating your save data.\n${message}\nInvalid data has been moved to save slot ${slot}.';
lime.app.Application.current.window.alert(fullMessage, "Save Data Failure");
return new Save(Save.getDefault());
}
}

View file

@ -54,7 +54,7 @@ class CreditsDataHandler
body: [
{line: 'ninjamuffin99'},
{line: 'PhantomArcade'},
{line: 'KawaiSprite'},
{line: 'Kawai Sprite'},
{line: 'evilsk8r'},
]
}

View file

@ -4,6 +4,7 @@ import flixel.text.FlxText;
import flixel.util.FlxColor;
import funkin.audio.FunkinSound;
import flixel.FlxSprite;
import funkin.ui.mainmenu.MainMenuState;
import flixel.group.FlxSpriteGroup;
/**
@ -199,7 +200,7 @@ class CreditsState extends MusicBeatState
function exit():Void
{
FlxG.switchState(funkin.ui.mainmenu.MainMenuState.new);
FlxG.switchState(() -> new MainMenuState());
}
public override function destroy():Void

View file

@ -62,7 +62,6 @@ class DebugMenuSubState extends MusicBeatSubState
#if sys
createItem("OPEN CRASH LOG FOLDER", openLogFolder);
#end
FlxG.camera.focusOn(new FlxPoint(camFocusPoint.x, camFocusPoint.y));
FlxG.camera.focusOn(new FlxPoint(camFocusPoint.x, camFocusPoint.y + 500));
}

View file

@ -1270,7 +1270,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
var result:Null<SongMetadata> = songMetadata.get(selectedVariation);
if (result == null)
{
result = new SongMetadata('DadBattle', 'Kawai Sprite', selectedVariation);
result = new SongMetadata('Default Song Name', Constants.DEFAULT_ARTIST, selectedVariation);
songMetadata.set(selectedVariation, result);
}
return result;
@ -4566,8 +4566,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
}
gridGhostHoldNote.visible = true;
gridGhostHoldNote.noteData = gridGhostNote.noteData;
gridGhostHoldNote.noteDirection = gridGhostNote.noteData.getDirection();
gridGhostHoldNote.noteData = currentPlaceNoteData;
gridGhostHoldNote.noteDirection = currentPlaceNoteData.getDirection();
gridGhostHoldNote.setHeightDirectly(dragLengthPixels, true);
gridGhostHoldNote.updateHoldNotePosition(renderedHoldNotes);

View file

@ -36,6 +36,8 @@ class ChartEditorHoldNoteSprite extends SustainTrail
zoom *= 0.7;
zoom *= ChartEditorState.GRID_SIZE / Strumline.STRUMLINE_SIZE;
flipY = false;
setup();
}

View file

@ -384,17 +384,34 @@ class ChartEditorImportExportHandler
if (variationId == '')
{
var variationMetadata:Null<SongMetadata> = state.songMetadata.get(variation);
if (variationMetadata != null) zipEntries.push(FileUtil.makeZIPEntry('${state.currentSongId}-metadata.json', variationMetadata.serialize()));
if (variationMetadata != null)
{
variationMetadata.version = funkin.data.song.SongRegistry.SONG_METADATA_VERSION;
variationMetadata.generatedBy = funkin.data.song.SongRegistry.DEFAULT_GENERATEDBY;
zipEntries.push(FileUtil.makeZIPEntry('${state.currentSongId}-metadata.json', variationMetadata.serialize()));
}
var variationChart:Null<SongChartData> = state.songChartData.get(variation);
if (variationChart != null) zipEntries.push(FileUtil.makeZIPEntry('${state.currentSongId}-chart.json', variationChart.serialize()));
if (variationChart != null)
{
variationChart.version = funkin.data.song.SongRegistry.SONG_CHART_DATA_VERSION;
variationChart.generatedBy = funkin.data.song.SongRegistry.DEFAULT_GENERATEDBY;
zipEntries.push(FileUtil.makeZIPEntry('${state.currentSongId}-chart.json', variationChart.serialize()));
}
}
else
{
var variationMetadata:Null<SongMetadata> = state.songMetadata.get(variation);
if (variationMetadata != null) zipEntries.push(FileUtil.makeZIPEntry('${state.currentSongId}-metadata-$variationId.json',
variationMetadata.serialize()));
if (variationMetadata != null)
{
zipEntries.push(FileUtil.makeZIPEntry('${state.currentSongId}-metadata-$variationId.json', variationMetadata.serialize()));
}
var variationChart:Null<SongChartData> = state.songChartData.get(variation);
if (variationChart != null) zipEntries.push(FileUtil.makeZIPEntry('${state.currentSongId}-chart-$variationId.json', variationChart.serialize()));
if (variationChart != null)
{
variationChart.version = funkin.data.song.SongRegistry.SONG_CHART_DATA_VERSION;
variationChart.generatedBy = funkin.data.song.SongRegistry.DEFAULT_GENERATEDBY;
zipEntries.push(FileUtil.makeZIPEntry('${state.currentSongId}-chart-$variationId.json', variationChart.serialize()));
}
}
}

View file

@ -29,6 +29,7 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox
{
var inputSongName:TextField;
var inputSongArtist:TextField;
var inputSongCharter:TextField;
var inputStage:DropDown;
var inputNoteStyle:DropDown;
var buttonCharacterPlayer:Button;
@ -89,6 +90,20 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox
}
};
inputSongCharter.onChange = function(event:UIEvent) {
var valid:Bool = event.target.text != null && event.target.text != '';
if (valid)
{
inputSongCharter.removeClass('invalid-value');
chartEditorState.currentSongMetadata.charter = event.target.text;
}
else
{
chartEditorState.currentSongMetadata.charter = null;
}
};
inputStage.onChange = function(event:UIEvent) {
var valid:Bool = event.data != null && event.data.id != null;
@ -178,6 +193,7 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox
inputSongName.value = chartEditorState.currentSongMetadata.songName;
inputSongArtist.value = chartEditorState.currentSongMetadata.artist;
inputSongCharter.value = chartEditorState.currentSongMetadata.charter;
inputStage.value = chartEditorState.currentSongMetadata.playData.stage;
inputNoteStyle.value = chartEditorState.currentSongMetadata.playData.noteStyle;
inputBPM.value = chartEditorState.currentSongMetadata.timeChanges[0].bpm;

View file

@ -66,7 +66,7 @@ class AlbumRoll extends FlxSpriteGroup
add(newAlbumArt);
difficultyStars = new DifficultyStars(140, 39);
difficultyStars.stars.visible = false;
difficultyStars.visible = false;
add(difficultyStars);
}
@ -131,7 +131,7 @@ class AlbumRoll extends FlxSpriteGroup
if (exitMovers == null) return;
exitMovers.set([newAlbumArt],
exitMovers.set([newAlbumArt, difficultyStars],
{
x: FlxG.width,
speed: 0.4,
@ -149,7 +149,7 @@ class AlbumRoll extends FlxSpriteGroup
newAlbumArt.visible = true;
newAlbumArt.playAnimation(animNames.get('$albumId-active'), false, false, false);
difficultyStars.stars.visible = false;
difficultyStars.visible = false;
new FlxTimer().start(0.75, function(_) {
// showTitle();
showStars();
@ -172,6 +172,7 @@ class AlbumRoll extends FlxSpriteGroup
*/
public function showStars():Void
{
difficultyStars.stars.visible = true; // true;
difficultyStars.visible = true; // true;
difficultyStars.flameCheck();
}
}

View file

@ -58,12 +58,24 @@ class CapsuleText extends FlxSpriteGroup
function set_clipWidth(value:Int):Int
{
resetText();
if (whiteText.width > value)
checkClipWidth(value);
return clipWidth = value;
}
/**
* Checks if the text if it's too long, and clips if it is
* @param wid
*/
function checkClipWidth(?wid:Int):Void
{
if (wid == null) wid = clipWidth;
if (whiteText.width > wid)
{
tooLong = true;
blurredText.clipRect = new FlxRect(0, 0, value, blurredText.height);
whiteText.clipRect = new FlxRect(0, 0, value, whiteText.height);
blurredText.clipRect = new FlxRect(0, 0, wid, blurredText.height);
whiteText.clipRect = new FlxRect(0, 0, wid, whiteText.height);
}
else
{
@ -72,7 +84,6 @@ class CapsuleText extends FlxSpriteGroup
blurredText.clipRect = null;
whiteText.clipRect = null;
}
return clipWidth = value;
}
function set_text(value:String):String
@ -86,6 +97,7 @@ class CapsuleText extends FlxSpriteGroup
blurredText.text = value;
whiteText.text = value;
checkClipWidth();
whiteText.textField.filters = [
new openfl.filters.GlowFilter(0x00ccff, 1, 5, 5, 210, BitmapFilterQuality.MEDIUM),
// new openfl.filters.BlurFilter(5, 5, BitmapFilterQuality.LOW)

View file

@ -27,8 +27,8 @@ class DJBoyfriend extends FlxAtlasSprite
var gotSpooked:Bool = false;
static final SPOOK_PERIOD:Float = 120.0;
static final TV_PERIOD:Float = 180.0;
static final SPOOK_PERIOD:Float = 60.0;
static final TV_PERIOD:Float = 120.0;
// Time since dad last SPOOKED you.
var timeSinceSpook:Float = 0;

View file

@ -19,7 +19,7 @@ class DifficultyStars extends FlxSpriteGroup
public var stars:FlxAtlasSprite;
var flames:FreeplayFlames;
public var flames:FreeplayFlames;
var hsvShader:HSVShader;
@ -80,11 +80,16 @@ class DifficultyStars extends FlxSpriteGroup
curDifficulty = difficulty - 1;
}
flameCheck();
return difficulty;
}
public function flameCheck():Void
{
if (difficulty > 10) flames.flameCount = difficulty - 10;
else
flames.flameCount = 0;
return difficulty;
}
function set_curDifficulty(value:Int):Int

View file

@ -6,6 +6,7 @@ import flixel.addons.ui.FlxInputText;
import flixel.FlxCamera;
import flixel.FlxSprite;
import flixel.group.FlxGroup;
import funkin.graphics.shaders.GaussianBlurShader;
import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
import flixel.input.touch.FlxTouch;
@ -31,9 +32,12 @@ import funkin.graphics.shaders.StrokeShader;
import funkin.input.Controls;
import funkin.play.PlayStatePlaylist;
import funkin.play.song.Song;
import funkin.ui.story.Level;
import funkin.save.Save;
import funkin.save.Save.SaveScoreData;
import funkin.ui.AtlasText;
import funkin.play.scoring.Scoring;
import funkin.play.scoring.Scoring.ScoringRank;
import funkin.ui.mainmenu.MainMenuState;
import funkin.ui.MusicBeatSubState;
import funkin.ui.transition.LoadingState;
@ -42,6 +46,7 @@ import funkin.util.MathUtil;
import lime.utils.Assets;
import flixel.tweens.misc.ShakeTween;
import funkin.effects.IntervalShake;
import funkin.ui.freeplay.SongMenuItem.FreeplayRank;
/**
* Parameters used to initialize the FreeplayState.
@ -49,6 +54,39 @@ import funkin.effects.IntervalShake;
typedef FreeplayStateParams =
{
?character:String,
?fromResults:FromResultsParams,
};
/**
* A set of parameters for transitioning to the FreeplayState from the ResultsState.
*/
typedef FromResultsParams =
{
/**
* The previous rank the song hand, if any. Null if it had no score before.
*/
var ?oldRank:ScoringRank;
/**
* Whether or not to play the rank animation on returning to freeplay.
*/
var playRankAnim:Bool;
/**
* The new rank the song has.
*/
var newRank:ScoringRank;
/**
* The song ID to play the animation on.
*/
var songId:String;
/**
* The difficulty ID to play the animation on.
*/
var difficultyId:String;
};
/**
@ -73,6 +111,7 @@ class FreeplayState extends MusicBeatSubState
/**
* For the audio preview, the duration of the fade-out effect.
*
*/
public static final FADE_OUT_DURATION:Float = 0.25;
@ -160,10 +199,21 @@ class FreeplayState extends MusicBeatSubState
var bgDad:FlxSprite;
var cardGlow:FlxSprite;
var fromResultsParams:Null<FromResultsParams> = null;
var prepForNewRank:Bool = false;
public function new(?params:FreeplayStateParams, ?stickers:StickerSubState)
{
currentCharacter = params?.character ?? Constants.DEFAULT_CHARACTER;
fromResultsParams = params?.fromResults;
if (fromResultsParams?.playRankAnim == true)
{
prepForNewRank = true;
}
if (stickers != null)
{
stickerSubState = stickers;
@ -200,11 +250,14 @@ class FreeplayState extends MusicBeatSubState
isDebug = true;
#end
FunkinSound.playMusic('freakyMenu',
{
overrideExisting: true,
restartTrack: false
});
if (prepForNewRank == false)
{
FunkinSound.playMusic('freakyMenu',
{
overrideExisting: true,
restartTrack: false
});
}
// Add a null entry that represents the RANDOM option
songs.push(null);
@ -212,10 +265,24 @@ class FreeplayState extends MusicBeatSubState
// programmatically adds the songs via LevelRegistry and SongRegistry
for (levelId in LevelRegistry.instance.listSortedLevelIds())
{
for (songId in LevelRegistry.instance.parseEntryData(levelId).songs)
var level:Level = LevelRegistry.instance.fetchEntry(levelId);
if (level == null)
{
trace('[WARN] Could not find level with id (${levelId})');
continue;
}
for (songId in level.getSongs())
{
var song:Song = SongRegistry.instance.fetchEntry(songId);
if (song == null)
{
trace('[WARN] Could not find song with id (${songId})');
continue;
}
// 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);
@ -587,6 +654,11 @@ class FreeplayState extends MusicBeatSubState
cardGlow.visible = true;
FlxTween.tween(cardGlow, {alpha: 0, "scale.x": 1.2, "scale.y": 1.2}, 0.45, {ease: FlxEase.sineOut});
if (prepForNewRank)
{
rankAnimStart(fromResultsParams);
}
});
generateSongList(null, false);
@ -613,6 +685,11 @@ class FreeplayState extends MusicBeatSubState
FlxG.cameras.add(rankCamera, false);
rankBg.cameras = [rankCamera];
rankBg.alpha = 0;
if (prepForNewRank)
{
rankCamera.fade(0xFF000000, 0, false, null, true);
}
}
var currentFilter:SongFilter = null;
@ -680,6 +757,7 @@ class FreeplayState extends MusicBeatSubState
randomCapsule.alpha = 0;
randomCapsule.songText.visible = false;
randomCapsule.favIcon.visible = false;
randomCapsule.favIconBlurred.visible = false;
randomCapsule.ranking.visible = false;
randomCapsule.blurredRanking.visible = false;
randomCapsule.initJumpIn(0, force);
@ -702,13 +780,12 @@ class FreeplayState extends MusicBeatSubState
funnyMenu.capsule.alpha = 0.5;
funnyMenu.songText.visible = false;
funnyMenu.favIcon.visible = tempSongs[i].isFav;
funnyMenu.favIconBlurred.visible = tempSongs[i].isFav;
funnyMenu.hsvShader = hsvShader;
funnyMenu.newText.animation.curAnim.curFrame = 45 - ((i * 4) % 45);
funnyMenu.forcePosition();
funnyMenu.checkClip();
funnyMenu.forcePosition();
grpCapsules.add(funnyMenu);
}
@ -729,6 +806,13 @@ class FreeplayState extends MusicBeatSubState
*/
public function sortSongs(songsToFilter:Array<FreeplaySongData>, songFilter:SongFilter):Array<FreeplaySongData>
{
var filterAlphabetically = function(a:FreeplaySongData, b:FreeplaySongData):Int {
if (a?.songName.toLowerCase() < b?.songName.toLowerCase()) return -1;
else if (a?.songName.toLowerCase() > b?.songName.toLowerCase()) return 1;
else
return 0;
};
switch (songFilter.filterType)
{
case REGEXP:
@ -743,6 +827,8 @@ class FreeplayState extends MusicBeatSubState
return filterRegexp.match(str.songName);
});
songsToFilter.sort(filterAlphabetically);
case STARTSWITH:
// extra note: this is essentially a "search"
@ -757,25 +843,93 @@ class FreeplayState extends MusicBeatSubState
if (str == null) return true; // Random
return str.isFav;
});
songsToFilter.sort(filterAlphabetically);
default:
// return all on default
}
return songsToFilter;
}
function rankAnimStart()
var sparks:FlxSprite;
var sparksADD:FlxSprite;
function rankAnimStart(fromResults:Null<FromResultsParams>):Void
{
busy = true;
grpCapsules.members[curSelected].sparkle.alpha = 0;
// grpCapsules.members[curSelected].forcePosition();
if (fromResults != null)
{
rememberedSongId = fromResults.songId;
rememberedDifficulty = fromResults.difficultyId;
changeSelection();
changeDiff();
}
dj.fistPump();
// rankCamera.fade(FlxColor.BLACK, 0.5, true);
rankCamera.fade(0xFF000000, 0.5, true, null, true);
FlxG.sound.music.volume = 0;
if (FlxG.sound.music != null) FlxG.sound.music.volume = 0;
rankBg.alpha = 1;
originalPos.x = grpCapsules.members[curSelected].x;
originalPos.y = grpCapsules.members[curSelected].y;
if (fromResults?.oldRank != null)
{
grpCapsules.members[curSelected].fakeRanking.rank = fromResults.oldRank;
grpCapsules.members[curSelected].fakeBlurredRanking.rank = fromResults.oldRank;
grpCapsules.members[curSelected].ranking.alpha = 0;
grpCapsules.members[curSelected].blurredRanking.alpha = 0;
sparks = new FlxSprite(0, 0);
sparks.frames = Paths.getSparrowAtlas('freeplay/sparks');
sparks.animation.addByPrefix('sparks', 'sparks', 24, false);
sparks.visible = false;
sparks.blend = BlendMode.ADD;
sparks.setPosition(517, 134);
sparks.scale.set(0.5, 0.5);
add(sparks);
sparks.cameras = [rankCamera];
sparksADD = new FlxSprite(0, 0);
sparksADD.visible = false;
sparksADD.frames = Paths.getSparrowAtlas('freeplay/sparksadd');
sparksADD.animation.addByPrefix('sparks add', 'sparks add', 24, false);
sparksADD.setPosition(498, 116);
sparksADD.blend = BlendMode.ADD;
sparksADD.scale.set(0.5, 0.5);
add(sparksADD);
sparksADD.cameras = [rankCamera];
switch (fromResults.oldRank)
{
case SHIT:
sparksADD.color = 0xFF6044FF;
case GOOD:
sparksADD.color = 0xFFEF8764;
case GREAT:
sparksADD.color = 0xFFEAF6FF;
case EXCELLENT:
sparksADD.color = 0xFFFDCB42;
case PERFECT:
sparksADD.color = 0xFFFF58B4;
case PERFECT_GOLD:
sparksADD.color = 0xFFFFB619;
}
// sparksADD.color = sparks.color;
}
grpCapsules.members[curSelected].doLerp = false;
// originalPos.x = grpCapsules.members[curSelected].x;
// originalPos.y = grpCapsules.members[curSelected].y;
originalPos.x = 320.488;
originalPos.y = 235.6;
trace(originalPos);
grpCapsules.members[curSelected].ranking.visible = false;
grpCapsules.members[curSelected].blurredRanking.visible = false;
rankCamera.zoom = 1.85;
FlxTween.tween(rankCamera, {"zoom": 1.8}, 0.6, {ease: FlxEase.sineIn});
@ -784,40 +938,57 @@ class FreeplayState extends MusicBeatSubState
FlxTween.tween(funnyCam, {"zoom": 1.1}, 0.6, {ease: FlxEase.sineIn});
grpCapsules.members[curSelected].cameras = [rankCamera];
grpCapsules.members[curSelected].targetPos.set((FlxG.width / 2) - (grpCapsules.members[curSelected].width / 2),
// grpCapsules.members[curSelected].targetPos.set((FlxG.width / 2) - (grpCapsules.members[curSelected].width / 2),
// (FlxG.height / 2) - (grpCapsules.members[curSelected].height / 2));
grpCapsules.members[curSelected].setPosition((FlxG.width / 2) - (grpCapsules.members[curSelected].width / 2),
(FlxG.height / 2) - (grpCapsules.members[curSelected].height / 2));
new FlxTimer().start(0.5, _ -> {
grpCapsules.members[curSelected].doLerp = false;
rankDisplayNew();
rankDisplayNew(fromResults);
});
}
function rankDisplayNew()
function rankDisplayNew(fromResults:Null<FromResultsParams>):Void
{
grpCapsules.members[curSelected].ranking.alpha = 1;
grpCapsules.members[curSelected].blurredRanking.alpha = 1;
grpCapsules.members[curSelected].ranking.visible = true;
grpCapsules.members[curSelected].blurredRanking.visible = true;
grpCapsules.members[curSelected].ranking.scale.set(20, 20);
grpCapsules.members[curSelected].blurredRanking.scale.set(20, 20);
// var tempr:Int = FlxG.random.int(0, 4);
// grpCapsules.members[curSelected].ranking.rank = tempr;
grpCapsules.members[curSelected].ranking.animation.play(grpCapsules.members[curSelected].ranking.animation.curAnim.name, true);
grpCapsules.members[curSelected].ranking.animation.play(fromResults.newRank.getFreeplayRankIconAsset(), true);
// grpCapsules.members[curSelected].ranking.animation.curAnim.name, true);
FlxTween.tween(grpCapsules.members[curSelected].ranking, {"scale.x": 1, "scale.y": 1}, 0.1);
grpCapsules.members[curSelected].blurredRanking.animation.play(grpCapsules.members[curSelected].blurredRanking.animation.curAnim.name, true);
grpCapsules.members[curSelected].blurredRanking.animation.play(fromResults.newRank.getFreeplayRankIconAsset(), true);
FlxTween.tween(grpCapsules.members[curSelected].blurredRanking, {"scale.x": 1, "scale.y": 1}, 0.1);
new FlxTimer().start(0.1, _ -> {
trace(grpCapsules.members[curSelected].ranking.rank);
switch (grpCapsules.members[curSelected].tempr)
// trace(grpCapsules.members[curSelected].ranking.rank);
if (fromResults?.oldRank != null)
{
case 0:
grpCapsules.members[curSelected].fakeRanking.visible = false;
grpCapsules.members[curSelected].fakeBlurredRanking.visible = false;
sparks.visible = true;
sparksADD.visible = true;
sparks.animation.play('sparks', true);
sparksADD.animation.play('sparks add', true);
sparks.animation.finishCallback = anim -> {
sparks.visible = false;
sparksADD.visible = false;
};
}
switch (fromResultsParams?.newRank)
{
case SHIT:
FunkinSound.playOnce(Paths.sound('ranks/rankinbad'));
case 4:
case PERFECT:
FunkinSound.playOnce(Paths.sound('ranks/rankinperfect'));
case 5:
case PERFECT_GOLD:
FunkinSound.playOnce(Paths.sound('ranks/rankinperfect'));
default:
FunkinSound.playOnce(Paths.sound('ranks/rankinnormal'));
@ -846,31 +1017,31 @@ class FreeplayState extends MusicBeatSubState
});
new FlxTimer().start(0.6, _ -> {
rankAnimSlam();
rankAnimSlam(fromResults);
// IntervalShake.shake(grpCapsules.members[curSelected].capsule, 0.3, 1 / 30, 0, 0.3, FlxEase.quartIn);
});
}
function rankAnimSlam()
function rankAnimSlam(fromResultsParams:Null<FromResultsParams>)
{
// FlxTween.tween(rankCamera, {"zoom": 1.9}, 0.5, {ease: FlxEase.backOut});
FlxTween.tween(rankBg, {alpha: 0}, 0.5, {ease: FlxEase.expoIn});
// FlxTween.tween(grpCapsules.members[curSelected], {angle: 5}, 0.5, {ease: FlxEase.backIn});
switch (grpCapsules.members[curSelected].tempr)
switch (fromResultsParams?.newRank)
{
case 0:
case SHIT:
FunkinSound.playOnce(Paths.sound('ranks/loss'));
case 1:
case GOOD:
FunkinSound.playOnce(Paths.sound('ranks/good'));
case 2:
case GREAT:
FunkinSound.playOnce(Paths.sound('ranks/great'));
case 3:
case EXCELLENT:
FunkinSound.playOnce(Paths.sound('ranks/excellent'));
case 4:
case PERFECT:
FunkinSound.playOnce(Paths.sound('ranks/perfect'));
case 5:
case PERFECT_GOLD:
FunkinSound.playOnce(Paths.sound('ranks/perfect'));
default:
FunkinSound.playOnce(Paths.sound('ranks/loss'));
@ -880,7 +1051,7 @@ class FreeplayState extends MusicBeatSubState
new FlxTimer().start(0.5, _ -> {
funnyCam.shake(0.0045, 0.35);
if (grpCapsules.members[curSelected].tempr == 0)
if (fromResultsParams?.newRank == SHIT)
{
dj.pumpFistBad();
}
@ -915,6 +1086,11 @@ class FreeplayState extends MusicBeatSubState
IntervalShake.shake(capsule, 0.6, 1 / 24, 0.12, 0, FlxEase.quadOut, function(_) {
capsule.doLerp = true;
capsule.cameras = [funnyCam];
// NOW we can interact with the menu
busy = false;
grpCapsules.members[curSelected].sparkle.alpha = 0.7;
playCurSongPreview(capsule);
}, null);
// FlxTween.tween(capsule, {"targetPos.x": capsule.targetPos.x - 50}, 0.6,
@ -963,7 +1139,7 @@ class FreeplayState extends MusicBeatSubState
new FlxTimer().start(2, _ -> {
// dj.fistPump();
FlxG.sound.music.fadeIn(4.0, 0.0, 1.0);
prepForNewRank = false;
});
}
@ -981,7 +1157,10 @@ class FreeplayState extends MusicBeatSubState
var spamTimer:Float = 0;
var spamming:Bool = false;
var busy:Bool = false; // Set to true once the user has pressed enter to select a song.
/**
* If true, disable interaction with the interface.
*/
var busy:Bool = false;
var originalPos:FlxPoint = new FlxPoint();
@ -989,43 +1168,66 @@ class FreeplayState extends MusicBeatSubState
{
super.update(elapsed);
#if debug
if (FlxG.keys.justPressed.T)
{
rankAnimStart();
rankAnimStart(fromResultsParams);
}
if (FlxG.keys.justPressed.H)
{
rankDisplayNew();
}
// if (FlxG.keys.justPressed.H)
// {
// rankDisplayNew(fromResultsParams);
// }
// if (FlxG.keys.justPressed.G)
// {
// rankAnimSlam(fromResultsParams);
// }
if (FlxG.keys.justPressed.G)
{
rankAnimSlam();
sparks.y -= 2;
trace(sparks.x, sparks.y);
}
if (FlxG.keys.justPressed.V)
{
sparks.x -= 2;
trace(sparks.x, sparks.y);
}
if (FlxG.keys.justPressed.N)
{
sparks.x += 2;
trace(sparks.x, sparks.y);
}
if (FlxG.keys.justPressed.B)
{
sparks.y += 2;
trace(sparks.x, sparks.y);
}
if (FlxG.keys.justPressed.I)
{
confirmTextGlow.y -= 1;
trace(confirmTextGlow.x, confirmTextGlow.y);
sparksADD.y -= 2;
trace(sparksADD.x, sparksADD.y);
}
if (FlxG.keys.justPressed.J)
{
confirmTextGlow.x -= 1;
trace(confirmTextGlow.x, confirmTextGlow.y);
sparksADD.x -= 2;
trace(sparksADD.x, sparksADD.y);
}
if (FlxG.keys.justPressed.L)
{
confirmTextGlow.x += 1;
trace(confirmTextGlow.x, confirmTextGlow.y);
sparksADD.x += 2;
trace(sparksADD.x, sparksADD.y);
}
if (FlxG.keys.justPressed.K)
{
confirmTextGlow.y += 1;
trace(confirmTextGlow.x, confirmTextGlow.y);
sparksADD.y += 2;
trace(sparksADD.x, sparksADD.y);
}
#end
if (FlxG.keys.justPressed.F)
if (FlxG.keys.justPressed.F && !busy)
{
var targetSong = grpCapsules.members[curSelected]?.songData;
if (targetSong != null)
@ -1034,24 +1236,52 @@ class FreeplayState extends MusicBeatSubState
var isFav = targetSong.toggleFavorite();
if (isFav)
{
FlxTween.tween(grpCapsules.members[realShit], {angle: 360}, 0.4,
grpCapsules.members[realShit].favIcon.visible = true;
grpCapsules.members[realShit].favIconBlurred.visible = true;
grpCapsules.members[realShit].favIcon.animation.play('fav');
grpCapsules.members[realShit].favIconBlurred.animation.play('fav');
FunkinSound.playOnce(Paths.sound('fav'), 1);
grpCapsules.members[realShit].checkClip();
grpCapsules.members[realShit].selected = grpCapsules.members[realShit].selected; // set selected again, so it can run it's getter function to initialize movement
busy = true;
grpCapsules.members[realShit].doLerp = false;
FlxTween.tween(grpCapsules.members[realShit], {y: grpCapsules.members[realShit].y - 5}, 0.1, {ease: FlxEase.expoOut});
FlxTween.tween(grpCapsules.members[realShit], {y: grpCapsules.members[realShit].y + 5}, 0.1,
{
ease: FlxEase.elasticOut,
onComplete: _ -> {
grpCapsules.members[realShit].favIcon.visible = true;
grpCapsules.members[realShit].favIcon.animation.play('fav');
ease: FlxEase.expoIn,
startDelay: 0.1,
onComplete: function(_) {
grpCapsules.members[realShit].doLerp = true;
busy = false;
}
});
}
else
{
grpCapsules.members[realShit].favIcon.animation.play('fav', false, true);
new FlxTimer().start((1 / 24) * 14, _ -> {
grpCapsules.members[realShit].favIcon.animation.play('fav', true, true, 9);
grpCapsules.members[realShit].favIconBlurred.animation.play('fav', true, true, 9);
FunkinSound.playOnce(Paths.sound('unfav'), 1);
new FlxTimer().start(0.2, _ -> {
grpCapsules.members[realShit].favIcon.visible = false;
grpCapsules.members[realShit].favIconBlurred.visible = false;
grpCapsules.members[realShit].checkClip();
});
new FlxTimer().start((1 / 24) * 24, _ -> {
FlxTween.tween(grpCapsules.members[realShit], {angle: 0}, 0.4, {ease: FlxEase.elasticOut});
});
busy = true;
grpCapsules.members[realShit].doLerp = false;
FlxTween.tween(grpCapsules.members[realShit], {y: grpCapsules.members[realShit].y + 5}, 0.1, {ease: FlxEase.expoOut});
FlxTween.tween(grpCapsules.members[realShit], {y: grpCapsules.members[realShit].y - 5}, 0.1,
{
ease: FlxEase.expoIn,
startDelay: 0.1,
onComplete: function(_) {
grpCapsules.members[realShit].doLerp = true;
busy = false;
}
});
}
}
}
@ -1255,6 +1485,24 @@ class FreeplayState extends MusicBeatSubState
var longestTimer:Float = 0;
// FlxTween.color(bgDad, 0.33, 0xFFFFFFFF, 0xFF555555, {ease: FlxEase.quadOut});
FlxTween.color(pinkBack, 0.25, 0xFFFFD863, 0xFFFFD0D5, {ease: FlxEase.quadOut});
cardGlow.visible = true;
cardGlow.alpha = 1;
cardGlow.scale.set(1, 1);
FlxTween.tween(cardGlow, {alpha: 0, "scale.x": 1.2, "scale.y": 1.2}, 0.25, {ease: FlxEase.sineOut});
orangeBackShit.visible = false;
alsoOrangeLOL.visible = false;
moreWays.visible = false;
funnyScroll.visible = false;
txtNuts.visible = false;
funnyScroll2.visible = false;
moreWays2.visible = false;
funnyScroll3.visible = false;
for (grpSpr in exitMovers.keys())
{
var moveData:MoveData = exitMovers.get(grpSpr);
@ -1299,6 +1547,7 @@ class FreeplayState extends MusicBeatSubState
overrideExisting: true,
restartTrack: false
});
FlxG.sound.music.fadeIn(4.0, 0.0, 1.0);
close();
}
else
@ -1394,6 +1643,7 @@ class FreeplayState extends MusicBeatSubState
{
songCapsule.songData.currentDifficulty = currentDifficulty;
songCapsule.init(null, null, songCapsule.songData);
songCapsule.checkClip();
}
else
{
@ -1485,6 +1735,7 @@ class FreeplayState extends MusicBeatSubState
FunkinSound.playOnce(Paths.sound('confirmMenu'));
dj.confirm();
grpCapsules.members[curSelected].forcePosition();
grpCapsules.members[curSelected].songText.flickerText();
// FlxTween.color(bgDad, 0.33, 0xFFFFFFFF, 0xFF555555, {ease: FlxEase.quadOut});
@ -1522,6 +1773,8 @@ class FreeplayState extends MusicBeatSubState
funnyScroll3.visible = false;
new FlxTimer().start(1, function(tmr:FlxTimer) {
FunkinSound.emptyPartialQueue();
Paths.setCurrentLevel(cap.songData.levelId);
LoadingState.loadPlayState(
{
@ -1564,7 +1817,7 @@ class FreeplayState extends MusicBeatSubState
function changeSelection(change:Int = 0):Void
{
FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4);
if (!prepForNewRank) FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4);
var prevSelected:Int = curSelected;
@ -1605,49 +1858,58 @@ class FreeplayState extends MusicBeatSubState
if (index < curSelected) capsule.targetPos.y -= 100; // another 100 for good measure
}
if (grpCapsules.countLiving() > 0)
if (grpCapsules.countLiving() > 0 && !prepForNewRank)
{
if (curSelected == 0)
{
FunkinSound.playMusic('freeplayRandom',
{
startingVolume: 0.0,
overrideExisting: true,
restartTrack: true
});
FlxG.sound.music.fadeIn(2, 0, 0.8);
}
else
{
// TODO: Stream the instrumental of the selected song?
var didReplace:Bool = FunkinSound.playMusic('freakyMenu',
{
startingVolume: 0.0,
overrideExisting: true,
restartTrack: false
});
if (didReplace)
{
FunkinSound.playMusic('freakyMenu',
{
startingVolume: 0.0,
overrideExisting: true,
restartTrack: false
});
FlxG.sound.music.fadeIn(2, 0, 0.8);
}
}
playCurSongPreview(daSongCapsule);
grpCapsules.members[curSelected].selected = true;
}
}
public function playCurSongPreview(daSongCapsule:SongMenuItem):Void
{
if (curSelected == 0)
{
FunkinSound.playMusic('freeplayRandom',
{
startingVolume: 0.0,
overrideExisting: true,
restartTrack: false
});
FlxG.sound.music.fadeIn(2, 0, 0.8);
}
else
{
var potentiallyErect:String = (currentDifficulty == "erect") || (currentDifficulty == "nightmare") ? "-erect" : "";
FunkinSound.playMusic(daSongCapsule.songData.songId,
{
startingVolume: 0.0,
overrideExisting: true,
restartTrack: false,
pathsFunction: INST,
suffix: potentiallyErect,
partialParams:
{
loadPartial: true,
start: 0.05,
end: 0.25
},
onLoad: function() {
FlxG.sound.music.fadeIn(2, 0, 0.4);
}
});
}
}
/**
* Build an instance of `FreeplayState` that is above the `MainMenuState`.
* @return The MainMenuState with the FreeplayState as a substate.
*/
public static function build(?params:FreeplayStateParams, ?stickers:StickerSubState):MusicBeatState
{
var result = new MainMenuState();
var result:MainMenuState;
if (params?.fromResults.playRankAnim) result = new MainMenuState(true);
else
result = new MainMenuState(false);
result.openSubState(new FreeplayState(params, stickers));
result.persistentUpdate = false;
@ -1750,6 +2012,8 @@ class FreeplaySongData
*/
public var isFav:Bool = false;
public var isNew:Bool = false;
var song:Song;
public var levelId(default, null):String = '';
@ -1765,12 +2029,12 @@ class FreeplaySongData
public var currentDifficulty(default, set):String = Constants.DEFAULT_DIFFICULTY;
public var scoringRank:Null<ScoringRank> = null;
var displayedVariations:Array<String> = [Constants.DEFAULT_VARIATION];
function set_currentDifficulty(value:String):String
{
if (currentDifficulty == value) return value;
currentDifficulty = value;
updateValues(displayedVariations);
return value;
@ -1809,7 +2073,7 @@ class FreeplaySongData
function updateValues(variations:Array<String>):Void
{
this.songDifficulties = song.listDifficulties(variations, false, false);
this.songDifficulties = song.listDifficulties(null, variations, false, false);
if (!this.songDifficulties.contains(currentDifficulty)) currentDifficulty = Constants.DEFAULT_DIFFICULTY;
var songDifficulty:SongDifficulty = song.getDifficulty(currentDifficulty, variations);
@ -1827,6 +2091,10 @@ class FreeplaySongData
{
this.albumId = songDifficulty.album;
}
this.scoringRank = Save.instance.getSongRank(songId, currentDifficulty);
this.isNew = song.isSongNew(currentDifficulty);
}
}

View file

@ -20,6 +20,9 @@ import funkin.graphics.FunkinSprite;
import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
import flixel.addons.effects.FlxTrail;
import funkin.play.scoring.Scoring.ScoringRank;
import funkin.save.Save;
import funkin.save.Save.SaveScoreData;
import flixel.util.FlxColor;
class SongMenuItem extends FlxSpriteGroup
@ -37,10 +40,15 @@ class SongMenuItem extends FlxSpriteGroup
public var selected(default, set):Bool;
public var songText:CapsuleText;
public var favIconBlurred:FlxSprite;
public var favIcon:FlxSprite;
public var ranking:FreeplayRank;
public var blurredRanking:FreeplayRank;
public var fakeRanking:FreeplayRank;
public var fakeBlurredRanking:FreeplayRank;
var ranks:Array<String> = ["fail", "average", "great", "excellent", "perfect", "perfectsick"];
public var targetPos:FlxPoint = new FlxPoint();
@ -70,7 +78,9 @@ class SongMenuItem extends FlxSpriteGroup
var impactThing:FunkinSprite;
public var tempr:Int;
public var sparkle:FlxSprite;
var sparkleTimer:FlxTimer;
public function new(x:Float, y:Float)
{
@ -106,7 +116,7 @@ class SongMenuItem extends FlxSpriteGroup
newText.animation.play('newAnim', true);
newText.setGraphicSize(Std.int(newText.width * 0.9));
newText.visible = false;
// newText.visible = false;
add(newText);
@ -132,15 +142,35 @@ class SongMenuItem extends FlxSpriteGroup
// doesn't get added, simply is here to help with visibility of things for the pop in!
grpHide = new FlxGroup();
var rank:String = FlxG.random.getObject(ranks);
fakeRanking = new FreeplayRank(420, 41);
add(fakeRanking);
tempr = FlxG.random.int(0, 5);
ranking = new FreeplayRank(420, 41, tempr);
fakeBlurredRanking = new FreeplayRank(fakeRanking.x, fakeRanking.y);
fakeBlurredRanking.shader = new GaussianBlurShader(1);
add(fakeBlurredRanking);
fakeRanking.visible = false;
fakeBlurredRanking.visible = false;
ranking = new FreeplayRank(420, 41);
add(ranking);
blurredRanking = new FreeplayRank(ranking.x, ranking.y, tempr);
blurredRanking = new FreeplayRank(ranking.x, ranking.y);
blurredRanking.shader = new GaussianBlurShader(1);
add(blurredRanking);
sparkle = new FlxSprite(ranking.x, ranking.y);
sparkle.frames = Paths.getSparrowAtlas('freeplay/sparkle');
sparkle.animation.addByPrefix('sparkle', 'sparkle', 24, false);
sparkle.animation.play('sparkle', true);
sparkle.scale.set(0.8, 0.8);
sparkle.blend = BlendMode.ADD;
sparkle.visible = false;
sparkle.alpha = 0.7;
add(sparkle);
// ranking.loadGraphic(Paths.image('freeplay/ranks/' + rank));
// ranking.scale.x = ranking.scale.y = realScaled;
// ranking.alpha = 0.75;
@ -179,6 +209,16 @@ class SongMenuItem extends FlxSpriteGroup
add(pixelIcon);
grpHide.add(pixelIcon);
favIconBlurred = new FlxSprite(380, 40);
favIconBlurred.frames = Paths.getSparrowAtlas('freeplay/favHeart');
favIconBlurred.animation.addByPrefix('fav', 'favorite heart', 24, false);
favIconBlurred.animation.play('fav');
favIconBlurred.setGraphicSize(50, 50);
favIconBlurred.blend = BlendMode.ADD;
favIconBlurred.shader = new GaussianBlurShader(1.2);
favIconBlurred.visible = false;
add(favIconBlurred);
favIcon = new FlxSprite(380, 40);
favIcon.frames = Paths.getSparrowAtlas('freeplay/favHeart');
favIcon.animation.addByPrefix('fav', 'favorite heart', 24, false);
@ -196,6 +236,13 @@ class SongMenuItem extends FlxSpriteGroup
setVisibleGrp(false);
}
function sparkleEffect(timer:FlxTimer):Void
{
sparkle.setPosition(FlxG.random.float(ranking.x - 20, ranking.x + 3), FlxG.random.float(ranking.y - 29, ranking.y + 4));
sparkle.animation.play('sparkle', true);
sparkleTimer = new FlxTimer().start(FlxG.random.float(1.2, 4.5), sparkleEffect);
}
// no way to grab weeks rn, so this needs to be done :/
// negative values mean weekends
function checkWeek(name:String):Void
@ -255,7 +302,7 @@ class SongMenuItem extends FlxSpriteGroup
var clipType:Int = 0;
if (ranking.visible == true) clipType += 1;
if (favIcon.visible == true) clipType += 1;
if (favIcon.visible == true) clipType = 2;
switch (clipType)
{
case 2:
@ -268,8 +315,6 @@ class SongMenuItem extends FlxSpriteGroup
function updateBPM(newBPM:Int):Void
{
trace(newBPM);
var shiftX:Float = 191;
var tempShift:Float = 0;
@ -317,7 +362,7 @@ class SongMenuItem extends FlxSpriteGroup
var evilTrail:FlxTrail;
public function fadeAnim()
public function fadeAnim():Void
{
impactThing = new FunkinSprite(0, 0);
impactThing.frames = capsule.frames;
@ -344,19 +389,19 @@ class SongMenuItem extends FlxSpriteGroup
});
add(evilTrail);
switch (tempr)
switch (ranking.rank)
{
case 0:
case SHIT:
evilTrail.color = 0xFF6044FF;
case 1:
case GOOD:
evilTrail.color = 0xFFEF8764;
case 2:
case GREAT:
evilTrail.color = 0xFFEAF6FF;
case 3:
case EXCELLENT:
evilTrail.color = 0xFFFDCB42;
case 4:
case PERFECT:
evilTrail.color = 0xFFFF58B4;
case 5:
case PERFECT_GOLD:
evilTrail.color = 0xFFFFB619;
}
}
@ -375,7 +420,7 @@ class SongMenuItem extends FlxSpriteGroup
switch (i)
{
case 0:
if (newRating > 10)
if (newRating < 10)
{
bigNumbers[i].digit = 0;
}
@ -393,6 +438,21 @@ class SongMenuItem extends FlxSpriteGroup
// diffRatingSprite.visible = false;
}
function updateScoringRank(newRank:Null<ScoringRank>):Void
{
if (sparkleTimer != null) sparkleTimer.cancel();
sparkle.visible = false;
this.ranking.rank = newRank;
this.blurredRanking.rank = newRank;
if (newRank == PERFECT_GOLD)
{
sparkleTimer = new FlxTimer().start(1, sparkleEffect);
sparkle.visible = true;
}
}
function set_hsvShader(value:HSVShader):HSVShader
{
this.hsvShader = value;
@ -441,6 +501,8 @@ class SongMenuItem extends FlxSpriteGroup
if (songData?.songCharacter != null) setCharacter(songData.songCharacter);
updateBPM(Std.int(songData?.songStartingBpm) ?? 0);
updateDifficultyRating(songData?.difficultyRating ?? 0);
updateScoringRank(songData?.scoringRank);
newText.visible = songData?.isNew;
// Update opacity, offsets, etc.
updateSelected();
@ -653,56 +715,66 @@ class SongMenuItem extends FlxSpriteGroup
capsule.offset.x = this.selected ? 0 : -5;
capsule.animation.play(this.selected ? "selected" : "unselected");
ranking.alpha = this.selected ? 1 : 0.7;
favIcon.alpha = this.selected ? 1 : 0.6;
favIconBlurred.alpha = this.selected ? 1 : 0;
ranking.color = this.selected ? 0xFFFFFFFF : 0xFFAAAAAA;
if (selected)
{
if (songText.tooLong == true) songText.initMove();
}
else
{
if (songText.tooLong == true) songText.resetText();
}
if (songText.tooLong) songText.resetText();
if (selected && songText.tooLong) songText.initMove();
}
}
class FreeplayRank extends FlxSprite
{
public var rank(default, set):Int = 0;
public var rank(default, set):Null<ScoringRank> = null;
var numToRank:Array<String> = ["LOSS", "GOOD", "GREAT", "EXCELLENT", "PERFECT", "PERFECTSICK"];
function set_rank(val):Int
function set_rank(val:Null<ScoringRank>):Null<ScoringRank>
{
animation.play(numToRank[val], true, false);
rank = val;
centerOffsets(false);
switch (val)
if (rank == null || val == null)
{
case 0:
// offset.x -= 1;
case 1:
// offset.x -= 1;
offset.y -= 8;
case 2:
// offset.x -= 1;
offset.y -= 8;
case 3:
// offset.y += 5;
case 4:
// offset.y += 5;
default:
centerOffsets(false);
this.visible = false;
}
updateHitbox();
return val;
else
{
this.visible = true;
animation.play(val.getFreeplayRankIconAsset(), true, false);
centerOffsets(false);
switch (val)
{
case SHIT:
// offset.x -= 1;
case GOOD:
// offset.x -= 1;
offset.y -= 8;
case GREAT:
// offset.x -= 1;
offset.y -= 8;
case EXCELLENT:
// offset.y += 5;
case PERFECT:
// offset.y += 5;
case PERFECT_GOLD:
// offset.y += 5;
default:
centerOffsets(false);
this.visible = false;
}
updateHitbox();
}
return rank = val;
}
public var baseY:Float = 0;
public var baseX:Float = 0;
public var baseY:Float = 0;
public function new(x:Float, y:Float, ?initRank:Int = 0)
public function new(x:Float, y:Float)
{
super(x, y);
@ -717,9 +789,7 @@ class FreeplayRank extends FlxSprite
blend = BlendMode.ADD;
this.rank = initRank;
animation.play(numToRank[initRank], true);
this.rank = null;
// setGraphicSize(Std.int(width * 0.9));
scale.set(0.9, 0.9);

View file

@ -42,6 +42,15 @@ class MainMenuState extends MusicBeatState
var magenta:FlxSprite;
var camFollow:FlxObject;
var overrideMusic:Bool = false;
public function new(?_overrideMusic:Bool = false)
{
super();
overrideMusic = _overrideMusic;
}
override function create():Void
{
#if discord_rpc
@ -49,10 +58,12 @@ class MainMenuState extends MusicBeatState
DiscordClient.changePresence("In the Menus", null);
#end
FlxG.cameras.reset(new FunkinCamera('mainMenu'));
transIn = FlxTransitionableState.defaultTransIn;
transOut = FlxTransitionableState.defaultTransOut;
playMenuMusic();
if(overrideMusic == false) playMenuMusic();
// We want the state to always be able to begin with being able to accept inputs and show the anims of the menu items.
persistentUpdate = true;
@ -161,7 +172,7 @@ class MainMenuState extends MusicBeatState
function playMenuMusic():Void
{
FunkinSound.playMusic('freakyMenu',
FunkinSound.playMusic('freakyMenu',
{
overrideExisting: true,
restartTrack: false
@ -170,7 +181,6 @@ class MainMenuState extends MusicBeatState
function resetCamStuff():Void
{
FlxG.cameras.reset(new FunkinCamera('mainMenu'));
FlxG.camera.follow(camFollow, null, 0.06);
FlxG.camera.snapToTarget();
}
@ -329,6 +339,8 @@ class MainMenuState extends MusicBeatState
persistentUpdate = false;
FlxG.state.openSubState(new DebugMenuSubState());
// reset camera when debug menu is closed
subStateClosed.addOnce(_ -> resetCamStuff());
}
#end

View file

@ -11,12 +11,15 @@ class LevelProp extends Bopper
function set_propData(value:LevelPropData):LevelPropData
{
// Only reset the prop if the asset path has changed.
if (propData == null || value?.assetPath != propData?.assetPath)
if (propData == null || !(thx.Dynamics.equals(value, propData)))
{
this.propData = value;
this.visible = this.propData != null;
danceEvery = this.propData?.danceEvery ?? 0;
applyData();
}
this.visible = (value != null);
danceEvery = this.propData?.danceEvery ?? 0;
return this.propData;
}

View file

@ -306,7 +306,7 @@ class StoryMenuState extends MusicBeatState
{
Conductor.instance.update();
highScoreLerp = Std.int(MathUtil.smoothLerp(highScoreLerp, highScore, elapsed, 0.5));
highScoreLerp = Std.int(MathUtil.smoothLerp(highScoreLerp, highScore, elapsed, 0.25));
scoreText.text = 'LEVEL SCORE: ${Math.round(highScoreLerp)}';
@ -466,6 +466,9 @@ class StoryMenuState extends MusicBeatState
// Disable the funny music thing for now.
// funnyMusicThing();
}
updateText();
refresh();
}
final FADE_OUT_TIME:Float = 1.5;

View file

@ -67,9 +67,11 @@ class TitleState extends MusicBeatState
// DEBUG BULLSHIT
// netConnection.addEventListener(MouseEvent.MOUSE_DOWN, overlay_onMouseDown);
new FlxTimer().start(1, function(tmr:FlxTimer) {
if (!initialized) new FlxTimer().start(1, function(tmr:FlxTimer) {
startIntro();
});
else
startIntro();
}
function client_onMetaData(metaData:Dynamic)
@ -118,11 +120,11 @@ class TitleState extends MusicBeatState
function startIntro():Void
{
playMenuMusic();
if (!initialized || FlxG.sound.music == null) playMenuMusic();
persistentUpdate = true;
var bg:FunkinSprite = new FunkinSprite().makeSolidColor(FlxG.width, FlxG.height, FlxColor.BLACK);
var bg:FunkinSprite = new FunkinSprite(-1).makeSolidColor(FlxG.width + 2, FlxG.height, FlxColor.BLACK);
bg.screenCenter();
add(bg);
@ -231,7 +233,7 @@ class TitleState extends MusicBeatState
overrideExisting: true,
restartTrack: true
});
// Fade from 0.0 to 0.7 over 4 seconds
// Fade from 0.0 to 1 over 4 seconds
if (shouldFadeIn) FlxG.sound.music.fadeIn(4.0, 0.0, 1.0);
}

View file

@ -136,6 +136,8 @@ class FunkinPreloader extends FlxBasePreloader
// We can't even call trace() yet, until Flixel loads.
trace('Initializing custom preloader...');
funkin.util.CLIUtil.resetWorkingDir();
this.siteLockTitleText = Constants.SITE_LOCK_TITLE;
this.siteLockBodyText = Constants.SITE_LOCK_DESC;
}

View file

@ -248,6 +248,11 @@ class Constants
*/
public static final DEFAULT_ARTIST:String = 'Unknown';
/**
* The default charter for songs.
*/
public static final DEFAULT_CHARTER:String = 'Unknown';
/**
* The default note style for songs.
*/
@ -462,7 +467,7 @@ class Constants
// % 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_GREAT_THRESHOLD:Float = 0.80;
public static final RANK_GOOD_THRESHOLD:Float = 0.60;
// public static final RANK_SHIT_THRESHOLD:Float = 0.00;

View file

@ -23,6 +23,8 @@ class VersionUtil
{
try
{
var versionRaw:thx.semver.Version.SemVer = version;
trace('${versionRaw} satisfies (${versionRule})? ${version.satisfies(versionRule)}');
return version.satisfies(versionRule);
}
catch (e)
@ -39,13 +41,28 @@ class VersionUtil
if (thx.Types.isAnonymousObject(versionData.version))
{
// This is bad! versionData.version should be an array!
trace('[SAVE] Version data repair required! (got ${versionData.version})');
// Turn the objects back into arrays.
// I'd use DynamicsT.values but IDK if it maintains order
versionData.version = [versionData.version[0], versionData.version[1], versionData.version[2]];
// This is so jank but it should work.
var buildData:Dynamic<String> = cast versionData.build;
var buildDataFixed:Array<thx.semver.Version.Identifier> = thx.Dynamics.DynamicsT.values(buildData)
.map(function(d:Dynamic) return StringId(d.toString()));
versionData.build = buildDataFixed;
var preData:Dynamic<String> = cast versionData.pre;
var preDataFixed:Array<thx.semver.Version.Identifier> = thx.Dynamics.DynamicsT.values(preData).map(function(d:Dynamic) return StringId(d.toString()));
versionData.pre = preDataFixed;
var fixedVersion:thx.semver.Version = versionData;
trace('[SAVE] Fixed version: ${fixedVersion}');
return fixedVersion;
}
else
{
trace('[SAVE] Version data repair not required (got ${version})');
// No need for repair.
return version;
}