[Scummvm-git-logs] scummvm master -> 59eeb400cf9da62c890bfa5eb9936dab828fba75

sev- noreply at scummvm.org
Thu Aug 21 08:26:09 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:
59eeb400cf GOT: Add text-to-speech (TTS)


Commit: 59eeb400cf9da62c890bfa5eb9936dab828fba75
    https://github.com/scummvm/scummvm/commit/59eeb400cf9da62c890bfa5eb9936dab828fba75
Author: ellm135 (ellm13531 at gmail.com)
Date: 2025-08-21T10:26:06+02:00

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

Changed paths:
    engines/got/data/thorinfo.cpp
    engines/got/data/thorinfo.h
    engines/got/detection.h
    engines/got/detection_tables.h
    engines/got/events.cpp
    engines/got/events.h
    engines/got/game/boss1.cpp
    engines/got/game/boss2.cpp
    engines/got/game/boss3.cpp
    engines/got/got.cpp
    engines/got/metaengine.cpp
    engines/got/views/credits.cpp
    engines/got/views/dialogs/high_scores.cpp
    engines/got/views/dialogs/say.cpp
    engines/got/views/dialogs/select_item.cpp
    engines/got/views/dialogs/select_option.cpp
    engines/got/views/dialogs/select_option.h
    engines/got/views/game_status.cpp
    engines/got/views/game_status.h
    engines/got/views/part_title.cpp
    engines/got/views/story.cpp
    engines/got/views/story.h
    engines/got/views/view.cpp
    engines/got/views/view.h


diff --git a/engines/got/data/thorinfo.cpp b/engines/got/data/thorinfo.cpp
index acca6cd7c13..e0abc2bc0b3 100644
--- a/engines/got/data/thorinfo.cpp
+++ b/engines/got/data/thorinfo.cpp
@@ -49,6 +49,11 @@ void ThorInfo::clear() {
 	_lastObject = 0;
 	_lastObjectName = nullptr;
 	_armor = 0;
+#ifdef USE_TTS
+	_previousJewels = -1;
+	_previousScore = -1;
+	_previousKeys = -1;
+#endif
 	Common::fill(_filler, _filler + 65, 0);
 }
 
@@ -85,6 +90,12 @@ void ThorInfo::sync(Common::Serializer &s) {
 	if (s.isLoading()) {
 		_objectName = (_object == 0) ? nullptr : OBJECT_NAMES[_object - 1];
 		_lastObjectName = (_lastObject == 0) ? nullptr : OBJECT_NAMES[_lastObject - 1];
+
+#ifdef USE_TTS
+		_previousJewels = -1;
+		_previousScore = -1;
+		_previousKeys = -1;
+#endif
 	}
 }
 
diff --git a/engines/got/data/thorinfo.h b/engines/got/data/thorinfo.h
index 4faff526bae..2c54c82100b 100644
--- a/engines/got/data/thorinfo.h
+++ b/engines/got/data/thorinfo.h
@@ -51,6 +51,11 @@ struct ThorInfo {
 	const char *_lastObjectName = nullptr;
 	byte _armor = 0;
 	byte _filler[65] = {};
+#ifdef USE_TTS
+	int _previousJewels = -1;
+	int _previousScore = -1;
+	int _previousKeys = -1;
+#endif
 
 	void clear();
 	void sync(Common::Serializer &s);
diff --git a/engines/got/detection.h b/engines/got/detection.h
index 369636106c6..628060a8b34 100644
--- a/engines/got/detection.h
+++ b/engines/got/detection.h
@@ -39,6 +39,7 @@ extern const PlainGameDescriptor gotGames[];
 extern const ADGameDescription gameDescriptions[];
 
 #define GAMEOPTION_ORIGINAL_SAVELOAD GUIO_GAMEOPTIONS1
+#define GAMEOPTION_TTS               GUIO_GAMEOPTIONS2
 
 } // End of namespace Got
 
