[Scummvm-git-logs] scummvm master -> 8e763b5eea1eb16be0f6b59c9a4a75e566fed20f

criezy noreply at scummvm.org
Mon Jul 14 20:36:32 UTC 2025


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

Summary:
8e763b5eea DRACI: Add text-to-speech (TTS)


Commit: 8e763b5eea1eb16be0f6b59c9a4a75e566fed20f
    https://github.com/scummvm/scummvm/commit/8e763b5eea1eb16be0f6b59c9a4a75e566fed20f
Author: ellm135 (ellm13531 at gmail.com)
Date: 2025-07-14T21:36:28+01:00

Commit Message:
DRACI: Add text-to-speech (TTS)

Changed paths:
  A engines/draci/detection.h
    engines/draci/animation.cpp
    engines/draci/detection.cpp
    engines/draci/draci.cpp
    engines/draci/draci.h
    engines/draci/game.cpp
    engines/draci/game.h
    engines/draci/metaengine.cpp
    engines/draci/script.cpp


diff --git a/engines/draci/animation.cpp b/engines/draci/animation.cpp
index b52f705e8ed..ddfaf25dec3 100644
--- a/engines/draci/animation.cpp
+++ b/engines/draci/animation.cpp
@@ -263,6 +263,12 @@ void Animation::play() {
 	if (isPlaying()) {
 		return;
 	}
+	
+	// Title screen in the intro cutscene
+	if (getID() == 671 && _vm->_game->getMapID() == 42) {
+		_vm->setTTSVoice(kNarratorID);
+		_vm->sayText("Dra\x87\xa1 Historie");
+	}
 
 	// Mark the first frame dirty so it gets displayed
 	markDirtyRect(_vm->_screen->getSurface());
diff --git a/engines/draci/detection.cpp b/engines/draci/detection.cpp
index e6a09c6b29b..5f1db998c5a 100644
--- a/engines/draci/detection.cpp
+++ b/engines/draci/detection.cpp
@@ -25,6 +25,7 @@
 #include "engines/metaengine.h"
 
 #include "draci/draci.h"
+#include "draci/detection.h"
 
 static const DebugChannelDef debugFlagList[] = {
 	{Draci::kDraciGeneralDebugLevel, "general", "Draci general debug info"},
@@ -52,7 +53,7 @@ const ADGameDescription gameDescriptions[] = {
 		Common::EN_ANY,
 		Common::kPlatformDOS,
 		ADGF_NO_FLAGS,
-		GUIO0()
+		GUIO2(GAMEOPTION_TTS_OBJECTS, GAMEOPTION_TTS_SPEECH)
 	},
 
 	{
@@ -62,7 +63,7 @@ const ADGameDescription gameDescriptions[] = {
 		Common::CS_CZE,
 		Common::kPlatformDOS,
 		ADGF_NO_FLAGS,
-		GUIO0()
+		GUIO2(GAMEOPTION_TTS_OBJECTS, GAMEOPTION_TTS_MISSING_VOICE)
 	},
 
 	{
@@ -72,7 +73,7 @@ const ADGameDescription gameDescriptions[] = {
 		Common::PL_POL,
 		Common::kPlatformDOS,
 		ADGF_NO_FLAGS,
-		GUIO0()
+		GUIO2(GAMEOPTION_TTS_OBJECTS, GAMEOPTION_TTS_MISSING_VOICE)
 	},
 
 	{
@@ -82,7 +83,7 @@ const ADGameDescription gameDescriptions[] = {
 		Common::DE_DEU,
 		Common::kPlatformDOS,
 		ADGF_NO_FLAGS,
-		GUIO0()
+		GUIO2(GAMEOPTION_TTS_OBJECTS, GAMEOPTION_TTS_SPEECH)
 	},
 
 	AD_TABLE_END_MARKER
diff --git a/engines/draci/detection.h b/engines/draci/detection.h
new file mode 100644
index 00000000000..cd1f1279858
--- /dev/null
+++ b/engines/draci/detection.h
@@ -0,0 +1,29 @@
+/* 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 DRACI_DETECTION_H
+#define DRACI_DETECTION_H
+
+#define GAMEOPTION_TTS_OBJECTS				GUIO_GAMEOPTIONS1
+#define GAMEOPTION_TTS_SPEECH				GUIO_GAMEOPTIONS2
+#define GAMEOPTION_TTS_MISSING_VOICE		GUIO_GAMEOPTIONS3
+
+#endif // DRACI_DETECTION_H
diff --git a/engines/draci/draci.cpp b/engines/draci/draci.cpp
index 912af2b87df..c505ae13e5a 100644
--- a/engines/draci/draci.cpp
+++ b/engines/draci/draci.cpp
@@ -26,8 +26,10 @@
 #include "common/events.h"
 #include "common/file.h"
 #include "common/keyboard.h"
+#include "common/text-to-speech.h"
 
 #include "engines/util.h"
+#include "engines/advancedDetector.h"
 
 #include "graphics/cursorman.h"
 #include "graphics/font.h"
@@ -47,6 +49,60 @@
 
 namespace Draci {
 
+#ifdef USE_TTS
+
+// Used by all languages but Polish
+static const uint16 kamenickyEncodingTable[] = {
+	0xc48c, 0xc3bc, 0xc3a9, 0xc48f,	// Č, ü, é, ď
+	0xc3a4, 0xc48e, 0xc5a4, 0xc48d,	// ä, Ď, Ť, č
+	0xc49b, 0xc494, 0xc4b9, 0xc38d,	// ě, Ě, Ĺ, Í
+	0xc4be, 0xc4ba, 0xc384, 0xc381,	// ľ, ĺ, Ä, Á
+	0xc389, 0xc5be, 0xc5bd, 0xc3b4,	// É, ž, Ž, ô
+	0xc3b6, 0xc393, 0xc5af, 0xc39a,	// ö, Ó, ů, Ú
+	0xc3bd, 0xc396, 0xc39c, 0xc5a0,	// ý, Ö, Ü, Š
+	0xc4bd, 0xc39d, 0xc598, 0xc5a5,	// Ľ, Ý, Ř, ť
+	0xc3a1, 0xc3ad, 0xc3b3, 0xc3ba,	// á, í, ó, ú
+	0xc588, 0xc587, 0xc5ae, 0xc394,	// ň, Ň, Ů, Ô
+	0xc5a1, 0xc599, 0xc595, 0xc594	// Å¡, Å™, Å•, Å”
+};
+
+// Name of encoding unknown (described by the Draci website as "some ridiculous proprietary encoding")
+// After 0x9b (0xc5bb/Ż), it matches Kamenický encoding (though some of these Czech characters are replaced
+// for TTS because it struggles to pronounce them)
+static const uint16 polishEncodingTable[] = {
+	0xc485, 0xc487, 0xc499, 0xc582,	// ą, ć, ę, ł
+	0xc584, 0xc3b3, 0xc59b, 0xc5ba,	// ń, ó, ś, ź
+	0xc5bc, 0xc484, 0xc486, 0xc498, // ż, Ą, Ć, Ę
+	0xc581, 0xc583, 0xc393, 0xc59a,	// Ł, Ń, Ó, Ś
+	0xc5b9, 0xc5bb, 0x5a, 0x6f,		// Ź, Ż, Z, o
+	0xc3b6, 0xc393, 0xc5af, 0xc39a,	// ö, Ó, ů, Ú
+	0xc3bd, 0xc396, 0xc39c, 0x53,	// ý, Ö, Ü, S
+	0xc4bd, 0xc39d, 0x52, 0x74,		// Ľ, Ý, R, t
+	0xc3a1, 0xc3ad, 0xc3b3, 0xc3ba,	// á, í, ó, ú
+	0x6e, 0x4e, 0xc5ae, 0xc394,		// n, N, Ů, Ô
+	0x73, 0x72, 0x72, 0x52			// s, r, r, R
+};
+
+// TTS for all languages but Czech struggles to voice a lot of Czech characters in the credits, 
+// and oftentimes skips them entirely (i.e. "Å palek" is pronounced as "Palek")
+// To more closely resemble how the names are supposed to be pronounced,
+// this table replaces certain Czech characters with an alternative
+static const uint16 czechCharacterConversionTable[] = {
+	0x43, 0xc3bc, 0xc3a9, 0x64,		// C, ü, é, d
+	0xc3a4, 0x44, 0x54, 0x63,		// ä, D, T, c
+	0x65, 0x45, 0x4c, 0xc38d,		// e, E, L, Í
+	0x6c, 0xc4ba, 0xc384, 0xc381,	// l, ĺ, Ä, Á
+	0xc389, 0x7a, 0x5a, 0x6f,		// É, z, Z, o
+	0xc3b6, 0xc393, 0x75, 0xc39a,	// ö, Ó, u, Ú
+	0x0079, 0xc396, 0xc39c, 0x53,	// y, Ö, Ü, S
+	0x4c, 0x59, 0x52, 0x74,			// L, Y, R, t
+	0xc3a1, 0xc3ad, 0xc3b3, 0xc3ba,	// á, í, ó, ú
+	0x6e, 0x4e, 0x55, 0x4f,			// n, N, U, O
+	0x73, 0x72, 0x72, 0x52			// s, r, r, R
+};
+
+#endif
+
 // Data file paths
 
 const char *objectsPath = "OBJEKTY.DFW";
@@ -69,7 +125,7 @@ const uint kSoundsFrequency = 13000;
 const uint kDubbingFrequency = 22050;
 
 DraciEngine::DraciEngine(OSystem *syst, const ADGameDescription *gameDesc)
- : Engine(syst), _rnd("draci") {
+ : Engine(syst), _gameDescription(gameDesc), _rnd("draci") {
 
 	setDebugger(new DraciConsole(this));
 
@@ -302,6 +358,8 @@ void DraciEngine::handleEvents() {
 					// cut-scenes won't be played.
 					_game->setExitLoop(true);
 					_script->endCurrentProgram(true);
+
+					stopTextToSpeech();
 				}
 				break;
 			}
@@ -403,6 +461,12 @@ Common::Error DraciEngine::run() {
 	setTotalPlayTime(0);
 	_game->init();
 
+	Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+	if (ttsMan != nullptr) {
+		ttsMan->enable(ConfMan.getBool("tts_enabled_objects") || ConfMan.getBool("tts_enabled_speech") || ConfMan.getBool("tts_enabled_missing_voice"));
+		ttsMan->setLanguage(ConfMan.get("language"));
+	}
+
 	// Load game from specified slot, if any
 	if (ConfMan.hasKey("save_slot")) {
 		loadGameState(ConfMan.getInt("save_slot"));
@@ -472,4 +536,143 @@ bool DraciEngine::canSaveGameStateCurrently(Common::U32String *msg) {
 		(_game->getLoopSubstatus() == kOuterLoop);
 }
 
+void DraciEngine::sayText(const Common::String &text, bool isSubtitle) {
+	Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+	// _previousSaid is used to prevent the TTS from looping when sayText is called inside a loop,
+	// for example when the cursor stays on a dialog option. Without it when the text ends it would speak
+	// the same text again.
+	// _previousSaid is cleared when appropriate to allow for repeat requests
+	bool speak = (!isSubtitle && ConfMan.getBool("tts_enabled_objects") && _previousSaid != text) || 
+				 (isSubtitle && (ConfMan.getBool("tts_enabled_speech") || ConfMan.getBool("tts_enabled_missing_voice")));
+	if (ttsMan != nullptr && speak) {
+#ifdef USE_TTS
+		ttsMan->say(convertText(text), Common::TextToSpeechManager::INTERRUPT);
+#endif
+		_previousSaid = text;
+	}
+}
+
+void DraciEngine::stopTextToSpeech() {
+	Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+	if (ttsMan != nullptr && (ConfMan.getBool("tts_enabled_objects") || ConfMan.getBool("tts_enabled_speech") || ConfMan.getBool("tts_enabled_missing_voice")) && 
+			ttsMan->isSpeaking()) {
+		ttsMan->stop();
+		_previousSaid.clear();
+		setTTSVoice(kBertID);
+	}
+}
+
+void DraciEngine::setTTSVoice(int characterID) const {
+#ifdef USE_TTS
+	Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+	if (ttsMan != nullptr && (ConfMan.getBool("tts_enabled_objects") || ConfMan.getBool("tts_enabled_speech") || ConfMan.getBool("tts_enabled_missing_voice"))) {
+		Common::Array<int> voices;
+		int pitch = 0;
+		Common::TTSVoice::Gender gender;
+
+		if (characterDialogData[characterID].male) {
+			voices = ttsMan->getVoiceIndicesByGender(Common::TTSVoice::MALE);
+			gender = Common::TTSVoice::MALE;
+		} else {
+			voices = ttsMan->getVoiceIndicesByGender(Common::TTSVoice::FEMALE);
+			gender = Common::TTSVoice::FEMALE;
+		}
+
+		// If no voice is available for the necessary gender, set the voice to default
+		if (voices.empty()) {
+			ttsMan->setVoice(0);
+		} else {
+			int voiceIndex = characterDialogData[characterID].voiceID % voices.size();
+			ttsMan->setVoice(voices[voiceIndex]);
+		}
+
+		// If no voices are available for this gender, alter the pitch to mimic a voice
+		// of the other gender
+		if (ttsMan->getVoice().getGender() != gender) {
+			if (gender == Common::TTSVoice::MALE) {
+				pitch -= 50;
+			} else {
+				pitch += 50;
+			}
+		}
+
+		ttsMan->setPitch(pitch);
+	}
+#endif
+}
+
+#ifdef USE_TTS
+
+Common::U32String DraciEngine::convertText(const Common::String &text) const {
+	const uint16 *translationTable;
+
+	if (getLanguage() == Common::PL_POL) {
+		translationTable = polishEncodingTable;
+	} else {
+		translationTable = kamenickyEncodingTable;
+	}
+
+	const byte *bytes = (const byte *)text.c_str();
+	byte *convertedBytes = new byte[text.size() * 2 + 1];
+
+	int i = 0;
+	for (const byte *b = bytes; *b; ++b) {
+		if (*b == 0x7c) {	// Convert | to a space
+			convertedBytes[i] = 0x20;
+			i++;
+			continue;
+		}
+
+		if (*b < 0x80 || *b > 0xab) {
+			convertedBytes[i] = *b;
+			i++;
+			continue;
+		}
+
+		bool inTable = false;
+		for (int j = 0; translationTable[j]; ++j) {
+			if (*b - 0x80 == j) {
+				int convertedValue = translationTable[j];
+
+				if (translationTable[j] == 0xc3b4 && getLanguage() == Common::DE_DEU) {
+					// German encoding replaces ô with ß
+					convertedValue = 0xc39f;
+				} else if ((getLanguage() == Common::EN_ANY || getLanguage() == Common::DE_DEU) && 
+								translationTable[j] != czechCharacterConversionTable[j]) {
+					// Replace certain Czech characters for English and German TTS with close alternatives
+					// in those languages, for better TTS
+					convertedValue = czechCharacterConversionTable[j];
+				}
+
+				if (convertedValue <= 0xff) {
+					convertedBytes[i] = convertedValue;
+					i++;
+				} else {
+					convertedBytes[i] = (convertedValue >> 8) & 0xff;
+					convertedBytes[i + 1] = convertedValue & 0xff;
+					i += 2;
+				}
+				
+				inTable = true;
+				break;
+			}
+		}
+
+		if (!inTable) {
+			convertedBytes[i] = *b;
+			i++;
+		}
+	}
+	
+	convertedBytes[i] = 0;
+
+	Common::U32String result((char *)convertedBytes);
+	delete[] convertedBytes;
+
+	return result;
+}
+
+#endif
+
+
 } // End of namespace Draci
diff --git a/engines/draci/draci.h b/engines/draci/draci.h
index 7c30328ef45..832805166e4 100644
--- a/engines/draci/draci.h
+++ b/engines/draci/draci.h
@@ -54,6 +54,60 @@ enum DRACIAction {
 	kActionInvRotateNext
 };
 
+#ifdef USE_TTS
+
+struct CharacterDialogData {
+	uint8 voiceID;
+	bool male;
+};
+
+static const CharacterDialogData characterDialogData[] = {
+	{ 0, true },	// Bert
+	{ 1, true },	// Narrator
+	{ 2, true },	// Giant
+	{ 3, true },	// Captured dwarf
+	{ 4, true },	// Troll
+	{ 0, false },	// Berta
+	{ 5, true },	// Herbert
+	{ 1, false },	// Evelyn
+	{ 1, false },	// Evelyn
+	{ 2, false },	// Karmela
+	{ 6, true },	// King
+	{ 7, true },	// Wind
+	{ 8, true },	// Worm
+	{ 9, true },	// Pub dwarf
+	{ 10, true },	// Card player 2
+	{ 11, true },	// Card player 1
+	{ 12, true },	// Barkeeper
+	{ 13, true },	// Lazy man
+	{ 14, true },	// Goblin 1
+	{ 15, true },	// Goblin 2
+	{ 3, false },	// Chronicle
+	{ 16, true },	// Beggar
+	{ 17, true },	// Horn-blower
+	{ 18, true },	// Herbert
+	{ 19, true },	// Wizard
+	{ 20, true },	// Comedian
+	{ 21, true },	// Darter
+	{ 4, true },	// Troll
+	{ 21, true },	// Darter
+	{ 0, true },	// Skull
+	{ 22, true },	// Canary
+	{ 23, true },	// Oak-tree
+	{ 24, true },	// Beech-tree
+	{ 4, false },	// Agatha
+	{ 5, false },	// Eulanie
+	{ 25, true },	// Knight
+	{ 1, false },	// Evelyn
+	{ 20, true },	// Comedian
+	{ 1, true },	// Narrator
+};
+
+#endif
+
+static const int kBertID = 0;
+static const int kNarratorID = 1;
+
 class Screen;
 class Mouse;
 class Game;
@@ -73,6 +127,8 @@ public:
 	int init();
 	Common::Error run() override;
 
+	Common::Language getLanguage() const;
+
 	bool hasFeature(Engine::EngineFeature f) const override;
 	void pauseEngineIntern(bool pause) override;
 	void syncSoundSettings() override;
@@ -86,6 +142,15 @@ public:
 	Common::Error saveGameState(int slot, const Common::String &desc, bool isAutosave = false) override;
 	bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override;
 
+	void sayText(const Common::String &text, bool isSubtitle = false);
+	void stopTextToSpeech();
+	void setTTSVoice(int characterID) const;
+#ifdef USE_TTS
+	Common::U32String convertText(const Common::String &text) const;
+#endif
+
+	const ADGameDescription *_gameDescription;
+
 	Screen *_screen;
 	Mouse *_mouse;
 	Game *_game;
@@ -97,6 +162,8 @@ public:
 	Font *_smallFont;
 	Font *_bigFont;
 
+	Common::String _previousSaid;
+
 	BArchive *_iconsArchive;
 	BArchive *_objectsArchive;
 	BArchive *_spritesArchive;
diff --git a/engines/draci/game.cpp b/engines/draci/game.cpp
index f5e30e8e394..65ee37091d2 100644
--- a/engines/draci/game.cpp
+++ b/engines/draci/game.cpp
@@ -24,6 +24,8 @@
 #include "common/memstream.h"
 #include "common/system.h"
 #include "common/util.h"
+#include "common/text-to-speech.h"
+#include "common/config-manager.h"
 
 #include "draci/draci.h"
 #include "draci/animation.h"
@@ -206,6 +208,8 @@ Game::Game(DraciEngine *vm) : _vm(vm), _walkingState(vm) {
 }
 
 void Game::start() {
+	_vm->sayText("NoSense");
+
 	while (!gameShouldQuit()) {
 		// Reset the flag allowing to run the scripts.
 		_vm->_script->endCurrentProgram(false);
@@ -417,6 +421,7 @@ void Game::handleInventoryLoop() {
 		// If there is an inventory item under the cursor and we aren't
 		// holding any item, run its look GPL program
 		if (_itemUnderCursor && !getCurrentItem()) {
+			_vm->_previousSaid.clear();
 			_vm->_script->runWrapper(_itemUnderCursor->_program, _itemUnderCursor->_look, true, false);
 		// Otherwise, if we are holding an item, try to place it inside the
 		// inventory
@@ -461,17 +466,25 @@ void Game::handleDialogueLoop() {
 		return;
 	}
 
+	bool hoveringOverDialogueLine = false;
+
 	Text *text;
 	for (int i = 0; i < kDialogueLines; ++i) {
 		text = reinterpret_cast<Text *>(_dialogueAnims[i]->getCurrentFrame());
 
 		if (_animUnderCursor == _dialogueAnims[i]) {
 			text->setColor(kLineActiveColor);
+			_vm->sayText(_dialogueBlocks[_lines[i]]._title);
+			hoveringOverDialogueLine = true;
 		} else {
 			text->setColor(kLineInactiveColor);
 		}
 	}
 
+	if (!hoveringOverDialogueLine) {
+		_vm->_previousSaid.clear();
+	}
+
 	if (_vm->_mouse->lButtonPressed() || _vm->_mouse->rButtonPressed()) {
 		setExitLoop(true);
 		_vm->_mouse->lButtonSet(false);
@@ -519,10 +532,15 @@ void Game::advanceAnimationsAndTestLoopExit() {
 	if (_loopSubstatus == kInnerWhileTalk) {
 		// If the current speech text has expired or the user clicked a mouse button,
 		// advance to the next line of text
-		if ((getEnableSpeedText() && (_vm->_mouse->lButtonPressed() || _vm->_mouse->rButtonPressed())) ||
-			(_vm->_system->getMillis() - _speechTick) >= _speechDuration) {
-
+		if (getEnableSpeedText() && (_vm->_mouse->lButtonPressed() || _vm->_mouse->rButtonPressed())) {
 			setExitLoop(true);
+			_vm->stopTextToSpeech();
+		} else if ((_vm->_system->getMillis() - _speechTick) >= _speechDuration) {
+			// Delay moving to the next line until TTS is done speaking if necessary
+			Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+			if (!ttsMan || !ttsMan->isSpeaking()) {
+				setExitLoop(true);
+			}
 		}
 		_vm->_mouse->lButtonSet(false);
 		_vm->_mouse->rButtonSet(false);
@@ -787,11 +805,23 @@ void Game::updateTitle(int x, int y) {
 	if (_loopStatus == kStatusInventory) {
 		// If there is no item under the cursor, delete the title.
 		// Otherwise, show the item's title.
-		title->setText(_itemUnderCursor ? _itemUnderCursor->_title : "");
+		if (_itemUnderCursor) {
+			title->setText(_itemUnderCursor->_title);
+			_vm->sayText(_itemUnderCursor->_title);
+		} else {
+			title->setText("");
+			_vm->_previousSaid.clear();
+		}
 	} else {
 		// If there is no object under the cursor, delete the title.
 		// Otherwise, show the object's title.
-		title->setText(_objUnderCursor ? _objUnderCursor->_title : "");
+		if (_objUnderCursor) {
+			title->setText(_objUnderCursor->_title);
+			_vm->sayText(_objUnderCursor->_title);
+		} else {
+			title->setText("");
+			_vm->_previousSaid.clear();
+		}
 	}
 
 	// Move the title to the correct place (just above the cursor)
diff --git a/engines/draci/game.h b/engines/draci/game.h
index d07a174407f..f8e45a6cddc 100644
--- a/engines/draci/game.h
+++ b/engines/draci/game.h
@@ -85,6 +85,8 @@ enum InventoryConstants {
   kStatusChangeTimeout = 500
 };
 
+static const int kCreditsMapID = 46;
+
 class GameObject {
 public:
 	int _absNum;
diff --git a/engines/draci/metaengine.cpp b/engines/draci/metaengine.cpp
index 38d5386e7d0..c0c1510b620 100644
--- a/engines/draci/metaengine.cpp
+++ b/engines/draci/metaengine.cpp
@@ -21,6 +21,7 @@
 
 #include "draci/draci.h"
 #include "draci/saveload.h"
+#include "draci/detection.h"
 
 #include "backends/keymapper/action.h"
 #include "backends/keymapper/keymapper.h"
@@ -32,12 +33,70 @@
 #include "engines/advancedDetector.h"
 #include "engines/metaengine.h"
 
+namespace Draci {
+
+#ifdef USE_TTS
+
+static const ADExtraGuiOptionsMap optionsList[] = {
+	{
+		GAMEOPTION_TTS_OBJECTS,
+		{
+			_s("Enable Text to Speech for Objects and Options"),
+			_s("Use TTS to read the descriptions (if TTS is available)"),
+			"tts_enabled_objects",
+			false,
+			0,
+			0
+		}
+	},
+
+	{
+		GAMEOPTION_TTS_SPEECH,
+		{
+			_s("Enable Text to Speech for Subtitles"),
+			_s("Use TTS to read the subtitles (if TTS is available)"),
+			"tts_enabled_speech",
+			false,
+			0,
+			0
+		}
+	},
+
+	{
+		GAMEOPTION_TTS_MISSING_VOICE,
+		{
+			_s("Enable Text to Speech for Missing Voiceovers"),
+			_s("Use TTS to read the subtitles of missing voiceovers (if TTS is available)"),
+			"tts_enabled_missing_voice",
+			false,
+			0,
+			0
+		}
+	},
+
+	AD_EXTRA_GUI_OPTIONS_TERMINATOR
+};
+
+#endif
+
+Common::Language DraciEngine::getLanguage() const {
+	return _gameDescription->language;
+}
+
+} // End of namespace Draci
+
 class DraciMetaEngine : public AdvancedMetaEngine<ADGameDescription> {
 public:
 	const char *getName() const override {
 		return "draci";
 	}
 
+#ifdef USE_TTS
+	const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override {
+		return Draci::optionsList;
+	}
+#endif
+
 	bool hasFeature(MetaEngineFeature f) const override;
 	int getMaximumSaveSlot() const override { return 99; }
 	SaveStateList listSaves(const char *target) const override;
diff --git a/engines/draci/script.cpp b/engines/draci/script.cpp
index c4e56fb22a6..948602f0edf 100644
--- a/engines/draci/script.cpp
+++ b/engines/draci/script.cpp
@@ -732,7 +732,16 @@ void Script::talk(const Common::Array<int> &params) {
 	// Set the string and text color
 	surface->markDirtyRect(speechFrame->getRect(kNoDisplacement));
 	if (_vm->_sound->showSubtitles() || !sample) {
-		speechFrame->setText(Common::String((const char *)f->_data+1, f->_length-1));
+		Common::String text = Common::String((const char *)f->_data+1, f->_length-1);
+		speechFrame->setText(text);
+
+		_vm->setTTSVoice(personID);
+		// Count credits as objects for TTS purposes
+		if (_vm->_game->getMapID() == kCreditsMapID) {
+			_vm->sayText(text, false);
+		} else if (!sample) {
+			_vm->sayText(text, true);
+		}
 	} else {
 		speechFrame->setText("");
 	}
@@ -784,6 +793,7 @@ void Script::talk(const Common::Array<int> &params) {
 	// Delete the text
 	_vm->_screen->getSurface()->markDirtyRect(speechFrame->getRect(kNoDisplacement));
 	speechFrame->setText("");
+	_vm->setTTSVoice(kBertID);
 
 	// Stop the playing sample and deallocate it.  Stopping should only be
 	// necessary if the user interrupts the playback.




More information about the Scummvm-git-logs mailing list