mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-14 21:59:17 +00:00
1134 lines
37 KiB
C++
1134 lines
37 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "common/btea.h"
|
|
#include "common/debug-channels.h"
|
|
#include "common/savefile.h"
|
|
#include "twp/callback.h"
|
|
#include "twp/detection.h"
|
|
#include "twp/dialog.h"
|
|
#include "twp/ggpack.h"
|
|
#include "twp/hud.h"
|
|
#include "twp/object.h"
|
|
#include "twp/resmanager.h"
|
|
#include "twp/room.h"
|
|
#include "twp/savegame.h"
|
|
#include "twp/squtil.h"
|
|
#include "twp/time.h"
|
|
|
|
namespace Twp {
|
|
|
|
const static uint32 savegameKey[] = {0xAEA4EDF3, 0xAFF8332A, 0xB5A2DBB4, 0x9B4BA022};
|
|
|
|
static Common::SharedPtr<Object> actor(const Common::String &key) {
|
|
for (size_t i = 0; i < g_twp->_actors.size(); i++) {
|
|
Common::SharedPtr<Object> a = g_twp->_actors[i];
|
|
if (a->_key == key)
|
|
return a;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static Common::SharedPtr<Object> actor(int id) {
|
|
for (size_t i = 0; i < g_twp->_actors.size(); i++) {
|
|
Common::SharedPtr<Object> a = g_twp->_actors[i];
|
|
if (a->getId() == id)
|
|
return a;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static DialogConditionMode parseMode(char mode) {
|
|
switch (mode) {
|
|
case '?':
|
|
return Once;
|
|
case '#':
|
|
return ShowOnce;
|
|
case '&':
|
|
return OnceEver;
|
|
case '$':
|
|
return ShowOnceEver;
|
|
case '^':
|
|
return TempOnce;
|
|
default:
|
|
warning("Invalid dialog condition mode: %c", mode);
|
|
return TempOnce;
|
|
}
|
|
}
|
|
|
|
static DialogConditionState parseState(Common::String &dialog) {
|
|
Common::String dialogName;
|
|
size_t i = 1;
|
|
while (i < dialog.size() && !Common::isDigit(dialog[i])) {
|
|
dialogName += dialog[i];
|
|
i++;
|
|
}
|
|
|
|
while (!g_twp->_pack->assetExists((dialogName + ".byack").c_str()) && (i < dialog.size())) {
|
|
dialogName += dialog[i];
|
|
i++;
|
|
}
|
|
|
|
Common::String num;
|
|
while ((i < dialog.size()) && Common::isDigit(dialog[i])) {
|
|
num += dialog[i];
|
|
i++;
|
|
}
|
|
|
|
DialogConditionState result;
|
|
result.mode = parseMode(dialog[0]);
|
|
result.dialog = Common::move(dialogName);
|
|
result.line = atol(num.c_str());
|
|
result.actorKey = dialog.substr(i);
|
|
return result;
|
|
}
|
|
|
|
static Common::SharedPtr<Room> room(const Common::String &name) {
|
|
for (size_t i = 0; i < g_twp->_rooms.size(); i++) {
|
|
Common::SharedPtr<Room> r(g_twp->_rooms[i]);
|
|
if (r->_name == name) {
|
|
return r;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static Common::SharedPtr<Object> object(Common::SharedPtr<Room> room, const Common::String &key) {
|
|
for (size_t i = 0; i < room->_layers.size(); i++) {
|
|
Common::SharedPtr<Layer> layer = room->_layers[i];
|
|
for (size_t j = 0; j < layer->_objects.size(); j++) {
|
|
Common::SharedPtr<Object> o = layer->_objects[j];
|
|
if (o->_key == key)
|
|
return o;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static Common::SharedPtr<Object> object(const Common::String &key) {
|
|
for (size_t i = 0; i < g_twp->_rooms.size(); i++) {
|
|
Common::SharedPtr<Room> r(g_twp->_rooms[i]);
|
|
for (size_t j = 0; j < r->_layers.size(); j++) {
|
|
Common::SharedPtr<Layer> layer(r->_layers[j]);
|
|
for (size_t k = 0; k < layer->_objects.size(); k++) {
|
|
Common::SharedPtr<Object> o(layer->_objects[k]);
|
|
if (o->_key == key)
|
|
return o;
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static SQRESULT toSquirrel(const Common::JSONValue *json, HSQOBJECT &obj) {
|
|
HSQUIRRELVM v = g_twp->getVm();
|
|
SQInteger top = sq_gettop(v);
|
|
sq_resetobject(&obj);
|
|
if (json->isString()) {
|
|
sqpush(v, json->asString());
|
|
if (SQ_FAILED(sqget(v, -1, obj)))
|
|
return sq_throwerror(v, "failed to get string");
|
|
} else if (json->isIntegerNumber()) {
|
|
sqpush(v, static_cast<int>(json->asIntegerNumber()));
|
|
if (SQ_FAILED(sqget(v, -1, obj)))
|
|
return sq_throwerror(v, "failed to get int");
|
|
} else if (json->isBool()) {
|
|
sqpush(v, json->asBool());
|
|
if (SQ_FAILED(sqget(v, -1, obj)))
|
|
return sq_throwerror(v, "failed to get bool");
|
|
} else if (json->isNumber()) {
|
|
sqpush(v, json->asNumber());
|
|
if (SQ_FAILED(sqget(v, -1, obj)))
|
|
return sq_throwerror(v, "failed to get float");
|
|
} else if (json->isNull()) {
|
|
} else if (json->isArray()) {
|
|
sq_newarray(v, 0);
|
|
const Common::JSONArray &jArr = json->asArray();
|
|
for (size_t i = 0; i < jArr.size(); i++) {
|
|
HSQOBJECT tmp;
|
|
if (SQ_FAILED(toSquirrel(jArr[i], tmp)))
|
|
return sq_throwerror(v, "failed to get array element");
|
|
sqpush(v, tmp);
|
|
sq_arrayappend(v, -2);
|
|
}
|
|
if (SQ_FAILED(sqget(v, -1, obj)))
|
|
return sq_throwerror(v, "failed to get array");
|
|
} else if (json->isObject()) {
|
|
const Common::JSONObject &jObject = json->asObject();
|
|
// check if it's a reference to an actor
|
|
if (jObject.contains("_actorKey")) {
|
|
obj = actor(jObject["_actorKey"]->asString())->_table;
|
|
} else if (jObject.contains("_roomKey")) {
|
|
Common::String roomName = jObject["_roomKey"]->asString();
|
|
if (jObject.contains("_objectKey")) {
|
|
Common::String objName = jObject["_objectKey"]->asString();
|
|
Common::SharedPtr<Room> r = room(roomName);
|
|
if (!r)
|
|
warning("room with key=%s not found", roomName.c_str());
|
|
else {
|
|
Common::SharedPtr<Object> o = object(r, objName);
|
|
if (!o)
|
|
warning("room object with key=%s/%s not found", roomName.c_str(), objName.c_str());
|
|
else
|
|
obj = o->_table;
|
|
}
|
|
} else {
|
|
Common::SharedPtr<Room> r = room(roomName);
|
|
if (!r) {
|
|
warning("room with key=%s not found", roomName.c_str());
|
|
} else {
|
|
obj = r->_table;
|
|
}
|
|
}
|
|
} else if (jObject.contains("_objectKey")) {
|
|
Common::String objName = jObject["_objectKey"]->asString();
|
|
Common::SharedPtr<Object> o = object(objName);
|
|
if (!o) {
|
|
warning("object with key=%s not found", objName.c_str());
|
|
} else {
|
|
obj = o->_table;
|
|
}
|
|
} else {
|
|
sq_newtable(v);
|
|
for (auto it = jObject.begin(); it != jObject.end(); it++) {
|
|
sqpush(v, it->_key);
|
|
HSQOBJECT tmp;
|
|
if (SQ_FAILED(toSquirrel(it->_value, tmp)))
|
|
return sq_throwerror(v, "failed to get table element");
|
|
sqpush(v, tmp);
|
|
sq_newslot(v, -3, SQFalse);
|
|
}
|
|
if (SQ_FAILED(sqget(v, -1, obj)))
|
|
return sq_throwerror(v, "failed to get table");
|
|
}
|
|
}
|
|
sq_addref(v, &obj);
|
|
sq_settop(v, top);
|
|
return SQ_OK;
|
|
}
|
|
|
|
static Common::String sub(const Common::String &s, size_t pos, size_t end) {
|
|
return s.substr(pos, s.size() - end);
|
|
}
|
|
|
|
static void setAnimations(Common::SharedPtr<Object> actor, const Common::JSONArray &anims) {
|
|
Common::String headAnim = anims[0]->asString();
|
|
Common::String standAnim = sub(anims[9]->asString(), 0, 7);
|
|
Common::String walkAnim = sub(anims[11]->asString(), 0, 7);
|
|
Common::String reachAnim = sub(anims[15]->asString(), 0, 11);
|
|
actor->setAnimationNames(headAnim, standAnim, walkAnim, reachAnim);
|
|
}
|
|
|
|
static SQRESULT loadActor(Common::SharedPtr<Object> actor, const Common::JSONObject &json) {
|
|
HSQUIRRELVM v = g_twp->getVm();
|
|
actor->setTouchable(!json.contains("_untouchable") || json["_untouchable"]->asIntegerNumber() != 1);
|
|
actor->_node->setVisible(!json.contains("_hidden") || json["_hidden"]->asIntegerNumber() != 1);
|
|
actor->_volume = json.contains("_volume") ? json["_volume"]->asNumber() : 1.0f;
|
|
for (auto it = json.begin(); it != json.end(); it++) {
|
|
if (it->_key == "_animations") {
|
|
setAnimations(actor, it->_value->asArray());
|
|
} else if (it->_key == "_pos") {
|
|
actor->_node->setPos(parseVec2(it->_value->asString()));
|
|
} else if (it->_key == "_costume") {
|
|
Common::String sheet;
|
|
if (json.contains("_costumeSheet"))
|
|
sheet = json["_costumeSheet"]->asString();
|
|
actor->setCostume(it->_value->asString(), sheet);
|
|
} else if (it->_key == "_costumeSheet") {
|
|
} else if (it->_key == "_color") {
|
|
actor->_node->setColor(Color::rgb(it->_value->asIntegerNumber()));
|
|
actor->_node->setAlpha(Color::fromRgba(it->_value->asIntegerNumber()).rgba.a);
|
|
} else if (it->_key == "_dir") {
|
|
actor->setFacing((Facing)it->_value->asIntegerNumber());
|
|
} else if (it->_key == "_lockFacing") {
|
|
actor->lockFacing(it->_value->asIntegerNumber());
|
|
} else if (it->_key == "_useDir") {
|
|
actor->_useDir = (Direction)it->_value->asIntegerNumber();
|
|
} else if (it->_key == "_usePos") {
|
|
actor->_usePos = parseVec2(it->_value->asString());
|
|
} else if (it->_key == "_offset") {
|
|
actor->_node->setOffset(parseVec2(it->_value->asString()));
|
|
} else if (it->_key == "_renderOffset") {
|
|
actor->_node->setRenderOffset(parseVec2(it->_value->asString()));
|
|
} else if (it->_key == "_roomKey") {
|
|
Object::setRoom(actor, room(it->_value->asString()));
|
|
} else if ((it->_key == "_hidden") || (it->_key == "_untouchable") || (it->_key == "_volume")) {
|
|
} else {
|
|
if (!it->_key.hasPrefix("_")) {
|
|
HSQOBJECT tmp;
|
|
if (SQ_FAILED(toSquirrel(it->_value, tmp)))
|
|
return sq_throwerror(v, "failed to get table object");
|
|
if (sqrawexists(actor->_table, it->_key))
|
|
sqsetf(actor->_table, it->_key, tmp);
|
|
else
|
|
sqnewf(actor->_table, it->_key, tmp);
|
|
} else {
|
|
warning("load actor: key '%s' is unknown", it->_key.c_str());
|
|
}
|
|
}
|
|
}
|
|
return SQ_OK;
|
|
}
|
|
|
|
static SQRESULT loadObject(Common::SharedPtr<Object> obj, const Common::JSONObject &json) {
|
|
HSQUIRRELVM v = g_twp->getVm();
|
|
int state = 0;
|
|
if (json.contains("_state"))
|
|
state = json["_state"]->asIntegerNumber();
|
|
if (obj->_node)
|
|
obj->setState(state, true);
|
|
else
|
|
warning("obj '%s' has no node !", obj->_key.c_str());
|
|
bool touchable = true;
|
|
if (json.contains("_touchable"))
|
|
touchable = json["_touchable"]->asIntegerNumber() == 1;
|
|
obj->setTouchable(touchable);
|
|
bool hidden = false;
|
|
if (json.contains("_hidden"))
|
|
hidden = json["_hidden"]->asIntegerNumber() == 1;
|
|
obj->_node->setVisible(!hidden);
|
|
|
|
for (auto it = json.begin(); it != json.end(); it++) {
|
|
if ((it->_key == "_state") || (it->_key == "_touchable") || (it->_key == "_hidden")) {
|
|
// discard
|
|
} else if (it->_key == "_pos") {
|
|
obj->_node->setPos(parseVec2(it->_value->asString()));
|
|
} else if (it->_key == "_rotation") {
|
|
obj->_node->setRotation(it->_value->asNumber());
|
|
} else if (it->_key == "_dir") {
|
|
obj->setFacing((Facing)it->_value->asIntegerNumber());
|
|
} else if (it->_key == "_useDir") {
|
|
obj->_useDir = (Direction)it->_value->asIntegerNumber();
|
|
} else if (it->_key == "_usePos") {
|
|
obj->_usePos = parseVec2(it->_value->asString());
|
|
} else if (it->_key == "_offset") {
|
|
obj->_node->setOffset(parseVec2(it->_value->asString()));
|
|
} else if (it->_key == "_renderOffset") {
|
|
obj->_node->setRenderOffset(parseVec2(it->_value->asString()));
|
|
} else if (it->_key == "_roomKey") {
|
|
Object::setRoom(obj, room(it->_value->asString()));
|
|
} else if (!it->_key.hasPrefix("_")) {
|
|
HSQOBJECT tmp;
|
|
if (SQ_FAILED(toSquirrel(it->_value, tmp)))
|
|
return sq_throwerror(v, "failed to get table object");
|
|
if (sqrawexists(obj->_table, it->_key))
|
|
sqsetf(obj->_table, it->_key, tmp);
|
|
else
|
|
sqnewf(obj->_table, it->_key, tmp);
|
|
} else {
|
|
warning("load object (%s): key '%s' is unknown", obj->_key.c_str(), it->_key.c_str());
|
|
}
|
|
}
|
|
|
|
if (sqrawexists(obj->_table, "postLoad"))
|
|
sqcall(obj->_table, "postLoad");
|
|
|
|
return SQ_OK;
|
|
}
|
|
|
|
static void loadPseudoObjects(Common::SharedPtr<Room> room, const Common::JSONObject &json) {
|
|
for (auto it = json.begin(); it != json.end(); it++) {
|
|
Common::SharedPtr<Object> o(object(room, it->_key));
|
|
if (!o)
|
|
warning("load: room '%s' object '%s' not loaded because it has not been found", room->_name.c_str(), it->_key.c_str());
|
|
else
|
|
loadObject(o, it->_value->asObject());
|
|
}
|
|
}
|
|
|
|
static SQRESULT loadRoom(Common::SharedPtr<Room> room, const Common::JSONObject &json) {
|
|
HSQUIRRELVM v = g_twp->getVm();
|
|
for (auto it = json.begin(); it != json.end(); it++) {
|
|
if (it->_key == "_pseudoObjects") {
|
|
loadPseudoObjects(room, it->_value->asObject());
|
|
} else {
|
|
if (!it->_key.hasPrefix("_")) {
|
|
Common::SharedPtr<Object> o(object(room, it->_key));
|
|
if (!o) {
|
|
HSQOBJECT tmp;
|
|
if (SQ_FAILED(toSquirrel(it->_value, tmp)))
|
|
return sq_throwerror(v, "failed to get table object");
|
|
if (sqrawexists(room->_table, it->_key))
|
|
sqsetf(room->_table, it->_key, tmp);
|
|
else {
|
|
sqnewf(room->_table, it->_key, tmp);
|
|
}
|
|
} else {
|
|
loadObject(o, it->_value->asObject());
|
|
}
|
|
} else {
|
|
warning("Load room: key '{%s}' is unknown", it->_key.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sqrawexists(room->_table, "postLoad"))
|
|
sqcall(room->_table, "postLoad");
|
|
|
|
return SQ_OK;
|
|
}
|
|
|
|
static void setActor(const Common::String &key) {
|
|
if (key.empty())
|
|
return;
|
|
for (size_t i = 0; i < g_twp->_actors.size(); i++) {
|
|
Common::SharedPtr<Object> a = g_twp->_actors[i];
|
|
if (a->_key == key) {
|
|
g_twp->setActor(a, false);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int32 computeHash(byte *data, size_t n) {
|
|
int32 result = 0x6583463;
|
|
for (size_t i = 0; i < n; i++) {
|
|
result += (int32)data[i];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool SaveGameManager::loadGame(Common::SeekableReadStream &stream) {
|
|
Common::Array<byte> data(500016);
|
|
stream.read(data.data(), data.size());
|
|
Common::BTEACrypto::decrypt((uint32 *)data.data(), data.size() / 4, savegameKey);
|
|
|
|
MemStream ms;
|
|
if (!ms.open(data.data(), data.size() - 16))
|
|
return false;
|
|
|
|
GGHashMapDecoder decoder;
|
|
Common::JSONValue *jSavegame = decoder.open(&ms);
|
|
if (!jSavegame)
|
|
return false;
|
|
|
|
// dump savegame as json
|
|
if (DebugMan.isDebugChannelEnabled(kDebugGame)) {
|
|
debugC(kDebugGame, "load game: %s", jSavegame->stringify().c_str());
|
|
}
|
|
|
|
const Common::JSONObject &json = jSavegame->asObject();
|
|
long long int version = json["version"]->asIntegerNumber();
|
|
if (version != 2) {
|
|
delete jSavegame;
|
|
error("Cannot load savegame version %lld", version);
|
|
return false;
|
|
}
|
|
uint32 gameTime = (uint32)json["gameTime"]->asNumber();
|
|
|
|
sqcall("preLoad");
|
|
loadGameScene(json["gameScene"]->asObject());
|
|
loadDialog(json["dialog"]->asObject());
|
|
if (SQ_FAILED(loadCallbacks(json["callbacks"]->asObject()))) {
|
|
delete jSavegame;
|
|
return false;
|
|
}
|
|
if (SQ_FAILED(loadGlobals(json["globals"]->asObject()))) {
|
|
delete jSavegame;
|
|
return false;
|
|
}
|
|
if (SQ_FAILED(loadActors(json["actors"]->asObject()))) {
|
|
delete jSavegame;
|
|
return false;
|
|
}
|
|
loadInventory(json["inventory"]);
|
|
if (SQ_FAILED(loadRooms(json["rooms"]->asObject()))) {
|
|
delete jSavegame;
|
|
return false;
|
|
}
|
|
g_twp->_time = (float)gameTime;
|
|
g_twp->setTotalPlayTime(gameTime * 1000);
|
|
g_twp->_inputState.setState((InputStateFlag)json["inputState"]->asIntegerNumber());
|
|
if (SQ_FAILED(loadObjects(json["objects"]->asObject()))) {
|
|
delete jSavegame;
|
|
return false;
|
|
}
|
|
g_twp->setRoom(room(json["currentRoom"]->asString()), true);
|
|
setActor(json["selectedActor"]->asString());
|
|
if (g_twp->_actor)
|
|
g_twp->cameraAt(g_twp->_actor->_node->getPos());
|
|
|
|
HSQUIRRELVM v = g_twp->getVm();
|
|
sqsetf(sqrootTbl(v), "SAVEBUILD", static_cast<int>(json["savebuild"]->asIntegerNumber()));
|
|
|
|
for (auto a : g_twp->_actors) {
|
|
if (sqrawexists(a->_table, "postLoad")) {
|
|
sqcall(a->_table, "postLoad");
|
|
}
|
|
}
|
|
|
|
sqcall("postLoad");
|
|
|
|
delete jSavegame;
|
|
return true;
|
|
}
|
|
|
|
bool SaveGameManager::getSaveGame(Common::SeekableReadStream *stream, SaveGame &savegame) {
|
|
Common::Array<byte> data(stream->size());
|
|
stream->read(data.data(), data.size());
|
|
Common::BTEACrypto::decrypt((uint32 *)data.data(), data.size() / 4, savegameKey);
|
|
savegame.hashData = *(int32 *)(&data[data.size() - 16]);
|
|
savegame.time = *(int32 *)&data[data.size() - 12];
|
|
int32 hashCheck = computeHash(data.data(), data.size() - 16);
|
|
if (savegame.hashData != hashCheck)
|
|
return false;
|
|
|
|
MemStream ms;
|
|
if (!ms.open(data.data(), data.size() - 16))
|
|
return false;
|
|
|
|
GGHashMapDecoder decoder;
|
|
savegame.jSavegame.reset(decoder.open(&ms));
|
|
if (!savegame.jSavegame)
|
|
return false;
|
|
|
|
const Common::JSONObject &jSavegame = savegame.jSavegame->asObject();
|
|
savegame.gameTime = jSavegame["gameTime"]->asNumber();
|
|
savegame.easyMode = jSavegame["easy_mode"]->asIntegerNumber() != 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
void SaveGameManager::loadGameScene(const Common::JSONObject &json) {
|
|
int mode = 0;
|
|
if (json["actorsSelectable"]->asIntegerNumber() != 0)
|
|
mode |= asOn;
|
|
if (json["actorsTempUnselectable"]->asIntegerNumber())
|
|
mode |= asTemporaryUnselectable;
|
|
g_twp->_actorSwitcher._mode = mode;
|
|
// TODO: tmpPrefs().forceTalkieText = json["forceTalkieText"].getInt() != 0;
|
|
const Common::JSONArray &jSelectableActors = json["selectableActors"]->asArray();
|
|
for (size_t i = 0; i < jSelectableActors.size(); i++) {
|
|
const Common::JSONObject &jSelectableActor = jSelectableActors[i]->asObject();
|
|
Common::SharedPtr<Object> act = jSelectableActor.contains("_actorKey") ? actor(jSelectableActor["_actorKey"]->asString()) : nullptr;
|
|
g_twp->_hud->_actorSlots[i].actor = act;
|
|
g_twp->_hud->_actorSlots[i].selectable = jSelectableActor["selectable"]->asIntegerNumber() != 0;
|
|
}
|
|
}
|
|
|
|
void SaveGameManager::loadDialog(const Common::JSONObject &json) {
|
|
debugC(kDebugGame, "loadDialog");
|
|
g_twp->_dialog->_states.clear();
|
|
for (auto it = json.begin(); it != json.end(); it++) {
|
|
Common::String dialog(it->_key);
|
|
// dialog format: mode dialog number actor
|
|
// example: #ChetAgentStreetDialog14reyes
|
|
// mode:
|
|
// ?: once
|
|
// #: showonce
|
|
// &: onceever
|
|
// $: showonceever
|
|
// ^: temponce
|
|
DialogConditionState state = parseState(dialog);
|
|
g_twp->_dialog->_states.push_back(state);
|
|
// TODO: what to do with this dialog value ?
|
|
// let value = property.second.getInt()
|
|
}
|
|
}
|
|
|
|
struct GetHObjects {
|
|
explicit GetHObjects(Common::Array<HSQOBJECT> &objs) : _objs(objs) {}
|
|
|
|
void operator()(HSQOBJECT item) {
|
|
_objs.push_back(item);
|
|
}
|
|
|
|
private:
|
|
Common::Array<HSQOBJECT> &_objs;
|
|
};
|
|
|
|
SQRESULT SaveGameManager::loadCallbacks(const Common::JSONObject &json) {
|
|
HSQUIRRELVM v = g_twp->getVm();
|
|
debugC(kDebugGame, "loadCallbacks");
|
|
g_twp->_callbacks.clear();
|
|
if (!json["callbacks"]->isNull()) {
|
|
const Common::JSONArray &jCallbacks = json["callbacks"]->asArray();
|
|
for (size_t i = 0; i < jCallbacks.size(); i++) {
|
|
const Common::JSONObject &jCallBackHash = jCallbacks[i]->asObject();
|
|
const int id = jCallBackHash["guid"]->asIntegerNumber();
|
|
const float time = jCallBackHash["time"]->isIntegerNumber() ? (float)jCallBackHash["time"]->asIntegerNumber() : 0.f;
|
|
const Common::String name = jCallBackHash["function"]->asString();
|
|
Common::Array<HSQOBJECT> args;
|
|
if (jCallBackHash.contains("param")) {
|
|
HSQOBJECT arg;
|
|
if (SQ_FAILED(toSquirrel(jCallBackHash["param"], arg)))
|
|
return sq_throwerror(v, "failed to get callback arg");
|
|
sqgetitems(arg, GetHObjects(args));
|
|
}
|
|
g_twp->_callbacks.push_back(Common::SharedPtr<Callback>(new Callback(id, time, name, args)));
|
|
}
|
|
}
|
|
g_twp->_resManager->resetIds(json["nextGuid"]->asIntegerNumber());
|
|
return SQ_OK;
|
|
}
|
|
|
|
SQRESULT SaveGameManager::loadGlobals(const Common::JSONObject &json) {
|
|
debugC(kDebugGame, "loadGlobals");
|
|
HSQUIRRELVM v = g_twp->getVm();
|
|
HSQOBJECT g;
|
|
if (SQ_FAILED(sqgetf("g", g)))
|
|
return sq_throwerror(v, "Failed to get globals variable");
|
|
for (auto it = json.begin(); it != json.end(); it++) {
|
|
HSQOBJECT tmp;
|
|
if (SQ_FAILED(toSquirrel(it->_value, tmp)))
|
|
return sq_throwerror(v, "failed to get callback arg");
|
|
sq_addref(v, &tmp);
|
|
sqsetf(g, it->_key, tmp);
|
|
}
|
|
return SQ_OK;
|
|
}
|
|
|
|
SQRESULT SaveGameManager::loadActors(const Common::JSONObject &json) {
|
|
HSQUIRRELVM v = g_twp->getVm();
|
|
for (size_t i = 0; i < g_twp->_actors.size(); i++) {
|
|
Common::SharedPtr<Object> a = g_twp->_actors[i];
|
|
if (a->_key.size() > 0) {
|
|
if (SQ_FAILED(loadActor(a, json[a->_key]->asObject())))
|
|
return sq_throwerror(v, "failed to load actor");
|
|
}
|
|
}
|
|
return SQ_OK;
|
|
}
|
|
|
|
void SaveGameManager::loadInventory(const Common::JSONValue *json) {
|
|
if (json->isObject()) {
|
|
const Common::JSONObject &jInventory = json->asObject();
|
|
const Common::JSONArray &jSlots = jInventory["slots"]->asArray();
|
|
for (int i = 0; i < NUMACTORS; i++) {
|
|
Common::SharedPtr<Object> a(g_twp->_hud->_actorSlots[i].actor);
|
|
if (a) {
|
|
a->_inventory.clear();
|
|
const Common::JSONObject &jSlot = jSlots[i]->asObject();
|
|
if (jSlot.contains("objects")) {
|
|
if (jSlot["objects"]->isArray()) {
|
|
int jiggleCount = 0;
|
|
const Common::JSONArray &jSlotObjects = jSlot["objects"]->asArray();
|
|
for (size_t j = 0; j < jSlotObjects.size(); j++) {
|
|
const Common::JSONValue *jObj = jSlotObjects[j];
|
|
Common::SharedPtr<Object> obj = object(jObj->asString());
|
|
if (!obj)
|
|
warning("inventory obj '%s' not found", jObj->asString().c_str());
|
|
else {
|
|
Object::pickupObject(a, obj);
|
|
obj->_jiggle = jSlot.contains("jiggle") && jSlot["jiggle"]->isArray() && jSlot["jiggle"]->asArray()[jiggleCount++]->asIntegerNumber() != 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
a->_inventoryOffset = jSlot["scroll"]->asIntegerNumber();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SQRESULT SaveGameManager::loadRooms(const Common::JSONObject &json) {
|
|
HSQUIRRELVM v = g_twp->getVm();
|
|
for (auto it = json.begin(); it != json.end(); it++) {
|
|
if (SQ_FAILED(loadRoom(room(it->_key), it->_value->asObject())))
|
|
return sq_throwerror(v, "failed to load room");
|
|
}
|
|
return SQ_OK;
|
|
}
|
|
|
|
SQRESULT SaveGameManager::loadObjects(const Common::JSONObject &json) {
|
|
HSQUIRRELVM v = g_twp->getVm();
|
|
for (auto it = json.begin(); it != json.end(); it++) {
|
|
Common::SharedPtr<Object> o(object(it->_key));
|
|
if (o) {
|
|
if (SQ_FAILED(loadObject(o, it->_value->asObject())))
|
|
return sq_throwerror(v, "failed to load object");
|
|
} else {
|
|
warning("object '%s' not found", it->_key.c_str());
|
|
}
|
|
}
|
|
return SQ_OK;
|
|
}
|
|
|
|
struct JsonCallback {
|
|
bool skipObj;
|
|
bool pseudo;
|
|
HSQOBJECT *rootTable;
|
|
Common::JSONObject *jObj;
|
|
};
|
|
|
|
static Common::JSONValue *tojson(const HSQOBJECT &obj, bool checkId, bool skipObj = false, bool pseudo = false);
|
|
|
|
static void fillMissingProperties(const Common::String &k, HSQOBJECT &oTable, void *data) {
|
|
JsonCallback *params = static_cast<JsonCallback *>(data);
|
|
if ((k.size() > 0) && (!k.hasPrefix("_"))) {
|
|
if (!(params->skipObj && g_twp->_resManager->isObject(getId(oTable)) && (params->pseudo || sqrawexists(*params->rootTable, k)))) {
|
|
Common::JSONValue *json = tojson(oTable, true);
|
|
if (json) {
|
|
(*params->jObj)[k] = json;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct GetJArray {
|
|
explicit GetJArray(Common::JSONArray &arr) : _arr(arr) {}
|
|
|
|
void operator()(HSQOBJECT item) {
|
|
_arr.push_back(tojson(item, true));
|
|
}
|
|
|
|
private:
|
|
Common::JSONArray &_arr;
|
|
};
|
|
|
|
static SQRESULT toObject(Common::JSONObject &jObj, const HSQOBJECT &obj, bool checkId, bool skipObj = false, bool pseudo = false) {
|
|
HSQUIRRELVM v = g_twp->getVm();
|
|
if (checkId) {
|
|
SQInteger id = 0;
|
|
if (sqrawexists(obj, "_id") && SQ_FAILED(sqgetf(obj, "_id", id)))
|
|
return sq_throwerror(v, "Failed to get id");
|
|
if (g_twp->_resManager->isActor(id)) {
|
|
Common::SharedPtr<Object> a(actor(id));
|
|
jObj["_actorKey"] = new Common::JSONValue(a->_key);
|
|
return SQ_OK;
|
|
}
|
|
|
|
if (g_twp->_resManager->isObject(id)) {
|
|
Common::SharedPtr<Object> o(sqobj(id));
|
|
if (!o)
|
|
return SQ_OK;
|
|
jObj["_objectKey"] = new Common::JSONValue(o->_key);
|
|
if (o->_room && o->_room->_pseudo)
|
|
jObj["_roomKey"] = new Common::JSONValue(o->_room->_name);
|
|
return SQ_OK;
|
|
}
|
|
|
|
if (g_twp->_resManager->isRoom(id)) {
|
|
Common::SharedPtr<Room> r(getRoom(id));
|
|
jObj["_roomKey"] = new Common::JSONValue(r->_name);
|
|
return SQ_OK;
|
|
}
|
|
}
|
|
|
|
HSQOBJECT rootTbl = sqrootTbl(v);
|
|
|
|
JsonCallback params;
|
|
params.jObj = &jObj;
|
|
params.pseudo = pseudo;
|
|
params.rootTable = &rootTbl;
|
|
params.skipObj = skipObj;
|
|
return sqgetpairs(obj, fillMissingProperties, ¶ms);
|
|
}
|
|
|
|
static Common::JSONValue *tojson(const HSQOBJECT &obj, bool checkId, bool skipObj, bool pseudo) {
|
|
switch (obj._type) {
|
|
case OT_INTEGER:
|
|
return new Common::JSONValue(static_cast<long long>(sq_objtointeger(&obj)));
|
|
case OT_FLOAT:
|
|
return new Common::JSONValue(sq_objtofloat(&obj));
|
|
case OT_STRING:
|
|
return new Common::JSONValue(sq_objtostring(&obj));
|
|
case OT_NULL:
|
|
return new Common::JSONValue();
|
|
case OT_ARRAY: {
|
|
Common::JSONArray arr;
|
|
sqgetitems(obj, GetJArray(arr));
|
|
return new Common::JSONValue(arr);
|
|
}
|
|
case OT_TABLE: {
|
|
Common::JSONObject jObj;
|
|
toObject(jObj, obj, checkId, skipObj, pseudo);
|
|
return new Common::JSONValue(jObj);
|
|
}
|
|
default:
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
static Common::String removeFileExt(const Common::String &s) {
|
|
size_t i = s.findLastOf('.');
|
|
if (i != Common::String::npos) {
|
|
return s.substr(0, i);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
Common::String toString(const Math::Vector2d &pos) {
|
|
return Common::String::format("{%d,%d}", (int)pos.getX(), (int)pos.getY());
|
|
}
|
|
|
|
// static Common::String getCustomAnim(Common::SharedPtr<Object> actor, const Common::String &name) {
|
|
// return actor->_animNames.contains(name) ? actor->_animNames[name] : name;
|
|
// }
|
|
|
|
// static Common::JSONValue *getCustomAnims(Common::SharedPtr<Object> actor) {
|
|
// Common::JSONArray jAnims;
|
|
// // add head anims
|
|
// jAnims.push_back(new Common::JSONValue(getCustomAnim(actor, HEAD_ANIMNAME)));
|
|
// for (int i = 1; i < 7; i++) {
|
|
// jAnims.push_back(new Common::JSONValue(getCustomAnim(actor, Common::String::format("%s%d", HEAD_ANIMNAME, i))));
|
|
// }
|
|
// // add stand anims
|
|
// jAnims.push_back(new Common::JSONValue(getCustomAnim(actor, Common::String::format("%s%s", STAND_ANIMNAME, "_front"))));
|
|
// jAnims.push_back(new Common::JSONValue(getCustomAnim(actor, Common::String::format("%s%s", STAND_ANIMNAME, "_back"))));
|
|
// jAnims.push_back(new Common::JSONValue(getCustomAnim(actor, Common::String::format("%s%s", STAND_ANIMNAME, "_left"))));
|
|
// jAnims.push_back(new Common::JSONValue(getCustomAnim(actor, Common::String::format("%s%s", STAND_ANIMNAME, "_right"))));
|
|
// // add walk anims
|
|
// jAnims.push_back(new Common::JSONValue(getCustomAnim(actor, Common::String::format("%s%s", WALK_ANIMNAME, "_front"))));
|
|
// jAnims.push_back(new Common::JSONValue(getCustomAnim(actor, Common::String::format("%s%s", WALK_ANIMNAME, "_back"))));
|
|
// jAnims.push_back(new Common::JSONValue(getCustomAnim(actor, Common::String::format("%s%s", WALK_ANIMNAME, "_right"))));
|
|
// jAnims.push_back(new Common::JSONValue(getCustomAnim(actor, Common::String::format("%s%s", WALK_ANIMNAME, "_right"))));
|
|
// // add reach anims
|
|
// const char *dirs[] = {"_front", "_back", "_right", "_right"};
|
|
// for (int i = 0; i < ARRAYSIZE(dirs); i++) {
|
|
// const char *dir = dirs[i];
|
|
// jAnims.push_back(new Common::JSONValue(getCustomAnim(actor, Common::String::format("%s%s%s", REACH_ANIMNAME, "_low", dir))));
|
|
// jAnims.push_back(new Common::JSONValue(getCustomAnim(actor, Common::String::format("%s%s%s", REACH_ANIMNAME, "_med", dir))));
|
|
// jAnims.push_back(new Common::JSONValue(getCustomAnim(actor, Common::String::format("%s%s%s", REACH_ANIMNAME, "_high", dir))));
|
|
// }
|
|
// return new Common::JSONValue(jAnims);
|
|
// }
|
|
|
|
static Common::JSONValue *createJActor(Common::SharedPtr<Object> actor) {
|
|
Common::JSONObject jActor;
|
|
toObject(jActor, actor->_table, false);
|
|
int color = actor->_node->getComputedColor().toInt();
|
|
if (color != Color().toInt())
|
|
jActor["_color"] = new Common::JSONValue((long long int)color);
|
|
// if (!actor->_animNames.empty())
|
|
// jActor["_animations"] = getCustomAnims(actor);
|
|
jActor["_costume"] = new Common::JSONValue(removeFileExt(actor->_costumeName));
|
|
jActor["_dir"] = new Common::JSONValue((long long int)actor->_facing);
|
|
jActor["_lockFacing"] = new Common::JSONValue((long long int)actor->_facingLockValue);
|
|
jActor["_pos"] = new Common::JSONValue(toString(actor->_node->getPos()));
|
|
if (actor->_useDir != dNone)
|
|
jActor["_useDir"] = new Common::JSONValue((long long int)actor->_useDir);
|
|
if (actor->_usePos != Math::Vector2d(0.f, 0.f))
|
|
jActor["_usePos"] = new Common::JSONValue(toString(actor->_usePos));
|
|
if (actor->_node->getRenderOffset() != Math::Vector2d(0.f, 45.f))
|
|
jActor["_renderOffset"] = new Common::JSONValue(toString(actor->_node->getRenderOffset()));
|
|
if (actor->_costumeSheet.size() > 0)
|
|
jActor["_costumeSheet"] = new Common::JSONValue(actor->_costumeSheet);
|
|
if (actor->_room)
|
|
jActor["_roomKey"] = new Common::JSONValue(actor->_room->_name);
|
|
if (!actor->isTouchable() && actor->_node->isVisible())
|
|
jActor["_untouchable"] = new Common::JSONValue(1LL);
|
|
if (!actor->_node->isVisible())
|
|
jActor["_hidden"] = new Common::JSONValue(1LL);
|
|
if (actor->_volume != 1.f)
|
|
jActor["_volume"] = new Common::JSONValue(actor->_volume);
|
|
// result.fields.sort(cmpKey);
|
|
return new Common::JSONValue(jActor);
|
|
}
|
|
|
|
static Common::JSONValue *createJActors() {
|
|
Common::JSONObject jActors;
|
|
for (size_t i = 0; i < g_twp->_actors.size(); i++) {
|
|
Common::SharedPtr<Object> a(g_twp->_actors[i]);
|
|
if (a->_key.size() > 0) {
|
|
jActors[a->_key] = createJActor(a);
|
|
}
|
|
}
|
|
// result.fields.sort(cmpKey);
|
|
return new Common::JSONValue(jActors);
|
|
}
|
|
|
|
static Common::JSONValue *createJCallback(const Callback &callback) {
|
|
Common::JSONObject result;
|
|
result["function"] = new Common::JSONValue(callback.getName());
|
|
result["guid"] = new Common::JSONValue((long long int)callback.getId());
|
|
result["time"] = new Common::JSONValue((long long int)MAX(0.f, (callback.getDuration() - callback.getElapsed())));
|
|
Common::JSONArray jArgs;
|
|
const Common::Array<HSQOBJECT> &args = callback.getArgs();
|
|
for (size_t i = 0; i < args.size(); i++) {
|
|
jArgs.push_back(tojson(args[i], false));
|
|
}
|
|
if (jArgs.size() > 0)
|
|
result["param"] = new Common::JSONValue(jArgs);
|
|
return new Common::JSONValue(result);
|
|
}
|
|
|
|
static Common::JSONValue *createJCallbackArray() {
|
|
Common::JSONArray result;
|
|
for (size_t i = 0; i < g_twp->_callbacks.size(); i++) {
|
|
result.push_back(createJCallback(*g_twp->_callbacks[i]));
|
|
}
|
|
return new Common::JSONValue(result);
|
|
}
|
|
|
|
static Common::JSONValue *createJCallbacks() {
|
|
Common::JSONObject json;
|
|
json["callbacks"] = createJCallbackArray();
|
|
json["nextGuid"] = new Common::JSONValue((long long int)g_twp->_resManager->getCallbackId());
|
|
return new Common::JSONValue(json);
|
|
}
|
|
|
|
static Common::JSONValue *createJRoomKey(Common::SharedPtr<Room> room) {
|
|
return new Common::JSONValue(room ? room->_name : "Void");
|
|
}
|
|
|
|
Common::String createJDlgStateKey(const DialogConditionState &state) {
|
|
Common::String s;
|
|
switch (state.mode) {
|
|
case OnceEver:
|
|
s = "&";
|
|
break;
|
|
case ShowOnce:
|
|
s = "#";
|
|
break;
|
|
case Once:
|
|
s = "?";
|
|
break;
|
|
case ShowOnceEver:
|
|
s = "$";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return Common::String::format("%s%s%d%s", s.c_str(), state.dialog.c_str(), state.line, state.actorKey.c_str());
|
|
}
|
|
|
|
static Common::JSONValue *createJDialog() {
|
|
Common::JSONObject json;
|
|
for (size_t i = 0; i < g_twp->_dialog->_states.size(); i++) {
|
|
const DialogConditionState &state = g_twp->_dialog->_states[i];
|
|
if (state.mode != TempOnce) {
|
|
// TODO: value should be 1 or another value ?
|
|
json[createJDlgStateKey(state)] = new Common::JSONValue(state.mode == ShowOnce ? 2LL : 1LL);
|
|
}
|
|
}
|
|
return new Common::JSONValue(json);
|
|
}
|
|
|
|
static Common::JSONValue *createJEasyMode() {
|
|
HSQOBJECT g;
|
|
if (SQ_FAILED(sqgetf("g", g)))
|
|
error("Failed to get globals variable");
|
|
SQInteger easyMode;
|
|
if (SQ_FAILED(sqgetf(g, "easy_mode", easyMode)))
|
|
error("Failed to get easy_mode variable");
|
|
return new Common::JSONValue((long long int)easyMode);
|
|
}
|
|
|
|
static Common::JSONValue *createJBool(bool value) {
|
|
return new Common::JSONValue(value ? 1LL : 0LL);
|
|
}
|
|
|
|
static Common::JSONValue *createJSelectableActor(const ActorSlot &slot) {
|
|
Common::JSONObject json;
|
|
if (slot.actor) {
|
|
json["_actorKey"] = new Common::JSONValue(slot.actor->_key);
|
|
}
|
|
json["selectable"] = createJBool(slot.selectable);
|
|
return new Common::JSONValue(json);
|
|
}
|
|
|
|
static Common::JSONValue *createJSelectableActors() {
|
|
Common::JSONArray json;
|
|
for (int i = 0; i < NUMACTORS; i++) {
|
|
const ActorSlot &slot = g_twp->_hud->_actorSlots[i];
|
|
json.push_back(createJSelectableActor(slot));
|
|
}
|
|
return new Common::JSONValue(json);
|
|
}
|
|
|
|
static Common::JSONValue *createJGameScene() {
|
|
bool actorsSelectable = asOn & g_twp->_actorSwitcher._mode;
|
|
bool actorsTempUnselectable = asTemporaryUnselectable & g_twp->_actorSwitcher._mode;
|
|
Common::JSONObject json;
|
|
json["actorsSelectable"] = createJBool(actorsSelectable);
|
|
json["actorsTempUnselectable"] = createJBool(actorsTempUnselectable);
|
|
// TODO: json["forceTalkieText"] = createJBool(tmpPrefs().forceTalkieText);
|
|
json["selectableActors"] = createJSelectableActors();
|
|
return new Common::JSONValue(json);
|
|
}
|
|
|
|
static Common::JSONValue *createJGlobals() {
|
|
HSQOBJECT g;
|
|
if (SQ_FAILED(sqgetf("g", g)))
|
|
error("Failed to get globals variable");
|
|
// result.fields.sort(cmpKey);
|
|
return tojson(g, false);
|
|
}
|
|
|
|
static Common::JSONValue *createJInventory(const ActorSlot &slot) {
|
|
Common::JSONObject json;
|
|
if (!slot.actor) {
|
|
json["scroll"] = new Common::JSONValue(0LL);
|
|
} else {
|
|
Common::JSONArray objKeys;
|
|
Common::JSONArray jiggleArray;
|
|
bool anyJiggle = false;
|
|
for (size_t i = 0; i < slot.actor->_inventory.size(); i++) {
|
|
Common::SharedPtr<Object> obj = slot.actor->_inventory[i];
|
|
if (obj->_jiggle)
|
|
anyJiggle = true;
|
|
jiggleArray.push_back(createJBool(obj->_jiggle));
|
|
objKeys.push_back(new Common::JSONValue(obj->_key));
|
|
}
|
|
|
|
if (objKeys.size() > 0) {
|
|
json["objects"] = new Common::JSONValue(objKeys);
|
|
}
|
|
json["scroll"] = new Common::JSONValue((long long int)slot.actor->_inventoryOffset);
|
|
if (anyJiggle)
|
|
json["jiggle"] = new Common::JSONValue(jiggleArray);
|
|
}
|
|
return new Common::JSONValue(json);
|
|
}
|
|
|
|
static Common::JSONValue *createJInventory() {
|
|
Common::JSONArray slots;
|
|
for (int i = 0; i < NUMACTORS; i++) {
|
|
const ActorSlot &slot = g_twp->_hud->_actorSlots[i];
|
|
slots.push_back(createJInventory(slot));
|
|
}
|
|
Common::JSONObject json;
|
|
json["slots"] = new Common::JSONValue(slots);
|
|
return new Common::JSONValue(json);
|
|
}
|
|
|
|
static Common::JSONValue *createJObject(HSQOBJECT &table, Common::SharedPtr<Object> obj) {
|
|
Common::JSONObject json;
|
|
toObject(json, table, false);
|
|
if (obj) {
|
|
if (!obj->_node->isVisible())
|
|
json["_hidden"] = new Common::JSONValue(1LL);
|
|
if (obj->_state != 0)
|
|
json["_state"] = new Common::JSONValue((long long int)obj->_state);
|
|
if (obj->_node->isVisible() && !obj->isTouchable())
|
|
json["_touchable"] = new Common::JSONValue(0LL);
|
|
if (obj->_node->getOffset() != Math::Vector2d())
|
|
json["_offset"] = new Common::JSONValue(toString(obj->_node->getOffset()));
|
|
}
|
|
// result.fields.sort(cmpKey)
|
|
return new Common::JSONValue(json);
|
|
}
|
|
|
|
static Common::JSONValue *createJObjects() {
|
|
Common::JSONObject json;
|
|
for (const auto &r : g_twp->_rooms) {
|
|
for (const auto &layer : r->_layers) {
|
|
for (auto &obj : layer->_objects) {
|
|
if (obj->_objType != ObjectType::otNone)
|
|
continue;
|
|
if (obj->_room && obj->_room->_pseudo)
|
|
continue;
|
|
json[obj->_key] = createJObject(obj->_table, obj);
|
|
}
|
|
}
|
|
}
|
|
// result.fields.sort(cmpKey)
|
|
return new Common::JSONValue(json);
|
|
}
|
|
|
|
static Common::JSONValue *createJPseudoObjects(Common::SharedPtr<Room> room) {
|
|
Common::JSONObject json;
|
|
for (auto &layer : room->_layers) {
|
|
for (auto &obj : layer->_objects) {
|
|
if (obj->_objType != ObjectType::otNone)
|
|
continue;
|
|
if (obj->_room && obj->_room->_pseudo)
|
|
continue;
|
|
json[obj->_key] = createJObject(obj->_table, obj);
|
|
}
|
|
}
|
|
|
|
// result.fields.sort(cmpKey)
|
|
return new Common::JSONValue(json);
|
|
}
|
|
|
|
static Common::JSONValue *createJRoom(Common::SharedPtr<Room> room) {
|
|
Common::JSONObject json;
|
|
toObject(json, room->_table, false, true, room->_pseudo);
|
|
if (room->_pseudo) {
|
|
json["_pseudoObjects"] = createJPseudoObjects(room);
|
|
}
|
|
// result.fields.sort(cmpKey)
|
|
return new Common::JSONValue(json);
|
|
}
|
|
|
|
static Common::JSONValue *createJRooms() {
|
|
Common::JSONObject json;
|
|
for (size_t i = 0; i < g_twp->_rooms.size(); i++) {
|
|
Common::SharedPtr<Room> r(g_twp->_rooms[i]);
|
|
if (r)
|
|
json[r->_name] = createJRoom(r);
|
|
}
|
|
// result.fields.sort(cmpKey);
|
|
return new Common::JSONValue(json);
|
|
}
|
|
|
|
static Common::JSONValue *createSaveGame() {
|
|
Common::JSONObject json;
|
|
json["actors"] = createJActors();
|
|
json["callbacks"] = createJCallbacks();
|
|
json["currentRoom"] = createJRoomKey(g_twp->_room);
|
|
json["dialog"] = createJDialog();
|
|
json["easy_mode"] = createJEasyMode();
|
|
json["gameGUID"] = new Common::JSONValue("");
|
|
json["gameScene"] = createJGameScene();
|
|
json["gameTime"] = new Common::JSONValue(g_twp->_time);
|
|
json["globals"] = createJGlobals();
|
|
json["inputState"] = new Common::JSONValue((long long int)g_twp->_inputState.getState());
|
|
json["inventory"] = createJInventory();
|
|
json["objects"] = createJObjects();
|
|
json["rooms"] = createJRooms();
|
|
json["savebuild"] = new Common::JSONValue(958LL);
|
|
json["savetime"] = new Common::JSONValue((long long)getTime());
|
|
json["selectedActor"] = new Common::JSONValue(g_twp->_actor ? g_twp->_actor->_key : "");
|
|
json["version"] = new Common::JSONValue((long long int)2);
|
|
return new Common::JSONValue(json);
|
|
}
|
|
|
|
void SaveGameManager::saveGame(Common::WriteStream *ws) {
|
|
Common::JSONValue *data = createSaveGame();
|
|
|
|
if (DebugMan.isDebugChannelEnabled(kDebugGame)) {
|
|
debugC(kDebugGame, "save game: %s", data->stringify().c_str());
|
|
}
|
|
|
|
const uint32 fullSize = 500000;
|
|
Common::Array<byte> buffer(fullSize + 16);
|
|
OutMemStream stream;
|
|
stream.open(buffer.data(), buffer.size());
|
|
|
|
GGHashMapEncoder encoder;
|
|
encoder.open(&stream);
|
|
encoder.write(data->asObject());
|
|
|
|
int32 savetime = data->asObject()["savetime"]->asIntegerNumber();
|
|
int32 hash = computeHash(buffer.data(), fullSize);
|
|
byte marker = (8 - ((fullSize + 9) % 8));
|
|
|
|
// write at the end 16 bytes: hashdata (4 bytes) + savetime (4 bytes) + marker (8 bytes)
|
|
int32 *p = (int32 *)(buffer.data() + fullSize);
|
|
p[0] = hash;
|
|
p[1] = savetime;
|
|
memset(&p[2], marker, 8);
|
|
|
|
// then encode data
|
|
Common::BTEACrypto::encrypt((uint32 *)buffer.data(), buffer.size() / 4, savegameKey);
|
|
|
|
// and write data
|
|
ws->write(buffer.data(), buffer.size());
|
|
delete data;
|
|
|
|
sqcall("postSave");
|
|
}
|
|
|
|
} // namespace Twp
|