MUTATIONOFJB: Basic save/load support.

Warning: The save format is subject to change.
This commit is contained in:
Ľubomír Remák 2018-08-30 23:38:41 +02:00
parent 041ab36558
commit 543f7666f3
11 changed files with 400 additions and 37 deletions

View File

@ -23,6 +23,9 @@
#include "mutationofjb/mutationofjb.h"
#include "common/config-manager.h"
#include "common/system.h"
#include "common/savefile.h"
#include "common/serializer.h"
#include "engines/advancedDetector.h"
@ -89,20 +92,59 @@ public:
_directoryGlobs = mutationofjbDirectoryGlobs;
}
virtual const char *getName() const {
virtual const char *getName() const override {
return "Mutation of J.B.";
}
virtual const char *getOriginalCopyright() const {
virtual const char *getOriginalCopyright() const override {
return "Mutation of J.B. (C) 1996 RIKI Computer Games";
}
virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const override {
if (desc) {
*engine = new MutationOfJB::MutationOfJBEngine(syst);
}
return desc != nullptr;
}
virtual bool hasFeature(MetaEngineFeature f) const override {
if (f == kSupportsListSaves || f == kSimpleSavesNames) {
return true;
}
return false;
}
virtual int getMaximumSaveSlot() const override {
return 999;
}
virtual SaveStateList listSaves(const char *target) const override {
Common::SaveFileManager *const saveFileMan = g_system->getSavefileManager();
Common::StringArray filenames;
Common::String pattern = target;
pattern += ".###";
filenames = saveFileMan->listSavefiles(pattern);
SaveStateList saveList;
int slotNo = 0;
for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) {
// Obtain the last 3 digits of the filename, since they correspond to the save slot
slotNo = atoi(file->c_str() + file->size() - 3);
Common::InSaveFile *const in = saveFileMan->openForLoading(*file);
if (in) {
Common::Serializer sz(in, nullptr);
MutationOfJB::SaveHeader saveHdr;
if (saveHdr.sync(sz)) {
saveList.push_back(SaveStateDescriptor(slotNo, saveHdr._description));
}
}
}
return saveList;
}
};
#if PLUGIN_ENABLED_DYNAMIC(MUTATIONOFJB)

View File

@ -92,7 +92,7 @@ bool Game::loadGameData(bool partB) {
return false;
}
_gameData->loadFromStream(file);
_gameData->loadInitialState(file);
file.close();
@ -255,4 +255,14 @@ void Game::setActiveSayTask(const TaskPtr &sayTask) {
_activeSayTask = sayTask;
}
bool Game::loadSaveAllowed() const {
if (_scriptExecCtx.isCommandRunning())
return false;
if (isCurrentSceneMap())
return false;
return true;
}
}

View File

@ -81,6 +81,8 @@ public:
TaskPtr getActiveSayTask() const;
void setActiveSayTask(const TaskPtr &sayTask);
bool loadSaveAllowed() const;
private:
bool loadGameData(bool partB);
void runActiveCommand();

View File

