mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-06 18:00:44 +00:00
777 lines
25 KiB
C++
777 lines
25 KiB
C++
/* 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 && (uint)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 && (uint)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 && (uint)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->writeUint16LE(0xffff); // ???? - always FFFF
|
|
out->writeUint16LE(0xffff); // ???? - always FFFF
|
|
out->writeUint16LE(0xffff); // ???? - always FFFF
|
|
out->writeUint16LE(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
|
|
int32 appendixOffset = out->pos();
|
|
if (appendixOffset < 0) {
|
|
warning("OutSaveFile::pos() failed");
|
|
}
|
|
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);
|
|
|
|
out->writeUint32BE(appendixOffset);
|
|
|
|
// 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 ((uint)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 ((uint)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 ((uint)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::loadGameState(int slot) {
|
|
if (loadGame(slot) == 0)
|
|
return Common::kNoError;
|
|
else
|
|
return Common::kUnknownError;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
bool WageEngine::scummVMSaveLoadDialog(bool isSave) {
|
|
if (!isSave) {
|
|
// do loading
|
|
GUI::SaveLoadChooser dialog = GUI::SaveLoadChooser(_("Load game:"), _("Load"), false);
|
|
int slot = dialog.runModalWithCurrentTarget();
|
|
|
|
if (slot < 0)
|
|
return true;
|
|
|
|
return loadGameState(slot).getCode() == Common::kNoError;
|
|
}
|
|
|
|
// do saving
|
|
GUI::SaveLoadChooser dialog = GUI::SaveLoadChooser(_("Save game:"), _("Save"), true);
|
|
int slot = dialog.runModalWithCurrentTarget();
|
|
Common::String desc = dialog.getResultString();
|
|
|
|
if (desc.empty()) {
|
|
// create our own description for the saved game, the user didnt enter it
|
|
desc = dialog.createDefaultSaveDescription(slot);
|
|
}
|
|
|
|
if (desc.size() > 28)
|
|
desc = Common::String(desc.c_str(), 28);
|
|
|
|
if (slot < 0)
|
|
return true;
|
|
|
|
return saveGameState(slot, desc).getCode() == Common::kNoError;
|
|
}
|
|
|
|
} // End of namespace Agi
|