[Scummvm-git-logs] scummvm master -> a95eb645bac9e852270db00ba7b5ac0c4c2b2154

sev- noreply at scummvm.org
Sat Aug 9 19:09:39 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:
a95eb645ba EFH: Add text-to-speech (TTS)


Commit: a95eb645bac9e852270db00ba7b5ac0c4c2b2154
    https://github.com/scummvm/scummvm/commit/a95eb645bac9e852270db00ba7b5ac0c4c2b2154
Author: ellm135 (ellm13531 at gmail.com)
Date: 2025-08-09T21:09:36+02:00

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

Changed paths:
  A engines/efh/detection.h
    engines/efh/detection.cpp
    engines/efh/efh.cpp
    engines/efh/efh.h
    engines/efh/fight.cpp
    engines/efh/init.cpp
    engines/efh/menu.cpp
    engines/efh/metaengine.cpp
    engines/efh/script.cpp
    engines/efh/utils.cpp


diff --git a/engines/efh/detection.cpp b/engines/efh/detection.cpp
index 12b092989f3..8a1d569f389 100644
--- a/engines/efh/detection.cpp
+++ b/engines/efh/detection.cpp
@@ -22,6 +22,7 @@
 #include "base/plugins.h"
 #include "engines/advancedDetector.h"
 
