1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "scumm/scumm_v4.h"
24 #include "scumm/object.h"
25
26 namespace Scumm {
27
28 #define OPCODE(i, x) _opcodes[i]._OPCODE(ScummEngine_v4, x)
29
setupOpcodes()30 void ScummEngine_v4::setupOpcodes() {
31 ScummEngine_v5::setupOpcodes();
32
33 OPCODE(0x25, o5_drawObject);
34 OPCODE(0x45, o5_drawObject);
35 OPCODE(0x65, o5_drawObject);
36 OPCODE(0xa5, o5_drawObject);
37 OPCODE(0xc5, o5_drawObject);
38 OPCODE(0xe5, o5_drawObject);
39
40 OPCODE(0x50, o4_pickupObject);
41 OPCODE(0xd0, o4_pickupObject);
42
43 OPCODE(0x5c, o4_oldRoomEffect);
44 OPCODE(0xdc, o4_oldRoomEffect);
45
46 OPCODE(0x0f, o4_ifState);
47 OPCODE(0x4f, o4_ifState);
48 OPCODE(0x8f, o4_ifState);
49 OPCODE(0xcf, o4_ifState);
50
51 OPCODE(0x2f, o4_ifNotState);
52 OPCODE(0x6f, o4_ifNotState);
53 OPCODE(0xaf, o4_ifNotState);
54 OPCODE(0xef, o4_ifNotState);
55
56 OPCODE(0xa7, o4_saveLoadVars);
57
58 OPCODE(0x22, o4_saveLoadGame);
59 OPCODE(0xa2, o4_saveLoadGame);
60
61 // Disable some opcodes which are unused in v4.
62 _opcodes[0x3b].setProc(0, 0);
63 _opcodes[0x4c].setProc(0, 0);
64 _opcodes[0xbb].setProc(0, 0);
65 }
66
o4_ifState()67 void ScummEngine_v4::o4_ifState() {
68 int a = getVarOrDirectWord(PARAM_1);
69 int b = getVarOrDirectByte(PARAM_2);
70
71 // WORKAROUND bug #3306145 (also occurs in original): Some old versions of
72 // Indy3 sometimes fail to allocate IQ points correctly. To quote:
73 // "About the points error leaving Castle Brunwald: It seems to "reversed"!
74 // When you get caught, free yourself and escape, you get 25 IQ points even
75 // though you're not supposed to. However if you escape WITHOUT getting
76 // caught, you get 0 IQ points (supposed to get 25 IQ points)."
77 // This workaround is meant to address that.
78 if (_game.id == GID_INDY3 && a == 367 &&
79 vm.slot[_currentScript].number == 363 && _currentRoom == 25) {
80 b = 0;
81 }
82
83 jumpRelative(getState(a) == b);
84 }
85
o4_ifNotState()86 void ScummEngine_v4::o4_ifNotState() {
87 int a = getVarOrDirectWord(PARAM_1);
88 int b = getVarOrDirectByte(PARAM_2);
89
90 jumpRelative(getState(a) != b);
91 }
92
o4_pickupObject()93 void ScummEngine_v4::o4_pickupObject() {
94 int obj = getVarOrDirectWord(PARAM_1);
95
96 if (obj < 1) {
97 error("pickupObjectOld received invalid index %d (script %d)", obj, vm.slot[_currentScript].number);
98 }
99
100 if (getObjectIndex(obj) == -1)
101 return;
102
103 if (whereIsObject(obj) == WIO_INVENTORY) // Don't take an object twice
104 return;
105
106 // debug(0, "adding %d from %d to inventoryOld", obj, _currentRoom);
107 addObjectToInventory(obj, _roomResource);
108 markObjectRectAsDirty(obj);
109 putOwner(obj, VAR(VAR_EGO));
110 putClass(obj, kObjectClassUntouchable, 1);
111 putState(obj, 1);
112 clearDrawObjectQueue();
113 runInventoryScript(1);
114 }
115
o4_oldRoomEffect()116 void ScummEngine_v4::o4_oldRoomEffect() {
117 int a;
118
119 _opcode = fetchScriptByte();
120 if ((_opcode & 0x1F) == 3) {
121 a = getVarOrDirectWord(PARAM_1);
122
123 if (_game.platform == Common::kPlatformFMTowns && _game.version == 3) {
124 if (a == 4) {
125 _textSurface.fillRect(Common::Rect(0, 0, _textSurface.w * _textSurfaceMultiplier, _textSurface.h * _textSurfaceMultiplier), 0);
126 #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
127 if (_townsScreen)
128 _townsScreen->clearLayer(1);
129 #endif
130 return;
131 }
132 }
133
134 if (a) {
135 _switchRoomEffect = (byte)(a & 0xFF);
136 _switchRoomEffect2 = (byte)(a >> 8);
137 } else {
138 fadeIn(_newEffect);
139 }
140 }
141 }
142
o4_saveLoadVars()143 void ScummEngine_v4::o4_saveLoadVars() {
144 if (fetchScriptByte() == 1)
145 saveVars();
146 else
147 loadVars();
148 }
149
150 enum StringIds {
151 // The string IDs used by Indy3 to store the episode resp. series IQ points.
152 // Note that we save the episode IQ points but load the series IQ points,
153 // which matches the original Indy3 save/load code. See also the notes
154 // on Feature Request #1666521.
155 STRINGID_IQ_EPISODE = 7,
156 STRINGID_IQ_SERIES = 9,
157 // The string IDs of the first savegame name, used as an offset to determine
158 // the IDs of all savenames.
159 // Loom is the only game whose savenames start with a different ID.
160 STRINGID_SAVENAME1 = 10,
161 STRINGID_SAVENAME1_LOOM = 9
162 };
163
saveVars()164 void ScummEngine_v4::saveVars() {
165 int a, b;
166
167 while ((_opcode = fetchScriptByte()) != 0) {
168 switch (_opcode & 0x1F) {
169 case 0x01: // write a range of variables
170 getResultPos();
171 a = _resultVarNumber;
172 getResultPos();
173 b = _resultVarNumber;
174 debug(0, "stub saveVars: vars %d -> %d", a, b);
175 break;
176 case 0x02: // write a range of string variables
177 a = getVarOrDirectByte(PARAM_1);
178 b = getVarOrDirectByte(PARAM_2);
179
180 if (a == STRINGID_IQ_EPISODE && b == STRINGID_IQ_EPISODE) {
181 if (_game.id == GID_INDY3) {
182 saveIQPoints();
183 }
184 break;
185 }
186 // FIXME: changing savegame-names not supported
187 break;
188 case 0x03: // open file
189 a = resStrLen(_scriptPointer);
190 strncpy(_saveLoadVarsFilename, (const char *)_scriptPointer, a);
191 _saveLoadVarsFilename[a] = '\0';
192 _scriptPointer += a + 1;
193 break;
194 case 0x04:
195 return;
196 case 0x1F: // close file
197 _saveLoadVarsFilename[0] = '\0';
198 return;
199 }
200 }
201 }
202
loadVars()203 void ScummEngine_v4::loadVars() {
204 int a, b;
205
206 while ((_opcode = fetchScriptByte()) != 0) {
207 switch (_opcode & 0x1F) {
208 case 0x01: // read a range of variables
209 getResultPos();
210 a = _resultVarNumber;
211 getResultPos();
212 b = _resultVarNumber;
213 debug(0, "stub loadVars: vars %d -> %d", a, b);
214 break;
215 case 0x02: // read a range of string variables
216 a = getVarOrDirectByte(PARAM_1);
217 b = getVarOrDirectByte(PARAM_2);
218
219 int slot;
220 int slotSize;
221 byte* slotContent;
222 int savegameId;
223 bool avail_saves[100];
224
225 if (a == STRINGID_IQ_SERIES && b == STRINGID_IQ_SERIES) {
226 // Zak256 loads the IQ script-slot but does not use it -> ignore it
227 if (_game.id == GID_INDY3) {
228 byte *ptr = getResourceAddress(rtString, STRINGID_IQ_SERIES);
229 if (ptr) {
230 int size = getResourceSize(rtString, STRINGID_IQ_SERIES);
231 loadIQPoints(ptr, size);
232 }
233 }
234 break;
235 }
236
237 listSavegames(avail_saves, ARRAYSIZE(avail_saves));
238 for (slot = a; slot <= b; ++slot) {
239 slotSize = getResourceSize(rtString, slot);
240 slotContent = getResourceAddress(rtString, slot);
241
242 // load savegame names
243 savegameId = slot - a + 1;
244 Common::String name;
245 if (avail_saves[savegameId] && getSavegameName(savegameId, name)) {
246 int pos;
247 const char *ptr = name.c_str();
248 // slotContent ends with {'\0','@'} -> max. length = slotSize-2
249 for (pos = 0; pos < slotSize - 2; ++pos) {
250 if (!ptr[pos])
251 break;
252 // replace special characters
253 if (ptr[pos] >= 32 && ptr[pos] <= 122 && ptr[pos] != 64)
254 slotContent[pos] = ptr[pos];
255 else
256 slotContent[pos] = '_';
257 }
258 slotContent[pos] = '\0';
259 } else {
260 slotContent[0] = '\0';
261 }
262 }
263 break;
264 case 0x03: // open file
265 a = resStrLen(_scriptPointer);
266 strncpy(_saveLoadVarsFilename, (const char *)_scriptPointer, a);
267 _saveLoadVarsFilename[a] = '\0';
268 _scriptPointer += a + 1;
269 break;
270 case 0x04:
271 return;
272 case 0x1F: // close file
273 _saveLoadVarsFilename[0] = '\0';
274 return;
275 }
276 }
277 }
278
279 /**
280 * IQ Point calculation for Indy3.
281 * The scripts that perform this task are
282 * - script-9 (save/load dialog initialization, loads room 14),
283 * - room-14-204 (load series IQ string),
284 * - room-14-205 (save series IQ string),
285 * - room-14-206 (calculate series IQ string).
286 * Unfortunately script-9 contains lots of GUI stuff so calling this script
287 * directly is not possible. The other scripts depend on script-9.
288 */
updateIQPoints()289 void ScummEngine_v4::updateIQPoints() {
290 int seriesIQ;
291 // IQString[0..72] corresponds to each puzzle's IQ.
292 // IQString[73] indicates that the IQ-file was loaded successfully and is always 0 when
293 // the IQ is calculated, hence it will be ignored here.
294 const int NUM_PUZZLES = 73;
295 byte seriesIQString[NUM_PUZZLES];
296 byte *episodeIQString;
297 int episodeIQStringSize;
298
299 // load string with IQ points given per puzzle in any savegame
300 // IMPORTANT: the resource string STRINGID_IQ_SERIES is only valid while
301 // the original save/load dialog is executed, so do not use it here.
302 memset(seriesIQString, 0, sizeof(seriesIQString));
303 loadIQPoints(seriesIQString, sizeof(seriesIQString));
304
305 // string with IQ points given per puzzle in current savegame
306 episodeIQString = getResourceAddress(rtString, STRINGID_IQ_EPISODE);
307 if (!episodeIQString)
308 return;
309 episodeIQStringSize = getResourceSize(rtString, STRINGID_IQ_EPISODE);
310 if (episodeIQStringSize < NUM_PUZZLES)
311 return;
312
313 // merge episode and series IQ strings and calculate series IQ
314 seriesIQ = 0;
315 // iterate over puzzles
316 for (int i = 0; i < NUM_PUZZLES; ++i) {
317 byte puzzleIQ = seriesIQString[i];
318 // if puzzle is solved copy points to episode string
319 if (puzzleIQ > 0)
320 episodeIQString[i] = puzzleIQ;
321 // add puzzle's IQ-points to series IQ
322 seriesIQ += episodeIQString[i];
323 }
324 _scummVars[245] = seriesIQ;
325
326 // save series IQ string
327 saveIQPoints();
328 }
329
saveIQPoints()330 void ScummEngine_v4::saveIQPoints() {
331 // save Indy3 IQ-points
332 Common::OutSaveFile *file;
333 Common::String filename = _targetName + ".iq";
334
335 file = _saveFileMan->openForSaving(filename);
336 if (file != NULL) {
337 byte *ptr = getResourceAddress(rtString, STRINGID_IQ_EPISODE);
338 if (ptr) {
339 int size = getResourceSize(rtString, STRINGID_IQ_EPISODE);
340 file->write(ptr, size);
341 }
342 delete file;
343 }
344 }
345
loadIQPoints(byte * ptr,int size)346 void ScummEngine_v4::loadIQPoints(byte *ptr, int size) {
347 // load Indy3 IQ-points
348 Common::InSaveFile *file;
349 Common::String filename = _targetName + ".iq";
350
351 file = _saveFileMan->openForLoading(filename);
352 if (file != NULL) {
353 byte *tmp = (byte *)malloc(size);
354 int nread = file->read(tmp, size);
355 if (nread == size) {
356 memcpy(ptr, tmp, size);
357 }
358 free(tmp);
359 delete file;
360 }
361 }
362
o4_saveLoadGame()363 void ScummEngine_v4::o4_saveLoadGame() {
364 getResultPos();
365 byte slot;
366 byte a = getVarOrDirectByte(PARAM_1);
367 byte result = 0;
368
369 if ((_game.id == GID_MANIAC && _game.version <= 1) || (_game.id == GID_ZAK && _game.platform == Common::kPlatformC64)) {
370 // Convert V0/V1 load/save screen (they support only one savegame per disk)
371 // 1 Load
372 // 2 Save
373 slot = 1;
374 if (a == 1)
375 _opcode = 0x40;
376 else if ((a == 2) || (_game.platform == Common::kPlatformNES))
377 _opcode = 0x80;
378 } else {
379 slot = a & 0x1F;
380 // Slot numbers in older games start with 0, in newer games with 1
381 if (_game.version <= 2)
382 slot++;
383 _opcode = a & 0xE0;
384 }
385
386 switch (_opcode) {
387 case 0x00: // num slots available
388 result = 100;
389 break;
390 case 0x20: // drive
391 if (_game.version <= 3) {
392 // 0 = ???
393 // [1,2] = disk drive [A:,B:]
394 // 3 = hard drive
395 result = 3;
396 } else {
397 // set current drive
398 result = 1;
399 }
400 break;
401 case 0x40: // load
402 if (loadState(slot, false))
403 result = 3; // sucess
404 else
405 result = 5; // failed to load
406 break;
407 case 0x80: // save
408 if (_game.version <= 3) {
409 char name[32];
410 if (_game.version <= 2) {
411 // use generic name
412 sprintf(name, "Game %c", 'A'+slot-1);
413 } else {
414 // use name entered by the user
415 char* ptr;
416 int firstSlot = (_game.id == GID_LOOM) ? STRINGID_SAVENAME1_LOOM : STRINGID_SAVENAME1;
417 ptr = (char *)getStringAddress(slot + firstSlot - 1);
418 Common::strlcpy(name, ptr, sizeof(name));
419 }
420
421 if (savePreparedSavegame(slot, name))
422 result = 0;
423 else
424 result = 2;
425 } else {
426 result = 2; // failed to save
427 }
428 break;
429 case 0xC0: // test if save exists
430 {
431 Common::InSaveFile *file;
432 bool avail_saves[100];
433
434 listSavegames(avail_saves, ARRAYSIZE(avail_saves));
435 Common::String filename = makeSavegameName(slot, false);
436 if (avail_saves[slot] && (file = _saveFileMan->openForLoading(filename))) {
437 result = 6; // save file exists
438 delete file;
439 } else
440 result = 7; // save file does not exist
441 }
442 break;
443 default:
444 error("o4_saveLoadGame: unknown subopcode %d", _opcode);
445 }
446
447 setResult(result);
448 }
449
450 } // End of namespace Scumm
451