2023-10-17 04:38:28 +00:00
|
|
|
package funkin.save.migrator;
|
|
|
|
|
|
|
|
import funkin.save.Save;
|
|
|
|
import funkin.save.migrator.RawSaveData_v1_0_0;
|
|
|
|
import thx.semver.Version;
|
2024-04-03 05:01:58 +00:00
|
|
|
import funkin.util.StructureUtil;
|
2023-10-17 04:38:28 +00:00
|
|
|
import funkin.util.VersionUtil;
|
|
|
|
|
|
|
|
@:nullSafety
|
|
|
|
class SaveDataMigrator
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* Migrate from one 2.x version to another.
|
|
|
|
*/
|
|
|
|
public static function migrate(inputData:Dynamic):Save
|
|
|
|
{
|
2023-10-21 05:04:50 +00:00
|
|
|
var version:Null<thx.semver.Version> = VersionUtil.parseVersion(inputData?.version ?? null);
|
2023-10-17 04:38:28 +00:00
|
|
|
|
|
|
|
if (version == null)
|
|
|
|
{
|
|
|
|
trace('[SAVE] No version found in save data! Returning blank data.');
|
|
|
|
trace(inputData);
|
2024-03-04 21:37:42 +00:00
|
|
|
return new Save(Save.getDefault());
|
2023-10-17 04:38:28 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2024-05-21 06:23:21 +00:00
|
|
|
// Sometimes the Haxe serializer has issues with the version so we fix it here.
|
|
|
|
version = VersionUtil.repairVersion(version);
|
2023-10-21 05:04:50 +00:00
|
|
|
if (VersionUtil.validateVersion(version, Save.SAVE_DATA_VERSION_RULE))
|
2023-10-17 04:38:28 +00:00
|
|
|
{
|
2024-05-21 08:02:32 +00:00
|
|
|
// Import the structured data.
|
|
|
|
var saveDataWithDefaults:RawSaveData = thx.Objects.deepCombine(Save.getDefault(), inputData);
|
|
|
|
var save:Save = new Save(saveDataWithDefaults);
|
2023-10-17 04:38:28 +00:00
|
|
|
return save;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2024-05-21 06:23:21 +00:00
|
|
|
var message:String = 'Error migrating save data, expected ${Save.SAVE_DATA_VERSION}.';
|
|
|
|
lime.app.Application.current.window.alert(message, "Save Data Failure");
|
|
|
|
trace('[SAVE] ' + message);
|
2024-03-04 21:37:42 +00:00
|
|
|
return new Save(Save.getDefault());
|
2023-10-17 04:38:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Migrate from 1.x to the latest version.
|
|
|
|
*/
|
|
|
|
public static function migrateFromLegacy(inputData:Dynamic):Save
|
|
|
|
{
|
|
|
|
var inputSaveData:RawSaveData_v1_0_0 = cast inputData;
|
|
|
|
|
2024-03-04 21:37:42 +00:00
|
|
|
var result:Save = new Save(Save.getDefault());
|
2023-10-17 04:38:28 +00:00
|
|
|
|
|
|
|
result.volume = inputSaveData.volume;
|
|
|
|
result.mute = inputSaveData.mute;
|
|
|
|
|
|
|
|
result.ngSessionId = inputSaveData.sessionId;
|
|
|
|
|
|
|
|
// TODO: Port over the save data from the legacy save data format.
|
|
|
|
migrateLegacyScores(result, inputSaveData);
|
|
|
|
|
|
|
|
migrateLegacyControls(result, inputSaveData);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static function migrateLegacyScores(result:Save, inputSaveData:RawSaveData_v1_0_0):Void
|
|
|
|
{
|
|
|
|
if (inputSaveData.songCompletion == null)
|
|
|
|
{
|
|
|
|
inputSaveData.songCompletion = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (inputSaveData.songScores == null)
|
|
|
|
{
|
|
|
|
inputSaveData.songScores = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
migrateLegacyLevelScore(result, inputSaveData, 'week0');
|
|
|
|
migrateLegacyLevelScore(result, inputSaveData, 'week1');
|
|
|
|
migrateLegacyLevelScore(result, inputSaveData, 'week2');
|
|
|
|
migrateLegacyLevelScore(result, inputSaveData, 'week3');
|
|
|
|
migrateLegacyLevelScore(result, inputSaveData, 'week4');
|
|
|
|
migrateLegacyLevelScore(result, inputSaveData, 'week5');
|
|
|
|
migrateLegacyLevelScore(result, inputSaveData, 'week6');
|
|
|
|
migrateLegacyLevelScore(result, inputSaveData, 'week7');
|
|
|
|
|
|
|
|
migrateLegacySongScore(result, inputSaveData, ['tutorial', 'Tutorial']);
|
|
|
|
|
|
|
|
migrateLegacySongScore(result, inputSaveData, ['bopeebo', 'Bopeebo']);
|
|
|
|
migrateLegacySongScore(result, inputSaveData, ['fresh', 'Fresh']);
|
|
|
|
migrateLegacySongScore(result, inputSaveData, ['dadbattle', 'Dadbattle']);
|
|
|
|
|
|
|
|
migrateLegacySongScore(result, inputSaveData, ['monster', 'Monster']);
|
|
|
|
migrateLegacySongScore(result, inputSaveData, ['south', 'South']);
|
|
|
|
migrateLegacySongScore(result, inputSaveData, ['spookeez', 'Spookeez']);
|
|
|
|
|
|
|
|
migrateLegacySongScore(result, inputSaveData, ['pico', 'Pico']);
|
|
|
|
migrateLegacySongScore(result, inputSaveData, ['philly-nice', 'Philly', 'philly', 'Philly-Nice']);
|
|
|
|
migrateLegacySongScore(result, inputSaveData, ['blammed', 'Blammed']);
|
|
|
|
|
|
|
|
migrateLegacySongScore(result, inputSaveData, ['satin-panties', 'Satin-Panties']);
|
|
|
|
migrateLegacySongScore(result, inputSaveData, ['high', 'High']);
|
|
|
|
migrateLegacySongScore(result, inputSaveData, ['milf', 'Milf', 'MILF']);
|
|
|
|
|
|
|
|
migrateLegacySongScore(result, inputSaveData, ['cocoa', 'Cocoa']);
|
|
|
|
migrateLegacySongScore(result, inputSaveData, ['eggnog', 'Eggnog']);
|
|
|
|
migrateLegacySongScore(result, inputSaveData, ['winter-horrorland', 'Winter-Horrorland']);
|
|
|
|
|
|
|
|
migrateLegacySongScore(result, inputSaveData, ['senpai', 'Senpai']);
|
|
|
|
migrateLegacySongScore(result, inputSaveData, ['roses', 'Roses']);
|
|
|
|
migrateLegacySongScore(result, inputSaveData, ['thorns', 'Thorns']);
|
|
|
|
|
|
|
|
migrateLegacySongScore(result, inputSaveData, ['ugh', 'Ugh']);
|
|
|
|
migrateLegacySongScore(result, inputSaveData, ['guns', 'Guns']);
|
|
|
|
migrateLegacySongScore(result, inputSaveData, ['stress', 'Stress']);
|
|
|
|
}
|
|
|
|
|
|
|
|
static function migrateLegacyLevelScore(result:Save, inputSaveData:RawSaveData_v1_0_0, levelId:String):Void
|
|
|
|
{
|
|
|
|
var scoreDataEasy:SaveScoreData =
|
|
|
|
{
|
|
|
|
score: inputSaveData.songScores.get('${levelId}-easy') ?? 0,
|
|
|
|
accuracy: inputSaveData.songCompletion.get('${levelId}-easy') ?? 0.0,
|
|
|
|
tallies:
|
|
|
|
{
|
|
|
|
sick: 0,
|
|
|
|
good: 0,
|
|
|
|
bad: 0,
|
|
|
|
shit: 0,
|
|
|
|
missed: 0,
|
|
|
|
combo: 0,
|
|
|
|
maxCombo: 0,
|
|
|
|
totalNotesHit: 0,
|
|
|
|
totalNotes: 0,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
result.setLevelScore(levelId, 'easy', scoreDataEasy);
|
|
|
|
|
|
|
|
var scoreDataNormal:SaveScoreData =
|
|
|
|
{
|
|
|
|
score: inputSaveData.songScores.get('${levelId}') ?? 0,
|
|
|
|
accuracy: inputSaveData.songCompletion.get('${levelId}') ?? 0.0,
|
|
|
|
tallies:
|
|
|
|
{
|
|
|
|
sick: 0,
|
|
|
|
good: 0,
|
|
|
|
bad: 0,
|
|
|
|
shit: 0,
|
|
|
|
missed: 0,
|
|
|
|
combo: 0,
|
|
|
|
maxCombo: 0,
|
|
|
|
totalNotesHit: 0,
|
|
|
|
totalNotes: 0,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
result.setLevelScore(levelId, 'normal', scoreDataNormal);
|
|
|
|
|
|
|
|
var scoreDataHard:SaveScoreData =
|
|
|
|
{
|
|
|
|
score: inputSaveData.songScores.get('${levelId}-hard') ?? 0,
|
|
|
|
accuracy: inputSaveData.songCompletion.get('${levelId}-hard') ?? 0.0,
|
|
|
|
tallies:
|
|
|
|
{
|
|
|
|
sick: 0,
|
|
|
|
good: 0,
|
|
|
|
bad: 0,
|
|
|
|
shit: 0,
|
|
|
|
missed: 0,
|
|
|
|
combo: 0,
|
|
|
|
maxCombo: 0,
|
|
|
|
totalNotesHit: 0,
|
|
|
|
totalNotes: 0,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
result.setLevelScore(levelId, 'hard', scoreDataHard);
|
|
|
|
}
|
|
|
|
|
|
|
|
static function migrateLegacySongScore(result:Save, inputSaveData:RawSaveData_v1_0_0, songIds:Array<String>):Void
|
|
|
|
{
|
|
|
|
var scoreDataEasy:SaveScoreData =
|
|
|
|
{
|
|
|
|
score: 0,
|
|
|
|
accuracy: 0,
|
|
|
|
tallies:
|
|
|
|
{
|
|
|
|
sick: 0,
|
|
|
|
good: 0,
|
|
|
|
bad: 0,
|
|
|
|
shit: 0,
|
|
|
|
missed: 0,
|
|
|
|
combo: 0,
|
|
|
|
maxCombo: 0,
|
|
|
|
totalNotesHit: 0,
|
|
|
|
totalNotes: 0,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
for (songId in songIds)
|
|
|
|
{
|
|
|
|
scoreDataEasy.score = Std.int(Math.max(scoreDataEasy.score, inputSaveData.songScores.get('${songId}-easy') ?? 0));
|
|
|
|
scoreDataEasy.accuracy = Math.max(scoreDataEasy.accuracy, inputSaveData.songCompletion.get('${songId}-easy') ?? 0.0);
|
|
|
|
}
|
|
|
|
result.setSongScore(songIds[0], 'easy', scoreDataEasy);
|
|
|
|
|
|
|
|
var scoreDataNormal:SaveScoreData =
|
|
|
|
{
|
|
|
|
score: 0,
|
|
|
|
accuracy: 0,
|
|
|
|
tallies:
|
|
|
|
{
|
|
|
|
sick: 0,
|
|
|
|
good: 0,
|
|
|
|
bad: 0,
|
|
|
|
shit: 0,
|
|
|
|
missed: 0,
|
|
|
|
combo: 0,
|
|
|
|
maxCombo: 0,
|
|
|
|
totalNotesHit: 0,
|
|
|
|
totalNotes: 0,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
for (songId in songIds)
|
|
|
|
{
|
|
|
|
scoreDataNormal.score = Std.int(Math.max(scoreDataNormal.score, inputSaveData.songScores.get('${songId}') ?? 0));
|
|
|
|
scoreDataNormal.accuracy = Math.max(scoreDataNormal.accuracy, inputSaveData.songCompletion.get('${songId}') ?? 0.0);
|
|
|
|
}
|
|
|
|
result.setSongScore(songIds[0], 'normal', scoreDataNormal);
|
|
|
|
|
|
|
|
var scoreDataHard:SaveScoreData =
|
|
|
|
{
|
|
|
|
score: 0,
|
|
|
|
accuracy: 0,
|
|
|
|
tallies:
|
|
|
|
{
|
|
|
|
sick: 0,
|
|
|
|
good: 0,
|
|
|
|
bad: 0,
|
|
|
|
shit: 0,
|
|
|
|
missed: 0,
|
|
|
|
combo: 0,
|
|
|
|
maxCombo: 0,
|
|
|
|
totalNotesHit: 0,
|
|
|
|
totalNotes: 0,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
for (songId in songIds)
|
|
|
|
{
|
|
|
|
scoreDataHard.score = Std.int(Math.max(scoreDataHard.score, inputSaveData.songScores.get('${songId}-hard') ?? 0));
|
|
|
|
scoreDataHard.accuracy = Math.max(scoreDataHard.accuracy, inputSaveData.songCompletion.get('${songId}-hard') ?? 0.0);
|
|
|
|
}
|
|
|
|
result.setSongScore(songIds[0], 'hard', scoreDataHard);
|
|
|
|
}
|
|
|
|
|
|
|
|
static function migrateLegacyControls(result:Save, inputSaveData:RawSaveData_v1_0_0):Void
|
|
|
|
{
|
|
|
|
var p1Data = inputSaveData?.controls?.p1;
|
|
|
|
if (p1Data != null)
|
|
|
|
{
|
|
|
|
migrateLegacyPlayerControls(result, 1, p1Data);
|
|
|
|
}
|
|
|
|
|
|
|
|
var p2Data = inputSaveData?.controls?.p2;
|
|
|
|
if (p2Data != null)
|
|
|
|
{
|
|
|
|
migrateLegacyPlayerControls(result, 2, p2Data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static function migrateLegacyPlayerControls(result:Save, playerId:Int, controlsData:SavePlayerControlsData_v1_0_0):Void
|
|
|
|
{
|
|
|
|
var outputKeyControls:SaveControlsData =
|
|
|
|
{
|
|
|
|
ACCEPT: controlsData?.keys?.ACCEPT ?? null,
|
|
|
|
BACK: controlsData?.keys?.BACK ?? null,
|
|
|
|
CUTSCENE_ADVANCE: controlsData?.keys?.CUTSCENE_ADVANCE ?? null,
|
|
|
|
NOTE_DOWN: controlsData?.keys?.NOTE_DOWN ?? null,
|
|
|
|
NOTE_LEFT: controlsData?.keys?.NOTE_LEFT ?? null,
|
|
|
|
NOTE_RIGHT: controlsData?.keys?.NOTE_RIGHT ?? null,
|
|
|
|
NOTE_UP: controlsData?.keys?.NOTE_UP ?? null,
|
|
|
|
PAUSE: controlsData?.keys?.PAUSE ?? null,
|
|
|
|
RESET: controlsData?.keys?.RESET ?? null,
|
|
|
|
UI_DOWN: controlsData?.keys?.UI_DOWN ?? null,
|
|
|
|
UI_LEFT: controlsData?.keys?.UI_LEFT ?? null,
|
|
|
|
UI_RIGHT: controlsData?.keys?.UI_RIGHT ?? null,
|
|
|
|
UI_UP: controlsData?.keys?.UI_UP ?? null,
|
|
|
|
VOLUME_DOWN: controlsData?.keys?.VOLUME_DOWN ?? null,
|
|
|
|
VOLUME_MUTE: controlsData?.keys?.VOLUME_MUTE ?? null,
|
|
|
|
VOLUME_UP: controlsData?.keys?.VOLUME_UP ?? null,
|
|
|
|
};
|
|
|
|
|
|
|
|
var outputPadControls:SaveControlsData =
|
|
|
|
{
|
|
|
|
ACCEPT: controlsData?.pad?.ACCEPT ?? null,
|
|
|
|
BACK: controlsData?.pad?.BACK ?? null,
|
|
|
|
CUTSCENE_ADVANCE: controlsData?.pad?.CUTSCENE_ADVANCE ?? null,
|
|
|
|
NOTE_DOWN: controlsData?.pad?.NOTE_DOWN ?? null,
|
|
|
|
NOTE_LEFT: controlsData?.pad?.NOTE_LEFT ?? null,
|
|
|
|
NOTE_RIGHT: controlsData?.pad?.NOTE_RIGHT ?? null,
|
|
|
|
NOTE_UP: controlsData?.pad?.NOTE_UP ?? null,
|
|
|
|
PAUSE: controlsData?.pad?.PAUSE ?? null,
|
|
|
|
RESET: controlsData?.pad?.RESET ?? null,
|
|
|
|
UI_DOWN: controlsData?.pad?.UI_DOWN ?? null,
|
|
|
|
UI_LEFT: controlsData?.pad?.UI_LEFT ?? null,
|
|
|
|
UI_RIGHT: controlsData?.pad?.UI_RIGHT ?? null,
|
|
|
|
UI_UP: controlsData?.pad?.UI_UP ?? null,
|
|
|
|
VOLUME_DOWN: controlsData?.pad?.VOLUME_DOWN ?? null,
|
|
|
|
VOLUME_MUTE: controlsData?.pad?.VOLUME_MUTE ?? null,
|
|
|
|
VOLUME_UP: controlsData?.pad?.VOLUME_UP ?? null,
|
|
|
|
};
|
|
|
|
|
|
|
|
result.setControls(playerId, Keys, outputKeyControls);
|
|
|
|
result.setControls(playerId, Gamepad(0), outputPadControls);
|
|
|
|
}
|
|
|
|
}
|