1
0
Fork 0
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:
EliteMasterEric 2024-08-28 06:11:01 -04:00
parent 1fb5c31c22
commit e7fca119f8
5 changed files with 178 additions and 11 deletions

View file

@ -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.

View file

@ -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.
*/

View file

@ -290,7 +290,6 @@ class CharSelectSubState extends MusicBeatSubState
}
var grpIcons:FlxSpriteGroup;
var grpXSpread(default, set):Float = 107;
var grpYSpread(default, set):Float = 127;

View file

@ -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;
}

View file

@ -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)