/**
* This class obtains the list of savegames from the engine,
* builds the list dependent on selected filters and sorting order.
*
* If the selected savegame changes, class instances that subscribed via
* registerSelectionChangeHandler will have their onSelectionChange function
* called with the relevant savegame data.
*/
class SavegameList
{
constructor(campaignRun)
{
this.savedGamesMetadata = [];
this.selectionChangeHandlers = [];
// If not null, only show games for the following campaign run
// (campaign save-games are not shown by default).
// Campaign games are saved in the same folder as regular ones,
// as there is no strong reason to do otherwise (since games from different runs
// need to be hidden from one another anyways, we need code to handle it).
this.campaignRun = campaignRun;
this.gameSelection = Engine.GetGUIObjectByName("gameSelection");
this.gameSelectionFeedback = Engine.GetGUIObjectByName("gameSelectionFeedback");
this.confirmButton = Engine.GetGUIObjectByName("confirmButton");
this.compatibilityFilter = Engine.GetGUIObjectByName("compatibilityFilter");
this.compatibilityFilter.onPress = () => { this.updateSavegameList(); };
this.initSavegameList();
}
initSavegameList()
{
let engineInfo = Engine.GetEngineInfo();
this.gameSelection.onSelectionColumnChange = () => { this.updateSavegameList(); };
this.gameSelection.onMouseLeftDoubleClickItem = () => { this.confirmButton.onPress(); };
this.gameSelection.onSelectionChange = () => {
let gameId = this.gameSelection.list_data[this.gameSelection.selected];
let metadata = this.savedGamesMetadata[this.gameSelection.selected];
let label = this.generateSavegameLabel(metadata, engineInfo);
for (let handler of this.selectionChangeHandlers)
handler.onSelectionChange(gameId, metadata, label);
};
this.updateSavegameList();
}
registerSelectionChangeHandler(selectionChangeHandler)
{
this.selectionChangeHandlers.push(selectionChangeHandler);
}
onSavegameListChange()
{
this.updateSavegameList();
// Allow subscribers (delete button) to update their press function in case
// the list items changed but the selected index remained the same.
this.gameSelection.onSelectionChange();
}
selectFirst()
{
if (this.gameSelection.list.length)
this.gameSelection.selected = 0;
}
updateSavegameList()
{
let savedGames = Engine.GetSavedGames();
// Get current game version and loaded mods
let engineInfo = Engine.GetEngineInfo();
if (this.compatibilityFilter.checked)
savedGames = savedGames.filter(game => {
return this.isCompatibleSavegame(game.metadata, engineInfo) &&
this.campaignFilter(game.metadata, this.campaignRun);
});
else if (this.campaignRun)
savedGames = savedGames.filter(game => this.campaignFilter(game.metadata, this.campaignRun));
this.gameSelection.enabled = !!savedGames.length;
this.gameSelectionFeedback.hidden = !!savedGames.length;
let selectedGameId = this.gameSelection.list_data[this.gameSelection.selected];
// Save metadata for the detailed view
this.savedGamesMetadata = savedGames.map(game => {
game.metadata.id = game.id;
return game.metadata;
});
let sortKey = this.gameSelection.selected_column;
let sortOrder = this.gameSelection.selected_column_order;
this.savedGamesMetadata = this.savedGamesMetadata.sort((a, b) => {
let cmpA, cmpB;
switch (sortKey)
{
case 'date':
cmpA = +a.time;
cmpB = +b.time;
break;
case 'mapName':
cmpA = translate(a.initAttributes.settings.mapName);
cmpB = translate(b.initAttributes.settings.mapName);
break;
case 'mapType':
cmpA = translateMapType(a.initAttributes.mapType);
cmpB = translateMapType(b.initAttributes.mapType);
break;
case 'description':
cmpA = a.description;
cmpB = b.description;
break;
}
if (cmpA < cmpB)
return -sortOrder;
else if (cmpA > cmpB)
return +sortOrder;
return 0;
});
let list = this.savedGamesMetadata.map(metadata => {
let isCompatible = this.isCompatibleSavegame(metadata, engineInfo) &&
this.campaignFilter(metadata, this.campaignRun);
return {
"date": this.generateSavegameDateString(metadata, engineInfo),
"mapName": compatibilityColor(translate(metadata.initAttributes.settings.mapName), isCompatible),
"mapType": compatibilityColor(translateMapType(metadata.initAttributes.mapType), isCompatible),
"description": compatibilityColor(metadata.description, isCompatible)
};
});
if (list.length)
list = prepareForDropdown(list);
this.gameSelection.list_date = list.date || [];
this.gameSelection.list_mapName = list.mapName || [];
this.gameSelection.list_mapType = list.mapType || [];
this.gameSelection.list_description = list.description || [];
// Change these last, otherwise crash
this.gameSelection.list = this.savedGamesMetadata.map(metadata => 0);
this.gameSelection.list_data = this.savedGamesMetadata.map(metadata => metadata.id);
// Restore selection if the selected savegame still exists.
// If the last savegame was deleted, or if it was hidden by the compatibility filter, select the new last item.
let selectedGameIndex = this.savedGamesMetadata.findIndex(metadata => metadata.id == selectedGameId);
if (selectedGameIndex != -1)
this.gameSelection.selected = selectedGameIndex;
else if (this.gameSelection.selected >= this.savedGamesMetadata.length)
this.gameSelection.selected = this.savedGamesMetadata.length - 1;
}
campaignFilter(metadata, campaignRun)
{
if (!campaignRun)
return !metadata.initAttributes.campaignData;
if (metadata.initAttributes.campaignData)
return metadata.initAttributes.campaignData.run == campaignRun;
return false;
}
isCompatibleSavegame(metadata, engineInfo)
{
return engineInfo &&
metadata.engine_version &&
metadata.engine_version == engineInfo.engine_version &&
hasSameMods(metadata.mods, engineInfo.mods);
}
generateSavegameDateString(metadata, engineInfo)
{
return compatibilityColor(
Engine.FormatMillisecondsIntoDateStringLocal(metadata.time * 1000, translate("yyyy-MM-dd HH:mm:ss")),
this.isCompatibleSavegame(metadata, engineInfo));
}
generateSavegameLabel(metadata, engineInfo)
{
if (!metadata)
return undefined;
return sprintf(
metadata.description ?
translate("%(dateString)s %(map)s - %(description)s") :
translate("%(dateString)s %(map)s"),
{
"dateString": this.generateSavegameDateString(metadata, engineInfo),
"map": metadata.initAttributes.map,
"description": metadata.description || ""
});
}
}