Skip to content

Commit c0314c8

Browse files
committed
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
1 parent f862fb2 commit c0314c8

File tree

3 files changed

+384
-445
lines changed

3 files changed

+384
-445
lines changed

source/funkin/play/song/Song.hx

Lines changed: 72 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import funkin.data.song.SongRegistry;
1515
import funkin.modding.IScriptedClass.IPlayStateScriptedClass;
1616
import funkin.modding.events.ScriptEvent;
1717
import funkin.ui.freeplay.charselect.PlayableCharacter;
18+
import funkin.data.freeplay.player.PlayerRegistry;
1819
import funkin.util.SortUtil;
1920

2021
/**
@@ -79,7 +80,12 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
7980

8081
// key = variation id, value = metadata
8182
final _metadata:Map<String, SongMetadata>;
82-
final difficulties:Map<String, SongDifficulty>;
83+
84+
/**
85+
* holds the difficulties (as in SongDifficulty) for each variation
86+
* difficulties.get('default').get('easy') would return the easy difficulty for the default variation
87+
*/
88+
final difficulties:Map<String, Map<String, SongDifficulty>>;
8389

8490
/**
8591
* The list of variations a song has.
@@ -146,7 +152,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
146152
{
147153
this.id = id;
148154

149-
difficulties = new Map<String, SongDifficulty>();
155+
difficulties = new Map<String, Map<String, SongDifficulty>>();
150156

151157
_data = _fetchData(id);
152158

@@ -156,7 +162,8 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
156162
{
157163
for (vari in _data.playData.songVariations)
158164
{
159-
if (!validateVariationId(vari)) {
165+
if (!validateVariationId(vari))
166+
{
160167
trace(' [WARN] Variation id "$vari" is invalid, skipping...');
161168
continue;
162169
}
@@ -249,22 +256,39 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
249256
* List the album IDs for each variation of the song.
250257
* @return A map of variation IDs to album IDs.
251258
*/
252-
public function listAlbums():Map<String, String>
259+
public function listAlbums(variation:String):Map<String, String>
253260
{
254261
var result:Map<String, String> = new Map<String, String>();
255262

256-
for (difficultyId in difficulties.keys())
263+
for (variationMap in difficulties)
257264
{
258-
var meta:Null<SongDifficulty> = difficulties.get(difficultyId);
259-
if (meta != null && meta.album != null)
265+
for (difficultyId in variationMap.keys())
260266
{
261-
result.set(difficultyId, meta.album);
267+
var meta:Null<SongDifficulty> = variationMap.get(difficultyId);
268+
if (meta != null && meta.album != null)
269+
{
270+
result.set(difficultyId, meta.album);
271+
}
262272
}
263273
}
264274

265275
return result;
266276
}
267277

