2022-09-13 01:09:30 -04:00
|
|
|
package funkin.play.song;
|
|
|
|
|
2023-06-08 17:07:35 -04:00
|
|
|
import funkin.play.song.formats.FNFLegacy;
|
2022-09-13 01:09:30 -04:00
|
|
|
import funkin.play.song.SongData.SongChartData;
|
2023-06-08 17:07:35 -04:00
|
|
|
import funkin.play.song.SongData.SongEventData;
|
2022-09-13 01:09:30 -04:00
|
|
|
import funkin.play.song.SongData.SongMetadata;
|
2023-06-08 17:07:35 -04:00
|
|
|
import funkin.play.song.SongData.SongNoteData;
|
|
|
|
import funkin.play.song.SongData.SongPlayableChar;
|
2022-09-13 01:09:30 -04:00
|
|
|
import funkin.util.VersionUtil;
|
|
|
|
|
|
|
|
class SongMigrator
|
|
|
|
{
|
2023-01-22 19:55:30 -05:00
|
|
|
/**
|
|
|
|
* The current latest version string for the song data format.
|
|
|
|
* Handle breaking changes by incrementing this value
|
|
|
|
* and adding migration to the SongMigrator class.
|
|
|
|
*/
|
2023-06-08 17:07:35 -04:00
|
|
|
public static final CHART_VERSION:String = '2.0.0';
|
2022-09-13 01:09:30 -04:00
|
|
|
|
2023-06-08 17:07:35 -04:00
|
|
|
/**
|
|
|
|
* Version rule for which chart versions are compatible with the current version.
|
|
|
|
*/
|
|
|
|
public static final CHART_VERSION_RULE:String = '2.0.x';
|
2022-09-13 01:09:30 -04:00
|
|
|
|
2023-06-08 17:07:35 -04:00
|
|
|
/**
|
|
|
|
* Migrate song data from an older chart version to the current version.
|
|
|
|
* @param jsonData The song metadata to migrate.
|
|
|
|
* @param songId The ID of the song (only used for error reporting).
|
|
|
|
* @return The migrated song metadata, or null if the migration failed.
|
|
|
|
*/
|
2023-01-22 19:55:30 -05:00
|
|
|
public static function migrateSongMetadata(jsonData:Dynamic, songId:String):SongMetadata
|
|
|
|
{
|
2023-06-08 17:07:35 -04:00
|
|
|
if (jsonData.version != null)
|
2023-01-22 19:55:30 -05:00
|
|
|
{
|
2023-08-22 04:27:30 -04:00
|
|
|
if (VersionUtil.validateVersionStr(jsonData.version, CHART_VERSION_RULE))
|
2023-01-22 19:55:30 -05:00
|
|
|
{
|
|
|
|
trace('Song (${songId}) metadata version (${jsonData.version}) is valid and up-to-date.');
|
2022-09-13 01:09:30 -04:00
|
|
|
|
2023-01-22 19:55:30 -05:00
|
|
|
var songMetadata:SongMetadata = cast jsonData;
|
2022-09-13 01:09:30 -04:00
|
|
|
|
2023-01-22 19:55:30 -05:00
|
|
|
return songMetadata;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
trace('Song (${songId}) metadata version (${jsonData.version}) is outdated.');
|
|
|
|
switch (jsonData.version)
|
|
|
|
{
|
2023-06-08 17:07:35 -04:00
|
|
|
case '1.0.0':
|
|
|
|
return migrateSongMetadataFromLegacy(jsonData);
|
2023-01-22 19:55:30 -05:00
|
|
|
default:
|
2023-06-08 17:07:35 -04:00
|
|
|
trace('Song (${songId}) has unknown metadata version (${jsonData.version}), assuming FNF Legacy.');
|
|
|
|
return migrateSongMetadataFromLegacy(jsonData);
|
2023-01-22 19:55:30 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
trace('Song metadata version is missing.');
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
2022-09-13 01:09:30 -04:00
|
|
|
|
2023-06-08 17:07:35 -04:00
|
|
|
/**
|
|
|
|
* Migrate song chart data from an older chart version to the current version.
|
|
|
|
* @param jsonData The song chart data to migrate.
|
|
|
|
* @param songId The ID of the song (only used for error reporting).
|
|
|
|
* @return The migrated song chart data, or null if the migration failed.
|
|
|
|
*/
|
2023-01-22 19:55:30 -05:00
|
|
|
public static function migrateSongChartData(jsonData:Dynamic, songId:String):SongChartData
|
|
|
|
{
|
|
|
|
if (jsonData.version)
|
|
|
|
{
|
2023-08-22 04:27:30 -04:00
|
|
|
if (VersionUtil.validateVersionStr(jsonData.version, CHART_VERSION_RULE))
|
2023-01-22 19:55:30 -05:00
|
|
|
{
|
|
|
|
trace('Song (${songId}) chart version (${jsonData.version}) is valid and up-to-date.');
|
2022-09-13 01:09:30 -04:00
|
|
|
|
2023-01-22 19:55:30 -05:00
|
|
|
var songChartData:SongChartData = cast jsonData;
|
2022-09-13 01:09:30 -04:00
|
|
|
|
2023-01-22 19:55:30 -05:00
|
|
|
return songChartData;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
trace('Song (${songId}) chart version (${jsonData.version}) is outdated.');
|
|
|
|
switch (jsonData.version)
|
|
|
|
{
|
|
|
|
// TODO: Add migration functions as cases here.
|
|
|
|
default:
|
|
|
|
// Unknown version.
|
|
|
|
trace('Song (${songId}) unknown chart version: ${jsonData.version}');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
trace('Song chart version is missing.');
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
2023-06-08 17:07:35 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Migrate song metadata from FNF Legacy chart version to the current version.
|
|
|
|
* @param jsonData The song metadata to migrate.
|
|
|
|
* @param songId The ID of the song (only used for error reporting).
|
|
|
|
* @return The migrated song metadata, or null if the migration failed.
|
|
|
|
*/
|
2023-06-09 15:28:14 -04:00
|
|
|
public static function migrateSongMetadataFromLegacy(jsonData:Dynamic, difficulty:String = 'normal'):SongMetadata
|
2023-06-08 17:07:35 -04:00
|
|
|
{
|
|
|
|
trace('Migrating song metadata from FNF Legacy.');
|
|
|
|
|
|
|
|
var songData:FNFLegacy = cast jsonData;
|
|
|
|
|
|
|
|
var songMetadata:SongMetadata = new SongMetadata('Import', 'Kawai Sprite', 'default');
|
|
|
|
|
|
|
|
var hadError:Bool = false;
|
|
|
|
|
|
|
|
// Set generatedBy string for debugging.
|
|
|
|
songMetadata.generatedBy = 'Chart Editor Import (FNF Legacy)';
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
// Set the song's BPM.
|
|
|
|
songMetadata.timeChanges[0].bpm = songData.song.bpm;
|
|
|
|
}
|
|
|
|
catch (e)
|
|
|
|
{
|
|
|
|
trace("Couldn't parse BPM!");
|
|
|
|
hadError = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
// Set the song's stage.
|
|
|
|
songMetadata.playData.stage = songData.song.stageDefault;
|
|
|
|
}
|
|
|
|
catch (e)
|
|
|
|
{
|
|
|
|
trace("Couldn't parse stage!");
|
|
|
|
hadError = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
// Set's the song's name.
|
|
|
|
songMetadata.songName = songData.song.song;
|
|
|
|
}
|
|
|
|
catch (e)
|
|
|
|
{
|
|
|
|
trace("Couldn't parse song name!");
|
|
|
|
hadError = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
songMetadata.playData.difficulties = [];
|
|
|
|
if (songData.song != null && songData.song.notes != null)
|
|
|
|
{
|
2023-06-09 15:28:14 -04:00
|
|
|
if (Std.isOfType(songData.song.notes, Array))
|
|
|
|
{
|
|
|
|
// One difficulty of notes.
|
|
|
|
songMetadata.playData.difficulties.push(difficulty);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Multiple difficulties of notes.
|
|
|
|
var songNoteDataDynamic:haxe.DynamicAccess<Dynamic> = cast songData.song.notes;
|
|
|
|
for (difficultyKey in songNoteDataDynamic.keys())
|
|
|
|
{
|
|
|
|
songMetadata.playData.difficulties.push(difficultyKey);
|
|
|
|
}
|
|
|
|
}
|
2023-06-08 17:07:35 -04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
trace("Couldn't parse difficulties!");
|
|
|
|
hadError = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
songMetadata.playData.songVariations = [];
|
|
|
|
|
|
|
|
// Set the song's song variations.
|
|
|
|
songMetadata.playData.playableChars = {};
|
|
|
|
try
|
|
|
|
{
|
2023-09-12 18:37:59 -04:00
|
|
|
songMetadata.playData.playableChars.set(songData.song.player1, new SongPlayableChar('', songData.song.player2));
|
2023-06-08 17:07:35 -04:00
|
|
|
}
|
|
|
|
catch (e)
|
|
|
|
{
|
|
|
|
trace("Couldn't parse characters!");
|
|
|
|
hadError = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return songMetadata;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Migrate song chart data from FNF Legacy chart version to the current version.
|
|
|
|
* @param jsonData The song data to migrate.
|
|
|
|
* @param songId The ID of the song (only used for error reporting).
|
|
|
|
* @param difficulty The difficulty to migrate.
|
|
|
|
* @return The migrated song chart data, or null if the migration failed.
|
|
|
|
*/
|
2023-06-09 15:28:14 -04:00
|
|
|
public static function migrateSongChartDataFromLegacy(jsonData:Dynamic, difficulty:String = 'normal'):SongChartData
|
2023-06-08 17:07:35 -04:00
|
|
|
{
|
|
|
|
trace('Migrating song chart data from FNF Legacy.');
|
|
|
|
|
|
|
|
var songData:FNFLegacy = cast jsonData;
|
|
|
|
|
|
|
|
var songChartData:SongChartData = new SongChartData(1.0, [], []);
|
|
|
|
|
2023-06-09 15:28:14 -04:00
|
|
|
var songEventsEmpty:Bool = songChartData.getEvents() == null || songChartData.getEvents().length == 0;
|
|
|
|
if (songEventsEmpty) songChartData.setEvents(migrateSongEventDataFromLegacy(songData.song.notes));
|
|
|
|
songChartData.setNotes(migrateSongNoteDataFromLegacy(songData.song.notes), difficulty);
|
|
|
|
songChartData.setScrollSpeed(songData.song.speed, difficulty);
|
2023-06-08 17:07:35 -04:00
|
|
|
|
|
|
|
return songChartData;
|
|
|
|
}
|
|
|
|
|
|
|
|
static function migrateSongNoteDataFromLegacy(sections:Array<LegacyNoteSection>):Array<SongNoteData>
|
|
|
|
{
|
|
|
|
var songNotes:Array<SongNoteData> = [];
|
|
|
|
|
|
|
|
for (section in sections)
|
|
|
|
{
|
|
|
|
// Skip empty sections.
|
|
|
|
if (section.sectionNotes.length == 0) continue;
|
|
|
|
|
|
|
|
for (note in section.sectionNotes)
|
|
|
|
{
|
|
|
|
songNotes.push(new SongNoteData(note.time, note.getData(section.mustHitSection), note.length, note.kind));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return songNotes;
|
|
|
|
}
|
|
|
|
|
|
|
|
static function migrateSongEventDataFromLegacy(sections:Array<LegacyNoteSection>):Array<SongEventData>
|
|
|
|
{
|
|
|
|
var songEvents:Array<SongEventData> = [];
|
|
|
|
|
|
|
|
var lastSectionWasMustHit:Null<Bool> = null;
|
|
|
|
for (section in sections)
|
|
|
|
{
|
|
|
|
// Skip empty sections.
|
|
|
|
if (section.sectionNotes.length == 0) continue;
|
|
|
|
|
|
|
|
if (section.mustHitSection != lastSectionWasMustHit)
|
|
|
|
{
|
|
|
|
lastSectionWasMustHit = section.mustHitSection;
|
|
|
|
|
|
|
|
var firstNote:LegacyNote = section.sectionNotes[0];
|
|
|
|
|
|
|
|
songEvents.push(new SongEventData(firstNote.time, 'FocusCamera', {char: section.mustHitSection ? 0 : 1}));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return songEvents;
|
|
|
|
}
|
2022-09-13 01:09:30 -04:00
|
|
|
}
|