[Scummvm-git-logs] scummvm master -> 29900c627a167aeda632beaaab036da48650350f

mduggan noreply at scummvm.org
Sun Jan 12 06:02:15 UTC 2025


This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
29900c627a DGDS: Refactor and fix conversation TTM script execution


Commit: 29900c627a167aeda632beaaab036da48650350f
    https://github.com/scummvm/scummvm/commit/29900c627a167aeda632beaaab036da48650350f
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2025-01-12T16:56:07+11:00

Commit Message:
DGDS: Refactor and fix conversation TTM script execution

Willy Beamish CD voice acting is now *mostly* working.

Changed paths:
    engines/dgds/dgds.cpp
    engines/dgds/dgds.h
    engines/dgds/head.cpp
    engines/dgds/head.h
    engines/dgds/minigames/dragon_arcade_ttm.cpp
    engines/dgds/scene.cpp
    engines/dgds/scene.h
    engines/dgds/ttm.cpp
    engines/dgds/ttm.h


diff --git a/engines/dgds/dgds.cpp b/engines/dgds/dgds.cpp
index c15fb2c2210..bd6bb9ee383 100644
--- a/engines/dgds/dgds.cpp
+++ b/engines/dgds/dgds.cpp
@@ -499,7 +499,8 @@ void DgdsEngine::loadRestartFile() {
 	_gdsScene->loadRestart(_rstFileName, _resource, _decompressor);
 }
 
