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  * MIT License:
22  *
23  * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko
24  *
25  * Permission is hereby granted, free of charge, to any person
26  * obtaining a copy of this software and associated documentation
27  * files (the "Software"), to deal in the Software without
28  * restriction, including without limitation the rights to use,
29  * copy, modify, merge, publish, distribute, sublicense, and/or sell
30  * copies of the Software, and to permit persons to whom the
31  * Software is furnished to do so, subject to the following
32  * conditions:
33  *
34  * The above copyright notice and this permission notice shall be
35  * included in all copies or substantial portions of the Software.
36  *
37  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
38  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
39  * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
40  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
41  * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
42  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
43  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
44  * OTHER DEALINGS IN THE SOFTWARE.
45  *
46  */
47 
48 #include "common/file.h"
49 #include "common/debug.h"
50 #include "common/debug-channels.h"
51 #include "common/config-manager.h"
52 #include "common/savefile.h"
53 #include "common/system.h"
54 #include "common/textconsole.h"
55 #include "common/translation.h"
56 
57 #include "gui/saveload.h"
58 
59 #include "graphics/thumbnail.h"
60 #include "graphics/surface.h"
61 
62 #include "wage/wage.h"
63 #include "wage/world.h"
64 #include "wage/entities.h"
65 
66 #define SAVEGAME_CURRENT_VERSION 1
67 
68 //
69 // Original saves format is supported.
70 // ScummVM adds flags, description and thumbnail
71 // in the end of the file (shouldn't make saves incompatible).
72 //
73 // Version 0 (original/ScummVM):  first ScummVM version
74 //
75 
76 namespace Wage {
77 
78 static const uint32 WAGEflag = MKTAG('W', 'A', 'G', 'E');
79 
80 //TODO: make sure these are calculated right: (we add flag, description, etc)
81 #define VARS_INDEX 0x005E
82 #define SCENES_INDEX 0x0232
83 
84 #define SCENE_SIZE 0x0010
85 #define CHR_SIZE 0x0016
86 #define OBJ_SIZE 0x0010
87 
88 #define GET_HEX_OFFSET(ptr, baseOffset, entrySize) ((ptr) == nullptr ? -1 : ((baseOffset) + (entrySize) * (ptr)->_index))
89 #define GET_HEX_CHR_OFFSET(ptr) GET_HEX_OFFSET((ptr), chrsHexOffset, CHR_SIZE)
90 #define GET_HEX_OBJ_OFFSET(ptr) GET_HEX_OFFSET((ptr), objsHexOffset, OBJ_SIZE)
91 #define GET_HEX_SCENE_OFFSET(ptr) ((ptr) == nullptr ? -1 : \
92 	((ptr) == _world->_storageScene ? 0 : (SCENES_INDEX + getSceneIndex(_world->_player->_currentScene) * SCENE_SIZE)))
93 
getSceneIndex(Scene * scene) const94 int WageEngine::getSceneIndex(Scene *scene) const {
95 	assert(scene);
96 	Common::Array<Scene *> &orderedScenes = _world->_orderedScenes;
97 	for (uint32 i = 0; i < orderedScenes.size(); ++i) {
98 		if (orderedScenes[i] == scene) return i-1;
99 	}
100 
101 	warning("Scene's index not found");
102 	return -1;
103 }
104 
getObjByOffset(int offset,int objBaseOffset) const105 Obj *WageEngine::getObjByOffset(int offset, int objBaseOffset) const {
106 	int objIndex = -1;
107 
108 	if (offset != 0xFFFF) {
109 		objIndex = (offset - objBaseOffset) / CHR_SIZE;
110 	}
111 
112 	if (objIndex >= 0 && (uint)objIndex < _world->_orderedObjs.size()) {
113 		return _world->_orderedObjs[objIndex];
114 	}
115 
116 	return nullptr;
117 }
118 
getChrById(int resId) const119 Chr *WageEngine::getChrById(int resId) const {
120 	Common::Array<Chr *> &orderedChrs = _world->_orderedChrs;
121 	for (uint32 i = 0; i < orderedChrs.size(); ++i) {
122 		if (orderedChrs[i]->_resourceId == resId)
123 			return orderedChrs[i];
124 	}
125 
126 	return nullptr;
127 }
128 
getChrByOffset(int offset,int chrBaseOffset) const129 Chr *WageEngine::getChrByOffset(int offset, int chrBaseOffset) const {
130 	int chrIndex = -1;
131 
132 	if (offset != 0xFFFF) {
133 		chrIndex = (offset - chrBaseOffset) / CHR_SIZE;
134 	}
135 
136 	if (chrIndex >= 0 && (uint)chrIndex < _world->_orderedChrs.size()) {
137 		return _world->_orderedChrs[chrIndex];
138 	}
139 
140 	return nullptr;
141 }
142 
getSceneById(int resId) const143 Scene *WageEngine::getSceneById(int resId) const {
144 	Common::Array<Scene *> &orderedScenes = _world->_orderedScenes;
145 	for (uint32 i = 0; i < orderedScenes.size(); ++i) {
146 		if (orderedScenes[i]->_resourceId == resId)
147 			return orderedScenes[i];
148 	}
149 
150 	return nullptr;
151 }
152 
getSceneByOffset(int offset) const153 Scene *WageEngine::getSceneByOffset(int offset) const {
154 	int sceneIndex = -1;
155 
156 	if (offset != 0xFFFF) {
157 		if (offset == 0)
158 			sceneIndex = 0;
159 		else
160 			sceneIndex = 1 + (offset - SCENES_INDEX) / SCENE_SIZE;
161 	}
162 
163 	if (sceneIndex >= 0 && (uint)sceneIndex < _world->_orderedScenes.size()) {
164 		if (sceneIndex == 0) return _world->_storageScene;
165 		return _world->_orderedScenes[sceneIndex];
166 	}
167 
168 	return nullptr;
169 }
170 
saveGame(const Common::String & fileName,const Common::String & descriptionString)171 int WageEngine::saveGame(const Common::String &fileName, const Common::String &descriptionString) {
172 	Common::OutSaveFile *out;
173 	int result = 0;
174 
175 	debug(9, "WageEngine::saveGame(%s, %s)", fileName.c_str(), descriptionString.c_str());
176 	if (!(out = _saveFileMan->openForSaving(fileName))) {
177 		warning("Can't create file '%s', game not saved", fileName.c_str());
178 		return -1;
179 	} else {
180 		debug(9, "Successfully opened %s for writing", fileName.c_str());
181 	}
182 
183 	// Counters
184 	out->writeSint16LE(_world->_scenes.size()); //numScenes
185 	out->writeSint16LE(_world->_chrs.size()); //numChars
186 	out->writeSint16LE(_world->_objs.size()); //numObjs
187 
188 	// Hex Offsets
189 	int chrsHexOffset = SCENES_INDEX + _world->_scenes.size() * SCENE_SIZE; //chrs start after scenes
190 	int objsHexOffset = chrsHexOffset + _world->_chrs.size() * CHR_SIZE; //objs start after chrs
191 	out->writeSint32LE(chrsHexOffset);
192 	out->writeSint32LE(objsHexOffset);
193 
194 	// Unique 8-byte World Signature
195 	out->writeSint32LE(_world->_signature); //8-byte ints? seriously? (uses 4 bytes in java code too)
196 
197 	Chr *player = _world->_player;
198 	Context &playerContext = player->_context;
199 
200 	// More Counters
201 	out->writeSint32LE(playerContext._visits); //visitNum
202 	out->writeSint32LE(_loopCount); //loopNum
203 	out->writeSint32LE(playerContext._kills); //killNum
204 
205 	// Hex offset to player character
206 	out->writeSint32LE(GET_HEX_CHR_OFFSET(player)); //getPlayerHexOffset() == getHexOffsetForChr(player)
207 
208 	// character in this scene?
209 	out->writeSint32LE(GET_HEX_CHR_OFFSET(_monster)); //getPresCharHexOffset() == getHexOffsetForChr(monster)
210 
211 	// Hex offset to current scene
212 	out->writeSint32LE(GET_HEX_SCENE_OFFSET(player->_currentScene)); //getCurSceneHexOffset()
213 
214 	// wearing a helmet?
215 	out->writeSint32LE(GET_HEX_OBJ_OFFSET(player->_armor[Chr::HEAD_ARMOR])); //helmetIndex
216 
217 	// holding a shield?
218 	out->writeSint32LE(GET_HEX_OBJ_OFFSET(player->_armor[Chr::SHIELD_ARMOR])); //shieldIndex
219 
220 	// wearing chest armor?
221 	out->writeSint32LE(GET_HEX_OBJ_OFFSET(player->_armor[Chr::BODY_ARMOR])); //chestArmIndex
222 
223 	// wearing spiritual armor?
224 	out->writeSint32LE(GET_HEX_OBJ_OFFSET(player->_armor[Chr::MAGIC_ARMOR])); //sprtArmIndex
225 
226 	// TODO:
227 	out->writeUint16LE(0xffff);	// ???? - always FFFF
228 	out->writeUint16LE(0xffff);	// ???? - always FFFF
229 	out->writeUint16LE(0xffff);	// ???? - always FFFF
230 	out->writeUint16LE(0xffff);	// ???? - always FFFF
231 
232 	// did a character just escape?
233 	out->writeSint32LE(GET_HEX_CHR_OFFSET(_running)); //getRunCharHexOffset() == getHexOffsetForChr(running)
234 
235 	// players experience points
236 	out->writeSint32LE(playerContext._experience);
237 
238 	out->writeSint16LE(_aim); //aim
239 	out->writeSint16LE(_opponentAim); //opponentAim
240 
241 	// TODO:
242 	out->writeSint16LE(0x0000);	// always 0
243 	out->writeSint16LE(0x0000);	// always 0
244 	out->writeSint16LE(0x0000);	// always 0
245 
246 	// Base character stats
247 	out->writeByte(playerContext._statVariables[PHYS_STR_BAS]); //state.getBasePhysStr()
248 	out->writeByte(playerContext._statVariables[PHYS_HIT_BAS]); //state.getBasePhysHp()
249 	out->writeByte(playerContext._statVariables[PHYS_ARM_BAS]); //state.getBasePhysArm()
250 	out->writeByte(playerContext._statVariables[PHYS_ACC_BAS]); //state.getBasePhysAcc()
251 	out->writeByte(playerContext._statVariables[SPIR_STR_BAS]); //state.getBaseSprtStr()
252 	out->writeByte(playerContext._statVariables[SPIR_HIT_BAS]); //state.getBaseSprtHp()
253 	out->writeByte(playerContext._statVariables[SPIR_ARM_BAS]); //state.getBaseSprtArm()
254 	out->writeByte(playerContext._statVariables[SPIR_ACC_BAS]); //state.getBaseSprtAcc()
255 	out->writeByte(playerContext._statVariables[PHYS_SPE_BAS]); //state.getBaseRunSpeed()
256 
257 	// TODO:
258 	out->writeByte(0x0A);		// ???? - always seems to be 0x0A
259 
260 	// write user vars
261 	for (uint32 i = 0; i < 26 * 9; ++i)
262 		out->writeSint16LE(playerContext._userVariables[i]);
263 
264 	// write updated info for all scenes
265 	Common::Array<Scene *> &orderedScenes = _world->_orderedScenes;
266 	for (uint32 i = 0; i < orderedScenes.size(); ++i) {
267 		Scene *scene = orderedScenes[i];
268 		if (scene != _world->_storageScene) {
269 			out->writeSint16LE(scene->_resourceId);
270 			out->writeSint16LE(scene->_worldY);
271 			out->writeSint16LE(scene->_worldX);
272 			out->writeByte(scene->_blocked[NORTH] ? 0x01 : 0x00);
273 			out->writeByte(scene->_blocked[SOUTH] ? 0x01 : 0x00);
274 			out->writeByte(scene->_blocked[EAST] ? 0x01 : 0x00);
275 			out->writeByte(scene->_blocked[WEST] ? 0x01 : 0x00);
276 			out->writeSint16LE(scene->_soundFrequency);
277 			out->writeByte(scene->_soundType);
278 			// the following two bytes are currently unknown
279 			out->writeByte(0);
280 			out->writeByte(0);
281 			out->writeByte(scene->_visited ? 0x01 : 0x00);
282 		}
283 	}
284 
285 	// write updated info for all characters
286 	Common::Array<Chr *> &orderedChrs = _world->_orderedChrs;
287 	for (uint32 i = 0; i < orderedChrs.size(); ++i) {
288 		Chr *chr = orderedChrs[i];
289 		out->writeSint16LE(chr->_resourceId);
290 		out->writeSint16LE(chr->_currentScene->_resourceId);
291 		Context &chrContext = chr->_context;
292 		out->writeByte(chrContext._statVariables[PHYS_STR_CUR]);
293 		out->writeByte(chrContext._statVariables[PHYS_HIT_CUR]);
294 		out->writeByte(chrContext._statVariables[PHYS_ARM_CUR]);
295 		out->writeByte(chrContext._statVariables[PHYS_ACC_CUR]);
296 		out->writeByte(chrContext._statVariables[SPIR_STR_CUR]);
297 		out->writeByte(chrContext._statVariables[SPIR_HIT_CUR]);
298 		out->writeByte(chrContext._statVariables[SPIR_ARM_CUR]);
299 		out->writeByte(chrContext._statVariables[SPIR_ACC_CUR]);
300 		out->writeByte(chrContext._statVariables[PHYS_SPE_CUR]);
301 		out->writeByte(chr->_rejectsOffers);
302 		out->writeByte(chr->_followsOpponent);
303 		// bytes 16-20 are unknown
304 		out->writeByte(0);
305 		out->writeByte(0);
306 		out->writeByte(0);
307 		out->writeByte(0);
308 		out->writeByte(0);
309 		out->writeByte(chr->_weaponDamage1);
310 		out->writeByte(chr->_weaponDamage2);
311 	}
312 
313 	// write updated info for all objects
314 	Common::Array<Obj *> &orderedObjs = _world->_orderedObjs;
315 	for (uint32 i = 0; i < orderedObjs.size(); ++i) {
316 		Obj *obj = orderedObjs[i];
317 		Scene *location = obj->_currentScene;
318 		Chr *owner = obj->_currentOwner;
319 
320 		out->writeSint16LE(obj->_resourceId);
321 		out->writeSint16LE(location == nullptr ? 0 : location->_resourceId);
322 		out->writeSint16LE(owner == nullptr ? 0 : owner->_resourceId);
323 
324 		// bytes 7-9 are unknown (always = 0)
325 		out->writeByte(0);
326 		out->writeByte(0);
327 		out->writeByte(0);
328 
329 		out->writeByte(obj->_accuracy);
330 		out->writeByte(obj->_value);
331 		out->writeByte(obj->_type);
332 		out->writeByte(obj->_damage);
333 		out->writeByte(obj->_attackType);
334 		out->writeSint16LE(obj->_numberOfUses);
335 	}
336 
337 	// the following is appended by ScummVM
338 	int32 appendixOffset = out->pos();
339 	if (appendixOffset < 0) {
340 		warning("OutSaveFile::pos() failed");
341 	}
342 	out->writeUint32BE(WAGEflag);
343 
344 	// Write description of saved game, limited to WAGE_SAVEDGAME_DESCRIPTION_LEN characters + terminating NUL
345 	const int WAGE_SAVEDGAME_DESCRIPTION_LEN = 127;
346 	char description[WAGE_SAVEDGAME_DESCRIPTION_LEN + 1];
347 
348 	memset(description, 0, sizeof(description));
349 	strncpy(description, descriptionString.c_str(), WAGE_SAVEDGAME_DESCRIPTION_LEN);
350 	assert(WAGE_SAVEDGAME_DESCRIPTION_LEN + 1 == 128); // safety
351 	out->write(description, 128);
352 
353 	out->writeByte(SAVEGAME_CURRENT_VERSION);
354 	debug(9, "Writing save game version (%d)", SAVEGAME_CURRENT_VERSION);
355 
356 	// Thumbnail
357 	Graphics::saveThumbnail(*out);
358 
359 	out->writeUint32BE(appendixOffset);
360 
361 	// this one to make checking easier:
362 	// it couldn't be added to the beginning
363 	// and we won't be able to find it in the middle,
364 	// so these would be the last 4 bytes of the file
365 	out->writeUint32BE(WAGEflag);
366 
367 	out->finalize();
368 	if (out->err()) {
369 		warning("Can't write file '%s'. (Disk full?)", fileName.c_str());
370 		result = -1;
371 	} else
372 		debug(9, "Saved game %s in file %s", descriptionString.c_str(), fileName.c_str());
373 
374 	delete out;
375 	return result;
376 }
377 
loadGame(int slotId)378 int WageEngine::loadGame(int slotId) {
379 	Common::InSaveFile *data;
380 	Common::String fileName = getSaveStateName(slotId);
381 
382 	debug(9, "WageEngine::loadGame(%d)", slotId);
383 	if (!(data = _saveFileMan->openForLoading(fileName))) {
384 		warning("Can't open file '%s', game not loaded", fileName.c_str());
385 		return -1;
386 	} else {
387 		debug(9, "Successfully opened %s for reading", fileName.c_str());
388 	}
389 
390 	// Counters
391 	int numScenes = data->readSint16LE();
392 	int numChars = data->readSint16LE();
393 	int numObjs = data->readSint16LE();
394 
395 	// Hex Offsets
396 	int chrsHexOffset = data->readSint32LE();
397 	int objsHexOffset = data->readSint32LE();
398 
399 	// Unique 8-byte World Signature
400 	int signature = data->readSint32LE();
401 	if (_world->_signature != signature) {
402 		warning("This saved game is for a different world, please select another one");
403 		warning("World signature = %d, save signature = %d", _world->_signature, signature);
404 		delete data;
405 		return -1;
406 	}
407 
408 	// More Counters
409 	int visitNum = data->readSint32LE(); //visitNum
410 	int loopNum = data->readSint32LE(); //loopNum
411 	int killNum = data->readSint32LE(); //killNum
412 
413 	// Hex offset to player character
414 	int playerOffset = data->readSint32LE();
415 
416 	// character in this scene?
417 	int presCharOffset = data->readSint32LE();
418 
419 	// Hex offset to current scene
420 	int currentSceneOffset = data->readSint32LE();
421 
422 	// find player and current scene
423 	Chr *player = getChrByOffset(playerOffset, chrsHexOffset);
424 	if (player == nullptr) {
425 		warning("Invalid Character!  Aborting load.");
426 		delete data;
427 		return -1;
428 	}
429 
430 	Scene *currentScene = getSceneByOffset(currentSceneOffset);
431 	if (currentScene == nullptr) {
432 		warning("Invalid Scene!  Aborting load.");
433 		delete data;
434 		return -1;
435 	}
436 
437 	// set player character
438 	_world->_player = player;
439 
440 	// set current scene
441 	player->_currentScene = currentScene;
442 
443 	// clear the players inventory list
444 	player->_inventory.clear();
445 
446 	// wearing a helmet?
447 	int helmetOffset = data->readSint32LE(); //helmetIndex
448 
449 	// holding a shield?
450 	int shieldOffset = data->readSint32LE(); //shieldIndex
451 
452 	// wearing chest armor?
453 	int armorOffset = data->readSint32LE(); //chestArmIndex
454 
455 	// wearing spiritual armor?
456 	int spiritualArmorOffset = data->readSint32LE(); //sprtArmIndex
457 
458 	data->readSint16LE();	// FFFF
459 	data->readSint16LE();	// FFFF
460 	data->readSint16LE();	// FFFF
461 	data->readSint16LE();	// FFFF
462 
463 	/* int runCharOffset = */ data->readSint32LE();
464 
465 	// players experience points
466 	int exp = data->readSint32LE(); // @ playerContext._experience
467 
468 	int aim = data->readSint16LE(); //aim
469 	int opponentAim = data->readSint16LE(); //opponentAim
470 
471 	data->readSint16LE(); // 0000
472 	data->readSint16LE(); // 0000
473 	data->readSint16LE(); // 0000
474 
475 	// Base character stats
476 	int basePhysStr = data->readByte();
477 	int basePhysHp = data->readByte();
478 	int basePhysArm = data->readByte();
479 	int basePhysAcc = data->readByte();
480 	int baseSprtStr = data->readByte();
481 	int baseSprtHp = data->readByte();
482 	int baseSprtArm = data->readByte();
483 	int baseSprtAcc = data->readByte();
484 	int baseRunSpeed = data->readByte();
485 
486 	// set player stats
487 	Context &playerContext = player->_context;
488 	// I'm setting player fields also, because those are used as base values in Chr::resetState()
489 	playerContext._statVariables[PHYS_STR_BAS] = player->_physicalStrength = basePhysStr;
490 	playerContext._statVariables[PHYS_HIT_BAS] = player->_physicalHp = basePhysHp;
491 	playerContext._statVariables[PHYS_ARM_BAS] = player->_naturalArmor = basePhysArm;
492 	playerContext._statVariables[PHYS_ACC_BAS] = player->_physicalAccuracy = basePhysAcc;
493 	playerContext._statVariables[SPIR_STR_BAS] = player->_spiritualStength = baseSprtStr;
494 	playerContext._statVariables[SPIR_HIT_BAS] = player->_spiritialHp = baseSprtHp;
495 	playerContext._statVariables[SPIR_ARM_BAS] = player->_resistanceToMagic = baseSprtArm;
496 	playerContext._statVariables[SPIR_ACC_BAS] = player->_spiritualAccuracy = baseSprtAcc;
497 	playerContext._statVariables[PHYS_SPE_BAS] = player->_runningSpeed = baseRunSpeed;
498 
499 	// set visit#
500 	playerContext._visits = visitNum;
501 
502 	// set monsters killed
503 	playerContext._kills = killNum;
504 
505 	// set experience
506 	playerContext._experience = exp;
507 
508 	// if a character is present, move it to this scene
509 	// TODO: This is done in the engine object, would it be cleaner
510 	// to move it here?
511 	// well, it's actually down there now, now sure if that's "cleaner"
512 	// when it's up there or down there
513 
514 	// if a character just ran away, let our engine know
515 	// TODO: The current engine doesn't have a case for this, we
516 	// should update it
517 	// yep, I don't see such code anywhere in java, so not added it here
518 
519 	data->readByte(); // 0x0A?
520 
521 	// set all user variables
522 	for (uint32 i = 0; i < 26 * 9; ++i) {
523 		playerContext._userVariables[i] = data->readSint16LE();
524 	}
525 
526 	// update all scene stats
527 	Common::Array<Scene *> &orderedScenes = _world->_orderedScenes;
528 	if ((uint)numScenes != orderedScenes.size()) {
529 		warning("scenes number in file (%d) differs from the one in world (%d)", numScenes, orderedScenes.size());
530 	}
531 	for (uint32 i = 0; i < orderedScenes.size(); ++i) {
532 		Scene *scene = orderedScenes[i];
533 		if (scene == _world->_storageScene) {
534 			scene->_chrs.clear();
535 			scene->_objs.clear();
536 		} else {
537 			int id = data->readSint16LE();
538 
539 			if (scene->_resourceId != id) {
540 				warning("loadGame(): updating scenes: expected %d but got %d", scene->_resourceId, id);
541 				data->skip(14); //2,2,1,1,1,1,2,1,1,1,1 down there
542 				continue;
543 			}
544 
545 			scene->_worldY = data->readSint16LE();
546 			scene->_worldX = data->readSint16LE();
547 			scene->_blocked[NORTH] = data->readByte() != 0;
548 			scene->_blocked[SOUTH] = data->readByte() != 0;
549 			scene->_blocked[EAST] = data->readByte() != 0;
550 			scene->_blocked[WEST] = data->readByte() != 0;
551 			scene->_soundFrequency = data->readSint16LE();
552 			scene->_soundType = data->readByte();
553 			// the following two bytes are currently unknown
554 			data->readByte();
555 			data->readByte();
556 			scene->_visited = data->readByte() != 0;
557 		}
558 	}
559 
560 	// update all char locations and stats
561 	Common::Array<Chr *> &orderedChrs = _world->_orderedChrs;
562 	if ((uint)numChars != orderedChrs.size()) {
563 		warning("characters number in file (%d) differs from the one in world (%d)", numChars, orderedChrs.size());
564 	}
565 	for (uint32 i = 0; i < orderedChrs.size(); ++i) {
566 		int resourceId = data->readSint16LE();
567 		int sceneResourceId = data->readSint16LE();
568 
569 		int strength = data->readByte();
570 		int hp = data->readByte();
571 		int armor = data->readByte();
572 		int accuracy = data->readByte();
573 		int spirStrength = data->readByte();
574 		int spirHp = data->readByte();
575 		int spirArmor = data->readByte();
576 		int spirAccuracy = data->readByte();
577 		int speed = data->readByte();
578 		int rejectsOffers = data->readByte();
579 		int followsOpponent = data->readByte();
580 
581 		// bytes 16-20 are unknown
582 		data->readByte();
583 		data->readByte();
584 		data->readByte();
585 		data->readByte();
586 		data->readByte();
587 
588 		int weaponDamage1 = data->readByte();
589 		int weaponDamage2 = data->readByte();
590 
591 		Chr *chr = orderedChrs[i];
592 		if (chr->_resourceId != resourceId) {
593 			warning("loadGame(): updating chrs: expected %d but got %d", chr->_resourceId, resourceId);
594 			continue;
595 		}
596 
597 		chr->_currentScene = getSceneById(sceneResourceId);
598 		Context &chrContext = chr->_context;
599 		chrContext._statVariables[PHYS_STR_CUR] = strength;
600 		chrContext._statVariables[PHYS_HIT_CUR] = hp;
601 		chrContext._statVariables[PHYS_ARM_CUR] = armor;
602 		chrContext._statVariables[PHYS_ACC_CUR] = accuracy;
603 		chrContext._statVariables[SPIR_STR_CUR] = spirStrength;
604 		chrContext._statVariables[SPIR_HIT_CUR] = spirHp;
605 		chrContext._statVariables[SPIR_ARM_CUR] = spirArmor;
606 		chrContext._statVariables[SPIR_ACC_CUR] = spirAccuracy;
607 		chrContext._statVariables[PHYS_SPE_CUR] = speed;
608 		chr->_rejectsOffers = rejectsOffers;
609 		chr->_followsOpponent = followsOpponent;
610 		chr->_weaponDamage1 = weaponDamage1;
611 		chr->_weaponDamage2 = weaponDamage2;
612 	}
613 
614 	// update all object locations and stats
615 	Common::Array<Obj *> &orderedObjs = _world->_orderedObjs;
616 	if ((uint)numObjs != orderedObjs.size()) {
617 		warning("objects number in file (%d) differs from the one in world (%d)", numObjs, orderedObjs.size());
618 	}
619 	for (uint32 i = 0; i < orderedObjs.size(); ++i) {
620 		int resourceId = data->readSint16LE();
621 		int locationResourceId = data->readSint16LE();
622 		int ownerResourceId = data->readSint16LE();
623 
624 		// bytes 7-9 are unknown (always = 0)
625 		data->readByte();
626 		data->readByte();
627 		data->readByte();
628 
629 		int accuracy = data->readByte();
630 		int value = data->readByte();
631 		int type = data->readByte();
632 		int damage = data->readByte();
633 		int attackType= data->readByte();
634 		int numberOfUses = data->readSint16LE();
635 
636 		Obj *obj = orderedObjs[i];
637 		if (obj->_resourceId != resourceId) {
638 			warning("loadGame(): updating objs: expected %d but got %d", obj->_resourceId, resourceId);
639 			continue;
640 		}
641 
642 		if (ownerResourceId != 0) {
643 			obj->setCurrentOwner(getChrById(ownerResourceId));
644 			if (obj->_currentOwner == nullptr)
645 				warning("loadGame(): updating objs: owner not found - char with id %d", ownerResourceId);
646 		} else {
647 			obj->setCurrentScene(getSceneById(locationResourceId));
648 			if (obj->_currentScene == nullptr)
649 				warning("loadGame(): updating objs: scene with id %d not found", ownerResourceId);
650 		}
651 
652 		obj->_accuracy = accuracy;
653 		obj->_value = value;
654 		obj->_type = type;
655 		obj->_damage = damage;
656 		obj->_attackType = attackType;
657 		obj->_numberOfUses = numberOfUses;
658 	}
659 
660 	// update inventories and scene contents
661 	for (uint32 i = 0; i < orderedObjs.size(); ++i) {
662 		Obj *obj = orderedObjs[i];
663 		Chr *chr = obj->_currentOwner;
664 		if (chr != nullptr) {
665 			chr->_inventory.push_back(obj);
666 		} else {
667 			Scene *scene = obj->_currentScene;
668 			scene->_objs.push_back(obj);
669 		}
670 	}
671 
672 	// update scene chrs
673 	for (uint32 i = 0; i < orderedChrs.size(); ++i) {
674 		Chr *chr = orderedChrs[i];
675 		Scene *scene = chr->_currentScene;
676 		scene->_chrs.push_back(chr);
677 		if (chr != player) {
678 			wearObjs(chr);
679 		}
680 	}
681 
682 	// move all worn helmets, shields, chest armors and spiritual
683 	// armors to player
684 	for (int type = 0; type < Chr::NUMBER_OF_ARMOR_TYPES; ++type) {
685 		Obj *armor;
686 
687 		if (type == Chr::HEAD_ARMOR)
688 			armor = getObjByOffset(helmetOffset, objsHexOffset);
689 		else if (type == Chr::SHIELD_ARMOR)
690 			armor = getObjByOffset(shieldOffset, objsHexOffset);
691 		else if (type == Chr::BODY_ARMOR)
692 			armor = getObjByOffset(armorOffset, objsHexOffset);
693 		else
694 			armor = getObjByOffset(spiritualArmorOffset, objsHexOffset);
695 
696 		if (armor != nullptr) {
697 			_world->move(armor, player);
698 			player->_armor[type] = armor;
699 		}
700 	}
701 
702 	//TODO: make sure that armor in the inventory gets put on if we are wearing it
703 
704 	_loopCount = loopNum;
705 
706 	// let the engine know if there is a npc in the current scene
707 	if (presCharOffset != 0xffff) {
708 		_monster = getChrByOffset(presCharOffset, chrsHexOffset);
709 	}
710 
711 	// java engine calls clearOutput(); here
712 	// processTurn("look", NULL); called in Wage right after this loadGame()
713 
714 	// TODO: as you may see, aim, opponentAim or runCharOffset are not used anywhere
715 	// I'm fixing the first two, as those are clearly not even mentioned anywhere
716 	// the runCharOffset is mentioned up there as "not implemented case"
717 	_aim = aim;
718 	_opponentAim = opponentAim;
719 
720 	delete data;
721 	return 0;
722 }
723 
loadGameState(int slot)724 Common::Error WageEngine::loadGameState(int slot) {
725 	if (loadGame(slot) == 0)
726 		return Common::kNoError;
727 	else
728 		return Common::kUnknownError;
729 }
730 
saveGameState(int slot,const Common::String & description,bool isAutosave)731 Common::Error WageEngine::saveGameState(int slot, const Common::String &description, bool isAutosave) {
732 	Common::String saveLoadSlot = getSaveStateName(slot);
733 	if (saveGame(saveLoadSlot, description) == 0)
734 		return Common::kNoError;
735 	else
736 		return Common::kUnknownError;
737 }
738 
scummVMSaveLoadDialog(bool isSave)739 bool WageEngine::scummVMSaveLoadDialog(bool isSave) {
740 	if (!isSave) {
741 		// do loading
742 		GUI::SaveLoadChooser dialog = GUI::SaveLoadChooser(_("Load game:"), _("Load"), false);
743 		int slot = dialog.runModalWithCurrentTarget();
744 
745 		if (slot < 0)
746 			return true;
747 
748 		return loadGameState(slot).getCode() == Common::kNoError;
749 	}
750 
751 	// do saving
752 	GUI::SaveLoadChooser dialog = GUI::SaveLoadChooser(_("Save game:"), _("Save"), true);
753 	int slot = dialog.runModalWithCurrentTarget();
754 	Common::String desc = dialog.getResultString();
755 
756 	if (desc.empty()) {
757 		// create our own description for the saved game, the user didnt enter it
758 		desc = dialog.createDefaultSaveDescription(slot);
759 	}
760 
761 	if (desc.size() > 28)
762 		desc = Common::String(desc.c_str(), 28);
763 
764 	if (slot < 0)
765 		return true;
766 
767 	return saveGameState(slot, desc).getCode() == Common::kNoError;
768 }
769 
770 } // End of namespace Agi
771