1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2024-11-15 11:22:55 +00:00

Merge pull request #653 from FunkinCrew/feature/playable-pico-results

Feature/playable pico results
This commit is contained in:
Cameron Taylor 2024-07-08 21:52:23 -04:00 committed by GitHub
commit d9d0facfca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 225 additions and 255 deletions

View file

@ -2,7 +2,7 @@
<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.4.1" company="ninjamuffin99" />
<app title="Friday Night Funkin'" file="Funkin" packageName="com.funkin.fnf" package="com.funkin.fnf" main="Main" version="0.5.0" company="ninjamuffin99" />
<!--Switch Export with Unique ApplicationID and Icon-->
<set name="APP_ID" value="0x0100f6c013bbc000" />

2
assets

@ -1 +1 @@
Subproject commit 8d5bc0dce1e0cb4f545037ce4040e7a5f2d85871
Subproject commit 1b6502c3a4200cedb1195f926b09307722cbd347

View file

@ -223,6 +223,7 @@ class InitState extends FlxState
storyMode: false,
title: "Cum Song Erect by Kawai Sprite",
songId: "cum",
characterId: "pico-playable",
difficultyId: "nightmare",
isNewHighscore: true,
scoreData:
@ -238,8 +239,13 @@ class InitState extends FlxState
combo: 69,
maxCombo: 69,
totalNotesHit: 140,
totalNotes: 200 // 0,
totalNotes: 190
}
// 2000 = loss
// 240 = good
// 230 = great
// 210 = excellent
// 190 = perfect
},
}));
#elseif ANIMDEBUG

View file

@ -38,6 +38,8 @@ class PlayerData
@:optional
public var freeplayDJ:Null<PlayerFreeplayDJData> = null;
public var results:Null<PlayerResultsData> = null;
/**
* Whether this character is unlocked by default.
* Use a ScriptedPlayableCharacter to add custom logic.
@ -86,7 +88,6 @@ class PlayerFreeplayDJData
@:default("PROTECT YO NUTS")
var text3:String;
@:jignored
var animationMap:Map<String, AnimationData>;
@ -120,12 +121,18 @@ class PlayerFreeplayDJData
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 getFreeplayDJText(index:Int):String
{
switch (index)
{
case 1:
return text1;
case 2:
return text2;
case 3:
return text3;
default:
return '';
}
}
@ -178,6 +185,55 @@ class PlayerFreeplayDJData
}
}
typedef PlayerResultsData =
{
var perfect:Array<PlayerResultsAnimationData>;
var excellent:Array<PlayerResultsAnimationData>;
var great:Array<PlayerResultsAnimationData>;
var good:Array<PlayerResultsAnimationData>;
var loss:Array<PlayerResultsAnimationData>;
};
typedef PlayerResultsAnimationData =
{
/**
* `sparrow` or `animate` or whatever
*/
var renderType:String;
var assetPath:String;
@:optional
@:default([0, 0])
var offsets:Array<Float>;
@:optional
@:default(500)
var zIndex:Int;
@:optional
@:default(0.0)
var delay:Float;
@:optional
@:default(1.0)
var scale:Float;
@:optional
@:default('')
var startFrameLabel:Null<String>;
@:optional
@:default(true)
var looped:Bool;
@:optional
var loopFrame:Null<Int>;
@:optional
var loopFrameLabel:Null<String>;
};
typedef PlayerFreeplayDJCartoonData =
{
var soundClickFrame:Int;

View file

@ -58,7 +58,7 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData>
* @param characterId The stage character ID.
* @return The playable character.
*/
public function getCharacterOwnerId(characterId:String):String
public function getCharacterOwnerId(characterId:String):Null<String>
{
return ownedCharacterIds[characterId];
}

View file

@ -131,12 +131,14 @@ class FlxAtlasSprite extends FlxAnimate
anim.play('', false, false);
}
}
// Skip if the animation doesn't exist
if (!hasAnimation(id))
else
{
trace('Animation ' + id + ' not found');
return;
// Skip if the animation doesn't exist
if (!hasAnimation(id))
{
trace('Animation ' + id + ' not found');
return;
}
}
anim.callback = function(_, frame:Int) {
@ -156,6 +158,10 @@ class FlxAtlasSprite extends FlxAnimate
}
};
anim.onComplete = function() {
onAnimationFinish.dispatch(id);
};
// Prevent other animations from playing if `ignoreOther` is true.
if (ignoreOther) canPlayOtherAnims = false;

