MYST3: Rework save thumbnail handling, simplify autosaving

This commit is contained in:
Bastien Bouclet 2018-05-27 12:39:02 +02:00
parent 006aaa2c43
commit e338b1bb6d
7 changed files with 129 additions and 143 deletions

View File

@ -269,9 +269,10 @@ public:
data.syncWithSaveGame(s);
// Read and resize the thumbnail
Graphics::Surface *guiThumb = new Graphics::Surface();
guiThumb->create(kThumbnailWidth, kThumbnailHeight1, Texture::getRGBAPixelFormat());
data.resizeThumbnail(guiThumb);
Graphics::Surface *saveThumb = GameState::readThumbnail(saveFile);
Graphics::Surface *guiThumb = GameState::resizeThumbnail(saveThumb, kThumbnailWidth, kThumbnailHeight1);
saveThumb->free();
delete saveThumb;
// Set metadata
saveInfos.setThumbnail(guiThumb);

View File

@ -209,8 +209,7 @@ int16 GamepadDialog::update() {
Menu::Menu(Myst3Engine *vm) :
_vm(vm),
_saveLoadSpotItem(0),
_thumbnailValid(false) {
_saveLoadSpotItem(0) {
}
Menu::~Menu() {
@ -296,21 +295,13 @@ void Menu::updateMainMenu(uint16 action) {
}
}
void Menu::saveThumbnail() {
// ... and capture the screen
Graphics::Surface *Menu::captureThumbnail() {
Graphics::Surface *big = _vm->_gfx->getScreenshot();
Graphics::Surface *thumb = createThumbnail(big);
_vm->_state->setSaveThumbnail(thumb);
Graphics::Surface *thumbnail = createThumbnail(big);
big->free();
delete big;
}
bool Menu::getThumbnailValid() {
return _thumbnailValid;
}
void Menu::setThumbnailValid(bool valid) {
_thumbnailValid = valid;
return thumbnail;
}
void Menu::goToNode(uint16 node) {
@ -320,8 +311,9 @@ void Menu::goToNode(uint16 node) {
_vm->_state->setMenuSavedRoom(_vm->_state->getLocationRoom());
_vm->_state->setMenuSavedNode(_vm->_state->getLocationNode());
saveThumbnail();
_thumbnailValid = true;
// ... and capture the screen
Graphics::Surface *thumbnail = captureThumbnail();
_saveThumbnail.reset(thumbnail);
// Reset some sound variables
if (_vm->_state->getLocationAge() == 6 && _vm->_state->getSoundEdannaUnk587() == 1 && _vm->_state->getSoundEdannaUnk1031()) {
@ -448,6 +440,14 @@ void Menu::setSaveLoadSpotItem(uint16 id, SpotItemFace *spotItem) {
}
}
bool Menu::isOpen() const {
return _vm->_state->getLocationAge() == 9 && _vm->_state->getLocationRoom() == 901;
}
Graphics::Surface *Menu::borrowSaveThumbnail() {
return _saveThumbnail.get();
}
PagingMenu::PagingMenu(Myst3Engine *vm) :
Menu(vm),
_saveDrawCaret(false),
@ -547,17 +547,20 @@ void PagingMenu::loadMenuSelect(uint16 item) {
}
// Extract the age to load from the savegame
GameState *gameState = new GameState(_vm->getPlatform(), _vm->_db);
gameState->load(saveFile);
GameState gameState = GameState(_vm->getPlatform(), _vm->_db);
gameState.load(saveFile);
// Update the age name
_saveLoadAgeName = getAgeLabel(gameState);
_saveLoadAgeName = getAgeLabel(&gameState);
// Update the save thumbnail
if (_saveLoadSpotItem)
_saveLoadSpotItem->updateData(gameState->getSaveThumbnail());
if (_saveLoadSpotItem) {
Graphics::Surface *thumbnail = GameState::readThumbnail(saveFile);
_saveLoadSpotItem->updateData(thumbnail);
thumbnail->free();
delete thumbnail;
}
delete gameState;
delete saveFile;
}
@ -581,9 +584,8 @@ void PagingMenu::saveMenuOpen() {
saveLoadUpdateVars();
// Update the thumbnail to display
Graphics::Surface *saveThumb = _vm->_state->getSaveThumbnail();
if (_saveLoadSpotItem && saveThumb)
_saveLoadSpotItem->updateData(saveThumb);
if (_saveLoadSpotItem && _saveThumbnail)
_saveLoadSpotItem->updateData(_saveThumbnail.get());
}
void PagingMenu::saveMenuSelect(uint16 item) {
@ -627,8 +629,7 @@ void PagingMenu::saveMenuSave() {
// Save the state and the thumbnail
Common::OutSaveFile *save = _vm->getSaveFileManager()->openForSaving(fileName);
_vm->_state->setSaveDescription(_saveName);
_vm->_state->save(save,false);
_vm->_state->save(save, _saveName, _saveThumbnail.get());
delete save;
// Do next action
@ -868,9 +869,10 @@ void AlbumMenu::loadSaves() {
if (_albumSpotItems.contains(i)) {
// Read and resize the thumbnail
Graphics::Surface *miniThumb = new Graphics::Surface();
miniThumb->create(kAlbumThumbnailWidth, kAlbumThumbnailHeight, Texture::getRGBAPixelFormat());
data.resizeThumbnail(miniThumb);
Graphics::Surface *saveThumb = GameState::readThumbnail(saveFile);
Graphics::Surface *miniThumb = GameState::resizeThumbnail(saveThumb, kAlbumThumbnailWidth, kAlbumThumbnailHeight);
saveThumb->free();
delete saveThumb;
SpotItemFace *spotItem = _albumSpotItems.getVal(i);
spotItem->updateData(miniThumb);
@ -923,8 +925,12 @@ void AlbumMenu::loadMenuSelect() {
_saveLoadTime = gameState->formatSaveTime();
// Update the save thumbnail
if (_saveLoadSpotItem)
_saveLoadSpotItem->updateData(gameState->getSaveThumbnail());
if (_saveLoadSpotItem) {
Graphics::Surface *thumbnail = GameState::readThumbnail(saveFile);
_saveLoadSpotItem->updateData(thumbnail);
thumbnail->free();
delete thumbnail;
}
delete gameState;
}
@ -947,9 +953,8 @@ void AlbumMenu::saveMenuOpen() {
_saveLoadTime = "";
// Update the thumbnail to display
Graphics::Surface *saveThumb = _vm->_state->getSaveThumbnail();
if (_saveLoadSpotItem && saveThumb)
_saveLoadSpotItem->updateData(saveThumb);
if (_saveLoadSpotItem && _saveThumbnail)
_saveLoadSpotItem->updateData(_saveThumbnail.get());
}
void AlbumMenu::saveMenuSave() {
@ -966,8 +971,7 @@ void AlbumMenu::saveMenuSave() {
// Save the state and the thumbnail
Common::OutSaveFile *save = _vm->getSaveFileManager()->openForSaving(fileName);
_vm->_state->setSaveDescription(saveName);
_vm->_state->save(save,false);
_vm->_state->save(save, saveName, _saveThumbnail.get());
delete save;
// Do next action

View File

@ -54,6 +54,9 @@ public:
Menu(Myst3Engine *vm);
virtual ~Menu();
/** Indicates if the player is currently in a menu node */
bool isOpen() const;
/**
* Handle an event for the menu
*
@ -63,12 +66,18 @@ public:
void updateMainMenu(uint16 action);
void goToNode(uint16 node);
/*
* Grab a screenshot save it to state and free memory
*/
void saveThumbnail();
bool getThumbnailValid();
void setThumbnailValid(bool value);
/**
* Capture a save thumbnail
*/
Graphics::Surface *captureThumbnail();
/**
* Get the current save thumbnail
*
* Only valid while the menu is open
*/
Graphics::Surface *borrowSaveThumbnail();
virtual void saveLoadAction(uint16 action, uint16 item) = 0;
virtual void setSaveLoadSpotItem(uint16 id, SpotItemFace *spotItem);
@ -76,11 +85,11 @@ public:
protected:
Myst3Engine *_vm;
Common::ScopedPtr<Graphics::Surface, Graphics::SurfaceDeleter> _saveThumbnail;
SpotItemFace *_saveLoadSpotItem;
Common::String _saveLoadAgeName;
bool _thumbnailValid;
uint dialogIdFromType(DialogType type);
uint16 dialogConfirmValue();
uint16 dialogSaveValue();

View File

@ -210,8 +210,6 @@ Common::Error Myst3Engine::run() {
drawFrame();
}
if (!_menu->getThumbnailValid())
_menu->saveThumbnail(); // Update thumbnail before saving
tryAutoSaving(); //Attempt to autosave before exiting
unloadNode();
@ -434,7 +432,6 @@ void Myst3Engine::processInput(bool interactive) {
}
bool shouldInteractWithHoveredElement = false;
_menu->setThumbnailValid(false);
// Process events
Common::Event event;
@ -543,7 +540,6 @@ void Myst3Engine::processInput(bool interactive) {
}
if (shouldPerformAutoSave(_lastSaveTime)) {
_menu->saveThumbnail(); // Update thumbnail before saving
tryAutoSaving();
}
@ -1489,11 +1485,8 @@ void Myst3Engine::dragItem(uint16 statusVar, uint16 movie, uint16 frame, uint16
}
bool Myst3Engine::canSaveGameStateCurrently() {
return canLoadGameStateCurrently() && !(_state->getLocationRoom() == 901 && _state->getMenuSavedAge()==0);
}
bool Myst3Engine::canSaveCurrently() {
return canLoadGameStateCurrently() && !(_state->getLocationRoom() == 901 && _state->getMenuSavedAge()==0);
bool inMenuWithNoGameLoaded = _state->getLocationRoom() == 901 && _state->getMenuSavedAge() == 0;
return canLoadGameStateCurrently() && !inMenuWithNoGameLoaded && _cursor->isVisible();
}
bool Myst3Engine::canLoadGameStateCurrently() {
@ -1511,23 +1504,28 @@ void Myst3Engine::tryAutoSaving() {
_lastSaveTime = _system->getMillis();
// Get a thumbnail of the game screen
Common::DisposablePtr<Graphics::Surface, Graphics::SurfaceDeleter> thumbnail(nullptr, DisposeAfterUse::NO);
if (_menu->isOpen()) {
thumbnail.reset(_menu->borrowSaveThumbnail(), DisposeAfterUse::NO);
} else {
// Capture thumbnail before saving
thumbnail.reset(_menu->captureThumbnail(), DisposeAfterUse::YES);
}
// Open the save file
Common::String saveName = "Autosave";
Common::String fileName = Saves::buildName(saveName.c_str(),getPlatform());
Common::ScopedPtr<Common::InSaveFile> saveFile(getSaveFileManager()->openForLoading(fileName));
if (!_state->isAutoSaveAllowed(saveFile.get())) {
return; // Can't autosave ever, try again after the next autosave delay
Common::String fileName = Saves::buildName(saveName.c_str(), getPlatform());
Common::OutSaveFile *save = getSaveFileManager()->openForSaving(fileName);
if (!save) {
warning("Unable to open the autosave file: %s.", fileName.c_str());
}
// Save the state and the thumbnail
Common::OutSaveFile *save = getSaveFileManager()->openForSaving(fileName);
const Common::String prevSaveName = _state->getSaveDescription();
_state->setSaveDescription(saveName);
if (!_state->save(save, saveName, thumbnail.get())) {
warning("Unable to write the autosave to '%s'.", fileName.c_str());
}
if (!_state->save(save,true))
warning("Attempt to autosave has failed.");
_state->setSaveDescription(prevSaveName);
delete save;
}
@ -1550,6 +1548,8 @@ Common::Error Myst3Engine::loadGameState(Common::String fileName, TransitionType
_inventory->loadFromState();
// Leave the load menu
_state->setViewType(kMenu);
_state->setLocationNextAge(_state->getMenuSavedAge());
_state->setLocationNextRoom(_state->getMenuSavedRoom());
_state->setLocationNextNode(_state->getMenuSavedNode());

View File

@ -119,9 +119,8 @@ public:
uint32 getGameLocalizationType() const;
bool isWideScreenModEnabled() const;
bool canSaveGameStateCurrently() override; // Determines autosave saveability
bool canSaveGameStateCurrently() override;
bool canLoadGameStateCurrently() override;
bool canSaveCurrently() override; // Determines GMM saveability
void tryAutoSaving();
Common::Error loadGameState(int slot) override;
Common::Error loadGameState(Common::String fileName, TransitionType transition);

View File

@ -78,7 +78,6 @@ GameState::StateData::StateData() {
saveYear = 0;
saveHour = 0;
saveMinute = 0;
autoSave = false;
}
GameState::GameState(const Common::Platform platform, Database *database):
@ -474,38 +473,44 @@ void GameState::StateData::syncWithSaveGame(Common::Serializer &s) {
s.syncAsUint16LE(saveYear, 149);
s.syncAsByte(saveHour, 149);
s.syncAsByte(saveMinute, 149);
s.syncAsByte(autoSave, 150);
s.syncString(saveDescription, 149);
#ifdef SCUMM_BIG_ENDIAN
Graphics::PixelFormat saveFormat = Graphics::PixelFormat(4, 8, 8, 8, 0, 8, 16, 24, 0);
#else
Graphics::PixelFormat saveFormat = Graphics::PixelFormat(4, 8, 8, 8, 0, 16, 8, 0, 24);
#endif
if (s.isLoading()) {
thumbnail = Common::SharedPtr<Graphics::Surface>(new Graphics::Surface(), Graphics::SurfaceDeleter());
thumbnail->create(kThumbnailWidth, kThumbnailHeight, saveFormat);
s.syncBytes((byte *)thumbnail->getPixels(), kThumbnailWidth * kThumbnailHeight * 4);
thumbnail->convertToInPlace(Texture::getRGBAPixelFormat());
} else {
assert(thumbnail->format == Texture::getRGBAPixelFormat());
assert(thumbnail && thumbnail->w == kThumbnailWidth && thumbnail->h == kThumbnailHeight);
Graphics::Surface *converted = thumbnail->convertTo(saveFormat);
s.syncBytes((byte *)converted->getPixels(), kThumbnailWidth * kThumbnailHeight * 4);
converted->free();
delete converted;
}
}
void GameState::StateData::resizeThumbnail(Graphics::Surface *small) const {
Graphics::Surface *big = thumbnail.get();
assert(small->format == big->format && big->format.bytesPerPixel == 4);
const Graphics::PixelFormat GameState::getThumbnailSavePixelFormat() {
#ifdef SCUMM_BIG_ENDIAN
return Graphics::PixelFormat(4, 8, 8, 8, 0, 8, 16, 24, 0);
#else
return Graphics::PixelFormat(4, 8, 8, 8, 0, 16, 8, 0, 24);
#endif
}
Graphics::Surface *GameState::readThumbnail(Common::ReadStream *inStream) {
Graphics::Surface *thumbnail = new Graphics::Surface();
thumbnail->create(kThumbnailWidth, kThumbnailHeight, getThumbnailSavePixelFormat());
inStream->read((byte *)thumbnail->getPixels(), kThumbnailWidth * kThumbnailHeight * 4);
thumbnail->convertToInPlace(Texture::getRGBAPixelFormat());
return thumbnail;
}
void GameState::writeThumbnail(Common::WriteStream *outStream, const Graphics::Surface *thumbnail) {
assert(thumbnail->format == Texture::getRGBAPixelFormat());
assert(thumbnail && thumbnail->w == kThumbnailWidth && thumbnail->h == kThumbnailHeight);
Graphics::Surface *converted = thumbnail->convertTo(getThumbnailSavePixelFormat());
outStream->write((byte *)converted->getPixels(), kThumbnailWidth * kThumbnailHeight * 4);
converted->free();
delete converted;
}
Graphics::Surface *GameState::resizeThumbnail(Graphics::Surface *big, uint width, uint height) {
assert(big->format.bytesPerPixel == 4);
Graphics::Surface *small = new Graphics::Surface();
small->create(width, height, big->format);
uint32 *dst = (uint32 *)small->getPixels();
for (uint i = 0; i < small->h; i++) {
@ -518,6 +523,8 @@ void GameState::StateData::resizeThumbnail(Graphics::Surface *small) const {
*dst++ = *src;
}
}
return small;
}
void GameState::newGame() {
@ -534,21 +541,7 @@ bool GameState::load(Common::InSaveFile *saveFile) {
return true;
}
bool GameState::isAutoSaveAllowed(Common::InSaveFile *saveFile) {
// Check if file exists
if (!saveFile) { // The autosave file doesn't exist or is corrupt
return true;
}
// Get autoSave value from saved file
StateData data;
Common::Serializer s = Common::Serializer(saveFile, 0);
data.syncWithSaveGame(s);
return data.autoSave; // The autosave file exists and is either an autosave or not
}
bool GameState::save(Common::OutSaveFile *saveFile, bool autosave) {
bool GameState::save(Common::OutSaveFile *saveFile, const Common::String &description, const Graphics::Surface *thumbnail) {
Common::Serializer s = Common::Serializer(0, saveFile);
// Update save creation info
@ -559,26 +552,16 @@ bool GameState::save(Common::OutSaveFile *saveFile, bool autosave) {
_data.saveDay = t.tm_mday;
_data.saveHour = t.tm_hour;
_data.saveMinute = t.tm_min;
_data.autoSave = autosave;
_data.saveDescription = description;
_data.gameRunning = false;
_data.syncWithSaveGame(s);
writeThumbnail(saveFile, thumbnail);
_data.gameRunning = true;
return true;
}
Graphics::Surface *GameState::getSaveThumbnail() const {
return _data.thumbnail.get();
}
void GameState::setSaveThumbnail(Graphics::Surface *thumb) {
if (_data.thumbnail.get() == thumb)
return;
_data.thumbnail = Common::SharedPtr<Graphics::Surface>(thumb, Graphics::SurfaceDeleter());
}
Common::String GameState::formatSaveTime() {
if (_data.saveYear == 0)
return "";

View File

@ -53,13 +53,7 @@ public:
void newGame();
bool load(Common::InSaveFile *saveFile);
bool save(Common::OutSaveFile *saveFile, bool autosave);
/*
* Autosaving will be enabled if The autosave file is an autosave or if The autosave file doesn't exist
* The autosave file name is version dependent (PC vs. Xbox)
*/
bool isAutoSaveAllowed(Common::InSaveFile *saveFile);
bool save(Common::OutSaveFile *saveFile, const Common::String &description, const Graphics::Surface *thumbnail);
int32 getVar(uint16 var);
void setVar(uint16 var, int32 value);
@ -340,11 +334,7 @@ public:
void markNodeAsVisited(uint16 node, uint16 room, uint32 age);
bool isZipDestinationAvailable(uint16 node, uint16 room, uint32 age);
Graphics::Surface *getSaveThumbnail() const;
void setSaveThumbnail(Graphics::Surface *thumb);
Common::String formatSaveTime();
void setSaveDescription(const Common::String &description) { _data.saveDescription = description; }
const Common::String getSaveDescription() { return _data.saveDescription; }
Common::Array<uint16> getInventory();
void updateInventory(const Common::Array<uint16> &items);
@ -386,17 +376,17 @@ public:
uint8 saveHour;
uint8 saveMinute;
bool autoSave;
Common::String saveDescription;
Common::SharedPtr<Graphics::Surface> thumbnail;
StateData();
void syncWithSaveGame(Common::Serializer &s);
void resizeThumbnail(Graphics::Surface *small) const;
};
static const Graphics::PixelFormat getThumbnailSavePixelFormat();
static Graphics::Surface *readThumbnail(Common::ReadStream *inStream);
static void writeThumbnail(Common::WriteStream *outStream, const Graphics::Surface *thumbnail);
static Graphics::Surface *resizeThumbnail(Graphics::Surface *big, uint width, uint height);
static const uint kThumbnailWidth = 240;
static const uint kThumbnailHeight = 135;
@ -405,7 +395,7 @@ private:
const Common::Platform _platform;
Database *_db;
static const uint32 kSaveVersion = 150;
static const uint32 kSaveVersion = 149;
StateData _data;