diff --git a/assets b/assets index d2b3dcab9..486ea1cdc 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit d2b3dcab92f5cb4b11774a80cbe2e270972a9577 +Subproject commit 486ea1cdc37a1f1907ba9231b0a1946ff4051f27 diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx index 95c9b9a00..3d27c4336 100644 --- a/source/funkin/FreeplayState.hx +++ b/source/funkin/FreeplayState.hx @@ -1,5 +1,6 @@ package funkin; +import funkin.play.song.Song; import flash.text.TextField; import flixel.addons.display.FlxGridOverlay; import flixel.addons.transition.FlxTransitionableState; @@ -48,10 +49,13 @@ import lime.utils.Assets; class FreeplayState extends MusicBeatSubState { - var songs:Array = []; + var songs:Array> = []; + + var diffIdsCurrent:Array = []; + var diffIdsTotal:Array = []; var curSelected:Int = 0; - var curDifficulty:Int = 1; + var currentDifficulty:String = Constants.DEFAULT_DIFFICULTY; var fp:FreeplayScore; var txtCompletion:FlxText; @@ -60,7 +64,7 @@ class FreeplayState extends MusicBeatSubState var lerpScore:Float = 0; var intendedScore:Int = 0; - var grpDifficulties:FlxSpriteGroup; + var grpDifficulties:FlxTypedSpriteGroup; var coolColors:Array = [ 0xff9271fd, @@ -85,6 +89,10 @@ class FreeplayState extends MusicBeatSubState var stickerSubState:StickerSubState; + // + static var rememberedDifficulty:Null = "normal"; + static var rememberedSongId:Null = null; + public function new(?stickers:StickerSubState = null) { if (stickers != null) @@ -130,14 +138,23 @@ class FreeplayState extends MusicBeatSubState songs.push(null); // programmatically adds the songs via LevelRegistry and SongRegistry - for (coolWeek in LevelRegistry.instance.listBaseGameLevelIds()) + for (levelId in LevelRegistry.instance.listBaseGameLevelIds()) { - for (songId in LevelRegistry.instance.parseEntryData(coolWeek).songs) + for (songId in LevelRegistry.instance.parseEntryData(levelId).songs) { - var metadata = SongRegistry.instance.parseEntryMetadata(songId); - var char = metadata.playData.characters.opponent; - var songName = metadata.songName; - addSong(songId, songName, coolWeek, char); + var song:Song = SongRegistry.instance.fetchEntry(songId); + var songBaseDifficulty:SongDifficulty = song.getDifficulty(Constants.DEFAULT_DIFFICULTY); + + var songName = songBaseDifficulty.songName; + var songOpponent = songBaseDifficulty.characters.opponent; + var songDifficulties = song.listDifficulties(); + + songs.push(new FreeplaySongData(songId, songName, levelId, songOpponent, songDifficulties)); + + for (difficulty in songDifficulties) + { + diffIdsTotal.pushUnique(difficulty); + } } } @@ -283,7 +300,7 @@ class FreeplayState extends MusicBeatSubState grpCapsules = new FlxTypedGroup(); add(grpCapsules); - grpDifficulties = new FlxSpriteGroup(-300, 80); + grpDifficulties = new FlxTypedSpriteGroup(-300, 80); add(grpDifficulties); exitMovers.set([grpDifficulties], @@ -293,15 +310,22 @@ class FreeplayState extends MusicBeatSubState wait: 0 }); - grpDifficulties.add(new FlxSprite().loadGraphic(Paths.image('freeplay/freeplayEasy'))); - grpDifficulties.add(new FlxSprite().loadGraphic(Paths.image('freeplay/freeplayNorm'))); - grpDifficulties.add(new FlxSprite().loadGraphic(Paths.image('freeplay/freeplayHard'))); + for (diffId in diffIdsTotal) + { + var diffSprite:DifficultySprite = new DifficultySprite(diffId); + diffSprite.difficultyId = diffId; + grpDifficulties.add(diffSprite); + } grpDifficulties.group.forEach(function(spr) { spr.visible = false; }); - grpDifficulties.group.members[curDifficulty].visible = true; + for (diffSprite in grpDifficulties.group.members) + { + if (diffSprite == null) continue; + if (diffSprite.difficultyId == currentDifficulty) diffSprite.visible = true; + } var albumArt:FlxAtlasSprite = new FlxAtlasSprite(640, 360, Paths.animateAtlas("freeplay/albumRoll")); albumArt.visible = false; @@ -572,15 +596,12 @@ class FreeplayState extends MusicBeatSubState FlxG.console.registerFunction("changeSelection", changeSelection); + rememberSelection(); + changeSelection(); changeDiff(); } - public function addSong(songId:String, songName:String, levelId:String, songCharacter:String) - { - songs.push(new FreeplaySongData(songId, songName, levelId, songCharacter)); - } - var touchY:Float = 0; var touchX:Float = 0; var dxTouch:Float = 0; @@ -850,28 +871,24 @@ class FreeplayState extends MusicBeatSubState { touchTimer = 0; - curDifficulty += change; + var currentDifficultyIndex = diffIdsCurrent.indexOf(currentDifficulty); - if (curDifficulty < 0) curDifficulty = 2; - if (curDifficulty > 2) curDifficulty = 0; + if (currentDifficultyIndex == -1) currentDifficultyIndex = diffIdsCurrent.indexOf(Constants.DEFAULT_DIFFICULTY); - var targetDifficulty:String = switch (curDifficulty) - { - case 0: - 'easy'; - case 1: - 'normal'; - case 2: - 'hard'; - default: 'normal'; - }; + currentDifficultyIndex += change; + + if (currentDifficultyIndex < 0) currentDifficultyIndex = diffIdsCurrent.length - 1; + if (currentDifficultyIndex >= diffIdsCurrent.length) currentDifficultyIndex = 0; + + currentDifficulty = diffIdsCurrent[currentDifficultyIndex]; var daSong = songs[curSelected]; if (daSong != null) { - var songScore:SaveScoreData = Save.get().getSongScore(songs[curSelected].songId, targetDifficulty); + var songScore:SaveScoreData = Save.get().getSongScore(songs[curSelected].songId, currentDifficulty); intendedScore = songScore?.score ?? 0; intendedCompletion = songScore?.accuracy ?? 0.0; + rememberedDifficulty = currentDifficulty; } else { @@ -879,19 +896,31 @@ class FreeplayState extends MusicBeatSubState intendedCompletion = 0.0; } - grpDifficulties.group.forEach(function(spr) { - spr.visible = false; + grpDifficulties.group.forEach(function(diffSprite) { + diffSprite.visible = false; }); - var curShit:FlxSprite = grpDifficulties.group.members[curDifficulty]; - - curShit.visible = true; - curShit.offset.y += 5; - curShit.alpha = 0.5; - new FlxTimer().start(1 / 24, function(swag) { - curShit.alpha = 1; - curShit.updateHitbox(); - }); + for (diffSprite in grpDifficulties.group.members) + { + if (diffSprite == null) continue; + if (diffSprite.difficultyId == currentDifficulty) + { + if (change != 0) + { + diffSprite.visible = true; + diffSprite.offset.y += 5; + diffSprite.alpha = 0.5; + new FlxTimer().start(1 / 24, function(swag) { + diffSprite.alpha = 1; + diffSprite.updateHitbox(); + }); + } + else + { + diffSprite.visible = true; + } + } + } } // Clears the cache of songs, frees up memory, they' ll have to be loaded in later tho function clearDaCache(actualSongTho:String) @@ -899,6 +928,7 @@ class FreeplayState extends MusicBeatSubState { for (song in songs) { + if (song == null) return; if (song.songName != actualSongTho) { trace('trying to remove: ' + song.songName); @@ -913,22 +943,7 @@ class FreeplayState extends MusicBeatSubState var songId:String = cap.songTitle.toLowerCase(); var targetSong:Song = SongRegistry.instance.fetchEntry(songId); - var targetDifficulty:String = switch (curDifficulty) - { - case 0: - 'easy'; - case 1: - 'normal'; - case 2: - 'hard'; - default: 'normal'; - }; - - // TODO: Implement additional difficulties into the interface properly. - if (FlxG.keys.pressed.E) - { - targetDifficulty = 'erect'; - } + var targetDifficulty:String = currentDifficulty; // TODO: Implement Pico into the interface properly. var targetCharacter:String = 'bf'; @@ -957,6 +972,22 @@ class FreeplayState extends MusicBeatSubState }); } + function rememberSelection():Void + { + if (rememberedSongId != null) + { + curSelected = songs.findIndex(function(song) { + if (song == null) return false; + return song.songId == rememberedSongId; + }); + } + + if (rememberedDifficulty != null) + { + currentDifficulty = rememberedDifficulty; + } + } + function changeSelection(change:Int = 0) { // NGio.logEvent('Fresh'); @@ -968,28 +999,19 @@ class FreeplayState extends MusicBeatSubState if (curSelected < 0) curSelected = grpCapsules.countLiving() - 1; if (curSelected >= grpCapsules.countLiving()) curSelected = 0; - var targetDifficulty:String = switch (curDifficulty) - { - case 0: - 'easy'; - case 1: - 'normal'; - case 2: - 'hard'; - default: 'normal'; - }; - var daSong = songs[curSelected]; if (daSong != null) { - var songScore:SaveScoreData = Save.get().getSongScore(daSong.songId, targetDifficulty); - intendedScore = songScore?.score ?? 0; - intendedCompletion = songScore?.accuracy ?? 0.0; + diffIdsCurrent = daSong.songDifficulties; + rememberedSongId = daSong.songId; + changeDiff(); } else { intendedScore = 0; intendedCompletion = 0.0; + rememberedSongId = null; + rememberedDifficulty = null; } for (index => capsule in grpCapsules.members) @@ -1011,6 +1033,10 @@ class FreeplayState extends MusicBeatSubState FlxG.sound.playMusic(Paths.music('freeplay/freeplayRandom'), 0); FlxG.sound.music.fadeIn(2, 0, 0.8); } + else + { + // TODO: Try to stream the music? + } grpCapsules.members[curSelected].selected = true; } } @@ -1078,19 +1104,21 @@ enum abstract FilterType(String) class FreeplaySongData { + public var isFav:Bool = false; + public var songId:String = ""; public var songName:String = ""; public var levelId:String = ""; public var songCharacter:String = ""; - public var isFav:Bool = false; + public var songDifficulties:Array = []; - public function new(songId:String, songName:String, levelId:String, songCharacter:String, isFav:Bool = false) + public function new(songId:String, songName:String, levelId:String, songCharacter:String, songDifficulties:Array) { this.songId = songId; this.songName = songName; this.levelId = levelId; this.songCharacter = songCharacter; - this.isFav = isFav; + this.songDifficulties = songDifficulties; } } @@ -1101,3 +1129,17 @@ typedef MoveData = var ?speed:Float; var ?wait:Float; } + +class DifficultySprite extends FlxSprite +{ + public var difficultyId:String; + + public function new(diffId:String) + { + super(); + + difficultyId = diffId; + + loadGraphic(Paths.image('freeplay/freeplay' + diffId)); + } +} diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx index d11c7744b..000572d6a 100644 --- a/source/funkin/play/song/Song.hx +++ b/source/funkin/play/song/Song.hx @@ -162,7 +162,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry = null; + static var rememberedDifficulty:Null = "normal"; + public function new(?stickers:StickerSubState = null) { super(); @@ -133,6 +136,8 @@ class StoryMenuState extends MusicBeatState updateData(); + rememberSelection(); + // Explicitly define the background color. this.bgColor = FlxColor.BLACK; @@ -185,6 +190,7 @@ class StoryMenuState extends MusicBeatState leftDifficultyArrow.animation.play('idle'); add(leftDifficultyArrow); + buildDifficultySprite(Constants.DEFAULT_DIFFICULTY); buildDifficultySprite(); rightDifficultyArrow = new FlxSprite(difficultySprite.x + difficultySprite.width + 10, leftDifficultyArrow.y); @@ -207,6 +213,18 @@ class StoryMenuState extends MusicBeatState #end } + function rememberSelection():Void + { + if (rememberedLevelId != null) + { + currentLevelId = rememberedLevelId; + } + if (rememberedDifficulty != null) + { + currentDifficultyId = rememberedDifficulty; + } + } + function playMenuMusic():Void { if (FlxG.sound.music == null || !FlxG.sound.music.playing) @@ -228,34 +246,35 @@ class StoryMenuState extends MusicBeatState isLevelUnlocked = currentLevel == null ? false : currentLevel.isUnlocked(); } - function buildDifficultySprite():Void + function buildDifficultySprite(?diff:String):Void { + if (diff == null) diff = currentDifficultyId; remove(difficultySprite); - difficultySprite = difficultySprites.get(currentDifficultyId); + difficultySprite = difficultySprites.get(diff); if (difficultySprite == null) { difficultySprite = new FlxSprite(leftDifficultyArrow.x + leftDifficultyArrow.width + 10, leftDifficultyArrow.y); - if (Assets.exists(Paths.file('images/storymenu/difficulties/${currentDifficultyId}.xml'))) + if (Assets.exists(Paths.file('images/storymenu/difficulties/${diff}.xml'))) { - difficultySprite.frames = Paths.getSparrowAtlas('storymenu/difficulties/${currentDifficultyId}'); + difficultySprite.frames = Paths.getSparrowAtlas('storymenu/difficulties/${diff}'); difficultySprite.animation.addByPrefix('idle', 'idle0', 24, true); difficultySprite.animation.play('idle'); } else { - difficultySprite.loadGraphic(Paths.image('storymenu/difficulties/${currentDifficultyId}')); + difficultySprite.loadGraphic(Paths.image('storymenu/difficulties/${diff}')); } - difficultySprites.set(currentDifficultyId, difficultySprite); + difficultySprites.set(diff, difficultySprite); - difficultySprite.x += (difficultySprites.get('normal').width - difficultySprite.width) / 2; + difficultySprite.x += (difficultySprites.get(Constants.DEFAULT_DIFFICULTY).width - difficultySprite.width) / 2; } difficultySprite.alpha = 0; difficultySprite.y = leftDifficultyArrow.y - 15; var targetY:Float = leftDifficultyArrow.y + 10; - targetY -= (difficultySprite.height - difficultySprites.get('normal').height) / 2; + targetY -= (difficultySprite.height - difficultySprites.get(Constants.DEFAULT_DIFFICULTY).height) / 2; FlxTween.tween(difficultySprite, {y: targetY, alpha: 1}, 0.07); add(difficultySprite); @@ -399,6 +418,7 @@ class StoryMenuState extends MusicBeatState var previousLevelId:String = currentLevelId; currentLevelId = levelList[currentIndex]; + rememberedLevelId = currentLevelId; updateData(); @@ -442,6 +462,7 @@ class StoryMenuState extends MusicBeatState var hasChanged:Bool = currentDifficultyId != difficultyList[currentIndex]; currentDifficultyId = difficultyList[currentIndex]; + rememberedDifficulty = currentDifficultyId; if (difficultyList.length <= 1) { diff --git a/source/funkin/util/tools/ArrayTools.hx b/source/funkin/util/tools/ArrayTools.hx index 67cc1c041..ad24a23a7 100644 --- a/source/funkin/util/tools/ArrayTools.hx +++ b/source/funkin/util/tools/ArrayTools.hx @@ -23,6 +23,13 @@ class ArrayTools return result; } + public static function pushUnique(array:Array, element:T):Bool + { + if (array.contains(element)) return false; + array.push(element); + return true; + } + /** * Return the first element of the array that satisfies the predicate, or null if none do. * @param input The array to search @@ -38,6 +45,21 @@ class ArrayTools return null; } + /** + * Return the index of the first element of the array that satisfies the predicate, or `-1` if none do. + * @param input The array to search + * @param predicate The predicate to call + * @return The index of the result + */ + public static function findIndex(input:Array, predicate:T->Bool):Int + { + for (index in 0...input.length) + { + if (predicate(input[index])) return index; + } + return -1; + } + /** * Remove all elements from the array, without creating a new array. * @param array The array to clear.