View file

@ -3165,6 +3165,7 @@ class PlayState extends MusicBeatSubState
storyMode: PlayStatePlaylist.isStoryMode,
songId: currentChart.song.id,
difficultyId: currentDifficulty,
characterId: currentChart.characters.player,
title: PlayStatePlaylist.isStoryMode ? ('${PlayStatePlaylist.campaignTitle}') : ('${currentChart.songName} by ${currentChart.songArtist}'),
prevScoreData: prevScoreData,
scoreData:

View file

@ -14,6 +14,9 @@ import flixel.math.FlxRect;
import flixel.text.FlxBitmapText;
import funkin.ui.freeplay.FreeplayScore;
import flixel.text.FlxText;
import funkin.data.freeplay.player.PlayerRegistry;
import funkin.data.freeplay.player.PlayerData;
import funkin.ui.freeplay.charselect.PlayableCharacter;
import flixel.util.FlxColor;
import flixel.tweens.FlxEase;
import funkin.graphics.FunkinCamera;
@ -55,14 +58,17 @@ class ResultState extends MusicBeatSubState
final highscoreNew:FlxSprite;
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 characterAtlasAnimations:Array<
{
sprite:FlxAtlasSprite,
delay:Float,
forceLoop:Bool
}> = [];
var characterSparrowAnimations:Array<
{
sprite:FunkinSprite,
delay:Float
}> = [];
var rankBg:FunkinSprite;
final cameraBG:FunkinCamera;
@ -157,118 +163,95 @@ class ResultState extends MusicBeatSubState
soundSystem.zIndex = 1100;
add(soundSystem);
switch (rank)
// Fetch playable character data. Default to BF on the results screen if we can't find it.
var playerCharacterId:Null<String> = PlayerRegistry.instance.getCharacterOwnerId(params.characterId);
var playerCharacter:Null<PlayableCharacter> = PlayerRegistry.instance.fetchEntry(playerCharacterId ?? 'bf');
trace('Got playable character: ${playerCharacter?.getName()}');
// Query JSON data based on the rank, then use that to build the animation(s) the player sees.
var playerAnimationDatas:Array<PlayerResultsAnimationData> = playerCharacter != null ? playerCharacter.getResultsAnimationDatas(rank) : [];
for (animData in playerAnimationDatas)
{
case PERFECT | PERFECT_GOLD:
heartsPerfect = new FlxAtlasSprite(1342, 370, Paths.animateAtlas("resultScreen/results-bf/resultsPERFECT/hearts", "shared"));
heartsPerfect.visible = false;
heartsPerfect.zIndex = 501;
add(heartsPerfect);
if (animData == null) continue;
heartsPerfect.anim.onComplete = () -> {
if (heartsPerfect != null)
var animPath:String = Paths.stripLibrary(animData.assetPath);
var animLibrary:String = Paths.getLibrary(animData.assetPath);
var offsets = animData.offsets ?? [0, 0];
switch (animData.renderType)
{
case 'animateatlas':
var animation:FlxAtlasSprite = new FlxAtlasSprite(offsets[0], offsets[1], Paths.animateAtlas(animPath, animLibrary));
animation.zIndex = animData.zIndex ?? 500;
animation.scale.set(animData.scale ?? 1.0, animData.scale ?? 1.0);
if (!(animData.looped ?? true))
{
// bfPerfect.anim.curFrame = 137;
heartsPerfect.anim.curFrame = 43;
heartsPerfect.anim.play(); // unpauses this anim, since it's on PlayOnce!
// Animation is not looped.
animation.onAnimationFinish.add((_name:String) -> {
if (animation != null)
{
animation.anim.pause();
}
});
}
};
bfPerfect = new FlxAtlasSprite(1342, 370, Paths.animateAtlas("resultScreen/results-bf/resultsPERFECT", "shared"));
bfPerfect.visible = false;
bfPerfect.zIndex = 500;
add(bfPerfect);
bfPerfect.anim.onComplete = () -> {
if (bfPerfect != null)
else if (animData.loopFrameLabel != null)
{
// bfPerfect.anim.curFrame = 137;
bfPerfect.anim.curFrame = 137;
bfPerfect.anim.play(); // unpauses this anim, since it's on PlayOnce!
animation.onAnimationFinish.add((_name:String) -> {
if (animation != null)
{
animation.playAnimation(animData.loopFrameLabel ?? ''); // unpauses this anim, since it's on PlayOnce!
}
});
}
};
case EXCELLENT:
bfExcellent = new FlxAtlasSprite(1329, 429, Paths.animateAtlas("resultScreen/results-bf/resultsEXCELLENT", "shared"));
bfExcellent.visible = false;
bfExcellent.zIndex = 500;
add(bfExcellent);
bfExcellent.anim.onComplete = () -> {
if (bfExcellent != null)
else if (animData.loopFrame != null)
{
bfExcellent.anim.curFrame = 28;
bfExcellent.anim.play(); // unpauses this anim, since it's on PlayOnce!
animation.onAnimationFinish.add((_name:String) -> {
if (animation != null)
{
animation.anim.curFrame = animData.loopFrame ?? 0;
animation.anim.play(); // unpauses this anim, since it's on PlayOnce!
}
});
}
};
case GREAT:
gfGreat = new FlxAtlasSprite(802, 331, Paths.animateAtlas("resultScreen/results-bf/resultsGREAT/gf", "shared"));
gfGreat.visible = false;
gfGreat.zIndex = 499;
add(gfGreat);
// Hide until ready to play.
animation.visible = false;
// Queue to play.
characterAtlasAnimations.push(
{
sprite: animation,
delay: animData.delay ?? 0.0,
forceLoop: (animData.loopFrame ?? -1) == 0
});
// Add to the scene.
add(animation);
case 'sparrow':
var animation:FunkinSprite = FunkinSprite.createSparrow(offsets[0], offsets[1], animPath);
animation.animation.addByPrefix('idle', '', 24, false, false, false);
gfGreat.scale.set(0.93, 0.93);
gfGreat.anim.onComplete = () -> {
if (gfGreat != null)
if (animData.loopFrame != null)
{
gfGreat.anim.curFrame = 9;
gfGreat.anim.play(); // unpauses this anim, since it's on PlayOnce!
animation.animation.finishCallback = (_name:String) -> {
if (animation != null)
{
animation.animation.play('idle', true, false, animData.loopFrame ?? 0);
}
}
}
};
bfGreat = new FlxAtlasSprite(929, 363, Paths.animateAtlas("resultScreen/results-bf/resultsGREAT/bf", "shared"));
bfGreat.visible = false;
bfGreat.zIndex = 500;
add(bfGreat);
bfGreat.scale.set(0.93, 0.93);
bfGreat.anim.onComplete = () -> {
if (bfGreat != null)
{
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');
gfGood.animation.addByPrefix("clap", "Girlfriend Good Anim", 24, false);
gfGood.visible = false;
gfGood.zIndex = 500;
gfGood.animation.finishCallback = _ -> {
if (gfGood != null)
{
gfGood.animation.play('clap', true, false, 9);
}
};
add(gfGood);
bfGood = FunkinSprite.createSparrow(640, -200, 'resultScreen/results-bf/resultsGOOD/resultBoyfriendGOOD');
bfGood.animation.addByPrefix("fall", "Boyfriend Good Anim0", 24, false);
bfGood.visible = false;
bfGood.zIndex = 501;
bfGood.animation.finishCallback = function(_) {
if (bfGood != null)
{
bfGood.animation.play('fall', true, false, 14);
}
};
add(bfGood);
case SHIT:
bfShit = new FlxAtlasSprite(0, 20, Paths.animateAtlas("resultScreen/results-bf/resultsSHIT", "shared"));
bfShit.visible = false;
bfShit.zIndex = 500;
add(bfShit);
bfShit.onAnimationFinish.add((animName) -> {
if (bfShit != null)
{
bfShit.playAnimation('Loop Start');
}
});
// Hide until ready to play.
animation.visible = false;
// Queue to play.
characterSparrowAnimations.push(
{
sprite: animation,
delay: animData.delay ?? 0.0
});
// Add to the scene.
add(animation);
}
}
var diffSpr:String = 'diff_${params?.difficultyId ?? 'Normal'}';
@ -587,94 +570,22 @@ class ResultState extends MusicBeatSubState
{
showSmallClearPercent();
switch (rank)
for (atlas in characterAtlasAnimations)
{
case PERFECT | PERFECT_GOLD:
if (bfPerfect == null)
{
trace("Could not build PERFECT animation!");
}
else
{
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)
{
trace("Could not build EXCELLENT animation!");
}
else
{
bfExcellent.visible = true;
bfExcellent.playAnimation('');
}
case GREAT:
if (bfGreat == null)
{
trace("Could not build GREAT animation!");
}
else
{
bfGreat.visible = true;
bfGreat.playAnimation('');
}
new FlxTimer().start(atlas.delay, _ -> {
if (atlas.sprite == null) return;
atlas.sprite.visible = true;
atlas.sprite.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)
{
trace("Could not build SHIT animation!");
}
else
{
bfShit.visible = true;
bfShit.playAnimation('Intro');
}
case GOOD:
if (bfGood == null)
{
trace("Could not build GOOD animation!");
}
else
{
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)
{
gfGood.animation.play('clap', true);
gfGood.visible = true;
}
else
{
trace("Could not build GOOD animation!");
}
});
}
default:
for (sprite in characterSparrowAnimations)
{
new FlxTimer().start(sprite.delay, _ -> {
if (sprite.sprite == null) return;
sprite.sprite.visible = true;
sprite.sprite.animation.play('idle', true);
});
}
}
@ -776,52 +687,6 @@ class ResultState extends MusicBeatSubState
// }));
// }
// 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;
@ -922,12 +787,21 @@ typedef ResultsStateParams =
var storyMode:Bool;
/**
* A readable title for the song we just played.
* Either "Song Name by Artist Name" or "Week Name"
*/
var title:String;
/**
* The internal song ID for the song we just played.
*/
var songId:String;
/**
* The character ID for the song we just played.
*/
var characterId:String;
/**
* Whether the displayed score is a new highscore
*/

View file

@ -3,6 +3,7 @@ package funkin.ui.freeplay.charselect;
import funkin.data.IRegistryEntry;
import funkin.data.freeplay.player.PlayerData;
import funkin.data.freeplay.player.PlayerRegistry;
import funkin.play.scoring.Scoring.ScoringRank;
/**
* An object used to retrieve data about a playable character (also known as "weeks").
@ -87,6 +88,32 @@ class PlayableCharacter implements IRegistryEntry<PlayerData>
return _data.freeplayDJ.getFreeplayDJText(index);
}
/**
* @param rank Which rank to get info for
* @return An array of animations. For example, BF Great has two animations, one for BF and one for GF
*/
public function getResultsAnimationDatas(rank:ScoringRank):Array<PlayerResultsAnimationData>
{
if (_data.results == null)
{
return [];
}
switch (rank)
{
case PERFECT | PERFECT_GOLD:
return _data.results.perfect;
case EXCELLENT:
return _data.results.excellent;
case GREAT:
return _data.results.great;
case GOOD:
return _data.results.good;
case SHIT:
return _data.results.loss;
}
}
/**
* Returns whether this character is unlocked.
*/