[Scummvm-git-logs] scummvm master -> 0739ef0fbda0d597ff581e17d0ca3879f2a38346

criezy noreply at scummvm.org
Wed Aug 27 21:21:08 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:
0739ef0fbd HUGO: Add text-to-speech (TTS)


Commit: 0739ef0fbda0d597ff581e17d0ca3879f2a38346
    https://github.com/scummvm/scummvm/commit/0739ef0fbda0d597ff581e17d0ca3879f2a38346
Author: ellm135 (ellm13531 at gmail.com)
Date: 2025-08-27T22:21:04+01:00

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

Changed paths:
    engines/hugo/detection.cpp
    engines/hugo/detection.h
    engines/hugo/display.cpp
    engines/hugo/file.cpp
    engines/hugo/hugo.cpp
    engines/hugo/hugo.h
    engines/hugo/intro.cpp
    engines/hugo/metaengine.cpp
    engines/hugo/mouse.cpp
    engines/hugo/parser.cpp
    engines/hugo/schedule.cpp
    engines/hugo/sound.cpp
    engines/hugo/util.cpp
    engines/hugo/util.h


diff --git a/engines/hugo/detection.cpp b/engines/hugo/detection.cpp
index e6e79f852d7..e6a2ea27f99 100644
--- a/engines/hugo/detection.cpp
+++ b/engines/hugo/detection.cpp
@@ -59,7 +59,7 @@ static const HugoGameDescription gameDescriptions[] = {
 			Common::EN_ANY,
 			Common::kPlatformDOS,
 			ADGF_NO_FLAGS,
-			GUIO0()
+			GUIO1(GAMEOPTION_TTS)
 		},
 		kGameTypeHugo1
 	},
@@ -71,7 +71,7 @@ static const HugoGameDescription gameDescriptions[] = {
 			Common::EN_ANY,
 			Common::kPlatformWindows,
 			GF_PACKED,
-			GUIO0()
+			GUIO1(GAMEOPTION_TTS)
 		},
 		kGameTypeHugo1
 	},
@@ -83,7 +83,7 @@ static const HugoGameDescription gameDescriptions[] = {
 			Common::EN_ANY,
 			Common::kPlatformDOS,
 			GF_PACKED,
-			GUIO0()
+			GUIO1(GAMEOPTION_TTS)
 		},
 		kGameTypeHugo2
 	},
@@ -95,7 +95,7 @@ static const HugoGameDescription gameDescriptions[] = {
 			Common::EN_ANY,
 			Common::kPlatformWindows,
 			GF_PACKED,
-			GUIO0()
+			GUIO1(GAMEOPTION_TTS)
 		},
 		kGameTypeHugo2
 	},
@@ -107,7 +107,7 @@ static const HugoGameDescription gameDescriptions[] = {
 			Common::EN_ANY,
 			Common::kPlatformDOS,
 			GF_PACKED,
-			GUIO0()
+			GUIO1(GAMEOPTION_TTS)
 		},
 		kGameTypeHugo3
 	},
@@ -119,7 +119,7 @@ static const HugoGameDescription gameDescriptions[] = {
 			Common::EN_ANY,
 			Common::kPlatformWindows,
 			GF_PACKED,
-			GUIO0()
+			GUIO1(GAMEOPTION_TTS)
 		},
 		kGameTypeHugo3
 	},
diff --git a/engines/hugo/detection.h b/engines/hugo/detection.h
index 2714f40bd6e..19faef9af1a 100644
--- a/engines/hugo/detection.h
+++ b/engines/hugo/detection.h
@@ -54,6 +54,8 @@ struct HugoGameDescription {
 	GameType gameType;
 };
 
+#define GAMEOPTION_TTS               GUIO_GAMEOPTIONS1
+
 } // End of namespace Hugo
 
 #endif // HUGO_DETECTION_H
diff --git a/engines/hugo/display.cpp b/engines/hugo/display.cpp
index ff5a3242930..e0a2943d7e2 100644
--- a/engines/hugo/display.cpp
+++ b/engines/hugo/display.cpp
@@ -73,6 +73,10 @@ static const byte stdMouseCursor[] = {
 	1, 1,  1,  1,  1,  1,  1,  1,  0,  0,  1, 1
 };
 
