Skip to content
This repository has been archived by the owner on Jun 8, 2022. It is now read-only.

STARK: Implement the save & load menu #1422

Merged
merged 17 commits into from Jun 4, 2018
Merged

Conversation

Dougaak
Copy link
Contributor

@Dougaak Dougaak commented May 29, 2018

This PR is corresponding to implement the save & load menu of The Longest Journey.

@Dougaak Dougaak requested a review from bgK May 29, 2018 06:23
@Dougaak Dougaak force-pushed the branch-saveload branch 2 times, most recently from cb1dc6b to 5559fee Compare May 29, 2018 10:43
@@ -336,7 +336,8 @@ Common::Error StarkEngine::loadGameState(int slot) {

bool StarkEngine::canSaveGameStateCurrently() {
// Disallow saving when there is no level loaded or when a script is running
return _global->getLevel() && _userInterface->isInteractive();
// or when the save & load menu is currently displayed
return _global->getLevel() && _userInterface->isInteractive() && !_userInterface->isInSaveLoadMenuScreen();
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used to try an implementation that allows saving through GMM while the save & load menu is displayed, but it is kind of complex since the menu needs to be notified when a save slot is updated and update the thumbnail of the corresponding widget. Maybe just disable the saving is a better approach?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disabling saving from the GMM when in the saveload menu to avoid too much complication sounds good to me.

@@ -113,7 +131,16 @@ void SaveMenuScreen::open() {
}

void SaveMenuScreen::onSlotSelected(int slot) {
g_engine->saveGameState(slot, "TestSave");
int chapter = StarkGlobal->getCurrentChapter();

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does the returned value of getCurrentChapter() really mean? I thought it was the index of the chapter, but calling this function in Chapter 1 gives me value 10.

_textDesc(gfx),
_textTime(gfx),
_isMouseHovered(false) {
// Load the corresponding save slot data
Common::String filename = StarkEngine::formatSaveName(ConfMan.getActiveDomainName().c_str(), _slot);
Common::InSaveFile *save = g_system->getSavefileManager()->openForLoading(filename);
if (save) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like putting a delete save; at the end is needed, but it will crash the game. The StarkEngine::loadGameState() also doesn't delete the save, why is that?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The constructor for StateReadStream() is two arguments, the file and a flag (DisposeAfterUse::YES and DisposeAfterUse::NO) the flag indicates if the input file should be deleted or not after use.

In your case you used it with 1 argument which looks like that defaults to the 2 argument case with the flag set to dispose. I am not exactly sure where that is happening since the constructor to SubReadStream defaults the dispose to NO (see common/substeam.h). StateReadStream is a SeekableSubReadStream which is also a SubReadStream.

So you don't need to delete save since that is already being done by the destructor of StateReadStream, but just to be clear you may want to explicitly set the 2nd argument to be DisposeAfterUse::YES in the constructor of StateReadStream.

Alternately, you can set it to NO and delete the save manually.

@Dougaak
Copy link
Contributor Author

Dougaak commented May 31, 2018

Beware
I change the SaveMetaData class to include the second part of the saving time, which may invalidate old save data. New save data may also have problems loading to old ResidualVM.

@bgK
Copy link
Member

bgK commented May 31, 2018

I change the SaveMetaData class to include the second part of the saving time, which may invalidate old save data. New save data may also have problems loading to old ResidualVM.

Hi, Please increment the save version number and load the seconds for saves that have at least the new version number. This way old saves remain compatible.

static const uint kSaveVersion = 9;

metadata.saveHour, metadata.saveMinute, metadata.saveSecond,
metadata.saveMonth, metadata.saveDay, metadata.saveYear % 100));
_textTime.setColor(_textColor);
_textTime.setFont(FontProvider::FontType::kCustomFont, 3);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you want FontProvider::kCustomFont instead of FontProvider::FontType::kCustomFont.

Copy link
Member

@bgK bgK left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does the returned value of getCurrentChapter() really mean? I thought it was the index of the chapter, but calling this function in Chapter 1 gives me value 10.

It's more a progress marker than exactly the chapter number. To obtain the actual chapter number, you need to divide that value by 10.

@@ -336,7 +336,8 @@ Common::Error StarkEngine::loadGameState(int slot) {

bool StarkEngine::canSaveGameStateCurrently() {
// Disallow saving when there is no level loaded or when a script is running
return _global->getLevel() && _userInterface->isInteractive();
// or when the save & load menu is currently displayed
return _global->getLevel() && _userInterface->isInteractive() && !_userInterface->isInSaveLoadMenuScreen();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disabling saving from the GMM when in the saveload menu to avoid too much complication sounds good to me.

if (chapter == 0) {
desc = "Prologue";
} else {
desc = Common::String::format("Chapter %d", chapter);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ultimately, chapter names will have to come from chapters.ini so they are localized. But I guess having them hardcoded is fine for the first version. The original engine uses the string before the colon in chapters.ini.

// Obtain the thumbnail
Graphics::Surface *thumb = metadata.readGameScreenThumbnail(&stream);
_texture->update(thumb);
delete thumb;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphics::Surface is very stupid. ->free() needs to be called as well.

@Dougaak
Copy link
Contributor Author

Dougaak commented Jun 2, 2018

@bgK Currently SaveDataWidget will load and update its thumbnail and text only when it is created and clicked on the save menu. Obviously it is unrealistic to update the content on every frame. But the GMM provides saving and deleting operations, which will invalidate the current menu screen's content. Implementing the updating on the change from GMM is going to be a complex task, and it feels like unnecessary for such a strange behaviour. I mean, who on earth will open two save & load menus just for fun?

So, is it okay to just block both saving and loading from GMM when the game is in save & load menu?

p.s. Currently, the saving is already blocked. Deleting a save data through GMM while the load menu is displayed will make the corresponding widget, if it's on the current page, unresponsive.

@Dougaak
Copy link
Contributor Author

Dougaak commented Jun 2, 2018

A new service for retrieving the chapter title and subtitle from chapters.ini is created. This would also be helpful when implementing the conversation log, I guess.

@bgK
Copy link
Member

bgK commented Jun 2, 2018

So, is it okay to just block both saving and loading from GMM when the game is in save & load menu?

Sure, ok.


Common::String line, title, subtitle;

// Assume that the formats of all chapters.ini are the same
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you notice Common::INIFile?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, didn't see that. I'll take a look.

@Dougaak
Copy link
Contributor Author

Dougaak commented Jun 2, 2018

The current mechanism of capturing the screenshot will also capture the cursor. I have spent some time trying to get rid of it, but the best I could get will also remove the model from the thumbnail. Any suggestion?

@bgK
Copy link
Member

bgK commented Jun 3, 2018

The current mechanism of capturing the screenshot will also capture the cursor. I have spent some time trying to get rid of it, but the best I could get will also remove the model from the thumbnail. Any suggestion?

A simple way would be to re-render the game window only just before capturing the screenshot. (And maybe re-render the whole screen afterwards if that causes glitches)

@Dougaak
Copy link
Contributor Author

Dougaak commented Jun 3, 2018

A simple way would be to re-render the game window only just before capturing the screenshot.

Already tried this before. Simply re-render the game window (or even the game screen) will cause only the background rendered, without the models. Calling updateScreen() after will also render the cursor.

Still can't figure out what causes this.

@bgK
Copy link
Member

bgK commented Jun 3, 2018

Already tried this before. Simply re-render the game window (or even the game screen) will cause only the background rendered, without the models.

Oh, right. The depth buffer needs to be cleared for the depths tests to work correctly. Calling StarkGfx->clearScreen should do it.

}

// Obtain the thumbnail
Graphics::Surface *thumb = metadata.readGameScreenThumbnail(&stream);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thumbnail is only available since save metadata version 9. Most of my saves are older than that and show garbage instead of the thumbnail.

// Freeze the screen for a while to let the user notice the change
widget->loadSaveDataElements();
render();
g_system->updateScreen();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use GfxDriver->flipBuffer() instead not to break the Driver abstraction.

lineSurface.free();

// Set the position
_thumbPos.x = 41 + (_slot % 3) * (_thumbWidth + 39);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There could be constants for the number of slots per lines, the number of slot lines and the number of slots per page to make the code easier to understand.

}

void SaveLoadMenuScreen::changePage(int page) {
assert(page >= 0 && page <= 10);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a constant for the maximum number of pages. We may want to increase that number as the ResidualVM save and load dialogs have a lot more slots.

@@ -94,7 +94,7 @@ void DiaryIndexScreen::open() {
_widgets.push_back(new StaticLocationWidget(
"Back",
CLICK_HANDLER(DiaryIndexScreen, backHandler),
MOVE_HANDLER(DiaryIndexScreen, widgetTextColorHandler)));
nullptr));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just found out that this text will not change to blue in the original game.

_texture->update(thumb);
thumb->free();
delete thumb;
}
Copy link
Contributor Author

@Dougaak Dougaak Jun 3, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have save data older than version 9. Could you please check this code for me?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's working, thanks!

This is my collection of old saved games in case you need it: https://filenurse.com/download/361b5da097c32696eff053a7b026ad7b.html

A lot of them were made back when some entity save handlers were missing, so they don't load all the state, but oh well...

@bgK bgK merged commit 88fd362 into residualvm:master Jun 4, 2018
@Dougaak Dougaak deleted the branch-saveload branch June 26, 2018 13:29
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants