MYST3: Save game update

* Save floats in a portable way
* Save the creation date and description
* Clean up thumbnail handling
* Increment savegame version

Saves from the original engine are still compatible
This commit is contained in:
Bastien Bouclet 2012-12-11 14:58:19 +01:00
parent 23dfe79d88
commit f64ec08108
6 changed files with 188 additions and 90 deletions

View File

@ -167,8 +167,9 @@ public:
(f == kSupportsListSaves) ||
(f == kSupportsDeleteSave) ||
(f == kSupportsLoadingDuringStartup) ||
(f == kSavesSupportThumbnail) ||
(f == kSavesSupportMetaInfo) ||
(f == kSavesSupportThumbnail) ||
(f == kSavesSupportCreationDate) ||
(f == kSavesSupportPlayTime);
}
@ -224,17 +225,21 @@ public:
GameState::syncWithSaveGame(s, data);
// Read and resize the thumbnail
Graphics::Surface *thumbnail = GameState::loadThumbnail(saveFile);
Graphics::Surface *guiThumb = new Graphics::Surface();
guiThumb->create(160, 100, Graphics::PixelFormat(4, 8, 8, 8, 8, 16, 8, 0, 24));
resizeThumbnail(thumbnail, guiThumb);
resizeThumbnail(data.thumbnail.get(), guiThumb);
// Set metadata
saveInfos.setThumbnail(guiThumb);
saveInfos.setPlayTime(data.secondsPlayed * 1000);
thumbnail->free();
delete thumbnail;
if (data.saveYear != 0) {
saveInfos.setSaveDate(data.saveYear, data.saveMonth, data.saveDay);
saveInfos.setSaveTime(data.saveHour, data.saveMinute);
}
if (data.saveDescription != "")
saveInfos.setDescription(data.saveDescription);
delete saveFile;

View File

