[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> ¶ms) {
// 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> ¶ms) {
// 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