mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2024-11-15 11:22:55 +00:00
Migrate results animations to JSON data so we can implement them per-character
This commit is contained in:
parent
bd17d965f5
commit
e32fcee77c
2
assets
2
assets
|
@ -1 +1 @@
|
|||
Subproject commit bc1650ba789d675683a8c0cc27b1e2a42cb686cf
|
||||
Subproject commit 0351610af02b45eb25e4bbfef0380f6f1d8cf737
|
|
@ -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,7 +239,7 @@ class InitState extends FlxState
|
|||
combo: 69,
|
||||
maxCombo: 69,
|
||||
totalNotesHit: 140,
|
||||
totalNotes: 200 // 0,
|
||||
totalNotes: 240 // 0,
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
|
|
@ -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,51 @@ 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
|
||||
var loopFrame:Null<Int>;
|
||||
|
||||
@:optional
|
||||
var loopFrameLabel:Null<String>;
|
||||
};
|
||||
|
||||
typedef PlayerFreeplayDJCartoonData =
|
||||
{
|
||||
var soundClickFrame:Int;
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -3156,6 +3156,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:
|
||||
|
|
|
@ -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,16 @@ 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
|
||||
}> = [];
|
||||
var characterSparrowAnimations:Array<
|
||||
{
|
||||
sprite:FunkinSprite,
|
||||
delay:Float
|
||||
}> = [];
|
||||
|
||||
var rankBg:FunkinSprite;
|
||||
final cameraBG:FunkinCamera;
|
||||
|
@ -157,118 +162,84 @@ 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.loopFrameLabel != null)
|
||||
{
|
||||
// bfPerfect.anim.curFrame = 137;
|
||||
heartsPerfect.anim.curFrame = 43;
|
||||
heartsPerfect.anim.play(); // unpauses this anim, since it's on PlayOnce!
|
||||
animation.anim.onComplete = () -> {
|
||||
if (animation != null)
|
||||
{
|
||||
animation.playAnimation(animData.loopFrameLabel ?? ''); // unpauses this anim, since it's on PlayOnce!
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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.loopFrame != null)
|
||||
{
|
||||
// bfPerfect.anim.curFrame = 137;
|
||||
bfPerfect.anim.curFrame = 137;
|
||||
bfPerfect.anim.play(); // unpauses this anim, since it's on PlayOnce!
|
||||
animation.anim.onComplete = () -> {
|
||||
if (animation != null)
|
||||
{
|
||||
animation.anim.curFrame = animData.loopFrame ?? 0;
|
||||
animation.anim.play(); // 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);
|
||||
// Hide until ready to play.
|
||||
animation.visible = false;
|
||||
// Queue to play.
|
||||
characterAtlasAnimations.push(
|
||||
{
|
||||
sprite: animation,
|
||||
delay: animData.delay ?? 0.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);
|
||||
|
||||
bfExcellent.anim.onComplete = () -> {
|
||||
if (bfExcellent != null)
|
||||
if (animData.loopFrame != null)
|
||||
{
|
||||
bfExcellent.anim.curFrame = 28;
|
||||
bfExcellent.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
case GREAT:
|
||||
gfGreat = new FlxAtlasSprite(802, 331, Paths.animateAtlas("resultScreen/results-bf/resultsGREAT/gf", "shared"));
|
||||
gfGreat.visible = false;
|
||||
gfGreat.zIndex = 499;
|
||||
add(gfGreat);
|
||||
|
||||
gfGreat.scale.set(0.93, 0.93);
|
||||
|
||||
gfGreat.anim.onComplete = () -> {
|
||||
if (gfGreat != null)
|
||||
{
|
||||
gfGreat.anim.curFrame = 9;
|
||||
gfGreat.anim.play(); // unpauses this anim, since it's on PlayOnce!
|
||||
}
|
||||
};
|
||||
|
||||
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 +558,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 +675,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 +775,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
|
||||
*/
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
Loading…
Reference in a new issue