+#ifdef USE_TTS
+static const uint8 kHelpFirstNewlineIndex = 20;
+#endif
+
 
 Screen::Screen(HugoEngine *vm) : _vm(vm) {
 	_mainPalette = nullptr;
@@ -483,17 +487,25 @@ void Screen::shadowStr(int16 sx, const int16 sy, const char *s, const byte color
  * present in the DOS versions
  */
 void Screen::userHelp() const {
-	Utils::notifyBox(
-	           "F1  - Press F1 again\n"
-	           "      for instructions\n"
-	           "F2  - Sound on/off\n"
-	           "F3  - Recall last line\n"
-	           "F4  - Save game\n"
-	           "F5  - Restore game\n"
-	           "F6  - Inventory\n"
-	           "F8  - Turbo button\n"
-	           "\n"
-	           "ESC - Return to game");
+	Common::String message = "F1  - Press F1 again\n"
+	                         "      for instructions\n"
+	                         "F2  - Sound on/off\n"
+	                         "F3  - Recall last line\n"
+	                         "F4  - Save game\n"
+	                         "F5  - Restore game\n"
+	                         "F6  - Inventory\n"
+	                         "F8  - Turbo button\n"
+	                         "\n"
+	                         "ESC - Return to game";
+
+#ifdef USE_TTS
+	// Replace the first newline with a space for smoother voicing
+	message[kHelpFirstNewlineIndex] = ' ';
+	Utils::sayText(message, Common::TextToSpeechManager::INTERRUPT, false);
+	message[kHelpFirstNewlineIndex] = '\n';
+#endif
+
+	Utils::notifyBox(message, false);
 }
 
 void Screen::drawStatusText() {
diff --git a/engines/hugo/file.cpp b/engines/hugo/file.cpp
index 86d1cfdd2ce..c1fe3590bc2 100644
--- a/engines/hugo/file.cpp
+++ b/engines/hugo/file.cpp
@@ -450,6 +450,9 @@ bool FileManager::restoreGame(const int16 slot) {
 
 	int score = in->readSint16BE();
 	_vm->setScore(score);
+#ifdef USE_TTS
+	_vm->_previousScore = -1;
+#endif
 
 	gameStatus._storyModeFl = (in->readByte() == 1);
 	_vm->_mouse->setJumpExitFl(in->readByte() == 1);
diff --git a/engines/hugo/hugo.cpp b/engines/hugo/hugo.cpp
index fada0d9c0cd..3f0a30b4bb7 100644
--- a/engines/hugo/hugo.cpp
+++ b/engines/hugo/hugo.cpp
@@ -106,6 +106,13 @@ HugoEngine::HugoEngine(OSystem *syst, const HugoGameDescription *gd) : Engine(sy
 	_boot._exitLen = 0;
 	_file = nullptr;
 	_scheduler = nullptr;
+
+#ifdef USE_TTS
+	_queueAllVoicing = false;
+	_voiceScoreLine = true;
+	_voiceSoundSetting = false;
+	_previousScore = 0;
+#endif
 }
 
 HugoEngine::~HugoEngine() {
@@ -283,6 +290,14 @@ Common::Error HugoEngine::run() {
 
 	_scheduler->initCypher();
 
+#ifdef USE_TTS
+	Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+	if (ttsMan) {
+		ttsMan->enable(ConfMan.getBool("tts_enabled"));
+		ttsMan->setLanguage(ConfMan.get("language"));
+	}
+#endif
+
 	initStatus();                                   // Initialize game status
 	initConfig();                                   // Initialize user's config
 	if (!_status._doQuitFl) {
@@ -712,7 +727,7 @@ void HugoEngine::endGame() {
 
 	if (_boot._registered != kRegRegistered)
 		Utils::notifyBox(_text->getTextEngine(kEsAdvertise));
-	Utils::notifyBox(Common::String::format("%s\n%s", _episode, getCopyrightString()));
+	Utils::notifyBox(Common::String::format("%s\n%s", _episode, getCopyrightString()), true, false);
 	_status._viewState = kViewExit;
 }
 
@@ -738,4 +753,26 @@ Common::String HugoEngine::getSaveStateName(int slot) const {
 	return _targetName + Common::String::format("-%02d.SAV", slot);
 }
 
+#ifdef USE_TTS
+
+void HugoEngine::sayText(const Common::String &text, Common::TextToSpeechManager::Action action) {
+	// _previousSaid is used to prevent the TTS from looping when sayText is called inside a loop,
+	// for example when the user hovers over an object. Without it when the text ends it would speak
+	// the same text again.
+	// _previousSaid is cleared when appropriate to allow for repeat requests
+	if (_previousSaid != text) {
+		Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+		if (ttsMan && ttsMan->isSpeaking() && _queueAllVoicing) {
+			Utils::sayText(text, Common::TextToSpeechManager::QUEUE, false);
+		} else {
+			Utils::sayText(text, action, false);
+			_queueAllVoicing = false;
+		}
+
+		_previousSaid = text;
+	}
+}
+
+#endif
+
 } // End of namespace Hugo
diff --git a/engines/hugo/hugo.h b/engines/hugo/hugo.h
index e43497e8c7b..c17dea64782 100644
--- a/engines/hugo/hugo.h
+++ b/engines/hugo/hugo.h
@@ -22,6 +22,8 @@
 #ifndef HUGO_HUGO_H
 #define HUGO_HUGO_H
 
+#include "common/text-to-speech.h"
+
 #include "engines/engine.h"
 
 // This include is here temporarily while the engine is being refactored.
@@ -245,6 +247,15 @@ public:
 	Command _statusLine;
 	Command _scoreLine;
 
+#ifdef USE_TTS
+	bool _voiceScoreLine;
+	bool _voiceSoundSetting;
+	bool _queueAllVoicing;
+	int _previousScore;
+
+	Common::String _previousSaid;
+#endif
+
 	const HugoGameDescription *_gameDescription;
 	uint32 getFeatures() const;
 	const char *getGameId() const;
@@ -289,6 +300,10 @@ public:
 	Common::String getSaveStateName(int slot) const override;
 	uint16 **loadLongArray(Common::SeekableReadStream &in);
 
+#ifdef USE_TTS
+	void sayText(const Common::String &text, Common::TextToSpeechManager::Action action = Common::TextToSpeechManager::INTERRUPT);
+#endif
+
 	FileManager *_file;
 	Scheduler *_scheduler;
 	Screen *_screen;
diff --git a/engines/hugo/intro.cpp b/engines/hugo/intro.cpp
index f415a808efe..943f76018c4 100644
--- a/engines/hugo/intro.cpp
+++ b/engines/hugo/intro.cpp
@@ -104,6 +104,14 @@ bool intro_v1d::introPlay() {
 	if (_vm->getGameStatus()._skipIntroFl)
 		return true;
 
+#ifdef USE_TTS
+	Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+	if (ttsMan && ttsMan->isSpeaking()) {
+		return false;
+	}
+
+	Common::String ttsMessage;
+#endif
 	if (_introTicks < introSize) {
 		switch (_introState++) {
 		case 0:
@@ -132,9 +140,21 @@ bool intro_v1d::introPlay() {
 			_font.drawString(&_surf, buffer, 0, 163, 320, _TLIGHTMAGENTA, Graphics::kTextAlignCenter);
 			_font.drawString(&_surf, _vm->getCopyrightString(), 0, 176, 320, _TLIGHTMAGENTA, Graphics::kTextAlignCenter);
 
+#ifdef USE_TTS
+			ttsMessage = "Hugo's House of Horrors\n\n";
+			ttsMessage += buffer;
+			ttsMessage += '\n';
+			ttsMessage += _vm->getCopyrightString();
+#endif
+
 			if ((*_vm->_boot._distrib != '\0') && (scumm_stricmp(_vm->_boot._distrib, "David P. Gray"))) {
 				Common::sprintf_s(buffer, "Distributed by %s.", _vm->_boot._distrib);
 				_font.drawString(&_surf, buffer, 0, 75, 320, _TMAGENTA, Graphics::kTextAlignCenter);
+#ifdef USE_TTS
+				// Insert at second newline after "Hugo's House of Horrors" so that a newline remains between that line
+				// and this one
+				ttsMessage.insertString(buffer, ttsMessage.find('\n') + 1);
+#endif
 			}
 
 			// SCRIPT, size 24-16
@@ -164,6 +184,9 @@ bool intro_v1d::introPlay() {
 
 			Common::strcpy_s(buffer, "S t a r r i n g :");
 			_font.drawString(&_surf, buffer, 0, 95, 320, _TMAGENTA, Graphics::kTextAlignCenter);
+#ifdef USE_TTS
+			ttsMessage = "Starring:";
+#endif
 			break;
 		case 3:
 			// TROMAN, size 20-9
@@ -172,6 +195,9 @@ bool intro_v1d::introPlay() {
 
 			Common::strcpy_s(buffer, "Hugo !");
 			_font.drawString(&_surf, buffer, 0, 115, 320, _TLIGHTMAGENTA, Graphics::kTextAlignCenter);
+#ifdef USE_TTS
+			ttsMessage = buffer;
+#endif
 			break;
 		case 4:
 			_vm->_screen->drawRectangle(true, 82, 92, 237, 138, _TBLACK);
@@ -182,11 +208,17 @@ bool intro_v1d::introPlay() {
 
 			Common::strcpy_s(buffer, "P r o d u c e d  b y :");
 			_font.drawString(&_surf, buffer, 0, 95, 320, _TMAGENTA, Graphics::kTextAlignCenter);
+#ifdef USE_TTS
+			ttsMessage = "Produced by:";
+#endif
 			break;
 		case 5:
 			// TROMAN size 16-9
 			Common::strcpy_s(buffer, "David P Gray !");
 			_font.drawString(&_surf, buffer, 0, 115, 320, _TLIGHTMAGENTA, Graphics::kTextAlignCenter);
+#ifdef USE_TTS
+			ttsMessage = buffer;
+#endif
 			break;
 		case 6:
 			_vm->_screen->drawRectangle(true, 82, 92, 237, 138, _TBLACK);
@@ -194,11 +226,17 @@ bool intro_v1d::introPlay() {
 			// TROMAN, size 16-9
 			Common::strcpy_s(buffer, "D i r e c t e d   b y :");
 			_font.drawString(&_surf, buffer, 0, 95, 320, _TMAGENTA, Graphics::kTextAlignCenter);
+#ifdef USE_TTS
+			ttsMessage = "Directed by:";
+#endif
 			break;
 		case 7:
 			// TROMAN, size 16-9
 			Common::strcpy_s(buffer, "David P Gray !");
 			_font.drawString(&_surf, buffer, 0, 115, 320, _TLIGHTMAGENTA, Graphics::kTextAlignCenter);
+#ifdef USE_TTS
+			ttsMessage = buffer;
+#endif
 			break;
 		case 8:
 			_vm->_screen->drawRectangle(true, 82, 92, 237, 138, _TBLACK);
@@ -206,11 +244,17 @@ bool intro_v1d::introPlay() {
 			// TROMAN, size 16-9
 			Common::strcpy_s(buffer, "M u s i c   b y :");
 			_font.drawString(&_surf, buffer, 0, 95, 320, _TMAGENTA, Graphics::kTextAlignCenter);
+#ifdef USE_TTS
+			ttsMessage = "Music by:";
+#endif
 			break;
 		case 9:
 			// TROMAN, size 16-9
 			Common::strcpy_s(buffer, "David P Gray !");
 			_font.drawString(&_surf, buffer, 0, 115, 320, _TLIGHTMAGENTA, Graphics::kTextAlignCenter);
+#ifdef USE_TTS
+			ttsMessage = buffer;
+#endif
 			break;
 		case 10:
 			_vm->_screen->drawRectangle(true, 82, 92, 237, 138, _TBLACK);
@@ -221,11 +265,18 @@ bool intro_v1d::introPlay() {
 
 			Common::strcpy_s(buffer, "E n j o y !");
 			_font.drawString(&_surf, buffer, 0, 100, 320, _TLIGHTMAGENTA, Graphics::kTextAlignCenter);
+#ifdef USE_TTS
+			ttsMessage = "Enjoy!";
+#endif
 			break;
 		default:
 			break;
 		}
 
+#ifdef USE_TTS
+		_vm->sayText(ttsMessage);
+#endif
+
 		_vm->_screen->displayBackground();
 		g_system->updateScreen();
 		g_system->delayMillis(1000);
@@ -261,15 +312,34 @@ void intro_v2d::introInit() {
 
 	_font.drawString(&_surf, buffer, 0, 186, 320, _TLIGHTRED, Graphics::kTextAlignCenter);
 
+#ifdef USE_TTS
+	Common::String ttsMessage = buffer;
+#endif
+
 	if ((*_vm->_boot._distrib != '\0') && (scumm_stricmp(_vm->_boot._distrib, "David P. Gray"))) {
 		// TROMAN, size 10-5
 		Common::sprintf_s(buffer, "Distributed by %s.", _vm->_boot._distrib);
 		_font.drawString(&_surf, buffer, 0, 1, 320, _TLIGHTRED, Graphics::kTextAlignCenter);
+
+#ifdef USE_TTS
+		ttsMessage = buffer + ttsMessage;
+#endif
 	}
 
+#ifdef USE_TTS
+	_vm->sayText(ttsMessage);
+#endif
+
 	_vm->_screen->displayBackground();
 	g_system->updateScreen();
 	g_system->delayMillis(5000);
+
+#ifdef USE_TTS
+	Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+	while (ttsMan && ttsMan->isSpeaking()) {
+		g_system->delayMillis(10);
+	}
+#endif
 }
 
 bool intro_v2d::introPlay() {
@@ -302,15 +372,34 @@ void intro_v3d::introInit() {
 
 	_font.drawString(&_surf, buffer, 0, 190, 320, _TBROWN, Graphics::kTextAlignCenter);
 
+#ifdef USE_TTS
+	Common::String ttsMessage = buffer;
+#endif
+
 	if ((*_vm->_boot._distrib != '\0') && (scumm_stricmp(_vm->_boot._distrib, "David P. Gray"))) {
 		Common::sprintf_s(buffer, "Distributed by %s.", _vm->_boot._distrib);
 		_font.drawString(&_surf, buffer, 0, 0, 320, _TBROWN, Graphics::kTextAlignCenter);
+
+#ifdef USE_TTS
+		ttsMessage = buffer + ttsMessage;
+#endif
 	}
 
+#ifdef USE_TTS
+	_vm->sayText(ttsMessage);
+#endif
+
 	_vm->_screen->displayBackground();
 	g_system->updateScreen();
 	g_system->delayMillis(5000);
 
+#ifdef USE_TTS
+	Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+	while (ttsMan && ttsMan->isSpeaking()) {
+		g_system->delayMillis(10);
+	}
+#endif
+
 	_vm->_file->readBackground(22); // display screen MAP_3d
 	_vm->_screen->displayBackground();
 	_introTicks = 0;
diff --git a/engines/hugo/metaengine.cpp b/engines/hugo/metaengine.cpp
index e1e572a6c09..bb842185fd0 100644
--- a/engines/hugo/metaengine.cpp
+++ b/engines/hugo/metaengine.cpp
@@ -36,6 +36,26 @@
 
 namespace Hugo {
 
+#ifdef USE_TTS
+
+static const ADExtraGuiOptionsMap optionsList[] = {
+	{
+		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
+		}
+	},
+
+	AD_EXTRA_GUI_OPTIONS_TERMINATOR
+};
+
+#endif
+
 uint32 HugoEngine::getFeatures() const {
 	return _gameDescription->desc.flags;
 }
@@ -50,6 +70,12 @@ public:
 		return "hugo";
 	}
 
+#ifdef USE_TTS
+	const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override {
+		return optionsList;
+	}
+#endif
+
 	Common::Error createInstance(OSystem *syst, Engine **engine, const HugoGameDescription *gd) const override;
 	bool hasFeature(MetaEngineFeature f) const override;
 
diff --git a/engines/hugo/mouse.cpp b/engines/hugo/mouse.cpp
index 48cba2e240c..8a0717f8d67 100644
--- a/engines/hugo/mouse.cpp
+++ b/engines/hugo/mouse.cpp
@@ -142,6 +142,10 @@ void MouseHandler::cursorText(const char *buffer, const int16 cx, const int16 cy
 	// Display the string and add rect to display list
 	_vm->_screen->shadowStr(sx, sy, buffer, _TBRIGHTWHITE);
 	_vm->_screen->displayList(kDisplayAdd, sx, sy, sdx, sdy);
+
+#ifdef USE_TTS
+	_vm->sayText(buffer);
+#endif
 }
 
 /**
@@ -334,6 +338,11 @@ void MouseHandler::mouseHandler() {
 			const char *name = _vm->_text->getNoun(_vm->_object->_objects[(objId == kHeroIndex) ? _vm->_heroImage : objId]._nounIndex, kCursorNameIndex);
 			if (name[0] != kCursorNochar)
 				cursorText(name, cx, cy, U_FONT8, _TBRIGHTWHITE);
+#ifdef USE_TTS
+			else {
+				_vm->_previousSaid.clear();
+			}
+#endif
 
 			// Process right click over object in view or iconbar
 			if (_rightButtonFl)
@@ -347,6 +356,11 @@ void MouseHandler::mouseHandler() {
 				objId = kExitHotspot;
 				cursorText(_vm->_text->getTextMouse(kMsExit), cx, cy, U_FONT8, _TBRIGHTWHITE);
 			}
+#ifdef USE_TTS
+			else {
+				_vm->_previousSaid.clear();
+			}
+#endif
 		}
 	}
 	// Left click over icon, object or to move somewhere
diff --git a/engines/hugo/parser.cpp b/engines/hugo/parser.cpp
index 632d5317e1b..466f0b9feb4 100644
--- a/engines/hugo/parser.cpp
+++ b/engines/hugo/parser.cpp
@@ -40,6 +40,7 @@
 #include "hugo/object.h"
 #include "hugo/text.h"
 #include "hugo/inventory.h"
+#include "hugo/mouse.h"
 
 namespace Hugo {
 
@@ -208,6 +209,12 @@ void Parser::freeParser() {
 
 void Parser::switchTurbo() {
 	_vm->_config._turboFl = !_vm->_config._turboFl;
+
+#ifdef USE_TTS
+	if (_vm->_config._turboFl) {
+		_vm->sayText("T");
+	}
+#endif
 }
 
 /**
@@ -235,6 +242,10 @@ void Parser::charHandler() {
 				// Remove inventory bar if active
 				if (_vm->_inventory->getInventoryState() == kInventoryActive)
 					_vm->_inventory->setInventoryState(kInventoryUp);
+#ifdef USE_TTS
+				_vm->sayText(_cmdLine);
+				_vm->_previousSaid.clear();
+#endif
 				// Call Line handler and reset line
 				command(_cmdLine);
 				_cmdLine[_cmdLineIndex = 0] = '\0';
@@ -267,6 +278,30 @@ void Parser::charHandler() {
 	Common::sprintf_s(_vm->_statusLine, ">%s%c", _cmdLine, _cmdLineCursor);
 	Common::sprintf_s(_vm->_scoreLine, "F1-Help  %s  Score: %d of %d Sound %s", (_vm->_config._turboFl) ? "T" : " ", _vm->getScore(), _vm->getMaxScore(), (_vm->_config._soundFl) ? "On" : "Off");
 
+#ifdef USE_TTS
+	if (_vm->_previousScore != _vm->getScore()) {
+		_vm->sayText(Common::String::format("Score: %d of %d", _vm->getScore(), _vm->getMaxScore()));
+		_vm->_previousScore = _vm->getScore();
+	}
+
+	if (_vm->_voiceScoreLine) {
+		_vm->sayText(Common::String::format("F1: Help\n%s\nScore: %d of %d\nSound %s", (_vm->_config._turboFl) ? "T" : " ", _vm->getScore(), _vm->getMaxScore(), (_vm->_config._soundFl) ? "On" : "Off"));
+		_vm->_voiceScoreLine = false;
+	}
+
+	if (_vm->_voiceSoundSetting) {
+		_vm->sayText(Common::String::format("Sound %s", (_vm->_config._soundFl) ? "On" : "Off"));
+
+		// If the mouse is in the top menu range, the top menu will close and reopen after the sound setting is changed,
+		// causing the new sound setting voicing to be interrupted. Therefore, keep trying to voice the new sound setting
+		// as long as the top menu can be opened
+		int mouseY = _vm->_mouse->getMouseY();
+		if (mouseY <= 0 || mouseY >= 5) {
+			_vm->_voiceSoundSetting = false;
+		}
+	}
+#endif
+
 	// See if "look" button pressed
 	if (gameStatus._lookFl) {
 		command("look around");
@@ -452,18 +487,32 @@ void Parser::showDosInventory() const {
 
 	buffer += Common::String(_vm->_text->getTextParser(kTBIntro)) + "\n";
 	index = 0;
+#ifdef USE_TTS
+	Common::String ttsMessage = buffer;
+#endif
 	for (int i = 0; i < _vm->_object->_numObj; i++) { // Assign strings
 		if (_vm->_object->isCarried(i)) {
-			if (index++ & 1)
+			if (index++ & 1) {
 				buffer += Common::String(_vm->_text->getNoun(_vm->_object->_objects[i]._nounIndex, 2)) + "\n";
-			else
+#ifdef USE_TTS
+				ttsMessage += Common::String(_vm->_text->getNoun(_vm->_object->_objects[i]._nounIndex, 2)) + "\n";
+#endif
+			} else {
 				buffer += Common::String(_vm->_text->getNoun(_vm->_object->_objects[i]._nounIndex, 2)) + Common::String(blanks, len1 - strlen(_vm->_text->getNoun(_vm->_object->_objects[i]._nounIndex, 2)));
+#ifdef USE_TTS
+				ttsMessage += Common::String(_vm->_text->getNoun(_vm->_object->_objects[i]._nounIndex, 2)) + "\n" + Common::String(blanks, len1 - strlen(_vm->_text->getNoun(_vm->_object->_objects[i]._nounIndex, 2)));
+#endif
+			}
 		}
 	}
 	if (index & 1)
 		buffer += "\n";
 	buffer += Common::String(_vm->_text->getTextParser(kTBOutro));
-	Utils::notifyBox(buffer.c_str());
+#ifdef USE_TTS
+	ttsMessage += Common::String(_vm->_text->getTextParser(kTBOutro));
+	_vm->sayText(ttsMessage, Common::TextToSpeechManager::INTERRUPT);
+#endif
+	Utils::notifyBox(buffer.c_str(), false);
 }
 
 } // End of namespace Hugo
diff --git a/engines/hugo/schedule.cpp b/engines/hugo/schedule.cpp
index 0b5c5ec4fac..e065ffd6c81 100644
--- a/engines/hugo/schedule.cpp
+++ b/engines/hugo/schedule.cpp
@@ -1550,6 +1550,10 @@ void Scheduler_v1d::promptAction(Act *action) {
 
 	response = Utils::promptBox(_vm->_file->fetchString(action->_a3._promptIndex));
 
+#ifdef USE_TTS
+	_vm->_queueAllVoicing = true;
+#endif
+
 	response.toLowercase();
 
 	char resp[256];
@@ -1594,6 +1598,10 @@ void Scheduler_v2d::promptAction(Act *action) {
 	response = Utils::promptBox(_vm->_file->fetchString(action->_a3._promptIndex));
 	response.toLowercase();
 
+#ifdef USE_TTS
+	_vm->_queueAllVoicing = true;
+#endif
+
 	debug(1, "doAction(act3), expecting answer %s", _vm->_file->fetchString(action->_a3._responsePtr[0]));
 
 	bool  found = false;
diff --git a/engines/hugo/sound.cpp b/engines/hugo/sound.cpp
index f8a224a373f..5e4b917bdb5 100644
--- a/engines/hugo/sound.cpp
+++ b/engines/hugo/sound.cpp
@@ -171,6 +171,10 @@ void SoundHandler::toggleMusic() {
  */
 void SoundHandler::toggleSound() {
 	_vm->_config._soundFl = !_vm->_config._soundFl;
+
+#ifdef USE_TTS
+	_vm->_voiceSoundSetting = true;
+#endif
 }
 
 void SoundHandler::playMIDI(SoundPtr seqPtr, uint16 size) {
diff --git a/engines/hugo/util.cpp b/engines/hugo/util.cpp
index eb02c694819..748d1035ec5 100644
--- a/engines/hugo/util.cpp
+++ b/engines/hugo/util.cpp
@@ -85,7 +85,13 @@ void reverseByte(byte *data) {
 	*data = result;
 }
 
-void notifyBox(const Common::String &msg) {
+void notifyBox(const Common::String &msg, bool ttsVoiceText, bool ttsReplaceNewlines) {
+#ifdef USE_TTS
+	if (ttsVoiceText) {
+		sayText(msg, Common::TextToSpeechManager::QUEUE, ttsReplaceNewlines);
+	}
+#endif
+
 	notifyBox(Common::U32String(msg));
 }
 
@@ -95,20 +101,38 @@ void notifyBox(const Common::U32String &msg) {
 
 	GUI::MessageDialog dialog(msg);
 	dialog.runModal();
+
+#ifdef USE_TTS
+	stopTextToSpeech();
+#endif
 }
 
 Common::String promptBox(const Common::String &msg) {
 	if (msg.empty())
 		return Common::String();
 
+#ifdef USE_TTS
+	sayText(msg, Common::TextToSpeechManager::QUEUE);
+#endif
+
 	EntryDialog dialog(msg, "OK", "");
 
 	dialog.runModal();
 
-	return dialog.getEditString();
+	Common::U32String input = dialog.getEditString();
+
+#ifdef USE_TTS
+	sayText(input);
+#endif
+
+	return input;
 }
 
 bool yesNoBox(const Common::String &msg) {
+#ifdef USE_TTS
+	sayText(msg, Common::TextToSpeechManager::QUEUE);
+#endif
+
 	return yesNoBox(Common::U32String(msg));
 }
 
@@ -117,7 +141,14 @@ bool yesNoBox(const Common::U32String &msg) {
 		return 0;
 
 	GUI::MessageDialog dialog(msg, Common::U32String("YES"), Common::U32String("NO"));
-	return (dialog.runModal() == GUI::kMessageOK);
+
+	int result = dialog.runModal();
+
+#ifdef USE_TTS
+	stopTextToSpeech();
+#endif
+
+	return (result == GUI::kMessageOK);
 }
 
 char *hugo_strlwr(char *buffer) {
@@ -132,6 +163,39 @@ char *hugo_strlwr(char *buffer) {
 	return result;
 }
 
+#ifdef USE_TTS
+
+void sayText(const Common::String &text, Common::TextToSpeechManager::Action action, bool replaceNewlines) {
+	Common::String ttsMessage(text);
+
+	if (replaceNewlines) {
+		ttsMessage.replace('\n', ' ');
+		ttsMessage.replace('\r', ' ');
+	}
+
+	// Some text is enclosed in < and >, which won't be voiced unless they're replaced
+	Common::replace(ttsMessage, "<", ", ");
+	Common::replace(ttsMessage, ">", ", ");
+
+	sayText(Common::U32String(ttsMessage, Common::CodePage::kWindows1252), action);
+}
+
+void sayText(const Common::U32String &text, Common::TextToSpeechManager::Action action) {
+	Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+	if (ttsMan && ConfMan.getBool("tts_enabled")) {
+		ttsMan->say(text, action);
+	}
+}
+
+void stopTextToSpeech() {
+	Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+	if (ttsMan && ConfMan.getBool("tts_enabled") && ttsMan->isSpeaking()) {
+		ttsMan->stop();
+	}
+}
+
+#endif
+
 } // End of namespace Utils
 
 } // End of namespace Hugo
diff --git a/engines/hugo/util.h b/engines/hugo/util.h
index 0305b3cc6e2..ed270033db9 100644
--- a/engines/hugo/util.h
+++ b/engines/hugo/util.h
@@ -46,7 +46,7 @@ void  reverseByte(byte *data);
  * Show a dialog notifying the user about something, with
  * only a simple "OK" button to dismiss it.
  */
-void notifyBox(const Common::String &msg);	// Redirect to call notifyBox with u32strings
+void notifyBox(const Common::String &msg, bool ttsVoiceText = true, bool ttsReplaceNewlines = true);	// Redirect to call notifyBox with u32strings
 void notifyBox(const Common::U32String &msg);
 
 /**
@@ -67,6 +67,20 @@ bool yesNoBox(const Common::U32String &msg);
  */
 char *hugo_strlwr(char *buffer);
 
+#ifdef USE_TTS
+/**
+ * Voice text with the text-to-speech system.
+ */
+void sayText(const Common::String &text, Common::TextToSpeechManager::Action action = Common::TextToSpeechManager::INTERRUPT, 
+			bool replaceNewlines = true);	// Clean up text, then redirect to call sayText with u32strings
+void sayText(const Common::U32String &text, Common::TextToSpeechManager::Action action = Common::TextToSpeechManager::INTERRUPT);
+
+/**
+ * Stop TTS voicing.
+ */
+void stopTextToSpeech();
+#endif
+
 } // End of namespace Utils
 
 } // End of namespace Hugo




More information about the Scummvm-git-logs mailing list