KYRA: add more metadata to savegames

(creation date/time and playing time)
This commit is contained in:
athrxx 2022-05-22 02:26:47 +02:00
parent 34007cb7c2
commit d8004bebba
26 changed files with 107 additions and 30 deletions

View File

@ -213,6 +213,8 @@ bool CharacterGenerator::start(EoBCharacter *characters, const uint8 ***faceShap
_vm->snd_stopSound();
_vm->delay(_vm->_tickLength);
_vm->restartPlayTimerAt(0);
init(defaultParty);
if (defaultParty)

View File

@ -1019,7 +1019,6 @@ void EoBEngine::displayParchment(int id) {
if (id < 46 || id > 50)
return;
uint32 startTime = _system->getMillis();
disableSysTimer(2);
_screen->sega_fadeToBlack(2);
@ -1077,7 +1076,6 @@ void EoBEngine::displayParchment(int id) {
snd_playLevelScore();
enableSysTimer(2);
_totalPlaySecs += ((_system->getMillis() - startTime) / 1000);
}
const uint8 **EoBEngine::makePortalShapes() {

View File

@ -242,7 +242,7 @@ EoBCoreEngine::EoBCoreEngine(OSystem *system, const GameFlags &flags) : KyraRpgE
_amigaSoundMap = 0;
_amigaCurSoundFile = -1;
_prefMenuPlatformOffset = 0;
_lastVIntTick = _lastSecTick = _totalPlaySecs = _totalEnemiesKilled = _totalSteps = 0;
_lastVIntTick = _totalEnemiesKilled = _totalSteps = 0;
_levelMaps = 0;
_closeSpellbookAfterUse = false;
_wndBackgrnd = 0;
@ -734,7 +734,8 @@ void EoBCoreEngine::runLoop() {
if (_sceneUpdateRequired && !_sceneShakeCountdown)
drawScene(1);
updateAnimTimers();
updatePlayTimer();
updateAnimations();
uint32 curTime = _system->getMillis();
if (_envAudioTimer < curTime && !(_flags.gameID == GI_EOB1 && (_flags.platform == Common::kPlatformSegaCD || _flags.platform == Common::kPlatformAmiga || _currentLevel == 0 || _currentLevel > 3))) {
@ -786,13 +787,8 @@ bool EoBCoreEngine::checkPartyStatus(bool handleDeath) {
return false;
}
void EoBCoreEngine::updateAnimTimers() {
void EoBCoreEngine::updateAnimations() {
uint32 curTime = _system->getMillis();
if (_lastSecTick + 1000 <= curTime) {
_lastSecTick = curTime;
_totalPlaySecs++;
}
if (_lastVIntTick + 16 <= curTime) {
_lastVIntTick = curTime;
gui_updateAnimations();

View File

@ -343,7 +343,7 @@ protected:
void runLoop();
void update() override { screen()->updateScreen(); }
bool checkPartyStatus(bool handleDeath);
void updateAnimTimers();
void updateAnimations();
bool _runFlag;
@ -389,8 +389,6 @@ protected:
uint32 _restPartyElapsedTime;
uint32 _lastVIntTick;
uint32 _lastSecTick;
uint32 _totalPlaySecs;
uint32 _totalEnemiesKilled;
uint32 _totalSteps;

View File

@ -377,6 +377,7 @@ void KyraEngine_HoF::startup() {
loadNPCScript();
if (_gameToLoad == -1) {
restartPlayTimerAt(0);
snd_playWanderScoreViaMap(52, 1);
enterNewScene(_mainCharacter.sceneId, _mainCharacter.facing, 0, 0, 1);
saveGameStateIntern(0, "New Game", nullptr);
@ -446,6 +447,7 @@ void KyraEngine_HoF::runLoop() {
removeInputTop();
update();
updatePlayTimer();
if (inputFlag == 198 || inputFlag == 199) {
_savedMouseState = _mouseState;

View File

@ -428,6 +428,7 @@ void KyraEngine_LoK::startup() {
_gui->buttonMenuCallback(nullptr);
_menuDirectlyToLoad = false;
} else if (!shouldQuit()) {
restartPlayTimerAt(0);
saveGameStateIntern(0, "New game", nullptr);
}
} else {
@ -484,6 +485,7 @@ void KyraEngine_LoK::mainLoop() {
_timer->update();
_sound->process();
updateTextFade();
updatePlayTimer();
if (inputFlag == 198 || inputFlag == 199)
processInput(_mouseX, _mouseY);

View File

@ -613,6 +613,7 @@ void KyraEngine_MR::startup() {
assert(_invWsa);
_invWsa->open("MOODOMTR.WSA", 1, nullptr);
_invWsaFrame = 6;
restartPlayTimerAt(0);
saveGameStateIntern(0, "New Game", nullptr);
if (_gameToLoad == -1)
enterNewScene(_mainCharacter.sceneId, _mainCharacter.facing, 0, 0, 1);
@ -926,6 +927,7 @@ void KyraEngine_MR::runLoop() {
update();
_timer->update();
updatePlayTimer();
if (inputFlag == 198 || inputFlag == 199) {
_savedMouseState = _mouseState;

View File

@ -63,6 +63,7 @@ KyraEngine_v1::KyraEngine_v1(OSystem *system, const GameFlags &flags)
memset(_flagsTable, 0, sizeof(_flagsTable));
_isSaveAllowed = false;
_totalPlaySecs = _lastSecTick = _lastSecTickAtPauseStart = 0;
_mouseX = _mouseY = 0;
_transOffsY = 0;
@ -75,6 +76,7 @@ void KyraEngine_v1::pauseEngineIntern(bool pause) {
_sound->pause(pause);
if (_timer)
_timer->pause(pause);
pausePlayTimer(pause);
}
Common::Error KyraEngine_v1::init() {
@ -706,4 +708,24 @@ void KyraEngine_v1::syncSoundSettings() {
_sound->updateVolumeSettings();
}
void KyraEngine_v1::updatePlayTimer() {
uint32 curTime = _system->getMillis();
while (_lastSecTick + 1000 <= curTime) {
_lastSecTick += 1000;
_totalPlaySecs++;
}
}
void KyraEngine_v1::restartPlayTimerAt(uint32 totalPlaySecs) {
_lastSecTick = _system->getMillis();
_totalPlaySecs = totalPlaySecs;
}
void KyraEngine_v1::pausePlayTimer(bool pause) {
if (pause)
_lastSecTickAtPauseStart = _lastSecTick;
else
_lastSecTick += (_system->getMillis() - _lastSecTickAtPauseStart);
}
} // End of namespace Kyra

View File

@ -904,6 +904,8 @@ void LoLEngine::runLoop() {
update();
updatePlayTimer();
if (_sceneUpdateRequired)
gui_drawScene(0);
else

View File

@ -427,7 +427,7 @@ void EoBCoreEngine::sparkEffectDefensive(int charIndex) {
_screen->drawShape(0, _sparkShapes[shpIndex - 1], x, y, 0);
}
}
updateAnimTimers();
updateAnimations();
_screen->updateScreen();
delay(_tickLength >> 1);
}
@ -446,7 +446,7 @@ void EoBCoreEngine::sparkEffectOffensive() {
for (int i = 0; i < 44; i++) {
bool sceneShake = _sceneShakeCountdown;
updateAnimTimers();
updateAnimations();
if (sceneShake) {
_screen->copyRegion(0, 0, 0, 0, 176, 120, 0, 2, Screen::CR_NO_P_CHECK);
if (!_sceneShakeCountdown) {

View File

@ -578,7 +578,7 @@ void EoBCoreEngine::drawScene(int refresh) {
int diff = _flashShapeTimer - ct;
while ((diff > 0) && !shouldQuit()) {
updateInput();
updateAnimTimers();
updateAnimations();
uint32 step = MIN<uint32>(diff, _tickLength / 5);
_system->delayMillis(step);
diff -= step;

View File

@ -3192,6 +3192,7 @@ bool GUI_EoB::runSaveMenu(int x, int y) {
// does not survive this conversion. And the rest of the characters in these descriptions do not require it.
if (!(_vm->gameFlags().platform == Common::kPlatformSegaCD && _vm->gameFlags().lang == Common::JA_JPN && Common::String(temp).contains('\r')))
Util::convertDOSToUTF8(temp, 26);
_vm->updatePlayTimer();
Common::Error err = _vm->saveGameStateIntern(_savegameOffset + slot, temp, &thumb);
thumb.free();

View File

@ -34,7 +34,6 @@
namespace Kyra {
int EoBEngine::clickedCamp(Button *button) {
uint32 startTime = _system->getMillis();
gui_resetAnimations();
if (_flags.platform == Common::kPlatformSegaCD)
@ -46,7 +45,6 @@ int EoBEngine::clickedCamp(Button *button) {
return button->arg;
gui_resetAnimations();
_totalPlaySecs += ((_system->getMillis() - startTime) / 1000);
return button->arg;
}
@ -231,7 +229,6 @@ void EoBEngine::gui_drawCharacterStatsPage() {
}
void EoBEngine::gui_displayMap() {
uint32 startTime = _system->getMillis();
disableSysTimer(2);
_screen->sega_fadeToBlack(2);
@ -322,7 +319,6 @@ void EoBEngine::gui_displayMap() {
snd_playLevelScore();
enableSysTimer(2);
_totalPlaySecs += ((_system->getMillis() - startTime) / 1000);
}
void EoBEngine::gui_drawSpellbook() {

View File

@ -791,7 +791,7 @@ int GUI_LoK::saveGame(Button *button) {
_vm->_gameToLoad = getNextSavegameSlot();
if (_vm->_gameToLoad > 0) {
Util::convertDOSToUTF8(_savegameName, 35);
_vm->updatePlayTimer();
Graphics::Surface thumb;
createScreenThumbnail(thumb);
_vm->saveGameStateIntern(_vm->_gameToLoad, _savegameName, &thumb);

View File

@ -2848,6 +2848,7 @@ int GUI_LoL::clickedSavenameMenu(Button *button) {
int slot = _menuResult == -2 ? getNextSavegameSlot() : _menuResult - 1;
Graphics::Surface thumb;
createScreenThumbnail(thumb);
_vm->updatePlayTimer();
_vm->saveGameStateIntern(slot, _saveDescription, &thumb);
thumb.free();

View File

@ -638,6 +638,7 @@ int GUI_v2::saveMenu(Button *caller) {
Graphics::Surface thumb;
createScreenThumbnail(thumb);
_vm->updatePlayTimer();
Util::convertDOSToUTF8(_saveDescription, 81);
_vm->saveGameStateIntern(_saveSlot, _saveDescription, &thumb);
thumb.free();

View File

@ -28,7 +28,7 @@
#include "graphics/thumbnail.h"
#include "graphics/surface.h"
#define CURRENT_SAVE_VERSION 20
#define CURRENT_SAVE_VERSION 21
#define GF_FLOPPY (1 << 0)
#define GF_TALKIE (1 << 1)
@ -134,6 +134,20 @@ WARN_UNUSED_RESULT KyraEngine_v1::ReadSaveHeaderError KyraEngine_v1::readSaveHea
header.thumbnail = nullptr;
}
if (header.version >= 21) {
header.timeDate.tm_sec = in->readSint32BE();
header.timeDate.tm_min = in->readSint32BE();
header.timeDate.tm_hour = in->readSint32BE();
header.timeDate.tm_mday = in->readSint32BE();
header.timeDate.tm_mon = in->readSint32BE();
header.timeDate.tm_year = in->readSint32BE();
header.timeDate.tm_wday = in->readSint32BE();
header.totalPlaySecs = in->readUint32BE();
} else {
header.totalPlaySecs = 0;
memset(&header.timeDate, 0, sizeof(TimeDate));
}
return ((in->err() || in->eos()) ? kRSHEIoError : kRSHENoError);
}
@ -228,6 +242,19 @@ Common::OutSaveFile *KyraEngine_v1::openSaveForWriting(const char *filename, con
delete genThumbnail;
}
TimeDate td;
_system->getTimeAndDate(td);
out->writeSint32BE(td.tm_sec);
out->writeSint32BE(td.tm_min);
out->writeSint32BE(td.tm_hour);
out->writeSint32BE(td.tm_mday);
out->writeSint32BE(td.tm_mon);
out->writeSint32BE(td.tm_year);
out->writeSint32BE(td.tm_wday);
out->writeUint32BE(_totalPlaySecs);
return new Common::OutSaveFile(out);
}

View File

@ -140,7 +140,8 @@ Common::Error EoBCoreEngine::loadGameState(int slot) {
_returnAfterSpellCallback = in.readByte() ? true : false;
if (_flags.platform == Common::kPlatformSegaCD || header.version > 18) {
_totalPlaySecs = in.readUint32BE();
if (header.version < 21)
header.totalPlaySecs = in.readUint32BE();
_totalEnemiesKilled = in.readUint32BE();
_totalSteps = in.readUint32BE();
_levelMaps = in.readUint32BE();
@ -312,6 +313,8 @@ Common::Error EoBCoreEngine::loadGameState(int slot) {
if (_flags.platform != Common::kPlatformSegaCD)
_screen->fadeFromBlack(20);
restartPlayTimerAt(header.totalPlaySecs);
_loading = false;
removeInputTop();
@ -419,7 +422,6 @@ Common::Error EoBCoreEngine::saveGameStateIntern(int slot, const char *saveName,
out->writeByte(_returnAfterSpellCallback ? 1 : 0);
// SegaCD specific
out->writeUint32BE(_totalPlaySecs);
out->writeUint32BE(_totalEnemiesKilled);
out->writeUint32BE(_totalSteps);
out->writeUint32BE(_levelMaps);

View File

@ -304,6 +304,8 @@ Common::Error KyraEngine_HoF::loadGameState(int slot) {
_mainCharY = _mainCharacter.y2 = _mainCharacter.y1;
_mainCharacter.facing = 4;
restartPlayTimerAt(header.totalPlaySecs);
enterNewScene(_mainCharacter.sceneId, _mainCharacter.facing, 0, 0, 1);
setHandItem(_itemInHand);

View File

@ -211,6 +211,8 @@ Common::Error KyraEngine_LoK::loadGameState(int slot) {
// #4625 "KYRA1: Invisible Brandon" for an example of this.
_animator->_noDrawShapesFlag = 0;
restartPlayTimerAt(header.totalPlaySecs);
enterNewScene(_currentCharacter->sceneId, _currentCharacter->facing, 0, 0, 1);
_animator->animRefreshNPC(0);

View File

@ -322,6 +322,8 @@ Common::Error LoLEngine::loadGameState(int slot) {
setHandItem(_itemInHand);
loadLevel(_currentLevel);
gui_drawPlayField();
restartPlayTimerAt(header.totalPlaySecs);
timerSpecialCharacterUpdate(0);
_flagsTable[73] |= 0x08;

View File

@ -303,6 +303,8 @@ Common::Error KyraEngine_MR::loadGameState(int slot) {
_goodConscienceShown = false;
_goodConsciencePosition = false;
restartPlayTimerAt(header.totalPlaySecs);
enterNewScene(_mainCharacter.sceneId, _mainCharacter.facing, 0, 0, 1);
setHandItem(_itemInHand);

View File

@ -69,7 +69,6 @@ class KyraMetaEngine;
* Some execeptions:
* - The PC-98 version of Eye of the Beholder II is not yet supported.
* - We don't support NES or Gameboy versions of Eye of the Beholder.
* - The Macintosh version of Kyrandia 1 lacks sound effects and music.
*
* The official translations of the games of which we are aware are mostly
* supported. Some of the more rare versions (of which we don't even know
@ -376,6 +375,9 @@ protected:
bool oldHeader; // old scummvm save header
Graphics::Surface *thumbnail;
TimeDate timeDate;
uint32 totalPlaySecs;
};
enum ReadSaveHeaderError {
@ -399,6 +401,15 @@ protected:
// TODO: Consider moving this to Screen
virtual Graphics::Surface *generateSaveThumbnail() const { return 0; }
// Officially used in EOB SegaCD (appears in the final stats), but we also use this for the savegame metadata for all games.
void updatePlayTimer();
void restartPlayTimerAt(uint32 totalPlaySecs);
void pausePlayTimer(bool pause);
uint32 _lastSecTick;
uint32 _lastSecTickAtPauseStart;
uint32 _totalPlaySecs;
};
} // End of namespace Kyra

View File

@ -62,6 +62,8 @@ bool KyraMetaEngine::hasFeature(MetaEngineFeature f) const {
(f == kSupportsDeleteSave) ||
(f == kSavesSupportMetaInfo) ||
(f == kSavesSupportThumbnail) ||
(f == kSavesSupportCreationDate) ||
(f == kSavesSupportPlayTime) ||
(f == kSimpleSavesNames);
}
@ -217,6 +219,12 @@ SaveStateDescriptor KyraMetaEngine::querySaveMetaInfos(const char *target, int s
desc.setAutosave(true);
desc.setThumbnail(header.thumbnail);
if (header.version >= 21) {
desc.setPlayTime(header.totalPlaySecs * 1000);
desc.setSaveDate(header.timeDate.tm_year + 1900, header.timeDate.tm_mon + 1, header.timeDate.tm_mday);
desc.setSaveTime(header.timeDate.tm_hour, header.timeDate.tm_min);
}
return desc;
}
}

View File

@ -2379,10 +2379,10 @@ void EoBEngine::seq_xdeath() {
_screen->copyRegion(0, 0, 0, 0, 176, 120, 2, 0, Screen::CR_NO_P_CHECK);
_screen->drawShape(0, shapes2, 32, 10, 0);
_screen->updateScreen();
updateAnimTimers();
updateAnimations();
delete[] shapes2;
for (uint32 cur = _system->getMillis(); cur < del; cur = _system->getMillis()) {
updateAnimTimers();
updateAnimations();
delay(MIN<uint32>(8, del - cur));
}
}
@ -2817,7 +2817,6 @@ bool EoBEngine::seq_segaPlaySequence(int sequenceId, bool setupScreen) {
if (_flags.platform != Common::kPlatformSegaCD)
return true;
uint32 startTime = _system->getMillis();
_allowSkip = true;
resetSkipFlag();
@ -2832,8 +2831,6 @@ bool EoBEngine::seq_segaPlaySequence(int sequenceId, bool setupScreen) {
if (setupScreen)
seq_segaRestoreAfterSequence();
_totalPlaySecs += ((_system->getMillis() - startTime) / 1000);
if (!res)
error("EoBEngine::seq_segaPlaySequence(): Failed to play cutscene no. %d", sequenceId);

View File

@ -124,6 +124,7 @@ int LoLEngine::processPrologue() {
}
if (processSelection == 0) {
restartPlayTimerAt(0);
if (_flags.isDemo) {
_charSelection = 0;
_screen->loadBitmap("ITEMICN.SHP", 3, 3, 0);