@ -39,15 +39,9 @@ Menu::Menu(Myst3Engine *vm) :
_saveLoadSpotItem(0),
_saveDrawCaret(false),
_saveCaretCounter(0) {
_saveThumb = new Graphics::Surface();
_saveThumb->create(240, 135, Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24));
}
Menu::~Menu() {
if (_saveThumb) {
_saveThumb->free();
delete _saveThumb;
}
}
void Menu::updateMainMenu(uint16 action) {
@ -139,7 +133,8 @@ void Menu::goToNode(uint16 node) {
// ... and capture the screen
Graphics::Surface *big = _vm->_gfx->getScreenshot();
createThumbnail(big, _saveThumb);
Graphics::Surface *thumb = createThumbnail(big);
_vm->_state->setSaveThumbnail(thumb);
big->free();
delete big;
}
@ -302,13 +297,15 @@ void Menu::loadMenuSelect(uint16 item) {
// Extract the age to load from the savegame
GameState *gameState = new GameState(_vm);
gameState->load(filename);
_saveLoadAgeName = getAgeLabel(gameState);
delete gameState;
// Extract the thumbnail from the save
Common::InSaveFile *save = _vm->getSaveFileManager()->openForLoading(filename);
saveGameReadThumbnail(save);
delete save;
// Update the age name
_saveLoadAgeName = getAgeLabel(gameState);
// Update the save thumbnail
if (_saveLoadSpotItem)
_saveLoadSpotItem->updateData(gameState->getSaveThumbnail());
delete gameState;
}
void Menu::loadMenuLoad() {
@ -344,8 +341,9 @@ void Menu::saveMenuOpen() {
saveLoadUpdateVars();
// Update the thumbnail to display
if (_saveLoadSpotItem && _saveThumb)
_saveLoadSpotItem->updateData(_saveThumb);
Graphics::Surface *saveThumb = _vm->_state->getSaveThumbnail();
if (_saveLoadSpotItem && saveThumb)
_saveLoadSpotItem->updateData(saveThumb);
}
void Menu::saveMenuSelect(uint16 item) {
@ -389,8 +387,8 @@ void Menu::saveMenuSave() {
// Save the state and the thumbnail
Common::OutSaveFile *save = _vm->getSaveFileManager()->openForSaving(fileName);
_vm->_state->setSaveDescription(_saveName);
_vm->_state->save(save);
saveGameWriteThumbnail(save);
delete save;
// Do next action
@ -535,35 +533,6 @@ Common::String Menu::getAgeLabel(GameState *gameState) {
return label;
}
void Menu::saveGameReadThumbnail(Common::InSaveFile *save) {
Graphics::Surface *thumbnail = GameState::loadThumbnail(save);
// Convert to RGBA
thumbnail->convertToInPlace(Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24));
if (_saveLoadSpotItem)
_saveLoadSpotItem->updateData(thumbnail);
thumbnail->free();
delete thumbnail;
}
void Menu::saveGameWriteThumbnail(Common::OutSaveFile *save) {
// The file expects BGRA data instead of RGBA
uint8 *src = (uint8 *)_saveThumb->pixels;
for (uint i = 0; i < kMiniatureSize; i++) {
uint8 r, g, b, a;
r = *src++;
g = *src++;
b = *src++;
a = *src++;
save->writeByte(b);
save->writeByte(g);
save->writeByte(r);
save->writeByte(a);
}
}
Common::String Menu::prepareSaveNameForDisplay(const Common::String &name) {
Common::String display = name;
display.toUppercase();
@ -580,9 +549,11 @@ Common::String Menu::prepareSaveNameForDisplay(const Common::String &name) {
return display;
}
void Menu::createThumbnail(Graphics::Surface *big, Graphics::Surface *small) {
assert(big->format.bytesPerPixel == 4
&& small->format.bytesPerPixel == 4);
Graphics::Surface *Menu::createThumbnail(Graphics::Surface *big) {
assert(big->format.bytesPerPixel == 4);
Graphics::Surface *small = new Graphics::Surface();
small->create(240, 135, Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24));
uint bigHeight = big->h - Renderer::kTopBorderHeight - Renderer::kBottomBorderHeight;
uint bigYOffset = Renderer::kBottomBorderHeight;
@ -595,9 +566,14 @@ void Menu::createThumbnail(Graphics::Surface *big, Graphics::Surface *small) {
uint32 *src = (uint32 *)big->getBasePtr(srcX, srcY - 1);
// Copy RGBA pixel
*dst++ = *src++;
*dst++ = *src;
}
}
// The thumbnail is stored on disk in BGRA
small->convertToInPlace(Graphics::PixelFormat(4, 8, 8, 8, 8, 16, 8, 0, 24));
return small;
}
} /* namespace Myst3 */

View File

@ -70,14 +70,13 @@ private:
Common::String _saveName;
bool _saveDrawCaret;
int32 _saveCaretCounter;
Graphics::Surface *_saveThumb;
static const uint kCaretSpeed = 25;
static const uint kMiniatureSize = 240 * 135;
void saveLoadUpdateVars();
void createThumbnail(Graphics::Surface *big, Graphics::Surface *small);
Graphics::Surface *createThumbnail(Graphics::Surface *big);
void saveGameReadThumbnail(Common::InSaveFile *save);
void saveGameWriteThumbnail(Common::OutSaveFile *save);

View File

@ -316,6 +316,9 @@ void SpotItemFace::updateData(const Graphics::Surface *surface) {
_bitmap->free();
_bitmap->copyFrom(*surface);
// Ensure the pixel format is correct
_bitmap->convertToInPlace(Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24));
_drawn = false;
}

View File

@ -26,6 +26,54 @@
namespace Myst3 {
GameState::StateData::StateData() {
version = GameState::kSaveVersion;
gameRunning = true;
currentFrame = 0;
nextSecondsUpdate = 0;
secondsPlayed = 0;
dword_4C2C44 = 0;
dword_4C2C48 = 0;
dword_4C2C4C = 0;
dword_4C2C50 = 0;
dword_4C2C54 = 0;
dword_4C2C58 = 0;
dword_4C2C5C = 0;
dword_4C2C60 = 0;
currentNodeType = 0;
lookatPitch = 0;
lookatHeading = 0;
lookatFOV = 0;
pitchOffset = 0;
headingOffset = 0;
limitCubeCamera = 0;
minPitch = 0;
maxPitch = 0;
minHeading = 0;
maxHeading = 0;
dword_4C2C90 = 0;
for (uint i = 0; i < 2048; i++)
vars[i] = 0;
vars[0] = 0;
vars[1] = 1;
inventoryCount = 0;
for (uint i = 0; i < 7; i++)
inventoryList[i] = 0;
for (uint i = 0; i < 256; i++)
zipDestinations[i] = 0;
saveDay = 0;
saveMonth = 0;
saveYear = 0;
saveHour = 0;
saveMinute = 0;
}
GameState::GameState(Myst3Engine *vm):
_vm(vm) {
@ -205,9 +253,23 @@ GameState::GameState(Myst3Engine *vm):
GameState::~GameState() {
}
void GameState::syncFloat(Common::Serializer &s, float &val,
Common::Serializer::Version minVersion, Common::Serializer::Version maxVersion) {
static const float precision = 10000.0;
if (s.isLoading()) {
int32 saved;
s.syncAsSint32LE(saved, minVersion, maxVersion);
val = saved / precision;
} else {
int32 toSave = val * precision;
s.syncAsSint32LE(toSave, minVersion, maxVersion);
}
}
void GameState::syncWithSaveGame(Common::Serializer &s, StateData &data) {
if (!s.syncVersion(kSaveVersion))
error("This savegame (v%d) is too recent (max %d) please get a newer version of Residual", s.getVersion(), kSaveVersion);
error("This savegame (v%d) is too recent (max %d) please get a newer version of ResidualVM", s.getVersion(), kSaveVersion);
s.syncAsUint32LE(data.gameRunning);
s.syncAsUint32LE(data.currentFrame);
@ -223,19 +285,36 @@ void GameState::syncWithSaveGame(Common::Serializer &s, StateData &data) {
s.syncAsUint32LE(data.dword_4C2C60);
s.syncAsUint32LE(data.currentNodeType);
// FIXME Syncing IEE754 data is not cross platform
// Increase the savegame version and save those as integers
s.syncBytes((byte*) &data.lookatPitch, sizeof(float));
s.syncBytes((byte*) &data.lookatHeading, sizeof(float));
s.syncBytes((byte*) &data.lookatFOV, sizeof(float));
s.syncBytes((byte*) &data.pitchOffset, sizeof(float));
s.syncBytes((byte*) &data.headingOffset, sizeof(float));
// The original engine (v148) saved the raw IEE754 data,
// we save decimal data as fixed point instead to be achieve portability
if (s.getVersion() < 149) {
s.syncBytes((byte*) &data.lookatPitch, sizeof(float));
s.syncBytes((byte*) &data.lookatHeading, sizeof(float));
s.syncBytes((byte*) &data.lookatFOV, sizeof(float));
s.syncBytes((byte*) &data.pitchOffset, sizeof(float));
s.syncBytes((byte*) &data.headingOffset, sizeof(float));
} else {
syncFloat(s, data.lookatPitch);
syncFloat(s, data.lookatHeading);
syncFloat(s, data.lookatFOV);
syncFloat(s, data.pitchOffset);
syncFloat(s, data.headingOffset);
}
s.syncAsUint32LE(data.limitCubeCamera);
s.syncBytes((byte*) &data.minPitch, sizeof(float));
s.syncBytes((byte*) &data.maxPitch, sizeof(float));
s.syncBytes((byte*) &data.minHeading, sizeof(float));
s.syncBytes((byte*) &data.maxHeading, sizeof(float));
if (s.getVersion() < 149) {
s.syncBytes((byte*) &data.minPitch, sizeof(float));
s.syncBytes((byte*) &data.maxPitch, sizeof(float));
s.syncBytes((byte*) &data.minHeading, sizeof(float));
s.syncBytes((byte*) &data.maxHeading, sizeof(float));
} else {
syncFloat(s, data.minPitch);
syncFloat(s, data.maxPitch);
syncFloat(s, data.minHeading);
syncFloat(s, data.maxHeading);
}
s.syncAsUint32LE(data.dword_4C2C90);
for (uint i = 0; i < 2048; i++)
@ -248,15 +327,24 @@ void GameState::syncWithSaveGame(Common::Serializer &s, StateData &data) {
for (uint i = 0; i < 256; i++)
s.syncAsByte(data.zipDestinations[i]);
s.syncAsByte(data.saveDay, 149);
s.syncAsByte(data.saveMonth, 149);
s.syncAsUint16LE(data.saveYear, 149);
s.syncAsByte(data.saveHour, 149);
s.syncAsByte(data.saveMinute, 149);
s.syncString(data.saveDescription, 149);
if (s.isLoading()) {
Graphics::Surface *thumbnail = new Graphics::Surface();
thumbnail->create(240, 135, Graphics::PixelFormat(4, 8, 8, 8, 8, 16, 8, 0, 24));
data.thumbnail = Common::SharedPtr<Graphics::Surface>(thumbnail, Graphics::SharedPtrSurfaceDeleter());
}
s.syncBytes((byte *)data.thumbnail->pixels, 240 * 135 * 4);
}
void GameState::newGame() {
memset(&_data, 0, sizeof(_data));
_data.version = kSaveVersion;
_data.gameRunning = true;
_data.vars[0] = 0;
_data.vars[1] = 1;
_data = StateData();
}
bool GameState::load(const Common::String &file) {
@ -270,22 +358,18 @@ bool GameState::load(const Common::String &file) {
return true;
}
Graphics::Surface *GameState::loadThumbnail(Common::InSaveFile *save) {
// Start of thumbnail data
save->seek(8580);
Graphics::Surface *thumbnail = new Graphics::Surface();
thumbnail->create(240, 135, Graphics::PixelFormat(4, 8, 8, 8, 8, 16, 8, 0, 24));
// Read BGRA
save->read(thumbnail->pixels, 240 * 135 * 4);
return thumbnail;
}
bool GameState::save(Common::OutSaveFile *saveFile) {
Common::Serializer s = Common::Serializer(0, saveFile);
// Update save creation info
TimeDate t;
g_system->getTimeAndDate(t);
_data.saveYear = t.tm_year + 1900;
_data.saveMonth = t.tm_mon + 1;
_data.saveDay = t.tm_mday;
_data.saveHour = t.tm_hour;
_data.saveMinute = t.tm_min;
_data.gameRunning = false;
syncWithSaveGame(s, _data);
_data.gameRunning = true;
@ -293,6 +377,17 @@ bool GameState::save(Common::OutSaveFile *saveFile) {
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::SharedPtrSurfaceDeleter());
}
Common::Array<uint16> GameState::getInventory() {
Common::Array<uint16> items;

View File

@ -51,8 +51,6 @@ public:
bool load(const Common::String &file);
bool save(Common::OutSaveFile *save);
static Graphics::Surface *loadThumbnail(Common::InSaveFile *save);
int32 getVar(uint16 var);
void setVar(uint16 var, int32 value);
bool evaluate(int16 condition);
@ -234,6 +232,11 @@ public:
float getMinHeading() { return _data.minHeading; }
float getMaxHeading() { return _data.maxHeading; }
Graphics::Surface *getSaveThumbnail() const;
void setSaveThumbnail(Graphics::Surface *thumb);
void setSaveDescription(const Common::String &description) { _data.saveDescription = description; }
Common::Array<uint16> getInventory();
void updateInventory(const Common::Array<uint16> &items);
@ -267,6 +270,19 @@ public:
uint32 inventoryCount;
uint32 inventoryList[7];
int8 zipDestinations[256];
uint8 saveDay;
uint8 saveMonth;
uint16 saveYear;
uint8 saveHour;
uint8 saveMinute;
Common::String saveDescription;
Common::SharedPtr<Graphics::Surface> thumbnail;
StateData();
};
static void syncWithSaveGame(Common::Serializer &s, StateData &data);
@ -274,7 +290,7 @@ public:
private:
Myst3Engine *_vm;
static const uint32 kSaveVersion = 148;
static const uint32 kSaveVersion = 149;
StateData _data;
@ -293,6 +309,10 @@ private:
int32 engineGet(uint16 var);
void engineSet(uint16 var, int32 value);
static void syncFloat(Common::Serializer &s, float &val,
Common::Serializer::Version minVersion = 0,
Common::Serializer::Version maxVersion = Common::Serializer::kLastVersion);
};
} /* namespace Myst3 */