-static void _dumpFrame(const Graphics::ManagedSurface &surf, const char *name) {
+/*static*/ void
+DgdsEngine::dumpFrame(const Graphics::ManagedSurface &surf, const char *name) {
 #ifdef DUMP_FRAME_DATA
 	/* For debugging, dump the frame contents.. */
 	Common::DumpFile outf;
@@ -596,6 +597,28 @@ void DgdsEngine::pumpMessages() {
 	}
 }
 
+void DgdsEngine::dimPalForWillyDialog(bool force) {
+	WillyGlobals *globals = static_cast<WillyGlobals *>(_gameGlobals);
+	int16 fade = globals->getPalFade();
+	fade = CLIP(fade, (int16)0, (int16)255);
+
+	// TODO: Same constants are in globals.cpp
+	static const int FADE_STARTCOL = 0x40;
+	static const int FADE_NUMCOLS = 0xC0;
+
+	if (force || _scene->hasVisibleHead() ) {
+		fade = 0x80;
+	} else {
+		fade = 0;
+	}
+
+	if (_lastGlobalFade != fade || _lastGlobalFadedPal != _gamePals->getCurPalNum()) {
+		_gamePals->setFade(FADE_STARTCOL, FADE_NUMCOLS, 0, fade);
+		_lastGlobalFade = fade;
+		_lastGlobalFadedPal = _gamePals->getCurPalNum();
+	}
+}
+
 Common::Error DgdsEngine::run() {
 	syncSoundSettings();
 	_isLoading = true;
@@ -678,7 +701,7 @@ Common::Error DgdsEngine::run() {
 			//
 			//_scene->drawActiveDialogBgs(&_compositionBuffer);
 
-			_dumpFrame(_compositionBuffer, "comp-before-ads");
+			dumpFrame(_compositionBuffer, "comp-before-ads");
 
 			if (!_inventory->isOpen() || (_inventory->isZoomVisible() && getGameId() != GID_WILLY))
 				_adsInterp->run();
@@ -729,9 +752,9 @@ Common::Error DgdsEngine::run() {
 			_scene->runPostTickOps();
 			_scene->checkTriggers();
 
-			_dumpFrame(_backgroundBuffer, "back");
-			_dumpFrame(_storedAreaBuffer, "stor");
-			_dumpFrame(_compositionBuffer, "comp");
+			dumpFrame(_backgroundBuffer, "back");
+			dumpFrame(_storedAreaBuffer, "stor");
+			dumpFrame(_compositionBuffer, "comp");
 
 			if (!_inventory->isOpen()) {
 				_gdsScene->drawItems(_compositionBuffer);
@@ -747,15 +770,17 @@ Common::Error DgdsEngine::run() {
 				_scene->drawDebugHotAreas(&_compositionBuffer);
 
 			if (getGameId() == GID_WILLY) {
-				_scene->drawVisibleHeads(&_compositionBuffer);
+				if (!justChangedScene1())
+					_scene->drawVisibleHeads(&_compositionBuffer);
 				_scene->drawAndUpdateDialogs(&_compositionBuffer);
 				_scene->updateHotAreasFromDynamicRects();
 			} else {
 				_scene->drawAndUpdateDialogs(&_compositionBuffer);
-				_scene->drawVisibleHeads(&_compositionBuffer);
+				if (!justChangedScene1())
+					_scene->drawVisibleHeads(&_compositionBuffer);
 			}
 
-			_dumpFrame(_compositionBuffer, "comp-with-dlg");
+			dumpFrame(_compositionBuffer, "comp-with-dlg");
 
 			bool gameRunning = (!haveActiveDialog && _gameGlobals->getGlobal(0x57) /* TODO: && _dragItem == nullptr*/);
 			_clock.update(gameRunning);
@@ -769,29 +794,12 @@ Common::Error DgdsEngine::run() {
 		// Mouse event is now handled.
 		_lastMouseEvent = Common::EVENT_INVALID;
 
-		// Willy Beamish dims the palette of the screen while dialogs are active
 		if (getGameId() == GID_WILLY) {
-			WillyGlobals *globals = static_cast<WillyGlobals *>(_gameGlobals);
-			int16 fade = globals->getPalFade();
-			fade = CLIP(fade, (int16)0, (int16)255);
-
-			// TODO: Same constants are in globals.cpp
-			static const int FADE_STARTCOL = 0x40;
-			static const int FADE_NUMCOLS = 0xC0;
-
-			if (_scene->hasVisibleHead()) {
-				fade = 0x80;
-			} else {
-				fade = 0;
-			}
-
-			if (_lastGlobalFade != fade || _lastGlobalFadedPal != _gamePals->getCurPalNum()) {
-				_gamePals->setFade(FADE_STARTCOL, FADE_NUMCOLS, 0, fade);
-				_lastGlobalFade = fade;
-				_lastGlobalFadedPal = _gamePals->getCurPalNum();
-			}
+			// Willy Beamish dims the palette of the screen while dialogs are active
+			dimPalForWillyDialog(false);
 
 			// TODO: When should we show the cursor again?
+			WillyGlobals *globals = static_cast<WillyGlobals *>(_gameGlobals);
 			if (globals->isHideMouseCursor() && !_menu->menuShown())
 				CursorMan.showMouse(false);
 		}
diff --git a/engines/dgds/dgds.h b/engines/dgds/dgds.h
index 75031f9ce04..a715611e11a 100644
--- a/engines/dgds/dgds.h
+++ b/engines/dgds/dgds.h
@@ -283,6 +283,10 @@ public:
 	void setDebugShowHotAreas(bool enable) { _debugShowHotAreas = enable; }
 	bool getDebugShowHotAreas() const { return _debugShowHotAreas; }
 
+	static void dumpFrame(const Graphics::ManagedSurface &surf, const char *name);
+
+	void dimPalForWillyDialog(bool force);
+
 private:
 	Common::Error syncGame(Common::Serializer &s);
 
diff --git a/engines/dgds/head.cpp b/engines/dgds/head.cpp
index fd09f809047..81c39f39f23 100644
--- a/engines/dgds/head.cpp
+++ b/engines/dgds/head.cpp
@@ -22,7 +22,13 @@
 #include "dgds/head.h"
 #include "dgds/dgds.h"
 #include "dgds/image.h"
+#include "dgds/includes.h"
 #include "dgds/sound_raw.h"
+#include "dgds/drawing.h"
+#include "dgds/scene.h"
+#include "dgds/dialog.h"
+
+#include "graphics/cursorman.h"
 
 namespace Dgds {
 
@@ -162,26 +168,154 @@ bool TalkData::hasVisibleHead() const {
 	return false;
 }
 
-//////
+CDSTTMInterpreter::CDSTTMInterpreter(DgdsEngine *vm) : TTMInterpreter(vm) {
+	_storedAreaBuffer.create(SCREEN_WIDTH, SCREEN_HEIGHT, Graphics::PixelFormat::createFormatCLUT8());
+}
+
+
+void CDSTTMInterpreter::handleOperation(TTMEnviro &env_, TTMSeq &seq, uint16 op, byte count, const int16 *ivals, const Common::String &sval, const Common::Array<Common::Point> &pts) {
+	CDSTTMEnviro &env = static_cast<CDSTTMEnviro &>(env_);
+
+	switch (op) {
+	case 0x0080: // FREE SHAPE
+		env._scriptShapes[0].reset();
+		break;
+	case 0x0110: // PURGE void
+		break;
+	case 0x0FF0: // REFRESH
+		break;
+	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.
+		int delayMillis = (int)round(ivals[0] * MS_PER_FRAME);
+		env._cdsDelay = delayMillis;
+		break;
+	}
+	case 0x1050: // SELECT BMP:  id:int [0]
+		seq._currentBmpId = ivals[0];
+		break;
+	case 0x1060: // SELECT PAL:  id:int [0]
+		seq._currentPalId = ivals[0];
+		break;
+	case 0x1100: // SET_SCENE:  i:int   [1..n]
+	case 0x1110: // SET_SCENE:  i:int   [1..n]
+		break;
+	case 0x2000: // SET (DRAW) COLORS: fgcol,bgcol:int [0..255]
+		seq._drawColFG = static_cast<byte>(ivals[0]); // aka Line Color
+		seq._drawColBG = static_cast<byte>(ivals[1]); // aka Fill Color
+		break;
+	case 0x3200: // CDS FIND GOTO TARGET frameno
+		seq._gotoFrame = findGOTOTarget(env, seq, ivals[0]);
+		break;
+	case 0x3300: // CDS GOSUB - first 2 args are ignored by original
+			if (!env._cdsJumped && seq._gotoFrame + ivals[2] != seq._currentFrame && seq._gotoFrame >= 0) {
+			env._cdsJumped = true;
+			int64 prevPos = env.scr->pos();
+			int16 currentFrame = seq._currentFrame;
+			env.scr->seek(env._frameOffsets[seq._gotoFrame + ivals[2]]);
+			seq._currentFrame = seq._gotoFrame;
+			run(env, seq);
+			seq._currentFrame = currentFrame;
+			env.scr->seek(prevPos);
+			env._cdsJumped = false;
+		}
+		break;
+	case 0x4200: { // STORE AREA: x,y,w,h:int [0..n]  ; makes this area of foreground persist in the next frames.
+		if (env._cdsDidStoreArea) // this is a one-shot op
+			break;
+
+		if (env._storedAreaRect.width * 2 < (int)ivals[2]) {
+			env._storedAreaRect = DgdsRect(ivals[0] + env._xOff, ivals[1] + env._yOff, ivals[2], ivals[3]);
+		}
+		const Common::Rect rect = env._storedAreaRect.toCommonRect();
+		_storedAreaBuffer.blitFrom(_vm->_compositionBuffer, rect, rect);
+		env._cdsDidStoreArea = true;
+		break;
+	}
+	case 0xa500: // DRAW SPRITE: x,y,frameno,bmpno:int [-n,+n]
+	case 0xa510: // DRAW SPRITE FLIP V x,y:int
+	case 0xa520: // DRAW SPRITE FLIP H: x,y:int
+	case 0xa530: // DRAW SPRITE FLIP HV: x,y,frameno,bmpno:int	[-n,+n]
+		doDrawSpriteOp(env, seq, op, count, ivals, env._xOff, env._yOff);
+		break;
+	case 0xc220: // PLAY RAW SFX
+		if (env._cdsPlayedSound) // this is a one-shot op
+			break;
+		if (!env._soundRaw) {
+			warning("TODO: Trying to play raw SFX but nothing loaded");
+		} else {
+			env._soundRaw->play();
+			env._cdsPlayedSound = true;
+		}
+		break;
+	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, SEEK_CUR);
+			return false;
+		}*/
+		break;
+	}
+	case 0xa100: // DRAW FILLED RECT
+	case 0xa110: // DRAW EMPTY RECT  x1,y1,x2,y2:int
+		// Appear in the scripts but not implemented in the original.
+		break;
+	case 0xc200: // ??? SFX: ??,?? - not implemented in willy, ignore?
+	case 0xc210: // LOAD RAW SFX: filename:str
+	case 0xf010: // LOAD SCR:	filename:str
+	case 0xf020: // LOAD BMP:	filename:str
+	case 0xf050: // LOAD PAL:	filename:str
+		// Ignore all these in CDS scripts.  The original implements them but only to load
+		// patch data outside of the CDS files.  That data isn't present in the final
+		// games so we can just ignore the opcode.
+		break;
+	default:
+		if (count < 15)
+			warning("Unimplemented CDS TTM opcode: 0x%04X (%s, %d args) (ivals: %d %d %d %d)",
+					op, ttmOpName(op), count, ivals[0], ivals[1], ivals[2], ivals[3]);
+		else
+			warning("Unimplemented CDS TTM opcode: 0x%04X (%s, sval: %s)", op,
+					ttmOpName(op), sval.c_str());
+		break;
+	}
+}
+
 
 Conversation::~Conversation() {
-	unload();
+	unloadData();
 }
 
-void Conversation::unload() {
-	if (_sound) {
-		_sound->stop();
-		_sound.reset();
-	}
-	_img.reset();
+void Conversation::unloadData() {
+	debug(10, "CDS: Unloading data (%d,%d)", _dlgNum, _dlgFileNum);
 	_ttmScript.reset();
+
+	_ttmEnv._scriptShapes[0].reset();
 	if (_ttmEnv._soundRaw)
 		_ttmEnv._soundRaw->stop();
-	_ttmEnv = TTMEnviro();
+	_ttmEnv = CDSTTMEnviro();
+	_loadState = 0;
+}
+
+void Conversation::clear() {
+	_dlgNum = -1;
+	_dlgFileNum = -1;
+	_subNum = -1;
+	_finished = false;
+	_haveHeadData = false;
+	_stopScript = false;
+}
+
+bool Conversation::isForDlg(const Dialog *dlg) const {
+	return dlg && dlg->_num == _dlgNum && dlg->_fileNum == _dlgFileNum;
 }
 
-void Conversation::loadData(uint16 dlgFileNum, uint16 dlgNum, int16 sub) {
-	unload();
+void Conversation::loadData(uint16 dlgFileNum, uint16 dlgNum, int16 sub, bool haveHeadData) {
+	unloadData();
+	clear();
 
 	DgdsEngine *engine = DgdsEngine::getInstance();
 
@@ -189,6 +323,19 @@ void Conversation::loadData(uint16 dlgFileNum, uint16 dlgNum, int16 sub) {
 	if (engine->getGameId() != GID_WILLY)
 		return;
 
+	_dlgNum = dlgNum;
+	_dlgFileNum = dlgFileNum;
+	_subNum = sub;
+	_nextExecMs = 0;
+	_runTempFrame = 0;
+	_tempFrameNum = 0;
+	_thisFrameMs = 0;
+	_stopScript = false;
+	_haveHeadData = haveHeadData;
+
+	if (!haveHeadData)
+		_drawRect = DgdsRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
+
 	ResourceManager *resourceManager = engine->getResourceManager();
 	Decompressor *decompressor = engine->getDecompressor();
 
@@ -205,51 +352,205 @@ void Conversation::loadData(uint16 dlgFileNum, uint16 dlgNum, int16 sub) {
 
 	debug(10, "CDS: Load CDS resource %s", fname.c_str());
 
-	_sound.reset(new SoundRaw(resourceManager, decompressor));
-	_sound->load(fname);
-	_img.reset(new Image(resourceManager, decompressor));
-	_img->loadBitmap(fname);
-	_ttmScript.reset(new TTMInterpreter(engine));
+	// The scripts are designed so the resources are patchable, but by default
+	// they use the sound and image data from the CDS file.
+	_ttmEnv._soundRaw.reset(new SoundRaw(resourceManager, decompressor));
+	_ttmEnv._soundRaw->load(fname);
+	_ttmEnv._scriptShapes[0].reset(new Image(resourceManager, decompressor));
+	_ttmEnv._scriptShapes[0]->loadBitmap(fname);
+	_ttmScript.reset(new CDSTTMInterpreter(engine));
 	_ttmScript->load(fname, _ttmEnv);
 	_ttmScript->findAndAddSequences(_ttmEnv, _ttmSeqs);
 
-	// The scripts are designed 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;
+	_loadState = 1;
 
-	// Always run seq 1 on init.
-	_ttmEnv._cdsSeqNum = 1;
-	runScript();
+	// Then run START
+	for (const auto &tag : _ttmEnv._tags) {
+		if (tag._value.equalsIgnoreCase("START"))
+			_ttmEnv._cdsFrame = tag._key;
+	}
+	_tempFrameNum = _ttmScript->findGOTOTarget(_ttmEnv, *_ttmSeqs[0], _ttmEnv._cdsFrame);
+	_ttmEnv._cdsFrame = _tempFrameNum;
+
+	// Always run frame 1 on init.
+	runScriptFrame(1);
 }
 
-void Conversation::runScript() {
+bool Conversation::runScriptFrame(int16 frameNum) {
+	if (!_ttmScript || frameNum >= _ttmEnv._totalFrames)
+		return false;
+
+	_ttmEnv.scr->seek(_ttmEnv._frameOffsets[frameNum]);
+	TTMSeq *seq = _ttmSeqs[0].get();
+	seq->_currentFrame = frameNum;
+
+	debug(10, "CDS: Running TTM sequence %d frame %d", seq->_seqNum, seq->_currentFrame);
+
+	seq->_drawWin = _drawRect.toCommonRect();
+	return _ttmScript->run(_ttmEnv, *seq);
+}
+
+void Conversation::checkAndRunScript() {
+	if (!_ttmScript || _finished)
+		return;
+
+	DgdsEngine *engine = DgdsEngine::getInstance();
+
+	// Always add the stored buffer first.  Copy the whole thing to overwrite other state.
+	engine->_compositionBuffer.transBlitFrom(_ttmScript->getStoredAreaBuffer());
+
+	if (_runTempFrame) {
+		runScriptFrame(_tempFrameNum);
+	}
+	runScriptFrame(_ttmEnv._cdsFrame);
+	if (_ttmEnv._cdsDelay > 0) {
+		_nextExecMs = _thisFrameMs + _ttmEnv._cdsDelay;
+		_ttmEnv._cdsDelay = -1;
+	} else {
+		_nextExecMs = 0;
+	}
+}
+
+void Conversation::incrementFrame() {
 	if (!_ttmScript)
 		return;
 
+	// TODO: check load type 2 here?
+
+	if (_loadState == 1 && _ttmEnv._scriptShapes[0] && _ttmEnv._scriptShapes[0]->loadedFrameCount() > 0) {
+		_ttmEnv._storedAreaRect.x = _drawRect.x;
+		_ttmEnv._storedAreaRect.y = _drawRect.y;
+		_ttmEnv._storedAreaRect.width = _ttmEnv._scriptShapes[0]->getFrames()[0]->w + 8;
+		_ttmEnv._storedAreaRect.height = _ttmEnv._scriptShapes[0]->getFrames()[0]->h;
+		_loadState = 2;
+	}
+
+	if (_runTempFrame)
+		_runTempFrame--;
+
+	if (!_nextExecMs || _thisFrameMs >= _nextExecMs) {
+		debug(10, "CDS: Increment frame %d -> %d", _ttmEnv._cdsFrame, _ttmEnv._cdsFrame + 1);
+		_ttmEnv._cdsFrame++;
+	}
+}
+
+bool Conversation::isScriptRunning() {
+	return (_ttmScript &&
+		((_ttmEnv._soundRaw && _ttmEnv._soundRaw->isPlaying())
+		||
+		(_ttmEnv._cdsFrame < _ttmEnv._totalFrames)
+		));
+}
+
+void Conversation::pumpMessages() {
+	Common::Event ev;
 	DgdsEngine *engine = DgdsEngine::getInstance();
-	if (_nextExec && engine->getThisFrameMs() < _nextExec)
+
+	while (engine->getEventManager()->pollEvent(ev)) {
+		if (ev.type == Common::EVENT_LBUTTONDOWN ||
+			ev.type == Common::EVENT_RBUTTONDOWN ||
+			ev.type == Common::EVENT_KEYDOWN) {
+			_stopScript = true;
+			engine->getScene()->setIgnoreMouseUp();
+		}
+	}
+}
+
+void Conversation::runScript() {
+	if (!_ttmScript)
 		return;
 
-	_nextExec = 0;
+	DgdsEngine *engine = DgdsEngine::getInstance();
+	engine->disableKeymapper();
+
+	_nextExecMs = 0;
 	_ttmEnv._xOff = _drawRect.x;
 	_ttmEnv._yOff = _drawRect.y;
+	_runTempFrame = 2;
+
+	if (_drawRect.width == 0) {
+		_drawRect.width = SCREEN_WIDTH;
+		_drawRect.height = SCREEN_HEIGHT;
+	}
+
+	int frameCount = 0;
+	uint32 startMillis = g_system->getMillis();
+
+	DgdsEngine::dumpFrame(engine->_compositionBuffer, "cds-comp-before-script");
+
+	engine->getScene()->checkDialogActive();
+	// update here to make sure the dialog gets drawn.
+	engine->getScene()->drawAndUpdateDialogs(&engine->_compositionBuffer);
+
+	DgdsEngine::dumpFrame(engine->_compositionBuffer, "cds-comp-before-script-with-dlg");
+
+	_ttmScript->getStoredAreaBuffer().blitFrom(engine->_compositionBuffer);
+
+	CursorMan.showMouse(false);
+
+	g_system->copyRectToScreen(engine->_compositionBuffer.getPixels(), SCREEN_WIDTH, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
+	g_system->updateScreen();
+
+	// If we have head data we should dim the palette
+	engine->dimPalForWillyDialog(_haveHeadData);
 
-	for (auto seq : _ttmSeqs) {
-		if (seq->_seqNum == _ttmEnv._cdsSeqNum && seq->_currentFrame < (int)_ttmEnv._frameOffsets.size()) {
-			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++;
+	debug(10, "CDS: Start execution of (%d,%d) - frame %d maxFrame %d", _dlgNum, _dlgFileNum,
+		_ttmEnv._cdsFrame, _ttmEnv._totalFrames);
+
+	while (isScriptRunning() && !engine->shouldQuit()) {
+		_thisFrameMs = g_system->getMillis();
+		pumpMessages();
+		if (_stopScript)
+			break;
+
+		if (!_nextExecMs || _nextExecMs <= _thisFrameMs) {
+			incrementFrame();
+
+			const Common::Rect r = _drawRect.toCommonRect();
+
+			checkAndRunScript();
+
+			const byte *srcPtr = (const byte *)engine->_compositionBuffer.getPixels() + r.top * SCREEN_WIDTH + r.left;
+			g_system->copyRectToScreen(srcPtr, SCREEN_WIDTH, r.left, r.top, r.width(), r.height());
+		}
+
+		g_system->updateScreen();
+
+		frameCount++;
+		// Limit to 15 FPS
+		static const int framesPerSecond = 15;
+		uint32 thisFrameEndMillis = g_system->getMillis();
+		uint32 elapsedMillis = thisFrameEndMillis - startMillis;
+		const uint32 targetMillis = (frameCount * 1000 / framesPerSecond);
+		if (targetMillis > elapsedMillis) {
+			while (targetMillis > elapsedMillis) {
+				pumpMessages();
+				g_system->updateScreen();
+				g_system->delayMillis(5);
+				elapsedMillis = g_system->getMillis() - startMillis;
 			}
+		} else if (targetMillis < elapsedMillis) {
+			startMillis = thisFrameEndMillis;
+			frameCount = 0;
 		}
 	}
+
+	debug(10, "CDS: Finished execution of (%d,%d) - stop %s frame %d maxFrame %d", _dlgNum, _dlgFileNum,
+		_stopScript ? "true" : "false", _ttmEnv._cdsFrame, _ttmEnv._totalFrames);
+
+	CursorMan.showMouse(true);
+	engine->enableKeymapper();
+	if (_ttmEnv._soundRaw)
+		_ttmEnv._soundRaw->stop();
+
+	// If the dialog was cleared, set the force-clear flag in the scene.  Otherwise, just set
+	// a flag the scene can check when updating dialogs.
+	if (_stopScript)
+		engine->getScene()->setShouldClearDlg();
+	else
+		_finished = true;
+
+	unloadData();
 }
 
 } // end namespace Dgds
diff --git a/engines/dgds/head.h b/engines/dgds/head.h
index 916db07923e..25476a473b4 100644
--- a/engines/dgds/head.h
+++ b/engines/dgds/head.h
@@ -38,7 +38,7 @@ namespace Dgds {
 
 class Image;
 class SoundRaw;
-
+class Dialog;
 
 class TalkDataHeadFrame {
 public:
@@ -105,23 +105,75 @@ public:
 	bool hasVisibleHead() const;
 };
 
-/** CDS data from Willy Beamish talkie */
+
+/**
+ * A TTM interpreter with customized opcode handling for the TTM bit of
+ * CDS files (Willy Beamish CD version conversation data)
+ */
+class CDSTTMInterpreter : public TTMInterpreter {
+public:
+	CDSTTMInterpreter(DgdsEngine *vm);
+
+	Graphics::ManagedSurface &getStoredAreaBuffer() { return _storedAreaBuffer; }
+
+protected:
+	virtual void handleOperation(TTMEnviro &env, TTMSeq &seq, uint16 op, byte count, const int16 *ivals, const Common::String &sval, const Common::Array<Common::Point> &pts) override;
+
+	Graphics::ManagedSurface _storedAreaBuffer;
+};
+
+class CDSTTMEnviro : public TTMEnviro {
+public:
+	CDSTTMEnviro() : _cdsPlayedSound(false), _cdsFrame(-1), _cdsJumped(false), _cdsDelay(0),
+	_cdsDidStoreArea(false), TTMEnviro()
+	{}
+
+	bool _cdsPlayedSound;
+	int16 _cdsFrame; // The GOTO target to use in the CDS script (Willy Beamish talkie)
+	int16 _cdsDelay;
+	bool _cdsJumped;
+	bool _cdsDidStoreArea;
+	DgdsRect _storedAreaRect;
+};
+
+/** CDS data from Willy Beamish CD version talkie */
 class Conversation {
 public:
-	Conversation() : _nextExec(0) {}
+	Conversation() : _nextExecMs(0), _runTempFrame(0), _tempFrameNum(0), _stopScript(false), _loadState(0), _dlgNum(-1), _dlgFileNum(-1), _subNum(-1), _finished(false), _haveHeadData(false) {}
 	~Conversation();
 
-	void unload();
+	void unloadData();
 	void runScript();
-	void loadData(uint16 num, uint16 num2, int16 sub);
+	void loadData(uint16 num, uint16 num2, int16 sub, bool haveHeadData);
+	bool isForDlg(const Dialog *dlg) const;
+	bool isFinished() const { return _finished; }
+	void clear();
 
-	Common::SharedPtr<SoundRaw> _sound;
-	Common::SharedPtr<Image> _img;
-	Common::SharedPtr<TTMInterpreter> _ttmScript;
-	Common::Array<Common::SharedPtr<TTMSeq>> _ttmSeqs;
-	TTMEnviro _ttmEnv;
-	uint32 _nextExec;
 	DgdsRect _drawRect;
+
+private:
+	Common::SharedPtr<CDSTTMInterpreter> _ttmScript;
+	Common::Array<Common::SharedPtr<TTMSeq>> _ttmSeqs;
+	CDSTTMEnviro _ttmEnv;
+	uint32 _nextExecMs;
+
+	bool runScriptFrame(int16 frameNum);
+	void checkAndRunScript();
+	void incrementFrame();
+	bool isScriptRunning();
+	void pumpMessages();
+
+	int16 _runTempFrame;
+	int16 _tempFrameNum;
+
+	uint32 _thisFrameMs;
+	bool _stopScript;
+	int16 _loadState;
+	int16 _dlgNum;
+	int16 _dlgFileNum;
+	int16 _subNum;
+	bool _finished;
+	bool _haveHeadData;
 };
 
 
diff --git a/engines/dgds/minigames/dragon_arcade_ttm.cpp b/engines/dgds/minigames/dragon_arcade_ttm.cpp
index f2a2e640dab..ab8859fed9d 100644
--- a/engines/dgds/minigames/dragon_arcade_ttm.cpp
+++ b/engines/dgds/minigames/dragon_arcade_ttm.cpp
@@ -220,11 +220,9 @@ int16 DragonArcadeTTM::handleOperation(TTMEnviro &env, int16 page, uint16 op, by
 			debug(1, "Floor: %s", data.dump().c_str());
 			_floorData.push_back(data);
 		} else {
-			const Common::Rect r(Common::Point(ivals[0], ivals[1]), ivals[2] - 1, ivals[3] - 1);
-			compBuffer.drawLine(r.left, r.top, r.right, r.top, _drawColFG);
-			compBuffer.drawLine(r.left, r.bottom, r.right, r.bottom, _drawColFG);
-			compBuffer.drawLine(r.left, r.top, r.left, r.bottom, _drawColFG);
-			compBuffer.drawLine(r.right, r.top, r.right, r.bottom, _drawColFG);
+			const Common::Rect r(Common::Point(ivals[0], ivals[1]), ivals[2], ivals[3]);
+			const Common::Rect drawWin(SCREEN_WIDTH, SCREEN_HEIGHT);
+			Drawing::rectClipped(r, drawWin, &compBuffer, _drawColFG);
 		}
 		break;
 	case 0xA404: { // DRAW FILLED CIRCLE
diff --git a/engines/dgds/scene.cpp b/engines/dgds/scene.cpp
index 548fe3b3c0c..d828fd9f41d 100644
--- a/engines/dgds/scene.cpp
+++ b/engines/dgds/scene.cpp
@@ -592,7 +592,7 @@ void SDSScene::unload() {
 	_triggers.clear();
 	_talkData.clear();
 	_dynamicRects.clear();
-	_conversation.unload();
+	_conversation.unloadData();
 	_conditionalOps.clear();
 	_sceneDialogFlags = kDlgFlagNone;
 }
@@ -856,13 +856,16 @@ bool SDSScene::loadTalkData(uint16 num) {
 }
 
 
-void SDSScene::freeTalkData(uint16 num) {
+bool SDSScene::freeTalkData(uint16 num) {
+	bool result = false;
 	for (int i = 0; i < (int)_talkData.size(); i++) {
 		if (_talkData[i]._num == num) {
 			_talkData.remove_at(i);
 			i--;
+			result = true;
 		}
 	}
+	return result;
 }
 
 void SDSScene::updateVisibleTalkers() {
@@ -875,7 +878,10 @@ void SDSScene::drawVisibleHeads(Graphics::ManagedSurface *dst) {
 	for (const auto &tds : _talkData) {
 		tds.drawVisibleHeads(dst);
 	}
-	_conversation.runScript();
+
+	if (_conversation.isForDlg(getVisibleDialog())) {
+		_conversation.runScript();
+	}
 }
 
 bool SDSScene::hasVisibleHead() const {
@@ -887,8 +893,10 @@ bool SDSScene::hasVisibleHead() const {
 }
 
 
-void SDSScene::loadTalkDataAndSetFlags(uint16 talknum, uint16 headnum) {
+bool SDSScene::loadTalkDataAndSetFlags(uint16 talknum, uint16 headnum) {
 	updateVisibleTalkers();
+
+	_conversation._drawRect = DgdsRect();
 	if (loadTalkData(talknum)) {
 		for (auto &data : _talkData) {
 			if (data._num != talknum)
@@ -905,7 +913,9 @@ void SDSScene::loadTalkDataAndSetFlags(uint16 talknum, uint16 headnum) {
 			}
 			break;
 		}
+		return true;
 	}
+	return false;
 }
 
 
@@ -964,11 +974,12 @@ void SDSScene::showDialog(uint16 fileNum, uint16 dlgNum) {
 			dialog.setFlag(kDlgFlagOpening);
 
 			// For beamish
+			bool haveHeadData = false;
 			if (dialog._talkDataHeadNum) {
-				loadTalkDataAndSetFlags(dialog._talkDataNum, dialog._talkDataHeadNum);
+				haveHeadData = loadTalkDataAndSetFlags(dialog._talkDataNum, dialog._talkDataHeadNum);
 			}
 
-			_conversation.loadData(fileNum, dlgNum, -1);
+			_conversation.loadData(fileNum, dlgNum, -1, haveHeadData);
 
 			// hide time gets set the first time it's drawn.
 			if (_dlgWithFlagLo8IsClosing && dialog.hasFlag(kDlgFlagLo8)) {
@@ -1008,6 +1019,13 @@ bool SDSScene::checkDialogActive() {
 		if ((dlg._state->_hideTime == 0) && dlg._action.size() < 2)
 			no_options = true;
 
+		// If voice acting in Willy Beamish is finished, clear the dialog
+		// unless we are waiting for a choice.
+		if (dlg._action.size() < 2 && (_conversation.isForDlg(&dlg) && _conversation.isFinished())) {
+			finished = true;
+			_conversation.clear();
+		}
+
 		if ((!finished && !no_options) || dlg.hasFlag(kDlgFlagHi20) || dlg.hasFlag(kDlgFlagHi40)) {
 			if (!finished && dlg._action.size() > 1 && !dlg.hasFlag(kDlgFlagHiFinished)) {
 				DialogAction *action = dlg.pickAction(false, clearDlgFlag);
@@ -1022,19 +1040,18 @@ bool SDSScene::checkDialogActive() {
 			_dlgWithFlagLo8IsClosing = dlg.hasFlag(kDlgFlagLo8);
 
 			// For Willy Beamish
+			bool haveHeadData = false;
 			if (dlg._talkDataNum) {
-				freeTalkData(dlg._talkDataNum);
+				haveHeadData = freeTalkData(dlg._talkDataNum);
 			}
 
 			DialogAction *action = dlg.pickAction(true, clearDlgFlag);
 			if (action || dlg._action.empty()) {
 				dlg.setFlag(kDlgFlagHiFinished);
 				if (action) {
-					// TODO: We can load selected item voice acting here, but it generally
-					// 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?
-					_conversation.loadData(dlg._fileNum, dlg._num, action->num);
+					// Play the response voice acting script.
+					_conversation.loadData(dlg._fileNum, dlg._num, action->num, haveHeadData);
+					_conversation.runScript();
 
 					// Take a copy of the dialog because the actions might change the scene
 					Dialog dlgCopy = dlg;
@@ -1061,8 +1078,8 @@ bool SDSScene::checkDialogActive() {
 				dlg.setFlag(kDlgFlagHiFinished);
 				showDialog(dlg._nextDialogFileNum, dlg._nextDialogDlgNum);
 			} else {
-				// No next dialog clear CDS data
-				_conversation.unload();
+				// No next dialog .. clear CDS data?
+				//_conversation.unloadData();
 			}
 		}
 		if (dlg.hasFlag(kDlgFlagVisible)) {
@@ -1218,6 +1235,8 @@ void SDSScene::mouseLDown(const Common::Point &pt) {
 		return;
 	}
 
+	_ignoreMouseUp = false;
+
 	// Don't start drag in look/target mode.
 	if (_lookMode)
 		return;
diff --git a/engines/dgds/scene.h b/engines/dgds/scene.h
index 05d0c7790f1..ec84a3bbcd1 100644
--- a/engines/dgds/scene.h
+++ b/engines/dgds/scene.h
@@ -313,9 +313,9 @@ public:
 	Dialog *loadDialogData(uint16 num);
 	void freeDialogData(uint16 num);
 	bool loadTalkData(uint16 num);
-	void freeTalkData(uint16 num);
+	bool freeTalkData(uint16 num);
 	void updateVisibleTalkers();
-	void loadTalkDataAndSetFlags(uint16 talknum, uint16 headnum);
+	bool loadTalkDataAndSetFlags(uint16 talknum, uint16 headnum);
 	void drawVisibleHeads(Graphics::ManagedSurface *dst);
 	bool hasVisibleHead() const;
 
@@ -334,6 +334,8 @@ public:
 	void setDynamicSceneRect(int16 num, int16 x, int16 y, int16 width, int16 height);
 	void setSceneNum(int16 num) { _num = num; }
 	void drawDebugHotAreas(Graphics::ManagedSurface *dst) const;
+	void setIgnoreMouseUp() { _ignoreMouseUp = true; }
+	void setShouldClearDlg() { _shouldClearDlg = true; }
 
 protected:
 	HotArea *findAreaUnderMouse(const Common::Point &pt);
diff --git a/engines/dgds/ttm.cpp b/engines/dgds/ttm.cpp
index 17915de6262..0145480c5b8 100644
--- a/engines/dgds/ttm.cpp
+++ b/engines/dgds/ttm.cpp
@@ -132,7 +132,8 @@ bool TTMInterpreter::load(const Common::String &filename, TTMEnviro &scriptData)
 void TTMInterpreter::unload() {
 }
 
-static const char *ttmOpName(uint16 op) {
+/*static*/
+const char *TTMInterpreter::ttmOpName(uint16 op) {
 	switch (op) {
 	case 0x0000: return "FINISH";
 	case 0x0020: return "SAVE(free?) BACKGROUND";
@@ -493,7 +494,7 @@ void TTMInterpreter::doWipeOp(uint16 code, const TTMEnviro &env, const TTMSeq &s
 }
 
 
-int16 TTMInterpreter::doOpInitCreditScroll(const Image *img) {
+int16 TTMInterpreter::doInitCreditScrollOp(const Image *img) {
 	assert(img);
 	int16 maxWidth = 0;
 	for (int i = 0; i < img->loadedFrameCount(); i++)
@@ -501,7 +502,7 @@ int16 TTMInterpreter::doOpInitCreditScroll(const Image *img) {
 	return maxWidth;
 }
 
- bool TTMInterpreter::doOpCreditsScroll(const Image *img, int16 ygap, int16 ymax, int16 xoff, int16 measuredWidth, const Common::Rect &clipRect) {
+ bool TTMInterpreter::doCreditsScrollOp(const Image *img, int16 ygap, int16 ymax, int16 xoff, int16 measuredWidth, const Common::Rect &clipRect) {
 	int nframes = img->loadedFrameCount();
 	bool scrollFinished = true;
 	int y = SCREEN_HEIGHT - ymax;
@@ -573,6 +574,99 @@ void TTMInterpreter::doDrawDialogForStrings(const TTMEnviro &env, const TTMSeq &
 	}
 }
 
+/// Handle 0xa5xx draw ops
+void TTMInterpreter::doDrawSpriteOp(const TTMEnviro &env, const TTMSeq &seq, uint16 op, byte count, const int16 *ivals, int16 xoff, int16 yoff) {
+	int frameno;
+	int bmpNo;
+	int dstWidth = 0;
+	int dstHeight = 0;
+	if (count == 6) {
+		frameno = ivals[2];
+		bmpNo = ivals[3];
+		dstWidth = ivals[4];
+		dstHeight = ivals[5];
+	} else if (count == 4) {
+		frameno = ivals[2];
+		bmpNo = ivals[3];
+	} else {
+		frameno = seq._brushNum;
+		bmpNo = seq._currentBmpId;
+	}
+
+	ImageFlipMode flipMode = kImageFlipNone;
+	if (op == 0xa510)
+		flipMode = kImageFlipV;
+	else if (op == 0xa520)
+		flipMode = kImageFlipH;
+	else if (op == 0xa530)
+		flipMode = kImageFlipHV;
+
+	Common::SharedPtr<Image> img = env._scriptShapes[bmpNo];
+	if (img) {
+		int x = ivals[0] + xoff;
+		int y = ivals[1] + yoff;
+		// Use env offset if we are in gosub
+		if (_stackDepth > 0) {
+			x += env._xOff;
+			y += env._yOff;
+		}
+		img->drawBitmap(frameno, x, y, seq._drawWin, _vm->_compositionBuffer, flipMode, dstWidth, dstHeight);
+	} else {
+		warning("Trying to draw image %d in env %d which is not loaded", bmpNo, env._enviro);
+	}
+}
+
+void TTMInterpreter::doFadeOutOp(int16 colorno, int16 ncolors, int16 targetcol, int16 speed) {
+	if (speed == 0) {
+		_vm->getGamePals()->clearPalette();
+	} else {
+		// The original tight-loops here with 640 steps and i/10 as the fade level..
+		// bring that down a bit to use less cpu.
+		// Speed 4 should complete fade in 2 seconds (eg, Dynamix logo fade)
+
+		// TODO: this is a pretty bad way to do it - should pump messages in this loop?
+		for (int i = 0; i < 320; i += speed) {
+			int fade = MIN(i / 5, 63);
+			_vm->getGamePals()->setFade(colorno, ncolors, targetcol, fade * 4);
+			g_system->updateScreen();
+			g_system->delayMillis(5);
+		}
+	}
+
+	// Logic here is different in Dragon + HOC.  They clear all buffers after fade
+	if (_vm->getGameId() == GID_DRAGON || _vm->getGameId() == GID_HOC) {
+		_vm->_compositionBuffer.fillRect(Common::Rect(SCREEN_WIDTH, SCREEN_HEIGHT), 0);
+		_vm->getBackgroundBuffer().fillRect(Common::Rect(SCREEN_WIDTH, SCREEN_HEIGHT), 0);
+	} else {
+		// In Willy Beamish, copy comp->screen and comp->back
+		g_system->copyRectToScreen(_vm->_compositionBuffer.getPixels(), SCREEN_WIDTH, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
+		g_system->updateScreen();
+		_vm->getBackgroundBuffer().blitFrom(_vm->_compositionBuffer);
+	}
+	// Stored area is cleared in all games.
+	_vm->getStoredAreaBuffer().fillRect(Common::Rect(SCREEN_WIDTH, SCREEN_HEIGHT), 0);
+
+	// Reset to previous palette.
+	_vm->getGamePals()->setPalette();
+}
+
+void TTMInterpreter::doFadeInOp(int16 colorno, int16 ncolors, int16 targetcol, int16 speed) {
+	if (speed == 0) {
+		_vm->getGamePals()->setPalette();
+	} else {
+		for (int i = 320; i > 0; i -= speed) {
+			int fade = MAX(0, MIN(i / 5, 63));
+			_vm->getGamePals()->setFade(colorno, ncolors, targetcol, fade * 4);
+			if (i == 320) {
+				// update screen first to make the initial fade-in work
+				g_system->copyRectToScreen(_vm->_compositionBuffer.getPixels(), SCREEN_WIDTH, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
+			}
+			g_system->updateScreen();
+			g_system->delayMillis(5);
+		}
+	}
+}
+
 
 void 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) {
@@ -634,10 +728,7 @@ void TTMInterpreter::handleOperation(TTMEnviro &env, TTMSeq &seq, uint16 op, byt
 		break;
 	case 0x0110: // PURGE void
 		// only set if not running from CDS script
-		if (env._cdsSeqNum < 0)
-			_vm->adsInterpreter()->setHitTTMOp0110();
-		else
-			env._cdsSeqNum++;
+		_vm->adsInterpreter()->setHitTTMOp0110();
 		break;
 	case 0x0220: // STOP CURRENT MUSIC
 		if (seq._executed) // this is a one-shot op
@@ -661,12 +752,7 @@ void 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);
-		// 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);
+		_vm->adsInterpreter()->setScriptDelay(delayMillis);
 		break;
 	}
 	case 0x1030: // SET BRUSH:	id:int [-1:n]
@@ -680,8 +766,6 @@ void TTMInterpreter::handleOperation(TTMEnviro &env, TTMSeq &seq, uint16 op, byt
 		seq._currentPalId = ivals[0];
 		if (seq._executed) // this is a mostly on-shot op.
 			break;
-		if (env._cdsSeqNum >= 0) // don't change global pal for CDS scripts.
-			break;
 		_vm->getGamePals()->selectPalNum(env._scriptPals[ivals[0]]);
 		break;
 	case 0x1070: // SELECT FONT  i:int
@@ -794,23 +878,6 @@ void TTMInterpreter::handleOperation(TTMEnviro &env, TTMSeq &seq, uint16 op, byt
 		_vm->_compositionBuffer.blitFrom(_vm->getBackgroundBuffer());
 		break;
 	}
-	case 0x3200: // CDS FIND GOTO TARGET frameno
-		env._cdsSeqNum = findGOTOTarget(env, seq, ivals[0]);
-		break;
-	case 0x3300: // CDS GOSUB
-		if (!env._cdsJumped && env._frameOffsets[env._cdsSeqNum] != seq._currentFrame) {
-			env._cdsJumped = true;
-			int64 prevPos = env.scr->pos();
-			env._xOff += ivals[0];
-			env._yOff += ivals[1];
-			env.scr->seek(env._frameOffsets[env._cdsSeqNum]);
-			run(env, seq);
-			env._xOff -= ivals[0];
-			env._yOff -= ivals[1];
-			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);
@@ -818,65 +885,18 @@ void TTMInterpreter::handleOperation(TTMEnviro &env, TTMSeq &seq, uint16 op, byt
 	case 0x4110: // FADE OUT:	colorno,ncolors,targetcol,speed:byte
 		if (seq._executed) // this is a one-shot op.
 			break;
-		if (ivals[3] == 0) {
-			_vm->getGamePals()->clearPalette();
-		} else {
-			// The original tight-loops here with 640 steps and i/10 as the fade level..
-			// bring that down a bit to use less cpu.
-			// Speed 4 should complete fade in 2 seconds (eg, Dynamix logo fade)
-
-			// TODO: this is a pretty bad way to do it - should pump messages in this loop?
-			for (int i = 0; i < 320; i += ivals[3]) {
-				int fade = MIN(i / 5, 63);
-				_vm->getGamePals()->setFade(ivals[0], ivals[1], ivals[2], fade * 4);
-				g_system->updateScreen();
-				g_system->delayMillis(5);
-			}
-		}
-
-		// Logic here is different in Dragon + HOC.  They clear all buffers after fade
-		if (_vm->getGameId() == GID_DRAGON || _vm->getGameId() == GID_HOC) {
-			_vm->_compositionBuffer.fillRect(Common::Rect(SCREEN_WIDTH, SCREEN_HEIGHT), 0);
-			_vm->getBackgroundBuffer().fillRect(Common::Rect(SCREEN_WIDTH, SCREEN_HEIGHT), 0);
-		} else {
-			// In Willy Beamish, copy comp->screen and comp->back
-			g_system->copyRectToScreen(_vm->_compositionBuffer.getPixels(), SCREEN_WIDTH, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
-			g_system->updateScreen();
-			_vm->getBackgroundBuffer().blitFrom(_vm->_compositionBuffer);
-		}
-		// Stored area is cleared in all games.
-		_vm->getStoredAreaBuffer().fillRect(Common::Rect(SCREEN_WIDTH, SCREEN_HEIGHT), 0);
-
-		// Reset to previous palette.
-		_vm->getGamePals()->setPalette();
-
+		doFadeOutOp(ivals[0], ivals[1], ivals[2], ivals[3]);
 		break;
 	case 0x4120: { // FADE IN:	colorno,ncolors,targetcol,speed:byte
 		if (seq._executed) // this is a one-shot op.
 			break;
-
-		if (ivals[3] == 0) {
-			_vm->getGamePals()->setPalette();
-		} else {
-			for (int i = 320; i > 0; i -= ivals[3]) {
-				int fade = MAX(0, MIN(i / 5, 63));
-				_vm->getGamePals()->setFade(ivals[0], ivals[1], ivals[2], fade * 4);
-				if (i == 320) {
-					// update screen first to make the initial fade-in work
-					g_system->copyRectToScreen(_vm->_compositionBuffer.getPixels(), SCREEN_WIDTH, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
-				}
-				g_system->updateScreen();
-				g_system->delayMillis(5);
-			}
-		}
+		doFadeInOp(ivals[0], ivals[1], ivals[2], ivals[3]);
 		break;
 	}
 	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;
 		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;
 	}
@@ -979,47 +999,9 @@ void TTMInterpreter::handleOperation(TTMEnviro &env, TTMSeq &seq, uint16 op, byt
 	case 0xa500: // DRAW SPRITE: x,y,frameno,bmpno:int [-n,+n]
 	case 0xa510: // DRAW SPRITE FLIP V x,y:int
 	case 0xa520: // DRAW SPRITE FLIP H: x,y:int
-	case 0xa530: { // DRAW SPRITE FLIP HV: x,y,frameno,bmpno:int	[-n,+n] (CHINA+)
-		int frameno;
-		int bmpNo;
-		int dstWidth = 0;
-		int dstHeight = 0;
-		if (count == 6) {
-			frameno = ivals[2];
-			bmpNo = ivals[3];
-			dstWidth = ivals[4];
-			dstHeight = ivals[5];
-		} else if (count == 4) {
-			frameno = ivals[2];
-			bmpNo = ivals[3];
-		} else {
-			frameno = seq._brushNum;
-			bmpNo = seq._currentBmpId;
-		}
-
-		ImageFlipMode flipMode = kImageFlipNone;
-		if (op == 0xa510)
-			flipMode = kImageFlipV;
-		else if (op == 0xa520)
-			flipMode = kImageFlipH;
-		else if (op == 0xa530)
-			flipMode = kImageFlipHV;
-
-		Common::SharedPtr<Image> img = env._scriptShapes[bmpNo];
-		if (img) {
-			int x = ivals[0];
-			int y = ivals[1];
-			// Use env offset if we are in gosub *or* running from CDS
-			if (_stackDepth > 0 || env._cdsSeqNum >= 0) {
-				x += env._xOff;
-				y += env._yOff;
-			}
-			img->drawBitmap(frameno, x, y, seq._drawWin, _vm->_compositionBuffer, flipMode, dstWidth, dstHeight);
-		} else {
-			warning("Trying to draw image %d in env %d which is not loaded", bmpNo, env._enviro);
-		}
+	case 0xa530: // DRAW SPRITE FLIP HV: x,y,frameno,bmpno:int	[-n,+n] (CHINA+)
+		doDrawSpriteOp(env, seq, op, count, ivals);
 		break;
-	}
 	case 0xa600: { // DRAW GETPUT: i:int
 		if (seq._executed) // this is a one-shot op.
 			break;
@@ -1078,13 +1060,13 @@ void TTMInterpreter::handleOperation(TTMEnviro &env, TTMSeq &seq, uint16 op, byt
 	case 0xb000: // INIT CREDITS SCRLL
 		if (seq._executed) // this is a one-shot op
 			break;
-		env._creditScrollMeasure = doOpInitCreditScroll(env._scriptShapes[seq._currentBmpId].get());
+		env._creditScrollMeasure = doInitCreditScrollOp(env._scriptShapes[seq._currentBmpId].get());
 		env._creditScrollYOffset = 0;
 		break;
 	case 0xb010: { // DRAW CREDITS SCROLL ygap,ystep
 		const Image *img = env._scriptShapes[seq._currentBmpId].get();
 		if (img && img->isLoaded()) {
-			bool finished = doOpCreditsScroll(env._scriptShapes[seq._currentBmpId].get(), ivals[0], env._creditScrollYOffset,
+			bool finished = doCreditsScrollOp(env._scriptShapes[seq._currentBmpId].get(), ivals[0], env._creditScrollYOffset,
 							ivals[2], env._creditScrollMeasure, seq._drawWin);
 			env._creditScrollYOffset += ivals[1];
 			if (finished)
@@ -1140,8 +1122,7 @@ void TTMInterpreter::handleOperation(TTMEnviro &env, TTMSeq &seq, uint16 op, byt
 			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());
+			warning("TTM 0xC210: Skip loading RAW %s, not found.", sval.c_str());
 		}
 		break;
 	}
@@ -1162,23 +1143,9 @@ void TTMInterpreter::handleOperation(TTMEnviro &env, TTMSeq &seq, uint16 op, byt
 		}
 		break;
 	}
-	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, SEEK_CUR);
-			return false;
-		}*/
-		break;
-	}
 	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 4436a297490..8d7f0ba0165 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),
-			_cdsSeqNum(-1), _cdsJumped(false), _cdsDelay(0), ScriptParserData() {
+			ScriptParserData() {
 		ARRAYCLEAR(_scriptPals);
 	}
 
@@ -65,9 +65,6 @@ public:
 	int16 _xScroll;
 	int16 _yScroll;
 	Common::SharedPtr<SoundRaw> _soundRaw;
-	int16 _cdsSeqNum; // The GOTO target to use in the CDS script (Willy Beamish talkie)
-	int16 _cdsDelay;
-	bool _cdsJumped;
 };
 
 enum TTMRunType {
@@ -128,14 +125,19 @@ public:
 	void findAndAddSequences(TTMEnviro &scriptData, Common::Array<Common::SharedPtr<TTMSeq>> &seqArray);
 
 	static Common::String readTTMStringVal(Common::SeekableReadStream *scr);
+	int32 findGOTOTarget(const TTMEnviro &env, const TTMSeq &seq, int16 frame);
 
 protected:
-	void 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);
+	virtual void handleOperation(TTMEnviro &env, TTMSeq &seq, uint16 op, byte count, const int16 *ivals, const Common::String &sval, const Common::Array<Common::Point> &pts);
 	void doWipeOp(uint16 code, const TTMEnviro &env, const TTMSeq &seq, const Common::Rect &r);
-	int16 doOpInitCreditScroll(const Image *img);
-	bool doOpCreditsScroll(const Image *img, int16 ygap, int16 ymax, int16 xoff, int16 measuredWidth, const Common::Rect &clipRect);
+	int16 doInitCreditScrollOp(const Image *img);
+	bool doCreditsScrollOp(const Image *img, int16 ygap, int16 ymax, int16 xoff, int16 measuredWidth, const Common::Rect &clipRect);
 	void doDrawDialogForStrings(const TTMEnviro &env, const TTMSeq &seq, int16 x, int16 y, int16 width, int16 height);
+	void doDrawSpriteOp(const TTMEnviro &env, const TTMSeq &seq, uint16 op, byte count, const int16 *ivals, int16 xoff = 0, int16 yoff = 0);
+	void doFadeOutOp(int16 colorno, int16 ncolors, int16 targetcol, int16 speed);
+	void doFadeInOp(int16 colorno, int16 ncolors, int16 targetcol, int16 speed);
+
+	static const char *ttmOpName(uint16 op);
 
 	DgdsEngine *_vm;
 	int _stackDepth;




More information about the Scummvm-git-logs mailing list