diff options
-rw-r--r-- | engines/wage/entities.cpp | 10 | ||||
-rw-r--r-- | engines/wage/entities.h | 6 | ||||
-rw-r--r-- | engines/wage/module.mk | 1 | ||||
-rw-r--r-- | engines/wage/saveload.cpp | 732 | ||||
-rw-r--r-- | engines/wage/wage.cpp | 14 | ||||
-rw-r--r-- | engines/wage/wage.h | 15 | ||||
-rw-r--r-- | engines/wage/world.cpp | 10 | ||||
-rw-r--r-- | engines/wage/world.h | 3 |
8 files changed, 786 insertions, 5 deletions
diff --git a/engines/wage/entities.cpp b/engines/wage/entities.cpp index 43ac6c8cc7..b2babbab4d 100644 --- a/engines/wage/entities.cpp +++ b/engines/wage/entities.cpp @@ -80,6 +80,8 @@ Context::Context() { } Scene::Scene() { + _resourceId = 0; + _script = NULL; _design = NULL; _textBounds = NULL; @@ -104,6 +106,8 @@ Scene::Scene(Common::String name, Common::SeekableReadStream *data) { _classType = SCENE; _design = new Design(data); + _resourceId = 0; + _script = NULL; _textBounds = NULL; _fontSize = 0; @@ -221,6 +225,7 @@ Designed *Scene::lookUpEntity(int x, int y) { Obj::Obj() : _currentOwner(NULL), _currentScene(NULL) { _index = 0; + _resourceId = 0; _namePlural = false; _value = 0; _attackType = 0; @@ -231,7 +236,9 @@ Obj::Obj() : _currentOwner(NULL), _currentScene(NULL) { _damage = 0; } -Obj::Obj(Common::String name, Common::SeekableReadStream *data) { +Obj::Obj(Common::String name, Common::SeekableReadStream *data, int resourceId) { + _resourceId = resourceId; + _name = name; _classType = OBJ; _currentOwner = NULL; @@ -322,6 +329,7 @@ Chr::Chr(Common::String name, Common::SeekableReadStream *data) { _design = new Design(data); _index = 0; + _resourceId = 0; _currentScene = NULL; setDesignBounds(readRect(data)); diff --git a/engines/wage/entities.h b/engines/wage/entities.h index 9e706f0d58..f327270254 100644 --- a/engines/wage/entities.h +++ b/engines/wage/entities.h @@ -152,6 +152,7 @@ public: Chr(Common::String name, Common::SeekableReadStream *data); int _index; + int _resourceId; Common::String _initialScene; int _gender; bool _nameProperNoun; @@ -229,7 +230,7 @@ public: class Obj : public Designed { public: Obj(); - Obj(Common::String name, Common::SeekableReadStream *data); + Obj(Common::String name, Common::SeekableReadStream *data, int resourceId); ~Obj(); enum ObjectType { @@ -256,6 +257,7 @@ public: public: int _index; + int _resourceId; bool _namePlural; uint _value; int _attackType; @@ -301,6 +303,8 @@ public: RANDOM = 1 }; + int _resourceId; + Script *_script; Common::String _text; Common::Rect *_textBounds; diff --git a/engines/wage/module.mk b/engines/wage/module.mk index e150d5f27e..1f397b5a71 100644 --- a/engines/wage/module.mk +++ b/engines/wage/module.mk @@ -13,6 +13,7 @@ MODULE_OBJS := \ macwindow.o \ macwindowmanager.o \ randomhat.o \ + saveload.o \ script.o \ sound.o \ util.o \ diff --git a/engines/wage/saveload.cpp b/engines/wage/saveload.cpp new file mode 100644 index 0000000000..4889add4f0 --- /dev/null +++ b/engines/wage/saveload.cpp @@ -0,0 +1,732 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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. + * + * MIT License: + * + * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "common/file.h" +#include "common/debug.h" +#include "common/debug-channels.h" +#include "common/config-manager.h" +#include "common/savefile.h" +#include "common/system.h" +#include "common/textconsole.h" +#include "common/translation.h" + +#include "gui/saveload.h" + +#include "graphics/thumbnail.h" +#include "graphics/surface.h" + +#include "wage/wage.h" +#include "wage/world.h" +#include "wage/entities.h" + +#define SAVEGAME_CURRENT_VERSION 1 + +// +// Original saves format is supported. +// ScummVM adds flags, description and thumbnail +// in the end of the file (shouldn't make saves incompatible). +// +// Version 0 (original/ScummVM): first ScummVM version +// + +namespace Wage { + +static const uint32 WAGEflag = MKTAG('W', 'A', 'G', 'E'); + +//TODO: make sure these are calculated right: (we add flag, description, etc) +#define VARS_INDEX 0x005E +#define SCENES_INDEX 0x0232 + +#define SCENE_SIZE 0x0010 +#define CHR_SIZE 0x0016 +#define OBJ_SIZE 0x0010 + +#define GET_HEX_OFFSET(ptr, baseOffset, entrySize) ((ptr) == nullptr ? -1 : ((baseOffset) + (entrySize) * (ptr)->_index)) +#define GET_HEX_CHR_OFFSET(ptr) GET_HEX_OFFSET((ptr), chrsHexOffset, CHR_SIZE) +#define GET_HEX_OBJ_OFFSET(ptr) GET_HEX_OFFSET((ptr), objsHexOffset, OBJ_SIZE) +#define GET_HEX_SCENE_OFFSET(ptr) ((ptr) == nullptr ? -1 : \ + ((ptr) == _world->_storageScene ? 0 : (SCENES_INDEX + getSceneIndex(_world->_player->_currentScene) * SCENE_SIZE))) + +int WageEngine::getSceneIndex(Scene *scene) const { + assert(scene); + Common::Array<Scene *> &orderedScenes = _world->_orderedScenes; + for (uint32 i = 0; i < orderedScenes.size(); ++i) { + if (orderedScenes[i] == scene) return i-1; + } + + warning("Scene's index not found"); + return -1; +} + +Obj *WageEngine::getObjByOffset(int offset, int objBaseOffset) const { + int objIndex = -1; + + if (offset != 0xFFFF) { + objIndex = (offset - objBaseOffset) / CHR_SIZE; + } + + if (objIndex >= 0 && objIndex < _world->_orderedObjs.size()) { + return _world->_orderedObjs[objIndex]; + } + + return nullptr; +} + +Chr *WageEngine::getChrById(int resId) const { + Common::Array<Chr *> &orderedChrs = _world->_orderedChrs; + for (uint32 i = 0; i < orderedChrs.size(); ++i) { + if (orderedChrs[i]->_resourceId == resId) + return orderedChrs[i]; + } + + return nullptr; +} + +Chr *WageEngine::getChrByOffset(int offset, int chrBaseOffset) const { + int chrIndex = -1; + + if (offset != 0xFFFF) { + chrIndex = (offset - chrBaseOffset) / CHR_SIZE; + } + + if (chrIndex >= 0 && chrIndex < _world->_orderedChrs.size()) { + return _world->_orderedChrs[chrIndex]; + } + + return nullptr; +} + +Scene *WageEngine::getSceneById(int resId) const { + Common::Array<Scene *> &orderedScenes = _world->_orderedScenes; + for (uint32 i = 0; i < orderedScenes.size(); ++i) { + if (orderedScenes[i]->_resourceId == resId) + return orderedScenes[i]; + } + + return nullptr; +} + +Scene *WageEngine::getSceneByOffset(int offset) const { + int sceneIndex = -1; + + if (offset != 0xFFFF) { + if (offset == 0) + sceneIndex = 0; + else + sceneIndex = 1 + (offset - SCENES_INDEX) / SCENE_SIZE; + } + + if (sceneIndex >= 0 && sceneIndex < _world->_orderedScenes.size()) { + if (sceneIndex == 0) return _world->_storageScene; + return _world->_orderedScenes[sceneIndex]; + } + + return nullptr; +} + +int WageEngine::saveGame(const Common::String &fileName, const Common::String &descriptionString) { + Common::OutSaveFile *out; + int result = 0; + + debug(9, "WageEngine::saveGame(%s, %s)", fileName.c_str(), descriptionString.c_str()); + if (!(out = _saveFileMan->openForSaving(fileName))) { + warning("Can't create file '%s', game not saved", fileName.c_str()); + return -1; + } else { + debug(9, "Successfully opened %s for writing", fileName.c_str()); + } + + // Counters + out->writeSint16LE(_world->_scenes.size()); //numScenes + out->writeSint16LE(_world->_chrs.size()); //numChars + out->writeSint16LE(_world->_objs.size()); //numObjs + + // Hex Offsets + int chrsHexOffset = SCENES_INDEX + _world->_scenes.size() * SCENE_SIZE; //chrs start after scenes + int objsHexOffset = chrsHexOffset + _world->_chrs.size() * CHR_SIZE; //objs start after chrs + out->writeSint32LE(chrsHexOffset); + out->writeSint32LE(objsHexOffset); + + // Unique 8-byte World Signature + out->writeSint32LE(_world->_signature); //8-byte ints? seriously? (uses 4 bytes in java code too) + + Chr *player = _world->_player; + Context &playerContext = player->_context; + + // More Counters + out->writeSint32LE(playerContext._visits); //visitNum + out->writeSint32LE(_loopCount); //loopNum + out->writeSint32LE(playerContext._kills); //killNum + + // Hex offset to player character + out->writeSint32LE(GET_HEX_CHR_OFFSET(player)); //getPlayerHexOffset() == getHexOffsetForChr(player) + + // character in this scene? + out->writeSint32LE(GET_HEX_CHR_OFFSET(_monster)); //getPresCharHexOffset() == getHexOffsetForChr(monster) + + // Hex offset to current scene + out->writeSint32LE(GET_HEX_SCENE_OFFSET(player->_currentScene)); //getCurSceneHexOffset() + + // wearing a helmet? + out->writeSint32LE(GET_HEX_OBJ_OFFSET(player->_armor[Chr::HEAD_ARMOR])); //helmetIndex + + // holding a shield? + out->writeSint32LE(GET_HEX_OBJ_OFFSET(player->_armor[Chr::SHIELD_ARMOR])); //shieldIndex + + // wearing chest armor? + out->writeSint32LE(GET_HEX_OBJ_OFFSET(player->_armor[Chr::BODY_ARMOR])); //chestArmIndex + + // wearing spiritual armor? + out->writeSint32LE(GET_HEX_OBJ_OFFSET(player->_armor[Chr::MAGIC_ARMOR])); //sprtArmIndex + + // TODO: + out->writeSint16LE(0xffff); // ???? - always FFFF + out->writeSint16LE(0xffff); // ???? - always FFFF + out->writeSint16LE(0xffff); // ???? - always FFFF + out->writeSint16LE(0xffff); // ???? - always FFFF + + // did a character just escape? + out->writeSint32LE(GET_HEX_CHR_OFFSET(_running)); //getRunCharHexOffset() == getHexOffsetForChr(running) + + // players experience points + out->writeSint32LE(playerContext._experience); + + out->writeSint16LE(_aim); //aim + out->writeSint16LE(_opponentAim); //opponentAim + + // TODO: + out->writeSint16LE(0x0000); // always 0 + out->writeSint16LE(0x0000); // always 0 + out->writeSint16LE(0x0000); // always 0 + + // Base character stats + out->writeByte(playerContext._statVariables[PHYS_STR_BAS]); //state.getBasePhysStr() + out->writeByte(playerContext._statVariables[PHYS_HIT_BAS]); //state.getBasePhysHp() + out->writeByte(playerContext._statVariables[PHYS_ARM_BAS]); //state.getBasePhysArm() + out->writeByte(playerContext._statVariables[PHYS_ACC_BAS]); //state.getBasePhysAcc() + out->writeByte(playerContext._statVariables[SPIR_STR_BAS]); //state.getBaseSprtStr() + out->writeByte(playerContext._statVariables[SPIR_HIT_BAS]); //state.getBaseSprtHp() + out->writeByte(playerContext._statVariables[SPIR_ARM_BAS]); //state.getBaseSprtArm() + out->writeByte(playerContext._statVariables[SPIR_ACC_BAS]); //state.getBaseSprtAcc() + out->writeByte(playerContext._statVariables[PHYS_SPE_BAS]); //state.getBaseRunSpeed() + + // TODO: + out->writeByte(0x0A); // ???? - always seems to be 0x0A + + // write user vars + for (uint32 i = 0; i < 26 * 9; ++i) + out->writeSint16LE(playerContext._userVariables[i]); + + // write updated info for all scenes + Common::Array<Scene *> &orderedScenes = _world->_orderedScenes; + for (uint32 i = 0; i < orderedScenes.size(); ++i) { + Scene *scene = orderedScenes[i]; + if (scene != _world->_storageScene) { + out->writeSint16LE(scene->_resourceId); + out->writeSint16LE(scene->_worldY); + out->writeSint16LE(scene->_worldX); + out->writeByte(scene->_blocked[NORTH] ? 0x01 : 0x00); + out->writeByte(scene->_blocked[SOUTH] ? 0x01 : 0x00); + out->writeByte(scene->_blocked[EAST] ? 0x01 : 0x00); + out->writeByte(scene->_blocked[WEST] ? 0x01 : 0x00); + out->writeSint16LE(scene->_soundFrequency); + out->writeByte(scene->_soundType); + // the following two bytes are currently unknown + out->writeByte(0); + out->writeByte(0); + out->writeByte(scene->_visited ? 0x01 : 0x00); + } + } + + // write updated info for all characters + Common::Array<Chr *> &orderedChrs = _world->_orderedChrs; + for (uint32 i = 0; i < orderedChrs.size(); ++i) { + Chr *chr = orderedChrs[i]; + out->writeSint16LE(chr->_resourceId); + out->writeSint16LE(chr->_currentScene->_resourceId); + Context &chrContext = chr->_context; + out->writeByte(chrContext._statVariables[PHYS_STR_CUR]); + out->writeByte(chrContext._statVariables[PHYS_HIT_CUR]); + out->writeByte(chrContext._statVariables[PHYS_ARM_CUR]); + out->writeByte(chrContext._statVariables[PHYS_ACC_CUR]); + out->writeByte(chrContext._statVariables[SPIR_STR_CUR]); + out->writeByte(chrContext._statVariables[SPIR_HIT_CUR]); + out->writeByte(chrContext._statVariables[SPIR_ARM_CUR]); + out->writeByte(chrContext._statVariables[SPIR_ACC_CUR]); + out->writeByte(chrContext._statVariables[PHYS_SPE_CUR]); + out->writeByte(chr->_rejectsOffers); + out->writeByte(chr->_followsOpponent); + // bytes 16-20 are unknown + out->writeByte(0); + out->writeByte(0); + out->writeByte(0); + out->writeByte(0); + out->writeByte(0); + out->writeByte(chr->_weaponDamage1); + out->writeByte(chr->_weaponDamage2); + } + + // write updated info for all objects + Common::Array<Obj *> &orderedObjs = _world->_orderedObjs; + for (uint32 i = 0; i < orderedObjs.size(); ++i) { + Obj *obj = orderedObjs[i]; + Scene *location = obj->_currentScene; + Chr *owner = obj->_currentOwner; + + out->writeSint16LE(obj->_resourceId); + out->writeSint16LE(location == nullptr ? 0 : location->_resourceId); + out->writeSint16LE(owner == nullptr ? 0 : owner->_resourceId); + + // bytes 7-9 are unknown (always = 0) + out->writeByte(0); + out->writeByte(0); + out->writeByte(0); + + out->writeByte(obj->_accuracy); + out->writeByte(obj->_value); + out->writeByte(obj->_type); + out->writeByte(obj->_damage); + out->writeByte(obj->_attackType); + out->writeSint16LE(obj->_numberOfUses); + } + + // the following is appended by ScummVM + out->writeUint32BE(WAGEflag); + + // Write description of saved game, limited to WAGE_SAVEDGAME_DESCRIPTION_LEN characters + terminating NUL + const int WAGE_SAVEDGAME_DESCRIPTION_LEN = 127; + char description[WAGE_SAVEDGAME_DESCRIPTION_LEN + 1]; + + memset(description, 0, sizeof(description)); + strncpy(description, descriptionString.c_str(), WAGE_SAVEDGAME_DESCRIPTION_LEN); + assert(WAGE_SAVEDGAME_DESCRIPTION_LEN + 1 == 128); // safety + out->write(description, 128); + + out->writeByte(SAVEGAME_CURRENT_VERSION); + debug(9, "Writing save game version (%d)", SAVEGAME_CURRENT_VERSION); + + // Thumbnail + Graphics::saveThumbnail(*out); + + // this one to make checking easier: + // it couldn't be added to the beginning + // and we won't be able to find it in the middle, + // so these would be the last 4 bytes of the file + out->writeUint32BE(WAGEflag); + + out->finalize(); + if (out->err()) { + warning("Can't write file '%s'. (Disk full?)", fileName.c_str()); + result = -1; + } else + debug(9, "Saved game %s in file %s", descriptionString.c_str(), fileName.c_str()); + + delete out; + return result; +} + +int WageEngine::loadGame(int slotId) { + Common::InSaveFile *data; + Common::String fileName = getSavegameFilename(slotId); + + debug(9, "WageEngine::loadGame(%d)", slotId); + if (!(data = _saveFileMan->openForLoading(fileName))) { + warning("Can't open file '%s', game not loaded", fileName.c_str()); + return -1; + } else { + debug(9, "Successfully opened %s for reading", fileName.c_str()); + } + + // Counters + int numScenes = data->readSint16LE(); + int numChars = data->readSint16LE(); + int numObjs = data->readSint16LE(); + + // Hex Offsets + int chrsHexOffset = data->readSint32LE(); + int objsHexOffset = data->readSint32LE(); + + // Unique 8-byte World Signature + int signature = data->readSint32LE(); + if (_world->_signature != signature) { + warning("This saved game is for a different world, please select another one"); + warning("World signature = %d, save signature = %d", _world->_signature, signature); + delete data; + return -1; + } + + // More Counters + int visitNum = data->readSint32LE(); //visitNum + int loopNum = data->readSint32LE(); //loopNum + int killNum = data->readSint32LE(); //killNum + + // Hex offset to player character + int playerOffset = data->readSint32LE(); + + // character in this scene? + int presCharOffset = data->readSint32LE(); + + // Hex offset to current scene + int currentSceneOffset = data->readSint32LE(); + + // find player and current scene + Chr *player = getChrByOffset(playerOffset, chrsHexOffset); + if (player == nullptr) { + warning("Invalid Character! Aborting load."); + delete data; + return -1; + } + + Scene *currentScene = getSceneByOffset(currentSceneOffset); + if (currentScene == nullptr) { + warning("Invalid Scene! Aborting load."); + delete data; + return -1; + } + + // set player character + _world->_player = player; + + // set current scene + player->_currentScene = currentScene; + + // clear the players inventory list + player->_inventory.clear(); + + // wearing a helmet? + int helmetOffset = data->readSint32LE(); //helmetIndex + + // holding a shield? + int shieldOffset = data->readSint32LE(); //shieldIndex + + // wearing chest armor? + int armorOffset = data->readSint32LE(); //chestArmIndex + + // wearing spiritual armor? + int spiritualArmorOffset = data->readSint32LE(); //sprtArmIndex + + data->readSint16LE(); // FFFF + data->readSint16LE(); // FFFF + data->readSint16LE(); // FFFF + data->readSint16LE(); // FFFF + + int runCharOffset = data->readSint32LE(); + + // players experience points + int exp = data->readSint32LE(); // @ playerContext._experience + + int aim = data->readSint16LE(); //aim + int opponentAim = data->readSint16LE(); //opponentAim + + data->readSint16LE(); // 0000 + data->readSint16LE(); // 0000 + data->readSint16LE(); // 0000 + + // Base character stats + int basePhysStr = data->readByte(); + int basePhysHp = data->readByte(); + int basePhysArm = data->readByte(); + int basePhysAcc = data->readByte(); + int baseSprtStr = data->readByte(); + int baseSprtHp = data->readByte(); + int baseSprtArm = data->readByte(); + int baseSprtAcc = data->readByte(); + int baseRunSpeed = data->readByte(); + + // set player stats + Context &playerContext = player->_context; + // I'm setting player fields also, because those are used as base values in Chr::resetState() + playerContext._statVariables[PHYS_STR_BAS] = player->_physicalStrength = basePhysStr; + playerContext._statVariables[PHYS_HIT_BAS] = player->_physicalHp = basePhysHp; + playerContext._statVariables[PHYS_ARM_BAS] = player->_naturalArmor = basePhysArm; + playerContext._statVariables[PHYS_ACC_BAS] = player->_physicalAccuracy = basePhysAcc; + playerContext._statVariables[SPIR_STR_BAS] = player->_spiritualStength = baseSprtStr; + playerContext._statVariables[SPIR_HIT_BAS] = player->_spiritialHp = baseSprtHp; + playerContext._statVariables[SPIR_ARM_BAS] = player->_resistanceToMagic = baseSprtArm; + playerContext._statVariables[SPIR_ACC_BAS] = player->_spiritualAccuracy = baseSprtAcc; + playerContext._statVariables[PHYS_SPE_BAS] = player->_runningSpeed = baseRunSpeed; + + // set visit# + playerContext._visits = visitNum; + + // set monsters killed + playerContext._kills = killNum; + + // set experience + playerContext._experience = exp; + + // if a character is present, move it to this scene + // TODO: This is done in the engine object, would it be cleaner + // to move it here? + // well, it's actually down there now, now sure if that's "cleaner" + // when it's up there or down there + + // if a character just ran away, let our engine know + // TODO: The current engine doesn't have a case for this, we + // should update it + // yep, I don't see such code anywhere in java, so not added it here + + data->readByte(); // 0x0A? + + // set all user variables + for (uint32 i = 0; i < 26 * 9; ++i) { + playerContext._userVariables[i] = data->readSint16LE(); + } + + // update all scene stats + Common::Array<Scene *> &orderedScenes = _world->_orderedScenes; + if (numScenes != orderedScenes.size()) { + warning("scenes number in file (%d) differs from the one in world (%d)", numScenes, orderedScenes.size()); + } + for (uint32 i = 0; i < orderedScenes.size(); ++i) { + Scene *scene = orderedScenes[i]; + if (scene == _world->_storageScene) { + scene->_chrs.clear(); + scene->_objs.clear(); + } else { + int id = data->readSint16LE(); + + if (scene->_resourceId != id) { + warning("loadGame(): updating scenes: expected %d but got %d", scene->_resourceId, id); + data->skip(14); //2,2,1,1,1,1,2,1,1,1,1 down there + continue; + } + + scene->_worldY = data->readSint16LE(); + scene->_worldX = data->readSint16LE(); + scene->_blocked[NORTH] = data->readByte() != 0; + scene->_blocked[SOUTH] = data->readByte() != 0; + scene->_blocked[EAST] = data->readByte() != 0; + scene->_blocked[WEST] = data->readByte() != 0; + scene->_soundFrequency = data->readSint16LE(); + scene->_soundType = data->readByte(); + // the following two bytes are currently unknown + data->readByte(); + data->readByte(); + scene->_visited = data->readByte() != 0; + } + } + + // update all char locations and stats + Common::Array<Chr *> &orderedChrs = _world->_orderedChrs; + if (numChars != orderedChrs.size()) { + warning("characters number in file (%d) differs from the one in world (%d)", numChars, orderedChrs.size()); + } + for (uint32 i = 0; i < orderedChrs.size(); ++i) { + int resourceId = data->readSint16LE(); + int sceneResourceId = data->readSint16LE(); + + int strength = data->readByte(); + int hp = data->readByte(); + int armor = data->readByte(); + int accuracy = data->readByte(); + int spirStrength = data->readByte(); + int spirHp = data->readByte(); + int spirArmor = data->readByte(); + int spirAccuracy = data->readByte(); + int speed = data->readByte(); + int rejectsOffers = data->readByte(); + int followsOpponent = data->readByte(); + + // bytes 16-20 are unknown + data->readByte(); + data->readByte(); + data->readByte(); + data->readByte(); + data->readByte(); + + int weaponDamage1 = data->readByte(); + int weaponDamage2 = data->readByte(); + + Chr *chr = orderedChrs[i]; + if (chr->_resourceId != resourceId) { + warning("loadGame(): updating chrs: expected %d but got %d", chr->_resourceId, resourceId); + continue; + } + + chr->_currentScene = getSceneById(sceneResourceId); + Context &chrContext = chr->_context; + chrContext._statVariables[PHYS_STR_CUR] = strength; + chrContext._statVariables[PHYS_HIT_CUR] = hp; + chrContext._statVariables[PHYS_ARM_CUR] = armor; + chrContext._statVariables[PHYS_ACC_CUR] = accuracy; + chrContext._statVariables[SPIR_STR_CUR] = spirStrength; + chrContext._statVariables[SPIR_HIT_CUR] = spirHp; + chrContext._statVariables[SPIR_ARM_CUR] = spirArmor; + chrContext._statVariables[SPIR_ACC_CUR] = spirAccuracy; + chrContext._statVariables[PHYS_SPE_CUR] = speed; + chr->_rejectsOffers = rejectsOffers; + chr->_followsOpponent = followsOpponent; + chr->_weaponDamage1 = weaponDamage1; + chr->_weaponDamage2 = weaponDamage2; + } + + // update all object locations and stats + Common::Array<Obj *> &orderedObjs = _world->_orderedObjs; + if (numObjs != orderedObjs.size()) { + warning("objects number in file (%d) differs from the one in world (%d)", numObjs, orderedObjs.size()); + } + for (uint32 i = 0; i < orderedObjs.size(); ++i) { + int resourceId = data->readSint16LE(); + int locationResourceId = data->readSint16LE(); + int ownerResourceId = data->readSint16LE(); + + // bytes 7-9 are unknown (always = 0) + data->readByte(); + data->readByte(); + data->readByte(); + + int accuracy = data->readByte(); + int value = data->readByte(); + int type = data->readByte(); + int damage = data->readByte(); + int attackType= data->readByte(); + int numberOfUses = data->readSint16LE(); + + Obj *obj = orderedObjs[i]; + if (obj->_resourceId != resourceId) { + warning("loadGame(): updating objs: expected %d but got %d", obj->_resourceId, resourceId); + continue; + } + + if (ownerResourceId != 0) { + obj->setCurrentOwner(getChrById(ownerResourceId)); + if (obj->_currentOwner == nullptr) + warning("loadGame(): updating objs: owner not found - char with id %d", ownerResourceId); + } else { + obj->setCurrentScene(getSceneById(locationResourceId)); + if (obj->_currentScene == nullptr) + warning("loadGame(): updating objs: scene with id %d not found", ownerResourceId); + } + + obj->_accuracy = accuracy; + obj->_value = value; + obj->_type = type; + obj->_damage = damage; + obj->_attackType = attackType; + obj->_numberOfUses = numberOfUses; + } + + // update inventories and scene contents + for (uint32 i = 0; i < orderedObjs.size(); ++i) { + Obj *obj = orderedObjs[i]; + Chr *chr = obj->_currentOwner; + if (chr != nullptr) { + chr->_inventory.push_back(obj); + } else { + Scene *scene = obj->_currentScene; + scene->_objs.push_back(obj); + } + } + + // update scene chrs + for (uint32 i = 0; i < orderedChrs.size(); ++i) { + Chr *chr = orderedChrs[i]; + Scene *scene = chr->_currentScene; + scene->_chrs.push_back(chr); + if (chr != player) { + wearObjs(chr); + } + } + + // move all worn helmets, shields, chest armors and spiritual + // armors to player + for (int type = 0; type < Chr::NUMBER_OF_ARMOR_TYPES; ++type) { + Obj *armor; + + if (type == Chr::HEAD_ARMOR) + armor = getObjByOffset(helmetOffset, objsHexOffset); + else if (type == Chr::SHIELD_ARMOR) + armor = getObjByOffset(shieldOffset, objsHexOffset); + else if (type == Chr::BODY_ARMOR) + armor = getObjByOffset(armorOffset, objsHexOffset); + else + armor = getObjByOffset(spiritualArmorOffset, objsHexOffset); + + if (armor != nullptr) { + _world->move(armor, player); + player->_armor[type] = armor; + } + } + + //TODO: make sure that armor in the inventory gets put on if we are wearing it + + _loopCount = loopNum; + + // let the engine know if there is a npc in the current scene + if (presCharOffset != 0xffff) { + _monster = getChrByOffset(presCharOffset, chrsHexOffset); + } + + // java engine calls clearOutput(); here + // processTurn("look", NULL); called in Wage right after this loadGame() + + // TODO: as you may see, aim, opponentAim or runCharOffset are not used anywhere + // I'm fixing the first two, as those are clearly not even mentioned anywhere + // the runCharOffset is mentioned up there as "not implemented case" + _aim = aim; + _opponentAim = opponentAim; + + delete data; + return 0; +} + +Common::String WageEngine::getSavegameFilename(int16 slotId) const { + Common::String saveLoadSlot = _targetName; + saveLoadSlot += Common::String::format(".%.3d", slotId); + return saveLoadSlot; +} + +Common::Error WageEngine::saveGameState(int slot, const Common::String &description) { + Common::String saveLoadSlot = getSavegameFilename(slot); + if (saveGame(saveLoadSlot, description) == 0) + return Common::kNoError; + else + return Common::kUnknownError; +} + +} // End of namespace Agi diff --git a/engines/wage/wage.cpp b/engines/wage/wage.cpp index 567e2768d8..fe10b1aa8b 100644 --- a/engines/wage/wage.cpp +++ b/engines/wage/wage.cpp @@ -45,6 +45,7 @@ * */ +#include "common/config-manager.h" #include "common/debug-channels.h" #include "common/error.h" #include "common/events.h" @@ -126,6 +127,12 @@ Common::Error WageEngine::run() { _temporarilyHidden = true; performInitialSetup(); + if (ConfMan.hasKey("save_slot")) { + int saveSlot = ConfMan.getInt("save_slot"); + loadGame(saveSlot); + _gui->regenCommandsMenu(); + _gui->regenWeaponsMenu(); + } Common::String input("look"); processTurn(&input, NULL); _temporarilyHidden = false; @@ -246,6 +253,8 @@ bool WageEngine::saveDialog() { void WageEngine::saveGame() { warning("STUB: saveGame()"); + Common::String fileName = getSavegameFilename(1), desc = "test save #1"; + saveGame(fileName, desc); } void WageEngine::performInitialSetup() { @@ -307,6 +316,11 @@ void WageEngine::performInitialSetup() { } } +void WageEngine::wearObjs(Chr* chr) { + if (chr != nullptr) + chr->wearObjs(); +} + void WageEngine::doClose() { warning("STUB: doClose()"); } diff --git a/engines/wage/wage.h b/engines/wage/wage.h index eb50a2e3dd..718f5e2d64 100644 --- a/engines/wage/wage.h +++ b/engines/wage/wage.h @@ -208,6 +208,21 @@ public: void redrawScene(); void saveGame(); + Common::Error saveGameState(int slot, const Common::String &description); + +private: + int getSceneIndex(Scene *scene) const; + Obj *getObjByOffset(int offset, int objBaseOffset) const; + Chr *getChrById(int resId) const; + Chr *getChrByOffset(int offset, int chrBaseOffset) const; + Scene *getSceneById(int id) const; + Scene *getSceneByOffset(int offset) const; + int saveGame(const Common::String &fileName, const Common::String &descriptionString); + int loadGame(int slotId); + Common::String getSavegameFilename(int16 slotId) const; + +public: + virtual GUI::Debugger *getDebugger() { return _debugger; } private: diff --git a/engines/wage/world.cpp b/engines/wage/world.cpp index 0e40e114b4..286ecddf4d 100644 --- a/engines/wage/world.cpp +++ b/engines/wage/world.cpp @@ -69,6 +69,7 @@ World::World(WageEngine *engine) { _globalScript = nullptr; _player = nullptr; + _signature = 0; _weaponMenuDisabled = true; @@ -146,7 +147,8 @@ bool World::loadWorld(Common::MacResManager *resMan) { res = resMan->getResource(MKTAG('V','E','R','S'), resArray[0]); - res->skip(10); + _signature = res->readSint32LE(); + res->skip(6); byte b = res->readByte(); _weaponMenuDisabled = (b != 0); if (b != 0 && b != 1) @@ -215,6 +217,8 @@ bool World::loadWorld(Common::MacResManager *resMan) { delete res; } + + scene->_resourceId = *iter; addScene(scene); } @@ -224,7 +228,7 @@ bool World::loadWorld(Common::MacResManager *resMan) { for (iter = resArray.begin(); iter != resArray.end(); ++iter) { res = resMan->getResource(MKTAG('A','O','B','J'), *iter); - addObj(new Obj(resMan->getResName(MKTAG('A','O','B','J'), *iter), res)); + addObj(new Obj(resMan->getResName(MKTAG('A','O','B','J'), *iter), res, *iter)); } // Load Characters @@ -234,7 +238,7 @@ bool World::loadWorld(Common::MacResManager *resMan) { for (iter = resArray.begin(); iter != resArray.end(); ++iter) { res = resMan->getResource(MKTAG('A','C','H','R'), *iter); Chr *chr = new Chr(resMan->getResName(MKTAG('A','C','H','R'), *iter), res); - + chr->_resourceId = *iter; addChr(chr); // TODO: What if there's more than one player character? if (chr->_playerCharacter) diff --git a/engines/wage/world.h b/engines/wage/world.h index 468bedbc59..918616c9c1 100644 --- a/engines/wage/world.h +++ b/engines/wage/world.h @@ -48,10 +48,12 @@ #ifndef WAGE_WORLD_H #define WAGE_WORLD_H +#include "wage/entities.h" #include "wage/macwindowmanager.h" namespace Wage { +class Script; class Sound; class World { @@ -90,6 +92,7 @@ public: Patterns *_patterns; Scene *_storageScene; Chr *_player; + int _signature; //List<MoveListener> moveListeners; Common::String *_gameOverMessage; |