278+
/**
279+
* Input a difficulty ID and a variation ID, and get the album ID.
280+
* @param diffId
281+
* @param variation
282+
* @return String
283+
*/
284+
public function getAlbumId(diffId:String, variation:String):String
285+
{
286+
var diff:Null<SongDifficulty> = getDifficulty(diffId, variation);
287+
if (diff == null) return '';
288+
289+
return diff.album ?? '';
290+
}
291+
268292
/**
269293
* Populate the difficulty data from the provided metadata.
270294
* 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
285309
continue;
286310
}
287311

312+
// This resides within difficulties
313+
var difficultyMap:Map<String, SongDifficulty> = new Map<String, SongDifficulty>();
314+
288315
// There may be more difficulties in the chart file than in the metadata,
289316
// (i.e. non-playable charts like the one used for Pico on the speaker in Stress)
290317
// but all the difficulties in the metadata must be in the chart file.
@@ -309,10 +336,9 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
309336
difficulty.noteStyle = metadata.playData.noteStyle;
310337

311338
difficulty.characters = metadata.playData.characters;
312-
313-
var variationSuffix = (metadata.variation != Constants.DEFAULT_VARIATION) ? '-${metadata.variation}' : '';
314-
difficulties.set('$diffId$variationSuffix', difficulty);
339+
difficultyMap.set(diffId, difficulty);
315340
}
341+
difficulties.set(metadata.variation, difficultyMap);
316342
}
317343
}
318344

@@ -345,15 +371,18 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
345371

346372
for (diffId in chartNotes.keys())
347373
{
348-
// Retrieve the cached difficulty data.
349-
var variationSuffix = (variation != Constants.DEFAULT_VARIATION) ? '-$variation' : '';
350-
var difficulty:Null<SongDifficulty> = difficulties.get('$diffId$variationSuffix');
351-
if (difficulty == null)
374+
// Retrieve the cached difficulty data. This one could potentially be null.
375+
var nullDiff:Null<SongDifficulty> = getDifficulty(diffId, variation);
376+
377+
// if the difficulty doesn't exist, create a new one, and then proceed to fill it with data.
378+
// I mostly do this since I don't wanna throw around ? everywhere for null check lol?
379+
var difficulty:SongDifficulty = nullDiff ?? new SongDifficulty(this, diffId, variation);
380+
381+
if (nullDiff == null)
352382
{
353383
trace('Fabricated new difficulty for $diffId.');
354-
difficulty = new SongDifficulty(this, diffId, variation);
355384
var metadata = _metadata.get(variation);
356-
difficulties.set('$diffId$variationSuffix', difficulty);
385+
difficulties.get(variation)?.set(diffId, difficulty);
357386

358387
if (metadata != null)
359388
{
@@ -396,11 +425,9 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
396425

397426
for (currentVariation in variations)
398427
{
399-
var variationSuffix = (currentVariation != Constants.DEFAULT_VARIATION) ? '-$currentVariation' : '';
400-
401-
if (difficulties.exists('$diffId$variationSuffix'))
428+
if (difficulties.get(currentVariation)?.exists(diffId) ?? false)
402429
{
403-
return difficulties.get('$diffId$variationSuffix');
430+
return difficulties.get(currentVariation)?.get(diffId);
404431
}
405432
}
406433

@@ -417,8 +444,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
417444

418445
for (variationId in possibleVariations)
419446
{
420-
var variationSuffix = (variationId != Constants.DEFAULT_VARIATION) ? '-$variationId' : '';
421-
if (difficulties.exists('$diffId$variationSuffix')) return variationId;
447+
if (difficulties.exists('$variationId')) return variationId;
422448
}
423449

424450
return null;
@@ -440,7 +466,6 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
440466
}
441467

442468
var result = [];
443-
trace('Evaluating variations for ${this.id} ${char.id}: ${this.variations}');
444469
for (variation in variations)
445470
{
446471
var metadata = _metadata.get(variation);
@@ -459,6 +484,19 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
459484
return result;
460485
}
461486

487+
/**
488+
* Nearly the same thing as getVariationsByCharacter, but takes a character ID instead.
489+
* @param charId
490+
* @return Array<String>
491+
* @see getVariationsByCharacter
492+
*/
493+
public function getVariationsByCharacterId(?charId:String):Array<String>
494+
{
495+
var charPlayer = PlayerRegistry.instance.fetchEntry(charId ?? '');
496+
497+
return getVariationsByCharacter(charPlayer);
498+
}
499+
462500
/**
463501
* List all the difficulties in this song.
464502
*
@@ -501,6 +539,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
501539
/**
502540
* TODO: This line of code makes me sad, but you can't really fix it without a breaking migration.
503541
* @return `easy`, `erect`, `normal-pico`, etc.
542+
* @deprecated This function is deprecated, Funkin no longer uses suffixed difficulties.
504543
*/
505544
public function listSuffixedDifficulties(variationIds:Array<String>, ?showLocked:Bool, ?showHidden:Bool):Array<String>
506545
{
@@ -529,8 +568,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
529568

530569
for (targetVariation in variationIds)
531570
{
532-
var variationSuffix = (targetVariation != Constants.DEFAULT_VARIATION) ? '-$targetVariation' : '';
533-
if (difficulties.exists('$diffId$variationSuffix')) return true;
571+
if (difficulties.get(targetVariation)?.exists(diffId) ?? false) return true;
534572
}
535573
return false;
536574
}
@@ -565,13 +603,16 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
565603
}
566604

567605
/**
568-
* Purge the cached chart data for each difficulty of this song.
606+
* Purge the cached chart data for each difficulty/variation of this song.
569607
*/
570608
public function clearCharts():Void
571609
{
572-
for (diff in difficulties)
610+
for (variationMap in difficulties)
573611
{
574-
diff.clearChart();
612+
for (diff in variationMap)
613+
{
614+
diff.clearChart();
615+
}
575616
}
576617
}
577618

@@ -647,7 +688,8 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
647688
* Auto-accept if it's one of the base game default variations.
648689
* Reject if the ID starts with a number, or contains invalid characters.
649690
*/
650-
static function validateVariationId(variation:String):Bool {
691+
static function validateVariationId(variation:String):Bool
692+
{
651693
if (Constants.DEFAULT_VARIATION_LIST.contains(variation)) return true;
652694

653695
return VARIATION_REGEX.match(variation);

0 commit comments

Comments
 (0)