mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2024-11-15 11:22:55 +00:00
Logic + animations for new unlocks
This commit is contained in:
parent
1fb5c31c22
commit
e7fca119f8
|
@ -53,6 +53,41 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData>
|
|||
log('Loaded ${countEntries()} playable characters with ${ownedCharacterIds.size()} associations.');
|
||||
}
|
||||
|
||||
public function countUnlockedCharacters():Int
|
||||
{
|
||||
var count = 0;
|
||||
|
||||
for (charId in listEntryIds())
|
||||
{
|
||||
var player = fetchEntry(charId);
|
||||
if (player == null) continue;
|
||||
|
||||
if (player.isUnlocked()) count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public function hasNewCharacter():Bool
|
||||
{
|
||||
var characters = Save.instance.charactersSeen.clone();
|
||||
|
||||
for (charId in listEntryIds())
|
||||
{
|
||||
var player = fetchEntry(charId);
|
||||
if (player == null) continue;
|
||||
|
||||
if (!player.isUnlocked()) continue;
|
||||
if (characters.contains(charId)) continue;
|
||||
|
||||
// This character is unlocked but we haven't seen them in Freeplay yet.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fallthrough case.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the playable character associated with a given stage character.
|
||||
* @param characterId The stage character ID.
|
||||
|
|
|
@ -121,6 +121,12 @@ class Save
|
|||
modOptions: [],
|
||||
},
|
||||
|
||||
unlocks:
|
||||
{
|
||||
// Default to having seen the default character.
|
||||
charactersSeen: ["bf"],
|
||||
},
|
||||
|
||||
optionsChartEditor:
|
||||
{
|
||||
// Reasonable defaults.
|
||||
|
@ -393,6 +399,22 @@ class Save
|
|||
return data.optionsChartEditor.playbackSpeed;
|
||||
}
|
||||
|
||||
public var charactersSeen(get, never):Array<String>;
|
||||
|
||||
function get_charactersSeen():Array<String>
|
||||
{
|
||||
return data.unlocks.charactersSeen;
|
||||
}
|
||||
|
||||
/**
|
||||
* When we've seen a character unlock, add it to the list of characters seen.
|
||||
* @param character
|
||||
*/
|
||||
public function addCharacterSeen(character:String):Void
|
||||
{
|
||||
data.unlocks.charactersSeen.push(character);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the score the user achieved for a given level on a given difficulty.
|
||||
*
|
||||
|
@ -471,10 +493,18 @@ class Save
|
|||
for (difficulty in difficultyList)
|
||||
{
|
||||
var score:Null<SaveScoreData> = getLevelScore(levelId, difficulty);
|
||||
// TODO: Do we need to check accuracy/score here?
|
||||
if (score != null)
|
||||
{
|
||||
return true;
|
||||
if (score.score > 0)
|
||||
{
|
||||
// Level has score data, which means we cleared it!
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Level has score data, but the score is 0.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
@ -630,10 +660,18 @@ class Save
|
|||
for (difficulty in difficultyList)
|
||||
{
|
||||
var score:Null<SaveScoreData> = getSongScore(songId, difficulty);
|
||||
// TODO: Do we need to check accuracy/score here?
|
||||
if (score != null)
|
||||
{
|
||||
return true;
|
||||
if (score.score > 0)
|
||||
{
|
||||
// Level has score data, which means we cleared it!
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Level has score data, but the score is 0.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
@ -956,6 +994,8 @@ typedef RawSaveData =
|
|||
*/
|
||||
var options:SaveDataOptions;
|
||||
|
||||
var unlocks:SaveDataUnlocks;
|
||||
|
||||
/**
|
||||
* The user's favorited songs in the Freeplay menu,
|
||||
* as a list of song IDs.
|
||||
|
@ -980,6 +1020,15 @@ typedef SaveApiNewgroundsData =
|
|||
var sessionId:Null<String>;
|
||||
}
|
||||
|
||||
typedef SaveDataUnlocks =
|
||||
{
|
||||
/**
|
||||
* Every time we see the unlock animation for a character,
|
||||
* add it to this list so that we don't show it again.
|
||||
*/
|
||||
var charactersSeen:Array<String>;
|
||||
}
|
||||
|
||||
/**
|
||||
* An anoymous structure containing options about the user's high scores.
|
||||
*/
|
||||
|
|
|
@ -290,7 +290,6 @@ class CharSelectSubState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
var grpIcons:FlxSpriteGroup;
|
||||
|
||||
var grpXSpread(default, set):Float = 107;
|
||||
var grpYSpread(default, set):Float = 127;
|
||||
|
||||
|
|
|
@ -99,7 +99,7 @@ class FreeplayDJ extends FlxAtlasSprite
|
|||
playFlashAnimation(animPrefix, true, false, true);
|
||||
}
|
||||
|
||||
if (getCurrentAnimation() == animPrefix && this.isLoopFinished())
|
||||
if (getCurrentAnimation() == animPrefix && this.isLoopComplete())
|
||||
{
|
||||
if (timeIdling >= IDLE_EGG_PERIOD && !seenIdleEasterEgg)
|
||||
{
|
||||
|
@ -111,6 +111,16 @@ class FreeplayDJ extends FlxAtlasSprite
|
|||
}
|
||||
}
|
||||
timeIdling += elapsed;
|
||||
case NewUnlock:
|
||||
var animPrefix = playableCharData.getAnimationPrefix('newUnlock');
|
||||
if (!hasAnimation(animPrefix))
|
||||
{
|
||||
currentState = Idle;
|
||||
}
|
||||
if (getCurrentAnimation() != animPrefix)
|
||||
{
|
||||
playFlashAnimation(animPrefix, true, false, true);
|
||||
}
|
||||
case Confirm:
|
||||
var animPrefix = playableCharData.getAnimationPrefix('confirm');
|
||||
if (getCurrentAnimation() != animPrefix) playFlashAnimation(animPrefix, false);
|
||||
|
@ -226,7 +236,14 @@ class FreeplayDJ extends FlxAtlasSprite
|
|||
|
||||
if (name == playableCharData.getAnimationPrefix('intro'))
|
||||
{
|
||||
currentState = Idle;
|
||||
if (PlayerRegistry.instance.hasNewCharacter())
|
||||
{
|
||||
currentState = NewUnlock;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentState = Idle;
|
||||
}
|
||||
onIntroDone.dispatch();
|
||||
}
|
||||
else if (name == playableCharData.getAnimationPrefix('idle'))
|
||||
|
@ -266,9 +283,17 @@ class FreeplayDJ extends FlxAtlasSprite
|
|||
// runTvLogic();
|
||||
}
|
||||
trace('Replay idle: ${frame}');
|
||||
playAnimation(playableCharData.getAnimationPrefix('cartoon'), true, false, false, frame);
|
||||
playFlashAnimation(playableCharData.getAnimationPrefix('cartoon'), true, false, false, frame);
|
||||
// trace('Finished confirm');
|
||||
}
|
||||
else if (name == playableCharData.getAnimationPrefix('newUnlock'))
|
||||
{
|
||||
// Animation should loop.
|
||||
}
|
||||
else if (name == playableCharData.getAnimationPrefix('charSelect'))
|
||||
{
|
||||
onCharSelectComplete();
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Finished ${name}');
|
||||
|
@ -281,6 +306,15 @@ class FreeplayDJ extends FlxAtlasSprite
|
|||
seenIdleEasterEgg = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamic function, it's actually a variable you can reassign!
|
||||
* `dj.onCharSelectComplete = function() {};`
|
||||
*/
|
||||
public dynamic function onCharSelectComplete():Void
|
||||
{
|
||||
trace('onCharSelectComplete()');
|
||||
}
|
||||
|
||||
var offsetX:Float = 0.0;
|
||||
var offsetY:Float = 0.0;
|
||||
|
||||
|
@ -342,6 +376,22 @@ class FreeplayDJ extends FlxAtlasSprite
|
|||
currentState = Confirm;
|
||||
}
|
||||
|
||||
public function toCharSelect():Void
|
||||
{
|
||||
if (hasAnimation('charSelect'))
|
||||
{
|
||||
currentState = CharSelect;
|
||||
var animPrefix = playableCharData.getAnimationPrefix('charSelect');
|
||||
playFlashAnimation(animPrefix, true, false, false, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
currentState = Confirm;
|
||||
// Call this immediately; otherwise, we get locked out of Character Select.
|
||||
onCharSelectComplete();
|
||||
}
|
||||
}
|
||||
|
||||
public function fistPumpIntro():Void
|
||||
{
|
||||
currentState = FistPumpIntro;
|
||||
|
@ -456,6 +506,15 @@ enum FreeplayDJState
|
|||
* The actual frame label that gets played may vary based on the player's success.
|
||||
*/
|
||||
FistPump;
|
||||
IdleEasterEgg;
|
||||
Cartoon;
|
||||
|
||||
/**
|
||||
* Plays an animation to indicate that the player has a new unlock in Character Select.
|
||||
* Overrides all idle animations as well as the fist pump. Only Confirm and CharSelect will override this.
|
||||
*/
|
||||
NewUnlock;
|
||||
|
||||
/**
|
||||
* Plays an animation to transition to the Character Select screen.
|
||||
*/
|
||||
CharSelect;
|
||||
}
|
||||
|
|
|
@ -1253,7 +1253,32 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
if (controls.FREEPLAY_CHAR_SELECT && !busy)
|
||||
{
|
||||
FlxG.switchState(new funkin.ui.charSelect.CharSelectSubState());
|
||||
// Check if we have ACCESS to character select!
|
||||
trace('Is Pico unlocked? ${PlayerRegistry.instance.fetchEntry('pico')?.isUnlocked()}');
|
||||
trace('Number of characters: ${PlayerRegistry.instance.countUnlockedCharacters()}');
|
||||
|
||||
if (PlayerRegistry.instance.countUnlockedCharacters() > 1)
|
||||
{
|
||||
if (dj != null)
|
||||
{
|
||||
busy = true;
|
||||
// Transition to character select after animation
|
||||
dj.onCharSelectComplete = function() {
|
||||
FlxG.switchState(new funkin.ui.charSelect.CharSelectSubState());
|
||||
}
|
||||
dj.toCharSelect();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Transition to character select immediately
|
||||
FlxG.switchState(new funkin.ui.charSelect.CharSelectSubState());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Not enough characters unlocked to open character select!');
|
||||
FunkinSound.playOnce(Paths.sound('cancelMenu'));
|
||||
}
|
||||
}
|
||||
|
||||
if (controls.FREEPLAY_FAVORITE && !busy)
|
||||
|
|
Loading…
Reference in a new issue