1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2024-11-21 14:23:00 +00:00

fix(freeplay)!: Proper variation / difficulty loading for Freeplay Menu

Previously the game would load variations in a `variation-difficulty` string format, but now we map it out better and filter it based on that, rather than messing around with suffixes and whatnot. If you have a mod that depended on the functionality of the `variation-difficulty` format, you should accomodate that functionality in another way

re-add freeplay song preview

song names and icons implemented again

implement the scoring rank, bpm, and difficulty crud

albumId loading fix
This commit is contained in:
Cameron Taylor 2024-10-01 20:32:48 -04:00
parent f862fb2c3e
commit c0314c85ec
3 changed files with 385 additions and 446 deletions

View file

@ -15,6 +15,7 @@ import funkin.data.song.SongRegistry;
import funkin.modding.IScriptedClass.IPlayStateScriptedClass;
import funkin.modding.events.ScriptEvent;
import funkin.ui.freeplay.charselect.PlayableCharacter;
import funkin.data.freeplay.player.PlayerRegistry;
import funkin.util.SortUtil;
/**
@ -79,7 +80,12 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
// key = variation id, value = metadata
final _metadata:Map<String, SongMetadata>;
final difficulties:Map<String, SongDifficulty>;
/**
* holds the difficulties (as in SongDifficulty) for each variation
* difficulties.get('default').get('easy') would return the easy difficulty for the default variation
*/
final difficulties:Map<String, Map<String, SongDifficulty>>;
/**
* The list of variations a song has.
@ -146,7 +152,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
{
this.id = id;
difficulties = new Map<String, SongDifficulty>();
difficulties = new Map<String, Map<String, SongDifficulty>>();
_data = _fetchData(id);
@ -156,7 +162,8 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
{
for (vari in _data.playData.songVariations)
{
if (!validateVariationId(vari)) {
if (!validateVariationId(vari))
{
trace(' [WARN] Variation id "$vari" is invalid, skipping...');
continue;
}
@ -249,22 +256,39 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
* List the album IDs for each variation of the song.
* @return A map of variation IDs to album IDs.
*/
public function listAlbums():Map<String, String>
public function listAlbums(variation:String):Map<String, String>
{
var result:Map<String, String> = new Map<String, String>();
for (difficultyId in difficulties.keys())
for (variationMap in difficulties)
{
var meta:Null<SongDifficulty> = difficulties.get(difficultyId);
if (meta != null && meta.album != null)
for (difficultyId in variationMap.keys())
{
result.set(difficultyId, meta.album);
var meta:Null<SongDifficulty> = variationMap.get(difficultyId);
if (meta != null && meta.album != null)
{
result.set(difficultyId, meta.album);
}
}
}
return result;
}
/**
* Input a difficulty ID and a variation ID, and get the album ID.
* @param diffId
* @param variation
* @return String
*/
public function getAlbumId(diffId:String, variation:String):String
{
var diff:Null<SongDifficulty> = getDifficulty(diffId, variation);
if (diff == null) return '';
return diff.album ?? '';
}
/**
* Populate the difficulty data from the provided metadata.
* Does not load chart data (that is triggered later when we want to play the song).
@ -285,6 +309,9 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
continue;
}
// This resides within difficulties
var difficultyMap:Map<String, SongDifficulty> = new Map<String, SongDifficulty>();
// There may be more difficulties in the chart file than in the metadata,
// (i.e. non-playable charts like the one used for Pico on the speaker in Stress)
// but all the difficulties in the metadata must be in the chart file.
@ -309,10 +336,9 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
difficulty.noteStyle = metadata.playData.noteStyle;
difficulty.characters = metadata.playData.characters;
var variationSuffix = (metadata.variation != Constants.DEFAULT_VARIATION) ? '-${metadata.variation}' : '';
difficulties.set('$diffId$variationSuffix', difficulty);
difficultyMap.set(diffId, difficulty);
}
difficulties.set(metadata.variation, difficultyMap);
}
}
@ -345,15 +371,18 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
for (diffId in chartNotes.keys())
{
// Retrieve the cached difficulty data.
var variationSuffix = (variation != Constants.DEFAULT_VARIATION) ? '-$variation' : '';
var difficulty:Null<SongDifficulty> = difficulties.get('$diffId$variationSuffix');
if (difficulty == null)
// Retrieve the cached difficulty data. This one could potentially be null.
var nullDiff:Null<SongDifficulty> = getDifficulty(diffId, variation);
// if the difficulty doesn't exist, create a new one, and then proceed to fill it with data.
// I mostly do this since I don't wanna throw around ? everywhere for null check lol?
var difficulty:SongDifficulty = nullDiff ?? new SongDifficulty(this, diffId, variation);
if (nullDiff == null)
{
trace('Fabricated new difficulty for $diffId.');
difficulty = new SongDifficulty(this, diffId, variation);
var metadata = _metadata.get(variation);
difficulties.set('$diffId$variationSuffix', difficulty);
difficulties.get(variation)?.set(diffId, difficulty);
if (metadata != null)
{
@ -396,11 +425,9 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
for (currentVariation in variations)
{
var variationSuffix = (currentVariation != Constants.DEFAULT_VARIATION) ? '-$currentVariation' : '';
if (difficulties.exists('$diffId$variationSuffix'))
if (difficulties.get(currentVariation)?.exists(diffId) ?? false)
{
return difficulties.get('$diffId$variationSuffix');
return difficulties.get(currentVariation)?.get(diffId);
}
}
@ -417,8 +444,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
for (variationId in possibleVariations)
{
var variationSuffix = (variationId != Constants.DEFAULT_VARIATION) ? '-$variationId' : '';
if (difficulties.exists('$diffId$variationSuffix')) return variationId;
if (difficulties.exists('$variationId')) return variationId;
}
return null;
@ -440,7 +466,6 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
}
var result = [];
trace('Evaluating variations for ${this.id} ${char.id}: ${this.variations}');
for (variation in variations)
{
var metadata = _metadata.get(variation);
@ -459,6 +484,19 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
return result;
}
/**
* Nearly the same thing as getVariationsByCharacter, but takes a character ID instead.
* @param charId
* @return Array<String>
* @see getVariationsByCharacter
*/
public function getVariationsByCharacterId(?charId:String):Array<String>
{
var charPlayer = PlayerRegistry.instance.fetchEntry(charId ?? '');
return getVariationsByCharacter(charPlayer);
}
/**
* List all the difficulties in this song.
*
@ -501,6 +539,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
/**
* TODO: This line of code makes me sad, but you can't really fix it without a breaking migration.
* @return `easy`, `erect`, `normal-pico`, etc.
* @deprecated This function is deprecated, Funkin no longer uses suffixed difficulties.
*/
public function listSuffixedDifficulties(variationIds:Array<String>, ?showLocked:Bool, ?showHidden:Bool):Array<String>
{
@ -529,8 +568,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
for (targetVariation in variationIds)
{
var variationSuffix = (targetVariation != Constants.DEFAULT_VARIATION) ? '-$targetVariation' : '';
if (difficulties.exists('$diffId$variationSuffix')) return true;
if (difficulties.get(targetVariation)?.exists(diffId) ?? false) return true;
}
return false;
}
@ -565,13 +603,16 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
}
/**
* Purge the cached chart data for each difficulty of this song.
* Purge the cached chart data for each difficulty/variation of this song.
*/
public function clearCharts():Void
{
for (diff in difficulties)
for (variationMap in difficulties)
{
diff.clearChart();
for (diff in variationMap)
{
diff.clearChart();
}
}
}
@ -647,7 +688,8 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
* Auto-accept if it's one of the base game default variations.
* Reject if the ID starts with a number, or contains invalid characters.
*/
static function validateVariationId(variation:String):Bool {
static function validateVariationId(variation:String):Bool
{
if (Constants.DEFAULT_VARIATION_LIST.contains(variation)) return true;
return VARIATION_REGEX.match(variation);

File diff suppressed because it is too large Load diff

View file

@ -36,7 +36,7 @@ class SongMenuItem extends FlxSpriteGroup
* Modify this by calling `init()`
* If `null`, assume this SongMenuItem is for the "Random Song" option.
*/
public var songData(default, null):Null<FreeplaySongData> = null;
public var freeplayData(default, null):Null<FreeplaySongData> = null;
public var selected(default, set):Bool;
@ -422,6 +422,34 @@ class SongMenuItem extends FlxSpriteGroup
return evilTrail.color;
}
public function refreshDisplay():Void
{
if (freeplayData == null)
{
songText.text = 'Random';
pixelIcon.visible = false;
ranking.visible = false;
blurredRanking.visible = false;
favIcon.visible = false;
favIconBlurred.visible = false;
newText.visible = false;
}
else
{
songText.text = freeplayData.fullSongName;
if (freeplayData.songCharacter != null) pixelIcon.setCharacter(freeplayData.songCharacter);
pixelIcon.visible = true;
updateBPM(Std.int(freeplayData.songStartingBpm) ?? 0);
updateDifficultyRating(freeplayData.difficultyRating ?? 0);
updateScoringRank(freeplayData.scoringRank);
newText.visible = freeplayData.isNew;
favIcon.visible = freeplayData.isFav;
favIconBlurred.visible = freeplayData.isFav;
checkClip();
}
updateSelected();
}
function updateDifficultyRating(newRating:Int):Void
{
var ratingPadded:String = newRating < 10 ? '0$newRating' : '$newRating';
@ -500,11 +528,11 @@ class SongMenuItem extends FlxSpriteGroup
updateSelected();
}
public function init(?x:Float, ?y:Float, songData:Null<FreeplaySongData>, ?styleData:FreeplayStyle = null):Void
public function init(?x:Float, ?y:Float, freeplayData:Null<FreeplaySongData>, ?styleData:FreeplayStyle = null):Void
{
if (x != null) this.x = x;
if (y != null) this.y = y;
this.songData = songData;
this.freeplayData = freeplayData;
// im so mad i have to do this but im pretty sure with the capsules recycling i cant call the new function properly :/
// if thats possible someone Please change the new function to be something like
@ -517,21 +545,13 @@ class SongMenuItem extends FlxSpriteGroup
songText.applyStyle(styleData);
}
// Update capsule text.
songText.text = songData?.songName ?? 'Random';
// Update capsule character.
if (songData?.songCharacter != null) pixelIcon.setCharacter(songData.songCharacter);
updateBPM(Std.int(songData?.songStartingBpm) ?? 0);
updateDifficultyRating(songData?.difficultyRating ?? 0);
updateScoringRank(songData?.scoringRank);
newText.visible = songData?.isNew;
updateScoringRank(freeplayData?.scoringRank);
favIcon.animation.curAnim.curFrame = favIcon.animation.curAnim.numFrames - 1;
favIconBlurred.animation.curAnim.curFrame = favIconBlurred.animation.curAnim.numFrames - 1;
// Update opacity, offsets, etc.
updateSelected();
refreshDisplay();
checkWeek(songData?.songId);
checkWeek(freeplayData?.data.id);
}
var frameInTicker:Float = 0;