1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2025-11-26 06:09:02 +00:00

Working Pico DJ

This commit is contained in:
EliteMasterEric 2024-06-20 16:17:53 -04:00
parent 263039f52c
commit 9b3a748f37
9 changed files with 216 additions and 147 deletions

2
assets

@ -1 +1 @@
Subproject commit 6ec72f8aeb5ab77997dee4e2e98ae03f0ec347b8 Subproject commit 8dd51cde0b9a3730abe9f97d0f50365c396ca784

View file

@ -11,10 +11,17 @@ class Paths
{ {
static var currentLevel:Null<String> = null; static var currentLevel:Null<String> = null;
public static function setCurrentLevel(name:String):Void public static function setCurrentLevel(name:Null<String>):Void
{
if (name == null)
{
currentLevel = null;
}
else
{ {
currentLevel = name.toLowerCase(); currentLevel = name.toLowerCase();
} }
}
public static function stripLibrary(path:String):String public static function stripLibrary(path:String):String
{ {

View file

@ -35,6 +35,7 @@ class PlayerData
* Data for displaying this character in the Freeplay menu. * Data for displaying this character in the Freeplay menu.
* If null, display no DJ. * If null, display no DJ.
*/ */
@:optional
public var freeplayDJ:Null<PlayerFreeplayDJData> = null; public var freeplayDJ:Null<PlayerFreeplayDJData> = null;
/** /**
@ -73,9 +74,25 @@ class PlayerFreeplayDJData
var assetPath:String; var assetPath:String;
var animations:Array<AnimationData>; var animations:Array<AnimationData>;
@:optional
@:default("BOYFRIEND")
var text1:String;
@:optional
@:default("HOT BLOODED IN MORE WAYS THAN ONE")
var text2:String;
@:optional
@:default("PROTECT YO NUTS")
var text3:String;
@:jignored @:jignored
var animationMap:Map<String, AnimationData>; var animationMap:Map<String, AnimationData>;
@:jignored
var prefixToOffsetsMap:Map<String, Array<Float>>;
@:optional @:optional
var cartoon:Null<PlayerFreeplayDJCartoonData>; var cartoon:Null<PlayerFreeplayDJCartoonData>;
@ -87,11 +104,14 @@ class PlayerFreeplayDJData
function mapAnimations() function mapAnimations()
{ {
if (animationMap == null) animationMap = new Map(); if (animationMap == null) animationMap = new Map();
if (prefixToOffsetsMap == null) prefixToOffsetsMap = new Map();
animationMap.clear(); animationMap.clear();
prefixToOffsetsMap.clear();
for (anim in animations) for (anim in animations)
{ {
animationMap.set(anim.name, anim); animationMap.set(anim.name, anim);
prefixToOffsetsMap.set(anim.prefix, anim.offsets);
} }
} }
@ -100,6 +120,15 @@ class PlayerFreeplayDJData
return Paths.animateAtlas(assetPath); return Paths.animateAtlas(assetPath);
} }
public function getFreeplayDJText(index:Int):String {
switch (index) {
case 1: return text1;
case 2: return text2;
case 3: return text3;
default: return '';
}
}
public function getAnimationPrefix(name:String):Null<String> public function getAnimationPrefix(name:String):Null<String>
{ {
if (animationMap.size() == 0) mapAnimations(); if (animationMap.size() == 0) mapAnimations();
@ -109,13 +138,16 @@ class PlayerFreeplayDJData
return anim.prefix; return anim.prefix;
} }
public function getAnimationOffsets(name:String):Null<Array<Float>> public function getAnimationOffsetsByPrefix(?prefix:String):Array<Float>
{ {
if (animationMap.size() == 0) mapAnimations(); if (prefixToOffsetsMap.size() == 0) mapAnimations();
if (prefix == null) return [0, 0];
return prefixToOffsetsMap.get(prefix);
}
var anim = animationMap.get(name); public function getAnimationOffsets(name:String):Array<Float>
if (anim == null) return null; {
return anim.offsets; return getAnimationOffsetsByPrefix(getAnimationPrefix(name));
} }
// TODO: These should really be frame labels, ehe. // TODO: These should really be frame labels, ehe.

View file

@ -590,7 +590,7 @@ enum abstract ScoringRank(String)
} }
} }
public function getFreeplayRankIconAsset():Null<String> public function getFreeplayRankIconAsset():String
{ {
switch (abstract) switch (abstract)
{ {
@ -607,7 +607,7 @@ enum abstract ScoringRank(String)
case SHIT: case SHIT:
return 'LOSS'; return 'LOSS';
default: default:
return null; return 'LOSS';
} }
} }

View file

@ -135,8 +135,12 @@ class FreeplayDJ extends FlxAtlasSprite
timeIdling = 0; timeIdling = 0;
case Cartoon: case Cartoon:
var animPrefix = playableCharData.getAnimationPrefix('cartoon'); var animPrefix = playableCharData.getAnimationPrefix('cartoon');
if (animPrefix == null) {
currentState = IdleEasterEgg;
} else {
if (getCurrentAnimation() != animPrefix) playFlashAnimation(animPrefix, true); if (getCurrentAnimation() != animPrefix) playFlashAnimation(animPrefix, true);
timeIdling = 0; timeIdling = 0;
}
default: default:
// I shit myself. // I shit myself.
} }
@ -324,7 +328,7 @@ class FreeplayDJ extends FlxAtlasSprite
function applyAnimOffset() function applyAnimOffset()
{ {
var AnimName = getCurrentAnimation(); var AnimName = getCurrentAnimation();
var daOffset = playableCharData.getAnimationOffsets(AnimName); var daOffset = playableCharData.getAnimationOffsetsByPrefix(AnimName);
if (daOffset != null) if (daOffset != null)
{ {
var xValue = daOffset[0]; var xValue = daOffset[0];
@ -335,12 +339,12 @@ class FreeplayDJ extends FlxAtlasSprite
yValue += offsetY; yValue += offsetY;
} }
trace('Successfully applied offset: ' + xValue + ', ' + yValue); trace('Successfully applied offset ($AnimName): ' + xValue + ', ' + yValue);
offset.set(xValue, yValue); offset.set(xValue, yValue);
} }
else else
{ {
trace('No offset found, defaulting to: 0, 0'); trace('No offset found ($AnimName), defaulting to: 0, 0');
offset.set(0, 0); offset.set(0, 0);
} }
} }

View file

@ -1,54 +1,55 @@
package funkin.ui.freeplay; package funkin.ui.freeplay;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
import flixel.addons.transition.FlxTransitionableState; import flixel.addons.transition.FlxTransitionableState;
import flixel.addons.ui.FlxInputText; import flixel.addons.ui.FlxInputText;
import flixel.FlxCamera; import flixel.FlxCamera;
import flixel.FlxSprite; import flixel.FlxSprite;
import flixel.group.FlxGroup; import flixel.group.FlxGroup;
import funkin.graphics.shaders.GaussianBlurShader;
import flixel.group.FlxGroup.FlxTypedGroup; import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup; import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
import flixel.input.touch.FlxTouch; import flixel.input.touch.FlxTouch;
import flixel.math.FlxAngle; import flixel.math.FlxAngle;
import flixel.math.FlxPoint; import flixel.math.FlxPoint;
import openfl.display.BlendMode;
import flixel.system.debug.watch.Tracker.TrackerProfile; import flixel.system.debug.watch.Tracker.TrackerProfile;
import flixel.text.FlxText; import flixel.text.FlxText;
import flixel.tweens.FlxEase; import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween; import flixel.tweens.FlxTween;
import flixel.tweens.misc.ShakeTween;
import flixel.util.FlxColor; import flixel.util.FlxColor;
import flixel.util.FlxSpriteUtil; import flixel.util.FlxSpriteUtil;
import flixel.util.FlxTimer; import flixel.util.FlxTimer;
import funkin.audio.FunkinSound; import funkin.audio.FunkinSound;
import funkin.data.story.level.LevelRegistry;
import funkin.data.song.SongRegistry;
import funkin.data.freeplay.player.PlayerRegistry; import funkin.data.freeplay.player.PlayerRegistry;
import funkin.data.song.SongRegistry;
import funkin.data.story.level.LevelRegistry;
import funkin.effects.IntervalShake;
import funkin.graphics.adobeanimate.FlxAtlasSprite;
import funkin.graphics.FunkinCamera; import funkin.graphics.FunkinCamera;
import funkin.graphics.FunkinSprite; import funkin.graphics.FunkinSprite;
import funkin.graphics.shaders.AngleMask; import funkin.graphics.shaders.AngleMask;
import funkin.graphics.shaders.GaussianBlurShader;
import funkin.graphics.shaders.HSVShader; import funkin.graphics.shaders.HSVShader;
import funkin.graphics.shaders.PureColor; import funkin.graphics.shaders.PureColor;
import funkin.graphics.shaders.StrokeShader; import funkin.graphics.shaders.StrokeShader;
import funkin.input.Controls; import funkin.input.Controls;
import funkin.play.PlayStatePlaylist; import funkin.play.PlayStatePlaylist;
import funkin.play.scoring.Scoring;
import funkin.play.scoring.Scoring.ScoringRank;
import funkin.play.song.Song; import funkin.play.song.Song;
import funkin.ui.story.Level;
import funkin.save.Save; import funkin.save.Save;
import funkin.save.Save.SaveScoreData; import funkin.save.Save.SaveScoreData;
import funkin.ui.AtlasText; import funkin.ui.AtlasText;
import funkin.play.scoring.Scoring; import funkin.ui.freeplay.charselect.PlayableCharacter;
import funkin.play.scoring.Scoring.ScoringRank; import funkin.ui.freeplay.SongMenuItem.FreeplayRank;
import funkin.ui.mainmenu.MainMenuState; import funkin.ui.mainmenu.MainMenuState;
import funkin.ui.MusicBeatSubState; import funkin.ui.MusicBeatSubState;
import funkin.ui.story.Level;
import funkin.ui.transition.LoadingState; import funkin.ui.transition.LoadingState;
import funkin.ui.transition.StickerSubState; import funkin.ui.transition.StickerSubState;
import funkin.util.MathUtil; import funkin.util.MathUtil;
import funkin.util.SortUtil;
import lime.utils.Assets; import lime.utils.Assets;
import flixel.tweens.misc.ShakeTween; import openfl.display.BlendMode;
import funkin.effects.IntervalShake;
import funkin.ui.freeplay.SongMenuItem.FreeplayRank;
import funkin.ui.freeplay.charselect.PlayableCharacter;
/** /**
* Parameters used to initialize the FreeplayState. * Parameters used to initialize the FreeplayState.
@ -94,6 +95,7 @@ typedef FromResultsParams =
/** /**
* The state for the freeplay menu, allowing the player to select any song to play. * The state for the freeplay menu, allowing the player to select any song to play.
*/ */
@:nullSafety
class FreeplayState extends MusicBeatSubState class FreeplayState extends MusicBeatSubState
{ {
// //
@ -164,10 +166,9 @@ class FreeplayState extends MusicBeatSubState
var grpSongs:FlxTypedGroup<Alphabet>; var grpSongs:FlxTypedGroup<Alphabet>;
var grpCapsules:FlxTypedGroup<SongMenuItem>; var grpCapsules:FlxTypedGroup<SongMenuItem>;
var curCapsule:SongMenuItem;
var curPlaying:Bool = false; var curPlaying:Bool = false;
var dj:FreeplayDJ; var dj:Null<FreeplayDJ> = null;
var ostName:FlxText; var ostName:FlxText;
var albumRoll:AlbumRoll; var albumRoll:AlbumRoll;
@ -175,7 +176,7 @@ class FreeplayState extends MusicBeatSubState
var letterSort:LetterSort; var letterSort:LetterSort;
var exitMovers:ExitMoverData = new Map(); var exitMovers:ExitMoverData = new Map();
var stickerSubState:StickerSubState; var stickerSubState:Null<StickerSubState> = null;
public static var rememberedDifficulty:Null<String> = Constants.DEFAULT_DIFFICULTY; public static var rememberedDifficulty:Null<String> = Constants.DEFAULT_DIFFICULTY;
public static var rememberedSongId:Null<String> = 'tutorial'; public static var rememberedSongId:Null<String> = 'tutorial';
@ -210,8 +211,12 @@ class FreeplayState extends MusicBeatSubState
public function new(?params:FreeplayStateParams, ?stickers:StickerSubState) public function new(?params:FreeplayStateParams, ?stickers:StickerSubState)
{ {
currentCharacterId = params?.character ?? Constants.DEFAULT_CHARACTER; currentCharacterId = params?.character ?? Constants.DEFAULT_CHARACTER;
currentCharacter = PlayerRegistry.instance.fetchEntry(currentCharacterId); var fetchPlayableCharacter = function():PlayableCharacter {
if (currentCharacter == null) throw 'Could not build Freeplay state for character: $currentCharacterId'; var result = PlayerRegistry.instance.fetchEntry(params?.character ?? Constants.DEFAULT_CHARACTER);
if (result == null) throw 'No valid playable character with id ${params?.character}';
return result;
};
currentCharacter = fetchPlayableCharacter();
fromResultsParams = params?.fromResults; fromResultsParams = params?.fromResults;
@ -220,12 +225,54 @@ class FreeplayState extends MusicBeatSubState
prepForNewRank = true; prepForNewRank = true;
} }
super(FlxColor.TRANSPARENT);
if (stickers != null) if (stickers != null)
{ {
stickerSubState = stickers; stickerSubState = stickers;
} }
super(FlxColor.TRANSPARENT); // We build a bunch of sprites BEFORE create() so we can guarantee they aren't null later on.
albumRoll = new AlbumRoll();
fp = new FreeplayScore(460, 60, 7, 100);
cardGlow = new FlxSprite(-30, -30).loadGraphic(Paths.image('freeplay/cardGlow'));
confirmGlow = new FlxSprite(-30, 240).loadGraphic(Paths.image('freeplay/confirmGlow'));
confirmTextGlow = new FlxSprite(-8, 115).loadGraphic(Paths.image('freeplay/glowingText'));
rankCamera = new FunkinCamera('rankCamera', 0, 0, FlxG.width, FlxG.height);
funnyCam = new FunkinCamera('freeplayFunny', 0, 0, FlxG.width, FlxG.height);
funnyScroll = new BGScrollingText(0, 220, currentCharacter.getFreeplayDJText(1), FlxG.width / 2, false, 60);
funnyScroll2 = new BGScrollingText(0, 335, currentCharacter.getFreeplayDJText(1), FlxG.width / 2, false, 60);
grpCapsules = new FlxTypedGroup<SongMenuItem>();
grpDifficulties = new FlxTypedSpriteGroup<DifficultySprite>(-300, 80);
letterSort = new LetterSort(400, 75);
grpSongs = new FlxTypedGroup<Alphabet>();
moreWays = new BGScrollingText(0, 160, currentCharacter.getFreeplayDJText(2), FlxG.width, true, 43);
moreWays2 = new BGScrollingText(0, 397, currentCharacter.getFreeplayDJText(2), FlxG.width, true, 43);
pinkBack = FunkinSprite.create('freeplay/pinkBack');
rankBg = new FunkinSprite(0, 0);
rankVignette = new FlxSprite(0, 0).loadGraphic(Paths.image('freeplay/rankVignette'));
sparks = new FlxSprite(0, 0);
sparksADD = new FlxSprite(0, 0);
txtCompletion = new AtlasText(1185, 87, '69', AtlasFont.FREEPLAY_CLEAR);
txtNuts = new BGScrollingText(0, 285, currentCharacter.getFreeplayDJText(3), FlxG.width / 2, true, 43);
ostName = new FlxText(8, 8, FlxG.width - 8 - 8, 'OFFICIAL OST', 48);
orangeBackShit = new FunkinSprite(84, 440).makeSolidColor(Std.int(pinkBack.width), 75, 0xFFFEDA00);
bgDad = new FlxSprite(pinkBack.width * 0.74, 0).loadGraphic(Paths.image('freeplay/freeplayBGdad'));
alsoOrangeLOL = new FunkinSprite(0, orangeBackShit.y).makeSolidColor(100, Std.int(orangeBackShit.height), 0xFFFFD400);
confirmGlow2 = new FlxSprite(confirmGlow.x, confirmGlow.y).loadGraphic(Paths.image('freeplay/confirmGlow2'));
funnyScroll3 = new BGScrollingText(0, orangeBackShit.y + 10, currentCharacter.getFreeplayDJText(1), FlxG.width / 2, 60);
backingTextYeah = new FlxAtlasSprite(640, 370, Paths.animateAtlas("freeplay/backing-text-yeah"),
{
FrameRate: 24.0,
Reversed: false,
// ?OnComplete:Void -> Void,
ShowPivot: false,
Antialiasing: true,
ScrollFactor: new FlxPoint(1, 1),
});
} }
override function create():Void override function create():Void
@ -236,12 +283,6 @@ class FreeplayState extends MusicBeatSubState
FlxTransitionableState.skipNextTransIn = true; FlxTransitionableState.skipNextTransIn = true;
// dedicated camera for the state so we don't need to fuk around with camera scrolls from the mainmenu / elsewhere
funnyCam = new FunkinCamera('freeplayFunny', 0, 0, FlxG.width, FlxG.height);
funnyCam.bgColor = FlxColor.TRANSPARENT;
FlxG.cameras.add(funnyCam, false);
this.cameras = [funnyCam];
if (stickerSubState != null) if (stickerSubState != null)
{ {
this.persistentUpdate = true; this.persistentUpdate = true;
@ -277,7 +318,7 @@ class FreeplayState extends MusicBeatSubState
// programmatically adds the songs via LevelRegistry and SongRegistry // programmatically adds the songs via LevelRegistry and SongRegistry
for (levelId in LevelRegistry.instance.listSortedLevelIds()) for (levelId in LevelRegistry.instance.listSortedLevelIds())
{ {
var level:Level = LevelRegistry.instance.fetchEntry(levelId); var level:Null<Level> = LevelRegistry.instance.fetchEntry(levelId);
if (level == null) if (level == null)
{ {
@ -287,7 +328,7 @@ class FreeplayState extends MusicBeatSubState
for (songId in level.getSongs()) for (songId in level.getSongs())
{ {
var song:Song = SongRegistry.instance.fetchEntry(songId); var song:Null<Song> = SongRegistry.instance.fetchEntry(songId);
if (song == null) if (song == null)
{ {
@ -319,17 +360,14 @@ class FreeplayState extends MusicBeatSubState
trace(FlxG.camera.initialZoom); trace(FlxG.camera.initialZoom);
trace(FlxCamera.defaultZoom); trace(FlxCamera.defaultZoom);
pinkBack = FunkinSprite.create('freeplay/pinkBack');
pinkBack.color = 0xFFFFD4E9; // sets it to pink! pinkBack.color = 0xFFFFD4E9; // sets it to pink!
pinkBack.x -= pinkBack.width; pinkBack.x -= pinkBack.width;
FlxTween.tween(pinkBack, {x: 0}, 0.6, {ease: FlxEase.quartOut}); FlxTween.tween(pinkBack, {x: 0}, 0.6, {ease: FlxEase.quartOut});
add(pinkBack); add(pinkBack);
orangeBackShit = new FunkinSprite(84, 440).makeSolidColor(Std.int(pinkBack.width), 75, 0xFFFEDA00);
add(orangeBackShit); add(orangeBackShit);
alsoOrangeLOL = new FunkinSprite(0, orangeBackShit.y).makeSolidColor(100, Std.int(orangeBackShit.height), 0xFFFFD400);
add(alsoOrangeLOL); add(alsoOrangeLOL);
exitMovers.set([pinkBack, orangeBackShit, alsoOrangeLOL], exitMovers.set([pinkBack, orangeBackShit, alsoOrangeLOL],
@ -344,15 +382,11 @@ class FreeplayState extends MusicBeatSubState
orangeBackShit.visible = false; orangeBackShit.visible = false;
alsoOrangeLOL.visible = false; alsoOrangeLOL.visible = false;
confirmTextGlow = new FlxSprite(-8, 115).loadGraphic(Paths.image('freeplay/glowingText'));
confirmTextGlow.blend = BlendMode.ADD; confirmTextGlow.blend = BlendMode.ADD;
confirmTextGlow.visible = false; confirmTextGlow.visible = false;
confirmGlow = new FlxSprite(-30, 240).loadGraphic(Paths.image('freeplay/confirmGlow'));
confirmGlow.blend = BlendMode.ADD; confirmGlow.blend = BlendMode.ADD;
confirmGlow2 = new FlxSprite(confirmGlow.x, confirmGlow.y).loadGraphic(Paths.image('freeplay/confirmGlow2'));
confirmGlow.visible = false; confirmGlow.visible = false;
confirmGlow2.visible = false; confirmGlow2.visible = false;
@ -367,7 +401,6 @@ class FreeplayState extends MusicBeatSubState
FlxG.debugger.addTrackerProfile(new TrackerProfile(BGScrollingText, ['x', 'y', 'speed', 'size'])); FlxG.debugger.addTrackerProfile(new TrackerProfile(BGScrollingText, ['x', 'y', 'speed', 'size']));
moreWays = new BGScrollingText(0, 160, 'HOT BLOODED IN MORE WAYS THAN ONE', FlxG.width, true, 43);
moreWays.funnyColor = 0xFFFFF383; moreWays.funnyColor = 0xFFFFF383;
moreWays.speed = 6.8; moreWays.speed = 6.8;
grpTxtScrolls.add(moreWays); grpTxtScrolls.add(moreWays);
@ -378,7 +411,6 @@ class FreeplayState extends MusicBeatSubState
speed: 0.4, speed: 0.4,
}); });
funnyScroll = new BGScrollingText(0, 220, 'BOYFRIEND', FlxG.width / 2, false, 60);
funnyScroll.funnyColor = 0xFFFF9963; funnyScroll.funnyColor = 0xFFFF9963;
funnyScroll.speed = -3.8; funnyScroll.speed = -3.8;
grpTxtScrolls.add(funnyScroll); grpTxtScrolls.add(funnyScroll);
@ -391,7 +423,6 @@ class FreeplayState extends MusicBeatSubState
wait: 0 wait: 0
}); });
txtNuts = new BGScrollingText(0, 285, 'PROTECT YO NUTS', FlxG.width / 2, true, 43);
txtNuts.speed = 3.5; txtNuts.speed = 3.5;
grpTxtScrolls.add(txtNuts); grpTxtScrolls.add(txtNuts);
exitMovers.set([txtNuts], exitMovers.set([txtNuts],
@ -400,7 +431,6 @@ class FreeplayState extends MusicBeatSubState
speed: 0.4, speed: 0.4,
}); });
funnyScroll2 = new BGScrollingText(0, 335, 'BOYFRIEND', FlxG.width / 2, false, 60);
funnyScroll2.funnyColor = 0xFFFF9963; funnyScroll2.funnyColor = 0xFFFF9963;
funnyScroll2.speed = -3.8; funnyScroll2.speed = -3.8;
grpTxtScrolls.add(funnyScroll2); grpTxtScrolls.add(funnyScroll2);
@ -411,7 +441,6 @@ class FreeplayState extends MusicBeatSubState
speed: 0.5, speed: 0.5,
}); });
moreWays2 = new BGScrollingText(0, 397, 'HOT BLOODED IN MORE WAYS THAN ONE', FlxG.width, true, 43);
moreWays2.funnyColor = 0xFFFFF383; moreWays2.funnyColor = 0xFFFFF383;
moreWays2.speed = 6.8; moreWays2.speed = 6.8;
grpTxtScrolls.add(moreWays2); grpTxtScrolls.add(moreWays2);
@ -422,7 +451,6 @@ class FreeplayState extends MusicBeatSubState
speed: 0.4 speed: 0.4
}); });
funnyScroll3 = new BGScrollingText(0, orangeBackShit.y + 10, 'BOYFRIEND', FlxG.width / 2, 60);
funnyScroll3.funnyColor = 0xFFFEA400; funnyScroll3.funnyColor = 0xFFFEA400;
funnyScroll3.speed = -3.8; funnyScroll3.speed = -3.8;
grpTxtScrolls.add(funnyScroll3); grpTxtScrolls.add(funnyScroll3);
@ -433,19 +461,8 @@ class FreeplayState extends MusicBeatSubState
speed: 0.3 speed: 0.3
}); });
backingTextYeah = new FlxAtlasSprite(640, 370, Paths.animateAtlas("freeplay/backing-text-yeah"),
{
FrameRate: 24.0,
Reversed: false,
// ?OnComplete:Void -> Void,
ShowPivot: false,
Antialiasing: true,
ScrollFactor: new FlxPoint(1, 1),
});
add(backingTextYeah); add(backingTextYeah);
cardGlow = new FlxSprite(-30, -30).loadGraphic(Paths.image('freeplay/cardGlow'));
cardGlow.blend = BlendMode.ADD; cardGlow.blend = BlendMode.ADD;
cardGlow.visible = false; cardGlow.visible = false;
@ -462,7 +479,6 @@ class FreeplayState extends MusicBeatSubState
add(dj); add(dj);
} }
bgDad = new FlxSprite(pinkBack.width * 0.74, 0).loadGraphic(Paths.image('freeplay/freeplayBGdad'));
bgDad.shader = new AngleMask(); bgDad.shader = new AngleMask();
bgDad.visible = false; bgDad.visible = false;
@ -488,17 +504,13 @@ class FreeplayState extends MusicBeatSubState
blackOverlayBullshitLOLXD.shader = bgDad.shader; blackOverlayBullshitLOLXD.shader = bgDad.shader;
rankBg = new FunkinSprite(0, 0);
rankBg.makeSolidColor(FlxG.width, FlxG.height, 0xD3000000); rankBg.makeSolidColor(FlxG.width, FlxG.height, 0xD3000000);
add(rankBg); add(rankBg);
grpSongs = new FlxTypedGroup<Alphabet>();
add(grpSongs); add(grpSongs);
grpCapsules = new FlxTypedGroup<SongMenuItem>();
add(grpCapsules); add(grpCapsules);
grpDifficulties = new FlxTypedSpriteGroup<DifficultySprite>(-300, 80);
add(grpDifficulties); add(grpDifficulties);
exitMovers.set([grpDifficulties], exitMovers.set([grpDifficulties],
@ -525,7 +537,6 @@ class FreeplayState extends MusicBeatSubState
if (diffSprite.difficultyId == currentDifficulty) diffSprite.visible = true; if (diffSprite.difficultyId == currentDifficulty) diffSprite.visible = true;
} }
albumRoll = new AlbumRoll();
albumRoll.albumId = null; albumRoll.albumId = null;
add(albumRoll); add(albumRoll);
@ -540,7 +551,6 @@ class FreeplayState extends MusicBeatSubState
fnfFreeplay.font = 'VCR OSD Mono'; fnfFreeplay.font = 'VCR OSD Mono';
fnfFreeplay.visible = false; fnfFreeplay.visible = false;
ostName = new FlxText(8, 8, FlxG.width - 8 - 8, 'OFFICIAL OST', 48);
ostName.font = 'VCR OSD Mono'; ostName.font = 'VCR OSD Mono';
ostName.alignment = RIGHT; ostName.alignment = RIGHT;
ostName.visible = false; ostName.visible = false;
@ -572,7 +582,6 @@ class FreeplayState extends MusicBeatSubState
tmr.time = FlxG.random.float(20, 60); tmr.time = FlxG.random.float(20, 60);
}, 0); }, 0);
fp = new FreeplayScore(460, 60, 7, 100);
fp.visible = false; fp.visible = false;
add(fp); add(fp);
@ -580,11 +589,9 @@ class FreeplayState extends MusicBeatSubState
clearBoxSprite.visible = false; clearBoxSprite.visible = false;
add(clearBoxSprite); add(clearBoxSprite);
txtCompletion = new AtlasText(1185, 87, '69', AtlasFont.FREEPLAY_CLEAR);
txtCompletion.visible = false; txtCompletion.visible = false;
add(txtCompletion); add(txtCompletion);
letterSort = new LetterSort(400, 75);
add(letterSort); add(letterSort);
letterSort.visible = false; letterSort.visible = false;
@ -632,7 +639,8 @@ class FreeplayState extends MusicBeatSubState
// be careful not to "add()" things in here unless it's to a group that's already added to the state // be careful not to "add()" things in here unless it's to a group that's already added to the state
// otherwise it won't be properly attatched to funnyCamera (relavent code should be at the bottom of create()) // otherwise it won't be properly attatched to funnyCamera (relavent code should be at the bottom of create())
dj.onIntroDone.add(function() { var onDJIntroDone = function() {
// when boyfriend hits dat shiii // when boyfriend hits dat shiii
albumRoll.playIntro(); albumRoll.playIntro();
@ -679,20 +687,24 @@ class FreeplayState extends MusicBeatSubState
cardGlow.visible = true; cardGlow.visible = true;
FlxTween.tween(cardGlow, {alpha: 0, "scale.x": 1.2, "scale.y": 1.2}, 0.45, {ease: FlxEase.sineOut}); FlxTween.tween(cardGlow, {alpha: 0, "scale.x": 1.2, "scale.y": 1.2}, 0.45, {ease: FlxEase.sineOut});
if (prepForNewRank) if (prepForNewRank && fromResultsParams != null)
{ {
rankAnimStart(fromResultsParams); rankAnimStart(fromResultsParams);
} }
}); };
if (dj != null) {
dj.onIntroDone.add(onDJIntroDone);
} else {
onDJIntroDone();
}
generateSongList(null, false); generateSongList(null, false);
// dedicated camera for the state so we don't need to fuk around with camera scrolls from the mainmenu / elsewhere // dedicated camera for the state so we don't need to fuk around with camera scrolls from the mainmenu / elsewhere
funnyCam = new FunkinCamera('freeplayFunny', 0, 0, FlxG.width, FlxG.height);
funnyCam.bgColor = FlxColor.TRANSPARENT; funnyCam.bgColor = FlxColor.TRANSPARENT;
FlxG.cameras.add(funnyCam, false); FlxG.cameras.add(funnyCam, false);
rankVignette = new FlxSprite(0, 0).loadGraphic(Paths.image('freeplay/rankVignette'));
rankVignette.scale.set(2, 2); rankVignette.scale.set(2, 2);
rankVignette.updateHitbox(); rankVignette.updateHitbox();
rankVignette.blend = BlendMode.ADD; rankVignette.blend = BlendMode.ADD;
@ -704,7 +716,6 @@ class FreeplayState extends MusicBeatSubState
bs.cameras = [funnyCam]; bs.cameras = [funnyCam];
}); });
rankCamera = new FunkinCamera('rankCamera', 0, 0, FlxG.width, FlxG.height);
rankCamera.bgColor = FlxColor.TRANSPARENT; rankCamera.bgColor = FlxColor.TRANSPARENT;
FlxG.cameras.add(rankCamera, false); FlxG.cameras.add(rankCamera, false);
rankBg.cameras = [rankCamera]; rankBg.cameras = [rankCamera];
@ -716,8 +727,8 @@ class FreeplayState extends MusicBeatSubState
} }
} }
var currentFilter:SongFilter = null; var currentFilter:Null<SongFilter> = null;
var currentFilteredSongs:Array<FreeplaySongData> = []; var currentFilteredSongs:Array<Null<FreeplaySongData>> = [];
/** /**
* Given the current filter, rebuild the current song list. * Given the current filter, rebuild the current song list.
@ -728,7 +739,7 @@ class FreeplayState extends MusicBeatSubState
*/ */
public function generateSongList(filterStuff:Null<SongFilter>, force:Bool = false, onlyIfChanged:Bool = true):Void public function generateSongList(filterStuff:Null<SongFilter>, force:Bool = false, onlyIfChanged:Bool = true):Void
{ {
var tempSongs:Array<FreeplaySongData> = songs; var tempSongs:Array<Null<FreeplaySongData>> = songs;
// Remember just the difficulty because it's important for song sorting. // Remember just the difficulty because it's important for song sorting.
if (rememberedDifficulty != null) if (rememberedDifficulty != null)
@ -790,11 +801,12 @@ class FreeplayState extends MusicBeatSubState
for (i in 0...tempSongs.length) for (i in 0...tempSongs.length)
{ {
if (tempSongs[i] == null) continue; var tempSong = tempSongs[i];
if (tempSong == null) continue;
var funnyMenu:SongMenuItem = grpCapsules.recycle(SongMenuItem); var funnyMenu:SongMenuItem = grpCapsules.recycle(SongMenuItem);
funnyMenu.init(FlxG.width, 0, tempSongs[i]); funnyMenu.init(FlxG.width, 0, tempSong);
funnyMenu.onConfirm = function() { funnyMenu.onConfirm = function() {
capsuleOnConfirmDefault(funnyMenu); capsuleOnConfirmDefault(funnyMenu);
}; };
@ -803,8 +815,8 @@ class FreeplayState extends MusicBeatSubState
funnyMenu.ID = i; funnyMenu.ID = i;
funnyMenu.capsule.alpha = 0.5; funnyMenu.capsule.alpha = 0.5;
funnyMenu.songText.visible = false; funnyMenu.songText.visible = false;
funnyMenu.favIcon.visible = tempSongs[i].isFav; funnyMenu.favIcon.visible = tempSong.isFav;
funnyMenu.favIconBlurred.visible = tempSongs[i].isFav; funnyMenu.favIconBlurred.visible = tempSong.isFav;
funnyMenu.hsvShader = hsvShader; funnyMenu.hsvShader = hsvShader;
funnyMenu.newText.animation.curAnim.curFrame = 45 - ((i * 4) % 45); funnyMenu.newText.animation.curAnim.curFrame = 45 - ((i * 4) % 45);
@ -828,13 +840,10 @@ class FreeplayState extends MusicBeatSubState
* @param songFilter The filter to apply * @param songFilter The filter to apply
* @return Array<FreeplaySongData> * @return Array<FreeplaySongData>
*/ */
public function sortSongs(songsToFilter:Array<FreeplaySongData>, songFilter:SongFilter):Array<FreeplaySongData> public function sortSongs(songsToFilter:Array<Null<FreeplaySongData>>, songFilter:SongFilter):Array<Null<FreeplaySongData>>
{ {
var filterAlphabetically = function(a:FreeplaySongData, b:FreeplaySongData):Int { var filterAlphabetically = function(a:Null<FreeplaySongData>, b:Null<FreeplaySongData>):Int {
if (a?.songName.toLowerCase() < b?.songName.toLowerCase()) return -1; return SortUtil.alphabetically(a?.songName ?? '', b?.songName ?? '');
else if (a?.songName.toLowerCase() > b?.songName.toLowerCase()) return 1;
else
return 0;
}; };
switch (songFilter.filterType) switch (songFilter.filterType)
@ -858,7 +867,7 @@ class FreeplayState extends MusicBeatSubState
songsToFilter = songsToFilter.filter(str -> { songsToFilter = songsToFilter.filter(str -> {
if (str == null) return true; // Random if (str == null) return true; // Random
return str.songName.toLowerCase().startsWith(songFilter.filterData); return str.songName.toLowerCase().startsWith(songFilter.filterData ?? '');
}); });
case ALL: case ALL:
// no filter! // no filter!
@ -880,32 +889,28 @@ class FreeplayState extends MusicBeatSubState
var sparks:FlxSprite; var sparks:FlxSprite;
var sparksADD:FlxSprite; var sparksADD:FlxSprite;
function rankAnimStart(fromResults:Null<FromResultsParams>):Void function rankAnimStart(fromResults:FromResultsParams):Void
{ {
busy = true; busy = true;
grpCapsules.members[curSelected].sparkle.alpha = 0; grpCapsules.members[curSelected].sparkle.alpha = 0;
// grpCapsules.members[curSelected].forcePosition(); // grpCapsules.members[curSelected].forcePosition();
if (fromResults != null)
{
rememberedSongId = fromResults.songId; rememberedSongId = fromResults.songId;
rememberedDifficulty = fromResults.difficultyId; rememberedDifficulty = fromResults.difficultyId;
changeSelection(); changeSelection();
changeDiff(); changeDiff();
}
dj.fistPump(); if (dj != null) dj.fistPump();
// rankCamera.fade(FlxColor.BLACK, 0.5, true); // rankCamera.fade(FlxColor.BLACK, 0.5, true);
rankCamera.fade(0xFF000000, 0.5, true, null, true); rankCamera.fade(0xFF000000, 0.5, true, null, true);
if (FlxG.sound.music != null) FlxG.sound.music.volume = 0; if (FlxG.sound.music != null) FlxG.sound.music.volume = 0;
rankBg.alpha = 1; rankBg.alpha = 1;
if (fromResults?.oldRank != null) if (fromResults.oldRank != null)
{ {
grpCapsules.members[curSelected].fakeRanking.rank = fromResults.oldRank; grpCapsules.members[curSelected].fakeRanking.rank = fromResults.oldRank;
grpCapsules.members[curSelected].fakeBlurredRanking.rank = fromResults.oldRank; grpCapsules.members[curSelected].fakeBlurredRanking.rank = fromResults.oldRank;
sparks = new FlxSprite(0, 0);
sparks.frames = Paths.getSparrowAtlas('freeplay/sparks'); sparks.frames = Paths.getSparrowAtlas('freeplay/sparks');
sparks.animation.addByPrefix('sparks', 'sparks', 24, false); sparks.animation.addByPrefix('sparks', 'sparks', 24, false);
sparks.visible = false; sparks.visible = false;
@ -915,7 +920,6 @@ class FreeplayState extends MusicBeatSubState
add(sparks); add(sparks);
sparks.cameras = [rankCamera]; sparks.cameras = [rankCamera];
sparksADD = new FlxSprite(0, 0);
sparksADD.visible = false; sparksADD.visible = false;
sparksADD.frames = Paths.getSparrowAtlas('freeplay/sparksadd'); sparksADD.frames = Paths.getSparrowAtlas('freeplay/sparksadd');
sparksADD.animation.addByPrefix('sparks add', 'sparks add', 24, false); sparksADD.animation.addByPrefix('sparks add', 'sparks add', 24, false);
@ -980,14 +984,14 @@ class FreeplayState extends MusicBeatSubState
grpCapsules.members[curSelected].ranking.scale.set(20, 20); grpCapsules.members[curSelected].ranking.scale.set(20, 20);
grpCapsules.members[curSelected].blurredRanking.scale.set(20, 20); grpCapsules.members[curSelected].blurredRanking.scale.set(20, 20);
if (fromResults?.newRank != null) if (fromResults != null && fromResults.newRank != null)
{ {
grpCapsules.members[curSelected].ranking.animation.play(fromResults.newRank.getFreeplayRankIconAsset(), true); grpCapsules.members[curSelected].ranking.animation.play(fromResults.newRank.getFreeplayRankIconAsset(), true);
} }
FlxTween.tween(grpCapsules.members[curSelected].ranking, {"scale.x": 1, "scale.y": 1}, 0.1); FlxTween.tween(grpCapsules.members[curSelected].ranking, {"scale.x": 1, "scale.y": 1}, 0.1);
if (fromResults?.newRank != null) if (fromResults != null && fromResults.newRank != null)
{ {
grpCapsules.members[curSelected].blurredRanking.animation.play(fromResults.newRank.getFreeplayRankIconAsset(), true); grpCapsules.members[curSelected].blurredRanking.animation.play(fromResults.newRank.getFreeplayRankIconAsset(), true);
} }
@ -1078,11 +1082,11 @@ class FreeplayState extends MusicBeatSubState
if (fromResultsParams?.newRank == SHIT) if (fromResultsParams?.newRank == SHIT)
{ {
dj.pumpFistBad(); if (dj != null) dj.pumpFistBad();
} }
else else
{ {
dj.pumpFist(); if (dj != null) dj.pumpFist();
} }
rankCamera.zoom = 0.8; rankCamera.zoom = 0.8;
@ -1196,7 +1200,13 @@ class FreeplayState extends MusicBeatSubState
#if debug #if debug
if (FlxG.keys.justPressed.T) if (FlxG.keys.justPressed.T)
{ {
rankAnimStart(fromResultsParams); rankAnimStart(fromResultsParams ??
{
playRankAnim: true,
newRank: PERFECT_GOLD,
songId: "tutorial",
difficultyId: "hard"
});
} }
if (FlxG.keys.justPressed.P) if (FlxG.keys.justPressed.P)
@ -1427,7 +1437,7 @@ class FreeplayState extends MusicBeatSubState
} }
spamTimer += elapsed; spamTimer += elapsed;
dj.resetAFKTimer(); if (dj != null) dj.resetAFKTimer();
} }
else else
{ {
@ -1438,31 +1448,31 @@ class FreeplayState extends MusicBeatSubState
#if !html5 #if !html5
if (FlxG.mouse.wheel != 0) if (FlxG.mouse.wheel != 0)
{ {
dj.resetAFKTimer(); if (dj != null) dj.resetAFKTimer();
changeSelection(-Math.round(FlxG.mouse.wheel)); changeSelection(-Math.round(FlxG.mouse.wheel));
} }
#else #else
if (FlxG.mouse.wheel < 0) if (FlxG.mouse.wheel < 0)
{ {
dj.resetAFKTimer(); if (dj != null) dj.resetAFKTimer();
changeSelection(-Math.round(FlxG.mouse.wheel / 8)); changeSelection(-Math.round(FlxG.mouse.wheel / 8));
} }
else if (FlxG.mouse.wheel > 0) else if (FlxG.mouse.wheel > 0)
{ {
dj.resetAFKTimer(); if (dj != null) dj.resetAFKTimer();
changeSelection(-Math.round(FlxG.mouse.wheel / 8)); changeSelection(-Math.round(FlxG.mouse.wheel / 8));
} }
#end #end
if (controls.UI_LEFT_P) if (controls.UI_LEFT_P)
{ {
dj.resetAFKTimer(); if (dj != null) dj.resetAFKTimer();
changeDiff(-1); changeDiff(-1);
generateSongList(currentFilter, true); generateSongList(currentFilter, true);
} }
if (controls.UI_RIGHT_P) if (controls.UI_RIGHT_P)
{ {
dj.resetAFKTimer(); if (dj != null) dj.resetAFKTimer();
changeDiff(1); changeDiff(1);
generateSongList(currentFilter, true); generateSongList(currentFilter, true);
} }
@ -1472,7 +1482,7 @@ class FreeplayState extends MusicBeatSubState
busy = true; busy = true;
FlxTween.globalManager.clear(); FlxTween.globalManager.clear();
FlxTimer.globalManager.clear(); FlxTimer.globalManager.clear();
dj.onIntroDone.removeAll(); if (dj != null) dj.onIntroDone.removeAll();
FunkinSound.playOnce(Paths.sound('cancelMenu')); FunkinSound.playOnce(Paths.sound('cancelMenu'));
@ -1498,7 +1508,8 @@ class FreeplayState extends MusicBeatSubState
for (grpSpr in exitMovers.keys()) for (grpSpr in exitMovers.keys())
{ {
var moveData:MoveData = exitMovers.get(grpSpr); var moveData:Null<MoveData> = exitMovers.get(grpSpr);
if (moveData == null) continue;
for (spr in grpSpr) for (spr in grpSpr)
{ {
@ -1506,14 +1517,14 @@ class FreeplayState extends MusicBeatSubState
var funnyMoveShit:MoveData = moveData; var funnyMoveShit:MoveData = moveData;
if (moveData.x == null) funnyMoveShit.x = spr.x; var moveDataX = funnyMoveShit.x ?? spr.x;
if (moveData.y == null) funnyMoveShit.y = spr.y; var moveDataY = funnyMoveShit.y ?? spr.y;
if (moveData.speed == null) funnyMoveShit.speed = 0.2; var moveDataSpeed = funnyMoveShit.speed ?? 0.2;
if (moveData.wait == null) funnyMoveShit.wait = 0; var moveDataWait = funnyMoveShit.wait ?? 0;
FlxTween.tween(spr, {x: funnyMoveShit.x, y: funnyMoveShit.y}, funnyMoveShit.speed, {ease: FlxEase.expoIn}); FlxTween.tween(spr, {x: moveDataX, y: moveDataY}, moveDataSpeed, {ease: FlxEase.expoIn});
longestTimer = Math.max(longestTimer, funnyMoveShit.speed + funnyMoveShit.wait); longestTimer = Math.max(longestTimer, moveDataSpeed + moveDataWait);
} }
} }
@ -1586,19 +1597,18 @@ class FreeplayState extends MusicBeatSubState
var daSong:Null<FreeplaySongData> = grpCapsules.members[curSelected].songData; var daSong:Null<FreeplaySongData> = grpCapsules.members[curSelected].songData;
if (daSong != null) if (daSong != null)
{ {
// TODO: Make this actually be the variation you're focused on. We don't need to fetch the song metadata just to calculate it. var targetSong:Null<Song> = SongRegistry.instance.fetchEntry(daSong.songId);
var targetSong:Song = SongRegistry.instance.fetchEntry(grpCapsules.members[curSelected].songData.songId);
if (targetSong == null) if (targetSong == null)
{ {
FlxG.log.warn('WARN: could not find song with id (${grpCapsules.members[curSelected].songData.songId})'); FlxG.log.warn('WARN: could not find song with id (${daSong.songId})');
return; return;
} }
var targetVariation:String = targetSong.getFirstValidVariation(currentDifficulty); var targetVariation:String = targetSong.getFirstValidVariation(currentDifficulty) ?? '';
// TODO: This line of code makes me sad, but you can't really fix it without a breaking migration. // TODO: This line of code makes me sad, but you can't really fix it without a breaking migration.
var suffixedDifficulty = (targetVariation != Constants.DEFAULT_VARIATION var suffixedDifficulty = (targetVariation != Constants.DEFAULT_VARIATION
&& targetVariation != 'erect') ? '$currentDifficulty-${targetVariation}' : currentDifficulty; && targetVariation != 'erect') ? '$currentDifficulty-${targetVariation}' : currentDifficulty;
var songScore:SaveScoreData = Save.instance.getSongScore(grpCapsules.members[curSelected].songData.songId, suffixedDifficulty); var songScore:Null<SaveScoreData> = Save.instance.getSongScore(daSong.songId, suffixedDifficulty);
intendedScore = songScore?.score ?? 0; intendedScore = songScore?.score ?? 0;
intendedCompletion = songScore == null ? 0.0 : ((songScore.tallies.sick + songScore.tallies.good) / songScore.tallies.totalNotes); intendedCompletion = songScore == null ? 0.0 : ((songScore.tallies.sick + songScore.tallies.good) / songScore.tallies.totalNotes);
rememberedDifficulty = currentDifficulty; rememberedDifficulty = currentDifficulty;
@ -1660,7 +1670,7 @@ class FreeplayState extends MusicBeatSubState
} }
// Set the album graphic and play the animation if relevant. // Set the album graphic and play the animation if relevant.
var newAlbumId:String = daSong?.albumId; var newAlbumId:Null<String> = daSong?.albumId;
if (albumRoll.albumId != newAlbumId) if (albumRoll.albumId != newAlbumId)
{ {
albumRoll.albumId = newAlbumId; albumRoll.albumId = newAlbumId;
@ -1698,7 +1708,7 @@ class FreeplayState extends MusicBeatSubState
}); });
trace('Available songs: ${availableSongCapsules.map(function(cap) { trace('Available songs: ${availableSongCapsules.map(function(cap) {
return cap.songData.songName; return cap?.songData?.songName;
})}'); })}');
if (availableSongCapsules.length == 0) if (availableSongCapsules.length == 0)
@ -1727,17 +1737,20 @@ class FreeplayState extends MusicBeatSubState
PlayStatePlaylist.isStoryMode = false; PlayStatePlaylist.isStoryMode = false;
var targetSong:Song = SongRegistry.instance.fetchEntry(cap.songData.songId); var targetSongId:String = cap?.songData?.songId ?? 'unknown';
if (targetSong == null) var targetSongNullable:Null<Song> = SongRegistry.instance.fetchEntry(targetSongId);
if (targetSongNullable == null)
{ {
FlxG.log.warn('WARN: could not find song with id (${cap.songData.songId})'); FlxG.log.warn('WARN: could not find song with id (${targetSongId})');
return; return;
} }
var targetSong:Song = targetSongNullable;
var targetDifficultyId:String = currentDifficulty; var targetDifficultyId:String = currentDifficulty;
var targetVariation:String = targetSong.getFirstValidVariation(targetDifficultyId, currentCharacter); var targetVariation:Null<String> = targetSong.getFirstValidVariation(targetDifficultyId, currentCharacter);
PlayStatePlaylist.campaignId = cap.songData.levelId; var targetLevelId:Null<String> = cap?.songData?.levelId;
PlayStatePlaylist.campaignId = targetLevelId ?? null;
var targetDifficulty:SongDifficulty = targetSong.getDifficulty(targetDifficultyId, targetVariation); var targetDifficulty:Null<SongDifficulty> = targetSong.getDifficulty(targetDifficultyId, targetVariation);
if (targetDifficulty == null) if (targetDifficulty == null)
{ {
FlxG.log.warn('WARN: could not find difficulty with id (${targetDifficultyId})'); FlxG.log.warn('WARN: could not find difficulty with id (${targetDifficultyId})');
@ -1759,7 +1772,7 @@ class FreeplayState extends MusicBeatSubState
// Visual and audio effects. // Visual and audio effects.
FunkinSound.playOnce(Paths.sound('confirmMenu')); FunkinSound.playOnce(Paths.sound('confirmMenu'));
dj.confirm(); if (dj != null) dj.confirm();
grpCapsules.members[curSelected].forcePosition(); grpCapsules.members[curSelected].forcePosition();
grpCapsules.members[curSelected].songText.flickerText(); grpCapsules.members[curSelected].songText.flickerText();
@ -1801,7 +1814,7 @@ class FreeplayState extends MusicBeatSubState
new FlxTimer().start(1, function(tmr:FlxTimer) { new FlxTimer().start(1, function(tmr:FlxTimer) {
FunkinSound.emptyPartialQueue(); FunkinSound.emptyPartialQueue();
Paths.setCurrentLevel(cap.songData.levelId); Paths.setCurrentLevel(cap?.songData?.levelId);
LoadingState.loadPlayState( LoadingState.loadPlayState(
{ {
targetSong: targetSong, targetSong: targetSong,
@ -1856,7 +1869,7 @@ class FreeplayState extends MusicBeatSubState
var daSongCapsule:SongMenuItem = grpCapsules.members[curSelected]; var daSongCapsule:SongMenuItem = grpCapsules.members[curSelected];
if (daSongCapsule.songData != null) if (daSongCapsule.songData != null)
{ {
var songScore:SaveScoreData = Save.instance.getSongScore(daSongCapsule.songData.songId, currentDifficulty); var songScore:Null<SaveScoreData> = Save.instance.getSongScore(daSongCapsule.songData.songId, currentDifficulty);
intendedScore = songScore?.score ?? 0; intendedScore = songScore?.score ?? 0;
intendedCompletion = songScore == null ? 0.0 : ((songScore.tallies.sick + songScore.tallies.good) / songScore.tallies.totalNotes); intendedCompletion = songScore == null ? 0.0 : ((songScore.tallies.sick + songScore.tallies.good) / songScore.tallies.totalNotes);
diffIdsCurrent = daSongCapsule.songData.songDifficulties; diffIdsCurrent = daSongCapsule.songData.songDifficulties;
@ -1906,7 +1919,10 @@ class FreeplayState extends MusicBeatSubState
} }
else else
{ {
var previewSong:Null<Song> = SongRegistry.instance.fetchEntry(daSongCapsule.songData.songId); var previewSongId:Null<String> = daSongCapsule?.songData?.songId;
if (previewSongId == null) return;
var previewSong:Null<Song> = SongRegistry.instance.fetchEntry(previewSongId);
var songDifficulty = previewSong?.getDifficulty(currentDifficulty, var songDifficulty = previewSong?.getDifficulty(currentDifficulty,
previewSong?.getVariationsByCharacter(currentCharacter) ?? Constants.DEFAULT_VARIATION_LIST); previewSong?.getVariationsByCharacter(currentCharacter) ?? Constants.DEFAULT_VARIATION_LIST);
var baseInstrumentalId:String = songDifficulty?.characters?.instrumental ?? ''; var baseInstrumentalId:String = songDifficulty?.characters?.instrumental ?? '';
@ -1924,7 +1940,9 @@ class FreeplayState extends MusicBeatSubState
instSuffix = (instSuffix != '') ? '-$instSuffix' : ''; instSuffix = (instSuffix != '') ? '-$instSuffix' : '';
FunkinSound.playMusic(daSongCapsule.songData.songId, trace('Attempting to play partial preview: ${previewSongId}:${instSuffix}');
FunkinSound.playMusic(previewSongId,
{ {
startingVolume: 0.0, startingVolume: 0.0,
overrideExisting: true, overrideExisting: true,
@ -1951,7 +1969,7 @@ class FreeplayState extends MusicBeatSubState
public static function build(?params:FreeplayStateParams, ?stickers:StickerSubState):MusicBeatState public static function build(?params:FreeplayStateParams, ?stickers:StickerSubState):MusicBeatState
{ {
var result:MainMenuState; var result:MainMenuState;
if (params?.fromResults?.playRankAnim) result = new MainMenuState(true); if (params?.fromResults?.playRankAnim ?? false) result = new MainMenuState(true);
else else
result = new MainMenuState(false); result = new MainMenuState(false);

View file

@ -82,6 +82,11 @@ class PlayableCharacter implements IRegistryEntry<PlayerData>
return _data.freeplayDJ; return _data.freeplayDJ;
} }
public function getFreeplayDJText(index:Int):String
{
return _data.freeplayDJ.getFreeplayDJText(index);
}
/** /**
* Returns whether this character is unlocked. * Returns whether this character is unlocked.
*/ */

View file

@ -117,7 +117,10 @@ class MainMenuState extends MusicBeatState
FlxTransitionableState.skipNextTransIn = true; FlxTransitionableState.skipNextTransIn = true;
FlxTransitionableState.skipNextTransOut = true; FlxTransitionableState.skipNextTransOut = true;
openSubState(new FreeplayState()); openSubState(new FreeplayState(
{
character: FlxG.keys.pressed.SHIFT ? 'pico' : 'bf',
}));
}); });
#if CAN_OPEN_LINKS #if CAN_OPEN_LINKS

View file

@ -97,7 +97,7 @@ class SortUtil
* @param b The second string to compare. * @param b The second string to compare.
* @return 1 if `a` comes before `b`, -1 if `b` comes before `a`, 0 if they are equal * @return 1 if `a` comes before `b`, -1 if `b` comes before `a`, 0 if they are equal
*/ */
public static function alphabetically(a:String, b:String):Int public static function alphabetically(?a:String, ?b:String):Int
{ {
a = a.toUpperCase(); a = a.toUpperCase();
b = b.toUpperCase(); b = b.toUpperCase();