aboutsummaryrefslogtreecommitdiff
path: root/engines/mutationofjb
diff options
context:
space:
mode:
authorĽubomír Remák2018-08-30 23:38:41 +0200
committerĽubomír Remák2018-08-30 23:38:41 +0200
commit543f7666f3577eb4cdfe7988873fa731b995d45b (patch)
tree345751f755d60d06a409c01bc2adbb14cb21e4ae /engines/mutationofjb
parent041ab365588a1a612050159f0d42c7aea5426a71 (diff)
downloadscummvm-rg350-543f7666f3577eb4cdfe7988873fa731b995d45b.tar.gz
scummvm-rg350-543f7666f3577eb4cdfe7988873fa731b995d45b.tar.bz2
scummvm-rg350-543f7666f3577eb4cdfe7988873fa731b995d45b.zip
MUTATIONOFJB: Basic save/load support.
Warning: The save format is subject to change.
Diffstat (limited to 'engines/mutationofjb')
-rw-r--r--engines/mutationofjb/detection.cpp48
-rw-r--r--engines/mutationofjb/game.cpp12
-rw-r--r--engines/mutationofjb/game.h2
-rw-r--r--engines/mutationofjb/gamedata.cpp144
-rw-r--r--engines/mutationofjb/gamedata.h114
-rw-r--r--engines/mutationofjb/inventory.cpp17
-rw-r--r--engines/mutationofjb/inventory.h7
-rw-r--r--engines/mutationofjb/mutationofjb.cpp75
-rw-r--r--engines/mutationofjb/mutationofjb.h13
-rw-r--r--engines/mutationofjb/script.cpp4
-rw-r--r--engines/mutationofjb/script.h1
11 files changed, 400 insertions, 37 deletions
diff --git a/engines/mutationofjb/detection.cpp b/engines/mutationofjb/detection.cpp
index 33664c1f8e..3ec82e83bc 100644
--- a/engines/mutationofjb/detection.cpp
+++ b/engines/mutationofjb/detection.cpp
@@ -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)
diff --git a/engines/mutationofjb/game.cpp b/engines/mutationofjb/game.cpp
index 27def7dd1f..ff05bb99eb 100644
--- a/engines/mutationofjb/game.cpp
+++ b/engines/mutationofjb/game.cpp
@@ -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;
+}
+
}
diff --git a/engines/mutationofjb/game.h b/engines/mutationofjb/game.h
index 9ab52ef5b4..6230b93aff 100644
--- a/engines/mutationofjb/game.h
+++ b/engines/mutationofjb/game.h
@@ -81,6 +81,8 @@ public:
TaskPtr getActiveSayTask() const;
void setActiveSayTask(const TaskPtr &sayTask);
+ bool loadSaveAllowed() const;
+
private:
bool loadGameData(bool partB);
void runActiveCommand();
diff --git a/engines/mutationofjb/gamedata.cpp b/engines/mutationofjb/gamedata.cpp
index a669fcffbb..71f58c317b 100644
--- a/engines/mutationofjb/gamedata.cpp
+++ b/engines/mutationofjb/gamedata.cpp
@@ -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);
+}
+
}
diff --git a/engines/mutationofjb/gamedata.h b/engines/mutationofjb/gamedata.h
index 5d7dea1b05..3ba859b28e 100644
--- a/engines/mutationofjb/gamedata.h
+++ b/engines/mutationofjb/gamedata.h
@@ -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;
diff --git a/engines/mutationofjb/inventory.cpp b/engines/mutationofjb/inventory.cpp
index 2d7ba285f2..91bfd9f005 100644
--- a/engines/mutationofjb/inventory.cpp
+++ b/engines/mutationofjb/inventory.cpp
@@ -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]);
+ }
+}
+
}
diff --git a/engines/mutationofjb/inventory.h b/engines/mutationofjb/inventory.h
index d75ef5a674..2f9428add7 100644
--- a/engines/mutationofjb/inventory.h
+++ b/engines/mutationofjb/inventory.h
@@ -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);
diff --git a/engines/mutationofjb/mutationofjb.cpp b/engines/mutationofjb/mutationofjb.cpp
index 4e004f0868..1537646f9e 100644
--- a/engines/mutationofjb/mutationofjb.cpp
+++ b/engines/mutationofjb/mutationofjb.cpp
@@ -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;
+}
+
}
diff --git a/engines/mutationofjb/mutationofjb.h b/engines/mutationofjb/mutationofjb.h
index 380325487c..3430f13051 100644
--- a/engines/mutationofjb/mutationofjb.h
+++ b/engines/mutationofjb/mutationofjb.h
@@ -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();
diff --git a/engines/mutationofjb/script.cpp b/engines/mutationofjb/script.cpp
index 7538017199..85574dacb5 100644
--- a/engines/mutationofjb/script.cpp
+++ b/engines/mutationofjb/script.cpp
@@ -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();
diff --git a/engines/mutationofjb/script.h b/engines/mutationofjb/script.h
index 86f2450ce6..d168fdaa24 100644
--- a/engines/mutationofjb/script.h
+++ b/engines/mutationofjb/script.h
@@ -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;