scummvm/engines/agi/saveload.cpp
Max Horn 0ce2ca4e00 COMMON: Replace MKID_BE by MKTAG
MKID_BE relied on unspecified behavior of the C++ compiler,
and as such was always a bit unsafe. The new MKTAG macro
is slightly less elegant, but does no longer depend on the
behavior of the compiler.
Inspired by FFmpeg, which has an almost identical macro.
2011-04-12 16:53:15 +02:00

1032 lines
28 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $URL$
* $Id$
*
*/
//
// Savegame support by Vasyl Tsvirkunov <vasyl@pacbell.net>
// Multi-slots by Claudio Matsuoka <claudio@helllabs.org>
//
#include "common/file.h"
#include "common/config-manager.h"
#include "common/savefile.h"
#include "graphics/thumbnail.h"
#include "graphics/surface.h"
#include "agi/agi.h"
#include "agi/graphics.h"
#include "agi/sprite.h"
#include "agi/keyboard.h"
#include "agi/menu.h"
#define SAVEGAME_VERSION 5
//
// Version 0 (Sarien): view table has 64 entries
// Version 1 (Sarien): view table has 256 entries (needed in KQ3)
// Version 2 (ScummVM): first ScummVM version
// Version 3 (ScummVM): added AGIPAL save/load support
// Version 4 (ScummVM): added thumbnails and save creation date/time
// Version 5 (ScummVM): Added game md5
//
namespace Agi {
static const uint32 AGIflag = MKTAG('A','G','I',':');
int AgiEngine::saveGame(const char *fileName, const char *description) {
char gameIDstring[8] = "gameIDX";
int i;
Common::OutSaveFile *out;
int result = errOK;
debugC(3, kDebugLevelMain | kDebugLevelSavegame, "AgiEngine::saveGame(%s, %s)", fileName, description);
if (!(out = _saveFileMan->openForSaving(fileName))) {
warning("Can't create file '%s', game not saved", fileName);
return errBadFileOpen;
} else {
debugC(3, kDebugLevelMain | kDebugLevelSavegame, "Successfully opened %s for writing", fileName);
}
out->writeUint32BE(AGIflag);
out->write(description, 31);
out->writeByte(SAVEGAME_VERSION);
debugC(5, kDebugLevelMain | kDebugLevelSavegame, "Writing save game version (%d)", SAVEGAME_VERSION);
// Thumbnail
Graphics::saveThumbnail(*out);
// Creation date/time
TimeDate curTime;
_system->getTimeAndDate(curTime);
uint32 saveDate = ((curTime.tm_mday & 0xFF) << 24) | (((curTime.tm_mon + 1) & 0xFF) << 16) | ((curTime.tm_year + 1900) & 0xFFFF);
uint16 saveTime = ((curTime.tm_hour & 0xFF) << 8) | ((curTime.tm_min) & 0xFF);
out->writeUint32BE(saveDate);
debugC(5, kDebugLevelMain | kDebugLevelSavegame, "Writing save date (%d)", saveDate);
out->writeUint16BE(saveTime);
debugC(5, kDebugLevelMain | kDebugLevelSavegame, "Writing save time (%d)", saveTime);
// TODO: played time
out->writeByte(_game.state);
debugC(5, kDebugLevelMain | kDebugLevelSavegame, "Writing game state (%d)", _game.state);
strcpy(gameIDstring, _game.id);
out->write(gameIDstring, 8);
debugC(5, kDebugLevelMain | kDebugLevelSavegame, "Writing game id (%s, %s)", gameIDstring, _game.id);
const char *tmp = getGameMD5();
// As reported in bug report #2849084 "AGI: Crash when saving fallback-matched game"
// getGameMD5 will return NULL for fallback matched games. Since there is also no
// filename available we can not compute any MD5 here either. Thus we will just set
// the MD5 sum in the savegame to all zero, when getGameMD5 returns NULL.
if (!tmp) {
for (i = 0; i < 32; ++i)
out->writeByte(0);
} else {
for (i = 0; i < 32; ++i)
out->writeByte(tmp[i]);
}
for (i = 0; i < MAX_FLAGS; i++)
out->writeByte(_game.flags[i]);
for (i = 0; i < MAX_VARS; i++)
out->writeByte(_game.vars[i]);
out->writeSint16BE((int8)_game.horizon);
out->writeSint16BE((int16)_game.lineStatus);
out->writeSint16BE((int16)_game.lineUserInput);
out->writeSint16BE((int16)_game.lineMinPrint);
out->writeSint16BE((int16)_game.inputMode);
out->writeSint16BE((int16)_game.lognum);
out->writeSint16BE((int16)_game.playerControl);
out->writeSint16BE((int16)shouldQuit());
out->writeSint16BE((int16)_game.statusLine);
out->writeSint16BE((int16)_game.clockEnabled);
out->writeSint16BE((int16)_game.exitAllLogics);
out->writeSint16BE((int16)_game.pictureShown);
out->writeSint16BE((int16)_game.hasPrompt);
out->writeSint16BE((int16)_game.gameFlags);
out->writeSint16BE(_game.inputEnabled);
for (i = 0; i < _HEIGHT; i++)
out->writeByte(_game.priTable[i]);
out->writeSint16BE((int16)_game.gfxMode);
out->writeByte(_game.cursorChar);
out->writeSint16BE((int16)_game.colorFg);
out->writeSint16BE((int16)_game.colorBg);
// game.hires
// game.sbuf
// game.ego_words
// game.num_ego_words
out->writeSint16BE((int16)_game.numObjects);
for (i = 0; i < (int16)_game.numObjects; i++)
out->writeSint16BE((int16)objectGetLocation(i));
// game.ev_keyp
for (i = 0; i < MAX_STRINGS; i++)
out->write(_game.strings[i], MAX_STRINGLEN);
// record info about loaded resources
for (i = 0; i < MAX_DIRS; i++) {
out->writeByte(_game.dirLogic[i].flags);
out->writeSint16BE((int16)_game.logics[i].sIP);
out->writeSint16BE((int16)_game.logics[i].cIP);
}
for (i = 0; i < MAX_DIRS; i++)
out->writeByte(_game.dirPic[i].flags);
for (i = 0; i < MAX_DIRS; i++)
out->writeByte(_game.dirView[i].flags);
for (i = 0; i < MAX_DIRS; i++)
out->writeByte(_game.dirSound[i].flags);
// game.pictures
// game.logics
// game.views
// game.sounds
for (i = 0; i < MAX_VIEWTABLE; i++) {
VtEntry *v = &_game.viewTable[i];
out->writeByte(v->stepTime);
out->writeByte(v->stepTimeCount);
out->writeByte(v->entry);
out->writeSint16BE(v->xPos);
out->writeSint16BE(v->yPos);
out->writeByte(v->currentView);
// v->view_data
out->writeByte(v->currentLoop);
out->writeByte(v->numLoops);
// v->loop_data
out->writeByte(v->currentCel);
out->writeByte(v->numCels);
// v->cel_data
// v->cel_data_2
out->writeSint16BE(v->xPos2);
out->writeSint16BE(v->yPos2);
// v->s
out->writeSint16BE(v->xSize);
out->writeSint16BE(v->ySize);
out->writeByte(v->stepSize);
out->writeByte(v->cycleTime);
out->writeByte(v->cycleTimeCount);
out->writeByte(v->direction);
out->writeByte(v->motion);
out->writeByte(v->cycle);
out->writeByte(v->priority);
out->writeUint16BE(v->flags);
out->writeByte(v->parm1);
out->writeByte(v->parm2);
out->writeByte(v->parm3);
out->writeByte(v->parm4);
}
// Save image stack
for (i = 0; i < _imageStack.size(); i++) {
ImageStackElement ise = _imageStack[i];
out->writeByte(ise.type);
out->writeSint16BE(ise.parm1);
out->writeSint16BE(ise.parm2);
out->writeSint16BE(ise.parm3);
out->writeSint16BE(ise.parm4);
out->writeSint16BE(ise.parm5);
out->writeSint16BE(ise.parm6);
out->writeSint16BE(ise.parm7);
}
out->writeByte(0);
//Write which file number AGIPAL is using (0 if not being used)
out->writeSint16BE(_gfx->getAGIPalFileNum());
out->finalize();
if (out->err()) {
warning("Can't write file '%s'. (Disk full?)", fileName);
result = errIOError;
} else
debugC(1, kDebugLevelMain | kDebugLevelSavegame, "Saved game %s in file %s", description, fileName);
delete out;
debugC(3, kDebugLevelMain | kDebugLevelSavegame, "Closed %s", fileName);
_lastSaveTime = _system->getMillis();
return result;
}
int AgiEngine::loadGame(const char *fileName, bool checkId) {
char description[31], saveVersion, loadId[8];
int i, vtEntries = MAX_VIEWTABLE;
uint8 t;
int16 parm[7];
Common::InSaveFile *in;
debugC(3, kDebugLevelMain | kDebugLevelSavegame, "AgiEngine::loadGame(%s)", fileName);
if (!(in = _saveFileMan->openForLoading(fileName))) {
warning("Can't open file '%s', game not loaded", fileName);
return errBadFileOpen;
} else {
debugC(3, kDebugLevelMain | kDebugLevelSavegame, "Successfully opened %s for reading", fileName);
}
uint32 typea = in->readUint32BE();
if (typea == AGIflag) {
debugC(6, kDebugLevelMain | kDebugLevelSavegame, "Has AGI flag, good start");
} else {
warning("This doesn't appear to be an AGI savegame, game not restored");
delete in;
return errOK;
}
in->read(description, 31);
debugC(6, kDebugLevelMain | kDebugLevelSavegame, "Description is: %s", description);
saveVersion = in->readByte();
if (saveVersion < 2) // is the save game pre-ScummVM?
warning("Old save game version (%d, current version is %d). Will try and read anyway, but don't be surprised if bad things happen", saveVersion, SAVEGAME_VERSION);
if (saveVersion < 3)
warning("This save game contains no AGIPAL data, if the game is using the AGIPAL hack, it won't work correctly");
if (saveVersion >= 4) {
// We don't need the thumbnail here, so just read it and discard it
Graphics::Surface *thumbnail = new Graphics::Surface();
assert(thumbnail);
Graphics::loadThumbnail(*in, *thumbnail);
delete thumbnail;
thumbnail = 0;
in->readUint32BE(); // save date
in->readUint16BE(); // save time
// TODO: played time
}
_game.state = (State)in->readByte();
in->read(loadId, 8);
if (strcmp(loadId, _game.id) && checkId) {
delete in;
warning("This save seems to be from a different AGI game (save from %s, running %s), not loaded", loadId, _game.id);
return errBadFileOpen;
}
strncpy(_game.id, loadId, 8);
if (saveVersion >= 5) {
char md5[32 + 1];
for (i = 0; i < 32; i++) {
md5[i] = in->readByte();
}
md5[i] = 0; // terminate
// As noted above in AgiEngine::saveGame the MD5 sum field may be all zero
// when the save was made via a fallback matched game. In this case we will
// replace the MD5 sum with a nicer string, so that the user can easily see
// this fact in the debug output. The string saved in "md5" will never match
// any valid MD5 sum, thus it is safe to do that here.
if (md5[0] == 0)
strcpy(md5, "fallback matched");
debug(0, "Saved game MD5: \"%s\"", md5);
if (!getGameMD5()) {
warning("Since your game was only detected via the fallback detector, there is no possibility to assure the save is compatible with your game version");
debug(0, "The game used for saving is \"%s\".", md5);
} else if (strcmp(md5, getGameMD5())) {
warning("Game was saved with different gamedata - you may encounter problems");
debug(0, "Your game is \"%s\" and save is \"%s\".", getGameMD5(), md5);
}
}
for (i = 0; i < MAX_FLAGS; i++)
_game.flags[i] = in->readByte();
for (i = 0; i < MAX_VARS; i++)
_game.vars[i] = in->readByte();
setvar(vFreePages, 180); // Set amount of free memory to realistic value (Overwriting the just loaded value)
_game.horizon = in->readSint16BE();
_game.lineStatus = in->readSint16BE();
_game.lineUserInput = in->readSint16BE();
_game.lineMinPrint = in->readSint16BE();
// These are never saved
_game.cursorPos = 0;
_game.inputBuffer[0] = 0;
_game.echoBuffer[0] = 0;
_game.keypress = 0;
_game.inputMode = (InputMode)in->readSint16BE();
_game.lognum = in->readSint16BE();
_game.playerControl = in->readSint16BE();
if (in->readSint16BE())
quitGame();
_game.statusLine = in->readSint16BE();
_game.clockEnabled = in->readSint16BE();
_game.exitAllLogics = in->readSint16BE();
_game.pictureShown = in->readSint16BE();
_game.hasPrompt = in->readSint16BE();
_game.gameFlags = in->readSint16BE();
_game.inputEnabled = in->readSint16BE();
for (i = 0; i < _HEIGHT; i++)
_game.priTable[i] = in->readByte();
if (_game.hasWindow)
closeWindow();
_game.msgBoxTicks = 0;
_game.block.active = false;
// game.window - fixed by close_window()
// game.has_window - fixed by close_window()
_game.gfxMode = in->readSint16BE();
_game.cursorChar = in->readByte();
_game.colorFg = in->readSint16BE();
_game.colorBg = in->readSint16BE();
// game.hires - rebuilt from image stack
// game.sbuf - rebuilt from image stack
// game.ego_words - fixed by clean_input
// game.num_ego_words - fixed by clean_input
_game.numObjects = in->readSint16BE();
for (i = 0; i < (int16)_game.numObjects; i++)
objectSetLocation(i, in->readSint16BE());
// Those are not serialized
for (i = 0; i < MAX_DIRS; i++) {
_game.controllerOccured[i] = false;
}
for (i = 0; i < MAX_STRINGS; i++)
in->read(_game.strings[i], MAX_STRINGLEN);
for (i = 0; i < MAX_DIRS; i++) {
if (in->readByte() & RES_LOADED)
agiLoadResource(rLOGIC, i);
else
agiUnloadResource(rLOGIC, i);
_game.logics[i].sIP = in->readSint16BE();
_game.logics[i].cIP = in->readSint16BE();
}
for (i = 0; i < MAX_DIRS; i++) {
if (in->readByte() & RES_LOADED)
agiLoadResource(rPICTURE, i);
else
agiUnloadResource(rPICTURE, i);
}
for (i = 0; i < MAX_DIRS; i++) {
if (in->readByte() & RES_LOADED)
agiLoadResource(rVIEW, i);
else
agiUnloadResource(rVIEW, i);
}
for (i = 0; i < MAX_DIRS; i++) {
if (in->readByte() & RES_LOADED)
agiLoadResource(rSOUND, i);
else
agiUnloadResource(rSOUND, i);
}
// game.pictures - loaded above
// game.logics - loaded above
// game.views - loaded above
// game.sounds - loaded above
for (i = 0; i < vtEntries; i++) {
VtEntry *v = &_game.viewTable[i];
v->stepTime = in->readByte();
v->stepTimeCount = in->readByte();
v->entry = in->readByte();
v->xPos = in->readSint16BE();
v->yPos = in->readSint16BE();
v->currentView = in->readByte();
// v->view_data - fixed below
v->currentLoop = in->readByte();
v->numLoops = in->readByte();
// v->loop_data - fixed below
v->currentCel = in->readByte();
v->numCels = in->readByte();
// v->cel_data - fixed below
// v->cel_data_2 - fixed below
v->xPos2 = in->readSint16BE();
v->yPos2 = in->readSint16BE();
// v->s - fixed below
v->xSize = in->readSint16BE();
v->ySize = in->readSint16BE();
v->stepSize = in->readByte();
v->cycleTime = in->readByte();
v->cycleTimeCount = in->readByte();
v->direction = in->readByte();
v->motion = in->readByte();
v->cycle = in->readByte();
v->priority = in->readByte();
v->flags = in->readUint16BE();
v->parm1 = in->readByte();
v->parm2 = in->readByte();
v->parm3 = in->readByte();
v->parm4 = in->readByte();
}
for (i = vtEntries; i < MAX_VIEWTABLE; i++) {
memset(&_game.viewTable[i], 0, sizeof(VtEntry));
}
// Fix some pointers in viewtable
for (i = 0; i < MAX_VIEWTABLE; i++) {
VtEntry *v = &_game.viewTable[i];
if (_game.dirView[v->currentView].offset == _EMPTY)
continue;
if (!(_game.dirView[v->currentView].flags & RES_LOADED))
agiLoadResource(rVIEW, v->currentView);
setView(v, v->currentView); // Fix v->view_data
setLoop(v, v->currentLoop); // Fix v->loop_data
setCel(v, v->currentCel); // Fix v->cel_data
v->celData2 = v->celData;
v->s = NULL; // not sure if it is used...
}
_sprites->eraseBoth();
// Clear input line
_gfx->clearScreen(0);
writeStatus();
// Recreate background from saved image stack
clearImageStack();
while ((t = in->readByte()) != 0) {
for (i = 0; i < 7; i++)
parm[i] = in->readSint16BE();
replayImageStackCall(t, parm[0], parm[1], parm[2],
parm[3], parm[4], parm[5], parm[6]);
}
// Load AGIPAL Data
if (saveVersion >= 3)
_gfx->setAGIPal(in->readSint16BE());
delete in;
debugC(3, kDebugLevelMain | kDebugLevelSavegame, "Closed %s", fileName);
setflag(fRestoreJustRan, true);
_game.hasPrompt = 0; // force input line repaint if necessary
cleanInput();
_sprites->eraseBoth();
_sprites->blitBoth();
_sprites->commitBoth();
_picture->showPic();
_gfx->doUpdate();
return errOK;
}
#define NUM_SLOTS 100
#define NUM_VISIBLE_SLOTS 12
const char *AgiEngine::getSavegameFilename(int num) {
static Common::String saveLoadSlot;
char extension[5];
snprintf(extension, sizeof(extension), ".%.3d", num);
saveLoadSlot = _targetName + extension;
return saveLoadSlot.c_str();
}
void AgiEngine::getSavegameDescription(int num, char *buf, bool showEmpty) {
char fileName[MAXPATHLEN];
Common::InSaveFile *in;
debugC(4, kDebugLevelMain | kDebugLevelSavegame, "Current game id is %s", _targetName.c_str());
sprintf(fileName, "%s", getSavegameFilename(num));
if (!(in = _saveFileMan->openForLoading(fileName))) {
debugC(4, kDebugLevelMain | kDebugLevelSavegame, "File %s does not exist", fileName);
if (showEmpty)
strcpy(buf, " (empty slot)");
else
*buf = 0;
} else {
debugC(4, kDebugLevelMain | kDebugLevelSavegame, "Successfully opened %s for reading", fileName);
uint32 type = in->readUint32BE();
if (type == AGIflag) {
debugC(6, kDebugLevelMain | kDebugLevelSavegame, "Has AGI flag, good start");
in->read(buf, 31);
} else {
warning("This doesn't appear to be an AGI savegame");
strcpy(buf, "(corrupt file)");
}
delete in;
}
}
int AgiEngine::selectSlot() {
int i, key, active = 0;
int rc = -1;
int hm = 1, vm = 3; // box margins
int xmin, xmax, slotClicked;
char desc[NUM_VISIBLE_SLOTS][40];
int textCentre, buttonLength, buttonX[2], buttonY;
const char *buttonText[] = { " OK ", "Cancel", NULL };
_noSaveLoadAllowed = true;
for (i = 0; i < NUM_VISIBLE_SLOTS; i++) {
getSavegameDescription(_firstSlot + i, desc[i]);
}
textCentre = GFX_WIDTH / CHAR_LINES / 2;
buttonLength = 6;
buttonX[0] = (textCentre - 3 * buttonLength / 2) * CHAR_COLS;
buttonX[1] = (textCentre + buttonLength / 2) * CHAR_COLS;
buttonY = (vm + 17) * CHAR_LINES;
for (i = 0; i < 2; i++)
_gfx->drawCurrentStyleButton(buttonX[i], buttonY, buttonText[i], false, false, i == 0);
AllowSyntheticEvents on(this);
int oldFirstSlot = _firstSlot + 1;
int oldActive = active + 1;
while (!(shouldQuit() || _restartGame)) {
int sbPos = 0;
// Use the extreme scrollbar positions only if the extreme
// slots are in sight. (We have to calculate this even if we
// don't redraw the save slots, because it's also used for
// clicking in the scrollbar.
if (_firstSlot == 0)
sbPos = 1;
else if (_firstSlot == NUM_SLOTS - NUM_VISIBLE_SLOTS)
sbPos = NUM_VISIBLE_SLOTS - 2;
else {
sbPos = 2 + (_firstSlot * (NUM_VISIBLE_SLOTS - 4)) / (NUM_SLOTS - NUM_VISIBLE_SLOTS - 1);
if (sbPos >= NUM_VISIBLE_SLOTS - 3)
sbPos = NUM_VISIBLE_SLOTS - 3;
}
if (oldFirstSlot != _firstSlot || oldActive != active) {
char dstr[64];
for (i = 0; i < NUM_VISIBLE_SLOTS; i++) {
sprintf(dstr, "[%2d. %-28.28s]", i + _firstSlot, desc[i]);
printText(dstr, 0, hm + 1, vm + 4 + i,
(40 - 2 * hm) - 1, i == active ? MSG_BOX_COLOUR : MSG_BOX_TEXT,
i == active ? MSG_BOX_TEXT : MSG_BOX_COLOUR);
}
char upArrow[] = "^";
char downArrow[] = "v";
char scrollBar[] = " ";
for (i = 1; i < NUM_VISIBLE_SLOTS - 1; i++)
printText(scrollBar, 35, hm + 1, vm + 4 + i, 1, MSG_BOX_COLOUR, 7, true);
printText(upArrow, 35, hm + 1, vm + 4, 1, 8, 7);
printText(downArrow, 35, hm + 1, vm + 4 + NUM_VISIBLE_SLOTS - 1, 1, 8, 7);
printText(scrollBar, 35, hm + 1, vm + 4 + sbPos, 1, MSG_BOX_COLOUR, MSG_BOX_TEXT);
oldActive = active;
oldFirstSlot = _firstSlot;
}
pollTimer();
key = doPollKeyboard();
// It may happen that somebody will open GMM while
// this dialog is open, and load a game
// We are processing it here, effectively jumping
// out of the dead loop
if (getflag(fRestoreJustRan)) {
rc = -2;
goto getout;
}
switch (key) {
case KEY_ENTER:
rc = active;
strncpy(_game.strings[MAX_STRINGS], desc[i], MAX_STRINGLEN);
goto press;
case KEY_ESCAPE:
rc = -1;
goto getout;
case BUTTON_LEFT:
if (_gfx->testButton(buttonX[0], buttonY, buttonText[0])) {
rc = active;
strncpy(_game.strings[MAX_STRINGS], desc[i], MAX_STRINGLEN);
goto press;
}
if (_gfx->testButton(buttonX[1], buttonY, buttonText[1])) {
rc = -1;
goto getout;
}
slotClicked = ((int)_mouse.y - 1) / CHAR_COLS - (vm + 4);
xmin = (hm + 1) * CHAR_COLS;
xmax = xmin + CHAR_COLS * 34;
if ((int)_mouse.x >= xmin && (int)_mouse.x <= xmax) {
if (slotClicked >= 0 && slotClicked < NUM_VISIBLE_SLOTS)
active = slotClicked;
}
xmin = (hm + 36) * CHAR_COLS;
xmax = xmin + CHAR_COLS;
if ((int)_mouse.x >= xmin && (int)_mouse.x <= xmax) {
if (slotClicked >= 0 && slotClicked < NUM_VISIBLE_SLOTS) {
if (slotClicked == 0)
keyEnqueue(KEY_UP);
else if (slotClicked == NUM_VISIBLE_SLOTS - 1)
keyEnqueue(KEY_DOWN);
else if (slotClicked < sbPos)
keyEnqueue(KEY_UP_RIGHT);
else if (slotClicked > sbPos)
keyEnqueue(KEY_DOWN_RIGHT);
}
}
break;
case KEY_DOWN:
active++;
if (active >= NUM_VISIBLE_SLOTS) {
if (_firstSlot + NUM_VISIBLE_SLOTS < NUM_SLOTS) {
_firstSlot++;
for (i = 1; i < NUM_VISIBLE_SLOTS; i++)
memcpy(desc[i - 1], desc[i], sizeof(desc[0]));
getSavegameDescription(_firstSlot + NUM_VISIBLE_SLOTS - 1, desc[NUM_VISIBLE_SLOTS - 1]);
}
active = NUM_VISIBLE_SLOTS - 1;
}
break;
case KEY_UP:
active--;
if (active < 0) {
active = 0;
if (_firstSlot > 0) {
_firstSlot--;
for (i = NUM_VISIBLE_SLOTS - 1; i > 0; i--)
memcpy(desc[i], desc[i - 1], sizeof(desc[0]));
getSavegameDescription(_firstSlot, desc[0]);
}
}
break;
// Page Up/Down and mouse wheel scrolling all leave 'active'
// unchanged so that a visible slot will remain selected.
case WHEEL_DOWN:
if (_firstSlot < NUM_SLOTS - NUM_VISIBLE_SLOTS) {
_firstSlot++;
for (i = 1; i < NUM_VISIBLE_SLOTS; i++)
memcpy(desc[i - 1], desc[i], sizeof(desc[0]));
getSavegameDescription(_firstSlot + NUM_VISIBLE_SLOTS - 1, desc[NUM_VISIBLE_SLOTS - 1]);
}
break;
case WHEEL_UP:
if (_firstSlot > 0) {
_firstSlot--;
for (i = NUM_VISIBLE_SLOTS - 1; i > 0; i--)
memcpy(desc[i], desc[i - 1], sizeof(desc[0]));
getSavegameDescription(_firstSlot, desc[0]);
}
break;
case KEY_DOWN_RIGHT:
// This is probably triggered by Page Down.
_firstSlot += NUM_VISIBLE_SLOTS;
if (_firstSlot > NUM_SLOTS - NUM_VISIBLE_SLOTS) {
_firstSlot = NUM_SLOTS - NUM_VISIBLE_SLOTS;
}
for (i = 0; i < NUM_VISIBLE_SLOTS; i++)
getSavegameDescription(_firstSlot + i, desc[i]);
break;
case KEY_UP_RIGHT:
// This is probably triggered by Page Up.
_firstSlot -= NUM_VISIBLE_SLOTS;
if (_firstSlot < 0) {
_firstSlot = 0;
}
for (i = 0; i < NUM_VISIBLE_SLOTS; i++)
getSavegameDescription(_firstSlot + i, desc[i]);
break;
}
_gfx->doUpdate();
}
press:
debugC(8, kDebugLevelMain | kDebugLevelInput, "Button pressed: %d", rc);
getout:
closeWindow();
_noSaveLoadAllowed = false;
return rc;
}
int AgiEngine::saveGameDialog() {
char fileName[MAXPATHLEN];
char *desc;
const char *buttons[] = { "Do as I say!", "I regret", NULL };
char dstr[200];
int rc, slot = 0;
int hm, vm, hp, vp;
int w;
hm = 1;
vm = 3;
hp = hm * CHAR_COLS;
vp = vm * CHAR_LINES;
w = (40 - 2 * hm) - 1;
do {
drawWindow(hp, vp, GFX_WIDTH - hp, GFX_HEIGHT - vp);
printText("Select a slot in which you wish to\nsave the game:",
0, hm + 1, vm + 1, w, MSG_BOX_TEXT, MSG_BOX_COLOUR);
slot = selectSlot();
if (slot + _firstSlot == 0)
messageBox("That slot is for Autosave only.");
else if (slot < 0)
return errOK;
} while (slot + _firstSlot == 0);
drawWindow(hp, vp + 5 * CHAR_LINES, GFX_WIDTH - hp,
GFX_HEIGHT - vp - 9 * CHAR_LINES);
printText("Enter a description for this game:",
0, hm + 1, vm + 6, w, MSG_BOX_TEXT, MSG_BOX_COLOUR);
_gfx->drawRectangle(3 * CHAR_COLS, 11 * CHAR_LINES - 1,
37 * CHAR_COLS, 12 * CHAR_LINES, MSG_BOX_TEXT);
_gfx->flushBlock(3 * CHAR_COLS, 11 * CHAR_LINES - 1,
37 * CHAR_COLS, 12 * CHAR_LINES);
// The description field of the save/restore dialog holds 32 characters
// but we use four of them for the slot number. The input field is a
// bit wider than that, so we don't have to worry about leaving space
// for the cursor.
getString(2, 11, 28, MAX_STRINGS);
// If we're saving over an old slot, show the old description. We can't
// access that buffer directly, so we have to feed the characters to
// the input handler one at a time.
char name[40];
int numChars;
getSavegameDescription(_firstSlot + slot, name, false);
for (numChars = 0; numChars < 28 && name[numChars]; numChars++)
handleGetstring(name[numChars]);
_gfx->printCharacter(numChars + 3, 11, _game.cursorChar, MSG_BOX_COLOUR, MSG_BOX_TEXT);
do {
mainCycle();
} while (_game.inputMode == INPUT_GETSTRING);
closeWindow();
desc = _game.strings[MAX_STRINGS];
sprintf(dstr, "Are you sure you want to save the game "
"described as:\n\n%s\n\nin slot %d?\n\n\n", desc, _firstSlot + slot);
rc = selectionBox(dstr, buttons);
if (rc != 0) {
messageBox("Game NOT saved.");
return errOK;
}
sprintf(fileName, "%s", getSavegameFilename(_firstSlot + slot));
debugC(8, kDebugLevelMain | kDebugLevelResources, "file is [%s]", fileName);
// Make sure all graphics was blitted to screen. This fixes bug
// #2960567: "AGI: Ego partly erased in Load/Save thumbnails"
_gfx->doUpdate();
int result = saveGame(fileName, desc);
if (result == errOK)
messageBox("Game saved.");
else
messageBox("Error saving game.");
return result;
}
int AgiEngine::saveGameSimple() {
int result = saveGame(getSavegameFilename(0), "Default savegame");
if (result != errOK)
messageBox("Error saving game.");
return result;
}
int AgiEngine::loadGameDialog() {
char fileName[MAXPATHLEN];
int rc, slot = 0;
int hm, vm, hp, vp; // box margins
int w;
hm = 1;
vm = 3;
hp = hm * CHAR_COLS;
vp = vm * CHAR_LINES;
w = (40 - 2 * hm) - 1;
_sprites->eraseBoth();
_sound->stopSound();
drawWindow(hp, vp, GFX_WIDTH - hp, GFX_HEIGHT - vp);
printText("Select a game which you wish to\nrestore:",
0, hm + 1, vm + 1, w, MSG_BOX_TEXT, MSG_BOX_COLOUR);
slot = selectSlot();
if (slot < 0) {
if (slot == -1) // slot = -2 when GMM was launched
messageBox("Game NOT restored.");
return errOK;
}
sprintf(fileName, "%s", getSavegameFilename(_firstSlot + slot));
if ((rc = loadGame(fileName)) == errOK) {
messageBox("Game restored.");
_game.exitAllLogics = 1;
_menu->enableAll();
} else {
messageBox("Error restoring game.");
}
return rc;
}
int AgiEngine::loadGameSimple() {
char fileName[MAXPATHLEN];
int rc = 0;
sprintf(fileName, "%s", getSavegameFilename(0));
_sprites->eraseBoth();
_sound->stopSound();
closeWindow();
if ((rc = loadGame(fileName)) == errOK) {
messageBox("Game restored.");
_game.exitAllLogics = 1;
_menu->enableAll();
} else {
messageBox("Error restoring game.");
}
return rc;
}
void AgiEngine::recordImageStackCall(uint8 type, int16 p1, int16 p2, int16 p3,
int16 p4, int16 p5, int16 p6, int16 p7) {
ImageStackElement pnew;
pnew.type = type;
pnew.pad = 0;
pnew.parm1 = p1;
pnew.parm2 = p2;
pnew.parm3 = p3;
pnew.parm4 = p4;
pnew.parm5 = p5;
pnew.parm6 = p6;
pnew.parm7 = p7;
_imageStack.push(pnew);
}
void AgiEngine::replayImageStackCall(uint8 type, int16 p1, int16 p2, int16 p3,
int16 p4, int16 p5, int16 p6, int16 p7) {
switch (type) {
case ADD_PIC:
debugC(8, kDebugLevelMain, "--- decoding picture %d ---", p1);
agiLoadResource(rPICTURE, p1);
_picture->decodePicture(p1, p2, p3 != 0);
break;
case ADD_VIEW:
agiLoadResource(rVIEW, p1);
_sprites->addToPic(p1, p2, p3, p4, p5, p6, p7);
break;
}
}
void AgiEngine::clearImageStack() {
_imageStack.clear();
}
void AgiEngine::releaseImageStack() {
_imageStack.clear();
}
void AgiEngine::checkQuickLoad() {
if (ConfMan.hasKey("save_slot")) {
char saveNameBuffer[256];
snprintf(saveNameBuffer, 256, "%s.%03d", _targetName.c_str(), ConfMan.getInt("save_slot"));
_sprites->eraseBoth();
_sound->stopSound();
if (loadGame(saveNameBuffer, false) == errOK) { // Do not check game id
_game.exitAllLogics = 1;
_menu->enableAll();
}
}
}
Common::Error AgiEngine::loadGameState(int slot) {
char saveLoadSlot[12];
sprintf(saveLoadSlot, "%s.%.3d", _targetName.c_str(), slot);
_sprites->eraseBoth();
_sound->stopSound();
if (loadGame(saveLoadSlot) == errOK) {
_game.exitAllLogics = 1;
_menu->enableAll();
return Common::kNoError;
} else {
return Common::kUnknownError;
}
}
Common::Error AgiEngine::saveGameState(int slot, const char *desc) {
char saveLoadSlot[12];
sprintf(saveLoadSlot, "%s.%.3d", _targetName.c_str(), slot);
if (saveGame(saveLoadSlot, desc) == errOK)
return Common::kNoError;
else
return Common::kUnknownError;
}
} // End of namespace Agi