[Scummvm-git-logs] scummvm master -> 217f733e8a90dc44e1e2bd8cf484680c52989211
mduggan
noreply at scummvm.org
Mon Dec 16 07:31:21 UTC 2024
This automated email contains information about 3 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
1a374b66aa DGDS: Start to implement Willy Beamish CD version heads
b4f70c660d DGDS: Refactor scene class a bit
217f733e8a DGDS: Fix some issues in train game from Coverity
Commit: 1a374b66aa3d15b7a6320cd22faa48c2c012cd1f
https://github.com/scummvm/scummvm/commit/1a374b66aa3d15b7a6320cd22faa48c2c012cd1f
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2024-12-16T18:23:29+11:00
Commit Message:
DGDS: Start to implement Willy Beamish CD version heads
Still quite broken but making some progress.
Changed paths:
A engines/dgds/head.cpp
A engines/dgds/head.h
engines/dgds/image.cpp
engines/dgds/module.mk
engines/dgds/parser.cpp
engines/dgds/resource.cpp
engines/dgds/scene.cpp
engines/dgds/scene.h
engines/dgds/sound_raw.cpp
engines/dgds/sound_raw.h
engines/dgds/ttm.cpp
engines/dgds/ttm.h
diff --git a/engines/dgds/head.cpp b/engines/dgds/head.cpp
new file mode 100644
index 00000000000..45e7bea81e2
--- /dev/null
+++ b/engines/dgds/head.cpp
@@ -0,0 +1,217 @@
+/* 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 "dgds/head.h"
+#include "dgds/dgds.h"
+#include "dgds/image.h"
+#include "dgds/sound_raw.h"
+
+namespace Dgds {
+
+void TalkDataHead::drawHead(Graphics::ManagedSurface *dst, const TalkData &data) const {
+ uint drawtype = _drawType ? _drawType : 1;
+ // Use specific head shape if available (eg, in Willy Beamish), if not use talk data shape
+ Common::SharedPtr<Image> img = _shape;
+ if (!img)
+ img = data._shape;
+ if (!img)
+ return;
+ switch (drawtype) {
+ case 1:
+ drawHeadType1(dst, *img);
+ break;
+ case 2:
+ drawHeadType2(dst, *img);
+ break;
+ case 3:
+ if (DgdsEngine::getInstance()->getGameId() == GID_WILLY)
+ drawHeadType3Beamish(dst, data);
+ else
+ drawHeadType3(dst, *img);
+ break;
+ default:
+ error("Unsupported head draw type %d", drawtype);
+ }
+}
+
+void TalkDataHead::drawHeadType1(Graphics::ManagedSurface *dst, const Image &img) const {
+ Common::Rect r = _rect.toCommonRect();
+ dst->fillRect(r, _drawCol);
+ r.grow(-1);
+ dst->fillRect(r, _drawCol == 0 ? 15 : 0);
+ r.left += 2;
+ r.top += 2;
+ const int x = _rect.x;
+ const int y = _rect.y;
+ if (img.isLoaded()) {
+ for (const auto &frame : _headFrames) {
+ img.drawBitmap(frame._frameNo & 0xff, x + frame._xoff, y + frame._yoff, r, *dst);
+ }
+ }
+}
+
+void TalkDataHead::drawHeadType2(Graphics::ManagedSurface *dst, const Image &img) const {
+ if (!img.isLoaded())
+ return;
+ const Common::Rect r = _rect.toCommonRect();
+ for (const auto &frame : _headFrames) {
+ img.drawBitmap(frame._frameNo & 0xff, r.left + frame._xoff, r.top + frame._yoff, r, *dst);
+ }
+}
+
+void TalkDataHead::drawHeadType3Beamish(Graphics::ManagedSurface *dst, const TalkData &data) const {
+ const Common::Rect r = _rect.toCommonRect();
+
+ // Note: only really need the 1px border here but just fill the box.
+ dst->fillRect(r, 8);
+
+ Common::Rect fillRect(r);
+ fillRect.grow(-1);
+ dst->fillRect(fillRect, _drawCol);
+
+ for (const auto &frame : _headFrames) {
+ int frameNo = frame._frameNo & 0x7fff;
+ bool useHeadShape = frame._frameNo & 0x8000;
+
+ Common::SharedPtr<Image> img = useHeadShape ? _shape : data._shape;
+ if (!img || !img->isLoaded() || frameNo >= img->loadedFrameCount())
+ continue;
+
+ ImageFlipMode flip = kImageFlipNone;
+ // Yes, the numerical values are revesed here (1 -> 2 and 2 -> 1).
+ // The head flip flags are reversed from the image draw flags.
+ if (frame._flipFlags & 1)
+ flip = static_cast<ImageFlipMode>(flip & kImageFlipH);
+ if (frame._flipFlags & 2)
+ flip = static_cast<ImageFlipMode>(flip & kImageFlipV);
+
+ img->drawBitmap(frameNo, r.left + frame._xoff, r.top + frame._yoff, fillRect, *dst);
+ }
+}
+
+void TalkDataHead::drawHeadType3(Graphics::ManagedSurface *dst, const Image &img) const {
+ Common::Rect r = _rect.toCommonRect();
+ dst->fillRect(r, 0);
+ if (!img.isLoaded())
+ return;
+ for (const auto &frame : _headFrames) {
+ int frameNo = frame._frameNo;
+ if (frameNo < img.loadedFrameCount())
+ img.drawBitmap(frameNo, r.left + frame._xoff, r.top + frame._yoff, r, *dst);
+ else
+ dst->fillRect(r, 4);
+ }
+}
+
+void TalkDataHead::updateHead() {
+ warning("TODO: Update head");
+ _flags = static_cast<HeadFlags>(_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 TalkData::updateVisibleHeads() {
+ for (auto &head : _heads) {
+ if (head._flags & kHeadFlagVisible)
+ head.updateHead();
+ }
+}
+
+void TalkData::drawVisibleHeads(Graphics::ManagedSurface *dst) const {
+ for (const auto &h : _heads) {
+ if ((h._flags & kHeadFlagVisible) && !(h._flags & kHeadFlag40)) {
+ h.drawHead(dst, *this);
+ }
+ }
+}
+
+bool TalkData::hasVisibleHead() const {
+ for (const auto &h : _heads) {
+ if (h._flags & kHeadFlagVisible)
+ return true;
+ }
+ return false;
+}
+
+//////
+
+void Conversation::unload() {
+ if (_sound) {
+ _sound->stop();
+ _sound.reset();
+ }
+ _img.reset();
+ _ttmScript.reset();
+ _ttmEnv = TTMEnviro();
+}
+
+void Conversation::loadData(uint16 dlgFileNum, uint16 dlgNum, int16 sub) {
+ unload();
+
+ DgdsEngine *engine = DgdsEngine::getInstance();
+ ResourceManager *resourceManager = engine->getResourceManager();
+ Decompressor *decompressor = engine->getDecompressor();
+
+ Common::String fname;
+ if (sub >= 0) {
+ assert(sub < 26);
+ fname = Common::String::format("F%dB%d%c.CDS", dlgFileNum, dlgNum, 'A' + sub);
+ } else {
+ fname = Common::String::format("F%dB%d.CDS", dlgFileNum, dlgNum);
+ }
+
+ _sound.reset(new SoundRaw(resourceManager, decompressor));
+ _sound->load(fname);
+ _img.reset(new Image(resourceManager, decompressor));
+ _img->loadBitmap(fname);
+ _ttmScript.reset(new TTMInterpreter(engine));
+ _ttmScript->load(fname, _ttmEnv);
+ _ttmScript->findAndAddSequences(_ttmEnv, _ttmSeqs);
+
+ // The scripts are desiged so the resources are patchable, but by default
+ // they use the sound and image data from the CDS file.
+ _ttmEnv._soundRaw = _sound;
+ _ttmEnv._scriptShapes[0] = _img;
+ _ttmEnv._cdsTarget = _ttmSeqs[0]->_seqNum;
+}
+
+void Conversation::runScript() {
+ if (!_ttmScript)
+ return;
+ for (auto seq : _ttmSeqs) {
+ if (seq->_seqNum == _ttmEnv._cdsTarget) {
+ _ttmScript->run(_ttmEnv, *seq);
+ }
+ }
+}
+
+} // end namespace Dgds
diff --git a/engines/dgds/head.h b/engines/dgds/head.h
new file mode 100644
index 00000000000..d9dbfe5aecc
--- /dev/null
+++ b/engines/dgds/head.h
@@ -0,0 +1,127 @@
+/* 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/>.
+ *
+ */
+
+#ifndef DGDS_HEAD_H
+#define DGDS_HEAD_H
+
+#include "common/str.h"
+#include "common/types.h"
+#include "common/ptr.h"
+#include "common/array.h"
+
+#include "graphics/managed_surface.h"
+
+#include "dgds/dgds_rect.h"
+#include "dgds/ttm.h"
+
+// Classes related to talking heads and conversations.
+
+namespace Dgds {
+
+class Image;
+class SoundRaw;
+
+
+class TalkDataHeadFrame {
+public:
+ TalkDataHeadFrame() : _xoff(0), _yoff(0), _frameNo(0), _flipFlags(0) {}
+ Common::String dump(const Common::String &indent) const;
+
+ uint16 _frameNo;
+ int16 _xoff;
+ int16 _yoff;
+ uint16 _flipFlags;
+};
+
+enum HeadFlags {
+ kHeadFlagNone = 0,
+ kHeadFlag1 = 1,
+ kHeadFlag2 = 2,
+ kHeadFlag4 = 4,
+ kHeadFlag8 = 8,
+ kHeadFlag10 = 0x10,
+ kHeadFlagVisible = 0x20,
+ kHeadFlag40 = 0x40,
+ kHeadFlag80 = 0x80,
+};
+
+class TalkData;
+
+class TalkDataHead {
+public:
+ TalkDataHead() : _num(0), _drawType(0), _drawCol(0), _flags(kHeadFlagNone) {}
+ Common::String dump(const Common::String &indent) const;
+
+ void updateHead();
+
+ void drawHead(Graphics::ManagedSurface *dst, const TalkData &data) const;
+ void drawHeadType1(Graphics::ManagedSurface *dst, const Image &img) const;
+ void drawHeadType2(Graphics::ManagedSurface *dst, const Image &img) const;
+ void drawHeadType3(Graphics::ManagedSurface *dst, const Image &img) const;
+ void drawHeadType3Beamish(Graphics::ManagedSurface *dst, const TalkData &data) const;
+
+ uint16 _num;
+ uint16 _drawType;
+ uint16 _drawCol;
+ DgdsRect _rect;
+ Common::Array<TalkDataHeadFrame> _headFrames;
+ Common::String _bmpFile;
+ HeadFlags _flags;
+ Common::SharedPtr<Image> _shape;
+};
+
+/** TDS talking head data from HOC+ */
+class TalkData {
+public:
+ TalkData() : _num(0), _val(0) {}
+ Common::String dump(const Common::String &indent) const;
+
+ uint16 _num;
+ Common::SharedPtr<Image> _shape;
+ Common::Array<TalkDataHead> _heads;
+ uint16 _val;
+ Common::String _bmpFile;
+
+ void updateVisibleHeads();
+ void drawVisibleHeads(Graphics::ManagedSurface *dst) const;
+ bool hasVisibleHead() const;
+};
+
+/** CDS data from Willy Beamish talkie */
+class Conversation {
+public:
+ Conversation() {}
+
+ void unload();
+ void runScript();
+ void loadData(uint16 num, uint16 num2, int16 sub);
+
+ Common::SharedPtr<SoundRaw> _sound;
+ Common::SharedPtr<Image> _img;
+ Common::SharedPtr<TTMInterpreter> _ttmScript;
+ Common::Array<Common::SharedPtr<TTMSeq>> _ttmSeqs;
+ TTMEnviro _ttmEnv;
+};
+
+
+} // end namespace Dgds
+
+#endif // DGDS_HEAD_H
diff --git a/engines/dgds/image.cpp b/engines/dgds/image.cpp
index 1773b0e8919..029c19144fe 100644
--- a/engines/dgds/image.cpp
+++ b/engines/dgds/image.cpp
@@ -146,8 +146,8 @@ void Image::loadBitmap(const Common::String &filename) {
ex = 0;
}
- if (ex != EX_BMP) {
- warning("Unknown bitmap tag: %d", ex);
+ if (ex != EX_BMP && ex != EX_CDS) {
+ warning("Unknown bitmap extension: %d", ex);
delete fileStream;
return;
}
diff --git a/engines/dgds/module.mk b/engines/dgds/module.mk
index 157f6f51287..b9926eadcec 100644
--- a/engines/dgds/module.mk
+++ b/engines/dgds/module.mk
@@ -13,6 +13,7 @@ MODULE_OBJS := \
font.o \
game_palettes.o \
globals.o \
+ head.o \
hoc_intro.o \
image.o \
inventory.o \
diff --git a/engines/dgds/parser.cpp b/engines/dgds/parser.cpp
index cf29c344276..850f4d6328f 100644
--- a/engines/dgds/parser.cpp
+++ b/engines/dgds/parser.cpp
@@ -117,7 +117,7 @@ bool TTMParser::handleChunk(DgdsChunkReader &chunk, ParserData *data) {
scriptData->_frameOffsets.resize(scriptData->_totalFrames + 1, -1);
break;
default:
- warning("Unexpected chunk '%s' of size %d found in file '%s'", tag2str(chunk.getId()), chunk.getSize(), _filename.c_str());
+ debug("TTMParser: Unexpected chunk '%s' of size %d found in file '%s'", tag2str(chunk.getId()), chunk.getSize(), _filename.c_str());
//chunk._contentStream->skip(chunk._size);
break;
}
@@ -153,7 +153,7 @@ bool ADSParser::handleChunk(DgdsChunkReader &chunk, ParserData *data) {
case ID_VER: // Version - ignore
break;
default:
- warning("Unexpected chunk '%s' of size %d found in file '%s'", tag2str(chunk.getId()), chunk.getSize(), _filename.c_str());
+ warning("ADSParser: Unexpected chunk '%s' of size %d found in file '%s'", tag2str(chunk.getId()), chunk.getSize(), _filename.c_str());
break;
}
return false;
diff --git a/engines/dgds/resource.cpp b/engines/dgds/resource.cpp
index 0d5a2f90f1b..22de4099f77 100644
--- a/engines/dgds/resource.cpp
+++ b/engines/dgds/resource.cpp
@@ -176,7 +176,7 @@ bool DgdsChunkReader::isPacked() const {
packed = (_id == ID_BIN || _id == ID_VGA);
break;
case EX_CDS:
- packed = (_id == ID_TT3);
+ packed = (_id == ID_TT3 || _id == ID_BIN || _id == ID_VGA);
break;
case EX_GDS:
case EX_SDS:
diff --git a/engines/dgds/scene.cpp b/engines/dgds/scene.cpp
index 83bdeb56bdd..d4418e67e85 100644
--- a/engines/dgds/scene.cpp
+++ b/engines/dgds/scene.cpp
@@ -47,6 +47,7 @@
#include "dgds/dragon_native.h"
#include "dgds/hoc_intro.h"
#include "dgds/sound_raw.h"
+#include "dgds/ttm.h"
namespace Dgds {
@@ -1064,10 +1065,7 @@ void SDSScene::unload() {
_triggers.clear();
_talkData.clear();
_dynamicRects.clear();
- if (_dlgSound) {
- _dlgSound->stop();
- _dlgSound.reset();
- }
+ _conversation.unload();
_sceneDialogFlags = kDlgFlagNone;
}
@@ -1320,193 +1318,21 @@ void SDSScene::freeTalkData(uint16 num) {
void SDSScene::updateVisibleTalkers() {
for (auto &data : _talkData) {
- for (auto &head : data._heads) {
- if (head._flags & kHeadFlagVisible)
- updateHead(head);
- }
- }
-}
-
-
-bool SDSScene::loadCDSData(uint16 dlgFileNum, uint16 dlgNum, int16 sub) {
- if (_dlgSound) {
- _dlgSound->stop();
- _dlgSound.reset();
- }
-
- Common::String fname;
- if (sub >= 0) {
- assert(sub < 26);
- fname = Common::String::format("F%dB%d%c.CDS", dlgFileNum, dlgNum, 'A' + sub);
- } else {
- fname = Common::String::format("F%dB%d.CDS", dlgFileNum, dlgNum);
- }
-
- DgdsEngine *engine = DgdsEngine::getInstance();
- ResourceManager *resourceManager = engine->getResourceManager();
- Common::SeekableReadStream *cdsFile = resourceManager->getResource(fname);
- if (!cdsFile)
- return false;
-
- DgdsChunkReader chunk(cdsFile);
- Decompressor *decompressor = engine->getDecompressor();
-
- bool result = false;
-
- while (chunk.readNextHeader(EX_CDS, fname)) {
- if (chunk.isContainer()) {
- continue;
- }
-
- chunk.readContent(decompressor);
- Common::SeekableReadStream *stream = chunk.getContent();
-
- //
- // All CDS files contain TT3 sections with little scripts that load
- // and play a RAW sound file (eg F1B13.CDS loads CSCR013.RAW), but
- // they also have RAW sections with the sound data, embedded and the named
- // RAW files don't exist.
- //
- if (chunk.isSection(ID_RAW)) {
- _dlgSound.reset(new SoundRaw(resourceManager, decompressor));
- _dlgSound->loadFromStream(stream, chunk.getSize());
- _dlgSound->play();
- result = true;
- }
- }
-
- delete cdsFile;
- return result;
-}
-
-void SDSScene::drawHead(Graphics::ManagedSurface *dst, const TalkData &data, const TalkDataHead &head) {
- uint drawtype = head._drawType ? head._drawType : 1;
- // Use specific head shape if available (eg, in Willy Beamish), if not use talk data shape
- Common::SharedPtr<Image> img = head._shape;
- if (!img)
- img = data._shape;
- if (!img)
- return;
- switch (drawtype) {
- case 1:
- drawHeadType1(dst, head, *img);
- break;
- case 2:
- drawHeadType2(dst, head, *img);
- break;
- case 3:
- if (DgdsEngine::getInstance()->getGameId() == GID_WILLY)
- drawHeadType3Beamish(dst, data, head);
- else
- drawHeadType3(dst, head, *img);
- 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 & 0xff, 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 & 0xff, r.left + frame._xoff, r.top + frame._yoff, r, *dst);
+ data.updateVisibleHeads();
}
}
-void SDSScene::drawHeadType3Beamish(Graphics::ManagedSurface *dst, const TalkData &data, const TalkDataHead &head) {
- const Common::Rect r = head._rect.toCommonRect();
-
- // Note: only really need the 1px border here but just fill the box.
- dst->fillRect(r, 8);
-
- Common::Rect fillRect(r);
- fillRect.grow(-1);
- dst->fillRect(fillRect, head._drawCol);
-
- for (const auto &frame : head._headFrames) {
- int frameNo = frame._frameNo & 0x7fff;
- bool useHeadShape = frame._frameNo & 0x8000;
-
- Common::SharedPtr<Image> img = useHeadShape ? head._shape : data._shape;
- if (!img || !img->isLoaded() || frameNo >= img->loadedFrameCount())
- continue;
-
- ImageFlipMode flip = kImageFlipNone;
- // Yes, the numerical values are revesed here (1 -> 2 and 2 -> 1).
- // The head flip flags are reversed from the image draw flags.
- if (frame._flipFlags & 1)
- flip = static_cast<ImageFlipMode>(flip & kImageFlipH);
- if (frame._flipFlags & 2)
- flip = static_cast<ImageFlipMode>(flip & kImageFlipV);
-
- img->drawBitmap(frameNo, r.left + frame._xoff, r.top + frame._yoff, fillRect, *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) {
- int frameNo = frame._frameNo;
- if (frameNo < img.loadedFrameCount())
- img.drawBitmap(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);
- }
- }
+ tds.drawVisibleHeads(dst);
}
+ _conversation.runScript();
}
bool SDSScene::hasVisibleHead() const {
for (const auto &tds : _talkData) {
- for (const auto &h : tds._heads) {
- if (h._flags & kHeadFlagVisible)
- return true;
- }
+ if (tds.hasVisibleHead())
+ return true;
}
return false;
}
@@ -1587,7 +1413,7 @@ void SDSScene::showDialog(uint16 fileNum, uint16 dlgNum) {
loadTalkDataAndSetFlags(dialog._talkDataNum, dialog._talkDataHeadNum);
}
- loadCDSData(fileNum, dlgNum, -1);
+ _conversation.loadData(fileNum, dlgNum, -1);
// hide time gets set the first time it's drawn.
if (_dlgWithFlagLo8IsClosing && dialog.hasFlag(kDlgFlagLo8)) {
@@ -1653,7 +1479,7 @@ bool SDSScene::checkDialogActive() {
// immediately starts another dialog or changes scene, so the sound
// doesn't end up playing.
// Need to work out how to correctly delay until the sound finishes?
- loadCDSData(dlg._fileNum, dlg._num, action->num);
+ _conversation.loadData(dlg._fileNum, dlg._num, action->num);
// Take a copy of the dialog because the actions might change the scene
Dialog dlgCopy = dlg;
@@ -1679,6 +1505,9 @@ bool SDSScene::checkDialogActive() {
if (dlg._nextDialogDlgNum) {
dlg.setFlag(kDlgFlagHiFinished);
showDialog(dlg._nextDialogFileNum, dlg._nextDialogDlgNum);
+ } else {
+ // No next dialog clear CDS data
+ _conversation.unload();
}
}
if (dlg.hasFlag(kDlgFlagVisible)) {
diff --git a/engines/dgds/scene.h b/engines/dgds/scene.h
index 75d7412af17..40f61793845 100644
--- a/engines/dgds/scene.h
+++ b/engines/dgds/scene.h
@@ -27,6 +27,7 @@
#include "common/serializer.h"
#include "dgds/dialog.h"
+#include "dgds/head.h"
#include "dgds/dgds_rect.h"
#include "dgds/minigames/shell_game.h"
@@ -36,6 +37,8 @@ class ResourceManager;
class Decompressor;
class DgdsFont;
class SoundRaw;
+class TTMInterpreter;
+class TTMEnviro;
enum SceneCondition {
kSceneCondNone = 0,
@@ -262,57 +265,6 @@ private:
};
-class TalkDataHeadFrame {
-public:
- TalkDataHeadFrame() : _xoff(0), _yoff(0), _frameNo(0), _flipFlags(0) {}
- Common::String dump(const Common::String &indent) const;
-
- uint16 _frameNo;
- int16 _xoff;
- int16 _yoff;
- uint16 _flipFlags;
-};
-
-enum HeadFlags {
- kHeadFlagNone = 0,
- kHeadFlag1 = 1,
- kHeadFlag2 = 2,
- kHeadFlag4 = 4,
- kHeadFlag8 = 8,
- kHeadFlag10 = 0x10,
- kHeadFlagVisible = 0x20,
- kHeadFlag40 = 0x40,
- kHeadFlag80 = 0x80,
-};
-
-class TalkDataHead {
-public:
- TalkDataHead() : _num(0), _drawType(0), _drawCol(0), _flags(kHeadFlagNone) {}
- Common::String dump(const Common::String &indent) const;
-
- uint16 _num;
- uint16 _drawType;
- uint16 _drawCol;
- DgdsRect _rect;
- Common::Array<TalkDataHeadFrame> _headFrames;
- Common::String _bmpFile;
- HeadFlags _flags;
- Common::SharedPtr<Image> _shape;
-};
-
-class TalkData {
-public:
- TalkData() : _num(0), _val(0) {}
- Common::String dump(const Common::String &indent) const;
-
- uint16 _num;
- Common::SharedPtr<Image> _shape;
- Common::Array<TalkDataHead> _heads;
- uint16 _val;
- Common::String _bmpFile;
-};
-
-
/**
* A scene is described by an SDS file, which points to the ADS script to load
* and holds the dialog info.
@@ -482,7 +434,6 @@ public:
void loadTalkDataAndSetFlags(uint16 talknum, uint16 headnum);
void drawVisibleHeads(Graphics::ManagedSurface *dst);
bool hasVisibleHead() const;
- bool loadCDSData(uint16 num, uint16 num2, int16 sub);
// dragon-specific scene ops
void addAndShowTiredDialog();
@@ -505,12 +456,6 @@ protected:
private:
Dialog *getVisibleDialog();
bool readTalkData(Common::SeekableReadStream *s, TalkData &dst);
- void updateHead(TalkDataHead &head);
- void drawHead(Graphics::ManagedSurface *dst, const TalkData &data, const TalkDataHead &head);
- void drawHeadType1(Graphics::ManagedSurface *dst, const TalkDataHead &head, const Image &img);
- void drawHeadType2(Graphics::ManagedSurface *dst, const TalkDataHead &head, const Image &img);
- void drawHeadType3(Graphics::ManagedSurface *dst, const TalkDataHead &head, const Image &img);
- void drawHeadType3Beamish(Graphics::ManagedSurface *dst, const TalkData &data, const TalkDataHead &head);
int _num;
Common::Array<SceneOp> _enterSceneOps;
@@ -531,7 +476,7 @@ private:
// From here on is mutable stuff that might need saving
Common::Array<Dialog> _dialogs;
Common::Array<SceneTrigger> _triggers;
- Common::SharedPtr<SoundRaw> _dlgSound;
+ Conversation _conversation;
GameItem *_dragItem;
bool _shouldClearDlg;
diff --git a/engines/dgds/sound_raw.cpp b/engines/dgds/sound_raw.cpp
index 937055ba1e5..f02b90c6c9a 100644
--- a/engines/dgds/sound_raw.cpp
+++ b/engines/dgds/sound_raw.cpp
@@ -74,4 +74,12 @@ bool SoundRaw::isPlaying() const {
return mixer->isSoundHandleActive(_handle);
}
+uint32 SoundRaw::playedOffset() const {
+ if (!isPlaying())
+ return 0xFFFFFFFF;
+ Audio::Mixer *mixer = DgdsEngine::getInstance()->_mixer;
+ uint32 msecs = mixer->getSoundElapsedTime(_handle);
+ return (msecs * 11025) / 1000;
+}
+
} // end namespace Dgds
diff --git a/engines/dgds/sound_raw.h b/engines/dgds/sound_raw.h
index 1006595a321..e1e423a4824 100644
--- a/engines/dgds/sound_raw.h
+++ b/engines/dgds/sound_raw.h
@@ -46,6 +46,7 @@ public:
void stop();
bool isPlaying() const;
void loadFromStream(Common::SeekableReadStream *stream, int size);
+ uint32 playedOffset() const;
private:
Common::Array<byte> _data;
diff --git a/engines/dgds/ttm.cpp b/engines/dgds/ttm.cpp
index 9bdae9d7852..f6c4d97fe28 100644
--- a/engines/dgds/ttm.cpp
+++ b/engines/dgds/ttm.cpp
@@ -172,6 +172,8 @@ static const char *ttmOpName(uint16 op) {
case 0x2400: return "PAL DO BLOCK SWAP";
case 0x3000: return "GOSUB";
case 0x3100: return "SCROLL";
+ case 0x3200: return "CDS FIND TARGET";
+ case 0x3300: return "CDS GOSUB";
case 0x4000: return "SET CLIP WINDOW";
case 0x4110: return "FADE OUT";
case 0x4120: return "FADE IN";
@@ -251,7 +253,9 @@ static const char *ttmOpName(uint16 op) {
case 0xc0f0: return "SONG CONTROLLER??";
case 0xc100: return "SAMPLE VOL";
case 0xc210: return "LOAD RAW SFX";
- case 0xc220: return "PLAY RAW SFX ??";
+ case 0xc220: return "PLAY RAW SFX";
+ case 0xc240: return "STOP RAW SFX";
+ case 0xc250: return "SYNC RAW SFX";
case 0xcf10: return "SFX MASTER VOL";
default: return "UNKNOWN!!";
@@ -570,7 +574,7 @@ void TTMInterpreter::doDrawDialogForStrings(const TTMEnviro &env, const TTMSeq &
}
-void TTMInterpreter::handleOperation(TTMEnviro &env, TTMSeq &seq, uint16 op, byte count, const int16 *ivals, const Common::String &sval, const Common::Array<Common::Point> &pts) {
+bool TTMInterpreter::handleOperation(TTMEnviro &env, TTMSeq &seq, uint16 op, byte count, const int16 *ivals, const Common::String &sval, const Common::Array<Common::Point> &pts) {
switch (op) {
case 0x0000: // FINISH: void
break;
@@ -643,11 +647,13 @@ void TTMInterpreter::handleOperation(TTMEnviro &env, TTMSeq &seq, uint16 op, byt
break;
case 0x0ff0: // REFRESH: void
break;
- case 0x1020: // SET DELAY: i:int [0..n]
+ case 0x1020: { // SET DELAY: i:int [0..n]
// TODO: Probably should do this accounting (as well as timeCut and dialogs)
// in game frames, not millis.
- _vm->adsInterpreter()->setScriptDelay((int)round(ivals[0] * MS_PER_FRAME));
+ int delayMillis = (int)round(ivals[0] * MS_PER_FRAME);
+ _vm->adsInterpreter()->setScriptDelay(delayMillis);
break;
+ }
case 0x1030: // SET BRUSH: id:int [-1:n]
seq._brushNum = ivals[0];
break;
@@ -771,6 +777,19 @@ void TTMInterpreter::handleOperation(TTMEnviro &env, TTMSeq &seq, uint16 op, byt
_vm->_compositionBuffer.blitFrom(_vm->getBackgroundBuffer());
break;
}
+ case 0x3200:
+ env._cdsTarget = findGOTOTarget(env, seq, ivals[0]);
+ break;
+ case 0x3300:
+ if (!env._cdsJumped && env._frameOffsets[env._cdsTarget] != env.scr->pos()) {
+ env._cdsJumped = true;
+ int64 prevPos = env.scr->pos();
+ env.scr->seek(env._frameOffsets[env._cdsTarget]);
+ run(env, seq);
+ env.scr->seek(prevPos);
+ env._cdsJumped = false;
+ }
+ break;
case 0x4000: // SET CLIP WINDOW x,y,x2,y2:int [0..320,0..200]
// NOTE: params are xmax/ymax, NOT w/h
seq._drawWin = Common::Rect(ivals[0], ivals[1], ivals[2] + 1, ivals[3] + 1);
@@ -1072,9 +1091,14 @@ void TTMInterpreter::handleOperation(TTMEnviro &env, TTMSeq &seq, uint16 op, byt
case 0xc210: { // LOAD RAW SFX filename:str
if (seq._executed) // this is a one-shot op
break;
- SoundRaw *snd = new SoundRaw(_vm->getResourceManager(), _vm->getDecompressor());
- snd->load(sval);
- env._soundRaw.reset(snd);
+ if (_vm->getResourceManager()->hasResource(sval)) {
+ SoundRaw *snd = new SoundRaw(_vm->getResourceManager(), _vm->getDecompressor());
+ snd->load(sval);
+ env._soundRaw.reset(snd);
+ } else {
+ // This happens in Willy Beamish talkie CDS files.
+ debug("TTM 0xC210: Skip loading RAW %s, not found.", sval.c_str());
+ }
break;
}
case 0xc220: { // PLAY RAW SFX
@@ -1086,6 +1110,26 @@ void TTMInterpreter::handleOperation(TTMEnviro &env, TTMSeq &seq, uint16 op, byt
env._soundRaw->play();
break;
}
+ case 0xc240: { // STOP RAW SFX
+ if (env._soundRaw) {
+ env._soundRaw->stop();
+ } else {
+ warning("TODO: Trying to stop raw SFX but nothing loaded");
+ }
+ }
+ case 0xc250: { // SYNC RAW SFX
+ uint16 hi = (uint16)ivals[1];
+ uint16 lo = (uint16)ivals[0];
+ uint32 offset = ((uint32)hi << 16) + lo;
+ debug("TODO: 0xC250 Sync raw sfx?? offset %d", offset);
+ /*
+ if (env._soundRaw->playedOffset() < offset) {
+ // Not played to this point yet.
+ env.scr->seek(-6);
+ return false;
+ }
+ */
+ }
case 0xf010: { // LOAD SCR: filename:str
if (seq._executed) // this is a one-shot op
break;
@@ -1100,8 +1144,13 @@ void TTMInterpreter::handleOperation(TTMEnviro &env, TTMSeq &seq, uint16 op, byt
if (seq._executed) // this is a one-shot op
break;
//debug(1, "0xf020: Load bitmap %s to slot %d for env %d", sval.c_str(), env._enviro, seq._currentBmpId);
- env._scriptShapes[seq._currentBmpId].reset(new Image(_vm->getResourceManager(), _vm->getDecompressor()));
- env._scriptShapes[seq._currentBmpId]->loadBitmap(sval);
+ if (_vm->getResourceManager()->hasResource(sval)) {
+ env._scriptShapes[seq._currentBmpId].reset(new Image(_vm->getResourceManager(), _vm->getDecompressor()));
+ env._scriptShapes[seq._currentBmpId]->loadBitmap(sval);
+ } else {
+ // This happens in Willy Beamish talkie CDS files.
+ debug("TTM 0xF020: Skip loading BMP %s, not found.", sval.c_str());
+ }
break;
case 0xf040: { // LOAD FONT: filename:str
if (seq._executed) // this is a one-shot op
@@ -1162,6 +1211,7 @@ void TTMInterpreter::handleOperation(TTMEnviro &env, TTMSeq &seq, uint16 op, byt
ttmOpName(op), sval.c_str());
break;
}
+ return true;
}
Common::String TTMInterpreter::readTTMStringVal(Common::SeekableReadStream *scr) {
@@ -1230,7 +1280,9 @@ bool TTMInterpreter::run(TTMEnviro &env, TTMSeq &seq) {
}
debug(10, " (%s)", ttmOpName(op));
- handleOperation(env, seq, op, count, ivals, sval, pts);
+ bool opResult = handleOperation(env, seq, op, count, ivals, sval, pts);
+ if (!opResult)
+ break;
}
return true;
diff --git a/engines/dgds/ttm.h b/engines/dgds/ttm.h
index ab950dc39ad..cf73d26495a 100644
--- a/engines/dgds/ttm.h
+++ b/engines/dgds/ttm.h
@@ -41,7 +41,7 @@ class TTMEnviro : public ScriptParserData {
public:
TTMEnviro() : _totalFrames(330), _enviro(0), _creditScrollMeasure(0),
_creditScrollYOffset(0), _xOff(0), _yOff(0), _xScroll(0), _yScroll(0),
- ScriptParserData() {
+ _cdsTarget(0), _cdsJumped(false), ScriptParserData() {
ARRAYCLEAR(_scriptPals);
}
@@ -65,6 +65,8 @@ public:
int16 _xScroll;
int16 _yScroll;
Common::SharedPtr<SoundRaw> _soundRaw;
+ int16 _cdsTarget; // The GOTO target to use in the CDS script (Willy Beamish talkie)
+ bool _cdsJumped;
};
enum TTMRunType {
@@ -126,7 +128,7 @@ public:
static Common::String readTTMStringVal(Common::SeekableReadStream *scr);
protected:
- void handleOperation(TTMEnviro &env, TTMSeq &seq, uint16 op, byte count, const int16 *ivals, const Common::String &sval, const Common::Array<Common::Point> &pts);
+ bool handleOperation(TTMEnviro &env, TTMSeq &seq, uint16 op, byte count, const int16 *ivals, const Common::String &sval, const Common::Array<Common::Point> &pts);
int32 findGOTOTarget(const TTMEnviro &env, const TTMSeq &seq, int16 frame);
void doWipeOp(uint16 code, const TTMEnviro &env, const TTMSeq &seq, const Common::Rect &r);
int16 doOpInitCreditScroll(const Image *img);
Commit: b4f70c660d30394b11322a6610bdc140200611cd
https://github.com/scummvm/scummvm/commit/b4f70c660d30394b11322a6610bdc140200611cd
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2024-12-16T18:27:13+11:00
Commit Message:
DGDS: Refactor scene class a bit
Extract SceneOp and SceneCondition to their own files to reduce file size a
bit.
Also fix some small issues in CDS loading.
Changed paths:
A engines/dgds/debug_util.h
A engines/dgds/scene_condition.cpp
A engines/dgds/scene_condition.h
A engines/dgds/scene_op.cpp
A engines/dgds/scene_op.h
engines/dgds/dialog.cpp
engines/dgds/head.cpp
engines/dgds/head.h
engines/dgds/module.mk
engines/dgds/scene.cpp
engines/dgds/scene.h
engines/dgds/ttm.cpp
engines/dgds/ttm.h
diff --git a/engines/dgds/debug_util.h b/engines/dgds/debug_util.h
new file mode 100644
index 00000000000..668bf6d1fc8
--- /dev/null
+++ b/engines/dgds/debug_util.h
@@ -0,0 +1,48 @@
+/* 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/>.
+ *
+ */
+
+#ifndef DGDS_DEBUG_UTIL_H
+#define DGDS_DEBUG_UTIL_H
+
+#include "common/str.h"
+
+namespace Dgds {
+
+namespace DebugUtil {
+
+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;
+}
+
+};
+
+} // end namespace Dgds
+
+#endif // DGDS_DEBUG_UTIL_H
diff --git a/engines/dgds/dialog.cpp b/engines/dgds/dialog.cpp
index 8b4607c0d00..b88af143da3 100644
--- a/engines/dgds/dialog.cpp
+++ b/engines/dgds/dialog.cpp
@@ -36,23 +36,10 @@
#include "dgds/scene.h"
#include "dgds/font.h"
#include "dgds/drawing.h"
+#include "dgds/debug_util.h"
namespace Dgds {
-// TODO: This is repeated here and in scene.cpp
-template<class S> static Common::String _dumpStructList(const Common::String &indent, const Common::String &name, const Common::Array<S> &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;
-}
-
int Dialog::_lastSelectedDialogItemNum = 0;
Dialog *Dialog::_lastDialogSelectionChangedFor = nullptr;
@@ -694,7 +681,7 @@ Common::String Dialog::dump(const Common::String &indent) const {
_flags, _frameType, _time, _nextDialogFileNum, _nextDialogDlgNum, _talkDataNum, _talkDataHeadNum);
str += indent + "state=" + (_state ? _state->dump("") : "null");
str += "\n";
- str += _dumpStructList(indent, "actions", _action);
+ str += DebugUtil::dumpStructList(indent, "actions", _action);
str += "\n";
str += indent + " str='" + _str + "'>";
return str;
@@ -739,7 +726,7 @@ Common::Error DialogState::syncState(Common::Serializer &s) {
Common::String DialogAction::dump(const Common::String &indent) const {
Common::String str = Common::String::format("%sDialogueAction<span: %d-%d", indent.c_str(), strStart, strEnd);
- str += _dumpStructList(indent, "opList", sceneOpList);
+ str += DebugUtil::dumpStructList(indent, "opList", sceneOpList);
if (!sceneOpList.empty()) {
str += "\n";
str += indent;
diff --git a/engines/dgds/head.cpp b/engines/dgds/head.cpp
index 45e7bea81e2..96c53897b29 100644
--- a/engines/dgds/head.cpp
+++ b/engines/dgds/head.cpp
@@ -178,6 +178,11 @@ void Conversation::loadData(uint16 dlgFileNum, uint16 dlgNum, int16 sub) {
unload();
DgdsEngine *engine = DgdsEngine::getInstance();
+
+ // These files are only present in Willy Beamish CD version
+ if (engine->getGameId() != GID_WILLY)
+ return;
+
ResourceManager *resourceManager = engine->getResourceManager();
Decompressor *decompressor = engine->getDecompressor();
@@ -189,6 +194,9 @@ void Conversation::loadData(uint16 dlgFileNum, uint16 dlgNum, int16 sub) {
fname = Common::String::format("F%dB%d.CDS", dlgFileNum, dlgNum);
}
+ if (!resourceManager->hasResource(fname))
+ return;
+
_sound.reset(new SoundRaw(resourceManager, decompressor));
_sound->load(fname);
_img.reset(new Image(resourceManager, decompressor));
@@ -201,15 +209,37 @@ void Conversation::loadData(uint16 dlgFileNum, uint16 dlgNum, int16 sub) {
// they use the sound and image data from the CDS file.
_ttmEnv._soundRaw = _sound;
_ttmEnv._scriptShapes[0] = _img;
- _ttmEnv._cdsTarget = _ttmSeqs[0]->_seqNum;
+
+ // Always run seq 1 on init.
+ _ttmEnv._cdsSeqNum = 1;
+ runScript();
}
void Conversation::runScript() {
if (!_ttmScript)
return;
+
+ DgdsEngine *engine = DgdsEngine::getInstance();
+ if (_nextExec && engine->getThisFrameMs() < _nextExec)
+ return;
+
+ _nextExec = 0;
+ _ttmEnv._xOff = _drawRect.x;
+ _ttmEnv._yOff = _drawRect.y;
+
for (auto seq : _ttmSeqs) {
- if (seq->_seqNum == _ttmEnv._cdsTarget) {
+ if (seq->_seqNum == _ttmEnv._cdsSeqNum) {
+ debug(10, "CDS: Running TTM sequence %d frame %d", seq->_seqNum, seq->_currentFrame);
+ _ttmEnv.scr->seek(_ttmEnv._frameOffsets[seq->_currentFrame]);
+
+ seq->_drawWin = _drawRect.toCommonRect();
_ttmScript->run(_ttmEnv, *seq);
+ if (_ttmEnv._cdsDelay) {
+ _nextExec = engine->getThisFrameMs() + _ttmEnv._cdsDelay;
+ _ttmEnv._cdsDelay = 0;
+ } else {
+ seq->_currentFrame++;
+ }
}
}
}
diff --git a/engines/dgds/head.h b/engines/dgds/head.h
index d9dbfe5aecc..b2c7558f5a5 100644
--- a/engines/dgds/head.h
+++ b/engines/dgds/head.h
@@ -99,7 +99,7 @@ public:
Common::Array<TalkDataHead> _heads;
uint16 _val;
Common::String _bmpFile;
-
+
void updateVisibleHeads();
void drawVisibleHeads(Graphics::ManagedSurface *dst) const;
bool hasVisibleHead() const;
@@ -108,7 +108,7 @@ public:
/** CDS data from Willy Beamish talkie */
class Conversation {
public:
- Conversation() {}
+ Conversation() : _nextExec(0) {}
void unload();
void runScript();
@@ -119,6 +119,8 @@ public:
Common::SharedPtr<TTMInterpreter> _ttmScript;
Common::Array<Common::SharedPtr<TTMSeq>> _ttmSeqs;
TTMEnviro _ttmEnv;
+ uint32 _nextExec;
+ DgdsRect _drawRect;
};
diff --git a/engines/dgds/module.mk b/engines/dgds/module.mk
index b9926eadcec..2dc84cdb616 100644
--- a/engines/dgds/module.mk
+++ b/engines/dgds/module.mk
@@ -23,6 +23,8 @@ MODULE_OBJS := \
request.o \
resource.o \
scene.o \
+ scene_condition.o \
+ scene_op.o \
scripts.o \
sound.o \
sound_raw.o \
diff --git a/engines/dgds/scene.cpp b/engines/dgds/scene.cpp
index d4418e67e85..45a7ca23425 100644
--- a/engines/dgds/scene.cpp
+++ b/engines/dgds/scene.cpp
@@ -26,195 +26,34 @@
#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"
-#include "dgds/sound_raw.h"
-#include "dgds/ttm.h"
+#include "dgds/debug_util.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 cursor2 %d interactionRectNum %d",
indent.c_str(), _rect.dump("").c_str(), _num, _cursorNum, _cursorNum2, _objInteractionRectNum);
- str += _dumpStructList(indent, "enableConditions", enableConditions);
- str += _dumpStructList(indent, "onRClickOps", onRClickOps);
- str += _dumpStructList(indent, "onLDownOps", onLDownOps);
- str += _dumpStructList(indent, "onLClickOps", onLClickOps);
+ str += DebugUtil::dumpStructList(indent, "enableConditions", enableConditions);
+ str += DebugUtil::dumpStructList(indent, "onRClickOps", onRClickOps);
+ str += DebugUtil::dumpStructList(indent, "onLDownOps", onLDownOps);
+ str += DebugUtil::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 + " ");
@@ -222,8 +61,8 @@ Common::String GameItem::dump(const Common::String &indent) const {
"%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 += DebugUtil::dumpStructList(indent, "onDragFinishedOps", onDragFinishedOps);
+ str += DebugUtil::dumpStructList(indent, "onBothButtonsOps", onBothButtonsOps);
str += "\n";
str += indent + ">";
return str;
@@ -238,7 +77,7 @@ Common::String MouseCursor::dump(const Common::String &indent) const {
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 += DebugUtil::dumpStructList(indent, "opList", opList);
str += "\n";
str += indent + ">";
return str;
@@ -247,8 +86,8 @@ Common::String ObjectInteraction::dump(const Common::String &indent) const {
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 += DebugUtil::dumpStructList(indent, "conditionList", conditionList);
+ str += DebugUtil::dumpStructList(indent, "opList", sceneOpList);
str += "\n";
str += indent + ">";
return str;
@@ -623,244 +462,6 @@ void Scene::segmentStateOps(const Common::Array<uint16> &args) {
}
-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();
- 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 & kSceneOpHasConditionalOpsFlag) {
- uint16 opcode = op._opCode & ~kSceneOpHasConditionalOpsFlag;
- 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
@@ -884,24 +485,8 @@ bool Scene::runOps(const Common::Array<SceneOp> ops, int16 addMinuites /* = 0 */
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");
- }
- }
+
+ sceneChanged = op.runOp();
if (sceneChanged)
break;
@@ -1072,15 +657,15 @@ void SDSScene::unload() {
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 += DebugUtil::dumpStructList(indent, "enterSceneOps", _enterSceneOps);
+ str += DebugUtil::dumpStructList(indent, "leaveSceneOps", _leaveSceneOps);
+ str += DebugUtil::dumpStructList(indent, "preTickOps", _preTickOps);
+ str += DebugUtil::dumpStructList(indent, "postTickOps", _postTickOps);
+ str += DebugUtil::dumpStructList(indent, "hotAreaList", _hotAreaList);
+ str += DebugUtil::dumpStructList(indent, "objInteractions1", _objInteractions1);
+ str += DebugUtil::dumpStructList(indent, "objInteractions2", _objInteractions2);
+ str += DebugUtil::dumpStructList(indent, "dialogues", _dialogs);
+ str += DebugUtil::dumpStructList(indent, "triggers", _triggers);
str += "\n";
str += indent + ">";
@@ -1348,6 +933,8 @@ void SDSScene::loadTalkDataAndSetFlags(uint16 talknum, uint16 headnum) {
for (auto &head : data._heads) {
if (head._num != headnum)
continue;
+
+ _conversation._drawRect = head._rect;
head._flags = static_cast<HeadFlags>(head._flags & ~(kHeadFlag1 | kHeadFlag10));
head._flags = static_cast<HeadFlags>(head._flags | (kHeadFlag8 | kHeadFlagVisible));
break;
@@ -2240,15 +1827,15 @@ bool GDSScene::parse(Common::SeekableReadStream *stream) {
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 += DebugUtil::dumpStructList(indent, "gameItems", _gameItems);
+ str += DebugUtil::dumpStructList(indent, "startGameOps", _startGameOps);
+ str += DebugUtil::dumpStructList(indent, "quitGameOps", _quitGameOps);
+ str += DebugUtil::dumpStructList(indent, "preTickOps", _preTickOps);
+ str += DebugUtil::dumpStructList(indent, "postTickOps", _postTickOps);
+ str += DebugUtil::dumpStructList(indent, "onChangeSceneOps", _onChangeSceneOps);
+ str += DebugUtil::dumpStructList(indent, "perSceneGlobals", _perSceneGlobals);
+ str += DebugUtil::dumpStructList(indent, "objInteractions1", _objInteractions1);
+ str += DebugUtil::dumpStructList(indent, "objInteractions2", _objInteractions2);
str += "\n";
str += indent + ">";
diff --git a/engines/dgds/scene.h b/engines/dgds/scene.h
index 40f61793845..944e84aa411 100644
--- a/engines/dgds/scene.h
+++ b/engines/dgds/scene.h
@@ -30,6 +30,8 @@
#include "dgds/head.h"
#include "dgds/dgds_rect.h"
#include "dgds/minigames/shell_game.h"
+#include "dgds/scene_condition.h"
+#include "dgds/scene_op.h"
namespace Dgds {
@@ -40,32 +42,6 @@ class SoundRaw;
class TTMInterpreter;
class TTMEnviro;
-enum SceneCondition {
- kSceneCondNone = 0,
- kSceneCondLessThan = 1,
- kSceneCondEqual = 2,
- kSceneCondNegate = 4,
- kSceneCondAbsVal = 8,
- kSceneCondOr = 0x10,
- kSceneCondNeedItemSceneNum = 0x20,
- kSceneCondNeedItemQuality = 0x40,
- kSceneCondSceneState = 0x80
-};
-
-class SceneConditions {
-public:
- SceneConditions(uint16 num, SceneCondition cond, int16 val) : _num(num), _flags(cond), _val(val) {}
- Common::String dump(const Common::String &indent) const;
-
- uint16 getNum() const { return _num; }
- SceneCondition getCond() const { return _flags; }
- int16 getVal() const { return _val; }
-
-private:
- uint16 _num;
- SceneCondition _flags; /* eg, see usage in FUN_1f1a_2106 */
- int16 _val;
-};
class HotArea {
public:
@@ -94,93 +70,6 @@ public:
DgdsRect _rect;
};
-enum SceneOpCode {
- kSceneOpNone = 0,
- kSceneOpChangeScene = 1, // args: scene num
- kSceneOpNoop = 2, // args: none. Maybe should close dialogue?
- kSceneOpGlobal = 3, // args: array of uints
- kSceneOpSegmentStateOps = 4, // args: array of uint pairs [op seg, op seg], term with 0,0 that modify segment states
- kSceneOpSetItemAttr = 5, // args: [item num, item param 0x28, item param 0x2c]. set item attrs?
- kSceneOpSetDragItem = 6, // args: item num. give item?
- kSceneOpOpenInventory = 7, // args: none.
- kSceneOpShowDlg = 8, // args: dialogue number.
- kSceneOpShowInvButton = 9, // args: none.
- kSceneOpHideInvButton = 10, // args: none.
- kSceneOpEnableTrigger = 11, // args: trigger num
- kSceneOpChangeSceneToStored = 12, // args: none. Change scene to stored number
- kSceneOpAddFlagToDragItem = 13, // args: none.
- kSceneOpOpenInventoryZoom = 14, // args: none.
- kSceneOpMoveItemsBetweenScenes = 15, // args: none.
- kSceneOpShowClock = 16, // args: none. set clock script-visible.
- kSceneOpHideClock = 17, // args: none. set clock script-hidden.
- kSceneOpShowMouse = 18, // args: none.
- kSceneOpHideMouse = 19, // args: none.
- // Op 20 onward are common, but not in dragon
-
- kSceneOpLoadTalkDataAndSetFlags = 20, // args: tdsnum to load, headnum
- kSceneOpDrawVisibleTalkHeads = 21, // args: none
- kSceneOpLoadTalkData = 22, // args: tds num to load
- kSceneOpLoadDDSData = 24, // args: dds num to load
- kSceneOpFreeDDSData = 25, // args: dds num to free
- kSceneOpFreeTalkData = 26, // args: tds num to free
-
- // Dragon-specific opcodes
- kSceneOpPasscode = 100, // args: none.
- kSceneOpMeanwhile = 101, // args: none. Clears screen and displays "meanwhile".
- kSceneOpOpenGameOverMenu = 102, // args: none.
- kSceneOpTiredDialog = 103, // args: none. Something about "boy am I tired"?
- kSceneOpArcadeTick = 104, // args: none. Called in arcade post-tick.
- kSceneOpDrawDragonCountdown1 = 105, // args: none. Draw special countdown number at 141, 56
- kSceneOpDrawDragonCountdown2 = 106, // args: none. Draw some number at 250, 42
- kSceneOpOpenPlaySkipIntroMenu = 107, // args: none. DRAGON: Show menu 50, the "Play Introduction" / "Skip Introduction" menu.
- kSceneOpOpenBetterSaveGameMenu = 108, // args: none. DRAGON: Show menu 46, the "Before arcade maybe you better save your game" menu.
-
- // China-specific opcodes
- kSceneOpChinaTankInit = 100,
- kSceneOpChinaTankEnd = 101,
- kSceneOpChinaTankTick = 102,
- kSceneOpChinaSetLanding = 103,
- kSceneOpChinaScrollIntro = 104,
- kSceneOpChinaScrollLeft = 105,
- kSceneOpChinaScrollRight = 107,
- kSceneOpShellGameInit = 108,
- kSceneOpShellGameEnd = 109,
- kSceneOpShellGameTick = 110,
- kSceneOpChinaTrainInit = 111,
- kSceneOpChinaTrainEnd = 112,
- kSceneOpChinaTrainTick = 113,
- kSceneOpChinaOpenGameOverMenu = 114, // args: none.
- kSceneOpChinaOpenSkipCreditsMenu = 115, // args: none.
- kSceneOpChinaOnIntroTick = 116, // args: none.
- kSceneOpChinaOnIntroInit = 117, // args: none.
- kSceneOpChinaOnIntroEnd = 118, // args: none.
-
- // Beamish-specific opcodes
- kSceneOpOpenBeamishGameOverMenu = 100,
- kSceneOpOpenBeamishOpenSkipCreditsMenu = 101,
-
- kSceneOpMaxCode = 255, // for checking file load
-
- kSceneOpHasConditionalOpsFlag = 0x8000,
-};
-
-class SceneOp {
-public:
- Common::Array<SceneConditions> _conditionList;
- Common::Array<uint16> _args;
- SceneOpCode _opCode;
-
- Common::String dump(const Common::String &indent) const;
-};
-
-class ConditionalSceneOp {
-public:
- uint _opCode;
- Common::Array<SceneConditions> _conditionList;
- Common::Array<SceneOp> _opList;
-
- Common::String dump(const Common::String &indent) const;
-};
class GameItem : public HotArea {
public:
@@ -289,6 +178,13 @@ public:
virtual Common::Error syncState(Common::Serializer &s) = 0;
+ // These are all static as they are potentially run over scene changes.
+ static bool checkConditions(const Common::Array<SceneConditions> &cond);
+
+ static void segmentStateOps(const Common::Array<uint16> &args);
+ static void setItemAttrOp(const Common::Array<uint16> &args);
+ static void setDragItemOp(const Common::Array<uint16> &args);
+
protected:
bool readConditionList(Common::SeekableReadStream *s, Common::Array<SceneConditions> &list) const;
bool readHotArea(Common::SeekableReadStream *s, HotArea &dst) const;
@@ -302,17 +198,6 @@ protected:
bool readDialogActionList(Common::SeekableReadStream *s, Common::Array<DialogAction> &list) const;
bool readConditionalSceneOpList(Common::SeekableReadStream *s, Common::Array<ConditionalSceneOp> &list) const;
- static void segmentStateOps(const Common::Array<uint16> &args);
- static void setItemAttrOp(const Common::Array<uint16> &args);
- static void setDragItemOp(const Common::Array<uint16> &args);
-
- // These are all static as they are potentially run over scene changes.
- static bool checkConditions(const Common::Array<SceneConditions> &cond);
- static bool runSceneOp(const SceneOp &op);
- static bool runDragonOp(const SceneOp &op);
- static bool runChinaOp(const SceneOp &op);
- static bool runBeamishOp(const SceneOp &op);
-
uint32 _magic;
Common::String _version;
diff --git a/engines/dgds/scene_condition.cpp b/engines/dgds/scene_condition.cpp
new file mode 100644
index 00000000000..2129e5c61c5
--- /dev/null
+++ b/engines/dgds/scene_condition.cpp
@@ -0,0 +1,62 @@
+/* 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 "dgds/scene_condition.h"
+
+namespace Dgds {
+
+
+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);
+}
+
+} // end namespace Dgds
diff --git a/engines/dgds/scene_condition.h b/engines/dgds/scene_condition.h
new file mode 100644
index 00000000000..5324644efe5
--- /dev/null
+++ b/engines/dgds/scene_condition.h
@@ -0,0 +1,59 @@
+/* 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/>.
+ *
+ */
+
+#ifndef DGDS_SCENE_CONDITION_H
+#define DGDS_SCENE_CONDITION_H
+
+#include "common/types.h"
+#include "common/str.h"
+
+namespace Dgds {
+
+enum SceneCondition {
+ kSceneCondNone = 0,
+ kSceneCondLessThan = 1,
+ kSceneCondEqual = 2,
+ kSceneCondNegate = 4,
+ kSceneCondAbsVal = 8,
+ kSceneCondOr = 0x10,
+ kSceneCondNeedItemSceneNum = 0x20,
+ kSceneCondNeedItemQuality = 0x40,
+ kSceneCondSceneState = 0x80
+};
+
+class SceneConditions {
+public:
+ SceneConditions(uint16 num, SceneCondition cond, int16 val) : _num(num), _flags(cond), _val(val) {}
+ Common::String dump(const Common::String &indent) const;
+
+ uint16 getNum() const { return _num; }
+ SceneCondition getCond() const { return _flags; }
+ int16 getVal() const { return _val; }
+
+private:
+ uint16 _num;
+ SceneCondition _flags; /* eg, see usage in FUN_1f1a_2106 */
+ int16 _val;
+};
+
+} // end namespace Dgds
+
+#endif // DGDS_SCENE_CONDITION_H
diff --git a/engines/dgds/scene_op.cpp b/engines/dgds/scene_op.cpp
new file mode 100644
index 00000000000..b8878bc1471
--- /dev/null
+++ b/engines/dgds/scene_op.cpp
@@ -0,0 +1,399 @@
+/* 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 "graphics/cursorman.h"
+
+#include "dgds/scene_op.h"
+#include "dgds/dgds.h"
+#include "dgds/debug_util.h"
+#include "dgds/globals.h"
+#include "dgds/inventory.h"
+#include "dgds/scene.h"
+#include "dgds/dragon_native.h"
+#include "dgds/hoc_intro.h"
+#include "dgds/minigames/dragon_arcade.h"
+#include "dgds/minigames/china_train.h"
+#include "dgds/minigames/china_tank.h"
+
+namespace Dgds {
+
+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 += DebugUtil::dumpStructList(indent, "conditionList", _conditionList);
+ if (!_conditionList.empty()) {
+ str += "\n";
+ str += indent;
+ }
+ str += ">";
+ return str;
+}
+
+bool SceneOp::runOp() const {
+ bool sceneChanged;
+
+ if (_opCode < 100) {
+ sceneChanged = runCommonOp();
+ } else {
+ // Game-specific opcode
+ switch (DgdsEngine::getInstance()->getGameId()) {
+ case GID_DRAGON:
+ sceneChanged = runDragonOp();
+ break;
+ case GID_HOC:
+ sceneChanged = runChinaOp();
+ break;
+ case GID_WILLY:
+ sceneChanged = runBeamishOp();
+ break;
+ default:
+ error("TODO: Implement game-specific scene op for this game");
+ }
+ }
+ return sceneChanged;
+}
+
+bool SceneOp::runCommonOp() const {
+ DgdsEngine *engine = DgdsEngine::getInstance();
+ switch (_opCode) {
+ case kSceneOpChangeScene:
+ if (engine->changeScene(_args[0]))
+ return true;
+ break;
+ case kSceneOpNoop:
+ break;
+ case kSceneOpGlobal:
+ // The globals are held by the GDS scene
+ engine->getGDSScene()->globalOps(_args);
+ break;
+ case kSceneOpSegmentStateOps:
+ SDSScene::segmentStateOps(_args);
+ break;
+ case kSceneOpSetItemAttr:
+ SDSScene::setItemAttrOp(_args);
+ break;
+ case kSceneOpSetDragItem:
+ SDSScene::setDragItemOp(_args);
+ break;
+ case kSceneOpOpenInventory:
+ engine->getInventory()->open();
+ // This implicitly changes scene num
+ break;
+ case kSceneOpShowDlg:
+ if (_args.size() == 1)
+ engine->getScene()->showDialog(0, _args[0]);
+ else if (_args.size() > 1)
+ engine->getScene()->showDialog(_args[0], _args[1]);
+ break;
+ case kSceneOpShowInvButton:
+ engine->getScene()->addInvButtonToHotAreaList();
+ break;
+ case kSceneOpHideInvButton:
+ engine->getScene()->removeInvButtonFromHotAreaList();
+ break;
+ case kSceneOpEnableTrigger:
+ engine->getScene()->enableTrigger(_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(_args[0], _args[1]);
+ break;
+ case kSceneOpDrawVisibleTalkHeads: // args: none
+ engine->getScene()->updateVisibleTalkers();
+ break;
+ case kSceneOpLoadTalkData: // args: tds num to load
+ engine->getScene()->loadTalkData(_args[0]);
+ break;
+ case kSceneOpLoadDDSData: // args: dds num to load
+ if (_args[0])
+ engine->getScene()->loadDialogData(_args[0]);
+ break;
+ case kSceneOpFreeDDSData: // args: dds num to free
+ engine->getScene()->freeDialogData(_args[0]);
+ break;
+ case kSceneOpFreeTalkData: // args: tds num to free
+ engine->getScene()->freeTalkData(_args[0]);
+ break;
+
+ default:
+ warning("TODO: Implement generic scene op %d", _opCode);
+ break;
+ }
+ return false;
+}
+
+bool SceneOp::runDragonOp() const {
+ DgdsEngine *engine = DgdsEngine::getInstance();
+ switch (_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", _opCode);
+ break;
+ }
+ return false;
+}
+
+bool SceneOp::runChinaOp() const {
+ DgdsEngine *engine = DgdsEngine::getInstance();
+ switch (_opCode) {
+ case kSceneOpChinaTankInit:
+ engine->getChinaTank()->init();
+ break;
+ case kSceneOpChinaTankEnd:
+ engine->getChinaTank()->end();
+ break;
+ case kSceneOpChinaTankTick:
+ engine->getChinaTank()->tick();
+ 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: (%s)", dump("").c_str());
+ break;
+ }
+ return false;
+}
+
+bool SceneOp::runBeamishOp() const {
+ DgdsEngine *engine = DgdsEngine::getInstance();
+
+ if (_opCode & kSceneOpHasConditionalOpsFlag) {
+ uint16 opcode = _opCode & ~kSceneOpHasConditionalOpsFlag;
+ for (const ConditionalSceneOp &cop : engine->getScene()->getConditionalOps()) {
+ if (cop._opCode == opcode && engine->getScene()->checkConditions(cop._conditionList)) {
+ if (!Scene::runOps(cop._opList))
+ return true;
+ }
+ }
+ return false;
+ }
+
+ switch (_opCode) {
+ case kSceneOpOpenBeamishGameOverMenu:
+ engine->setMenuToTrigger(kMenuGameOver);
+ break;
+ case kSceneOpOpenBeamishOpenSkipCreditsMenu:
+ engine->setMenuToTrigger(kMenuSkipPlayIntro);
+ break;
+ default:
+ warning("TODO: Implement beamish-specific scene opcode %d", _opCode);
+ break;
+ }
+ return false;
+}
+
+} // end namespace Dgds
diff --git a/engines/dgds/scene_op.h b/engines/dgds/scene_op.h
new file mode 100644
index 00000000000..d7e0a0f3b3f
--- /dev/null
+++ b/engines/dgds/scene_op.h
@@ -0,0 +1,130 @@
+/* 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/>.
+ *
+ */
+
+#ifndef DGDS_SCENE_OP_H
+#define DGDS_SCENE_OP_H
+
+#include "common/types.h"
+#include "common/array.h"
+
+#include "dgds/scene_condition.h"
+
+namespace Dgds {
+
+enum SceneOpCode {
+ kSceneOpNone = 0,
+ kSceneOpChangeScene = 1, // args: scene num
+ kSceneOpNoop = 2, // args: none. Maybe should close dialogue?
+ kSceneOpGlobal = 3, // args: array of uints
+ kSceneOpSegmentStateOps = 4, // args: array of uint pairs [op seg, op seg], term with 0,0 that modify segment states
+ kSceneOpSetItemAttr = 5, // args: [item num, item param 0x28, item param 0x2c]. set item attrs?
+ kSceneOpSetDragItem = 6, // args: item num. give item?
+ kSceneOpOpenInventory = 7, // args: none.
+ kSceneOpShowDlg = 8, // args: dialogue number.
+ kSceneOpShowInvButton = 9, // args: none.
+ kSceneOpHideInvButton = 10, // args: none.
+ kSceneOpEnableTrigger = 11, // args: trigger num
+ kSceneOpChangeSceneToStored = 12, // args: none. Change scene to stored number
+ kSceneOpAddFlagToDragItem = 13, // args: none.
+ kSceneOpOpenInventoryZoom = 14, // args: none.
+ kSceneOpMoveItemsBetweenScenes = 15, // args: none.
+ kSceneOpShowClock = 16, // args: none. set clock script-visible.
+ kSceneOpHideClock = 17, // args: none. set clock script-hidden.
+ kSceneOpShowMouse = 18, // args: none.
+ kSceneOpHideMouse = 19, // args: none.
+ // Op 20 onward are common, but not in dragon
+
+ kSceneOpLoadTalkDataAndSetFlags = 20, // args: tdsnum to load, headnum
+ kSceneOpDrawVisibleTalkHeads = 21, // args: none
+ kSceneOpLoadTalkData = 22, // args: tds num to load
+ kSceneOpLoadDDSData = 24, // args: dds num to load
+ kSceneOpFreeDDSData = 25, // args: dds num to free
+ kSceneOpFreeTalkData = 26, // args: tds num to free
+
+ // Dragon-specific opcodes
+ kSceneOpPasscode = 100, // args: none.
+ kSceneOpMeanwhile = 101, // args: none. Clears screen and displays "meanwhile".
+ kSceneOpOpenGameOverMenu = 102, // args: none.
+ kSceneOpTiredDialog = 103, // args: none. Something about "boy am I tired"?
+ kSceneOpArcadeTick = 104, // args: none. Called in arcade post-tick.
+ kSceneOpDrawDragonCountdown1 = 105, // args: none. Draw special countdown number at 141, 56
+ kSceneOpDrawDragonCountdown2 = 106, // args: none. Draw some number at 250, 42
+ kSceneOpOpenPlaySkipIntroMenu = 107, // args: none. DRAGON: Show menu 50, the "Play Introduction" / "Skip Introduction" menu.
+ kSceneOpOpenBetterSaveGameMenu = 108, // args: none. DRAGON: Show menu 46, the "Before arcade maybe you better save your game" menu.
+
+ // China-specific opcodes
+ kSceneOpChinaTankInit = 100,
+ kSceneOpChinaTankEnd = 101,
+ kSceneOpChinaTankTick = 102,
+ kSceneOpChinaSetLanding = 103,
+ kSceneOpChinaScrollIntro = 104,
+ kSceneOpChinaScrollLeft = 105,
+ kSceneOpChinaScrollRight = 107,
+ kSceneOpShellGameInit = 108,
+ kSceneOpShellGameEnd = 109,
+ kSceneOpShellGameTick = 110,
+ kSceneOpChinaTrainInit = 111,
+ kSceneOpChinaTrainEnd = 112,
+ kSceneOpChinaTrainTick = 113,
+ kSceneOpChinaOpenGameOverMenu = 114, // args: none.
+ kSceneOpChinaOpenSkipCreditsMenu = 115, // args: none.
+ kSceneOpChinaOnIntroTick = 116, // args: none.
+ kSceneOpChinaOnIntroInit = 117, // args: none.
+ kSceneOpChinaOnIntroEnd = 118, // args: none.
+
+ // Beamish-specific opcodes
+ kSceneOpOpenBeamishGameOverMenu = 100,
+ kSceneOpOpenBeamishOpenSkipCreditsMenu = 101,
+
+ kSceneOpMaxCode = 255, // for checking file load
+
+ kSceneOpHasConditionalOpsFlag = 0x8000,
+};
+
+class SceneOp {
+public:
+ Common::Array<SceneConditions> _conditionList;
+ Common::Array<uint16> _args;
+ SceneOpCode _opCode;
+
+ Common::String dump(const Common::String &indent) const;
+ bool runOp() const;
+
+private:
+ bool runCommonOp() const;
+ bool runDragonOp() const;
+ bool runChinaOp() const;
+ bool runBeamishOp() const;
+};
+
+class ConditionalSceneOp {
+public:
+ uint _opCode;
+ Common::Array<SceneConditions> _conditionList;
+ Common::Array<SceneOp> _opList;
+
+ Common::String dump(const Common::String &indent) const;
+};
+
+
+} // end namespace Dgds
+
+#endif // DGDS_SCENE_OP_H
diff --git a/engines/dgds/ttm.cpp b/engines/dgds/ttm.cpp
index f6c4d97fe28..00a620fcc1d 100644
--- a/engines/dgds/ttm.cpp
+++ b/engines/dgds/ttm.cpp
@@ -627,7 +627,11 @@ bool TTMInterpreter::handleOperation(TTMEnviro &env, TTMSeq &seq, uint16 op, byt
env._getPuts[seq._currentGetPutId].reset();
break;
case 0x0110: // PURGE void
- _vm->adsInterpreter()->setHitTTMOp0110();
+ // only set if not running from CDS script
+ if (env._cdsSeqNum < 0)
+ _vm->adsInterpreter()->setHitTTMOp0110();
+ else
+ env._cdsSeqNum++;
break;
case 0x0220: // STOP CURRENT MUSIC
if (seq._executed) // this is a one-shot op
@@ -651,7 +655,12 @@ bool TTMInterpreter::handleOperation(TTMEnviro &env, TTMSeq &seq, uint16 op, byt
// TODO: Probably should do this accounting (as well as timeCut and dialogs)
// in game frames, not millis.
int delayMillis = (int)round(ivals[0] * MS_PER_FRAME);
- _vm->adsInterpreter()->setScriptDelay(delayMillis);
+ // Slight HACK - if we are running from CDS (Willy Beamish conversation) script,
+ // set that delay, otherwise set ADS interpreter delay.
+ if (env._cdsSeqNum >= 0)
+ env._cdsDelay = delayMillis;
+ else
+ _vm->adsInterpreter()->setScriptDelay(delayMillis);
break;
}
case 0x1030: // SET BRUSH: id:int [-1:n]
@@ -778,13 +787,13 @@ bool TTMInterpreter::handleOperation(TTMEnviro &env, TTMSeq &seq, uint16 op, byt
break;
}
case 0x3200:
- env._cdsTarget = findGOTOTarget(env, seq, ivals[0]);
+ env._cdsSeqNum = findGOTOTarget(env, seq, ivals[0]);
break;
case 0x3300:
- if (!env._cdsJumped && env._frameOffsets[env._cdsTarget] != env.scr->pos()) {
+ if (!env._cdsJumped && env._frameOffsets[env._cdsSeqNum] != seq._currentFrame) {
env._cdsJumped = true;
int64 prevPos = env.scr->pos();
- env.scr->seek(env._frameOffsets[env._cdsTarget]);
+ env.scr->seek(env._frameOffsets[env._cdsSeqNum]);
run(env, seq);
env.scr->seek(prevPos);
env._cdsJumped = false;
@@ -842,7 +851,9 @@ bool TTMInterpreter::handleOperation(TTMEnviro &env, TTMSeq &seq, uint16 op, byt
case 0x4200: { // STORE AREA: x,y,w,h:int [0..n] ; makes this area of foreground persist in the next frames.
if (seq._executed) // this is a one-shot op
break;
- const Common::Rect rect(Common::Point(ivals[0], ivals[1]), ivals[2], ivals[3]);
+ Common::Rect rect(Common::Point(ivals[0], ivals[1]), ivals[2], ivals[3]);
+ if (env._cdsSeqNum >= 0)
+ rect.translate(env._xOff, env._yOff);
_vm->getStoredAreaBuffer().blitFrom(_vm->_compositionBuffer, rect, rect);
break;
}
@@ -973,7 +984,8 @@ bool TTMInterpreter::handleOperation(TTMEnviro &env, TTMSeq &seq, uint16 op, byt
if (img) {
int x = ivals[0];
int y = ivals[1];
- if (_stackDepth > 0) {
+ // Use env offset if we are in gosub *or* running from CDS
+ if (_stackDepth > 0 || env._cdsSeqNum >= 0) {
x += env._xOff;
y += env._yOff;
}
@@ -1122,17 +1134,17 @@ bool TTMInterpreter::handleOperation(TTMEnviro &env, TTMSeq &seq, uint16 op, byt
uint16 lo = (uint16)ivals[0];
uint32 offset = ((uint32)hi << 16) + lo;
debug("TODO: 0xC250 Sync raw sfx?? offset %d", offset);
- /*
- if (env._soundRaw->playedOffset() < offset) {
+ /*if (env._soundRaw->playedOffset() < offset) {
// Not played to this point yet.
- env.scr->seek(-6);
+ env.scr->seek(-6, SEEK_CUR);
return false;
- }
- */
+ }*/
}
case 0xf010: { // LOAD SCR: filename:str
if (seq._executed) // this is a one-shot op
break;
+ if (env._cdsSeqNum >= 0) // don't run from CDS scripts?
+ break;
Image tmp(_vm->getResourceManager(), _vm->getDecompressor());
tmp.drawScreen(sval, _vm->getBackgroundBuffer());
_vm->_compositionBuffer.blitFrom(_vm->getBackgroundBuffer());
diff --git a/engines/dgds/ttm.h b/engines/dgds/ttm.h
index cf73d26495a..f4a1f321659 100644
--- a/engines/dgds/ttm.h
+++ b/engines/dgds/ttm.h
@@ -41,7 +41,7 @@ class TTMEnviro : public ScriptParserData {
public:
TTMEnviro() : _totalFrames(330), _enviro(0), _creditScrollMeasure(0),
_creditScrollYOffset(0), _xOff(0), _yOff(0), _xScroll(0), _yScroll(0),
- _cdsTarget(0), _cdsJumped(false), ScriptParserData() {
+ _cdsSeqNum(-1), _cdsJumped(false), _cdsDelay(0), ScriptParserData() {
ARRAYCLEAR(_scriptPals);
}
@@ -65,7 +65,8 @@ public:
int16 _xScroll;
int16 _yScroll;
Common::SharedPtr<SoundRaw> _soundRaw;
- int16 _cdsTarget; // The GOTO target to use in the CDS script (Willy Beamish talkie)
+ int16 _cdsSeqNum; // The GOTO target to use in the CDS script (Willy Beamish talkie)
+ int16 _cdsDelay;
bool _cdsJumped;
};
Commit: 217f733e8a90dc44e1e2bd8cf484680c52989211
https://github.com/scummvm/scummvm/commit/217f733e8a90dc44e1e2bd8cf484680c52989211
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2024-12-16T18:27:39+11:00
Commit Message:
DGDS: Fix some issues in train game from Coverity
Changed paths:
engines/dgds/minigames/china_train.cpp
engines/dgds/minigames/china_train.h
diff --git a/engines/dgds/minigames/china_train.cpp b/engines/dgds/minigames/china_train.cpp
index 7489c2f90a6..49425148c5d 100644
--- a/engines/dgds/minigames/china_train.cpp
+++ b/engines/dgds/minigames/china_train.cpp
@@ -35,7 +35,10 @@
namespace Dgds {
-/* Not used anywhere.
+/*
+ * Not used anywhere, but these strings are in the EXE and conveniently
+ * tell us what the PlayerAction enum values are.
+ *
static const char *ACTIONS[] = {
"Stand Right",
"Walk Right",
@@ -1416,10 +1419,17 @@ void ChinaTrain::checkRegions(TrainPlayer &player) {
if (mode == 0 || (mode == 2 && _cabooseTrail == 0)) {
player.setAction(kActionJumpRight, true);
engine->_soundPlayer->playSFX(134);
- } else if (mode == 0 && _cabooseTrail != 0) {
- player.setAction(kActionHeroicJump, true);
- engine->_soundPlayer->playSFX(134);
}
+ //
+ // The original also has this code, but it can never execute because
+ // mode == 0 would have taken the above branch.
+ // There's also no code to handle kActionHeroicJump in doProcess,
+ // so it was probably vestigal.
+ //
+ // } else if (mode == 0 && _cabooseTrail != 0) {
+ // player.setAction(kActionHeroicJump, true);
+ // engine->_soundPlayer->playSFX(134);
+ // }
} else if (player._action == kActionWalkLeft) {
if ((mode == 1 || mode == 3) && (_players._tong._intent != 5 || _currentCar != 3 || &player == &_players._tong)) {
player.setAction(kActionJumpLeft, true);
diff --git a/engines/dgds/minigames/china_train.h b/engines/dgds/minigames/china_train.h
index d9505c150fd..23ac5c8ea8c 100644
--- a/engines/dgds/minigames/china_train.h
+++ b/engines/dgds/minigames/china_train.h
@@ -82,6 +82,7 @@ struct PlayerData {
};
struct TunnelData {
+ TunnelData() : _start(0), _end(0) {}
int32 _start;
int32 _end;
};
@@ -94,7 +95,7 @@ public:
bool isBlocking() const { return _action == kActionBlock || _action == kActionBlockUp; }
bool isDucking() const { return _action == kActionDuckRight || _action == kActionDuckLeft; }
bool isFalling() const { return _action == kActionFallRight || _action == kActionFallLeft; }
- bool isJumping() const { return _action == kActionJumpRight || _action == kActionJumpRight; }
+ bool isJumping() const { return _action == kActionJumpRight || _action == kActionJumpLeft; }
bool isStaggering() const { return _action == kActionStagger; }
bool isStanding() const { return _action == kActionStandRight || _action == kActionStandLeft; }
bool isWalking() const { return _action == kActionWalkLeft || _action == kActionWalkRight; }
More information about the Scummvm-git-logs
mailing list