@@ -15,6 +15,7 @@ import funkin.data.song.SongRegistry;
1515import funkin .modding .IScriptedClass .IPlayStateScriptedClass ;
1616import funkin .modding .events .ScriptEvent ;
1717import funkin .ui .freeplay .charselect .PlayableCharacter ;
18+ import funkin .data .freeplay .player .PlayerRegistry ;
1819import 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