/* 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 .
*
*/
#include "common/debug.h"
#include "common/endian.h"
#include "common/file.h"
#include "common/rect.h"
#include "common/system.h"
#include "common/util.h"
#include "dgds/dgds.h"
#include "dgds/includes.h"
#include "dgds/resource.h"
#include "dgds/scene.h"
#include "dgds/ads.h"
#include "dgds/globals.h"
#include "dgds/inventory.h"
#include "dgds/debug_util.h"
#include "dgds/game_palettes.h"
namespace Dgds {
//
// The number of frames between mouse down and the action changing:
// short long
// L button: use -> pick up
// R button: look -> target
//
static const int MOUSE_DOWN_TIMEOUT = 3;
Common::String HotArea::dump(const Common::String &indent) const {
Common::String str = Common::String::format("%sHotArea<%s num %d cursor %d cursor2 %d interactionRectNum %d",
indent.c_str(), _rect.dump("").c_str(), _num, _cursorNum, _cursorNum2, _objInteractionRectNum);
str += DebugUtil::dumpStructList(indent, "enableConditions", enableConditions);
str += DebugUtil::dumpStructList(indent, "onLookOps", onLookOps);
str += DebugUtil::dumpStructList(indent, "onPickUpOps", onPickUpOps);
str += DebugUtil::dumpStructList(indent, "onUseOps", onUseOps);
str += "\n";
str += indent + ">";
return str;
}
Common::String GameItem::dump(const Common::String &indent) const {
Common::String super = HotArea::dump(indent + " ");
Common::String str = Common::String::format(
"%sGameItem<\n%s\n%saltCursor %d icon %d sceneNum %d flags 0x%x quality %d",
indent.c_str(), super.c_str(), indent.c_str(), _altCursor,
_iconNum, _inSceneNum, _flags, _quality);
str += DebugUtil::dumpStructList(indent, "onDragFinishedOps", onDragFinishedOps);
str += DebugUtil::dumpStructList(indent, "onBothButtonsOps", onBothButtonsOps);
str += "\n";
str += indent + ">";
return str;
}
Common::String MouseCursor::dump(const Common::String &indent) const {
return Common::String::format("%sMouseCursor<%d %d>", indent.c_str(), _hot.x, _hot.y);
}
Common::String ObjectInteraction::dump(const Common::String &indent) const {
Common::String str = Common::String::format("%sObjectInteraction";
return str;
}
Common::String SceneTrigger::dump(const Common::String &indent) const {
Common::String str = Common::String::format("%sSceneTrigger";
return str;
}
Common::String PerSceneGlobal::dump(const Common::String &indent) const {
return Common::String::format("%sPerSceneGlobal", indent.c_str(), _num, _sceneNo, _val);
}
// //////////////////////////////////// //
//
// Check that a list length seems "sensible" so we can crash with
// a nice error message instead of crash trying to allocate a
// massive list.
//
static void _checkListNotTooLong(uint16 len, const char *list_type) {
if (len > 1000)
error("Too many %s in list (%d), scene data is likely corrupt.", list_type, len);
}
Scene::Scene() : _magic(0) {
}
bool Scene::isVersionOver(const char *version) const {
assert(!_version.empty());
return strncmp(_version.c_str(), version, _version.size()) > 0;
}
bool Scene::isVersionUnder(const char *version) const {
assert(!_version.empty());
return strncmp(_version.c_str(), version, _version.size()) < 0;
}
bool Scene::readConditionList(Common::SeekableReadStream *s, Common::Array &list) const {
uint16 num = s->readUint16LE();
_checkListNotTooLong(num, "scene conditions");
for (uint16 i = 0; i < num; i++) {
uint16 cnum = s->readUint16LE();
SceneCondition cond = static_cast(s->readUint16LE());
int16 val = s->readSint16LE();
list.push_back(SceneConditions(cnum, cond, val));
}
return !s->err();
}
bool Scene::readHotArea(Common::SeekableReadStream *s, HotArea &dst) const {
dst._rect.x = s->readUint16LE();
dst._rect.y = s->readUint16LE();
dst._rect.width = s->readUint16LE();
dst._rect.height = s->readUint16LE();
dst._num = s->readUint16LE();
dst._cursorNum = s->readUint16LE();
if (isVersionOver(" 1.217"))
dst._cursorNum2 = s->readUint16LE();
else
dst._cursorNum2 = 0;
if (isVersionOver(" 1.218")) {
dst._objInteractionRectNum = s->readUint16LE();
if (dst._objInteractionRectNum) {
dst._rect = DgdsRect();
}
} else {
dst._objInteractionRectNum = 0;
}
readConditionList(s, dst.enableConditions);
readOpList(s, dst.onLookOps);
readOpList(s, dst.onPickUpOps);
readOpList(s, dst.onUseOps);
return !s->err();
}
bool Scene::readHotAreaList(Common::SeekableReadStream *s, Common::List &list) const {
uint16 num = s->readUint16LE();
_checkListNotTooLong(num, "hot areas");
for (uint16 i = 0; i < num; i++) {
HotArea dst;
readHotArea(s, dst);
list.push_back(dst);
}
return !s->err();
}
bool Scene::readGameItemList(Common::SeekableReadStream *s, Common::Array &list) const {
uint16 num = s->readUint16LE();
_checkListNotTooLong(num, "game items");
list.resize(num);
for (GameItem &dst : list) {
readHotArea(s, dst);
}
for (GameItem &dst : list) {
dst._iconNum = s->readUint16LE();
dst._inSceneNum = s->readUint16LE();
dst._quality = s->readUint16LE();
if (!isVersionUnder(" 1.211"))
dst._flags = s->readUint16LE() & 0xfffe;
if (!isVersionUnder(" 1.204")) {
dst._altCursor = s->readUint16LE();
readOpList(s, dst.onDragFinishedOps);
readOpList(s, dst.onBothButtonsOps);
}
}
return !s->err();
}
bool Scene::readMouseHotspotList(Common::SeekableReadStream *s, Common::Array &list) const {
uint16 num = s->readUint16LE();
uint16 hotX, hotY;
_checkListNotTooLong(num, "mouse hotspots");
for (uint16 i = 0; i < num; i++) {
hotX = s->readUint16LE();
hotY = s->readUint16LE();
list.push_back(MouseCursor(hotX, hotY));
}
return !s->err();
}
bool Scene::readObjInteractionList(Common::SeekableReadStream *s, Common::Array &list) const {
uint16 num = s->readUint16LE();
_checkListNotTooLong(num, "interactions");
for (uint16 i = 0; i < num; i++) {
uint16 dropped, target;
if (!isVersionOver(" 1.205")) {
target = s->readUint16LE();
dropped = s->readUint16LE();
target += s->readUint16LE();
} else {
dropped = s->readUint16LE();
target = s->readUint16LE();
}
list.push_back(ObjectInteraction(dropped, target));
readOpList(s, list.back().opList);
}
return !s->err();
}
bool Scene::readOpList(Common::SeekableReadStream *s, Common::Array &list) const {
uint16 nitems = s->readUint16LE();
_checkListNotTooLong(nitems, "scene ops");
list.resize(nitems);
for (SceneOp &dst : list) {
readConditionList(s, dst._conditionList);
dst._opCode = static_cast(s->readUint16LE());
if ((dst._opCode & ~kSceneOpHasConditionalOpsFlag) > kSceneOpMaxCode || dst._opCode == kSceneOpNone)
error("Unexpected scene opcode %d", (int)dst._opCode);
uint16 nvals = s->readUint16LE();
_checkListNotTooLong(nvals, "scene op args");
for (uint16 i = 0; i < nvals / 2; i++) {
dst._args.push_back(s->readUint16LE());
}
}
return !s->err();
}
bool Scene::readDialogList(Common::SeekableReadStream *s, Common::List