scummvm/engines/agi/savegame.cpp
2007-02-10 16:34:45 +00:00

696 lines
18 KiB
C++

/* ScummVM - Scumm Interpreter
* Copyright (C) 2006 The ScummVM project
*
* Copyright (C) 1999-2003 Sarien Team
*
* 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/stdafx.h"
#include "common/file.h"
#include "agi/agi.h"
#include "agi/graphics.h"
#include "agi/sprite.h"
#include "agi/keyboard.h"
#include "agi/menu.h"
#define SAVEGAME_VERSION 2
/*
* 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
*/
namespace Agi {
static const uint32 AGIflag=MKID_BE('AGI:');
int AgiEngine::saveGame(const char *fileName, const char *description) {
char gameIDstring[8]="gameIDX";
int i;
struct ImageStackElement *ptr = _imageStack;
Common::OutSaveFile *out;
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);
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);
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)_game.quitProgNow);
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((int16)_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 < _imageStackPointer; i++) {
ptr = &_imageStack[i];
out->writeByte(ptr->type);
out->writeSint16BE(ptr->parm1);
out->writeSint16BE(ptr->parm2);
out->writeSint16BE(ptr->parm3);
out->writeSint16BE(ptr->parm4);
out->writeSint16BE(ptr->parm5);
out->writeSint16BE(ptr->parm6);
out->writeSint16BE(ptr->parm7);
}
out->writeByte(0);
out->flush();
if (out->ioFailed())
warning("Can't write file '%s'. (Disk full?)", fileName);
else
debugC(1, kDebugLevelMain | kDebugLevelSavegame, "Saved game %s in file %s", description, fileName);
delete out;
debugC(3, kDebugLevelMain | kDebugLevelSavegame, "Closed %s", fileName);
return errOK;
}
int AgiEngine::loadGame(const char *fileName) {
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 != SAVEGAME_VERSION)
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);
_game.state = in->readByte();
in->read(loadId, 8);
if (strcmp(loadId, _game.id)) {
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;
}
for (i = 0; i < MAX_FLAGS; i++)
_game.flags[i] = in->readByte();
for (i = 0; i < MAX_VARS; i++)
_game.vars[i] = in->readByte();
_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 = in->readSint16BE();
_game.lognum = in->readSint16BE();
_game.playerControl = in->readSint16BE();
_game.quitProgNow = in->readSint16BE();
_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.evKeyp[i].occured = 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]);
}
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 12
const char *AgiEngine::getSavegameFilename(int num) {
static char saveLoadSlot[12];
sprintf(saveLoadSlot, "%s.%.3d", _targetName.c_str(), num);
return saveLoadSlot;
}
int AgiEngine::selectSlot() {
int i, key, active = 0;
int rc = -1;
int hm = 2, vm = 3; /* box margins */
int xmin, xmax, slotClicked;
char desc[NUM_SLOTS][40];
int textCentre, buttonLength, buttonX[2], buttonY;
const char *buttonText[] = { " OK ", "Cancel", NULL };
for (i = 0; i < NUM_SLOTS; i++) {
char fileName[MAX_PATH];
Common::InSaveFile *in;
debugC(4, kDebugLevelMain | kDebugLevelSavegame, "Current game id is %s", _targetName.c_str());
sprintf(fileName, "%s", getSavegameFilename(i));
if (!(in = _saveFileMan->openForLoading(fileName))) {
debugC(4, kDebugLevelMain | kDebugLevelSavegame, "File %s does not exist", fileName);
strcpy(desc[i], " (empty slot)");
} 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(desc[i], 31);
} else {
warning("This doesn't appear to be an AGI savegame");
strcpy(desc[i], "(corrupt file)");
}
delete in;
}
}
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->drawButton(buttonX[i], buttonY, buttonText[i], 0, 0, MSG_BOX_TEXT, MSG_BOX_COLOUR);
for (;;) {
char dstr[64];
for (i = 0; i < NUM_SLOTS; i++) {
sprintf(dstr, "[%-32.32s]", 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);
}
_gfx->pollTimer(); /* msdos driver -> does nothing */
key = doPollKeyboard();
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;
}
xmin = (hm + 1) * CHAR_COLS;
xmax = xmin + CHAR_COLS * 34;
if ((int)g_mouse.x >= xmin && (int)g_mouse.x <= xmax) {
slotClicked = ((int)g_mouse.y-1)/CHAR_COLS-(vm+4);
if (slotClicked >= 0 && slotClicked < NUM_SLOTS)
active = slotClicked;
}
break;
case KEY_DOWN:
active++;
active %= NUM_SLOTS;
break;
case KEY_UP:
active--;
if (active < 0)
active = NUM_SLOTS - 1;
break;
}
_gfx->doUpdate();
}
press:
debugC(8, kDebugLevelMain | kDebugLevelInput, "Button pressed: %d", rc);
getout:
closeWindow();
return rc;
}
int AgiEngine::saveGameDialog() {
char fileName[MAX_PATH];
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 = 2;
vm = 3;
hp = hm * CHAR_COLS;
vp = vm * CHAR_LINES;
w = (40 - 2 * hm) - 1;
sprintf(fileName, "%s", getSavegameFilename(slot));
drawWindow(hp, vp, GFX_WIDTH - hp, GFX_HEIGHT - vp);
printText("Select a slot in which you wish to save the game:",
0, hm + 1, vm + 1, w, MSG_BOX_TEXT, MSG_BOX_COLOUR);
slot = selectSlot();
if (slot < 0)
return errOK;
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);
getString(2, 11, 31, MAX_STRINGS);
_gfx->printCharacter(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, slot);
rc = selectionBox(dstr, buttons);
if (rc != 0) {
messageBox("Game NOT saved.");
return errOK;
}
sprintf(fileName, "%s", getSavegameFilename(slot));
debugC(8, kDebugLevelMain | kDebugLevelResources, "file is [%s]", fileName);
saveGame(fileName, desc);
messageBox("Game saved.");
return errOK;
}
int AgiEngine::saveGameSimple() {
char fileName[MAX_PATH];
sprintf(fileName, "%s", getSavegameFilename(0));
saveGame(fileName, "Default savegame");
return errOK;
}
int AgiEngine::loadGameDialog() {
char fileName[MAX_PATH];
int rc, slot = 0;
int hm, vm, hp, vp; /* box margins */
int w;
hm = 2;
vm = 3;
hp = hm * CHAR_COLS;
vp = vm * CHAR_LINES;
w = (40 - 2 * hm) - 1;
sprintf(fileName, "%s", getSavegameFilename(slot));
_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) {
messageBox("Game NOT restored.");
return errOK;
}
sprintf(fileName, "%s", getSavegameFilename(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[MAX_PATH];
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;
}
} // End of namespace Agi