aboutsummaryrefslogtreecommitdiff
path: root/engines/scumm/script_v4.cpp
diff options
context:
space:
mode:
authorMax Horn2009-04-19 01:01:54 +0000
committerMax Horn2009-04-19 01:01:54 +0000
commit3044593da01c055ad274b2ae552b8a285ce4fdb0 (patch)
treecfea7dd1e502ab5fba1f3e472693b6617f353eb7 /engines/scumm/script_v4.cpp
parent0e82403daa80424fb9c6089632f7dcfd9015689d (diff)
downloadscummvm-rg350-3044593da01c055ad274b2ae552b8a285ce4fdb0.tar.gz
scummvm-rg350-3044593da01c055ad274b2ae552b8a285ce4fdb0.tar.bz2
scummvm-rg350-3044593da01c055ad274b2ae552b8a285ce4fdb0.zip
SCUMM: Moved o5_saveLoadGame and o5_saveLoadVars to ScummEngine_v4 (the highest SCUMM version to implement these opcodes. Actually, our code was bugged in so far as we only ever invoked o5_saveLoadGame in V3 games, never in V4 games (but this properly never mattered ;)
svn-id: r40014
Diffstat (limited to 'engines/scumm/script_v4.cpp')
-rw-r--r--engines/scumm/script_v4.cpp319
1 files changed, 316 insertions, 3 deletions
diff --git a/engines/scumm/script_v4.cpp b/engines/scumm/script_v4.cpp
index 08df1e4653..b8964c4194 100644
--- a/engines/scumm/script_v4.cpp
+++ b/engines/scumm/script_v4.cpp
@@ -47,13 +47,19 @@ void ScummEngine_v4::setupOpcodes() {
OPCODE(0xdc, o4_oldRoomEffect);
OPCODE(0x0f, o4_ifState);
- OPCODE(0x2f, o4_ifNotState);
OPCODE(0x4f, o4_ifState);
- OPCODE(0x6f, o4_ifNotState);
OPCODE(0x8f, o4_ifState);
- OPCODE(0xaf, o4_ifNotState);
OPCODE(0xcf, o4_ifState);
+
+ OPCODE(0x2f, o4_ifNotState);
+ OPCODE(0x6f, o4_ifNotState);
+ OPCODE(0xaf, o4_ifNotState);
OPCODE(0xef, o4_ifNotState);
+
+ OPCODE(0xa7, o4_saveLoadVars);
+
+ OPCODE(0x22, o4_saveLoadGame);
+ OPCODE(0xa2, o4_saveLoadGame);
}
void ScummEngine_v4::o4_ifState() {
@@ -171,4 +177,311 @@ void ScummEngine_v4::o4_oldRoomEffect() {
}
}
+void ScummEngine_v4::o4_saveLoadVars() {
+ if (fetchScriptByte() == 1)
+ saveVars();
+ else
+ loadVars();
+}
+
+enum StringIds {
+ // The string IDs used by Indy3 to store the episode resp. series IQ points.
+ // Note that we save the episode IQ points but load the series IQ points,
+ // which matches the original Indy3 save/load code. See also the notes
+ // on Feature Request #1666521.
+ STRINGID_IQ_EPISODE = 7,
+ STRINGID_IQ_SERIES = 9,
+ // The string IDs of the first savegame name, used as an offset to determine
+ // the IDs of all savenames.
+ // Loom is the only game whose savenames start with a different ID.
+ STRINGID_SAVENAME1 = 10,
+ STRINGID_SAVENAME1_LOOM = 9
+};
+
+void ScummEngine_v4::saveVars() {
+ int a, b;
+
+ while ((_opcode = fetchScriptByte()) != 0) {
+ switch (_opcode & 0x1F) {
+ case 0x01: // write a range of variables
+ getResultPos();
+ a = _resultVarNumber;
+ getResultPos();
+ b = _resultVarNumber;
+ debug(0, "stub saveVars: vars %d -> %d", a, b);
+ break;
+ case 0x02: // write a range of string variables
+ a = getVarOrDirectByte(PARAM_1);
+ b = getVarOrDirectByte(PARAM_2);
+
+ if (a == STRINGID_IQ_EPISODE && b == STRINGID_IQ_EPISODE) {
+ if (_game.id == GID_INDY3) {
+ saveIQPoints();
+ }
+ break;
+ }
+ // FIXME: changing savegame-names not supported
+ break;
+ case 0x03: // open file
+ a = resStrLen(_scriptPointer);
+ strncpy(_saveLoadVarsFilename, (const char *)_scriptPointer, a);
+ _saveLoadVarsFilename[a] = '\0';
+ _scriptPointer += a + 1;
+ break;
+ case 0x04:
+ return;
+ case 0x1F: // close file
+ _saveLoadVarsFilename[0] = '\0';
+ return;
+ }
+ }
+}
+
+void ScummEngine_v4::loadVars() {
+ int a, b;
+
+ while ((_opcode = fetchScriptByte()) != 0) {
+ switch (_opcode & 0x1F) {
+ case 0x01: // read a range of variables
+ getResultPos();
+ a = _resultVarNumber;
+ getResultPos();
+ b = _resultVarNumber;
+ debug(0, "stub loadVars: vars %d -> %d", a, b);
+ break;
+ case 0x02: // read a range of string variables
+ a = getVarOrDirectByte(PARAM_1);
+ b = getVarOrDirectByte(PARAM_2);
+
+ int slot;
+ int slotSize;
+ byte* slotContent;
+ int savegameId;
+ bool avail_saves[100];
+
+ if (a == STRINGID_IQ_SERIES && b == STRINGID_IQ_SERIES) {
+ // Zak256 loads the IQ script-slot but does not use it -> ignore it
+ if (_game.id == GID_INDY3) {
+ byte *ptr = getResourceAddress(rtString, STRINGID_IQ_SERIES);
+ if (ptr) {
+ int size = getResourceSize(rtString, STRINGID_IQ_SERIES);
+ loadIQPoints(ptr, size);
+ }
+ }
+ break;
+ }
+
+ listSavegames(avail_saves, ARRAYSIZE(avail_saves));
+ for (slot = a; slot <= b; ++slot) {
+ slotSize = getResourceSize(rtString, slot);
+ slotContent = getResourceAddress(rtString, slot);
+
+ // load savegame names
+ savegameId = slot - a + 1;
+ Common::String name;
+ if (avail_saves[savegameId] && getSavegameName(savegameId, name)) {
+ int pos;
+ const char *ptr = name.c_str();
+ // slotContent ends with {'\0','@'} -> max. length = slotSize-2
+ for (pos = 0; pos < slotSize - 2; ++pos) {
+ if (!ptr[pos])
+ break;
+ // replace special characters
+ if (ptr[pos] >= 32 && ptr[pos] <= 122 && ptr[pos] != 64)
+ slotContent[pos] = ptr[pos];
+ else
+ slotContent[pos] = '_';
+ }
+ slotContent[pos] = '\0';
+ } else {
+ slotContent[0] = '\0';
+ }
+ }
+ break;
+ case 0x03: // open file
+ a = resStrLen(_scriptPointer);
+ strncpy(_saveLoadVarsFilename, (const char *)_scriptPointer, a);
+ _saveLoadVarsFilename[a] = '\0';
+ _scriptPointer += a + 1;
+ break;
+ case 0x04:
+ return;
+ case 0x1F: // close file
+ _saveLoadVarsFilename[0] = '\0';
+ return;
+ }
+ }
+}
+
+/**
+ * IQ Point calculation for Indy3.
+ * The scripts that perform this task are
+ * - script-9 (save/load dialog initialization, loads room 14),
+ * - room-14-204 (load series IQ string),
+ * - room-14-205 (save series IQ string),
+ * - room-14-206 (calculate series IQ string).
+ * Unfortunately script-9 contains lots of GUI stuff so calling this script
+ * directly is not possible. The other scripts depend on script-9.
+ */
+void ScummEngine_v4::updateIQPoints() {
+ int seriesIQ;
+ // IQString[0..72] corresponds to each puzzle's IQ.
+ // IQString[73] indicates that the IQ-file was loaded successfully and is always 0 when
+ // the IQ is calculated, hence it will be ignored here.
+ const int NUM_PUZZLES = 73;
+ byte seriesIQString[NUM_PUZZLES];
+ byte *episodeIQString;
+ int episodeIQStringSize;
+
+ // load string with IQ points given per puzzle in any savegame
+ // IMPORTANT: the resource string STRINGID_IQ_SERIES is only valid while
+ // the original save/load dialog is executed, so do not use it here.
+ memset(seriesIQString, 0, sizeof(seriesIQString));
+ loadIQPoints(seriesIQString, sizeof(seriesIQString));
+
+ // string with IQ points given per puzzle in current savegame
+ episodeIQString = getResourceAddress(rtString, STRINGID_IQ_EPISODE);
+ if (!episodeIQString)
+ return;
+ episodeIQStringSize = getResourceSize(rtString, STRINGID_IQ_EPISODE);
+ if (episodeIQStringSize < NUM_PUZZLES)
+ return;
+
+ // merge episode and series IQ strings and calculate series IQ
+ seriesIQ = 0;
+ // iterate over puzzles
+ for (int i = 0; i < NUM_PUZZLES ; ++i) {
+ byte puzzleIQ = seriesIQString[i];
+ // if puzzle is solved copy points to episode string
+ if (puzzleIQ > 0)
+ episodeIQString[i] = puzzleIQ;
+ // add puzzle's IQ-points to series IQ
+ seriesIQ += episodeIQString[i];
+ }
+ _scummVars[245] = seriesIQ;
+
+ // save series IQ string
+ saveIQPoints();
+}
+
+void ScummEngine_v4::saveIQPoints() {
+ // save Indy3 IQ-points
+ Common::OutSaveFile *file;
+ Common::String filename = _targetName + ".iq";
+
+ file = _saveFileMan->openForSaving(filename.c_str());
+ if (file != NULL) {
+ byte *ptr = getResourceAddress(rtString, STRINGID_IQ_EPISODE);
+ if (ptr) {
+ int size = getResourceSize(rtString, STRINGID_IQ_EPISODE);
+ file->write(ptr, size);
+ }
+ delete file;
+ }
+}
+
+void ScummEngine_v4::loadIQPoints(byte *ptr, int size) {
+ // load Indy3 IQ-points
+ Common::InSaveFile *file;
+ Common::String filename = _targetName + ".iq";
+
+ file = _saveFileMan->openForLoading(filename.c_str());
+ if (file != NULL) {
+ byte *tmp = (byte*)malloc(size);
+ int nread = file->read(tmp, size);
+ if (nread == size) {
+ memcpy(ptr, tmp, size);
+ }
+ free(tmp);
+ delete file;
+ }
+}
+
+void ScummEngine_v4::o4_saveLoadGame() {
+ getResultPos();
+ byte a = getVarOrDirectByte(PARAM_1);
+ byte slot = a & 0x1F;
+ byte result = 0;
+
+ // Slot numbers in older games start with 0, in newer games with 1
+ if (_game.version <= 2)
+ slot++;
+
+ if ((_game.id == GID_MANIAC) && (_game.version <= 1)) {
+ // Convert older load/save screen
+ // 1 Load
+ // 2 Save
+ slot = 1;
+ if (a == 1)
+ _opcode = 0x40;
+ else if ((a == 2) || (_game.platform == Common::kPlatformNES))
+ _opcode = 0x80;
+ } else {
+ _opcode = a & 0xE0;
+ }
+
+ switch (_opcode) {
+ case 0x00: // num slots available
+ result = 100;
+ break;
+ case 0x20: // drive
+ if (_game.version <= 3) {
+ // 0 = ???
+ // [1,2] = disk drive [A:,B:]
+ // 3 = hard drive
+ result = 3;
+ } else {
+ // set current drive
+ result = 1;
+ }
+ break;
+ case 0x40: // load
+ if (loadState(slot, false))
+ result = 3; // sucess
+ else
+ result = 5; // failed to load
+ break;
+ case 0x80: // save
+ if (_game.version <= 3) {
+ char name[32];
+ if (_game.version <= 2) {
+ // use generic name
+ sprintf(name, "Game %c", 'A'+slot-1);
+ } else {
+ // use name entered by the user
+ char* ptr;
+ int firstSlot = (_game.id == GID_LOOM) ? STRINGID_SAVENAME1_LOOM : STRINGID_SAVENAME1;
+ ptr = (char*)getStringAddress(slot + firstSlot - 1);
+ strncpy(name, ptr, sizeof(name));
+ }
+
+ if (savePreparedSavegame(slot, name))
+ result = 0;
+ else
+ result = 2;
+ } else {
+ result = 2; // failed to save
+ }
+ break;
+ case 0xC0: // test if save exists
+ {
+ Common::InSaveFile *file;
+ bool avail_saves[100];
+
+ listSavegames(avail_saves, ARRAYSIZE(avail_saves));
+ Common::String filename = makeSavegameName(slot, false);
+ if (avail_saves[slot] && (file = _saveFileMan->openForLoading(filename.c_str()))) {
+ result = 6; // save file exists
+ delete file;
+ } else
+ result = 7; // save file does not exist
+ }
+ break;
+ default:
+ error("o4_saveLoadGame: unknown subopcode %d", _opcode);
+ }
+
+ setResult(result);
+}
+
} // End of namespace Scumm