mirror of
https://github.com/scummvm/scummvm.git
synced 2025-04-02 10:52:32 -04:00
2412 lines
72 KiB
C++
2412 lines
72 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/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<class C> 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<SceneCondition>(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<flg 0x%02x(%s) num %d val %d>", 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<op: %s args: %s", indent.c_str(), _sceneOpCodeName(_opCode).c_str(), argsStr.c_str());
|
|
|
|
str += _dumpStructList(indent, "conditionList", _conditionList);
|
|
if (!_conditionList.empty()) {
|
|
str += "\n";
|
|
str += indent;
|
|
}
|
|
str += ">";
|
|
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<dropped %d target %d", indent.c_str(), _droppedItemNum, _targetItemNum);
|
|
|
|
str += _dumpStructList(indent, "opList", opList);
|
|
str += "\n";
|
|
str += indent + ">";
|
|
return str;
|
|
}
|
|
|
|
|
|
Common::String SceneTrigger::dump(const Common::String &indent) const {
|
|
Common::String str = Common::String::format("%sSceneTrigger<num %d %s %d", indent.c_str(), _num, _enabled ? "enabled" : "disabled", _timesToCheckBeforeRunning);
|
|
str += _dumpStructList(indent, "conditionList", conditionList);
|
|
str += _dumpStructList(indent, "opList", sceneOpList);
|
|
str += "\n";
|
|
str += indent + ">";
|
|
return str;
|
|
}
|
|
|
|
|
|
Common::String PerSceneGlobal::dump(const Common::String &indent) const {
|
|
return Common::String::format("%sPerSceneGlobal<num %d scene %d val %d>", 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<SceneConditions> &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<SceneCondition>(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<HotArea> &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<GameItem> &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<MouseCursor> &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<ObjectInteraction> &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<SceneOp> &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<SceneOpCode>(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<Dialog> &list, int16 filenum /* = 0 */) const {
|
|
// Some data on this format here https://www.oldgamesitalia.net/forum/index.php?showtopic=24055&st=25&p=359214&#entry359214
|
|
|
|
uint16 nitems = s->readUint16LE();
|
|
_checkListNotTooLong(nitems, "dialogs");
|
|
uint startsize = list.size();
|
|
list.resize(startsize + nitems);
|
|
|
|
for (uint i = startsize; i < list.size(); i++) {
|
|
Dialog &dst = list[i];
|
|
dst._num = s->readUint16LE();
|
|
dst._fileNum = filenum;
|
|
dst._rect.x = s->readUint16LE();
|
|
dst._rect.y = s->readUint16LE();
|
|
dst._rect.width = s->readUint16LE();
|
|
dst._rect.height = s->readUint16LE();
|
|
dst._bgColor = s->readUint16LE();
|
|
dst._fontColor = s->readUint16LE(); // 0 = black, 0xf = white
|
|
if (isVersionUnder(" 1.209")) {
|
|
dst._selectionBgCol = dst._bgColor;
|
|
dst._selectonFontCol = dst._fontColor;
|
|
} else {
|
|
dst._selectionBgCol = s->readUint16LE();
|
|
dst._selectonFontCol = s->readUint16LE();
|
|
}
|
|
dst._fontSize = s->readUint16LE(); // 01 = 8x8, 02 = 6x6, 03 = 4x5
|
|
if (isVersionUnder(" 1.210")) {
|
|
dst._flags = static_cast<DialogFlags>(s->readUint16LE());
|
|
} else {
|
|
// Game reads a 32 bit int but then truncates anyway..
|
|
// probably never used the full thing in SDS files
|
|
// as most higher bits are render state.
|
|
dst._flags = static_cast<DialogFlags>(s->readUint32LE() & 0xffff);
|
|
}
|
|
|
|
dst._frameType = static_cast<DialogFrameType>(s->readUint16LE());
|
|
dst._time = s->readUint16LE();
|
|
if (isVersionOver(" 1.215")) {
|
|
dst._nextDialogFileNum = s->readUint16LE();
|
|
} else {
|
|
dst._nextDialogFileNum = 0;
|
|
}
|
|
if (isVersionOver(" 1.207")) {
|
|
dst._nextDialogDlgNum = s->readUint16LE();
|
|
}
|
|
|
|
if (isVersionOver(" 1.216")) {
|
|
dst._unk1 = s->readUint16LE();
|
|
dst._unk2 = s->readUint16LE();
|
|
}
|
|
|
|
uint16 nbytes = s->readUint16LE();
|
|
if (nbytes > 0) {
|
|
dst._str = s->readString('\0', nbytes);
|
|
} else {
|
|
dst._str.clear();
|
|
}
|
|
readDialogActionList(s, dst._action);
|
|
|
|
if (isVersionUnder(" 1.209") && !dst._action.empty()) {
|
|
if (dst._fontColor == 0)
|
|
dst._selectonFontCol = 4;
|
|
else if (dst._fontColor == 0xff)
|
|
dst._fontColor = 7;
|
|
else
|
|
dst._fontColor = dst._fontColor ^ 8;
|
|
}
|
|
}
|
|
|
|
return !s->err();
|
|
}
|
|
|
|
|
|
bool Scene::readTriggerList(Common::SeekableReadStream *s, Common::Array<SceneTrigger> &list) const {
|
|
uint16 num = s->readUint16LE();
|
|
_checkListNotTooLong(num, "triggers");
|
|
for (uint16 i = 0; i < num; i++) {
|
|
list.push_back(SceneTrigger(s->readUint16LE()));
|
|
if (isVersionOver(" 1.219"))
|
|
list.back()._timesToCheckBeforeRunning = s->readUint16LE();
|
|
readConditionList(s, list.back().conditionList);
|
|
readOpList(s, list.back().sceneOpList);
|
|
}
|
|
|
|
return !s->err();
|
|
}
|
|
|
|
bool Scene::readConditionalSceneOpList(Common::SeekableReadStream *s, Common::Array<ConditionalSceneOp> &list) const {
|
|
uint16 num = s->readUint16LE();
|
|
_checkListNotTooLong(num, "conditional scene ops");
|
|
list.resize(num);
|
|
|
|
for (ConditionalSceneOp &dst : list) {
|
|
dst._opCode = s->readUint16LE();
|
|
readConditionList(s, dst._conditionList);
|
|
readOpList(s, dst._opList);
|
|
}
|
|
return !s->err();
|
|
}
|
|
|
|
|
|
bool Scene::readDialogActionList(Common::SeekableReadStream *s, Common::Array<DialogAction> &list) const {
|
|
uint16 num = s->readUint16LE();
|
|
_checkListNotTooLong(num, "dialog actions");
|
|
list.resize(num);
|
|
|
|
// The original initializes a field in the first entry to 1 here, but it seems
|
|
// only used for memory management so we don't need it?
|
|
// if (!list.empty())
|
|
// list[0].val = 1;
|
|
|
|
for (DialogAction &dst : list) {
|
|
dst.strStart = s->readUint16LE();
|
|
dst.strEnd = s->readUint16LE();
|
|
readOpList(s, dst.sceneOpList);
|
|
}
|
|
|
|
return !s->err();
|
|
}
|
|
|
|
|
|
void Scene::setItemAttrOp(const Common::Array<uint16> &args) {
|
|
if (args.size() < 3)
|
|
error("Expect 3 args for item attr opcode.");
|
|
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
for (auto &item : engine->getGDSScene()->getGameItems()) {
|
|
if (item._num != args[0])
|
|
continue;
|
|
|
|
if (args[1] != 0xffff) {
|
|
//bool doDraw = item._inSceneNum != args[1] && engine->getScene()->getNum() == args[1];
|
|
item._inSceneNum = args[1];
|
|
}
|
|
|
|
if (args[2])
|
|
item._quality = args[2];
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Scene::setDragItemOp(const Common::Array<uint16> &args) {
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
|
|
for (auto &item : engine->getGDSScene()->getGameItems()) {
|
|
if (item._num != args[0])
|
|
continue;
|
|
|
|
bool inScene = (item._inSceneNum == engine->getScene()->getNum());
|
|
engine->getScene()->setDragItem(&item);
|
|
if (!inScene)
|
|
item._inSceneNum = engine->getScene()->getNum(); // else do some redraw??
|
|
|
|
Common::Point lastMouse = engine->getLastMouse();
|
|
item._rect.x = lastMouse.x;
|
|
item._rect.y = lastMouse.y;
|
|
engine->setMouseCursor(item._iconNum);
|
|
}
|
|
}
|
|
|
|
void Scene::segmentStateOps(const Common::Array<uint16> &args) {
|
|
ADSInterpreter *interp = DgdsEngine::getInstance()->adsInterpreter();
|
|
|
|
for (uint i = 0; i < args.size(); i += 2) {
|
|
uint16 subop = args[i];
|
|
uint16 arg = args[i + 1];
|
|
if (!subop && !arg)
|
|
return;
|
|
switch (subop) {
|
|
case 1: // Restart
|
|
interp->segmentOrState(arg, 3);
|
|
break;
|
|
case 2: // Start
|
|
interp->segmentOrState(arg, 4);
|
|
break;
|
|
case 3: // Stop
|
|
interp->segmentSetState(arg, 6);
|
|
break;
|
|
case 4: // Pause
|
|
interp->segmentSetState(arg, 5);
|
|
break;
|
|
case 9:
|
|
warning("TODO: Apply segment state 3 to all loaded ADS texts");
|
|
interp->segmentOrState(arg, 3);
|
|
break;
|
|
case 10:
|
|
warning("TODO: Apply segment state 4 to all loaded ADS texts");
|
|
interp->segmentOrState(arg, 4);
|
|
break;
|
|
case 11:
|
|
warning("TODO: Apply segment state 6 to all loaded ADS texts");
|
|
interp->segmentSetState(arg, 6);
|
|
break;
|
|
case 12:
|
|
warning("TODO: Apply segment state 5 to all loaded ADS texts");
|
|
interp->segmentSetState(arg, 5);
|
|
break;
|
|
default:
|
|
error("Unknown scene op 4 sub-opcode %d", subop);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool Scene::runSceneOp(const SceneOp &op) {
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
switch (op._opCode) {
|
|
case kSceneOpChangeScene:
|
|
if (engine->changeScene(op._args[0]))
|
|
return true;
|
|
break;
|
|
case kSceneOpNoop:
|
|
break;
|
|
case kSceneOpGlobal:
|
|
// The globals are held by the GDS scene
|
|
engine->getGDSScene()->globalOps(op._args);
|
|
break;
|
|
case kSceneOpSegmentStateOps:
|
|
SDSScene::segmentStateOps(op._args);
|
|
break;
|
|
case kSceneOpSetItemAttr:
|
|
SDSScene::setItemAttrOp(op._args);
|
|
break;
|
|
case kSceneOpSetDragItem:
|
|
SDSScene::setDragItemOp(op._args);
|
|
break;
|
|
case kSceneOpOpenInventory:
|
|
engine->getInventory()->open();
|
|
// This implicitly changes scene num
|
|
break;
|
|
case kSceneOpShowDlg:
|
|
if (op._args.size() == 1)
|
|
engine->getScene()->showDialog(0, op._args[0]);
|
|
else if (op._args.size() > 1)
|
|
engine->getScene()->showDialog(op._args[0], op._args[1]);
|
|
break;
|
|
case kSceneOpShowInvButton:
|
|
engine->getScene()->addInvButtonToHotAreaList();
|
|
break;
|
|
case kSceneOpHideInvButton:
|
|
engine->getScene()->removeInvButtonFromHotAreaList();
|
|
break;
|
|
case kSceneOpEnableTrigger:
|
|
engine->getScene()->enableTrigger(op._args[0]);
|
|
break;
|
|
case kSceneOpChangeSceneToStored: {
|
|
int16 sceneNo = engine->getGameGlobals()->getGlobal(0x61);
|
|
if (engine->changeScene(sceneNo))
|
|
return true;
|
|
break;
|
|
}
|
|
case kSceneOpAddFlagToDragItem: {
|
|
GameItem *item = engine->getScene()->getDragItem();
|
|
if (item) {
|
|
item->_flags |= 1;
|
|
// TODO: Use hot x/y or just position?
|
|
Common::Point lastMouse = engine->getLastMouseMinusHot();
|
|
item->_rect.x = lastMouse.x;
|
|
item->_rect.y = lastMouse.y;
|
|
}
|
|
break;
|
|
}
|
|
case kSceneOpOpenInventoryZoom:
|
|
engine->getInventory()->setShowZoomBox(true);
|
|
engine->getInventory()->open();
|
|
return true;
|
|
case kSceneOpMoveItemsBetweenScenes: {
|
|
int16 fromScene = engine->getGameGlobals()->getGlobal(0x55);
|
|
int16 toScene = engine->getGameGlobals()->getGlobal(0x54);
|
|
for (auto &item : engine->getGDSScene()->getGameItems()) {
|
|
if (item._inSceneNum == fromScene)
|
|
item._inSceneNum = toScene;
|
|
}
|
|
break;
|
|
}
|
|
case kSceneOpShowClock:
|
|
engine->setShowClock(true);
|
|
break;
|
|
case kSceneOpHideClock:
|
|
engine->setShowClock(false);
|
|
break;
|
|
case kSceneOpShowMouse:
|
|
CursorMan.showMouse(true);
|
|
break;
|
|
case kSceneOpHideMouse:
|
|
CursorMan.showMouse(false);
|
|
break;
|
|
case kSceneOpLoadTalkDataAndSetFlags: // args: tdsnum to load, headnum
|
|
engine->getScene()->loadTalkDataAndSetFlags(op._args[0], op._args[1]);
|
|
break;
|
|
case kSceneOpDrawVisibleTalkHeads: // args: none
|
|
engine->getScene()->updateVisibleTalkers();
|
|
break;
|
|
case kSceneOpLoadTalkData: // args: tds num to load
|
|
engine->getScene()->loadTalkData(op._args[0]);
|
|
break;
|
|
case kSceneOpLoadDDSData: // args: dds num to load
|
|
if (op._args[0])
|
|
engine->getScene()->loadDialogData(op._args[0]);
|
|
break;
|
|
case kSceneOpFreeDDSData: // args: dds num to free
|
|
engine->getScene()->freeDialogData(op._args[0]);
|
|
break;
|
|
case kSceneOpFreeTalkData: // args: tds num to free
|
|
engine->getScene()->freeTalkData(op._args[0]);
|
|
break;
|
|
|
|
default:
|
|
warning("TODO: Implement generic scene op %d", op._opCode);
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*static*/
|
|
bool Scene::runDragonOp(const SceneOp &op) {
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
switch (op._opCode) {
|
|
case kSceneOpPasscode:
|
|
DragonNative::updatePasscodeGlobal();
|
|
break;
|
|
case kSceneOpMeanwhile:
|
|
// TODO: Should we draw "meanwhile" like the original? it just gets overwritten with the image anyway.
|
|
// Probably need to do something here to avoid flashing..
|
|
//engine->_compositionBuffer.fillRect(Common::Rect(SCREEN_WIDTH, SCREEN_HEIGHT), 0);
|
|
break;
|
|
case kSceneOpOpenGameOverMenu:
|
|
engine->setMenuToTrigger(kMenuGameOver);
|
|
break;
|
|
case kSceneOpTiredDialog:
|
|
engine->getInventory()->close();
|
|
engine->getScene()->addAndShowTiredDialog();
|
|
break;
|
|
case kSceneOpArcadeTick:
|
|
// TODO: Add a configuration option to skip arcade sequence?
|
|
// g_system->displayMessageOnOSD(_("Skipping DGDS arcade sequence"));
|
|
// engine->getGameGlobals()->setGlobal(0x21, 6);
|
|
engine->getDragonArcade()->arcadeTick();
|
|
break;
|
|
case kSceneOpDrawDragonCountdown1:
|
|
DragonNative::drawCountdown(FontManager::k4x5Font, 141, 56);
|
|
break;
|
|
case kSceneOpDrawDragonCountdown2:
|
|
DragonNative::drawCountdown(FontManager::k8x8Font, 250, 42);
|
|
break;
|
|
case kSceneOpOpenPlaySkipIntroMenu:
|
|
engine->setMenuToTrigger(kMenuSkipPlayIntro);
|
|
break;
|
|
case kSceneOpOpenBetterSaveGameMenu:
|
|
engine->setMenuToTrigger(kMenuSaveBeforeArcade);
|
|
break;
|
|
default:
|
|
error("Unexpected Dragon scene opcode %d", op._opCode);
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*static*/
|
|
bool Scene::runChinaOp(const SceneOp &op) {
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
switch (op._opCode) {
|
|
case kSceneOpChinaTankInit:
|
|
engine->getChinaTank()->init();
|
|
break;
|
|
case kSceneOpChinaTankEnd:
|
|
engine->getChinaTank()->end();
|
|
break;
|
|
case kSceneOpChinaTankTick:
|
|
engine->getChinaTank()->tick();
|
|
//engine->setMenuToTrigger(kMenuSkipArcade);
|
|
break;
|
|
case kSceneOpShellGameTick:
|
|
engine->getShellGame()->shellGameTick();
|
|
break;
|
|
case kSceneOpShellGameEnd:
|
|
engine->getShellGame()->shellGameEnd();
|
|
break;
|
|
case kSceneOpChinaTrainInit:
|
|
engine->getChinaTrain()->init();
|
|
break;
|
|
case kSceneOpChinaTrainEnd:
|
|
engine->getChinaTrain()->end();
|
|
break;
|
|
case kSceneOpChinaTrainTick:
|
|
engine->getChinaTrain()->tick();
|
|
break;
|
|
case kSceneOpChinaOpenGameOverMenu:
|
|
engine->setMenuToTrigger(kMenuGameOver);
|
|
break;
|
|
case kSceneOpChinaOpenSkipCreditsMenu:
|
|
engine->setMenuToTrigger(kMenuSkipPlayIntro);
|
|
break;
|
|
case kSceneOpChinaOnIntroInit:
|
|
engine->getHocIntro()->init();
|
|
break;
|
|
case kSceneOpChinaOnIntroTick:
|
|
engine->getHocIntro()->tick();
|
|
break;
|
|
case kSceneOpChinaOnIntroEnd:
|
|
engine->getHocIntro()->end();
|
|
break;
|
|
case kSceneOpChinaScrollIntro:
|
|
case kSceneOpChinaScrollLeft:
|
|
case kSceneOpChinaScrollRight:
|
|
// These map to null functions.
|
|
break;
|
|
default:
|
|
warning("TODO: Implement china-specific scene opcode %d (%s)", op._opCode,
|
|
_sceneOpCodeName(op._opCode).c_str());
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Scene::runBeamishOp(const SceneOp &op) {
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
|
|
if (op._opCode & 0x8000) {
|
|
uint16 opcode = op._opCode & 0x7fff;
|
|
for (const ConditionalSceneOp &cop : engine->getScene()->getConditionalOps()) {
|
|
if (cop._opCode == opcode && checkConditions(cop._conditionList)) {
|
|
if (!runOps(cop._opList))
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
switch (op._opCode) {
|
|
case kSceneOpOpenBeamishGameOverMenu:
|
|
engine->setMenuToTrigger(kMenuGameOver);
|
|
break;
|
|
case kSceneOpOpenBeamishOpenSkipCreditsMenu:
|
|
engine->setMenuToTrigger(kMenuSkipPlayIntro);
|
|
break;
|
|
default:
|
|
warning("TODO: Implement beamish-specific scene opcode %d", op._opCode);
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// Note: ops list here is not a reference on purpose, it must be copied.
|
|
// The underlying list might be freed during execution if the scene changes, but
|
|
// we have to finish executing the list if the scene changed into inventory -
|
|
// which could have invalidated the op list pointer. We *don't* finish executing
|
|
// if any other scene change happens.
|
|
//
|
|
// Because scene change can also invalidate the `this` pointer, this is static
|
|
// and the runOp functions fetch the scene through the engine.
|
|
//
|
|
/*static*/
|
|
bool Scene::runOps(const Common::Array<SceneOp> ops, int16 addMinuites /* = 0 */) {
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
bool sceneChanged = false;
|
|
int16 startSceneNum = engine->getScene()->getNum();
|
|
for (const SceneOp &op : ops) {
|
|
if (!checkConditions(op._conditionList))
|
|
continue;
|
|
debug(10, "Exec %s", op.dump("").c_str());
|
|
if (addMinuites) {
|
|
engine->getClock().addGameTime(addMinuites);
|
|
addMinuites = 0;
|
|
}
|
|
if (op._opCode < 100) {
|
|
sceneChanged = runSceneOp(op);
|
|
} else {
|
|
// Game-specific opcode
|
|
switch (engine->getGameId()) {
|
|
case GID_DRAGON:
|
|
sceneChanged = runDragonOp(op);
|
|
break;
|
|
case GID_HOC:
|
|
sceneChanged = runChinaOp(op);
|
|
break;
|
|
case GID_WILLY:
|
|
sceneChanged = runBeamishOp(op);
|
|
break;
|
|
default:
|
|
error("TODO: Implement game-specific scene op for this game");
|
|
}
|
|
}
|
|
|
|
if (sceneChanged)
|
|
break;
|
|
}
|
|
|
|
//
|
|
// The definition of "scene changed" returned by this function is slightly different -
|
|
// for the purpose of continuing to run ops above, we ignore changes to scene 2 (the
|
|
// inventory), but for the purpose of telling the caller, any change means they
|
|
// need to stop as pointers are no longer valid.
|
|
//
|
|
int16 endSceneNum = engine->getScene()->getNum();
|
|
return startSceneNum == endSceneNum;
|
|
}
|
|
|
|
/*static*/
|
|
bool Scene::checkConditions(const Common::Array<SceneConditions> &conds) {
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
|
|
uint cnum = 0;
|
|
while (cnum < conds.size()) {
|
|
const SceneConditions &c = conds[cnum];
|
|
int16 refval = c.getVal();
|
|
int16 checkval = -1;
|
|
SceneCondition cflag = c.getCond();
|
|
// Hit an "or" here means the last result was true.
|
|
if (cflag & kSceneCondOr)
|
|
return true;
|
|
|
|
if (cflag & kSceneCondSceneState) {
|
|
refval = 1;
|
|
checkval = engine->adsInterpreter()->getStateForSceneOp(c.getNum());
|
|
SceneCondition equalOrNegate = static_cast<SceneCondition>(cflag & (kSceneCondEqual | kSceneCondNegate));
|
|
if (equalOrNegate != kSceneCondEqual && equalOrNegate != kSceneCondNegate)
|
|
refval = 0;
|
|
cflag = kSceneCondEqual;
|
|
} else if (cflag & kSceneCondNeedItemQuality || cflag & kSceneCondNeedItemSceneNum) {
|
|
const Common::Array<GameItem> &items = engine->getGDSScene()->getGameItems();
|
|
for (const auto &item : items) {
|
|
if (item._num == c.getNum()) {
|
|
if (cflag & kSceneCondNeedItemSceneNum)
|
|
checkval = item._inSceneNum;
|
|
else // cflag & kSceneCondNeedItemQuality
|
|
checkval = item._quality;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
checkval = engine->getGDSScene()->getGlobal(c.getNum());
|
|
if (!(cflag & kSceneCondAbsVal))
|
|
refval = engine->getGDSScene()->getGlobal((uint16)refval);
|
|
}
|
|
|
|
bool result = false;
|
|
cflag = static_cast<SceneCondition>(cflag & ~(kSceneCondSceneState | kSceneCondNeedItemSceneNum | kSceneCondNeedItemQuality));
|
|
if (cflag == kSceneCondNone)
|
|
cflag = static_cast<SceneCondition>(kSceneCondEqual | kSceneCondNegate);
|
|
if ((cflag & kSceneCondLessThan) && checkval < refval)
|
|
result = true;
|
|
if ((cflag & kSceneCondEqual) && checkval == refval)
|
|
result = true;
|
|
if (cflag & kSceneCondNegate)
|
|
result = !result;
|
|
|
|
debug(11, "Cond: %s -> %s", c.dump("").c_str(), result ? "true": "false");
|
|
|
|
if (!result) {
|
|
// Skip just past the next or, or to the end.
|
|
while (cnum < conds.size() && !(conds[cnum].getCond() & kSceneCondOr))
|
|
cnum++;
|
|
if (cnum >= conds.size())
|
|
return false;
|
|
}
|
|
cnum++;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
bool SDSScene::_dlgWithFlagLo8IsClosing = false;
|
|
DialogFlags SDSScene::_sceneDialogFlags = kDlgFlagNone;
|
|
|
|
SDSScene::SDSScene() : _num(-1), _dragItem(nullptr), _shouldClearDlg(false), _ignoreMouseUp(false), _field6_0x14(0), _rbuttonDown(false), _lbuttonDown(false) {
|
|
}
|
|
|
|
bool SDSScene::load(const Common::String &filename, ResourceManager *resourceManager, Decompressor *decompressor) {
|
|
Common::SeekableReadStream *sceneFile = resourceManager->getResource(filename);
|
|
if (!sceneFile)
|
|
error("Scene file %s not found", filename.c_str());
|
|
|
|
DgdsChunkReader chunk(sceneFile);
|
|
|
|
bool result = false;
|
|
|
|
while (chunk.readNextHeader(EX_SDS, filename)) {
|
|
if (chunk.isContainer()) {
|
|
continue;
|
|
}
|
|
|
|
chunk.readContent(decompressor);
|
|
Common::SeekableReadStream *stream = chunk.getContent();
|
|
|
|
if (chunk.isSection(ID_SDS)) {
|
|
result = parse(stream);
|
|
}
|
|
}
|
|
|
|
delete sceneFile;
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
bool SDSScene::parse(Common::SeekableReadStream *stream) {
|
|
_magic = stream->readUint32LE();
|
|
_version = stream->readString();
|
|
//if (isVersionOver(" 1.211")) { // Dragon
|
|
//if (isVersionOver(" 1.216")) { // HoC
|
|
if (isVersionOver(" 1.224")) { // Beamish
|
|
error("Unsupported scene version '%s'", _version.c_str());
|
|
}
|
|
_num = stream->readUint16LE();
|
|
readOpList(stream, _enterSceneOps);
|
|
readOpList(stream, _leaveSceneOps);
|
|
if (isVersionOver(" 1.206")) {
|
|
readOpList(stream, _preTickOps);
|
|
}
|
|
readOpList(stream, _postTickOps);
|
|
_field6_0x14 = stream->readUint16LE();
|
|
_adsFile = stream->readString();
|
|
readHotAreaList(stream, _hotAreaList);
|
|
readObjInteractionList(stream, _objInteractions1);
|
|
if (isVersionOver(" 1.205")) {
|
|
readObjInteractionList(stream, _objInteractions2);
|
|
}
|
|
if (isVersionUnder(" 1.214")) {
|
|
readDialogList(stream, _dialogs);
|
|
}
|
|
if (isVersionOver(" 1.203")) {
|
|
readTriggerList(stream, _triggers);
|
|
}
|
|
if (isVersionOver(" 1.223")) {
|
|
readConditionalSceneOpList(stream, _conditionalOps);
|
|
}
|
|
|
|
return !stream->err();
|
|
}
|
|
|
|
void SDSScene::unload() {
|
|
_num = 0;
|
|
_enterSceneOps.clear();
|
|
_leaveSceneOps.clear();
|
|
_preTickOps.clear();
|
|
_postTickOps.clear();
|
|
_field6_0x14 = 0;
|
|
_adsFile.clear();
|
|
_hotAreaList.clear();
|
|
_objInteractions1.clear();
|
|
_objInteractions2.clear();
|
|
_dialogs.clear();
|
|
_triggers.clear();
|
|
_talkData.clear();
|
|
_sceneDialogFlags = kDlgFlagNone;
|
|
}
|
|
|
|
|
|
Common::String SDSScene::dump(const Common::String &indent) const {
|
|
Common::String str = Common::String::format("%sSDSScene<num %d %d ads %s", indent.c_str(), _num, _field6_0x14, _adsFile.c_str());
|
|
str += _dumpStructList(indent, "enterSceneOps", _enterSceneOps);
|
|
str += _dumpStructList(indent, "leaveSceneOps", _leaveSceneOps);
|
|
str += _dumpStructList(indent, "preTickOps", _preTickOps);
|
|
str += _dumpStructList(indent, "postTickOps", _postTickOps);
|
|
str += _dumpStructList(indent, "hotAreaList", _hotAreaList);
|
|
str += _dumpStructList(indent, "objInteractions1", _objInteractions1);
|
|
str += _dumpStructList(indent, "objInteractions2", _objInteractions2);
|
|
str += _dumpStructList(indent, "dialogues", _dialogs);
|
|
str += _dumpStructList(indent, "triggers", _triggers);
|
|
|
|
str += "\n";
|
|
str += indent + ">";
|
|
return str;
|
|
}
|
|
|
|
|
|
void SDSScene::enableTrigger(uint16 num, bool enable /* = true */) {
|
|
for (auto &trigger : _triggers) {
|
|
if (trigger.getNum() == num) {
|
|
trigger._enabled = enable;
|
|
return;
|
|
}
|
|
}
|
|
|
|
warning("enableTrigger: Trigger %d not found", num);
|
|
}
|
|
|
|
bool SDSScene::isTriggerEnabled(uint16 num) {
|
|
for (auto &trigger : _triggers) {
|
|
if (trigger.getNum() == num) {
|
|
return trigger._enabled;
|
|
}
|
|
}
|
|
|
|
warning("isTriggerEnabled: Trigger %d not found", num);
|
|
return false;
|
|
}
|
|
|
|
void SDSScene::checkTriggers() {
|
|
for (SceneTrigger &trigger : _triggers) {
|
|
if (!trigger._enabled)
|
|
continue;
|
|
|
|
if (trigger._timesToCheckBeforeRunning) {
|
|
trigger._timesToCheckBeforeRunning--;
|
|
continue;
|
|
}
|
|
|
|
if (!checkConditions(trigger.conditionList))
|
|
continue;
|
|
|
|
trigger._enabled = false;
|
|
bool keepGoing = runOps(trigger.sceneOpList);
|
|
|
|
// If the scene changed, the list is no longer valid. Abort!
|
|
if (!keepGoing)
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
Dialog *SDSScene::loadDialogData(uint16 num) {
|
|
if (num == 0)
|
|
return &_dialogs.front();
|
|
|
|
for (auto &dlg: _dialogs)
|
|
if (dlg._fileNum == num)
|
|
return &dlg;
|
|
|
|
const Common::String filename = Common::String::format("D%d.DDS", num);
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
ResourceManager *resourceManager = engine->getResourceManager();
|
|
Common::SeekableReadStream *dlgFile = resourceManager->getResource(filename);
|
|
if (!dlgFile)
|
|
error("Dialog file %s not found", filename.c_str());
|
|
|
|
DgdsChunkReader chunk(dlgFile);
|
|
Decompressor *decompressor = engine->getDecompressor();
|
|
|
|
bool result = false;
|
|
|
|
while (chunk.readNextHeader(EX_DDS, filename)) {
|
|
if (chunk.isContainer()) {
|
|
continue;
|
|
}
|
|
|
|
chunk.readContent(decompressor);
|
|
Common::SeekableReadStream *stream = chunk.getContent();
|
|
|
|
if (chunk.isSection(ID_DDS)) {
|
|
uint32 magic = stream->readUint32LE();
|
|
if (magic != _magic)
|
|
error("Dialog file magic mismatch %08x vs scene %08x", magic, _magic);
|
|
Common::String fileVersion = stream->readString();
|
|
Common::String fileId = stream->readString();
|
|
// slight hack, set file version while loading
|
|
Common::String oldVer = _version;
|
|
_version = fileVersion;
|
|
result = readDialogList(stream, _dialogs, num);
|
|
_version = oldVer;
|
|
}
|
|
}
|
|
|
|
delete dlgFile;
|
|
|
|
if (!result)
|
|
return nullptr;
|
|
|
|
for (auto &dlg : _dialogs) {
|
|
if (dlg._nextDialogDlgNum && !dlg._nextDialogFileNum) {
|
|
dlg._nextDialogFileNum = num;
|
|
}
|
|
}
|
|
|
|
// TODO: Maybe not this?
|
|
return &_dialogs.front();
|
|
}
|
|
|
|
void SDSScene::freeDialogData(uint16 num) {
|
|
if (!num)
|
|
return;
|
|
|
|
for (int i = 0; i < (int)_dialogs.size(); i++) {
|
|
if (_dialogs[i]._num == num) {
|
|
_dialogs.remove_at(i);
|
|
i--;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SDSScene::readTalkData(Common::SeekableReadStream *s, TalkData &dst) {
|
|
dst._bmpFile = s->readString();
|
|
|
|
uint16 nvals = s->readUint16LE();
|
|
_checkListNotTooLong(nvals, "talk data");
|
|
dst._heads.resize(nvals);
|
|
for (auto &h : dst._heads) {
|
|
h._num = s->readUint16LE();
|
|
h._drawType = s->readUint16LE();
|
|
h._drawCol = s->readUint16LE();
|
|
h._rect.x = s->readUint16LE();
|
|
h._rect.y = s->readUint16LE();
|
|
h._rect.width = s->readUint16LE();
|
|
h._rect.height = s->readUint16LE();
|
|
uint16 nsub = s->readUint16LE();
|
|
_checkListNotTooLong(nsub, "talk head frames");
|
|
h._headFrames.resize(nsub);
|
|
for (auto &sub : h._headFrames) {
|
|
sub._frameNo = s->readUint16LE();
|
|
sub._xoff = s->readUint16LE();
|
|
sub._yoff = s->readUint16LE();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SDSScene::loadTalkData(uint16 num) {
|
|
if (!num)
|
|
return false;
|
|
|
|
for (auto &talk : _talkData) {
|
|
if (talk._num == num)
|
|
return true;
|
|
}
|
|
|
|
const Common::String filename = Common::String::format("T%d.TDS", num);
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
ResourceManager *resourceManager = engine->getResourceManager();
|
|
Common::SeekableReadStream *dlgFile = resourceManager->getResource(filename);
|
|
if (!dlgFile)
|
|
error("Talk file %s not found", filename.c_str());
|
|
|
|
DgdsChunkReader chunk(dlgFile);
|
|
Decompressor *decompressor = engine->getDecompressor();
|
|
|
|
bool result = false;
|
|
|
|
while (chunk.readNextHeader(EX_TDS, filename)) {
|
|
if (chunk.isContainer()) {
|
|
continue;
|
|
}
|
|
|
|
chunk.readContent(decompressor);
|
|
Common::SeekableReadStream *stream = chunk.getContent();
|
|
|
|
if (chunk.isSection(ID_THD)) {
|
|
uint32 magic = stream->readUint32LE();
|
|
if (magic != _magic)
|
|
error("Talk file magic mismatch %08x vs scene %08x", magic, _magic);
|
|
Common::String fileVersion = stream->readString();
|
|
Common::String fileId = stream->readString();
|
|
// slight hack, set file version while loading
|
|
Common::String oldVer = _version;
|
|
_version = fileVersion;
|
|
_talkData.insert_at(0, TalkData());
|
|
result = readTalkData(stream, _talkData.front());
|
|
_talkData.front()._num = num;
|
|
_version = oldVer;
|
|
|
|
Image *img = new Image(resourceManager, decompressor);
|
|
img->loadBitmap(_talkData.front()._bmpFile);
|
|
_talkData.front()._shape.reset(img);
|
|
}
|
|
}
|
|
|
|
delete dlgFile;
|
|
|
|
return result;
|
|
}
|
|
|
|
void SDSScene::freeTalkData(uint16 num) {
|
|
for (int i = 0; i < (int)_talkData.size(); i++) {
|
|
if (_talkData[i]._num == num) {
|
|
_talkData.remove_at(i);
|
|
i--;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SDSScene::updateVisibleTalkers() {
|
|
for (auto &data : _talkData) {
|
|
for (auto &head : data._heads) {
|
|
if (head._flags & kHeadFlagVisible)
|
|
updateHead(head);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void SDSScene::drawHead(Graphics::ManagedSurface *dst, const TalkData &data, const TalkDataHead &head) {
|
|
uint drawtype = head._drawType ? head._drawType : 1;
|
|
if (!data._shape)
|
|
return;
|
|
switch (drawtype) {
|
|
case 1:
|
|
drawHeadType1(dst, head, *data._shape);
|
|
break;
|
|
case 2:
|
|
drawHeadType2(dst, head, *data._shape);
|
|
break;
|
|
case 3:
|
|
drawHeadType3(dst, head, *data._shape);
|
|
break;
|
|
default:
|
|
error("Unsupported head draw type %d", drawtype);
|
|
}
|
|
}
|
|
|
|
void SDSScene::drawHeadType1(Graphics::ManagedSurface *dst, const TalkDataHead &head, const Image &img) {
|
|
Common::Rect r = head._rect.toCommonRect();
|
|
dst->fillRect(r, head._drawCol);
|
|
r.grow(-1);
|
|
dst->fillRect(r, head._drawCol == 0 ? 15 : 0);
|
|
r.left += 2;
|
|
r.top += 2;
|
|
const int x = head._rect.x;
|
|
const int y = head._rect.y;
|
|
if (img.isLoaded()) {
|
|
for (const auto &frame : head._headFrames) {
|
|
img.drawBitmap(frame._frameNo, x + frame._xoff, y + frame._yoff, r, *dst);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SDSScene::drawHeadType2(Graphics::ManagedSurface *dst, const TalkDataHead &head, const Image &img) {
|
|
if (!img.isLoaded())
|
|
return;
|
|
const Common::Rect r = head._rect.toCommonRect();
|
|
for (const auto &frame : head._headFrames) {
|
|
img.drawBitmap(frame._frameNo, r.left + frame._xoff, r.top + frame._yoff, r, *dst);
|
|
}
|
|
}
|
|
|
|
void SDSScene::drawHeadType3(Graphics::ManagedSurface *dst, const TalkDataHead &head, const Image &img) {
|
|
Common::Rect r = head._rect.toCommonRect();
|
|
dst->fillRect(r, 0);
|
|
if (!img.isLoaded())
|
|
return;
|
|
for (const auto &frame : head._headFrames) {
|
|
if (frame._frameNo < img.loadedFrameCount())
|
|
img.drawBitmap(frame._frameNo, r.left + frame._xoff, r.top + frame._yoff, r, *dst);
|
|
else
|
|
dst->fillRect(r, 4);
|
|
}
|
|
}
|
|
|
|
void SDSScene::updateHead(TalkDataHead &head) {
|
|
warning("TODO: Update head");
|
|
head._flags = static_cast<HeadFlags>(head._flags & ~(kHeadFlag1 | kHeadFlag8 | kHeadFlag10 | kHeadFlagVisible));
|
|
|
|
/* This seems to just be a "needs redraw" flag, but we always redraw
|
|
for (auto tds : _talkData) {
|
|
for (auto h : tds._heads) {
|
|
if ((h._flags & kHeadFlagVisible) && !(h._flags & (kHeadFlag8 | kHeadFlag10 | kHeadFlag80))) {
|
|
if (h._rect.toCommonRect().intersects(head._rect.toCommonRect())) {
|
|
h._flags = static_cast<HeadFlags>(h._flags | kHeadFlag4);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
|
|
void SDSScene::drawVisibleHeads(Graphics::ManagedSurface *dst) {
|
|
for (const auto &tds : _talkData) {
|
|
for (const auto &h : tds._heads) {
|
|
if ((h._flags & kHeadFlagVisible) && !(h._flags & kHeadFlag40)) {
|
|
drawHead(dst, tds, h);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SDSScene::loadTalkDataAndSetFlags(uint16 talknum, uint16 headnum) {
|
|
updateVisibleTalkers();
|
|
if (loadTalkData(talknum)) {
|
|
for (auto &data : _talkData) {
|
|
if (data._num != talknum)
|
|
continue;
|
|
|
|
for (auto &head : data._heads) {
|
|
if (head._num != headnum)
|
|
continue;
|
|
head._flags = static_cast<HeadFlags>(head._flags & ~(kHeadFlag1 | kHeadFlag10));
|
|
head._flags = static_cast<HeadFlags>(head._flags | (kHeadFlag8 | kHeadFlagVisible));
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static const uint16 TIRED_DLG_ID = 7777;
|
|
|
|
void SDSScene::addAndShowTiredDialog() {
|
|
bool haveTiredDlg = false;
|
|
for (auto &d : _dialogs) {
|
|
if (d._num == TIRED_DLG_ID) {
|
|
haveTiredDlg = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!haveTiredDlg) {
|
|
Dialog dlg;
|
|
dlg._num = TIRED_DLG_ID;
|
|
dlg._rect = DgdsRect(4, 18, 208, 91);
|
|
dlg._bgColor = 15;
|
|
dlg._fontColor = 0;
|
|
dlg._selectionBgCol = 15;
|
|
dlg._selectonFontCol = 0;
|
|
dlg._flags = static_cast<DialogFlags>(kDlgFlagLo8 | kDlgFlagLeftJust | kDlgFlagFlatBg);
|
|
dlg._frameType = kDlgFrameThought;
|
|
dlg._time = 420;
|
|
if (DgdsEngine::getInstance()->getGameLang() == Common::EN_ANY) {
|
|
dlg._str = "Boy, am I tired. Better get some sleep in about an hour.";
|
|
} else if (DgdsEngine::getInstance()->getGameLang() == Common::DE_DEU) {
|
|
dlg._str = "Mensch, bin ich m\x81""de! Am Besten gehe ich bald mal ins Bett.";
|
|
} else {
|
|
error("Unsupported language %d", DgdsEngine::getInstance()->getGameLang());
|
|
}
|
|
|
|
_dialogs.push_back(dlg);
|
|
}
|
|
showDialog(0, TIRED_DLG_ID);
|
|
}
|
|
|
|
|
|
void SDSScene::showDialog(uint16 fileNum, uint16 dlgNum) {
|
|
if (fileNum)
|
|
loadDialogData(fileNum);
|
|
|
|
for (auto &dialog : _dialogs) {
|
|
if (dialog._num == dlgNum) {
|
|
dialog.clearFlag(kDlgFlagHiFinished);
|
|
dialog.clearFlag(kDlgFlagRedrawSelectedActionChanged);
|
|
dialog.clearFlag(kDlgFlagHi10);
|
|
//dialog.clearFlag(kDlgFlagHi20);
|
|
dialog.clearFlag(kDlgFlagHi40);
|
|
dialog.setFlag(kDlgFlagHi20);
|
|
dialog.setFlag(kDlgFlagVisible);
|
|
dialog.setFlag(kDlgFlagOpening);
|
|
// hide time gets set the first time it's drawn.
|
|
if (_dlgWithFlagLo8IsClosing && dialog.hasFlag(kDlgFlagLo8)) {
|
|
_sceneDialogFlags = static_cast<DialogFlags>(_sceneDialogFlags | kDlgFlagLo8 | kDlgFlagVisible);
|
|
}
|
|
if (_dlgWithFlagLo8IsClosing) {
|
|
// TODO: call some function (FUN_1f1a_4205) here.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SDSScene::checkDialogActive() {
|
|
uint32 timeNow = DgdsEngine::getInstance()->getThisFrameMs();
|
|
bool retval = false;
|
|
|
|
_sceneDialogFlags = kDlgFlagNone;
|
|
|
|
bool clearDlgFlag = _shouldClearDlg; // ((g_gameStateFlag_41f6 | UINT_39e5_41f8) & 6) != 0); ??
|
|
_shouldClearDlg = false;
|
|
|
|
for (auto &dlg : _dialogs) {
|
|
if (!dlg.hasFlag(kDlgFlagVisible))
|
|
continue;
|
|
|
|
if (!dlg._state)
|
|
dlg._state.reset(new DialogState());
|
|
|
|
// FIXME: double-check this logic.
|
|
// Mark finished if we are manually clearing *or* the timer has expired.
|
|
bool finished = false;
|
|
if (clearDlgFlag || (dlg._state->_hideTime && timeNow >= dlg._state->_hideTime)) {
|
|
finished = true;
|
|
}
|
|
|
|
bool no_options = false;
|
|
if ((dlg._state->_hideTime == 0) && dlg._action.size() < 2)
|
|
no_options = true;
|
|
|
|
if ((!finished && !no_options) || dlg.hasFlag(kDlgFlagHi20) || dlg.hasFlag(kDlgFlagHi40)) {
|
|
if (!finished && dlg._action.size() > 1 && !dlg.hasFlag(kDlgFlagHiFinished)) {
|
|
DialogAction *action = dlg.pickAction(false, clearDlgFlag);
|
|
if (dlg._state->_selectedAction != action) {
|
|
dlg._state->_selectedAction = action;
|
|
dlg.clearFlag(kDlgFlagHi10);
|
|
dlg.setFlag(kDlgFlagRedrawSelectedActionChanged);
|
|
}
|
|
}
|
|
} else {
|
|
// this dialog is finished - call the ops and maybe show the next one
|
|
_dlgWithFlagLo8IsClosing = dlg.hasFlag(kDlgFlagLo8);
|
|
DialogAction *action = dlg.pickAction(true, clearDlgFlag);
|
|
if (action || dlg._action.empty()) {
|
|
dlg.setFlag(kDlgFlagHiFinished);
|
|
if (action) {
|
|
// Take a copy of the dialog because the actions might change the scene
|
|
Dialog dlgCopy = dlg;
|
|
if (dlgCopy._state)
|
|
dlgCopy._state->_selectedAction = nullptr;
|
|
debug(1, "Dialog %d closing: run action (%d ops)", dlg._num, action->sceneOpList.size());
|
|
if (!runOps(action->sceneOpList)) {
|
|
// HACK: the scene changed, but we haven't yet drawn the foreground for the
|
|
// dialog, this is our last chance so do it now. The game does it in a
|
|
// different way that relies on delayed disposal of the dialog data.
|
|
if (dlgCopy.hasFlag(kDlgFlagVisible) && !dlgCopy.hasFlag(kDlgFlagOpening)) {
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
dlgCopy.draw(&engine->_compositionBuffer, kDlgDrawFindSelectionPointXY);
|
|
dlgCopy.draw(&engine->_compositionBuffer, kDlgDrawFindSelectionTxtOffset);
|
|
dlgCopy.draw(&engine->_compositionBuffer, kDlgDrawStageForeground);
|
|
}
|
|
_dlgWithFlagLo8IsClosing = false;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
if (dlg._nextDialogDlgNum) {
|
|
dlg.setFlag(kDlgFlagHiFinished);
|
|
showDialog(dlg._nextDialogFileNum, dlg._nextDialogDlgNum);
|
|
}
|
|
}
|
|
if (dlg.hasFlag(kDlgFlagVisible)) {
|
|
_sceneDialogFlags = static_cast<DialogFlags>(_sceneDialogFlags | kDlgFlagVisible);
|
|
}
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
void SDSScene::drawActiveDialogBgs(Graphics::ManagedSurface *dst) {
|
|
for (auto &dlg : _dialogs) {
|
|
if (dlg.hasFlag(kDlgFlagVisible) && !dlg.hasFlag(kDlgFlagOpening)) {
|
|
dlg.draw(dst, kDlgDrawStageBackground);
|
|
// FIXME: Original clears Hi20 and sets Hi40 here, but with our
|
|
// call sequence that means the time never works right in
|
|
// drawAndUpdateDialogs??
|
|
//dlg.clearFlag(kDlgFlagHi20);
|
|
//dlg.setFlag(kDlgFlagHi40);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SDSScene::checkForClearedDialogs() {
|
|
bool result = false;
|
|
bool have8 = false;
|
|
for (auto &dlg : _dialogs) {
|
|
if (!dlg.hasFlag(kDlgFlagHiFinished)) {
|
|
if (dlg.hasFlag(kDlgFlagLo8))
|
|
have8 = true;
|
|
} else {
|
|
dlg.clear();
|
|
result = true;
|
|
}
|
|
}
|
|
|
|
if (!have8) {
|
|
_sceneDialogFlags = static_cast<DialogFlags>(_sceneDialogFlags & ~kDlgFlagLo8);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool SDSScene::drawAndUpdateDialogs(Graphics::ManagedSurface *dst) {
|
|
bool retval = false;
|
|
const DgdsEngine *engine = DgdsEngine::getInstance();
|
|
for (auto &dlg : _dialogs) {
|
|
if (dlg.hasFlag(kDlgFlagVisible) && !dlg.hasFlag(kDlgFlagLo4) &&
|
|
!dlg.hasFlag(kDlgFlagHi20) && !dlg.hasFlag(kDlgFlagHi40)) {
|
|
// TODO: do something with "transfer"s?
|
|
dlg.setFlag(kDlgFlagHi4);
|
|
}
|
|
if (!dlg.hasFlag(kDlgFlagVisible) || (!dlg.hasFlag(kDlgFlagLo4) && !dlg.hasFlag(kDlgFlagHi4) && !dlg.hasFlag(kDlgFlagHi20) && !dlg.hasFlag(kDlgFlagHi40))) {
|
|
if (dlg.hasFlag(kDlgFlagRedrawSelectedActionChanged) || dlg.hasFlag(kDlgFlagHi10)) {
|
|
dlg.draw(dst, kDlgDrawStageForeground);
|
|
if (!dlg.hasFlag(kDlgFlagRedrawSelectedActionChanged)) {
|
|
dlg.clearFlag(kDlgFlagHi10);
|
|
} else {
|
|
dlg.flipFlag(kDlgFlagRedrawSelectedActionChanged);
|
|
dlg.flipFlag(kDlgFlagHi10);
|
|
}
|
|
}
|
|
} else if (!dlg.hasFlag(kDlgFlagOpening)) {
|
|
dlg.draw(dst, kDlgDrawStageBackground);
|
|
// HACK: always draw foreground here too..??? The original doesn't but we never
|
|
// seem to end up calling the foreground draw function..
|
|
dlg.draw(dst, kDlgDrawFindSelectionPointXY);
|
|
dlg.draw(dst, kDlgDrawFindSelectionTxtOffset);
|
|
dlg.draw(dst, kDlgDrawStageForeground);
|
|
if (dlg.hasFlag(kDlgFlagHi20)) {
|
|
// Reset the dialog time and selected action
|
|
int delay = 0xffff;
|
|
if (dlg._time)
|
|
delay = dlg._time;
|
|
|
|
int time = delay * (9 - engine->getTextSpeed());
|
|
assert(dlg._state);
|
|
|
|
dlg._state->_hideTime = DgdsEngine::getInstance()->getThisFrameMs() + time;
|
|
dlg._state->_selectedAction = nullptr;
|
|
dlg.updateSelectedAction(0);
|
|
if (dlg._action.size() > 1 && !dlg._state->_selectedAction) {
|
|
dlg._state->_selectedAction = dlg.pickAction(false, false);
|
|
if (dlg._state->_selectedAction)
|
|
dlg.draw(dst, kDlgDrawStageForeground);
|
|
}
|
|
}
|
|
|
|
if (!dlg.hasFlag(kDlgFlagHi20)) {
|
|
dlg.clearFlag(kDlgFlagHi40);
|
|
} else {
|
|
dlg.flipFlag(kDlgFlagHi20);
|
|
dlg.flipFlag(kDlgFlagHi40);
|
|
}
|
|
dlg.clearFlag(kDlgFlagHi4);
|
|
retval = true;
|
|
} else if (!engine->justChangedScene1()) {
|
|
dlg.clearFlag(kDlgFlagOpening);
|
|
}
|
|
|
|
if (dlg.hasFlag(kDlgFlagVisible) && !dlg.hasFlag(kDlgFlagLo4) &&
|
|
!dlg.hasFlag(kDlgFlagHi20) && !dlg.hasFlag(kDlgFlagHi40)) {
|
|
// TODO: do something with "transfer"s?
|
|
// warning("SDSScene::drawActiveDrawAndUpdateDialogs: Do something with transfers?");
|
|
dlg.setFlag(kDlgFlagHi4);
|
|
}
|
|
if (dlg.hasFlag(kDlgFlagVisible) && !dlg.hasFlag(kDlgFlagOpening)) {
|
|
_sceneDialogFlags = static_cast<DialogFlags>(_sceneDialogFlags | kDlgFlagLo8 | kDlgFlagVisible);
|
|
}
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
void SDSScene::mouseMoved(const Common::Point &pt) {
|
|
Dialog *dlg = getVisibleDialog();
|
|
const HotArea *area = findAreaUnderMouse(pt);
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
|
|
int16 cursorNum = (!dlg && area) ? area->_cursorNum : 0;
|
|
if (_dragItem) {
|
|
cursorNum = _dragItem->_iconNum;
|
|
} else if (_rbuttonDown) {
|
|
GameItem *activeItem = engine->getGDSScene()->getActiveItem();
|
|
if (activeItem)
|
|
cursorNum = activeItem->_altCursor;
|
|
}
|
|
|
|
engine->setMouseCursor(cursorNum);
|
|
}
|
|
|
|
void SDSScene::mouseLDown(const Common::Point &pt) {
|
|
_lbuttonDown = true;
|
|
if (hasVisibleDialog()) {
|
|
debug(9, "Mouse LDown on at %d,%d clearing visible dialog", pt.x, pt.y);
|
|
_shouldClearDlg = true;
|
|
_ignoreMouseUp = true;
|
|
return;
|
|
}
|
|
|
|
HotArea *area = findAreaUnderMouse(pt);
|
|
if (!area)
|
|
return;
|
|
|
|
debug(9, "Mouse LDown on area %d (%d,%d,%d,%d) cursor %d. Run %d ops", area->_num,
|
|
area->_rect.x, area->_rect.y, area->_rect.width, area->_rect.height,
|
|
area->_cursorNum, area->onLDownOps.size());
|
|
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
int16 addmins = engine->getGameGlobals()->getGameMinsToAddOnStartDrag();
|
|
runOps(area->onLDownOps, addmins);
|
|
GameItem *item = dynamic_cast<GameItem *>(area);
|
|
if (item) {
|
|
_dragItem = item;
|
|
if (item->_iconNum)
|
|
engine->setMouseCursor(item->_iconNum);
|
|
}
|
|
}
|
|
|
|
static bool _isInRect(const Common::Point &pt, const DgdsRect rect) {
|
|
return rect.x <= pt.x && (rect.x + rect.width) > pt.x
|
|
&& rect.y <= pt.y && (rect.y + rect.height) > pt.y;
|
|
}
|
|
|
|
static const ObjectInteraction * _findInteraction(const Common::Array<ObjectInteraction> &interList, int16 droppedNum, uint16 targetNum) {
|
|
for (const auto &i : interList) {
|
|
if (i.matches(droppedNum, targetNum)) {
|
|
return &i;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void SDSScene::mouseLUp(const Common::Point &pt) {
|
|
_lbuttonDown = false;
|
|
if (_ignoreMouseUp) {
|
|
debug(9, "Ignoring mouseup at %d,%d as it was used to clear a dialog", pt.x, pt.y);
|
|
_ignoreMouseUp = false;
|
|
return;
|
|
}
|
|
|
|
if (_dragItem) {
|
|
onDragFinish(pt);
|
|
return;
|
|
}
|
|
|
|
const HotArea *area = findAreaUnderMouse(pt);
|
|
if (!area)
|
|
return;
|
|
|
|
debug(9, "Mouse LUp on area %d (%d,%d,%d,%d) cursor %d", area->_num, area->_rect.x, area->_rect.y,
|
|
area->_rect.width, area->_rect.height, area->_cursorNum);
|
|
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
if (!_rbuttonDown)
|
|
engine->setMouseCursor(area->_cursorNum);
|
|
|
|
GDSScene *gds = engine->getGDSScene();
|
|
|
|
if (area->_num == 0) {
|
|
debug(1, "Mouseup on inventory.");
|
|
engine->getInventory()->open();
|
|
} else if (area->_num == 0xffff) {
|
|
debug(1, "Mouseup on swap characters.");
|
|
bool haveInvBtn = _hotAreaList.size() && _hotAreaList.front()._num == 0;
|
|
if (haveInvBtn)
|
|
removeInvButtonFromHotAreaList();
|
|
int16 prevChar = gds->getGlobal(0x33);
|
|
gds->setGlobal(0x33, gds->getGlobal(0x34));
|
|
gds->setGlobal(0x34, prevChar);
|
|
if (haveInvBtn)
|
|
addInvButtonToHotAreaList();
|
|
} else {
|
|
if (_rbuttonDown) {
|
|
debug(1, " --> exec both-button click ops for area %d", area->_num);
|
|
// A both-button-click event, find the interaction list.
|
|
const GameItem *activeItem = engine->getGDSScene()->getActiveItem();
|
|
if (activeItem) {
|
|
if (!runOps(activeItem->onBothButtonsOps))
|
|
return;
|
|
|
|
const GameItem *destItem = dynamic_cast<const GameItem *>(area);
|
|
const ObjectInteraction *i;
|
|
if (destItem) {
|
|
i =_findInteraction(gds->getObjInteractions2(), activeItem->_num, area->_num);
|
|
} else {
|
|
i = _findInteraction(_objInteractions2, activeItem->_num, area->_num);
|
|
}
|
|
if (i) {
|
|
debug(1, " --> exec %d both-click ops for item combo %d", i->opList.size(), activeItem->_num);
|
|
if (!runOps(i->opList, engine->getGameGlobals()->getGameMinsToAddOnObjInteraction()))
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
debug(1, " --> exec %d click ops for area %d", area->onLClickOps.size(), area->_num);
|
|
int16 addmins = engine->getGameGlobals()->getGameMinsToAddOnLClick();
|
|
runOps(area->onLClickOps, addmins);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SDSScene::onDragFinish(const Common::Point &pt) {
|
|
assert(_dragItem);
|
|
|
|
debug(9, "Drag finished at %d, %d", pt.x , pt.y);
|
|
// Unlike a click operation, this runs the drop event for *all* areas
|
|
// and items, ignoring enable condition.
|
|
|
|
GameItem *dragItem = _dragItem;
|
|
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
const Globals *globals = engine->getGameGlobals();
|
|
GDSScene *gdsScene = engine->getGDSScene();
|
|
|
|
runOps(dragItem->onDragFinishedOps, globals->getGameMinsToAddOnDragFinished());
|
|
|
|
// TODO: Both these loops are very similar.. there should be a cleaner way.
|
|
|
|
for (const auto &item : gdsScene->getGameItems()) {
|
|
if (item._inSceneNum == _num && _isInRect(pt, item._rect)) {
|
|
debug(1, "Dragged item %d onto item %d @ (%d, %d)", dragItem->_num, item._num, pt.x, pt.y);
|
|
const ObjectInteraction *i = _findInteraction(gdsScene->getObjInteractions1(), dragItem->_num, item._num);
|
|
if (i) {
|
|
debug(1, " --> exec %d drag ops for item %d", i->opList.size(), item._num);
|
|
if (!runOps(i->opList, globals->getGameMinsToAddOnObjInteraction()))
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
SDSScene *scene = engine->getScene();
|
|
for (const auto &area : _hotAreaList) {
|
|
if (!_isInRect(pt, area._rect))
|
|
continue;
|
|
|
|
if (area._num == 0) {
|
|
debug(1, "Item %d dropped on inventory.", dragItem->_num);
|
|
dragItem->_inSceneNum = 2;
|
|
if (engine->getGameId() == GID_HOC)
|
|
dragItem->_quality = Inventory::HOC_CHARACTER_QUALS[gdsScene->getGlobal(0x33)];
|
|
|
|
const ObjectInteraction *i = _findInteraction(gdsScene->getObjInteractions1(), dragItem->_num, 0xffff);
|
|
if (i) {
|
|
debug(1, " --> exec %d drag ops for area %d", i->opList.size(), 0xffff);
|
|
if (!runOps(i->opList, globals->getGameMinsToAddOnObjInteraction()))
|
|
return;
|
|
}
|
|
} else if (area._num == 0xffff) {
|
|
debug(1, "Item %d dropped on other character button.", dragItem->_num);
|
|
dragItem->_inSceneNum = 2;
|
|
if (engine->getGameId() == GID_HOC)
|
|
dragItem->_quality = Inventory::HOC_CHARACTER_QUALS[gdsScene->getGlobal(0x34)];
|
|
|
|
const ObjectInteraction *i = _findInteraction(gdsScene->getObjInteractions1(), dragItem->_num, 0xffff);
|
|
if (i) {
|
|
debug(1, " --> exec %d drag ops for area %d", i->opList.size(), 0xffff);
|
|
if (!runOps(i->opList, globals->getGameMinsToAddOnObjInteraction()))
|
|
return;
|
|
}
|
|
} else {
|
|
debug(1, "Dragged item %d onto area %d @ (%d, %d)", dragItem->_num, area._num, pt.x, pt.y);
|
|
const ObjectInteraction *i = _findInteraction(scene->getObjInteractions1(), dragItem->_num, area._num);
|
|
if (i) {
|
|
debug(1, " --> exec %d drag ops for area %d", i->opList.size(), area._num);
|
|
if (!runOps(i->opList, globals->getGameMinsToAddOnObjInteraction()))
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
engine->setMouseCursor(gdsScene->getDefaultMouseCursor());
|
|
_dragItem = nullptr;
|
|
}
|
|
|
|
void SDSScene::mouseRDown(const Common::Point &pt) {
|
|
_rbuttonDown = true;
|
|
}
|
|
|
|
void SDSScene::mouseRUp(const Common::Point &pt) {
|
|
_rbuttonDown = false;
|
|
Dialog *dlg = getVisibleDialog();
|
|
if (dlg) {
|
|
// HACK: Check for dialog action selection! for now, just close
|
|
// it here to make game playable.
|
|
dlg->clear();
|
|
return;
|
|
}
|
|
|
|
// Update the cursor..
|
|
mouseMoved(pt);
|
|
|
|
const HotArea *area = findAreaUnderMouse(pt);
|
|
if (!area)
|
|
return;
|
|
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
|
|
if (area->_num == 0) {
|
|
debug(1, "Mouse RUp on inventory.");
|
|
engine->getInventory()->setShowZoomBox(true);
|
|
engine->getInventory()->open();
|
|
} else if (area->_num == 0xffff) {
|
|
debug(1, "Mouse RUp on character swap.");
|
|
int16 swapDlgFile = engine->getGDSScene()->getGlobal(0x36);
|
|
int16 swapDlgNum = engine->getGDSScene()->getGlobal(0x35);
|
|
if (swapDlgFile && swapDlgNum)
|
|
showDialog(swapDlgFile, swapDlgNum);
|
|
} else {
|
|
int16 addmins = engine->getGameGlobals()->getGameMinsToAddOnLClick();
|
|
debug(1, "Mouse RUp on area %d, run %d ops (+%d mins)", area->_num, area->onRClickOps.size(), addmins);
|
|
runOps(area->onRClickOps, addmins);
|
|
}
|
|
}
|
|
|
|
Dialog *SDSScene::getVisibleDialog() {
|
|
for (auto &dlg : _dialogs) {
|
|
if (dlg.hasFlag(kDlgFlagVisible) && !dlg.hasFlag(kDlgFlagOpening)) {
|
|
return &dlg;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool SDSScene::hasVisibleDialog() {
|
|
return getVisibleDialog() != nullptr;
|
|
}
|
|
|
|
bool SDSScene::hasVisibleOrOpeningDialog() const {
|
|
for (const auto &dlg : _dialogs) {
|
|
if (dlg.hasFlag(kDlgFlagVisible) || dlg.hasFlag(kDlgFlagOpening)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
HotArea *SDSScene::findAreaUnderMouse(const Common::Point &pt) {
|
|
for (auto &item : DgdsEngine::getInstance()->getGDSScene()->getGameItems()) {
|
|
if (item._inSceneNum == _num && checkConditions(item.enableConditions)
|
|
&& _isInRect(pt, item._rect)) {
|
|
return &item;
|
|
}
|
|
}
|
|
|
|
for (auto &area : _hotAreaList) {
|
|
if (checkConditions(area.enableConditions) && _isInRect(pt, area._rect)) {
|
|
return &area;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void SDSScene::addInvButtonToHotAreaList() {
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
const Common::Array<MouseCursor> &cursors = engine->getGDSScene()->getCursorList();
|
|
const Common::SharedPtr<Image> &icons = engine->getIcons();
|
|
|
|
if (cursors.empty() || !icons || icons->loadedFrameCount() <= 2 || _num == 2)
|
|
return;
|
|
|
|
if (_hotAreaList.size() && _hotAreaList.front()._num == 0)
|
|
return;
|
|
|
|
int16 invButtonIcon = engine->getGDSScene()->getInvIconNum();
|
|
if (engine->getGameId() == GID_HOC) {
|
|
static const byte HOC_INV_ICONS[] = { 0, 2, 18, 19 };
|
|
invButtonIcon = HOC_INV_ICONS[engine->getGDSScene()->getGlobal(0x33)];
|
|
}
|
|
|
|
HotArea area;
|
|
area._num = 0;
|
|
area._cursorNum = engine->getGDSScene()->getInvIconMouseCursor();
|
|
area._rect.width = icons->width(invButtonIcon);
|
|
area._rect.height = icons->height(invButtonIcon);
|
|
area._rect.x = SCREEN_WIDTH - area._rect.width;
|
|
area._rect.y = SCREEN_HEIGHT - area._rect.height;
|
|
area._otherCursorNum = 0;
|
|
area._objInteractionListFlag = 0;
|
|
|
|
// Add swap character button for HoC
|
|
if (engine->getGameId() == GID_HOC && engine->getGDSScene()->getGlobal(0x34) != 0) {
|
|
int16 charNum = engine->getGDSScene()->getGlobal(0x34);
|
|
int16 iconNum = DgdsEngine::HOC_CHAR_SWAP_ICONS[charNum];
|
|
HotArea area2;
|
|
area2._num = 0xffff;
|
|
area2._cursorNum = 0;
|
|
area2._rect.width = icons->width(iconNum);
|
|
area2._rect.height = icons->height(iconNum);
|
|
area2._rect.x = 5;
|
|
area2._rect.y = SCREEN_HEIGHT - area2._rect.height - 5;
|
|
area2._otherCursorNum = 0;
|
|
area2._objInteractionListFlag = 0;
|
|
|
|
_hotAreaList.push_front(area2);
|
|
}
|
|
|
|
_hotAreaList.push_front(area);
|
|
}
|
|
|
|
void SDSScene::removeInvButtonFromHotAreaList() {
|
|
if (_hotAreaList.size() && _hotAreaList.front()._num == 0)
|
|
_hotAreaList.pop_front();
|
|
// Also remove character swap button in HoC
|
|
if (_hotAreaList.size() && _hotAreaList.front()._num == 0xffff)
|
|
_hotAreaList.pop_front();
|
|
}
|
|
|
|
Common::Error SDSScene::syncState(Common::Serializer &s) {
|
|
// num should be synced as part of the engine -
|
|
// at this point we are already loaded.
|
|
assert(_num);
|
|
|
|
// The dialogs and triggers are stateful, everything else is stateless.
|
|
uint16 ndlgs = _dialogs.size();
|
|
s.syncAsUint16LE(ndlgs);
|
|
if (_dialogs.size() && ndlgs != _dialogs.size()) {
|
|
error("Dialog count in save doesn't match count in game (%d vs %d)",
|
|
ndlgs, _dialogs.size());
|
|
} else if (_dialogs.size()) {
|
|
for (auto &dlg : _dialogs) {
|
|
dlg.syncState(s);
|
|
}
|
|
} else if (ndlgs && s.isLoading()) {
|
|
warning("Skipping dialog data in save");
|
|
Dialog dlg;
|
|
for (uint i = 0; i < ndlgs; i++)
|
|
dlg.syncState(s);
|
|
}
|
|
|
|
uint16 ntrig = _triggers.size();
|
|
s.syncAsUint16LE(ntrig);
|
|
if (ntrig != _triggers.size()) {
|
|
error("Trigger count in save doesn't match count in game (%d vs %d)",
|
|
ntrig, _triggers.size());
|
|
}
|
|
for (auto &trg : _triggers)
|
|
s.syncAsByte(trg._enabled);
|
|
|
|
return Common::kNoError;
|
|
}
|
|
|
|
void SDSScene::prevChoice() {
|
|
Dialog *dlg = getVisibleDialog();
|
|
if (!dlg)
|
|
return;
|
|
dlg->updateSelectedAction(-1);
|
|
}
|
|
|
|
void SDSScene::nextChoice() {
|
|
Dialog *dlg = getVisibleDialog();
|
|
if (!dlg)
|
|
return;
|
|
dlg->updateSelectedAction(1);
|
|
}
|
|
|
|
void SDSScene::activateChoice() {
|
|
Dialog *dlg = getVisibleDialog();
|
|
if (!dlg)
|
|
return;
|
|
_shouldClearDlg = true;
|
|
}
|
|
|
|
|
|
GDSScene::GDSScene() : _defaultMouseCursor(0), _field3a(0), _invIconNum(0), _invIconMouseCursor(0), _field40(0) {
|
|
}
|
|
|
|
bool GDSScene::load(const Common::String &filename, ResourceManager *resourceManager, Decompressor *decompressor) {
|
|
Common::SeekableReadStream *sceneFile = resourceManager->getResource(filename);
|
|
if (!sceneFile)
|
|
error("Scene file %s not found", filename.c_str());
|
|
|
|
DgdsChunkReader chunk(sceneFile);
|
|
|
|
bool result = false;
|
|
|
|
while (chunk.readNextHeader(EX_GDS, filename)) {
|
|
if (chunk.isContainer()) {
|
|
continue;
|
|
}
|
|
|
|
chunk.readContent(decompressor);
|
|
Common::SeekableReadStream *stream = chunk.getContent();
|
|
|
|
if (chunk.isSection(ID_GDS)) {
|
|
// do nothing, this is the container.
|
|
assert(chunk.isContainer());
|
|
} else if (chunk.isSection(ID_INF)) {
|
|
result = parseInf(stream);
|
|
} else if (chunk.isSection(ID_SDS)) {
|
|
result = parse(stream);
|
|
}
|
|
}
|
|
|
|
initIconSizes();
|
|
|
|
delete sceneFile;
|
|
|
|
return result;
|
|
}
|
|
|
|
bool GDSScene::loadRestart(const Common::String &filename, ResourceManager *resourceManager, Decompressor *decompressor) {
|
|
Common::SeekableReadStream *file = resourceManager->getResource(filename);
|
|
if (!file)
|
|
error("Restart data %s not found", filename.c_str());
|
|
|
|
uint32 magic = file->readUint32LE();
|
|
if (magic != _magic)
|
|
error("Restart file magic doesn't match (%04X vs %04X)", magic, _magic);
|
|
|
|
uint16 num = file->readUint16LE();
|
|
// Find matching game item and load its values
|
|
while (num) {
|
|
bool found = false;
|
|
for (GameItem &item : _gameItems) {
|
|
if (item._num == num) {
|
|
item._rect.x = file->readUint16LE();
|
|
item._rect.y = file->readUint16LE();
|
|
item._rect.width = file->readUint16LE();
|
|
item._rect.height = file->readUint16LE();
|
|
item._inSceneNum = file->readUint16LE();
|
|
item._flags = file->readUint16LE();
|
|
item._quality = file->readUint16LE();
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
error("Reset file references unknown item %d", num);
|
|
num = file->readUint16LE();
|
|
}
|
|
initIconSizes();
|
|
|
|
num = file->readUint16LE();
|
|
while (num) {
|
|
uint16 scene = file->readUint16LE();
|
|
int16 val = file->readSint16LE();
|
|
bool found = false;
|
|
for (PerSceneGlobal &glob : _perSceneGlobals) {
|
|
if (glob.matches(num, scene)) {
|
|
glob._val = val;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
error("Reset file references unknown global %d", num);
|
|
num = file->readUint16LE();
|
|
}
|
|
|
|
/*uint32 unk = */ file->readUint32LE();
|
|
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
Common::Array<Global *> &globs = engine->getGameGlobals()->getAllGlobals();
|
|
|
|
if (globs.size() > 50)
|
|
error("Too many globals to load from RST file");
|
|
|
|
int g = 0;
|
|
for (Global *glob : globs) {
|
|
int16 val = file->readUint16LE();
|
|
glob->setRaw(val);
|
|
g++;
|
|
}
|
|
|
|
// Always 50 int16s worth of globals in the file, skip any unused.
|
|
if (g < 50)
|
|
file->skip(2 * (50 - g));
|
|
|
|
uint16 triggers[100];
|
|
for (int i = 0; i < ARRAYSIZE(triggers); i++) {
|
|
triggers[i] = file->readUint16LE();
|
|
}
|
|
|
|
engine->_compositionBuffer.fillRect(Common::Rect(SCREEN_WIDTH, SCREEN_HEIGHT), 0);
|
|
// TODO: FIXME: What should this scene num be? For now hacked to work with Dragon.
|
|
engine->changeScene(3);
|
|
SDSScene *scene = engine->getScene();
|
|
int t = 0;
|
|
num = triggers[t++];
|
|
while (num) {
|
|
uint16 val = triggers[t++];
|
|
scene->enableTrigger(num, (bool)val);
|
|
num = triggers[t++];
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void GDSScene::initIconSizes() {
|
|
const Common::SharedPtr<Image> icons = DgdsEngine::getInstance()->getIcons();
|
|
uint16 nicons = icons ? icons->getFrames().size() : 0;
|
|
for (GameItem &item : _gameItems) {
|
|
if (item._iconNum < nicons) {
|
|
item._rect.width = icons->getFrames()[item._iconNum]->w;
|
|
item._rect.height = icons->getFrames()[item._iconNum]->h;
|
|
} else {
|
|
item._rect.width = 32;
|
|
item._rect.height = 32;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool GDSScene::readPerSceneGlobals(Common::SeekableReadStream *s) {
|
|
uint16 numGlobals = s->readUint16LE();
|
|
uint16 num, scene;
|
|
for (uint16 i = 0; i < numGlobals; i++) {
|
|
num = s->readUint16LE();
|
|
scene = s->readUint16LE();
|
|
_perSceneGlobals.push_back(PerSceneGlobal(num, scene));
|
|
_perSceneGlobals.back()._val = s->readSint16LE();
|
|
}
|
|
return !s->err();
|
|
}
|
|
|
|
bool GDSScene::parseInf(Common::SeekableReadStream *s) {
|
|
_magic = s->readUint32LE();
|
|
_version = s->readString();
|
|
return !s->err();
|
|
}
|
|
|
|
bool GDSScene::parse(Common::SeekableReadStream *stream) {
|
|
readOpList(stream, _startGameOps);
|
|
readOpList(stream, _quitGameOps);
|
|
if (isVersionOver(" 1.206"))
|
|
readOpList(stream, _preTickOps);
|
|
readOpList(stream, _postTickOps);
|
|
if (isVersionOver(" 1.208"))
|
|
readOpList(stream, _onChangeSceneOps);
|
|
readPerSceneGlobals(stream);
|
|
_iconFile = stream->readString();
|
|
readMouseHotspotList(stream, _cursorList);
|
|
readGameItemList(stream, _gameItems);
|
|
readObjInteractionList(stream, _objInteractions1);
|
|
if (isVersionOver(" 1.205"))
|
|
readObjInteractionList(stream, _objInteractions2);
|
|
|
|
if (isVersionOver(" 1.218")) {
|
|
_defaultMouseCursor = stream->readUint16LE();
|
|
_field3a = stream->readUint16LE();
|
|
_invIconNum = stream->readUint16LE();
|
|
_invIconMouseCursor = stream->readUint16LE();
|
|
_field40 = stream->readUint16LE();
|
|
} else {
|
|
_defaultMouseCursor = 0;
|
|
_field3a = 1;
|
|
_invIconNum = 2;
|
|
_invIconMouseCursor = 0;
|
|
_field40 = 6;
|
|
}
|
|
|
|
return !stream->err();
|
|
}
|
|
|
|
Common::String GDSScene::dump(const Common::String &indent) const {
|
|
Common::String str = Common::String::format("%sGDSScene<icons %s", indent.c_str(), _iconFile.c_str());
|
|
str += _dumpStructList(indent, "gameItems", _gameItems);
|
|
str += _dumpStructList(indent, "startGameOps", _startGameOps);
|
|
str += _dumpStructList(indent, "quitGameOps", _quitGameOps);
|
|
str += _dumpStructList(indent, "preTickOps", _preTickOps);
|
|
str += _dumpStructList(indent, "postTickOps", _postTickOps);
|
|
str += _dumpStructList(indent, "onChangeSceneOps", _onChangeSceneOps);
|
|
str += _dumpStructList(indent, "perSceneGlobals", _perSceneGlobals);
|
|
str += _dumpStructList(indent, "objInteractions1", _objInteractions1);
|
|
str += _dumpStructList(indent, "objInteractions2", _objInteractions2);
|
|
|
|
str += "\n";
|
|
str += indent + ">";
|
|
return str;
|
|
}
|
|
|
|
void GDSScene::globalOps(const Common::Array<uint16> &args) {
|
|
if (!args.size())
|
|
error("GDSScene::globalOps: Empty arg list");
|
|
|
|
// The arg list should be a first value giving the count of operations,
|
|
// then 3 values for each op (num, opcode, val).
|
|
uint nops = args.size() / 3;
|
|
uint nops_in_args = args[0];
|
|
if (args.size() != nops * 3 + 1 || nops != nops_in_args)
|
|
error("GDSScene::globalOps: Op list should be length 3*n+1");
|
|
|
|
for (uint i = 0; i < nops; i++) {
|
|
uint16 num = args[i * 3 + 1];
|
|
uint16 op = args[i * 3 + 2];
|
|
int16 val = args[i * 3 + 3];
|
|
|
|
// CHECK ME: The original uses a different function here, but the
|
|
// result appears to be the same as just calling getGlobal?
|
|
int16 num2 = getGlobal(num);
|
|
|
|
// Op bit 3 on means use absolute val of val.
|
|
// Off means val is another global to lookup
|
|
if (op & 8)
|
|
op = op & 0xfff7;
|
|
else
|
|
val = getGlobal((uint16)val);
|
|
|
|
if (op == 1)
|
|
val = num2 + val;
|
|
else if (op == 6)
|
|
val = (val == 0);
|
|
else if (op == 5)
|
|
val = num2 - val;
|
|
|
|
setGlobal(num, val);
|
|
}
|
|
}
|
|
|
|
int16 GDSScene::getGlobal(uint16 num) {
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
int curSceneNum = engine->getScene()->getNum();
|
|
DgdsGameId gameId = engine->getGameId();
|
|
|
|
for (const auto &global : _perSceneGlobals) {
|
|
if (global.matches(num, curSceneNum))
|
|
return global._val;
|
|
else if (!global.matches(num, curSceneNum) && global.numMatches(num)) {
|
|
// Don't warn on known reusable scene globals
|
|
if (gameId == GID_WILLY && num == 185)
|
|
return global._val;
|
|
|
|
// This looks like a script bug, get it anyway
|
|
warning("getGlobal: scene global %d is not in scene %d", num, curSceneNum);
|
|
return global._val;
|
|
}
|
|
}
|
|
Globals *gameGlobals = engine->getGameGlobals();
|
|
return gameGlobals->getGlobal(num);
|
|
}
|
|
|
|
int16 GDSScene::setGlobal(uint16 num, int16 val) {
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
int curSceneNum = engine->getScene()->getNum();
|
|
for (auto &global : _perSceneGlobals) {
|
|
if (global.matches(num, curSceneNum)) {
|
|
global._val = val;
|
|
return val;
|
|
} else if (!global.matches(num, curSceneNum) && global.numMatches(num)) {
|
|
// This looks like a script bug, set it anyway
|
|
warning("setGlobal: scene global %d is not in scene %d", num, curSceneNum);
|
|
global._val = val;
|
|
return val;
|
|
}
|
|
}
|
|
Globals *gameGlobals = engine->getGameGlobals();
|
|
return gameGlobals->setGlobal(num, val);
|
|
}
|
|
|
|
void GDSScene::drawItems(Graphics::ManagedSurface &surf) {
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
const Common::SharedPtr<Image> &icons = engine->getIcons();
|
|
int currentScene = engine->getScene()->getNum();
|
|
if (!icons || icons->loadedFrameCount() < 3)
|
|
return;
|
|
|
|
int xoff = 20;
|
|
const Common::Rect screenWin(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
|
|
// Don't overlap the inventory icon.
|
|
const int maxx = SCREEN_WIDTH - (icons->width(2) + 10);
|
|
for (auto &item : _gameItems) {
|
|
if (item._inSceneNum == currentScene && &item != engine->getScene()->getDragItem()) {
|
|
if (!(item._flags & 1)) {
|
|
// Dropped item.
|
|
// Update the rect for the icon - Note: original doesn't do this,
|
|
// but then the napent icon is offset??
|
|
/*Common::SharedPtr<Graphics::ManagedSurface> icon = icons->getSurface(item._iconNum);
|
|
if (icon) {
|
|
item._rect.width = MIN((int)icon->w, item._rect.width);
|
|
item._rect.height = MIN((int)icon->h, item._rect.height);
|
|
}*/
|
|
if (xoff + item._rect.width > maxx)
|
|
xoff = 20;
|
|
int yoff = SCREEN_HEIGHT - (item._rect.height + 2);
|
|
item._rect.x = xoff;
|
|
item._rect.y = yoff;
|
|
icons->drawBitmap(item._iconNum, xoff, yoff, screenWin, surf);
|
|
xoff += (item._rect.width + 6);
|
|
} else {
|
|
icons->drawBitmap(item._iconNum, item._rect.x, item._rect.y, screenWin, surf);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int GDSScene::countItemsInScene2() const {
|
|
int result = 0;
|
|
for (const auto &item : _gameItems) {
|
|
if (item._inSceneNum == 2)
|
|
result++;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
GameItem *GDSScene::getActiveItem() {
|
|
int16 itemNum = getGlobal(0x60);
|
|
if (itemNum <= 0)
|
|
return nullptr;
|
|
for (auto &item : _gameItems) {
|
|
if (item._num == (uint16)itemNum)
|
|
return &item;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
Common::Error GDSScene::syncState(Common::Serializer &s) {
|
|
// Only items and globals are stateful - everything else is stateless.
|
|
// Game should already be loaded at this point so the lsits are already
|
|
// filled out.
|
|
|
|
assert(!_gameItems.empty());
|
|
assert(!_perSceneGlobals.empty());
|
|
|
|
// TODO: Maybe it would be nicer to save the item/global numbers
|
|
// with the values in case the order changed in some other version of
|
|
// the game data? This assumes they will be the same order.
|
|
|
|
uint16 nitems = _gameItems.size();
|
|
s.syncAsUint16LE(nitems);
|
|
if (nitems != _gameItems.size()) {
|
|
error("Item count in save doesn't match count in game (%d vs %d)",
|
|
nitems, _gameItems.size());
|
|
}
|
|
for (GameItem &item : _gameItems) {
|
|
s.syncAsUint16LE(item._inSceneNum);
|
|
if (s.getVersion() > 1)
|
|
s.syncAsUint16LE(item._flags);
|
|
s.syncAsUint16LE(item._quality);
|
|
//debug(1, "loaded item: %d %d %d %d", item._num, item._inSceneNum, item._flags, item._quality);
|
|
}
|
|
|
|
uint16 nglobals = _perSceneGlobals.size();
|
|
s.syncAsUint16LE(nglobals);
|
|
if (nglobals != _perSceneGlobals.size()) {
|
|
error("Scene global count in save doesn't match count in game (%d vs %d)",
|
|
nglobals, _perSceneGlobals.size());
|
|
}
|
|
for (PerSceneGlobal &glob : _perSceneGlobals) {
|
|
s.syncAsUint16LE(glob._val);
|
|
}
|
|
|
|
return Common::kNoError;
|
|
}
|
|
|
|
} // End of namespace Dgds
|