/* 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 "graphics/cursorman.h"
#include "graphics/surface.h"
#include "graphics/primitives.h"
#include "dgds/dgds.h"
#include "dgds/includes.h"
#include "dgds/resource.h"
#include "dgds/request.h"
#include "dgds/scene.h"
#include "dgds/ads.h"
#include "dgds/menu.h"
#include "dgds/font.h"
#include "dgds/globals.h"
#include "dgds/image.h"
#include "dgds/inventory.h"
#include "dgds/minigames/china_tank.h"
#include "dgds/minigames/china_train.h"
#include "dgds/minigames/dragon_arcade.h"
#include "dgds/dragon_native.h"
#include "dgds/hoc_intro.h"
namespace Dgds {
template static Common::String _dumpStructList(const Common::String &indent, const Common::String &name, const C &list) {
if (list.empty())
return "";
const Common::String nextind = indent + " ";
Common::String str = Common::String::format("\n%s%s=", Common::String(indent + " ").c_str(), name.c_str());
for (const auto &s : list) {
str += "\n";
str += s.dump(nextind);
}
return str;
}
Common::String _sceneConditionStr(SceneCondition cflag) {
Common::String ret;
if (cflag & kSceneCondOr)
return "or";
if (cflag & kSceneCondSceneState)
ret += "state|";
if (cflag & kSceneCondNeedItemSceneNum)
ret += "itemsnum|";
if (cflag & kSceneCondNeedItemQuality)
ret += "quality|";
if ((cflag & (kSceneCondSceneState | kSceneCondNeedItemSceneNum | kSceneCondNeedItemQuality)) == 0)
ret += "global|";
cflag = static_cast(cflag & ~(kSceneCondSceneState | kSceneCondNeedItemSceneNum | kSceneCondNeedItemQuality));
if (cflag == kSceneCondNone)
ret += "nocond";
if (cflag & kSceneCondLessThan)
ret += "less";
if (cflag & kSceneCondEqual)
ret += "equal";
if (cflag & kSceneCondNegate)
ret += "-not";
if (cflag & kSceneCondAbsVal)
ret += "(abs)";
return ret;
}
Common::String SceneConditions::dump(const Common::String &indent) const {
return Common::String::format("%sSceneCondition", indent.c_str(),
_flags, _sceneConditionStr(_flags).c_str(), _num, _val);
}
Common::String HotArea::dump(const Common::String &indent) const {
Common::String str = Common::String::format("%sHotArea<%s num %d cursor %d unk1 %d unk2 %d",
indent.c_str(), _rect.dump("").c_str(), _num, _cursorNum, _otherCursorNum, _objInteractionListFlag);
str += _dumpStructList(indent, "enableConditions", enableConditions);
str += _dumpStructList(indent, "onRClickOps", onRClickOps);
str += _dumpStructList(indent, "onLDownOps", onLDownOps);
str += _dumpStructList(indent, "onLClickOps", onLClickOps);
str += "\n";
str += indent + ">";
return str;
}
static Common::String _sceneOpCodeName(SceneOpCode code) {
switch (code) {
case kSceneOpNone: return "none";
case kSceneOpChangeScene: return "changeScene";
case kSceneOpNoop: return "noop";
case kSceneOpGlobal: return "global";
case kSceneOpSegmentStateOps: return "sceneOpSegmentStateOps";
case kSceneOpSetItemAttr: return "setItemAttr";
case kSceneOpSetDragItem: return "setDragItem";
case kSceneOpOpenInventory: return "openInventory";
case kSceneOpShowDlg: return "showdlg";
case kSceneOpShowInvButton: return "showInvButton";
case kSceneOpHideInvButton: return "hideInvButton";
case kSceneOpEnableTrigger: return "enabletrigger";
case kSceneOpChangeSceneToStored: return "changeSceneToStored";
case kSceneOpAddFlagToDragItem: return "addFlagToDragItem";
case kSceneOpMoveItemsBetweenScenes: return "moveItemsBetweenScenes";
case kSceneOpOpenInventoryZoom: return "openInventoryZoom";
case kSceneOpShowClock: return "sceneOpShowClock";
case kSceneOpHideClock: return "sceneOpHideClock";
case kSceneOpShowMouse: return "sceneOpShowMouse";
case kSceneOpHideMouse: return "sceneOpHideMouse";
case kSceneOpLoadTalkDataAndSetFlags: return "sceneOpLoadTalkDataAndSetFlags";
case kSceneOpDrawVisibleTalkHeads: return "sceneOpDrawVisibleTalksHeads";
case kSceneOpLoadTalkData: return "sceneOpLoadTalkData";
case kSceneOpLoadDDSData: return "sceneOpLoadDDSData";
case kSceneOpFreeDDSData: return "sceneOpFreeDDSData";
case kSceneOpFreeTalkData: return "sceneOpFreeTalkData";
default:
break;
}
if (DgdsEngine::getInstance()->getGameId() == GID_DRAGON) {
switch (code) {
case kSceneOpPasscode: return "passcode";
case kSceneOpMeanwhile: return "meanwhile";
case kSceneOpOpenGameOverMenu: return "openGameOverMenu";
case kSceneOpTiredDialog: return "openTiredDialog";
case kSceneOpArcadeTick: return "sceneOpArcadeTick";
case kSceneOpDrawDragonCountdown1: return "drawDragonCountdown1";
case kSceneOpDrawDragonCountdown2: return "drawDragonCountdown2";
case kSceneOpOpenPlaySkipIntroMenu: return "openPlaySkipIntroMovie";
case kSceneOpOpenBetterSaveGameMenu: return "openBetterSaveGameMenu";
default:
break;
}
} else if (DgdsEngine::getInstance()->getGameId() == GID_HOC) {
switch (code) {
case kSceneOpChinaTankInit: return "tankInit";
case kSceneOpChinaTankEnd: return "tankEnd";
case kSceneOpChinaTankTick: return "tankTick";
case kSceneOpChinaScrollLeft: return "scrollLeft";
case kSceneOpChinaScrollRight: return "scrollRight";
case kSceneOpShellGameInit: return "shellGameInit";
case kSceneOpShellGameEnd: return "shellGameEnd";
case kSceneOpShellGameTick: return "shellGameTick";
case kSceneOpChinaTrainInit: return "trainInit";
case kSceneOpChinaTrainEnd: return "trainEnd";
case kSceneOpChinaTrainTick: return "trainTick";
case kSceneOpChinaOpenGameOverMenu: return "gameOverMenu";
case kSceneOpChinaOpenSkipCreditsMenu: return "skipCreditsMenu";
case kSceneOpChinaOnIntroInit: return "chinaOnIntroInit";
case kSceneOpChinaOnIntroTick: return "chinaOnIntroTick";
case kSceneOpChinaOnIntroEnd: return "chinaOnIntroEnd";
default:
break;
}
} else if (DgdsEngine::getInstance()->getGameId() == GID_WILLY) {
switch (code) {
case kSceneOpOpenBeamishGameOverMenu: return "openGameOverMenu";
case kSceneOpOpenBeamishOpenSkipCreditsMenu: return "skipCreditsMenu";
default:
break;
}
}
return Common::String::format("sceneOp%d", (int)code);
}
Common::String SceneOp::dump(const Common::String &indent) const {
Common::String argsStr;
if (_args.empty()) {
argsStr = "[]";
} else {
argsStr = "[";
for (uint i : _args)
argsStr += Common::String::format("%d ", i);
argsStr.setChar(']', argsStr.size() - 1);
}
Common::String str = Common::String::format("%sSceneOp";
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 %d quality %d",
indent.c_str(), super.c_str(), indent.c_str(), _altCursor,
_iconNum, _inSceneNum, _flags, _quality);
str += _dumpStructList(indent, "onDragFinishedOps", onDragFinishedOps);
str += _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());
uint16 val = s->readUint16LE();
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._otherCursorNum = s->readUint16LE();
else
dst._otherCursorNum = 0;
if (isVersionOver(" 1.218")) {
dst._objInteractionListFlag = s->readUint16LE();
if (dst._objInteractionListFlag) {
dst._rect = DgdsRect();
}
} else {
dst._objInteractionListFlag = 0;
}
readConditionList(s, dst.enableConditions);
readOpList(s, dst.onRClickOps);
readOpList(s, dst.onLDownOps);
readOpList(s, dst.onLClickOps);
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 & 0x7fff) > 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::Array