diff --git a/engines/got/detection_tables.h b/engines/got/detection_tables.h
index 6557ef4cb6e..914a0c42f17 100644
--- a/engines/got/detection_tables.h
+++ b/engines/got/detection_tables.h
@@ -34,7 +34,7 @@ const ADGameDescription gameDescriptions[] = {
 		 Common::EN_ANY,
 		 Common::kPlatformDOS,
 		 ADGF_TESTING,
-		 GUIO1(GUIO_NOSPEECH)
+		 GUIO2(GUIO_NOSPEECH, GAMEOPTION_TTS)
 	},
 	{
 		"got",
@@ -43,7 +43,7 @@ const ADGameDescription gameDescriptions[] = {
 		 Common::EN_ANY,
 		 Common::kPlatformDOS,
 		 ADGF_TESTING | ADGF_DEMO,
-		 GUIO1(GUIO_NOSPEECH)
+		 GUIO2(GUIO_NOSPEECH, GAMEOPTION_TTS)
 	},
 	{
 		"got",
@@ -52,7 +52,7 @@ const ADGameDescription gameDescriptions[] = {
 		 Common::EN_ANY,
 		 Common::kPlatformDOS,
 		 ADGF_TESTING,
-		 GUIO1(GUIO_NOSPEECH)
+		 GUIO2(GUIO_NOSPEECH, GAMEOPTION_TTS)
 	},
 	{
 		"got",
@@ -61,7 +61,7 @@ const ADGameDescription gameDescriptions[] = {
 		 Common::EN_ANY,
 		 Common::kPlatformDOS,
 		 ADGF_TESTING | ADGF_DEMO,
-		 GUIO1(GUIO_NOSPEECH)
+		 GUIO2(GUIO_NOSPEECH, GAMEOPTION_TTS)
 	},
 
 	AD_TABLE_END_MARKER};
diff --git a/engines/got/events.cpp b/engines/got/events.cpp
index d2256760aa1..0645288d298 100644
--- a/engines/got/events.cpp
+++ b/engines/got/events.cpp
@@ -413,6 +413,9 @@ UIElement *UIElement::findViewGlobally(const Common::String &name) {
 void UIElement::close() {
 	assert(g_events->focusedView() == this);
 	g_events->popView();
+#ifdef USE_TTS
+	stopTextToSpeech();
+#endif
 }
 
 void UIElement::draw() {
@@ -484,4 +487,17 @@ void UIElement::timeout() {
 	redraw();
 }
 
+#ifdef USE_TTS
+
+void UIElement::stopTextToSpeech() {
+	Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+	if (ttsMan && ConfMan.getBool("tts_enabled") && ttsMan->isSpeaking()) {
+		ttsMan->stop();
+	}
+
+	_previousSaid.clear();
+}
+
+#endif
+
 } // namespace Got
diff --git a/engines/got/events.h b/engines/got/events.h
index 996a5f4e088..2c3c17a5455 100644
--- a/engines/got/events.h
+++ b/engines/got/events.h
@@ -87,6 +87,9 @@ protected:
 	Bounds _bounds;
 	bool _needsRedraw = true;
 	Common::String _name;
+#ifdef USE_TTS
+	Common::String _previousSaid;
+#endif
 
 protected:
 	/**
@@ -215,6 +218,13 @@ protected:
 		return false;
 	}
 
+#ifdef USE_TTS
+	/**
+	 * Stops TTS voicing and clears the previously spoken text
+	 */
+	void stopTextToSpeech();
+#endif
+
 public:
 	bool send(const MouseMoveMessage &msg) {
 		return msgMouseMove(msg);
diff --git a/engines/got/game/boss1.cpp b/engines/got/game/boss1.cpp
index 4990685b18f..6fc45b8bb16 100644
--- a/engines/got/game/boss1.cpp
+++ b/engines/got/game/boss1.cpp
@@ -278,6 +278,10 @@ void boss1ClosingSequence4() {
 	_G(scrn)._music = 4;
 	showLevel(BOSS_LEVEL1);
 
+#ifdef USE_TTS
+	_G(thorInfo)._previousScore = -1;
+#endif
+
 	playSound(ANGEL, true);
 	placeTile(18, 6, 148);
 	placeTile(19, 6, 202);
diff --git a/engines/got/game/boss2.cpp b/engines/got/game/boss2.cpp
index fa9c9e8c7b2..8be341c1d08 100644
--- a/engines/got/game/boss2.cpp
+++ b/engines/got/game/boss2.cpp
@@ -412,6 +412,10 @@ void boss2ClosingSequence4() {
 
 	showLevel(BOSS_LEVEL2);
 
+#ifdef USE_TTS
+	_G(thorInfo)._previousScore = -1;
+#endif
+
 	playSound(ANGEL, true);
 	placeTile(18, 10, 152);
 	placeTile(19, 10, 202);
diff --git a/engines/got/game/boss3.cpp b/engines/got/game/boss3.cpp
index 58cd3ad8b64..9f7f38af0be 100644
--- a/engines/got/game/boss3.cpp
+++ b/engines/got/game/boss3.cpp
@@ -548,6 +548,10 @@ void boss3ClosingSequence3() {
 	_G(scrn)._music = 6;
 	showLevel(BOSS_LEVEL3);
 
+#ifdef USE_TTS
+	_G(thorInfo)._previousScore = -1;
+#endif
+
 	_G(exitFlag) = 0;
 	musicPause();
 
diff --git a/engines/got/got.cpp b/engines/got/got.cpp
index a533eba14d3..7c55cbdc145 100644
--- a/engines/got/got.cpp
+++ b/engines/got/got.cpp
@@ -78,6 +78,14 @@ Common::Error GotEngine::run() {
 		initGame();
 	syncSoundSettings();
 
+#ifdef USE_TTS
+	Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+	if (ttsMan) {
+		ttsMan->enable(ConfMan.getBool("tts_enabled"));
+		ttsMan->setLanguage(ConfMan.get("language"));
+	}
+#endif
+
 	runGame();
 
 	return Common::kNoError;
diff --git a/engines/got/metaengine.cpp b/engines/got/metaengine.cpp
index 2024fa7b045..33048fbebc1 100644
--- a/engines/got/metaengine.cpp
+++ b/engines/got/metaengine.cpp
@@ -40,6 +40,21 @@ static const ADExtraGuiOptionsMap optionsList[] = {
             0
         }
     },
+
+#ifdef USE_TTS
+    {
+        GAMEOPTION_TTS,
+        {
+            _s("Enable Text to Speech"),
+            _s("Use TTS to read text in the game (if TTS is available)"),
+            "tts_enabled",
+            false,
+            0,
+            0
+        }
+    },
+#endif
+
     AD_EXTRA_GUI_OPTIONS_TERMINATOR
 };
 
diff --git a/engines/got/views/credits.cpp b/engines/got/views/credits.cpp
index 8204c8e565e..ea13a51c3d3 100644
--- a/engines/got/views/credits.cpp
+++ b/engines/got/views/credits.cpp
@@ -25,6 +25,22 @@
 namespace Got {
 namespace Views {
 
+#ifdef USE_TTS
+
+static const char *creditsText[] = {
+	"Programming: Ron Davis",
+	"Graphics: Gary Sirois",
+	"Level Design: Adam Pedersen",
+	"Additional Programming: Jason Blochowiak",
+	"Music: Roy Davis",
+	"Title Screen Art: Wayne C. Timmerman",
+	"Additional Level Design: Ron Davis, Doug Howell, Ken Heckbert, Evan Heckbert",
+	"Play Testing: Ken Heckbert, Doug Howell, Tom King",
+	"Play Testing: Kelly Rogers, Michael Smith, Rik Pierce"
+};
+
+#endif
+
 #define CREDITS_COUNT 9
 #define FADE_FRAMES 15
 #define DISPLAY_TIME 15
@@ -68,6 +84,10 @@ void Credits::draw() {
 		drawCredit(s, gfxNum2, gfxNum4, 16, 40 + 24);
 	}
 
+#ifdef USE_TTS
+	sayText(creditsText[creditNum]);
+#endif
+
 	s.markAllDirty();
 }
 
@@ -111,7 +131,12 @@ bool Credits::tick() {
 		if (_frameCtr == (CREDIT_TIME * CREDITS_COUNT) + 10) {
 			replaceView("HighScores", true, true);
 		} else {
-			++_frameCtr;
+#ifdef USE_TTS
+			// Pause credits progression until TTS is finished
+			Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+			if (_frameCtr % CREDIT_TIME < FADE_FRAMES + DISPLAY_TIME || !ttsMan || !ttsMan->isSpeaking())
+#endif
+				++_frameCtr;
 			redraw();
 		}
 	}
diff --git a/engines/got/views/dialogs/high_scores.cpp b/engines/got/views/dialogs/high_scores.cpp
index 26154660545..029aecdf65f 100644
--- a/engines/got/views/dialogs/high_scores.cpp
+++ b/engines/got/views/dialogs/high_scores.cpp
@@ -55,6 +55,9 @@ void HighScores::draw() {
 
 	const int titleStart = (s.w - title.size() * 8) / 2;
 	s.print(Common::Point(titleStart, 4), title, 54);
+#ifdef USE_TTS
+	sayText(title);
+#endif
 
 	for (int i = 0; i < 7; ++i) {
 		const HighScore &hs = _G(highScores)._scores[_currentArea - 1][i];
@@ -67,6 +70,11 @@ void HighScores::draw() {
 		s.print(Common::Point(15, 24 + i * 18), hs._name, 14);
 		Common::String score = Common::String::format("%d", hs._total);
 		s.print(Common::Point(275 - (score.size() * 8), 24 + i * 18), score, 14);
+
+#ifdef USE_TTS
+		sayText(Common::String::format("%s: %d", hs._name, hs._total), Common::TextToSpeechManager::QUEUE);
+		_previousSaid.clear();
+#endif
 	}
 }
 
@@ -125,7 +133,8 @@ void HighScores::goToNextArea() {
 }
 
 bool HighScores::tick() {
-	if (--_timeoutCtr < 0)
+	Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+	if (--_timeoutCtr < 0 && (!ttsMan || !ttsMan->isSpeaking()))
 		goToNextArea();
 
 	return true;
diff --git a/engines/got/views/dialogs/say.cpp b/engines/got/views/dialogs/say.cpp
index 6c368e9045f..e4afd5c24a1 100644
--- a/engines/got/views/dialogs/say.cpp
+++ b/engines/got/views/dialogs/say.cpp
@@ -69,6 +69,24 @@ void Say::draw() {
 	const char *endP = _content + _contentLength;
 	int color = 14;
 	int x = 20, lc = 0;
+#ifdef USE_TTS
+	// Voice all text immediately, so that TTS syncs better with the text appearing on screen
+	Common::String ttsMessage = _content;
+	int ttsMessageLc = 0;
+	for (uint i = 0; i < ttsMessage.size(); ++i) {
+		if (ttsMessage[i] == '\n') {
+			ttsMessageLc++;
+
+			if (ttsMessageLc > 4) {
+				ttsMessage.erase(i);
+				ttsMessage += "\n\nMore...";
+				break;
+			}
+		}
+	}
+
+	sayText(ttsMessage);
+#endif
 
 	while (p < endP) {
 		if (*p == '~' && Common::isXDigit(*(p + 1))) {
diff --git a/engines/got/views/dialogs/select_item.cpp b/engines/got/views/dialogs/select_item.cpp
index 3dd3c856dc6..25bd8b83e7d 100644
--- a/engines/got/views/dialogs/select_item.cpp
+++ b/engines/got/views/dialogs/select_item.cpp
@@ -45,6 +45,9 @@ void SelectItem::draw() {
 
 	if (_G(thorInfo)._inventory == 0) {
 		s.print(Common::Point(44, 52), "No Items Found", 14);
+#ifdef USE_TTS
+		sayText("No Items Found");
+#endif
 		return;
 	}
 
@@ -66,6 +69,10 @@ void SelectItem::draw() {
 	else
 		objName = _G(thorInfo)._objectName;
 
+#ifdef USE_TTS
+	sayText(objName);
+#endif
+
 	s.print(Common::Point((s.w - (strlen(objName) * 8)) / 2, 66), objName, 12);
 	s.frameRect(Common::Rect(26 + (_selectedItem * _HRZSP), 22,
 							 43 + (_selectedItem * _HRZSP), 42),
diff --git a/engines/got/views/dialogs/select_option.cpp b/engines/got/views/dialogs/select_option.cpp
index 6e8ddaa9981..17e3f968ce3 100644
--- a/engines/got/views/dialogs/select_option.cpp
+++ b/engines/got/views/dialogs/select_option.cpp
@@ -77,6 +77,16 @@ void SelectOption::draw() {
 	for (uint i = 0; i < _options.size(); ++i)
 		s.print(Common::Point(32, 28 + i * 16), _options[i], 14);
 
+#ifdef USE_TTS
+	if (!_titleVoiced) {
+		sayText(_title);
+		sayText(_options[_selectedItem], Common::TextToSpeechManager::QUEUE);
+		_titleVoiced = true;
+	} else {
+		sayText(_options[_selectedItem]);
+	}
+#endif
+
 	// Draw selection pointer
 	if (_smackCtr > 0) {
 		// Selecting an item
@@ -91,6 +101,11 @@ void SelectOption::draw() {
 bool SelectOption::msgFocus(const FocusMessage &msg) {
 	_selectedItem = 0;
 	_smackCtr = 0;
+
+#ifdef USE_TTS
+	_titleVoiced = false;
+#endif
+
 	return true;
 }
 
diff --git a/engines/got/views/dialogs/select_option.h b/engines/got/views/dialogs/select_option.h
index ee3d75309b5..8134bb70e47 100644
--- a/engines/got/views/dialogs/select_option.h
+++ b/engines/got/views/dialogs/select_option.h
@@ -39,6 +39,10 @@ private:
 	int _hammerFrame = 0;
 	int _smackCtr = 0;
 
+#ifdef USE_TTS
+	bool _titleVoiced = false;
+#endif
+
 protected:
 	int _selectedItem = 0;
 
diff --git a/engines/got/views/game_status.cpp b/engines/got/views/game_status.cpp
index 5a14c25af36..67b38362d36 100644
--- a/engines/got/views/game_status.cpp
+++ b/engines/got/views/game_status.cpp
@@ -61,6 +61,20 @@ void GameStatus::displayMagic(GfxSurface &s) {
 
 void GameStatus::displayJewels(GfxSurface &s) {
 	const Common::String str = Common::String::format("%d", _G(thorInfo)._jewels);
+
+#ifdef USE_TTS
+	if (_G(thorInfo)._previousJewels != _G(thorInfo)._jewels && _G(gameMode) != MODE_SCORE_INV) {
+		if (_lastStatusSpoken == kJewels) {
+			stopTextToSpeech();
+		}
+
+		sayText("Jewels: " + str, Common::TextToSpeechManager::QUEUE);
+
+		_G(thorInfo)._previousJewels = _G(thorInfo)._jewels;
+		_lastStatusSpoken = kJewels;
+	}
+#endif
+
 	int x;
 	if (str.size() == 1)
 		x = 70;
@@ -77,6 +91,19 @@ void GameStatus::displayScore(GfxSurface &s) {
 	const Common::String str = Common::String::format("%ld", _G(thorInfo)._score);
 	const int x = 276 - (str.size() * 8);
 
+#ifdef USE_TTS
+	if (_G(thorInfo)._previousScore != _G(thorInfo)._score && _G(gameMode) != MODE_SCORE_INV && _scoreCountdown == 0) {
+		if (_lastStatusSpoken == kScore) {
+			stopTextToSpeech();
+		}
+
+		sayText("Score: " + str, Common::TextToSpeechManager::QUEUE);
+
+		_G(thorInfo)._previousScore = _G(thorInfo)._score;
+		_lastStatusSpoken = kScore;
+	}
+#endif
+
 	s.fillRect(Common::Rect(223, 32, 279, 42), STAT_COLOR);
 	s.print(Common::Point(x, 32), str, 14);
 }
@@ -84,6 +111,19 @@ void GameStatus::displayScore(GfxSurface &s) {
 void GameStatus::displayKeys(GfxSurface &s) {
 	const Common::String str = Common::String::format("%d", _G(thorInfo)._keys);
 
+#ifdef USE_TTS
+	if (_G(thorInfo)._previousKeys != _G(thorInfo)._keys && _G(gameMode) != MODE_SCORE_INV) {
+		if (_lastStatusSpoken == kKeys) {
+			stopTextToSpeech();
+		}
+
+		sayText("Keys: " + str, Common::TextToSpeechManager::QUEUE);
+
+		_G(thorInfo)._previousKeys = _G(thorInfo)._keys;
+		_lastStatusSpoken = kKeys;
+	}
+#endif
+
 	int x;
 	if (str.size() == 1)
 		x = 150;
diff --git a/engines/got/views/game_status.h b/engines/got/views/game_status.h
index d48c2095c97..d94a7ed2fa7 100644
--- a/engines/got/views/game_status.h
+++ b/engines/got/views/game_status.h
@@ -27,6 +27,17 @@
 namespace Got {
 namespace Views {
 
+#ifdef USE_TTS
+
+enum LastStatusSpoken {
+	kNone = 0,
+	kJewels = 1,
+	kScore = 2,
+	kKeys = 3
+};
+
+#endif
+
 class GameStatus : public View {
 private:
 	int _scoreCountdown = 0;
@@ -39,6 +50,10 @@ private:
 	void displayKeys(GfxSurface &s);
 	void displayItem(GfxSurface &s);
 
+#ifdef USE_TTS
+	LastStatusSpoken _lastStatusSpoken = kNone;
+#endif
+
 public:
 	GameStatus() : View("GameStatus") {}
 	virtual ~GameStatus() {}
diff --git a/engines/got/views/part_title.cpp b/engines/got/views/part_title.cpp
index d63952c8e01..d34485f35c1 100644
--- a/engines/got/views/part_title.cpp
+++ b/engines/got/views/part_title.cpp
@@ -30,20 +30,35 @@ void PartTitle::draw() {
 	GfxSurface s = getSurface();
 	s.clear();
 	s.print(Common::Point(13 * 8, 13 * 8), "God of Thunder", 14);
+#ifdef USE_TTS
+	Common::String ttsMessage = "God of Thunder: ";
+#endif
 
 	switch (_G(area)) {
 	case 1:
 		s.print(Common::Point(8 * 8, 15 * 8), "Part I: Serpent Surprise", 32);
+#ifdef USE_TTS
+		ttsMessage += "Part 1: Serpent Surprise";
+#endif
 		break;
 	case 2:
 		s.print(Common::Point(7 * 8, 15 * 8), "Part II: Non-Stick Nognir", 32);
+#ifdef USE_TTS
+		ttsMessage += "Part 2: Non-Stick Nognir";
+#endif
 		break;
 	case 3:
 		s.print(Common::Point(7 * 8, 15 * 8), "Part III: Lookin' for Loki", 32);
+#ifdef USE_TTS
+		ttsMessage += "Part 3: Lookin' for Loki";
+#endif
 		break;
 	default:
 		break;
 	}
+#ifdef USE_TTS
+	sayText(ttsMessage);
+#endif
 }
 
 bool PartTitle::msgAction(const ActionMessage &msg) {
@@ -54,7 +69,8 @@ bool PartTitle::msgAction(const ActionMessage &msg) {
 }
 
 bool PartTitle::tick() {
-	if (++_timeoutCtr == 80) {
+	Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+	if (++_timeoutCtr >= 80 && (!ttsMan || !ttsMan->isSpeaking())) {
 		_timeoutCtr = 0;
 		done();
 	}
@@ -63,6 +79,9 @@ bool PartTitle::tick() {
 }
 
 void PartTitle::done() {
+#ifdef USE_TTS
+	stopTextToSpeech();
+#endif
 	replaceView("Game", true, true);
 }
 
diff --git a/engines/got/views/story.cpp b/engines/got/views/story.cpp
index f4d7205c072..38f2fdd3e12 100644
--- a/engines/got/views/story.cpp
+++ b/engines/got/views/story.cpp
@@ -60,6 +60,10 @@ bool Story::msgFocus(const FocusMessage &msg) {
 
 	const char *p = (const char *)_G(tmpBuff);
 
+#ifdef USE_TTS
+	_ttsMessage.clear();
+#endif
+
 	while (i < 46) {
 		if (*p == '\n') {
 			x = 8;
@@ -69,7 +73,13 @@ bool Story::msgFocus(const FocusMessage &msg) {
 			if (i == 23) {
 				// Move to start of of "second page" of the surface
 				y = 240 + 2;
+#ifdef USE_TTS
+				_secondPageIndex = _ttsMessage.size() - 1;
+#endif
 			}
+#ifdef USE_TTS
+			_ttsMessage += '\n';
+#endif
 		} else if (*p == '/' && *(p + 4) == '/') {
 			p++;
 			s[0] = *p++;
@@ -88,11 +98,19 @@ bool Story::msgFocus(const FocusMessage &msg) {
 			_surface.rawPrintChar(*p, x + 1, y, 255);
 			_surface.rawPrintChar(*p, x, y, color);
 			x += 8;
+
+#ifdef USE_TTS
+			_ttsMessage += *p;
+#endif
 		}
 
 		p++;
 	}
 
+#ifdef USE_TTS
+	sayText(_ttsMessage.substr(0, _secondPageIndex));
+#endif
+
 	// Final two glyphs
 	Gfx::Pics glyphs("STORYPIC", 262);
 
@@ -130,9 +148,12 @@ void Story::draw() {
 bool Story::msgAction(const ActionMessage &msg) {
 	if (msg._action == KEYBIND_ESCAPE || _yp == MAX_Y)
 		done();
-	else if (!_scrolling)
+	else if (!_scrolling) {
+#ifdef USE_TTS
+		sayText(_ttsMessage.substr(_secondPageIndex));
+#endif
 		_scrolling = true;
-	else
+	} else
 		_yp = MAX_Y;
 
 	return true;
diff --git a/engines/got/views/story.h b/engines/got/views/story.h
index e528ef0ec08..7d0f302b2e8 100644
--- a/engines/got/views/story.h
+++ b/engines/got/views/story.h
@@ -35,6 +35,11 @@ private:
 
 	void done();
 
+#ifdef USE_TTS
+	Common::String _ttsMessage;
+	uint _secondPageIndex = 0;
+#endif
+
 public:
 	Story() : View("Story") {}
 	virtual ~Story() {}
diff --git a/engines/got/views/view.cpp b/engines/got/views/view.cpp
index df75a31e293..3d519781b93 100644
--- a/engines/got/views/view.cpp
+++ b/engines/got/views/view.cpp
@@ -19,6 +19,7 @@
  *
  */
 
+#include "common/config-manager.h"
 #include "got/views/view.h"
 #include "got/gfx/palette.h"
 #include "got/vars.h"
@@ -120,5 +121,52 @@ void View::fadeIn(const byte *pal) {
 	Gfx::fadeIn(pal);
 }
 
+#ifdef USE_TTS
+
+void View::sayText(const Common::String &text, Common::TextToSpeechManager::Action action) {
+	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 options in selection menus are voiced. Without it when the text ends it would speak
+	// the same text again.
+	// _previousSaid is cleared when appropriate to allow for repeat requests
+	if (ttsMan && ConfMan.getBool("tts_enabled") && _previousSaid != text) {
+		Common::String ttsMessage;
+
+		for (uint i = 0; i < text.size(); ++i) {
+			// Some text is enclosed in < and >, which causes the text to not be voiced by TTS if they aren't replaced
+			if (text[i] == '<' || text[i] == '>') {
+				ttsMessage += ", ";
+				continue;
+			}
+
+			// Ignore color changing characters
+			if (text[i] == '~' && i < text.size() - 1 && Common::isXDigit(text[i + 1])) {
+				i++;
+				continue;
+			}
+
+			// Replace single newlines with spaces to make voicing of dialog smoother. If there are two or more newlines in a row,
+			// then the pieces of text are most likely supposed to be voiced as separate sentences, so keep the newlines in
+			// that case
+			if (text[i] == '\n') {
+				if (i < text.size() - 1 && text[i + 1] == '\n') {
+					i++;
+				} else {
+					ttsMessage += ' ';
+					continue;
+				}
+			}
+
+			ttsMessage += text[i];
+		}
+
+		ttsMan->say(ttsMessage, action, Common::CodePage::kDos850);
+		_previousSaid = text;
+	}
+}
+
+#endif
+
 } // namespace Views
 } // namespace Got
diff --git a/engines/got/views/view.h b/engines/got/views/view.h
index b0e213f63db..d70e73967df 100644
--- a/engines/got/views/view.h
+++ b/engines/got/views/view.h
@@ -22,6 +22,7 @@
 #ifndef GOT_VIEWS_VIEW_H
 #define GOT_VIEWS_VIEW_H
 
+#include "common/text-to-speech.h"
 #include "got/events.h"
 #include "got/gfx/gfx_chunks.h"
 
@@ -67,6 +68,10 @@ protected:
 	void fadeOut();
 	void fadeIn(const byte *pal = nullptr);
 
+#ifdef USE_TTS
+	void sayText(const Common::String &text, Common::TextToSpeechManager::Action action = Common::TextToSpeechManager::INTERRUPT);
+#endif
+
 public:
 	View(const Common::String &name, UIElement *uiParent) : UIElement(name, uiParent) {}
 	View(const Common::String &name) : UIElement(name) {}




More information about the Scummvm-git-logs mailing list