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