+#include "efh/detection.h"
 #include "efh/efh.h"
 
 namespace Efh {
@@ -40,7 +41,7 @@ static const ADGameDescription gameDescriptions[] = {
 		Common::EN_ANY,
 		Common::kPlatformDOS,
 		ADGF_NO_FLAGS,
-		GUIO0()
+		GUIO1(GAMEOPTION_TTS)
 	},
 	// Escape From Hell English
 	{
@@ -48,7 +49,7 @@ static const ADGameDescription gameDescriptions[] = {
 		Common::EN_ANY,
 		Common::kPlatformDOS,
 		ADGF_NO_FLAGS,
-		GUIO0()
+		GUIO1(GAMEOPTION_TTS)
 	},
 	AD_TABLE_END_MARKER
 };
diff --git a/engines/efh/detection.h b/engines/efh/detection.h
new file mode 100644
index 00000000000..c7c3fc7e23e
--- /dev/null
+++ b/engines/efh/detection.h
@@ -0,0 +1,27 @@
+/* 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 EFH_DETECTION_H
+#define EFH_DETECTION_H
+
+#define GAMEOPTION_TTS      	    GUIO_GAMEOPTIONS1
+
+#endif // EFH_DETECTION_H
diff --git a/engines/efh/efh.cpp b/engines/efh/efh.cpp
index 7d820e7a498..e0064d2a593 100644
--- a/engines/efh/efh.cpp
+++ b/engines/efh/efh.cpp
@@ -71,6 +71,12 @@ Common::Error EfhEngine::run() {
 	// Setup mixer
 	syncSoundSettings();
 
+	Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+	if (ttsMan != nullptr) {
+		ttsMan->enable(ConfMan.getBool("tts_enabled"));
+		ttsMan->setLanguage(ConfMan.get("language"));
+	}
+
 	// Sometimes a ghost key event stops the intro, so we ass a short delay and purge the keyboard events
 	_system->delayMillis(100);
 	_system->getEventManager()->purgeKeyboardEvents();
@@ -149,7 +155,7 @@ Common::Error EfhEngine::run() {
 }
 
 void EfhEngine::handleEvents() {
-	Common::KeyCode retVal = getLastCharAfterAnimCount(4);
+	Common::KeyCode retVal = getLastCharAfterAnimCount(4, false);
 
 	switch (_customAction) {
 	case kActionSave:
@@ -226,6 +232,8 @@ void EfhEngine::handleEvents() {
 }
 
 void EfhEngine::handleActionSave() {
+	sayText("Are you sure you want to save?", kNoRestriction, Common::TextToSpeechManager::INTERRUPT);
+
 	for (uint counter = 0; counter < 2; ++counter) {
 		clearBottomTextZone(0);
 		displayCenteredString("Are You Sure You Want To Save?", 24, 296, 160);
@@ -235,10 +243,12 @@ void EfhEngine::handleActionSave() {
 	Common::KeyCode input = waitForKey();
 	if (input == Common::KEYCODE_y) {
 		displayMenuAnswerString("-> Yes <-", 24, 296, 169);
+		sayText("Yes", kNoRestriction, Common::TextToSpeechManager::INTERRUPT);
 		getInput(2);
 		saveGameDialog();
 	} else {
 		displayMenuAnswerString("-> No!!! <-", 24, 296, 169);
+		sayText("No", kNoRestriction, Common::TextToSpeechManager::INTERRUPT);
 		getInput(2);
 	}
 	clearBottomTextZone_2(0);
@@ -246,6 +256,8 @@ void EfhEngine::handleActionSave() {
 }
 
 void EfhEngine::handleActionLoad() {
+	sayText("Are you sure you want to load?", kNoRestriction, Common::TextToSpeechManager::INTERRUPT);
+
 	for (uint counter = 0; counter < 2; ++counter) {
 		clearBottomTextZone(0);
 		displayCenteredString("Are You Sure You Want To Load?", 24, 296, 160);
@@ -255,10 +267,12 @@ void EfhEngine::handleActionLoad() {
 	Common::KeyCode input = waitForKey();
 	if (input == Common::KEYCODE_y) {
 		displayMenuAnswerString("-> Yes <-", 24, 296, 169);
+		sayText("Yes", kNoRestriction, Common::TextToSpeechManager::INTERRUPT);
 		getInput(2);
 		loadGameDialog();
 	} else {
 		displayMenuAnswerString("-> No!!! <-", 24, 296, 169);
+		sayText("No", kNoRestriction, Common::TextToSpeechManager::INTERRUPT);
 		getInput(2);
 	}
 	clearBottomTextZone_2(0);
@@ -411,8 +425,12 @@ void EfhEngine::initEngine() {
 	readFileToBuffer(fileName, _titleSong);
 	Common::KeyCode lastInput = Common::KEYCODE_INVALID;
 
-	if (_loadSaveSlot == -1)
+	if (_loadSaveSlot == -1) {
+		sayText("Richard and Alan's Escape from Hell\nCopyright 1990 Electronic Arts\n"
+				"By Richard L. Seaborne with Alan J. Murphy", kNoRestriction);
 		lastInput = playSong(_titleSong);
+		stopTextToSpeech();
+	}
 
 	if (lastInput != Common::KEYCODE_ESCAPE && _loadSaveSlot == -1)
 		playIntro();
@@ -427,6 +445,7 @@ void EfhEngine::initEngine() {
 		loadEfhGame();
 		resetGame();
 	} else {
+		_sayLowStatusScreen = true;
 		loadGameState(_loadSaveSlot);
 		_loadSaveSlot = -1;
 	}
@@ -680,15 +699,20 @@ void EfhEngine::displayLowStatusScreen(bool flag) {
 				Common::String buffer = _npcBuf[charId]._name;
 				setTextPos(16, textPosY);
 				displayStringAtTextPos(buffer);
+				sayText("Name: " + buffer, kLowStatusMenu);
 				buffer = Common::String::format("%d", getEquipmentDefense(charId));
 				displayCenteredString(buffer, 104, 128, textPosY);
+				sayText("Defense: " + buffer, kLowStatusMenu);
 				buffer = Common::String::format("%d", _npcBuf[charId]._hitPoints);
 				displayCenteredString(buffer, 144, 176, textPosY);
+				sayText("HP: " + buffer, kLowStatusMenu);
 				buffer = Common::String::format("%d", _npcBuf[charId]._maxHP);
 				displayCenteredString(buffer, 192, 224, textPosY);
+				sayText("Max HP: " + buffer, kLowStatusMenu);
 
 				if (_npcBuf[charId]._hitPoints <= 0) {
 					displayCenteredString("* DEAD *", 225, 302, textPosY);
+					sayText("Dead", kLowStatusMenu);
 					continue;
 				}
 
@@ -713,11 +737,19 @@ void EfhEngine::displayLowStatusScreen(bool flag) {
 				}
 
 				displayCenteredString(_nameBuffer, 225, 302, textPosY);
+
+				if (_teamChar[i]._status._type == kEfhStatusNormal) {
+					sayText("Weapon: " + _nameBuffer, kLowStatusMenu);
+				} else {
+					sayText(_nameBuffer, kLowStatusMenu);
+				}
 			}
 		}
 
 		if (counter == 0 && flag)
 			displayFctFullScreen();
+		
+		_sayLowStatusScreen = false;
 	}
 }
 
@@ -808,6 +840,8 @@ void EfhEngine::handleWinSequence() {
 	loadImageSet(64, winSeqBuf3, winSeqSubFilesArray1, decompBuffer);
 	loadImageSet(65, winSeqBuf4, winSeqSubFilesArray2, decompBuffer);
 
+	sayText("Come back soon", kNoRestriction, Common::TextToSpeechManager::INTERRUPT);
+
 	for (uint counter = 0; counter < 2; ++counter) {
 		displayRawDataAtPos(winSeqSubFilesArray1[0], 0, 0);
 		displayRawDataAtPos(winSeqSubFilesArray2[0], 136, 48);
@@ -931,6 +965,7 @@ int16 EfhEngine::handleCharacterJoining() {
 		if (counter == 0)
 			displayFctFullScreen();
 	}
+	sayText("Replace Who?", kNoRestriction);
 
 	int16 charId = chooseCharacterToReplace();
 	for (uint counter = 0; counter < 2; ++counter) {
@@ -942,6 +977,7 @@ int16 EfhEngine::handleCharacterJoining() {
 	if (charId == 0x1B) // Escape Keycode
 		return -1;
 
+	sayText(_npcBuf[_teamChar[charId]._id]._name, kNoRestriction, Common::TextToSpeechManager::INTERRUPT);
 	removeCharacterFromTeam(charId);
 	return 2;
 }
@@ -972,6 +1008,8 @@ void EfhEngine::drawText(uint8 *srcPtr, int16 posX, int16 posY, int16 maxX, int1
 		}
 	}
 
+	sayText(_messageToBePrinted, kNoRestriction, Common::TextToSpeechManager::QUEUE_NO_REPEAT);
+
 	script_parse(_messageToBePrinted, posX, posY, maxX, maxY, flag);
 }
 
@@ -985,6 +1023,7 @@ void EfhEngine::displayMiddleLeftTempText(uint8 *impArray, bool flag) {
 			if (impArray != nullptr) {
 				_tempTextDelay = 4;
 				_tempTextPtr = impArray;
+				stopTextToSpeech();
 				drawText(impArray, 17, 115, 110, 133, false);
 			}
 			if (counter == 0 && flag)
@@ -1027,7 +1066,7 @@ void EfhEngine::transitionMap(int16 centerX, int16 centerY) {
 		drawScreen();
 	}
 
-	getLastCharAfterAnimCount(3);
+	getLastCharAfterAnimCount(3, false);
 	_drawHeroOnMapFl = true;
 }
 
@@ -1221,6 +1260,8 @@ void EfhEngine::goSouthWest() {
 
 void EfhEngine::showCharacterStatus(uint8 character) {
 	if (_teamChar[character]._id != -1) {
+		stopTextToSpeech();
+		_sayMenu = true;
 		handleStatusMenu(1, _teamChar[character]._id);
 		_tempTextPtr = nullptr;
 		drawGameScreenAndTempText(true);
@@ -1704,8 +1745,10 @@ void EfhEngine::handleMapMonsterMoves() {
 		} while (!monsterMovedFl && retryCounter > 0 && !shouldQuit());
 	}
 
-	if (attackMonsterId != -1)
+	if (attackMonsterId != -1) {
+		stopTextToSpeech();
 		handleFight(attackMonsterId);
+	}
 }
 
 bool EfhEngine::checkMapMonsterAvailability(int16 monsterId) {
@@ -1870,6 +1913,7 @@ bool EfhEngine::handleTalk(int16 monsterId, int16 arg2, int16 itemId) {
 				_enemyNamePt2 = _npcBuf[npcId]._name;
 				_characterNamePt2 = _npcBuf[_teamChar[charId]._id]._name;
 				Common::String buffer = Common::String::format("%s asks that %s leave your party.", _enemyNamePt2.c_str(), _characterNamePt2.c_str());
+				sayText(buffer, kNoRestriction, Common::TextToSpeechManager::INTERRUPT);
 				for (uint i = 0; i < 2; ++i) {
 					clearBottomTextZone(0);
 					_textColor = 0xE;
@@ -1879,6 +1923,7 @@ bool EfhEngine::handleTalk(int16 monsterId, int16 arg2, int16 itemId) {
 					if (i == 0)
 						displayFctFullScreen();
 				}
+				sayText("Will you do this?", kNoRestriction);
 				setTextColorRed();
 				Common::KeyCode input = waitForKey();
 				if (input == Common::KEYCODE_y) {
@@ -2017,6 +2062,10 @@ void EfhEngine::displayImp1Text(int16 textId) {
 								displayFctFullScreen();
 						}
 
+						if (!_initiatedTalkByMenu) {
+							stopTextToSpeech();
+						}
+
 						nextTextId = displayBoxWithText(_messageToBePrinted, 1, 1, true);
 						if (nextTextId != 0xFF)
 							curTextId = nextTextId;
@@ -2031,7 +2080,15 @@ void EfhEngine::displayImp1Text(int16 textId) {
 								if (counter == 0)
 									displayFctFullScreen();
 							}
+
+							if (textComplete) {
+								sayText("Done", kNoRestriction);
+							} else {
+								sayText("More", kNoRestriction);
+							}
 							waitForKey();
+							stopTextToSpeech();
+							_initiatedTalkByMenu = false;
 						}
 					}
 					if (nextTextId != 0xFF)
@@ -2115,6 +2172,7 @@ bool EfhEngine::handleInteractionText(int16 mapPosX, int16 mapPosY, int16 charId
 			// Note: The 2 checks on var6 are useless, as [0x78..0xEF] - 0x78 => [0x00..0x77]
 			// Note: In the data,all resulting values are between 2 and 14, so it's working
 			if (var6 >= 0 && var6 <= 0x8B && var6 == itemId && _mapSpecialTiles[_techId][tileId]._triggerValue <= _npcBuf[charId]._activeScore[itemId]) {
+				_initiatedTalkByMenu = true;
 				displayImp1Text(_mapSpecialTiles[_techId][tileId]._field5_textId);
 				return true;
 			}
@@ -2347,6 +2405,7 @@ bool EfhEngine::checkMonsterCollision() {
 				++mobsterCount;
 		}
 
+		_sayMenu = true;
 		bool endLoop = false;
 		Common::String buffer;
 		do {
@@ -2375,50 +2434,62 @@ bool EfhEngine::checkMonsterCollision() {
 				_textColor = 0xE;
 				displayCenteredString("Interaction", 24, 296, 152);
 				displayCenteredString(buffer, 24, 296, 161);
+				sayText("Interaction " + buffer, kMenu, Common::TextToSpeechManager::INTERRUPT);
 				setTextPos(24, 169);
 				setTextColorWhite();
 				displayStringAtTextPos("T");
 				setTextColorRed();
 				buffer = Common::String("alk to the ") + dest;
 				displayStringAtTextPos(buffer);
+				sayText("Talk to the " + dest, kMenu);
 				setTextPos(24, 178);
 				setTextColorWhite();
 				displayStringAtTextPos("A");
 				setTextColorRed();
 				buffer = Common::String("ttack the ") + dest;
 				displayStringAtTextPos(buffer);
+				sayText("Attack the " + dest, kMenu);
 				setTextPos(198, 169);
 				setTextColorWhite();
 				displayStringAtTextPos("S");
 				setTextColorRed();
 				displayStringAtTextPos("tatus");
+				sayText("Status", kMenu);
 				setTextPos(198, 178);
 				setTextColorWhite();
 				displayStringAtTextPos("L");
 				setTextColorRed();
 				displayStringAtTextPos("eave");
+				sayText("Leave", kMenu);
 				if (displayCounter == 0)
 					displayFctFullScreen();
+				_sayMenu = false;
 			}
 
 			Common::KeyCode input = waitForKey();
 
 			switch (input) {
 			case Common::KEYCODE_a: // Attack
+				sayText("Attack", kNoRestriction, Common::TextToSpeechManager::INTERRUPT);
 				handleFight(monsterId);
 				endLoop = true;
 				break;
 			case Common::KEYCODE_ESCAPE:
 			case Common::KEYCODE_l: // Leave
+				sayText("Leave", kNoRestriction, Common::TextToSpeechManager::INTERRUPT);
 				endLoop = true;
 				break;
 			case Common::KEYCODE_s: // Status
+				sayText("Status", kNoRestriction, Common::TextToSpeechManager::INTERRUPT);
+				_sayMenu = true;
 				handleStatusMenu(1, _teamChar[0]._id);
 				endLoop = true;
 				_tempTextPtr = nullptr;
 				drawGameScreenAndTempText(true);
 				break;
 			case Common::KEYCODE_t: // Talk
+				sayText("Talk", kNoRestriction, Common::TextToSpeechManager::INTERRUPT);
+				_initiatedTalkByMenu = true;
 				startTalkMenu(monsterId);
 				endLoop = true;
 				break;
@@ -2520,6 +2591,8 @@ void EfhEngine::loadEfhGame() {
 
 	_lastMainPlaceId = 0xFFFF;
 	loadPlacesFile(_fullPlaceId, true);
+
+	_sayLowStatusScreen = true;
 }
 
 uint8 EfhEngine::getMapTileInfo(int16 mapPosX, int16 mapPosY) {
@@ -2576,4 +2649,20 @@ void EfhEngine::copyCurrentPlaceToBuffer(int16 id) {
 	}
 }
 
+void EfhEngine::sayText(const Common::String &text, TTSMenuRestriction menuRestriction, Common::TextToSpeechManager::Action action) const {
+	Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+	bool speak = menuRestriction == kNoRestriction || (menuRestriction == kLowStatusMenu && _sayLowStatusScreen) || (menuRestriction == kMenu && _sayMenu);
+	if (ttsMan != nullptr && ConfMan.getBool("tts_enabled") && speak) {
+		ttsMan->say(text, action, Common::CodePage::kDos850);
+	}
+}
+
+void EfhEngine::stopTextToSpeech() const {
+	Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+	if (ttsMan != nullptr && ConfMan.getBool("tts_enabled") && ttsMan->isSpeaking()) {
+		ttsMan->stop();
+	}
+}
+
+
 } // End of namespace Efh
diff --git a/engines/efh/efh.h b/engines/efh/efh.h
index 448d856960f..f2ca873538d 100644
--- a/engines/efh/efh.h
+++ b/engines/efh/efh.h
@@ -28,6 +28,7 @@
 #include "common/rect.h"
 #include "common/events.h"
 #include "common/serializer.h"
+#include "common/text-to-speech.h"
 
 #include "engines/advancedDetector.h"
 #include "engines/engine.h"
@@ -257,6 +258,12 @@ struct TeamMonster {
 	void init();
 };
 
+enum TTSMenuRestriction {
+	kNoRestriction,
+	kLowStatusMenu,
+	kMenu
+};
+
 enum EFHAction {
 	kActionNone,
 	kActionExit,
@@ -480,7 +487,7 @@ private:
 	void displayColoredMenuBox(int16 minX, int16 minY, int16 maxX, int16 maxY, int16 color);
 
 	// Menu
-	int16 displayBoxWithText(const Common::String &str, int16 menuType, int16 displayOption, bool displayTeamWindowFl);
+	int16 displayBoxWithText(const Common::String &str, int16 menuType, int16 displayOption, bool displayTeamWindowFl, bool voiceText = true);
 	bool handleDeathMenu();
 	void displayCombatMenu(int16 charId);
 	void displayMenuItemString(int16 menuBoxId, int16 thisBoxId, int16 minX, int16 maxX, int16 minY, const char *str);
@@ -516,13 +523,15 @@ private:
 	void generateSound5(int repeat);
 	void generateSound(int16 soundType);
 	void genericGenerateSound(int16 soundType, int16 repeatCount);
+	void sayText(const Common::String &text, TTSMenuRestriction menuRestriction, Common::TextToSpeechManager::Action action = Common::TextToSpeechManager::QUEUE) const;
+	void stopTextToSpeech() const;
 
 	// Utils
 	void decryptImpFile(bool techMapFl);
 	void loadImageSet(int16 imageSetId, uint8 *buffer, uint8 **subFilesArray, uint8 *destBuffer);
 	uint32 uncompressBuffer(uint8 *compressedBuf, uint8 *destBuf);
 	int16 getRandom(int16 maxVal);
-	Common::KeyCode getLastCharAfterAnimCount(int16 delay);
+	Common::KeyCode getLastCharAfterAnimCount(int16 delay, bool waitForTTS = true);
 	Common::KeyCode getInput(int16 delay);
 	Common::KeyCode waitForKey();
 	Common::KeyCode handleAndMapInput(bool animFl);
@@ -632,6 +641,10 @@ private:
 	int16 _menuStatItemArr[15];
 	int16 _menuDepth;
 	int16 _menuItemCounter;
+	int16 _selectedMenuBox;
+	bool _sayMenu;
+	bool _sayLowStatusScreen;
+	bool _initiatedTalkByMenu;
 
 	TeamChar _teamChar[3];
 	TeamMonster _teamMonster[5];
diff --git a/engines/efh/fight.cpp b/engines/efh/fight.cpp
index bc1d6bb4fe9..26c219cb7cc 100644
--- a/engines/efh/fight.cpp
+++ b/engines/efh/fight.cpp
@@ -92,6 +92,7 @@ bool EfhEngine::handleFight(int16 monsterId) {
 		return true;
 	}
 
+	_sayMenu = false;
 	drawCombatScreen(0, false, true);
 
 	for (bool mainLoopCond = false; !mainLoopCond && !shouldQuit();) {
@@ -129,7 +130,7 @@ bool EfhEngine::handleFight(int16 monsterId) {
 		}
 
 		computeInitiatives();
-		displayBoxWithText("", 2, 1, false);
+		displayBoxWithText("", 2, 1, false, false);
 
 		for (uint counter = 0; counter < 8; ++counter) {
 			int16 monsterGroupIdOrMonsterId = _initiatives[counter]._id;
@@ -1000,10 +1001,15 @@ int16 EfhEngine::determineTeamTarget(int16 charId, int16 unkFied18Val, bool chec
 	}
 
 	do {
+		if (_teamMonster[1]._id != -1) {
+			_sayMenu = true;
+			sayText("Select Monster Group:", kMenu);
+		}
+
 		for (uint counter = 0; counter < 2; ++counter) {
 			drawCombatScreen(charId, true, false);
 			if (_teamMonster[1]._id != -1)
-				displayBoxWithText("Select Monster Group:", 3, 0, false);
+				displayBoxWithText("Select Monster Group:", 3, 0, false, false);
 
 			if (counter == 0)
 				displayFctFullScreen();
@@ -1034,7 +1040,9 @@ bool EfhEngine::getTeamAttackRoundPlans() {
 	debugC(3, kDebugFight, "getTeamAttackRoundPlans");
 
 	bool retVal = false;
+	_sayLowStatusScreen = true;
 	for (int charId = 0; charId < _teamSize; ++charId) {
+		_sayMenu = true;
 		_teamChar[charId]._lastAction = 0;
 		if (!isTeamMemberStatusNormal(charId))
 			continue;
@@ -1044,23 +1052,29 @@ bool EfhEngine::getTeamAttackRoundPlans() {
 			drawCombatScreen(_teamChar[charId]._id, false, true);
 			switch (handleAndMapInput(true)) {
 			case Common::KEYCODE_a: // Attack
+				sayText("Attack", kNoRestriction, Common::TextToSpeechManager::INTERRUPT);
 				_teamChar[charId]._lastAction = 'A';
 				_teamChar[charId]._nextAttack = determineTeamTarget(_teamChar[charId]._id, 9, true);
 				if (_teamChar[charId]._nextAttack == -1)
 					_teamChar[charId]._lastAction = 0;
 				break;
 			case Common::KEYCODE_d: // Defend
+				sayText("Defend", kNoRestriction, Common::TextToSpeechManager::INTERRUPT);
 				_teamChar[charId]._lastAction = 'D';
 				break;
 			case Common::KEYCODE_h: // Hide
+				sayText("Hide", kNoRestriction, Common::TextToSpeechManager::INTERRUPT);
 				_teamChar[charId]._lastAction = 'H';
 				break;
 			case Common::KEYCODE_r: // Run
+				sayText("Run", kNoRestriction, Common::TextToSpeechManager::INTERRUPT);
 				for (int counter2 = 0; counter2 < _teamSize; ++counter2) {
 					_teamChar[counter2]._lastAction = 'R';
 				}
 				return true;
 			case Common::KEYCODE_s: { // Status
+				sayText("Status", kNoRestriction, Common::TextToSpeechManager::INTERRUPT);
+				_sayMenu = true;
 				int16 lastInvId = handleStatusMenu(2, _teamChar[charId]._id);
 				redrawCombatScreenWithTempText(_teamChar[charId]._id);
 				if (lastInvId >= 999) {
@@ -1122,7 +1136,9 @@ bool EfhEngine::getTeamAttackRoundPlans() {
 			} break;
 			case Common::KEYCODE_t: // Terrain
 				redrawScreenForced();
+				sayText("Terrain", kNoRestriction, Common::TextToSpeechManager::INTERRUPT);
 				waitForKey();
+				_sayMenu = true;
 				drawCombatScreen(_teamChar[charId]._id, false, true);
 				break;
 			default:
@@ -1143,10 +1159,19 @@ void EfhEngine::drawCombatScreen(int16 charId, bool whiteFl, bool drawFl) {
 			displayCenteredString("Combat", 128, 303, 9);
 			drawColoredRect(200, 112, 278, 132, 0);
 			displayCenteredString("'T' for Terrain", 128, 303, 117);
-			displayBoxWithText("", 1, 0, false);
+			displayBoxWithText("", 1, 0, false, false);
+			if (!whiteFl) {
+				sayText(_npcBuf[charId]._name, kMenu);
+			}
 			displayEncounterInfo(whiteFl);
+			if (whiteFl) {
+				_sayMenu = false;
+			} else {
+				sayText("'T' for Terrain", kMenu);
+			}
 			displayCombatMenu(charId);
 			displayLowStatusScreen(false);
+			_sayMenu = false;
 		}
 
 		if (counter == 0 && drawFl)
@@ -1334,46 +1359,58 @@ void EfhEngine::displayEncounterInfo(bool whiteFl) {
 
 		setTextPos(129, textPosY);
 		Common::String buffer = Common::String::format("%c)", 'A' + counter);
+		Common::String ttsMessage = buffer + " ";
 		displayStringAtTextPos(buffer);
 		setTextColorRed();
 		int16 var1 = _mapMonsters[_techId][_teamMonster[counter]._id]._possessivePronounSHL6 & 0x3F;
 		if (var1 <= 0x3D) {
 			buffer = Common::String::format("%d %s", mobsterCount, kEncounters[_mapMonsters[_techId][_teamMonster[counter]._id]._monsterRef]._name);
+			ttsMessage += buffer;
 			displayStringAtTextPos(buffer);
-			if (mobsterCount > 1)
+			if (mobsterCount > 1) {
 				displayStringAtTextPos("s");
+				ttsMessage += "s";
+			}
 		} else if (var1 == 0x3E) {
 			displayStringAtTextPos("(NOT DEFINED)");
 		} else if (var1 == 0x3F) {
 			Common::String stringToDisplay = _npcBuf[_mapMonsters[_techId][_teamMonster[counter]._id]._npcId]._name;
 			displayStringAtTextPos(stringToDisplay);
+			ttsMessage += stringToDisplay;
 		}
 
 		setTextPos(228, textPosY);
 		if (checkMonsterMovementType(counter, true)) {
 			_textColor = 0xE;
 			displayStringAtTextPos("Hostile");
+			ttsMessage += ", Hostile, ";
 		} else {
 			_textColor = 0x2;
 			displayStringAtTextPos("Friendly");
+			ttsMessage += ", Friendly, ";
 		}
 
 		setTextColorRed();
 		switch (monsterDistance) {
 		case 1:
 			displayCenteredString("S", 290, 302, textPosY);
+			ttsMessage += "S";
 			break;
 		case 2:
 			displayCenteredString("M", 290, 302, textPosY);
+			ttsMessage += "M";
 			break;
 		case 3:
 			displayCenteredString("L", 290, 302, textPosY);
+			ttsMessage += "L";
 			break;
 		default:
 			displayCenteredString("?", 290, 302, textPosY);
+			ttsMessage += "?";
 			break;
 		}
 
+		sayText(ttsMessage, kMenu);
 		textPosY += 9;
 	}
 }
@@ -1682,6 +1719,12 @@ int16 EfhEngine::selectMonsterGroup() {
 		}
 	}
 
+	_sayMenu = true;
+	stopTextToSpeech();
+	if (retVal != -1 && retVal != 27) {
+		sayText(Common::String::format("%c", retVal + Common::KEYCODE_a), kNoRestriction);
+	}
+
 	return retVal;
 }
 
diff --git a/engines/efh/init.cpp b/engines/efh/init.cpp
index 9ef80f076c0..e6e6a7bde9c 100644
--- a/engines/efh/init.cpp
+++ b/engines/efh/init.cpp
@@ -352,6 +352,10 @@ EfhEngine::EfhEngine(OSystem *syst, const ADGameDescription *gd) : Engine(syst),
 	_statusMenuActive = false;
 	_menuDepth = 0;
 	_menuItemCounter = 0;
+	_selectedMenuBox = -1;
+	_sayMenu = true;
+	_sayLowStatusScreen = false;
+	_initiatedTalkByMenu = false;
 
 	for (int i = 0; i < 15; ++i)
 		_menuStatItemArr[i] = 0;
diff --git a/engines/efh/menu.cpp b/engines/efh/menu.cpp
index 95adc30493c..08e789dfea1 100644
--- a/engines/efh/menu.cpp
+++ b/engines/efh/menu.cpp
@@ -23,7 +23,7 @@
 
 namespace Efh {
 
-int16 EfhEngine::displayBoxWithText(const Common::String &str, int16 menuType, int16 displayOption, bool displayTeamWindowFl) {
+int16 EfhEngine::displayBoxWithText(const Common::String &str, int16 menuType, int16 displayOption, bool displayTeamWindowFl, bool voiceText) {
 	debugC(3, kDebugEngine, "displayBoxWithText %s %d %d %s", str.c_str(), menuType, displayOption, displayTeamWindowFl ? "True" : "False");
 
 	int16 retVal = 0xFF;
@@ -61,6 +61,20 @@ int16 EfhEngine::displayBoxWithText(const Common::String &str, int16 menuType, i
 		break;
 	}
 
+	if (voiceText) {
+		Common::String ttsMessage(str);
+
+		// Some messages have a ^ followed by a few numbers at the end
+		uint32 caretPosition = ttsMessage.find('^');
+		if (caretPosition != Common::String::npos) {
+			ttsMessage.erase(caretPosition, Common::String::npos);
+		}
+
+		ttsMessage.replace('|', '\n');
+
+		sayText(ttsMessage, kNoRestriction);
+	}
+
 	drawColoredRect(minX, minY, maxX, maxY, 0);
 	if (!str.empty())
 		retVal = script_parse(str, minX, minY, maxX, maxY, true);
@@ -100,6 +114,11 @@ bool EfhEngine::handleDeathMenu() {
 	_imageSetSubFilesIdx = 213;
 	drawScreen();
 
+	sayText("Darkness prevails... death has taken you!", kNoRestriction, Common::TextToSpeechManager::INTERRUPT);
+	sayText("Load last saved game", kNoRestriction);
+	sayText("Restart from beginning", kNoRestriction);
+	sayText("Quit for now", kNoRestriction);
+
 	for (uint counter = 0; counter < 2; ++counter) {
 		clearBottomTextZone(0);
 		displayCenteredString("Darkness Prevails...Death Has Taken You!", 24, 296, 153);
@@ -165,26 +184,31 @@ void EfhEngine::displayCombatMenu(int16 charId) {
 	displayStringAtTextPos("A");
 	setTextColorRed();
 	displayStringAtTextPos("ttack");
+	sayText("Attack", kMenu);
 	setTextPos(195, 79);
 	setTextColorWhite();
 	displayStringAtTextPos("H");
 	setTextColorRed();
 	displayStringAtTextPos("ide");
+	sayText("Hide", kMenu);
 	setTextPos(152, 88);
 	setTextColorWhite();
 	displayStringAtTextPos("D");
 	setTextColorRed();
 	displayStringAtTextPos("efend");
+	sayText("Defend", kMenu);
 	setTextPos(195, 88);
 	setTextColorWhite();
 	displayStringAtTextPos("R");
 	setTextColorRed();
 	displayStringAtTextPos("un");
+	sayText("Run", kMenu);
 	setTextPos(152, 97);
 	setTextColorWhite();
 	displayStringAtTextPos("S");
 	setTextColorRed();
 	displayStringAtTextPos("tatus");
+	sayText("Status", kMenu);
 }
 
 void EfhEngine::displayMenuItemString(int16 menuBoxId, int16 thisBoxId, int16 minX, int16 maxX, int16 minY, const char *str) {
@@ -196,6 +220,11 @@ void EfhEngine::displayMenuItemString(int16 menuBoxId, int16 thisBoxId, int16 mi
 		else
 			setTextColorGrey();
 
+		if (_selectedMenuBox != thisBoxId) {
+			sayText(str, kNoRestriction, Common::TextToSpeechManager::INTERRUPT);
+			_selectedMenuBox = thisBoxId;
+		}
+
 		Common::String buffer = Common::String::format("> %s <", str);
 		displayCenteredString(buffer, minX, maxX, minY);
 		setTextColorRed();
@@ -275,33 +304,47 @@ void EfhEngine::displayCharacterSummary(int16 curMenuLine, int16 npcId) {
 	setTextColorRed();
 	Common::String buffer1 = _npcBuf[npcId]._name;
 	setTextPos(146, 27);
-	displayStringAtTextPos("Name: ");
+	buffer1 = "Name: " + buffer1;
+	Common::String ttsMessage = buffer1;
 	displayStringAtTextPos(buffer1);
 	buffer1 = Common::String::format("Level: %d", getXPLevel(_npcBuf[npcId]._xp));
+	ttsMessage += '\n' + buffer1;
 	setTextPos(146, 36);
 	displayStringAtTextPos(buffer1);
 	buffer1 = Common::String::format("XP: %u", _npcBuf[npcId]._xp);
+	ttsMessage += '\n' + buffer1;
 	setTextPos(227, 36);
 	displayStringAtTextPos(buffer1);
 	buffer1 = Common::String::format("Speed: %d", _npcBuf[npcId]._speed);
+	ttsMessage += '\n' + buffer1;
 	setTextPos(146, 45);
 	displayStringAtTextPos(buffer1);
 	buffer1 = Common::String::format("Defense: %d", getEquipmentDefense(npcId));
+	ttsMessage += '\n' + buffer1;
 	setTextPos(146, 54);
 	displayStringAtTextPos(buffer1);
 	buffer1 = Common::String::format("Hit Points: %d", _npcBuf[npcId]._hitPoints);
+	ttsMessage += '\n' + buffer1;
 	setTextPos(146, 63);
 	displayStringAtTextPos(buffer1);
 	buffer1 = Common::String::format("Max HP: %d", _npcBuf[npcId]._maxHP);
+	ttsMessage += '\n' + buffer1;
 	setTextPos(227, 63);
 	displayStringAtTextPos(buffer1);
+
+	if (curMenuLine == -1) {
+		sayText(ttsMessage, kMenu);
+	}
+
 	displayCenteredString("Inventory", 144, 310, 72);
+	sayText("Inventory", kMenu);
 
 	if (_menuItemCounter == 0) {
 		if (curMenuLine != -1)
 			setTextColorWhite();
 
 		displayCenteredString("Nothing Carried", 144, 310, 117);
+		sayText("Nothing Carried", kMenu);
 		setTextColorRed();
 		return;
 	}
@@ -319,6 +362,7 @@ void EfhEngine::displayCharacterSummary(int16 curMenuLine, int16 npcId) {
 			if (_npcBuf[npcId]._inventory[_menuStatItemArr[counter]].isEquipped()) {
 				setTextPos(146, textPosY);
 				displayCharAtTextPos('E');
+				sayText("E", kMenu);
 			}
 		}
 
@@ -329,12 +373,14 @@ void EfhEngine::displayCharacterSummary(int16 curMenuLine, int16 npcId) {
 			buffer1 = Common::String::format("%c)", 'A' + counter);
 		}
 		displayStringAtTextPos(buffer1);
+		sayText(buffer1, kMenu);
 
 		if (itemId != 0x7FFF) {
 			setTextPos(168, textPosY);
 			buffer1 = Common::String::format("  %s", _items[itemId]._name);
 			displayStringAtTextPos(buffer1);
 			setTextPos(262, textPosY);
+			sayText(buffer1, kMenu);
 
 			if (_items[itemId]._defense > 0) {
 				int16 curHitPoints = _npcBuf[npcId]._inventory[_menuStatItemArr[counter]]._curHitPoints;
@@ -343,6 +389,7 @@ void EfhEngine::displayCharacterSummary(int16 curMenuLine, int16 npcId) {
 					displayStringAtTextPos(buffer1);
 					setTextPos(286, textPosY);
 					displayStringAtTextPos("Def");
+					sayText(buffer1 + " Defense", kMenu);
 				}
 			} else if (_items[itemId]._uses != 0x7F) {
 				int16 stat1 = _npcBuf[npcId]._inventory[_menuStatItemArr[counter]].getUsesLeft();
@@ -350,10 +397,13 @@ void EfhEngine::displayCharacterSummary(int16 curMenuLine, int16 npcId) {
 					buffer1 = Common::String::format("%d", stat1);
 					displayStringAtTextPos(buffer1);
 					setTextPos(286, textPosY);
-					if (stat1 == 1)
+					if (stat1 == 1) {
 						displayStringAtTextPos("Use");
-					else
+						sayText(buffer1 + " Use", kMenu);
+					} else {
 						displayStringAtTextPos("Uses");
+						sayText(buffer1 + " Uses", kMenu);
+					}
 				}
 			}
 		}
@@ -366,14 +416,20 @@ void EfhEngine::displayCharacterInformationOrSkills(int16 curMenuLine, int16 cha
 
 	setTextColorRed();
 	Common::String buffer = _npcBuf[charId]._name;
+	buffer = "Name: " + buffer;
 	setTextPos(146, 27);
-	displayStringAtTextPos("Name: ");
 	displayStringAtTextPos(buffer);
+	
+	sayText(buffer, kMenu);
+
 	if (_menuItemCounter <= 0) {
 		if (curMenuLine != -1)
 			setTextColorWhite();
 		displayCenteredString("No Skills To Select", 144, 310, 96);
 		setTextColorRed();
+
+		sayText("No Skills To Select", kMenu);
+
 		return;
 	}
 
@@ -389,9 +445,11 @@ void EfhEngine::displayCharacterInformationOrSkills(int16 curMenuLine, int16 cha
 		}
 
 		displayStringAtTextPos(buffer);
+		sayText(buffer, kMenu);
 		setTextPos(163, textPosY);
 		int16 scoreId = _menuStatItemArr[counter];
 		displayStringAtTextPos(kSkillArray[scoreId]);
+		sayText(kSkillArray[scoreId], kMenu);
 		if (scoreId < 15)
 			buffer = Common::String::format("%d", _npcBuf[charId]._activeScore[_menuStatItemArr[counter]]);
 		else if (scoreId < 26)
@@ -400,6 +458,7 @@ void EfhEngine::displayCharacterInformationOrSkills(int16 curMenuLine, int16 cha
 			buffer = Common::String::format("%d", _npcBuf[charId]._infoScore[_menuStatItemArr[counter] - 26]);
 		setTextPos(278, textPosY);
 		displayStringAtTextPos(buffer);
+		sayText(buffer, kMenu);
 		setTextColorRed();
 	}
 }
@@ -413,44 +472,56 @@ void EfhEngine::displayStatusMenuActions(int16 menuId, int16 curMenuLine, int16
 	switch (menuId) {
 	case kEfhMenuEquip:
 		displayCenteredString("Select Item to Equip", 144, 310, 15);
+		sayText("Select Item to Equip", kMenu);
 		displayCharacterSummary(curMenuLine, npcId);
 		break;
 	case kEfhMenuUse:
 		displayCenteredString("Select Item to Use", 144, 310, 15);
+		sayText("Select Item to Use", kMenu);
 		displayCharacterSummary(curMenuLine, npcId);
 		break;
 	case kEfhMenuGive:
 		displayCenteredString("Select Item to Give", 144, 310, 15);
+		sayText("Select Item to Give", kMenu);
 		displayCharacterSummary(curMenuLine, npcId);
 		break;
 	case kEfhMenuTrade:
 		displayCenteredString("Select Item to Trade", 144, 310, 15);
+		sayText("Select Item to Trade", kMenu);
 		displayCharacterSummary(curMenuLine, npcId);
 		break;
 	case kEfhMenuDrop:
 		displayCenteredString("Select Item to Drop", 144, 310, 15);
+		sayText("Select Item to Drop", kMenu);
 		displayCharacterSummary(curMenuLine, npcId);
 		break;
 	case kEfhMenuInfo:
 		displayCenteredString("Character Information", 144, 310, 15);
+		sayText("Character Information", kMenu);
 		displayCharacterInformationOrSkills(curMenuLine, npcId);
 		break;
 	case kEfhMenuPassive:
 		displayCenteredString("Passive Skills", 144, 310, 15);
+		sayText("Passive Skills", kMenu);
 		displayCharacterInformationOrSkills(curMenuLine, npcId);
 		break;
 	case kEfhMenuActive:
 		displayCenteredString("Active Skills", 144, 310, 15);
+		sayText("Active Skills", kMenu);
 		displayCharacterInformationOrSkills(curMenuLine, npcId);
 		break;
 	case kEfhMenuLeave:
 	case kEfhMenuInvalid:
 		displayCenteredString("Character Summary", 144, 310, 15);
+		sayText("Character Summary", kMenu);
 		displayCharacterSummary(curMenuLine, npcId);
 		break;
 	default:
 		break;
 	}
+
+	sayText("Escape Aborts", kMenu);
+	_sayMenu = false;
 }
 
 void EfhEngine::prepareStatusMenu(int16 windowId, int16 menuId, int16 curMenuLine, int16 charId, bool refreshFl) {
@@ -480,6 +551,7 @@ void EfhEngine::displayWindowAndStatusMenu(int16 charId, int16 windowId, int16 m
 int16 EfhEngine::displayStringInSmallWindowWithBorder(const Common::String &str, bool delayFl, int16 charId, int16 windowId, int16 menuId, int16 curMenuLine) {
 	debugC(3, kDebugEngine, "displayStringInSmallWindowWithBorder %s %s %d %d %d %d", str.c_str(), delayFl ? "True" : "False", charId, windowId, menuId, curMenuLine);
 
+	sayText(str, kNoRestriction);
 	int16 retVal = 0;
 
 	for (uint counter = 0; counter < 2; ++counter) {
@@ -514,6 +586,7 @@ int16 EfhEngine::handleStatusMenu(int16 gameMode, int16 charId) {
 	int16 curMenuLine = -1;
 	bool selectionDoneFl = false;
 	bool var2 = false;
+	_selectedMenuBox = 0;
 
 	saveAnimImageSetId();
 
@@ -554,6 +627,7 @@ int16 EfhEngine::handleStatusMenu(int16 gameMode, int16 charId) {
 					break;
 				case Common::KEYCODE_ESCAPE:
 				case Common::KEYCODE_l:
+					stopTextToSpeech();
 					windowId = kEfhMenuLeave;
 					input = Common::KEYCODE_RETURN;
 					break;
@@ -573,6 +647,10 @@ int16 EfhEngine::handleStatusMenu(int16 gameMode, int16 charId) {
 					debugC(9, kDebugEngine, "handleStatusMenu - unhandled keys");
 					break;
 				}
+
+				if (input == Common::KEYCODE_RETURN) {
+					_selectedMenuBox = -1;
+				}
 			} else if (_menuDepth == 1) {
 				// in the sub-menus, only a list of selectable items is displayed
 				if (input >= Common::KEYCODE_a && input <= Common::KEYCODE_z) {
@@ -592,6 +670,7 @@ int16 EfhEngine::handleStatusMenu(int16 gameMode, int16 charId) {
 					if (menuId >= kEfhMenuLeave)
 						selectionDoneFl = true;
 					else {
+						_sayMenu = true;
 						_menuDepth = 1;
 						curMenuLine = 0;
 					}
@@ -600,6 +679,7 @@ int16 EfhEngine::handleStatusMenu(int16 gameMode, int16 charId) {
 						_menuDepth = 0;
 						curMenuLine = -1;
 						menuId = kEfhMenuInvalid;
+						_sayMenu = true;
 						prepareStatusMenu(windowId, menuId, curMenuLine, charId, true);
 					} else {
 						selectedLine = curMenuLine;
@@ -611,6 +691,8 @@ int16 EfhEngine::handleStatusMenu(int16 gameMode, int16 charId) {
 				_menuDepth = 0;
 				curMenuLine = -1;
 				menuId = kEfhMenuInvalid;
+				stopTextToSpeech();
+				_sayMenu = true;
 				prepareStatusMenu(windowId, menuId, curMenuLine, charId, true);
 				break;
 			case Common::KEYCODE_2:
@@ -667,6 +749,7 @@ int16 EfhEngine::handleStatusMenu(int16 gameMode, int16 charId) {
 		case kEfhMenuEquip:
 			objectId = _menuStatItemArr[selectedLine];
 			itemId = _npcBuf[charId]._inventory[objectId]._ref; // CHECKME: Useless?
+			sayText(_items[itemId]._name, kNoRestriction, Common::TextToSpeechManager::INTERRUPT);
 			tryToggleEquipped(charId, objectId, windowId, menuId, curMenuLine);
 			if (gameMode == 2) {
 				restoreAnimImageSetId();
@@ -677,6 +760,7 @@ int16 EfhEngine::handleStatusMenu(int16 gameMode, int16 charId) {
 		case kEfhMenuUse:
 			objectId = _menuStatItemArr[selectedLine];
 			itemId = _npcBuf[charId]._inventory[objectId]._ref;
+			sayText(_items[itemId]._name, kNoRestriction, Common::TextToSpeechManager::INTERRUPT);
 			if (gameMode == 2) {
 				restoreAnimImageSetId();
 				_statusMenuActive = false;
@@ -693,6 +777,8 @@ int16 EfhEngine::handleStatusMenu(int16 gameMode, int16 charId) {
 		case kEfhMenuGive:
 			objectId = _menuStatItemArr[selectedLine];
 			itemId = _npcBuf[charId]._inventory[objectId]._ref;
+			sayText(_items[itemId]._name, kNoRestriction, Common::TextToSpeechManager::INTERRUPT);
+			_initiatedTalkByMenu = true;
 			if (hasObjectEquipped(charId, objectId) && isItemCursed(itemId)) {
 				displayStringInSmallWindowWithBorder("The item is cursed!  IT IS EVIL!!!!!!!!", true, charId, windowId, menuId, curMenuLine);
 			} else {
@@ -700,6 +786,7 @@ int16 EfhEngine::handleStatusMenu(int16 gameMode, int16 charId) {
 					displayStringInSmallWindowWithBorder("Item is Equipped!  Give anyway?", false, charId, windowId, menuId, curMenuLine);
 					if (!getValidationFromUser())
 						validationFl = false;
+					stopTextToSpeech();
 					displayWindowAndStatusMenu(charId, windowId, menuId, curMenuLine);
 				}
 				if (validationFl) {
@@ -719,6 +806,7 @@ int16 EfhEngine::handleStatusMenu(int16 gameMode, int16 charId) {
 		case kEfhMenuTrade:
 			objectId = _menuStatItemArr[selectedLine];
 			itemId = _npcBuf[charId]._inventory[objectId]._ref;
+			sayText(_items[itemId]._name, kNoRestriction, Common::TextToSpeechManager::INTERRUPT);
 			if (hasObjectEquipped(charId, objectId) && isItemCursed(itemId)) {
 				displayStringInSmallWindowWithBorder("The item is cursed!  IT IS EVIL!!!!!!!!", true, charId, windowId, menuId, curMenuLine);
 				break;
@@ -728,6 +816,7 @@ int16 EfhEngine::handleStatusMenu(int16 gameMode, int16 charId) {
 				displayStringInSmallWindowWithBorder("Item is Equipped!  Trade anyway?", false, charId, windowId, menuId, curMenuLine);
 				if (!getValidationFromUser())
 					validationFl = false;
+				stopTextToSpeech();
 				displayWindowAndStatusMenu(charId, windowId, menuId, curMenuLine);
 			}
 
@@ -738,6 +827,7 @@ int16 EfhEngine::handleStatusMenu(int16 gameMode, int16 charId) {
 					if (_teamChar[2]._id != -1) {
 						displayStringInSmallWindowWithBorder("Who will you give the item to?", false, charId, windowId, menuId, curMenuLine);
 						destCharId = selectOtherCharFromTeam();
+						stopTextToSpeech();
 						var2 = false;
 					} else if (_teamChar[1]._id == -1) {
 						destCharId = 0x1A;
@@ -752,6 +842,7 @@ int16 EfhEngine::handleStatusMenu(int16 gameMode, int16 charId) {
 
 					if (destCharId != 0x1A && destCharId != 0x1B) {
 						givenFl = giveItemTo(_teamChar[destCharId]._id, objectId, charId);
+						sayText(_npcBuf[_teamChar[destCharId]._id]._name, kNoRestriction);
 						if (!givenFl) {
 							displayStringInSmallWindowWithBorder("That character cannot carry anymore!", false, charId, windowId, menuId, curMenuLine);
 							getLastCharAfterAnimCount(_guessAnimationAmount);
@@ -781,6 +872,7 @@ int16 EfhEngine::handleStatusMenu(int16 gameMode, int16 charId) {
 		case kEfhMenuDrop:
 			objectId = _menuStatItemArr[selectedLine];
 			itemId = _npcBuf[charId]._inventory[objectId]._ref;
+			sayText(_items[itemId]._name, kNoRestriction, Common::TextToSpeechManager::INTERRUPT);
 			if (hasObjectEquipped(charId, objectId) && isItemCursed(itemId)) {
 				displayStringInSmallWindowWithBorder("The item is cursed!  IT IS EVIL!!!!!!!!", true, charId, windowId, menuId, curMenuLine);
 			} else {
@@ -791,6 +883,8 @@ int16 EfhEngine::handleStatusMenu(int16 gameMode, int16 charId) {
 					displayWindowAndStatusMenu(charId, windowId, menuId, curMenuLine);
 				}
 				if (validationFl) {
+					stopTextToSpeech();
+					_sayMenu = true;
 					removeObject(charId, objectId);
 					if (gameMode == 2) {
 						restoreAnimImageSetId();
@@ -808,7 +902,9 @@ int16 EfhEngine::handleStatusMenu(int16 gameMode, int16 charId) {
 		case kEfhMenuInfo:
 		case kEfhMenuPassive:
 		case kEfhMenuActive:
+			stopTextToSpeech();
 			objectId = _menuStatItemArr[selectedLine];
+			sayText(kSkillArray[objectId], kNoRestriction, Common::TextToSpeechManager::INTERRUPT);
 			if (gameMode == 2) {
 				displayStringInSmallWindowWithBorder("Not a Combat Option!", true, charId, windowId, menuId, curMenuLine);
 			} else if (handleInteractionText(_mapPosX, _mapPosY, charId, objectId, 4, -1)) {
@@ -823,6 +919,7 @@ int16 EfhEngine::handleStatusMenu(int16 gameMode, int16 charId) {
 		if (menuId != kEfhMenuLeave) {
 			selectionDoneFl = false;
 			_menuDepth = 0;
+			_sayMenu = true;
 			menuId = kEfhMenuInvalid;
 			selectedLine = -1;
 			curMenuLine = -1;
@@ -831,6 +928,7 @@ int16 EfhEngine::handleStatusMenu(int16 gameMode, int16 charId) {
 		if (menuId == kEfhMenuLeave) {
 			restoreAnimImageSetId();
 			_statusMenuActive = false;
+			_sayMenu = true;
 			return 0x7FFF;
 		}
 
diff --git a/engines/efh/metaengine.cpp b/engines/efh/metaengine.cpp
index 44811a59f34..14e9da986a5 100644
--- a/engines/efh/metaengine.cpp
+++ b/engines/efh/metaengine.cpp
@@ -30,10 +30,31 @@
 #include "graphics/thumbnail.h"
 #include "graphics/surface.h"
 
+#include "efh/detection.h"
 #include "efh/efh.h"
 
 namespace Efh {
 
+#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 EfhEngine::getFeatures() const {
 	return _gameDescription->flags;
 }
@@ -67,6 +88,12 @@ public:
 		return "efh";
 	}
 
+#ifdef USE_TTS
+	const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override {
+		return Efh::optionsList;
+	}
+#endif
+
 	Common::Error createInstance(OSystem *syst, Engine **engine, const ADGameDescription *gd) const override;
 	bool hasFeature(MetaEngineFeature f) const override;
 
diff --git a/engines/efh/script.cpp b/engines/efh/script.cpp
index 895e620267c..ea22d99a137 100644
--- a/engines/efh/script.cpp
+++ b/engines/efh/script.cpp
@@ -483,6 +483,7 @@ int16 EfhEngine::script_parse(Common::String stringBuffer, int16 posX, int16 pos
 			_teamChar[teamSlot]._id = joiningNpcId;
 		}
 		refreshTeamSize();
+		_sayLowStatusScreen = true;
 	}
 
 	return retVal;
diff --git a/engines/efh/utils.cpp b/engines/efh/utils.cpp
index 33ed35bc7ac..78dd45b68d6 100644
--- a/engines/efh/utils.cpp
+++ b/engines/efh/utils.cpp
@@ -134,7 +134,7 @@ int16 EfhEngine::getRandom(int16 maxVal) {
 	return 1 + _rnd->getRandomNumber(maxVal - 1);
 }
 
-Common::KeyCode EfhEngine::getLastCharAfterAnimCount(int16 delay) {
+Common::KeyCode EfhEngine::getLastCharAfterAnimCount(int16 delay, bool waitForTTS) {
 	debugC(1, kDebugUtils, "getLastCharAfterAnimCount %d", delay);
 	if (delay == 0)
 		return Common::KEYCODE_INVALID;
@@ -143,7 +143,8 @@ Common::KeyCode EfhEngine::getLastCharAfterAnimCount(int16 delay) {
 	_customAction = kActionNone;
 
 	uint32 lastMs = _system->getMillis();
-	while (delay > 0 && lastChar == Common::KEYCODE_INVALID && _customAction == kActionNone && !shouldQuit()) {
+	Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+	while ((delay > 0 || (waitForTTS && ttsMan && ttsMan->isSpeaking())) && lastChar == Common::KEYCODE_INVALID && _customAction == kActionNone && !shouldQuit()) {
 		_system->delayMillis(20);
 		uint32 newMs = _system->getMillis();
 
@@ -156,6 +157,10 @@ Common::KeyCode EfhEngine::getLastCharAfterAnimCount(int16 delay) {
 		lastChar = handleAndMapInput(false);
 	}
 
+	if (waitForTTS) {
+		stopTextToSpeech();
+	}
+
 	return lastChar;
 }
 
@@ -168,7 +173,8 @@ Common::KeyCode EfhEngine::getInput(int16 delay) {
 	Common::KeyCode retVal = Common::KEYCODE_INVALID;
 
 	uint32 lastMs = _system->getMillis();
-	while (delay > 0 && !shouldQuit()) {
+	Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+	while ((delay > 0 || (ttsMan && ttsMan->isSpeaking())) && !shouldQuit()) {
 		_system->delayMillis(20);
 		uint32 newMs = _system->getMillis();
 




More information about the Scummvm-git-logs mailing list