@ -21,12 +21,14 @@
*/
#include "mutationofjb/gamedata.h"
#include "common/serializer.h"
#include "common/stream.h"
#include "common/util.h"
namespace MutationOfJB {
static bool readString(Common::ReadStream &stream, char *str) {
static bool readEntityNameString(Common::ReadStream &stream, char *str) {
char buf[MAX_ENTITY_NAME_LENGTH];
memset(str, 0, MAX_ENTITY_NAME_LENGTH + 1);
@ -39,12 +41,24 @@ static bool readString(Common::ReadStream &stream, char *str) {
return true;
}
static void syncEntityNameString(char *cstr, Common::Serializer &sz) {
if (sz.isLoading()) {
Common::String str;
sz.syncString(str);
strncpy(cstr, str.c_str(), MAX_ENTITY_NAME_LENGTH);
cstr[MAX_ENTITY_NAME_LENGTH] = 0;
} else {
Common::String str(cstr);
sz.syncString(str);
}
}
bool Door::isActive() {
return *_name != '\0';
}
bool Door::loadFromStream(Common::ReadStream &stream) {
readString(stream, _name);
bool Door::loadInitialState(Common::ReadStream &stream) {
readEntityNameString(stream, _name);
_destSceneId = stream.readByte();
_destX = stream.readUint16LE();
@ -60,7 +74,21 @@ bool Door::loadFromStream(Common::ReadStream &stream) {
return true;
}
bool Object::loadFromStream(Common::ReadStream &stream) {
void Door::saveLoadWithSerializer(Common::Serializer &sz) {
syncEntityNameString(_name, sz);
sz.syncAsByte(_destSceneId);
sz.syncAsUint16LE(_destX);
sz.syncAsUint16LE(_destY);
sz.syncAsUint16LE(_x);
sz.syncAsByte(_y);
sz.syncAsUint16LE(_width);
sz.syncAsByte(_height);
sz.syncAsUint16LE(_walkToX);
sz.syncAsByte(_walkToY);
sz.syncAsByte(_SP);
}
bool Object::loadInitialState(Common::ReadStream &stream) {
_active = stream.readByte();
_firstFrame = stream.readByte();
_randomFrame = stream.readByte();
@ -79,9 +107,26 @@ bool Object::loadFromStream(Common::ReadStream &stream) {
return true;
}
bool Static::loadFromStream(Common::ReadStream &stream) {
void Object::saveLoadWithSerializer(Common::Serializer &sz) {
sz.syncAsByte(_active);
sz.syncAsByte(_firstFrame);
sz.syncAsByte(_randomFrame);
sz.syncAsByte(_numFrames);
sz.syncAsByte(_roomFrameLSB);
sz.syncAsByte(_jumpChance);
sz.syncAsByte(_currentFrame);
sz.syncAsUint16LE(_x);
sz.syncAsByte(_y);
sz.syncAsUint16LE(_width);
sz.syncAsByte(_height);
sz.syncAsUint16LE(_WX);
sz.syncAsByte(_roomFrameMSB);
sz.syncAsByte(_SP);
}
bool Static::loadInitialState(Common::ReadStream &stream) {
_active = stream.readByte();
readString(stream, _name);
readEntityNameString(stream, _name);
_x = stream.readUint16LE();
_y = stream.readByte();
_width = stream.readUint16LE();
@ -93,7 +138,19 @@ bool Static::loadFromStream(Common::ReadStream &stream) {
return true;
}
bool Bitmap::loadFromStream(Common::ReadStream &stream) {
void Static::saveLoadWithSerializer(Common::Serializer &sz) {
sz.syncAsByte(_active);
syncEntityNameString(_name, sz);
sz.syncAsUint16LE(_x);
sz.syncAsByte(_y);
sz.syncAsUint16LE(_width);
sz.syncAsByte(_height);
sz.syncAsUint16LE(_walkToX);
sz.syncAsByte(_walkToY);
sz.syncAsByte(_walkToFrame);
}
bool Bitmap::loadInitialState(Common::ReadStream &stream) {
_roomFrame = stream.readByte();
_isVisible = stream.readByte();
_x1 = stream.readUint16LE();
@ -104,7 +161,16 @@ bool Bitmap::loadFromStream(Common::ReadStream &stream) {
return true;
}
bool Scene::loadFromStream(Common::ReadStream &stream) {
void Bitmap::saveLoadWithSerializer(Common::Serializer &sz) {
sz.syncAsByte(_roomFrame);
sz.syncAsByte(_isVisible);
sz.syncAsUint16LE(_x1);
sz.syncAsByte(_y1);
sz.syncAsUint16LE(_x2);
sz.syncAsByte(_y2);
}
bool Scene::loadInitialState(Common::ReadStream &stream) {
int i;
_startup = stream.readByte();
@ -116,23 +182,23 @@ bool Scene::loadFromStream(Common::ReadStream &stream) {
_noDoors = stream.readByte();
_noDoors = MIN(_noDoors, static_cast<uint8>(ARRAYSIZE(_doors)));
for (i = 0; i < ARRAYSIZE(_doors); ++i) {
_doors[i].loadFromStream(stream);
_doors[i].loadInitialState(stream);
}
_noObjects = stream.readByte();
_noObjects = MIN(_noObjects, static_cast<uint8>(ARRAYSIZE(_objects)));
for (i = 0; i < ARRAYSIZE(_objects); ++i) {
_objects[i].loadFromStream(stream);
_objects[i].loadInitialState(stream);
}
_noStatics = stream.readByte();
_noStatics = MIN(_noStatics, static_cast<uint8>(ARRAYSIZE(_statics)));
for (i = 0; i < ARRAYSIZE(_statics); ++i) {
_statics[i].loadFromStream(stream);
_statics[i].loadInitialState(stream);
}
for (i = 0; i < ARRAYSIZE(_bitmaps); ++i) {
_bitmaps[i].loadFromStream(stream);
_bitmaps[i].loadInitialState(stream);
}
_obstacleY1 = stream.readUint16LE();
@ -141,13 +207,50 @@ bool Scene::loadFromStream(Common::ReadStream &stream) {
_palRotDelay = stream.readByte();
_exhaustedConvItemNext = stream.readByte();
for (i = 0; i < 79; ++i) {
for (i = 0; i < ARRAYSIZE(_exhaustedConvItems); ++i) {
_exhaustedConvItems[i]._encodedData = stream.readByte();
}
return true;
}
void Scene::saveLoadWithSerializer(Common::Serializer &sz) {
sz.syncAsByte(_startup);
sz.syncAsByte(_unknown001);
sz.syncAsByte(_unknown002);
sz.syncAsByte(_unknown003);
sz.syncAsByte(_delay);
sz.syncAsByte(_noDoors);
for (int i = 0; i < ARRAYSIZE(_doors); ++i) {
_doors[i].saveLoadWithSerializer(sz);
}
sz.syncAsByte(_noObjects);
for (int i = 0; i < ARRAYSIZE(_objects); ++i) {
_objects[i].saveLoadWithSerializer(sz);
}
sz.syncAsByte(_noStatics);
for (int i = 0; i < ARRAYSIZE(_statics); ++i) {
_statics[i].saveLoadWithSerializer(sz);
}
for (int i = 0; i < ARRAYSIZE(_bitmaps); ++i) {
_bitmaps[i].saveLoadWithSerializer(sz);
}
sz.syncAsUint16LE(_obstacleY1);
sz.syncAsByte(_palRotFirst);
sz.syncAsByte(_palRotLast);
sz.syncAsByte(_palRotDelay);
sz.syncAsByte(_exhaustedConvItemNext);
for (int i = 0; i < ARRAYSIZE(_exhaustedConvItems); ++i) {
sz.syncAsByte(_exhaustedConvItems[i]._encodedData);
}
}
Door *Scene::getDoor(uint8 doorId) {
if (doorId == 0 || doorId > _noDoors) {
warning("Door %d does not exist", doorId);
@ -270,12 +373,23 @@ Inventory &GameData::getInventory() {
return _inventory;
}
bool GameData::loadFromStream(Common::ReadStream &stream) {
bool GameData::loadInitialState(Common::ReadStream &stream) {
for (int i = 0; i < ARRAYSIZE(_scenes); ++i) {
_scenes[i].loadFromStream(stream);
_scenes[i].loadInitialState(stream);
}
return true;
}
void GameData::saveLoadWithSerializer(Common::Serializer &sz) {
for (int i = 0; i < ARRAYSIZE(_scenes); ++i) {
_scenes[i].saveLoadWithSerializer(sz);
}
sz.syncAsByte(_currentScene);
sz.syncAsByte(_partB);
_inventory.saveLoadWithSerializer(sz);
sz.syncString(_currentAPK);
}
}

View File

@ -23,9 +23,11 @@
#ifndef MUTATIONOFJB_GAMEDATA_H
#define MUTATIONOFJB_GAMEDATA_H
#include "common/scummsys.h"
#include "mutationofjb/inventory.h"
#include "common/serializer.h"
#include "common/scummsys.h"
namespace Common {
class ReadStream;
}
@ -36,7 +38,7 @@ enum {
MAX_ENTITY_NAME_LENGTH = 0x14
};
/** @file gamedata.h
/** @file
* There are 4 types of entities present in the game data:
* - Door
* - Object
@ -47,7 +49,9 @@ enum {
/**
* An interactable scene changer with no visual representation.
*/
struct Door {
struct Door : public Common::Serializable {
virtual ~Door() {}
/**
* Door name (NM register).
*
@ -80,7 +84,7 @@ struct Door {
uint16 _walkToX;
/** Y coordinate for position player will walk towards after clicking the door (WY register). */
uint8 _walkToY;
/* Unknown for now - likely not even used. */
/** Unknown for now - likely not even used. */
uint8 _SP;
/**
@ -89,7 +93,20 @@ struct Door {
*/
bool isActive();
bool loadFromStream(Common::ReadStream &stream);
/**
* Load initial state from game data file.
*
* @param stream Stream for reading.
* @return True if success, false otherwise.
*/
bool loadInitialState(Common::ReadStream &stream);
/**
* (De)serialization for save/load.
*
* @param sz Serializer.
*/
virtual void saveLoadWithSerializer(Common::Serializer &sz) override;
};
/**
@ -105,7 +122,7 @@ struct Door {
*
* For details regarding animation playback, see objectanimationtask.cpp.
*/
struct Object {
struct Object : public Common::Serializable {
/** Controls whether the animation is playing. */
uint8 _active;
/**
@ -163,16 +180,29 @@ struct Object {
* @see _roomFrameLSB
*/
uint8 _roomFrameMSB;
/* Unknown. TODO: Figure out what this does. */
/** Unknown. TODO: Figure out what this does. */
uint8 _SP;
bool loadFromStream(Common::ReadStream &stream);
/**
* Load initial state from game data file.
*
* @param stream Stream for reading.
* @return True if success, false otherwise.
*/
bool loadInitialState(Common::ReadStream &stream);
/**
* (De)serialization for save/load.
*
* @param sz Serializer.
*/
virtual void saveLoadWithSerializer(Common::Serializer &sz) override;
};
/**
* An interactable area, usually without a visual representation.
*/
struct Static {
struct Static : public Common::Serializable {
/** Whether you can mouse over and interact with the static (AC register). */
uint8 _active;
/**
@ -206,14 +236,27 @@ struct Static {
/** Player frame (rotation) set after the player finishes walking towards the walk to position (SP register). */
uint8 _walkToFrame;
bool loadFromStream(Common::ReadStream &stream);
/**
* Load initial state from game data file.
*
* @param stream Stream for reading.
* @return True if success, false otherwise.
*/
bool loadInitialState(Common::ReadStream &stream);
/**
* (De)serialization for save/load.
*
* @param sz Serializer.
*/
virtual void saveLoadWithSerializer(Common::Serializer &sz) override;
};
/**
* A static image that is carved out of a room frame based on its rectangle.
* The bitmap rectangle also specifies where to blit it on the screen.
*/
struct Bitmap {
struct Bitmap : public Common::Serializable {
/** Room frame that this bitmap carves out of. */
uint8 _roomFrame;
/** Whether to draw the bitmap. */
@ -227,7 +270,20 @@ struct Bitmap {
/** Y coordinate of the bottom right corner of the bitmap rectangle. */
uint8 _y2;
bool loadFromStream(Common::ReadStream &stream);
/**
* Load initial state from game data file.
*
* @param stream Stream for reading.
* @return True if success, false otherwise.
*/
bool loadInitialState(Common::ReadStream &stream);
/**
* (De)serialization for save/load.
*
* @param sz Serializer.
*/
virtual void saveLoadWithSerializer(Common::Serializer &sz) override;
};
/**
@ -256,7 +312,7 @@ struct ExhaustedConvItem {
_encodedData(((context & 0x1) << 7) | ((convItemIndex & 0x7) << 4) | (convGroupIndex & 0xF)) {}
};
struct Scene {
struct Scene : Common::Serializable {
Door *getDoor(uint8 objectId);
Object *getObject(uint8 objectId, bool ignoreNo = false);
Static *getStatic(uint8 staticId, bool ignoreNo = false);
@ -328,7 +384,20 @@ struct Scene {
uint8 _exhaustedConvItemNext;
ExhaustedConvItem _exhaustedConvItems[79];
bool loadFromStream(Common::ReadStream &stream);
/**
* Load initial state from game data file.
*
* @param stream Stream for reading.
* @return True if success, false otherwise.
*/
bool loadInitialState(Common::ReadStream &stream);
/**
* (De)serialization for save/load.
*
* @param sz Serializer.
*/
virtual void saveLoadWithSerializer(Common::Serializer &sz) override;
};
struct ConversationInfo {
@ -346,14 +415,27 @@ struct ConversationInfo {
uint8 _color;
};
struct GameData {
struct GameData : public Common::Serializable {
public:
GameData();
Scene *getScene(uint8 sceneId);
Scene *getCurrentScene();
Inventory &getInventory();
bool loadFromStream(Common::ReadStream &stream);
/**
* Load initial state from game data file.
*
* @param stream Stream for reading.
* @return True if success, false otherwise.
*/
bool loadInitialState(Common::ReadStream &stream);
/**
* (De)serialization for save/load.
*
* @param sz Serializer.
*/
virtual void saveLoadWithSerializer(Common::Serializer &sz) override;
uint8 _currentScene; // Persistent.
uint8 _lastScene;

View File

@ -137,4 +137,21 @@ void Inventory::reverseItems(uint from, uint to) {
}
}
void Inventory::saveLoadWithSerializer(Common::Serializer &sz) {
if (sz.isLoading()) {
uint32 length = 0;
sz.syncAsUint32LE(length);
if (length) {
_items.resize(length);
}
} else {
uint32 length = static_cast<uint32>(_items.size());
sz.syncAsUint32LE(length);
}
for (Items::size_type i = 0; i < _items.size(); ++i) {
sz.syncString(_items[i]);
}
}
}

View File

@ -23,8 +23,9 @@
#ifndef MUTATIONOFJB_INVENTORY_H
#define MUTATIONOFJB_INVENTORY_H
#include "common/scummsys.h"
#include "common/array.h"
#include "common/serializer.h"
#include "common/scummsys.h"
#include "common/str.h"
namespace MutationOfJB {
@ -37,7 +38,7 @@ public:
virtual ~InventoryObserver() {}
};
class Inventory {
class Inventory : public Common::Serializable {
public:
enum {
VISIBLE_ITEMS = 6
@ -59,6 +60,8 @@ public:
void setObserver(InventoryObserver *observer);
virtual void saveLoadWithSerializer(Common::Serializer &sz) override;
private:
void rotateItemsRight(uint n);
void rotateItemsLeft(uint n);

View File

@ -28,6 +28,7 @@
#include "common/system.h"
#include "common/events.h"
#include "common/fs.h"
#include "common/savefile.h"
#include "graphics/screen.h"
#include "graphics/cursorman.h"
@ -125,6 +126,55 @@ void MutationOfJBEngine::updateCursor() {
}
}
bool MutationOfJBEngine::hasFeature(Engine::EngineFeature f) const {
if (f == kSupportsLoadingDuringRuntime || f == kSupportsSavingDuringRuntime) {
return true;
}
return false;
}
bool MutationOfJBEngine::canLoadGameStateCurrently() {
return _game->loadSaveAllowed();
}
Common::Error MutationOfJBEngine::loadGameState(int slot) {
const Common::String saveName = Common::String::format("%s.%03d", _targetName.c_str(), slot);
Common::InSaveFile *const saveFile = g_system->getSavefileManager()->openForLoading(saveName);
Common::Serializer sz(saveFile, nullptr);
SaveHeader saveHdr;
saveHdr.sync(sz);
_game->getGameData().saveLoadWithSerializer(sz);
delete saveFile;
_game->changeScene(_game->getGameData()._currentScene, _game->getGameData()._partB);
_game->getGui().markDirty();
return Common::kNoError;
}
bool MutationOfJBEngine::canSaveGameStateCurrently() {
return _game->loadSaveAllowed();
}
Common::Error MutationOfJBEngine::saveGameState(int slot, const Common::String &desc) {
const Common::String saveName = Common::String::format("%s.%03d", _targetName.c_str(), slot);
Common::OutSaveFile *const saveFile = g_system->getSavefileManager()->openForSaving(saveName);
Common::Serializer sz(nullptr, saveFile);
SaveHeader saveHdr;
saveHdr._description = desc;
saveHdr.sync(sz);
_game->getGameData().saveLoadWithSerializer(sz);
saveFile->finalize();
delete saveFile;
return Common::kNoError;
}
void MutationOfJBEngine::handleNormalScene(const Common::Event &event) {
Scene *const scene = _game->getGameData().getCurrentScene();
@ -255,6 +305,9 @@ Common::Error MutationOfJBEngine::run() {
event.kbd.ascii == '~' || event.kbd.ascii == '#') {
_console->attach();
}
if (event.kbd.keycode == Common::KEYCODE_F5 && event.kbd.hasFlags(0)) {
openMainMenuDialog();
}
break;
}
case Common::EVENT_KEYUP: {
@ -297,4 +350,26 @@ Common::Error MutationOfJBEngine::run() {
return Common::kNoError;
}
bool SaveHeader::sync(Common::Serializer &sz) {
const uint32 SAVE_MAGIC_NUMBER = MKTAG('M', 'O', 'J', 'B');
const uint32 SAVE_FILE_VERSION = 1;
if (sz.isLoading()) {
uint32 magic = 0;
sz.syncAsUint32BE(magic);
if (magic != SAVE_MAGIC_NUMBER) {
warning("Invalid save");
return false;
}
} else {
uint32 magic = SAVE_MAGIC_NUMBER;
sz.syncAsUint32BE(magic);
}
sz.syncVersion(SAVE_FILE_VERSION);
sz.syncString(_description);
return true;
}
}

View File

@ -28,6 +28,7 @@
namespace Common {
struct Event;
class Serializer;
}
namespace Graphics {
@ -39,6 +40,12 @@ namespace MutationOfJB {
class Console;
class Game;
struct SaveHeader {
bool sync(Common::Serializer &sz);
Common::String _description;
};
class MutationOfJBEngine : public Engine {
public:
enum CursorState {
@ -56,6 +63,12 @@ public:
void setCursorState(CursorState cursorState);
void updateCursor();
virtual bool hasFeature(EngineFeature f) const override;
virtual bool canLoadGameStateCurrently() override;
virtual Common::Error loadGameState(int slot) override;
virtual bool canSaveGameStateCurrently() override;
virtual Common::Error saveGameState(int slot, const Common::String &desc) override;
private:
bool loadGameData(bool partB);
void setupCursor();

View File

@ -207,6 +207,10 @@ Command *ScriptExecutionContext::getExtra(const Common::String &name) const {
return cmd;
}
bool ScriptExecutionContext::isCommandRunning() const {
return _activeCommand;
}
bool Script::loadFromStream(Common::SeekableReadStream &stream) {
destroy();

View File

@ -119,6 +119,7 @@ public:
GameData &getGameData();
Command *getMacro(const Common::String &name) const;
Command *getExtra(const Common::String &name) const;
bool isCommandRunning() const;
private:
Game &_game;